mirror of
https://github.com/eerimoq/moblin.git
synced 2026-07-04 15:06:48 +00:00
Swift 6 prep.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
|
||||
extension AVAudioPCMBuffer {
|
||||
final func makeSampleBuffer(_ presentationTimeStamp: CMTime) -> CMSampleBuffer? {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import CoreMedia
|
||||
|
||||
extension CMSampleBuffer {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import AVKit
|
||||
import MapKit
|
||||
import Network
|
||||
@@ -659,8 +659,8 @@ class RgbColor: Codable, Equatable {
|
||||
.opacity
|
||||
}
|
||||
|
||||
static let white = RgbColor(red: 255, green: 255, blue: 255)
|
||||
static let black = RgbColor(red: 0, green: 0, blue: 0)
|
||||
nonisolated(unsafe) static let white = RgbColor(red: 255, green: 255, blue: 255)
|
||||
nonisolated(unsafe) static let black = RgbColor(red: 0, green: 0, blue: 0)
|
||||
|
||||
var red: Int = 0
|
||||
var green: Int = 0
|
||||
|
||||
@@ -37,7 +37,7 @@ protocol SampleBufferReceiverDelegate: AnyObject {
|
||||
|
||||
private let lockQueue = DispatchQueue(label: "com.eerimoq.Moblin.SampleBufferReceiver")
|
||||
|
||||
class SampleBufferReceiver {
|
||||
class SampleBufferReceiver: @unchecked Sendable {
|
||||
private var listenerFd: Int32 = -1
|
||||
weak var delegate: (any SampleBufferReceiverDelegate)?
|
||||
private var formatDescription: CMVideoFormatDescription?
|
||||
|
||||
@@ -150,8 +150,8 @@ protocol VideoEncoderDelegate: AnyObject {
|
||||
func videoEncoderOutputSampleBuffer(_ sampleBuffer: CMSampleBuffer)
|
||||
}
|
||||
|
||||
class VideoEncoder {
|
||||
weak var delegate: VideoEncoderDelegate?
|
||||
class VideoEncoder: @unchecked Sendable {
|
||||
weak var delegate: (any VideoEncoderDelegate)?
|
||||
private var session: VTCompressionSession?
|
||||
private var formatDescription: CMFormatDescription? {
|
||||
didSet {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import SwiftUI
|
||||
import WatchConnectivity
|
||||
|
||||
private class Cache {
|
||||
private class Cache: @unchecked Sendable {
|
||||
private var cache: [URL: Image] = [:]
|
||||
private var waiters: [URL: [(Image) -> Void]] = [:]
|
||||
private var waitingForResponse = false
|
||||
|
||||
@@ -77,7 +77,7 @@ struct WatchChatPost: Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
class WatchModel: NSObject, ObservableObject {
|
||||
class WatchModel: NSObject, ObservableObject, @unchecked Sendable {
|
||||
let chat = Chat()
|
||||
let preview = Preview()
|
||||
let control = Control()
|
||||
@@ -526,9 +526,10 @@ extension WatchModel: WCSessionDelegate {
|
||||
) {}
|
||||
|
||||
func session(_: WCSession, didReceiveMessage message: [String: Any]) {
|
||||
guard let (type, data) = WatchMessageToWatch.unpack(message) else {
|
||||
guard let (type, value) = WatchMessageToWatch.unpack(message) else {
|
||||
return
|
||||
}
|
||||
nonisolated(unsafe) let data = value
|
||||
DispatchQueue.main.async {
|
||||
self.numberOfMessagesReceived += 1
|
||||
do {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// Created by Krister Berntsen on 09/06/2025.
|
||||
//
|
||||
import BlackSharkLib
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
import Foundation
|
||||
|
||||
private let blackSharkCoolerDeviceDispatchQueue =
|
||||
@@ -25,9 +25,9 @@ enum BlackSharkCoolerDeviceState {
|
||||
|
||||
private let blackSharkCoolerServiceId = CBUUID(string: BlackSharkLib.getServiceUUID().uuidString)
|
||||
|
||||
let blackSharkCoolerScanner = BluetoothScanner(serviceIds: [])
|
||||
nonisolated(unsafe) let blackSharkCoolerScanner = BluetoothScanner(serviceIds: [])
|
||||
|
||||
class BlackSharkCoolerDevice: NSObject {
|
||||
class BlackSharkCoolerDevice: NSObject, @unchecked Sendable {
|
||||
private var state: BlackSharkCoolerDeviceState = .disconnected
|
||||
private var centralManager: CBCentralManager?
|
||||
private var peripheral: CBPeripheral?
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Based on https://github.com/rbaron/catprinter
|
||||
// MIT License
|
||||
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import Collections
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
import CoreImage
|
||||
import Foundation
|
||||
|
||||
@@ -74,7 +74,7 @@ private let catPrinterServices = [
|
||||
CBUUID(string: "0000af30-0000-1000-8000-00805f9b34fb"),
|
||||
]
|
||||
|
||||
let catPrinterScanner = BluetoothScanner(serviceIds: catPrinterServices)
|
||||
nonisolated(unsafe) let catPrinterScanner = BluetoothScanner(serviceIds: catPrinterServices)
|
||||
|
||||
private let printCharacteristicId = CBUUID(string: "AE01")
|
||||
private let notifyCharacteristicId = CBUUID(string: "AE02")
|
||||
@@ -86,7 +86,7 @@ private struct PrintJob {
|
||||
let printMode: CatPrinterPrintMode
|
||||
}
|
||||
|
||||
class CatPrinter: NSObject {
|
||||
class CatPrinter: NSObject, @unchecked Sendable {
|
||||
private var state: CatPrinterState = .disconnected
|
||||
private var centralManager: CBCentralManager?
|
||||
private var peripheral: CBPeripheral?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
import Foundation
|
||||
|
||||
// The actual values do not matter.
|
||||
|
||||
@@ -10,7 +10,7 @@ struct DjiDiscoveredDevice {
|
||||
}
|
||||
|
||||
class DjiDeviceScanner: NSObject, ObservableObject {
|
||||
static let shared = DjiDeviceScanner()
|
||||
nonisolated(unsafe) static let shared = DjiDeviceScanner()
|
||||
@Published var discoveredDevices: [DjiDiscoveredDevice] = []
|
||||
private var centralManager: CBCentralManager?
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ class Emote {
|
||||
}
|
||||
}
|
||||
|
||||
class Emotes {
|
||||
class Emotes: @unchecked Sendable {
|
||||
private var emotes: [String: Emote] = [:]
|
||||
private var task: Task<Void, any Error>?
|
||||
private var ready: Bool = false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Collections
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
import CryptoKit
|
||||
|
||||
private let vehicleServiceUuid = CBUUID(string: "00000211-b2d1-43f0-9b88-960cebf8b91e")
|
||||
|
||||
@@ -3,7 +3,7 @@ import Foundation
|
||||
|
||||
private let dispatchQueue = DispatchQueue(label: "com.eerimoq.workout-device")
|
||||
|
||||
let workoutDeviceScanner = BluetoothScanner(serviceIds: [
|
||||
nonisolated(unsafe) let workoutDeviceScanner = BluetoothScanner(serviceIds: [
|
||||
workoutDeviceHeartRateServiceId,
|
||||
workoutDeviceCyclingPowerServiceId,
|
||||
workoutDeviceRunningServiceId,
|
||||
@@ -23,7 +23,7 @@ enum WorkoutDeviceState {
|
||||
case connected
|
||||
}
|
||||
|
||||
class WorkoutDevice: NSObject {
|
||||
class WorkoutDevice: NSObject, @unchecked Sendable {
|
||||
private var state: WorkoutDeviceState = .disconnected
|
||||
private var centralManager: CBCentralManager?
|
||||
private var peripheral: CBPeripheral?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Collections
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
import Foundation
|
||||
|
||||
let workoutDeviceCyclingPowerServiceId = CBUUID(string: "1818")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
|
||||
let workoutDeviceHeartRateServiceId = CBUUID(string: "180D")
|
||||
let workoutDeviceHeartRateMeasurementCharacteristicId = CBUUID(string: "2A37")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import CoreBluetooth
|
||||
@preconcurrency import CoreBluetooth
|
||||
|
||||
let workoutDeviceRunningServiceId = CBUUID(string: "1814")
|
||||
let workoutDeviceRunningMeasurementCharacteristicId = CBUUID(string: "2A53")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import AppIntents
|
||||
@preconcurrency import AppIntents
|
||||
import IntentsUI
|
||||
|
||||
final class MoblinShortcuts: AppShortcutsProvider {
|
||||
static var shortcutTileColor = ShortcutTileColor.navy
|
||||
static let shortcutTileColor = ShortcutTileColor.navy
|
||||
|
||||
static var appShortcuts: [AppShortcut] = [
|
||||
static let appShortcuts: [AppShortcut] = [
|
||||
AppShortcut(intent: MuteIntent(), phrases: [
|
||||
"\(.applicationName), mute",
|
||||
],
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AppIntents
|
||||
|
||||
struct MuteIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Mute"
|
||||
static var description: IntentDescription? = IntentDescription("Mutes audio.")
|
||||
static var openAppWhenRun: Bool = false
|
||||
static let title: LocalizedStringResource = "Mute"
|
||||
static let description: IntentDescription? = IntentDescription("Mutes audio.")
|
||||
static let openAppWhenRun: Bool = false
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AppIntents
|
||||
|
||||
struct SnapshotIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Take snapshot"
|
||||
static var description: IntentDescription? = IntentDescription("Take a snapshot.")
|
||||
static var openAppWhenRun: Bool = false
|
||||
static let title: LocalizedStringResource = "Take snapshot"
|
||||
static let description: IntentDescription? = IntentDescription("Take a snapshot.")
|
||||
static let openAppWhenRun: Bool = false
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import AppIntents
|
||||
|
||||
struct UnmuteIntent: AppIntent {
|
||||
static var title: LocalizedStringResource = "Unmute"
|
||||
static var description: IntentDescription? = IntentDescription("Unmutes audio.")
|
||||
static var openAppWhenRun: Bool = false
|
||||
static let title: LocalizedStringResource = "Unmute"
|
||||
static let description: IntentDescription? = IntentDescription("Unmutes audio.")
|
||||
static let openAppWhenRun: Bool = false
|
||||
|
||||
@MainActor
|
||||
func perform() async throws -> some IntentResult {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
|
||||
protocol AudioEncoderDelegate: AnyObject {
|
||||
func audioEncoderOutputFormat(_ format: AVAudioFormat)
|
||||
func audioEncoderOutputBuffer(_ buffer: AVAudioCompressedBuffer, _ presentationTimeStamp: CMTime)
|
||||
}
|
||||
|
||||
class AudioEncoder {
|
||||
weak var delegate: AudioEncoderDelegate?
|
||||
class AudioEncoder: @unchecked Sendable {
|
||||
weak var delegate: (any AudioEncoderDelegate)?
|
||||
private var isRunning = false
|
||||
private let lockQueue: DispatchQueue
|
||||
private var ringBuffer: AudioEncoderRingBuffer?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import CoreAudio
|
||||
|
||||
final class AudioEncoderRingBuffer {
|
||||
|
||||
@@ -6,7 +6,7 @@ struct VTSessionProperty {
|
||||
let value: AnyObject
|
||||
}
|
||||
|
||||
struct VTSessionPropertyKey {
|
||||
struct VTSessionPropertyKey: @unchecked Sendable {
|
||||
static let profileLevel = VTSessionPropertyKey(value: kVTCompressionPropertyKey_ProfileLevel)
|
||||
static let h264EntropyMode = VTSessionPropertyKey(value: kVTCompressionPropertyKey_H264EntropyMode)
|
||||
static let colorPrimaries = VTSessionPropertyKey(value: kVTCompressionPropertyKey_ColorPrimaries)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import VideoToolbox
|
||||
|
||||
protocol VideoDecoderDelegate: AnyObject {
|
||||
func videoDecoderOutputSampleBuffer(_ codec: VideoDecoder, _ sampleBuffer: CMSampleBuffer)
|
||||
}
|
||||
|
||||
class VideoDecoder {
|
||||
class VideoDecoder: @unchecked Sendable {
|
||||
private var isRunning = false
|
||||
private let lockQueue: DispatchQueue
|
||||
private var formatDescription: CMFormatDescription?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import VideoToolbox
|
||||
|
||||
var numberOfFailedEncodings = 0
|
||||
nonisolated(unsafe) var numberOfFailedEncodings = 0
|
||||
|
||||
protocol VideoEncoderDelegate: AnyObject {
|
||||
func videoEncoderOutputFormat(_ encoder: VideoEncoder, _ formatDescription: CMFormatDescription)
|
||||
@@ -14,7 +14,7 @@ protocol VideoEncoderControlDelegate: AnyObject {
|
||||
func videoEncoderControlResolutionChanged(_ encoder: VideoEncoder, resolution: CGSize)
|
||||
}
|
||||
|
||||
class VideoEncoder {
|
||||
class VideoEncoder: @unchecked Sendable {
|
||||
var settings: Atomic<VideoEncoderSettings> = .init(.init()) {
|
||||
didSet {
|
||||
lockQueue.async {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Foundation
|
||||
import VideoToolbox
|
||||
|
||||
var videoEncoderDataRateLimitFactor = 1.2
|
||||
nonisolated(unsafe) var videoEncoderDataRateLimitFactor = 1.2
|
||||
|
||||
func createDataRateLimits(bitRate: UInt32) -> CFArray {
|
||||
var bitRate = Double(bitRate)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency public import AVFoundation
|
||||
import Foundation
|
||||
|
||||
extension AVCaptureColorSpace: @retroactive CustomStringConvertible {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import Foundation
|
||||
|
||||
extension AVCaptureDevice.Format {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import Foundation
|
||||
|
||||
extension AVFrameRateRange {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import CoreAudio
|
||||
public import CoreAudio
|
||||
import Foundation
|
||||
|
||||
extension AudioStreamBasicDescription: @retroactive Equatable {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
|
||||
extension CMVideoFormatDescription {
|
||||
static func create(imageBuffer: CVImageBuffer) -> CMVideoFormatDescription? {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
|
||||
enum FlvAacPacketType: UInt8 {
|
||||
case seq = 0
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import Collections
|
||||
import CoreMedia
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import Collections
|
||||
import CoreAudio
|
||||
|
||||
@@ -66,7 +66,7 @@ private class TalkbackPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioUnitAttachParams {
|
||||
struct AudioUnitAttachParams: @unchecked Sendable {
|
||||
let device: AVCaptureDevice?
|
||||
let builtinDelay: Double
|
||||
let bufferedAudio: UUID?
|
||||
@@ -89,7 +89,7 @@ func makeChannelMap(
|
||||
return channelMap.map { NSNumber(value: $0) }
|
||||
}
|
||||
|
||||
final class AudioUnit: NSObject {
|
||||
final class AudioUnit: NSObject, @unchecked Sendable {
|
||||
let encoder = AudioEncoder(lockQueue: processorPipelineQueue)
|
||||
private var input: AVCaptureDeviceInput?
|
||||
private var output: AVCaptureAudioDataOutput?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import AVFoundation
|
||||
@preconcurrency import AVFoundation
|
||||
import Collections
|
||||
|
||||
private let deltaLimit = 0.03
|
||||
|
||||
@@ -9,7 +9,7 @@ protocol MacScreenCaptureDelegate: AnyObject {
|
||||
}
|
||||
|
||||
@available(macCatalyst 18.2, *)
|
||||
class MacScreenCapture: NSObject {
|
||||
class MacScreenCapture: NSObject, @unchecked Sendable {
|
||||
static let shared = MacScreenCapture()
|
||||
weak var delegate: (any MacScreenCaptureDelegate)?
|
||||
private var stream: SCStream?
|
||||
|
||||
@@ -14,7 +14,7 @@ protocol RecorderDelegate: AnyObject {
|
||||
|
||||
private let fileWriterQueue = DispatchQueue(label: "com.eerimoq.recorder")
|
||||
|
||||
class Recorder: NSObject {
|
||||
final class Recorder: NSObject, @unchecked Sendable {
|
||||
private var replay = false
|
||||
private var audioOutputSettings: [String: Any] = [:]
|
||||
private var videoOutputSettings: [String: Any] = [:]
|
||||
@@ -41,6 +41,10 @@ class Recorder: NSObject {
|
||||
audioOutputSettings: [String: Any],
|
||||
videoOutputSettings: [String: Any]
|
||||
) {
|
||||
nonisolated(unsafe)
|
||||
let audioOutputSettings = audioOutputSettings
|
||||
nonisolated(unsafe)
|
||||
let videoOutputSettings = videoOutputSettings
|
||||
processorPipelineQueue.async {
|
||||
self.startRunningInternal(
|
||||
url: url,
|
||||
|
||||
@@ -45,7 +45,7 @@ class PreviewView: UIView {
|
||||
layer.videoGravity = videoGravity
|
||||
}
|
||||
|
||||
func enqueue(_ sampleBuffer: CMSampleBuffer?, isFirstAfterAttach: Bool) {
|
||||
nonisolated func enqueue(_ sampleBuffer: CMSampleBuffer?, isFirstAfterAttach: Bool) {
|
||||
guard let sampleBuffer else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ enum VideoEffectDetectionsMode {
|
||||
case interval(UUID?, Double)
|
||||
}
|
||||
|
||||
class VideoEffect: NSObject {
|
||||
class VideoEffect: NSObject, @unchecked Sendable {
|
||||
var effects: [VideoEffect] = []
|
||||
|
||||
func needsFaceDetections(_: Double) -> VideoEffectDetectionsMode {
|
||||
|
||||
@@ -4,7 +4,7 @@ import CoreImage
|
||||
import MetalPetal
|
||||
import SwiftUI
|
||||
import VideoToolbox
|
||||
import Vision
|
||||
@preconcurrency import Vision
|
||||
|
||||
private let deltaLimit = 0.03
|
||||
|
||||
@@ -15,7 +15,7 @@ struct DetectionJob {
|
||||
let detectText: Bool
|
||||
}
|
||||
|
||||
struct VideoUnitAttachParams {
|
||||
struct VideoUnitAttachParams: @unchecked Sendable {
|
||||
let devices: CaptureDevices
|
||||
let builtinDelay: Double
|
||||
let cameraPreviewLayer: AVCaptureVideoPreviewLayer
|
||||
@@ -81,8 +81,8 @@ struct CaptureDevices {
|
||||
var devices: [CaptureDevice]
|
||||
}
|
||||
|
||||
var pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||
var allowVideoRangePixelFormat = false
|
||||
nonisolated(unsafe) var pixelFormatType = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|
||||
nonisolated(unsafe) var allowVideoRangePixelFormat = false
|
||||
private let detectionsQueue = DispatchQueue(
|
||||
label: "com.haishinkit.HaishinKit.Detections",
|
||||
attributes: .concurrent
|
||||
@@ -115,7 +115,7 @@ struct Detections {
|
||||
let text: [TextDetection]
|
||||
}
|
||||
|
||||
private class DetectionsCompletion {
|
||||
private class DetectionsCompletion: @unchecked Sendable {
|
||||
let sequenceNumber: UInt64
|
||||
let sampleBuffer: CMSampleBuffer
|
||||
let isFirstAfterAttach: Bool
|
||||
@@ -159,7 +159,7 @@ private func makeCaptureSession() -> AVCaptureSession {
|
||||
return session
|
||||
}
|
||||
|
||||
final class VideoUnit: NSObject {
|
||||
final class VideoUnit: NSObject, @unchecked Sendable {
|
||||
static let defaultFrameRate: Float64 = 30
|
||||
private var device: AVCaptureDevice?
|
||||
private var captureSessionDevices: [CaptureSessionDevice] = []
|
||||
@@ -1339,7 +1339,9 @@ final class VideoUnit: NSObject {
|
||||
}
|
||||
|
||||
private func detectObjects(detectionJob: DetectionJob, completion: DetectionsCompletion) {
|
||||
nonisolated(unsafe)
|
||||
var faceDetections: [VNFaceObservation] = []
|
||||
nonisolated(unsafe)
|
||||
var textDetections: [TextDetection] = []
|
||||
var faceLandmarksRequest: VNDetectFaceLandmarksRequest?
|
||||
var textRequest: VNRecognizeTextRequest?
|
||||
|
||||
Reference in New Issue
Block a user