forked from MeloNX/MeloNX
Compare commits
2 Commits
ceab2f0ac8
...
fd4bc5ccbb
Author | SHA1 | Date | |
---|---|---|---|
fd4bc5ccbb | |||
ce66d4091d |
@ -0,0 +1,592 @@
|
||||
//
|
||||
// Joystick4FTG.swift
|
||||
// MeloNX
|
||||
//
|
||||
// Created by RZH on 2025/3/7.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public struct Joystick4FTG: View {
|
||||
//先声明数据
|
||||
@State var iscool: Bool? = nil
|
||||
@State private var joystickPositionText = ""
|
||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||
var dragDiameter: CGFloat {
|
||||
var selfs = CGFloat(160)
|
||||
selfs *= controllerScale
|
||||
if UIDevice.current.systemName.contains("iPadOS") {
|
||||
return selfs * 1.2
|
||||
}
|
||||
return selfs
|
||||
}
|
||||
public var body: some View {
|
||||
VStack{
|
||||
// 新增坐标显示(在摇杆上方)
|
||||
//旧的FTGJoystick:
|
||||
/*FTGJoystickBuilder { relativePosition in
|
||||
|
||||
let scaledX = Double(relativePosition.x )
|
||||
let scaledY = Double(relativePosition.y )
|
||||
|
||||
//print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||
|
||||
*/
|
||||
//新版八向摇杆:
|
||||
/*
|
||||
EnhancedEightWayJoystick{relativePosition in
|
||||
let scaledX = Double(relativePosition.x * 10)
|
||||
let scaledY = Double(relativePosition.y * 10)
|
||||
let formattedText = String(format: "X: %5.2f, Y: %5.2f", scaledX, scaledY)
|
||||
print("Joystick Position: \(formattedText)")
|
||||
// 更新显示文本
|
||||
//self.joystickPositionText = formattedText
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
//新版自由摇杆:
|
||||
FreeRoamJoystick { position in
|
||||
let scaledX = Double(position.x * 10)
|
||||
let scaledY = Double(position.y * 10)
|
||||
//print("Joystick Position: \(formattedText)")
|
||||
// 更新显示文本
|
||||
//self.joystickPositionText = formattedText
|
||||
|
||||
*/
|
||||
//九宫格摇杆:
|
||||
/*
|
||||
NineGridJoystick { position in
|
||||
let scaledX = Double(position.x * 10)
|
||||
let scaledY = Double(position.y * 10)
|
||||
*/
|
||||
|
||||
//新的动态九宫格摇杆
|
||||
DynamicNineGridJoystick{ position in
|
||||
let scaledX = Double(position.x * 10)
|
||||
let scaledY = Double(position.y * 10)
|
||||
|
||||
if iscool != nil {
|
||||
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: scaledX, y: scaledY)
|
||||
} else {
|
||||
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: scaledX, y: scaledY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct EnhancedEightWayJoystick: View {
|
||||
var onDirectionChanged: ((x: Int, y: Int)) -> Void
|
||||
|
||||
@State private var currentDirection: (x: Int, y: Int) = (0, 0)
|
||||
private let radius: CGFloat = 50
|
||||
private let centerPoint = CGPoint(x: 50, y: 50)
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// 触摸区域
|
||||
Circle()
|
||||
.fill(Color.gray.opacity(0.2))
|
||||
.frame(width: 200, height: 200)
|
||||
.contentShape(Circle())
|
||||
|
||||
// 方向指示器
|
||||
Circle()
|
||||
.fill(Color.white.opacity(0.7))
|
||||
.frame(width: 75, height: 75)
|
||||
.offset(x: CGFloat(currentDirection.x), y: CGFloat(currentDirection.y))
|
||||
}
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { gesture in
|
||||
let location = gesture.location
|
||||
//添加使用Geometry计算的dxdy坐标
|
||||
let viewFrame = geometry.frame(in: .local)
|
||||
let center = CGPoint(
|
||||
x: viewFrame.midX,
|
||||
y: viewFrame.midY
|
||||
)
|
||||
|
||||
//let dx = location.x - centerPoint.x
|
||||
//let dy = location.y - centerPoint.y
|
||||
let dx = location.x - center.x
|
||||
let dy = location.y - center.y
|
||||
|
||||
// 计算极坐标
|
||||
let radians = atan2(dy, dx)
|
||||
var degrees = radians * 180 / .pi
|
||||
if degrees < 0 { degrees += 360 }
|
||||
|
||||
// 获取调整后的方向
|
||||
let direction = get45DegreeDirection(from: degrees)
|
||||
|
||||
// 仅在方向变化时更新
|
||||
if direction.x != currentDirection.x || direction.y != currentDirection.y {
|
||||
currentDirection = direction
|
||||
onDirectionChanged(direction)
|
||||
}
|
||||
}
|
||||
.onEnded { _ in
|
||||
currentDirection = (0, 0)
|
||||
onDirectionChanged((0, 0))
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 45度均匀分布方向判断
|
||||
//调转Y轴
|
||||
//调整scale到 168
|
||||
private func get45DegreeDirection(from degrees: CGFloat) -> (x: Int, y: Int) {
|
||||
switch degrees {
|
||||
// 右 (337.5°-22.5°)
|
||||
case 337.5...360, 0..<22.5:
|
||||
return (50, 0)
|
||||
|
||||
// 右上 (22.5°-67.5°)
|
||||
case 22.5..<67.5:
|
||||
//return (50, -50)
|
||||
return (50, 50)
|
||||
// 上 (67.5°-112.5°)
|
||||
case 67.5..<112.5:
|
||||
//return (0, -50)
|
||||
return (0, 50)
|
||||
// 左上 (112.5°-157.5°)
|
||||
case 112.5..<157.5:
|
||||
//return (-50, -50)
|
||||
return (-50, 50)
|
||||
// 左 (157.5°-202.5°)
|
||||
case 157.5..<202.5:
|
||||
return (-50, 0)
|
||||
|
||||
// 左下 (202.5°-247.5°)
|
||||
case 202.5..<247.5:
|
||||
//return (-50, 50)
|
||||
return (-50, -50)
|
||||
// 下 (247.5°-292.5°)
|
||||
case 247.5..<292.5:
|
||||
//return (0, 50)
|
||||
return (0, -50)
|
||||
// 右下 (292.5°-337.5°)
|
||||
case 292.5..<337.5:
|
||||
//return (50, 50)
|
||||
return (50, -50)
|
||||
default:
|
||||
return (0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct FreeRoamJoystick: View {
|
||||
var onPositionChanged: (CGPoint) -> Void
|
||||
|
||||
private let trackRadius: CGFloat = 90
|
||||
private let thumbRadius: CGFloat = 75
|
||||
@State private var isEightWay = 0 // 0: 自由模式,1: 八向模式
|
||||
@State private var position = CGPoint.zero
|
||||
@State private var viewCenter: CGPoint = .zero
|
||||
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
Spacer()
|
||||
// 摇杆背景(点击区域)
|
||||
Circle()
|
||||
.stroke(Color.gray.opacity(0.5), lineWidth: 4)
|
||||
.frame(width: trackRadius*2, height: trackRadius*2)
|
||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
||||
.contentShape(Circle()) // 关键修改点1:设置点击区域形状
|
||||
// 摇杆头
|
||||
let maxMoveRadius = trackRadius - thumbRadius
|
||||
Circle()
|
||||
.fill(Color.white.opacity(0.4))
|
||||
.frame(width: thumbRadius*2, height: thumbRadius*2)
|
||||
//.offset(x: position.x, y: position.y)
|
||||
.offset(
|
||||
x: min(max(position.x, -maxMoveRadius), maxMoveRadius),
|
||||
y: min(max(position.y, -maxMoveRadius), maxMoveRadius)
|
||||
)
|
||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
||||
}
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0) // 关键修改点2:零距离触发
|
||||
.onChanged { gesture in
|
||||
let viewFrame = geometry.frame(in: .local)
|
||||
let touchPoint = gesture.location
|
||||
|
||||
// 计算相对中心坐标
|
||||
let center = CGPoint(
|
||||
x: viewFrame.midX,
|
||||
y: viewFrame.midY
|
||||
)
|
||||
let delta = CGPoint(
|
||||
x: touchPoint.x - center.x,
|
||||
y: touchPoint.y - center.y
|
||||
)
|
||||
|
||||
// 限制在圆形范围内
|
||||
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
|
||||
let clampedDelta = distance > trackRadius ? CGPoint(
|
||||
x: delta.x * trackRadius / distance,
|
||||
y: delta.y * trackRadius / distance
|
||||
) : delta
|
||||
var test_data = clampedDelta
|
||||
// 更新状态
|
||||
//尝试不同返回值
|
||||
if isEightWay == 1 {
|
||||
test_data = getEightDirection(for: test_data)
|
||||
}
|
||||
|
||||
//position = clampedDelta
|
||||
position = test_data
|
||||
//onPositionChanged(clampedDelta)
|
||||
onPositionChanged(test_data)
|
||||
}
|
||||
.onEnded { _ in
|
||||
|
||||
position = .zero
|
||||
onPositionChanged(.zero)
|
||||
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
viewCenter = CGPoint(
|
||||
x: geometry.size.width/2,
|
||||
y: geometry.size.height/2
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(width: trackRadius, height: trackRadius)
|
||||
//.frame(alignment: .bottomLeading)
|
||||
}
|
||||
private func getEightDirection(for delta: CGPoint) -> CGPoint {
|
||||
|
||||
|
||||
let angle = atan2(-delta.y, delta.x)
|
||||
let degrees = angle * 180 / .pi
|
||||
let normalized = degrees < 0 ? degrees + 360 : degrees
|
||||
|
||||
switch normalized {
|
||||
case 337.5...360, 0..<22.5: // 右
|
||||
return CGPoint(x: trackRadius, y: 0)
|
||||
case 22.5..<67.5: // 右上
|
||||
return CGPoint(x: trackRadius, y: -trackRadius)
|
||||
case 67.5..<112.5: // 上
|
||||
return CGPoint(x: 0, y: -trackRadius)
|
||||
case 112.5..<157.5: // 左上
|
||||
return CGPoint(x: -trackRadius, y: -trackRadius)
|
||||
case 157.5..<202.5: // 左
|
||||
return CGPoint(x: -trackRadius, y: 0)
|
||||
case 202.5..<247.5: // 左下
|
||||
return CGPoint(x: -trackRadius, y: trackRadius)
|
||||
case 247.5..<292.5: // 下
|
||||
return CGPoint(x: 0, y: trackRadius)
|
||||
case 292.5..<337.5: // 右下
|
||||
return CGPoint(x: trackRadius, y: trackRadius)
|
||||
default:
|
||||
return .zero
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
struct NineGridJoystick: View {
|
||||
var onDirectionChanged: (CGPoint) -> Void
|
||||
|
||||
private let trackRadius: CGFloat = 90
|
||||
private let thumbRadius: CGFloat = 35
|
||||
@State private var position = CGPoint.zero
|
||||
@State private var activeColor = Color.white.opacity(0.4)
|
||||
|
||||
// 九宫格配置参数
|
||||
private var gridThreshold: CGFloat {
|
||||
trackRadius / 3 // 56
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// 九宫格背景
|
||||
gridBackgroundView()
|
||||
|
||||
// 摇杆头
|
||||
Circle()
|
||||
.fill(activeColor)
|
||||
.frame(width: thumbRadius*2, height: thumbRadius*2)
|
||||
.offset(x: position.x, y: position.y)
|
||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
||||
//.animation(.spring(), value: position)
|
||||
}
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { gesture in
|
||||
let center = CGPoint(x: geometry.size.width/2, y: geometry.size.height/2)
|
||||
let delta = CGPoint(
|
||||
x: gesture.location.x - center.x,
|
||||
y: gesture.location.y - center.y
|
||||
)
|
||||
|
||||
let direction = getGridDirection(for: delta)
|
||||
updatePosition(to: direction)
|
||||
}
|
||||
.onEnded { _ in
|
||||
resetPosition()
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(width: trackRadius*2, height: trackRadius*2)
|
||||
}
|
||||
|
||||
// MARK: - 九宫格视图
|
||||
private func gridBackgroundView() -> some View {
|
||||
ZStack {
|
||||
// 背景网格线
|
||||
Path { path in
|
||||
// 垂直中线
|
||||
path.move(to: CGPoint(x: trackRadius - gridThreshold, y: 0))
|
||||
path.addLine(to: CGPoint(x: trackRadius - gridThreshold, y: trackRadius*2))
|
||||
path.move(to: CGPoint(x: trackRadius + gridThreshold, y: 0))
|
||||
path.addLine(to: CGPoint(x: trackRadius + gridThreshold, y: trackRadius*2))
|
||||
// 水平中线
|
||||
path.move(to: CGPoint(x: 0, y: trackRadius - gridThreshold))
|
||||
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius - gridThreshold))
|
||||
path.move(to: CGPoint(x: 0, y: trackRadius + gridThreshold))
|
||||
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius + gridThreshold))
|
||||
}
|
||||
.stroke(Color.gray.opacity(0.3), lineWidth: 2)
|
||||
|
||||
// 中心区域高亮
|
||||
Rectangle()
|
||||
.fill(Color.gray.opacity(0.1))
|
||||
.frame(width: gridThreshold*2, height: gridThreshold*2)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 方向判断逻辑
|
||||
private func getGridDirection(for delta: CGPoint) -> CGPoint {
|
||||
let xVal = delta.x
|
||||
let yVal = delta.y
|
||||
|
||||
switch (xVal, yVal) {
|
||||
case (..<(-gridThreshold), ..<(-gridThreshold)): // 左上
|
||||
return CGPoint(x: -trackRadius, y: -trackRadius)
|
||||
case ((-gridThreshold)...gridThreshold, ..<(-gridThreshold)): // 上
|
||||
return CGPoint(x: 0, y: -trackRadius)
|
||||
case (gridThreshold..., ..<(-gridThreshold)): // 右上
|
||||
return CGPoint(x: trackRadius, y: -trackRadius)
|
||||
case (..<(-gridThreshold), (-gridThreshold)...gridThreshold): // 左
|
||||
return CGPoint(x: -trackRadius, y: 0)
|
||||
case ((-gridThreshold)...gridThreshold, (-gridThreshold)...gridThreshold): // 中
|
||||
return .zero
|
||||
case (gridThreshold..., (-gridThreshold)...gridThreshold): // 右
|
||||
return CGPoint(x: trackRadius, y: 0)
|
||||
case (..<(-gridThreshold), gridThreshold...): // 左下
|
||||
return CGPoint(x: -trackRadius, y: trackRadius)
|
||||
case ((-gridThreshold)...gridThreshold, gridThreshold...): // 下
|
||||
return CGPoint(x: 0, y: trackRadius)
|
||||
case (gridThreshold..., gridThreshold...): // 右下
|
||||
return CGPoint(x: trackRadius, y: trackRadius)
|
||||
default:
|
||||
return .zero
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 状态更新
|
||||
private func updatePosition(to direction: CGPoint) {
|
||||
//withAnimation(.spring()) {
|
||||
position = direction
|
||||
activeColor = direction == .zero ?
|
||||
Color.white.opacity(0.4) :
|
||||
Color.blue.opacity(0.6)
|
||||
onDirectionChanged(direction)
|
||||
//}
|
||||
}
|
||||
|
||||
private func resetPosition() {
|
||||
//withAnimation(.spring()) {
|
||||
position = .zero
|
||||
activeColor = Color.white.opacity(0.4)
|
||||
onDirectionChanged(.zero)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct DynamicNineGridJoystick: View {
|
||||
var onDirectionChanged: (CGPoint) -> Void
|
||||
|
||||
private let trackRadius: CGFloat = 100
|
||||
private let thumbRadius: CGFloat = 35
|
||||
@State private var position = CGPoint.zero
|
||||
@State private var activeColor = Color.white.opacity(0.4)
|
||||
@State private var lastDirection: CGPoint = .zero
|
||||
|
||||
// 九宫格配置
|
||||
private var gridThreshold: CGFloat { trackRadius / 3 }
|
||||
private let hapticGenerator = UIImpactFeedbackGenerator(style: .soft)
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// 九宫格背景
|
||||
gridBackgroundView()
|
||||
|
||||
// 摇杆头
|
||||
Circle()
|
||||
.fill(activeColor)
|
||||
.frame(width: thumbRadius*2, height: thumbRadius*2)
|
||||
.offset(x: position.x, y: position.y)
|
||||
.position(x: geometry.size.width/2, y: geometry.size.height/2)
|
||||
//.animation(.spring(), value: position)
|
||||
}
|
||||
// 关键修改点:设置全区域点击识别
|
||||
.contentShape(Rectangle()) // 使用矩形识别区域覆盖整个GeometryReader
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { gesture in
|
||||
handleGestureChange(
|
||||
location: gesture.location,
|
||||
center: CGPoint(x: geometry.size.width/2,
|
||||
y: geometry.size.height/2)
|
||||
)
|
||||
}
|
||||
.onEnded { _ in
|
||||
resetPosition()
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(width: trackRadius*2, height: trackRadius*2)
|
||||
.onAppear {
|
||||
hapticGenerator.prepare()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 手势处理
|
||||
private func handleGestureChange(location: CGPoint, center: CGPoint) {
|
||||
let delta = CGPoint(
|
||||
x: location.x - center.x,
|
||||
y: location.y - center.y
|
||||
)
|
||||
|
||||
let currentDirection = getGridDirection(for: delta)
|
||||
|
||||
// 仅在新方向变化时触发
|
||||
if currentDirection != lastDirection {
|
||||
updatePosition(to: currentDirection)
|
||||
triggerHapticFeedback()
|
||||
lastDirection = currentDirection
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 九宫格方向判断(优化版)
|
||||
private func getGridDirection(for delta: CGPoint) -> CGPoint {
|
||||
let xVal = delta.x
|
||||
let yVal = delta.y
|
||||
|
||||
// 定义九宫格边界
|
||||
let isLeft = xVal < -gridThreshold
|
||||
let isRight = xVal > gridThreshold
|
||||
let isUp = yVal < -gridThreshold
|
||||
let isDown = yVal > gridThreshold
|
||||
|
||||
switch (isLeft, isRight, isUp, isDown) {
|
||||
case (true, false, true, false): // 左上
|
||||
return CGPoint(x: -trackRadius, y: -trackRadius)
|
||||
case (false, false, true, false): // 上
|
||||
return CGPoint(x: 0, y: -trackRadius)
|
||||
case (false, true, true, false): // 右上
|
||||
return CGPoint(x: trackRadius, y: -trackRadius)
|
||||
case (true, false, false, false): // 左
|
||||
return CGPoint(x: -trackRadius, y: 0)
|
||||
case (false, false, false, false): // 中
|
||||
return .zero
|
||||
case (false, true, false, false): // 右
|
||||
return CGPoint(x: trackRadius, y: 0)
|
||||
case (true, false, false, true): // 左下
|
||||
return CGPoint(x: -trackRadius, y: trackRadius)
|
||||
case (false, false, false, true): // 下
|
||||
return CGPoint(x: 0, y: trackRadius)
|
||||
case (false, true, false, true): // 右下
|
||||
return CGPoint(x: trackRadius, y: trackRadius)
|
||||
default: // 处理对角线滑动时的中间状态
|
||||
return calculateEdgeCase(delta: delta)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理边缘滑动状态
|
||||
private func calculateEdgeCase(delta: CGPoint) -> CGPoint {
|
||||
let angle = atan2(delta.y, delta.x) * 180 / .pi
|
||||
let normalized = angle < 0 ? angle + 360 : angle
|
||||
|
||||
switch normalized {
|
||||
case 45..<135: // 上象限
|
||||
return CGPoint(x: 0, y: -trackRadius)
|
||||
case 135..<225: // 左象限
|
||||
return CGPoint(x: -trackRadius, y: 0)
|
||||
case 225..<315: // 下象限
|
||||
return CGPoint(x: 0, y: trackRadius)
|
||||
default: // 右象限
|
||||
return CGPoint(x: trackRadius, y: 0)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 视觉与反馈更新
|
||||
private func updatePosition(to direction: CGPoint) {
|
||||
//withAnimation(.interactiveSpring()) {
|
||||
|
||||
position = direction
|
||||
activeColor = direction == .zero ?
|
||||
Color.white.opacity(0.4) :
|
||||
Color.blue.opacity(0.6)
|
||||
onDirectionChanged(direction)
|
||||
|
||||
}
|
||||
|
||||
private func resetPosition() {
|
||||
//withAnimation(.spring()) {
|
||||
position = .zero
|
||||
activeColor = Color.white.opacity(0.4)
|
||||
lastDirection = .zero
|
||||
onDirectionChanged(.zero)
|
||||
//}
|
||||
}
|
||||
|
||||
private func triggerHapticFeedback() {
|
||||
guard position != .zero else { return }
|
||||
hapticGenerator.impactOccurred(intensity: 0.7)
|
||||
}
|
||||
|
||||
// MARK: - 九宫格视图(优化版)
|
||||
private func gridBackgroundView() -> some View {
|
||||
ZStack {
|
||||
// 网格线
|
||||
Path { path in
|
||||
let offsets = [-gridThreshold, 0, gridThreshold]
|
||||
for offset in offsets {
|
||||
path.move(to: CGPoint(x: trackRadius + offset, y: 0))
|
||||
path.addLine(to: CGPoint(x: trackRadius + offset, y: trackRadius*2))
|
||||
path.move(to: CGPoint(x: 0, y: trackRadius + offset))
|
||||
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius + offset))
|
||||
}
|
||||
}
|
||||
.stroke(Color.gray.opacity(0.3), lineWidth: 2)
|
||||
|
||||
// 中心区域
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color.gray.opacity(0.1))
|
||||
.frame(width: gridThreshold*2, height: gridThreshold*2)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user