mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '5e5f19e6e98298c95fefe548ec0de31752af9c55'
This commit is contained in:
commit
a0032f4fee
@ -2062,7 +2062,7 @@
|
|||||||
|
|
||||||
"StickerPack.Share" = "Share";
|
"StickerPack.Share" = "Share";
|
||||||
"StickerPack.Send" = "Send Sticker";
|
"StickerPack.Send" = "Send Sticker";
|
||||||
"StickerPack.Select" = "Select Sticker";
|
"StickerPack.AddSticker" = "Add Sticker";
|
||||||
|
|
||||||
"StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker";
|
"StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker";
|
||||||
"StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers";
|
"StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers";
|
||||||
|
@ -57,6 +57,37 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
.iPodTouch5,
|
.iPodTouch5,
|
||||||
.iPodTouch6,
|
.iPodTouch6,
|
||||||
.iPodTouch7,
|
.iPodTouch7,
|
||||||
|
.iPhone,
|
||||||
|
.iPhone3G,
|
||||||
|
.iPhone3GS,
|
||||||
|
.iPhone4,
|
||||||
|
.iPhone4S,
|
||||||
|
.iPhone5,
|
||||||
|
.iPhone5C,
|
||||||
|
.iPhone5S,
|
||||||
|
.iPhone6,
|
||||||
|
.iPhone6Plus,
|
||||||
|
.iPhone6S,
|
||||||
|
.iPhone6SPlus,
|
||||||
|
.iPhoneSE,
|
||||||
|
.iPhone7,
|
||||||
|
.iPhone7Plus,
|
||||||
|
.iPhone8,
|
||||||
|
.iPhone8Plus,
|
||||||
|
.iPhoneX,
|
||||||
|
.iPhoneXS,
|
||||||
|
.iPhoneXR,
|
||||||
|
.iPhone11,
|
||||||
|
.iPhone11Pro,
|
||||||
|
.iPhone11ProMax,
|
||||||
|
.iPhone12,
|
||||||
|
.iPhone12Mini,
|
||||||
|
.iPhone12Pro,
|
||||||
|
.iPhone12ProMax,
|
||||||
|
.iPhone13,
|
||||||
|
.iPhone13Mini,
|
||||||
|
.iPhone13Pro,
|
||||||
|
.iPhone13ProMax,
|
||||||
.iPhone14,
|
.iPhone14,
|
||||||
.iPhone14Plus,
|
.iPhone14Plus,
|
||||||
.iPhone14Pro,
|
.iPhone14Pro,
|
||||||
@ -72,9 +103,40 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
case iPodTouch6
|
case iPodTouch6
|
||||||
case iPodTouch7
|
case iPodTouch7
|
||||||
|
|
||||||
|
case iPhone
|
||||||
|
case iPhone3G
|
||||||
|
case iPhone3GS
|
||||||
|
|
||||||
|
case iPhone4
|
||||||
|
case iPhone4S
|
||||||
|
|
||||||
|
case iPhone5
|
||||||
|
case iPhone5C
|
||||||
|
case iPhone5S
|
||||||
|
|
||||||
|
case iPhone6
|
||||||
|
case iPhone6Plus
|
||||||
|
case iPhone6S
|
||||||
|
case iPhone6SPlus
|
||||||
|
|
||||||
|
case iPhoneSE
|
||||||
|
|
||||||
|
case iPhone7
|
||||||
|
case iPhone7Plus
|
||||||
|
case iPhone8
|
||||||
|
case iPhone8Plus
|
||||||
|
|
||||||
case iPhoneX
|
case iPhoneX
|
||||||
case iPhoneXS
|
case iPhoneXS
|
||||||
|
case iPhoneXSMax
|
||||||
|
case iPhoneXR
|
||||||
|
|
||||||
|
case iPhone11
|
||||||
|
case iPhone11Pro
|
||||||
|
case iPhone11ProMax
|
||||||
|
|
||||||
|
case iPhoneSE2ndGen
|
||||||
|
|
||||||
case iPhone12
|
case iPhone12
|
||||||
case iPhone12Mini
|
case iPhone12Mini
|
||||||
case iPhone12Pro
|
case iPhone12Pro
|
||||||
@ -85,6 +147,8 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
case iPhone13Pro
|
case iPhone13Pro
|
||||||
case iPhone13ProMax
|
case iPhone13ProMax
|
||||||
|
|
||||||
|
case iPhoneSE3rdGen
|
||||||
|
|
||||||
case iPhone14
|
case iPhone14
|
||||||
case iPhone14Plus
|
case iPhone14Plus
|
||||||
case iPhone14Pro
|
case iPhone14Pro
|
||||||
@ -108,10 +172,56 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
return ["iPod7,1"]
|
return ["iPod7,1"]
|
||||||
case .iPodTouch7:
|
case .iPodTouch7:
|
||||||
return ["iPod9,1"]
|
return ["iPod9,1"]
|
||||||
|
case .iPhone:
|
||||||
|
return ["iPhone1,1"]
|
||||||
|
case .iPhone3G:
|
||||||
|
return ["iPhone1,2"]
|
||||||
|
case .iPhone3GS:
|
||||||
|
return ["iPhone2,1"]
|
||||||
|
case .iPhone4:
|
||||||
|
return ["iPhone3,1", "iPhone3,2", "iPhone3,3"]
|
||||||
|
case .iPhone4S:
|
||||||
|
return ["iPhone4,1", "iPhone4,2", "iPhone4,3"]
|
||||||
|
case .iPhone5:
|
||||||
|
return ["iPhone5,1", "iPhone5,2"]
|
||||||
|
case .iPhone5C:
|
||||||
|
return ["iPhone5,3", "iPhone5,4"]
|
||||||
|
case .iPhone5S:
|
||||||
|
return ["iPhone6,1", "iPhone6,2"]
|
||||||
|
case .iPhone6:
|
||||||
|
return ["iPhone7,2"]
|
||||||
|
case .iPhone6Plus:
|
||||||
|
return ["iPhone7,1"]
|
||||||
|
case .iPhone6S:
|
||||||
|
return ["iPhone8,1"]
|
||||||
|
case .iPhone6SPlus:
|
||||||
|
return ["iPhone8,2"]
|
||||||
|
case .iPhoneSE:
|
||||||
|
return ["iPhone8,4"]
|
||||||
|
case .iPhone7:
|
||||||
|
return ["iPhone9,1", "iPhone9,3"]
|
||||||
|
case .iPhone7Plus:
|
||||||
|
return ["iPhone9,2", "iPhone9,4"]
|
||||||
|
case .iPhone8:
|
||||||
|
return ["iPhone10,1", "iPhone10,4"]
|
||||||
|
case .iPhone8Plus:
|
||||||
|
return ["iPhone10,2", "iPhone10,5"]
|
||||||
case .iPhoneX:
|
case .iPhoneX:
|
||||||
return ["iPhone11,2"]
|
return ["iPhone10,3", "iPhone10,6"]
|
||||||
case .iPhoneXS:
|
case .iPhoneXS:
|
||||||
|
return ["iPhone11,2"]
|
||||||
|
case .iPhoneXSMax:
|
||||||
return ["iPhone11,4", "iPhone11,6"]
|
return ["iPhone11,4", "iPhone11,6"]
|
||||||
|
case .iPhoneXR:
|
||||||
|
return ["iPhone11,8"]
|
||||||
|
case .iPhone11:
|
||||||
|
return ["iPhone12,1"]
|
||||||
|
case .iPhone11Pro:
|
||||||
|
return ["iPhone12,3"]
|
||||||
|
case .iPhone11ProMax:
|
||||||
|
return ["iPhone12,5"]
|
||||||
|
case .iPhoneSE2ndGen:
|
||||||
|
return ["iPhone12,8"]
|
||||||
case .iPhone12:
|
case .iPhone12:
|
||||||
return ["iPhone13,2"]
|
return ["iPhone13,2"]
|
||||||
case .iPhone12Mini:
|
case .iPhone12Mini:
|
||||||
@ -128,6 +238,8 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
return ["iPhone14,2"]
|
return ["iPhone14,2"]
|
||||||
case .iPhone13ProMax:
|
case .iPhone13ProMax:
|
||||||
return ["iPhone14,3"]
|
return ["iPhone14,3"]
|
||||||
|
case .iPhoneSE3rdGen:
|
||||||
|
return ["iPhone14,6"]
|
||||||
case .iPhone14:
|
case .iPhone14:
|
||||||
return ["iPhone14,7"]
|
return ["iPhone14,7"]
|
||||||
case .iPhone14Plus:
|
case .iPhone14Plus:
|
||||||
@ -157,10 +269,56 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
return "iPod touch 6G"
|
return "iPod touch 6G"
|
||||||
case .iPodTouch7:
|
case .iPodTouch7:
|
||||||
return "iPod touch 7G"
|
return "iPod touch 7G"
|
||||||
|
case .iPhone:
|
||||||
|
return "iPhone"
|
||||||
|
case .iPhone3G:
|
||||||
|
return "iPhone 3G"
|
||||||
|
case .iPhone3GS:
|
||||||
|
return "iPhone 3GS"
|
||||||
|
case .iPhone4:
|
||||||
|
return "iPhone 4"
|
||||||
|
case .iPhone4S:
|
||||||
|
return "iPhone 4S"
|
||||||
|
case .iPhone5:
|
||||||
|
return "iPhone 5"
|
||||||
|
case .iPhone5C:
|
||||||
|
return "iPhone 5C"
|
||||||
|
case .iPhone5S:
|
||||||
|
return "iPhone 5S"
|
||||||
|
case .iPhone6:
|
||||||
|
return "iPhone 6"
|
||||||
|
case .iPhone6Plus:
|
||||||
|
return "iPhone 6 Plus"
|
||||||
|
case .iPhone6S:
|
||||||
|
return "iPhone 6S"
|
||||||
|
case .iPhone6SPlus:
|
||||||
|
return "iPhone 6S Plus"
|
||||||
|
case .iPhoneSE:
|
||||||
|
return "iPhone SE"
|
||||||
|
case .iPhone7:
|
||||||
|
return "iPhone 7"
|
||||||
|
case .iPhone7Plus:
|
||||||
|
return "iPhone 7 Plus"
|
||||||
|
case .iPhone8:
|
||||||
|
return "iPhone 8"
|
||||||
|
case .iPhone8Plus:
|
||||||
|
return "iPhone 8 Plus"
|
||||||
case .iPhoneX:
|
case .iPhoneX:
|
||||||
return "iPhone X"
|
return "iPhone X"
|
||||||
case .iPhoneXS:
|
case .iPhoneXS:
|
||||||
return "iPhone XS"
|
return "iPhone XS"
|
||||||
|
case .iPhoneXSMax:
|
||||||
|
return "iPhone XS Max"
|
||||||
|
case .iPhoneXR:
|
||||||
|
return "iPhone XR"
|
||||||
|
case .iPhone11:
|
||||||
|
return "iPhone 11"
|
||||||
|
case .iPhone11Pro:
|
||||||
|
return "iPhone 11 Pro"
|
||||||
|
case .iPhone11ProMax:
|
||||||
|
return "iPhone 11 Pro Max"
|
||||||
|
case .iPhoneSE2ndGen:
|
||||||
|
return "iPhone SE (2nd gen)"
|
||||||
case .iPhone12:
|
case .iPhone12:
|
||||||
return "iPhone 12"
|
return "iPhone 12"
|
||||||
case .iPhone12Mini:
|
case .iPhone12Mini:
|
||||||
@ -177,6 +335,8 @@ enum DeviceModel: CaseIterable, Equatable {
|
|||||||
return "iPhone 13 Pro"
|
return "iPhone 13 Pro"
|
||||||
case .iPhone13ProMax:
|
case .iPhone13ProMax:
|
||||||
return "iPhone 13 Pro Max"
|
return "iPhone 13 Pro Max"
|
||||||
|
case .iPhoneSE3rdGen:
|
||||||
|
return "iPhone SE (3rd gen)"
|
||||||
case .iPhone14:
|
case .iPhone14:
|
||||||
return "iPhone 14"
|
return "iPhone 14"
|
||||||
case .iPhone14Plus:
|
case .iPhone14Plus:
|
||||||
|
@ -82,6 +82,16 @@ public final class DeviceAccess {
|
|||||||
return self.locationPromise.get()
|
return self.locationPromise.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static let cameraPromise = Promise<Bool?>(nil)
|
||||||
|
static var camera: Signal<Bool?, NoError> {
|
||||||
|
return self.cameraPromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let microphonePromise = Promise<Bool?>(nil)
|
||||||
|
static var microphone: Signal<Bool?, NoError> {
|
||||||
|
return self.microphonePromise.get()
|
||||||
|
}
|
||||||
|
|
||||||
public static func isMicrophoneAccessAuthorized() -> Bool? {
|
public static func isMicrophoneAccessAuthorized() -> Bool? {
|
||||||
return AVAudioSession.sharedInstance().recordPermission == .granted
|
return AVAudioSession.sharedInstance().recordPermission == .granted
|
||||||
}
|
}
|
||||||
@ -248,12 +258,72 @@ public final class DeviceAccess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
case .camera:
|
||||||
|
return Signal { subscriber in
|
||||||
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
||||||
|
switch status {
|
||||||
|
case .authorized:
|
||||||
|
subscriber.putNext(.allowed)
|
||||||
|
case .denied, .restricted:
|
||||||
|
subscriber.putNext(.denied)
|
||||||
|
case .notDetermined:
|
||||||
|
subscriber.putNext(.notDetermined)
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|> then(self.camera
|
||||||
|
|> mapToSignal { authorized -> Signal<AccessType, NoError> in
|
||||||
|
if let authorized = authorized {
|
||||||
|
return .single(authorized ? .allowed : .denied)
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
case .microphone:
|
||||||
|
return Signal { subscriber in
|
||||||
|
let status = AVCaptureDevice.authorizationStatus(for: .audio)
|
||||||
|
switch status {
|
||||||
|
case .authorized:
|
||||||
|
subscriber.putNext(.allowed)
|
||||||
|
case .denied, .restricted:
|
||||||
|
subscriber.putNext(.denied)
|
||||||
|
case .notDetermined:
|
||||||
|
subscriber.putNext(.notDetermined)
|
||||||
|
@unknown default:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
subscriber.putCompletion()
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|> then(self.microphone
|
||||||
|
|> mapToSignal { authorized -> Signal<AccessType, NoError> in
|
||||||
|
if let authorized = authorized {
|
||||||
|
return .single(authorized ? .allowed : .denied)
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return .single(.notDetermined)
|
return .single(.notDetermined)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func authorizeAccess(to subject: DeviceAccessSubject, onlyCheck: Bool = false, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: LocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) {
|
public static func authorizeAccess(
|
||||||
|
to subject: DeviceAccessSubject,
|
||||||
|
onlyCheck: Bool = false,
|
||||||
|
registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil,
|
||||||
|
requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil,
|
||||||
|
locationManager: LocationManager? = nil,
|
||||||
|
presentationData: PresentationData? = nil,
|
||||||
|
present: @escaping (ViewController, Any?) -> Void = { _, _ in },
|
||||||
|
openSettings: @escaping () -> Void = { },
|
||||||
|
displayNotificationFromBackground: @escaping (String) -> Void = { _ in },
|
||||||
|
_ completion: @escaping (Bool) -> Void = { _ in }) {
|
||||||
switch subject {
|
switch subject {
|
||||||
case let .camera(cameraSubject):
|
case let .camera(cameraSubject):
|
||||||
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
let status = AVCaptureDevice.authorizationStatus(for: .video)
|
||||||
@ -262,6 +332,7 @@ public final class DeviceAccess {
|
|||||||
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
|
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
completion(response)
|
completion(response)
|
||||||
|
self.cameraPromise.set(.single(response))
|
||||||
if !response, let presentationData = presentationData {
|
if !response, let presentationData = presentationData {
|
||||||
let text: String
|
let text: String
|
||||||
switch cameraSubject {
|
switch cameraSubject {
|
||||||
@ -331,6 +402,7 @@ public final class DeviceAccess {
|
|||||||
displayNotificationFromBackground(text)
|
displayNotificationFromBackground(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.microphonePromise.set(.single(granted))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -694,6 +694,8 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
if let selectedEntityView = self.selectedEntityView, let selectionView = selectedEntityView.selectionView {
|
if let selectedEntityView = self.selectedEntityView, let selectionView = selectedEntityView.selectionView {
|
||||||
if !self.hasBin {
|
if !self.hasBin {
|
||||||
selectionView.handlePan(gestureRecognizer)
|
selectionView.handlePan(gestureRecognizer)
|
||||||
|
} else if let stickerEntity = selectedEntityView.entity as? DrawingStickerEntity, case .dualVideoReference = stickerEntity.content {
|
||||||
|
selectionView.handlePan(gestureRecognizer)
|
||||||
} else {
|
} else {
|
||||||
var isTrappedInBin = false
|
var isTrappedInBin = false
|
||||||
let scale = 100.0 / selectedEntityView.bounds.size.width
|
let scale = 100.0 / selectedEntityView.bounds.size.width
|
||||||
|
@ -2930,7 +2930,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
|
|||||||
}
|
}
|
||||||
let images = imageItems as! [UIImage]
|
let images = imageItems as! [UIImage]
|
||||||
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
||||||
let entity = DrawingStickerEntity(content: .image(image, false))
|
let entity = DrawingStickerEntity(content: .image(image, .sticker))
|
||||||
strongSelf.node.insertEntity.invoke(entity)
|
strongSelf.node.insertEntity.invoke(entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,7 +289,7 @@ private final class StickerSelectionComponent: Component {
|
|||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
inputNodeInteraction: inputNodeInteraction,
|
inputNodeInteraction: inputNodeInteraction,
|
||||||
mode: mappedMode,
|
mode: mappedMode,
|
||||||
stickerActionTitle: presentationData.strings.StickerPack_Select,
|
stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
|
||||||
trendingGifsPromise: trendingGifsPromise,
|
trendingGifsPromise: trendingGifsPromise,
|
||||||
cancel: {
|
cancel: {
|
||||||
},
|
},
|
||||||
@ -585,7 +585,7 @@ public class StickerPickerScreen: ViewController {
|
|||||||
CTLineDraw(line, context)
|
CTLineDraw(line, context)
|
||||||
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||||
}) {
|
}) {
|
||||||
strongSelf.controller?.completion(.image(image, false))
|
strongSelf.controller?.completion(.image(image, .sticker))
|
||||||
}
|
}
|
||||||
strongSelf.controller?.dismiss(animated: true)
|
strongSelf.controller?.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
@ -261,6 +261,7 @@ func assetImage(asset: PHAsset, targetSize: CGSize, exact: Bool, deliveryMode: P
|
|||||||
|
|
||||||
if let info = info {
|
if let info = info {
|
||||||
if let cancelled = info[PHImageCancelledKey] as? Bool, cancelled {
|
if let cancelled = info[PHImageCancelledKey] as? Bool, cancelled {
|
||||||
|
subscriber.putCompletion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if let degradedValue = info[PHImageResultIsDegradedKey] as? Bool, degradedValue {
|
if let degradedValue = info[PHImageResultIsDegradedKey] as? Bool, degradedValue {
|
||||||
|
@ -125,23 +125,27 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
override init() {
|
override init() {
|
||||||
self.backgroundNode = ASImageNode()
|
self.backgroundNode = ASImageNode()
|
||||||
self.backgroundNode.contentMode = .scaleToFill
|
self.backgroundNode.contentMode = .scaleToFill
|
||||||
|
self.backgroundNode.isLayerBacked = true
|
||||||
|
|
||||||
self.imageNode = ImageNode()
|
self.imageNode = ImageNode()
|
||||||
self.imageNode.clipsToBounds = true
|
self.imageNode.clipsToBounds = true
|
||||||
self.imageNode.contentMode = .scaleAspectFill
|
self.imageNode.contentMode = .scaleAspectFill
|
||||||
self.imageNode.isLayerBacked = false
|
self.imageNode.isLayerBacked = true
|
||||||
self.imageNode.animateFirstTransition = false
|
self.imageNode.animateFirstTransition = false
|
||||||
|
|
||||||
self.gradientNode = ASImageNode()
|
self.gradientNode = ASImageNode()
|
||||||
self.gradientNode.displaysAsynchronously = false
|
self.gradientNode.displaysAsynchronously = false
|
||||||
self.gradientNode.displayWithoutProcessing = true
|
self.gradientNode.displayWithoutProcessing = true
|
||||||
self.gradientNode.image = maskImage
|
self.gradientNode.image = maskImage
|
||||||
|
self.gradientNode.isLayerBacked = true
|
||||||
|
|
||||||
self.typeIconNode = ASImageNode()
|
self.typeIconNode = ASImageNode()
|
||||||
self.typeIconNode.displaysAsynchronously = false
|
self.typeIconNode.displaysAsynchronously = false
|
||||||
self.typeIconNode.displayWithoutProcessing = true
|
self.typeIconNode.displayWithoutProcessing = true
|
||||||
|
self.typeIconNode.isLayerBacked = true
|
||||||
|
|
||||||
self.durationNode = ImmediateTextNode()
|
self.durationNode = ImmediateTextNode()
|
||||||
|
self.durationNode.isLayerBacked = true
|
||||||
self.draftNode = ImmediateTextNode()
|
self.draftNode = ImmediateTextNode()
|
||||||
|
|
||||||
self.activateAreaNode = AccessibilityAreaNode()
|
self.activateAreaNode = AccessibilityAreaNode()
|
||||||
@ -472,7 +476,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let originalSignal = assetImageSignal //assetImage(fetchResult: fetchResult, index: index, targetSize: targetSize, exact: false, synchronous: true)
|
let originalSignal = assetImageSignal
|
||||||
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
let imageSignal: Signal<UIImage?, NoError> = editedSignal
|
||||||
|> mapToSignal { result in
|
|> mapToSignal { result in
|
||||||
if let result = result {
|
if let result = result {
|
||||||
@ -519,18 +523,22 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
self.addSubnode(self.typeIconNode)
|
self.addSubnode(self.typeIconNode)
|
||||||
self.setNeedsLayout()
|
self.setNeedsLayout()
|
||||||
}
|
}
|
||||||
} else if asset.mediaType == .video {
|
}
|
||||||
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
|
|
||||||
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaSlomo")
|
if asset.mediaType == .video {
|
||||||
} else if asset.mediaSubtypes.contains(.videoTimelapse) {
|
if !asset.isFavorite {
|
||||||
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaTimelapse")
|
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
|
||||||
} else {
|
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaSlomo")
|
||||||
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaVideo")
|
} else if asset.mediaSubtypes.contains(.videoTimelapse) {
|
||||||
|
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaTimelapse")
|
||||||
|
} else {
|
||||||
|
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaVideo")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.durationNode.attributedText = NSAttributedString(string: stringForDuration(Int32(asset.duration)), font: Font.semibold(12.0), textColor: .white)
|
self.durationNode.attributedText = NSAttributedString(string: stringForDuration(Int32(asset.duration)), font: Font.semibold(12.0), textColor: .white)
|
||||||
|
|
||||||
if self.typeIconNode.supernode == nil {
|
if self.durationNode.supernode == nil {
|
||||||
self.addSubnode(self.gradientNode)
|
self.addSubnode(self.gradientNode)
|
||||||
self.addSubnode(self.typeIconNode)
|
self.addSubnode(self.typeIconNode)
|
||||||
self.addSubnode(self.durationNode)
|
self.addSubnode(self.durationNode)
|
||||||
@ -588,7 +596,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
let backgroundSize = CGSize(width: self.bounds.width, height: floorToScreenPixels(self.bounds.height / 9.0 * 16.0))
|
let backgroundSize = CGSize(width: self.bounds.width, height: floorToScreenPixels(self.bounds.height / 9.0 * 16.0))
|
||||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.bounds.height - backgroundSize.height) / 2.0)), size: backgroundSize)
|
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((self.bounds.height - backgroundSize.height) / 2.0)), size: backgroundSize)
|
||||||
self.imageNode.frame = self.bounds.insetBy(dx: -1.0 + UIScreenPixel, dy: -1.0 + UIScreenPixel)
|
self.imageNode.frame = self.bounds
|
||||||
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 36.0, width: self.bounds.width, height: 36.0)
|
self.gradientNode.frame = CGRect(x: 0.0, y: self.bounds.height - 36.0, width: self.bounds.width, height: 36.0)
|
||||||
self.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
|
self.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
|
||||||
self.activateAreaNode.frame = self.bounds
|
self.activateAreaNode.frame = self.bounds
|
||||||
@ -619,11 +627,11 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||||||
|
|
||||||
func transitionView(snapshot: Bool) -> UIView {
|
func transitionView(snapshot: Bool) -> UIView {
|
||||||
if snapshot {
|
if snapshot {
|
||||||
let view = self.imageNode.view.snapshotContentTree(unhide: true, keepTransform: true)!
|
let view = self.imageNode.layer.snapshotContentTreeAsView(unhide: true)!
|
||||||
view.frame = self.convert(self.bounds, to: nil)
|
view.frame = self.convert(self.bounds, to: nil)
|
||||||
return view
|
return view
|
||||||
} else {
|
} else {
|
||||||
return self.imageNode.view
|
return self.view
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
|
|
||||||
let throttledContentOffsetSignal = self.fastScrollContentOffset.get()
|
let throttledContentOffsetSignal = self.fastScrollContentOffset.get()
|
||||||
|> mapToThrottled { next -> Signal<CGPoint, NoError> in
|
|> mapToThrottled { next -> Signal<CGPoint, NoError> in
|
||||||
return .single(next) |> then(.complete() |> delay(0.02, queue: Queue.concurrentDefaultQueue()))
|
return .single(next) |> then(.complete() |> delay(0.05, queue: Queue.concurrentDefaultQueue()))
|
||||||
}
|
}
|
||||||
self.fastScrollDisposable = (throttledContentOffsetSignal
|
self.fastScrollDisposable = (throttledContentOffsetSignal
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] contentOffset in
|
|> deliverOnMainQueue).start(next: { [weak self] contentOffset in
|
||||||
@ -1266,7 +1266,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||||||
itemHeight = floor(itemWidth * 1.227)
|
itemHeight = floor(itemWidth * 1.227)
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: itemHeight * 3.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemHeight), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
|
let preloadSize: CGFloat = itemHeight// * 3.0
|
||||||
|
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: bounds.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: preloadSize, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemHeight), fillWidth: true, lineSpacing: itemSpacing, itemSpacing: itemSpacing), cutout: cameraRect), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -464,13 +464,16 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||||||
var menuItems: [ContextMenuItem] = []
|
var menuItems: [ContextMenuItem] = []
|
||||||
if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||||
if strongSelf.sendSticker != nil {
|
if strongSelf.sendSticker != nil {
|
||||||
|
var iconName: String
|
||||||
let actionTitle: String
|
let actionTitle: String
|
||||||
if let title = strongSelf.controller?.actionTitle {
|
if let title = strongSelf.controller?.actionTitle {
|
||||||
actionTitle = title
|
actionTitle = title
|
||||||
|
iconName = "Chat/Context Menu/Add"
|
||||||
} else {
|
} else {
|
||||||
actionTitle = strongSelf.presentationData.strings.StickerPack_Send
|
actionTitle = strongSelf.presentationData.strings.StickerPack_Send
|
||||||
|
iconName = "Chat/Context Menu/Resend"
|
||||||
}
|
}
|
||||||
menuItems.append(.action(ContextMenuActionItem(text: actionTitle, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Resend"), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
menuItems.append(.action(ContextMenuActionItem(text: actionTitle, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: iconName), color: theme.contextMenu.primaryColor) }, action: { _, f in
|
||||||
if let strongSelf = self, let peekController = strongSelf.peekController {
|
if let strongSelf = self, let peekController = strongSelf.peekController {
|
||||||
if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
|
if let animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
|
||||||
let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode.view, animationNode.bounds)
|
let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode.view, animationNode.bounds)
|
||||||
|
@ -71,12 +71,16 @@ swift_library(
|
|||||||
"//submodules/Components/BlurredBackgroundComponent",
|
"//submodules/Components/BlurredBackgroundComponent",
|
||||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||||
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||||
|
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
"//submodules/TooltipUI",
|
"//submodules/TooltipUI",
|
||||||
"//submodules/TelegramUI/Components/MediaEditor",
|
"//submodules/TelegramUI/Components/MediaEditor",
|
||||||
"//submodules/Components/MetalImageView",
|
"//submodules/Components/MetalImageView",
|
||||||
"//submodules/TelegramUI/Components/CameraButtonComponent",
|
"//submodules/TelegramUI/Components/CameraButtonComponent",
|
||||||
"//submodules/Utils/VolumeButtons",
|
"//submodules/Utils/VolumeButtons",
|
||||||
"//submodules/TelegramNotices",
|
"//submodules/TelegramNotices",
|
||||||
|
"//submodules/DeviceAccess",
|
||||||
|
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -21,6 +21,7 @@ import BundleIconComponent
|
|||||||
import CameraButtonComponent
|
import CameraButtonComponent
|
||||||
import VolumeButtons
|
import VolumeButtons
|
||||||
import TelegramNotices
|
import TelegramNotices
|
||||||
|
import DeviceAccess
|
||||||
|
|
||||||
let videoRedColor = UIColor(rgb: 0xff3b30)
|
let videoRedColor = UIColor(rgb: 0xff3b30)
|
||||||
|
|
||||||
@ -87,6 +88,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
|
|
||||||
let context: AccountContext
|
let context: AccountContext
|
||||||
let cameraState: CameraState
|
let cameraState: CameraState
|
||||||
|
let cameraAuthorizationStatus: AccessType
|
||||||
|
let microphoneAuthorizationStatus: AccessType
|
||||||
let hasAppeared: Bool
|
let hasAppeared: Bool
|
||||||
let isVisible: Bool
|
let isVisible: Bool
|
||||||
let panelWidth: CGFloat
|
let panelWidth: CGFloat
|
||||||
@ -101,6 +104,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
init(
|
init(
|
||||||
context: AccountContext,
|
context: AccountContext,
|
||||||
cameraState: CameraState,
|
cameraState: CameraState,
|
||||||
|
cameraAuthorizationStatus: AccessType,
|
||||||
|
microphoneAuthorizationStatus: AccessType,
|
||||||
hasAppeared: Bool,
|
hasAppeared: Bool,
|
||||||
isVisible: Bool,
|
isVisible: Bool,
|
||||||
panelWidth: CGFloat,
|
panelWidth: CGFloat,
|
||||||
@ -114,6 +119,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
) {
|
) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.cameraState = cameraState
|
self.cameraState = cameraState
|
||||||
|
self.cameraAuthorizationStatus = cameraAuthorizationStatus
|
||||||
|
self.microphoneAuthorizationStatus = microphoneAuthorizationStatus
|
||||||
self.hasAppeared = hasAppeared
|
self.hasAppeared = hasAppeared
|
||||||
self.isVisible = isVisible
|
self.isVisible = isVisible
|
||||||
self.panelWidth = panelWidth
|
self.panelWidth = panelWidth
|
||||||
@ -133,6 +140,12 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
if lhs.cameraState != rhs.cameraState {
|
if lhs.cameraState != rhs.cameraState {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.cameraAuthorizationStatus != rhs.cameraAuthorizationStatus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.microphoneAuthorizationStatus != rhs.microphoneAuthorizationStatus {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.hasAppeared != rhs.hasAppeared {
|
if lhs.hasAppeared != rhs.hasAppeared {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -166,11 +179,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined
|
|
||||||
private var microphoneAuthorizationStatus: AVAuthorizationStatus = .notDetermined
|
|
||||||
private var galleryAuthorizationStatus: PHAuthorizationStatus = .notDetermined
|
|
||||||
|
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let present: (ViewController) -> Void
|
private let present: (ViewController) -> Void
|
||||||
private let completion: ActionSlot<Signal<CameraScreen.Result, NoError>>
|
private let completion: ActionSlot<Signal<CameraScreen.Result, NoError>>
|
||||||
@ -180,7 +189,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
private let getController: () -> CameraScreen?
|
private let getController: () -> CameraScreen?
|
||||||
|
|
||||||
private var resultDisposable = MetaDisposable()
|
private var resultDisposable = MetaDisposable()
|
||||||
|
|
||||||
private var mediaAssetsContext: MediaAssetsContext?
|
private var mediaAssetsContext: MediaAssetsContext?
|
||||||
fileprivate var lastGalleryAsset: PHAsset?
|
fileprivate var lastGalleryAsset: PHAsset?
|
||||||
private var lastGalleryAssetsDisposable: Disposable?
|
private var lastGalleryAssetsDisposable: Disposable?
|
||||||
@ -502,6 +511,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var body: Body {
|
static var body: Body {
|
||||||
|
let placeholder = Child(PlaceholderComponent.self)
|
||||||
let cancelButton = Child(CameraButton.self)
|
let cancelButton = Child(CameraButton.self)
|
||||||
let captureControls = Child(CaptureControlsComponent.self)
|
let captureControls = Child(CaptureControlsComponent.self)
|
||||||
let zoomControl = Child(ZoomComponent.self)
|
let zoomControl = Child(ZoomComponent.self)
|
||||||
@ -535,12 +545,51 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let panelWidth = min(component.panelWidth, 185.0)
|
let panelWidth = min(component.panelWidth, 185.0)
|
||||||
|
|
||||||
var controlsBottomInset: CGFloat = 0.0
|
var controlsBottomInset: CGFloat = 0.0
|
||||||
|
let previewHeight = floorToScreenPixels(availableSize.width * 1.77778)
|
||||||
if !isTablet {
|
if !isTablet {
|
||||||
let previewHeight = floorToScreenPixels(availableSize.width * 1.77778)
|
|
||||||
if availableSize.height < previewHeight + 30.0 {
|
if availableSize.height < previewHeight + 30.0 {
|
||||||
controlsBottomInset = -48.0
|
controlsBottomInset = -48.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasAllRequiredAccess: Bool
|
||||||
|
switch component.cameraAuthorizationStatus {
|
||||||
|
case .notDetermined:
|
||||||
|
hasAllRequiredAccess = true
|
||||||
|
case .allowed:
|
||||||
|
switch component.microphoneAuthorizationStatus {
|
||||||
|
case .notDetermined:
|
||||||
|
hasAllRequiredAccess = true
|
||||||
|
case .allowed:
|
||||||
|
hasAllRequiredAccess = true
|
||||||
|
default:
|
||||||
|
hasAllRequiredAccess = false
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
hasAllRequiredAccess = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasAllRequiredAccess {
|
||||||
|
let accountContext = component.context
|
||||||
|
let placeholder = placeholder.update(
|
||||||
|
component: PlaceholderComponent(
|
||||||
|
context: component.context,
|
||||||
|
mode: .denied,
|
||||||
|
action: {
|
||||||
|
accountContext.sharedContext.applicationBindings.openSettings()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: availableSize.width, height: previewHeight),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
context.add(placeholder
|
||||||
|
.position(CGPoint(x: availableSize.width / 2.0, y: environment.safeInsets.top + previewHeight / 2.0))
|
||||||
|
.clipsToBounds(true)
|
||||||
|
.cornerRadius(11.0)
|
||||||
|
.appear(.default(alpha: true))
|
||||||
|
.disappear(.default(alpha: true))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if case .holding = component.cameraState.recording {
|
if case .holding = component.cameraState.recording {
|
||||||
|
|
||||||
@ -593,7 +642,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let captureControls = captureControls.update(
|
let captureControls = captureControls.update(
|
||||||
component: CaptureControlsComponent(
|
component: CaptureControlsComponent(
|
||||||
isTablet: isTablet,
|
isTablet: isTablet,
|
||||||
hasAppeared: component.hasAppeared,
|
hasAppeared: component.hasAppeared && hasAllRequiredAccess,
|
||||||
|
hasAccess: hasAllRequiredAccess,
|
||||||
shutterState: shutterState,
|
shutterState: shutterState,
|
||||||
lastGalleryAsset: state.lastGalleryAsset,
|
lastGalleryAsset: state.lastGalleryAsset,
|
||||||
tag: captureControlsTag,
|
tag: captureControlsTag,
|
||||||
@ -739,51 +789,53 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let flashButton = flashButton.update(
|
if hasAllRequiredAccess {
|
||||||
component: CameraButton(
|
let flashButton = flashButton.update(
|
||||||
content: flashContentComponent,
|
|
||||||
action: { [weak state] in
|
|
||||||
if let state {
|
|
||||||
state.toggleFlashMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).tagged(flashButtonTag),
|
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
|
||||||
transition: .immediate
|
|
||||||
)
|
|
||||||
context.add(flashButton
|
|
||||||
.position(CGPoint(x: isTablet ? availableSize.width - smallPanelWidth / 2.0 : availableSize.width - topControlInset - flashButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + flashButton.size.height / 2.0))
|
|
||||||
.appear(.default(scale: true))
|
|
||||||
.disappear(.default(scale: true))
|
|
||||||
)
|
|
||||||
|
|
||||||
if !isTablet && Camera.isDualCameraSupported {
|
|
||||||
let dualButton = dualButton.update(
|
|
||||||
component: CameraButton(
|
component: CameraButton(
|
||||||
content: AnyComponentWithIdentity(
|
content: flashContentComponent,
|
||||||
id: "dual",
|
|
||||||
component: AnyComponent(
|
|
||||||
DualIconComponent(isSelected: component.cameraState.isDualCameraEnabled)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
action: { [weak state] in
|
action: { [weak state] in
|
||||||
if let state {
|
if let state {
|
||||||
state.toggleDualCamera()
|
state.toggleFlashMode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).tagged(dualButtonTag),
|
).tagged(flashButtonTag),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
context.add(dualButton
|
context.add(flashButton
|
||||||
.position(CGPoint(x: availableSize.width - topControlInset - flashButton.size.width / 2.0 - 58.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + dualButton.size.height / 2.0 + 2.0))
|
.position(CGPoint(x: isTablet ? availableSize.width - smallPanelWidth / 2.0 : availableSize.width - topControlInset - flashButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + flashButton.size.height / 2.0))
|
||||||
.appear(.default(scale: true))
|
.appear(.default(scale: true))
|
||||||
.disappear(.default(scale: true))
|
.disappear(.default(scale: true))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if !isTablet && Camera.isDualCameraSupported {
|
||||||
|
let dualButton = dualButton.update(
|
||||||
|
component: CameraButton(
|
||||||
|
content: AnyComponentWithIdentity(
|
||||||
|
id: "dual",
|
||||||
|
component: AnyComponent(
|
||||||
|
DualIconComponent(isSelected: component.cameraState.isDualCameraEnabled)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
action: { [weak state] in
|
||||||
|
if let state {
|
||||||
|
state.toggleDualCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).tagged(dualButtonTag),
|
||||||
|
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||||
|
transition: .immediate
|
||||||
|
)
|
||||||
|
context.add(dualButton
|
||||||
|
.position(CGPoint(x: availableSize.width - topControlInset - flashButton.size.width / 2.0 - 58.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + dualButton.size.height / 2.0 + 2.0))
|
||||||
|
.appear(.default(scale: true))
|
||||||
|
.disappear(.default(scale: true))
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTablet {
|
if isTablet && hasAllRequiredAccess {
|
||||||
let flipButton = flipButton.update(
|
let flipButton = flipButton.update(
|
||||||
component: CameraButton(
|
component: CameraButton(
|
||||||
content: AnyComponentWithIdentity(
|
content: AnyComponentWithIdentity(
|
||||||
@ -807,6 +859,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
context.add(flipButton
|
context.add(flipButton
|
||||||
.position(CGPoint(x: smallPanelWidth / 2.0, y: availableSize.height / 2.0))
|
.position(CGPoint(x: smallPanelWidth / 2.0, y: availableSize.height / 2.0))
|
||||||
|
.appear(.default(scale: true))
|
||||||
|
.disappear(.default(scale: true))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -885,7 +939,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if case .none = component.cameraState.recording, !state.isTransitioning {
|
if case .none = component.cameraState.recording, !state.isTransitioning && hasAllRequiredAccess {
|
||||||
let availableModeControlSize: CGSize
|
let availableModeControlSize: CGSize
|
||||||
if isTablet {
|
if isTablet {
|
||||||
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
|
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
|
||||||
@ -1141,6 +1195,11 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var cameraAuthorizationStatus: AccessType = .notDetermined
|
||||||
|
private var microphoneAuthorizationStatus: AccessType = .notDetermined
|
||||||
|
private var galleryAuthorizationStatus: AccessType = .notDetermined
|
||||||
|
private var authorizationStatusDisposables = DisposableSet()
|
||||||
|
|
||||||
init(controller: CameraScreen) {
|
init(controller: CameraScreen) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
@ -1312,12 +1371,33 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
|
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||||
|
|
||||||
|
self.authorizationStatusDisposables.add((DeviceAccess.authorizationStatus(subject: .camera(.video))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
|
if let self {
|
||||||
|
self.cameraAuthorizationStatus = status
|
||||||
|
self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .easeInOut(duration: 0.2))
|
||||||
|
|
||||||
|
self.maybeSetupCamera()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
self.authorizationStatusDisposables.add((DeviceAccess.authorizationStatus(subject: .microphone(.video))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||||
|
if let self {
|
||||||
|
self.microphoneAuthorizationStatus = status
|
||||||
|
self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .easeInOut(duration: 0.2))
|
||||||
|
|
||||||
|
self.maybeSetupCamera()
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.cameraStateDisposable?.dispose()
|
self.cameraStateDisposable?.dispose()
|
||||||
self.changingPositionDisposable?.dispose()
|
self.changingPositionDisposable?.dispose()
|
||||||
self.idleTimerExtensionDisposable.dispose()
|
self.idleTimerExtensionDisposable.dispose()
|
||||||
|
self.authorizationStatusDisposables.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pipPanGestureRecognizer: UIPanGestureRecognizer?
|
private var pipPanGestureRecognizer: UIPanGestureRecognizer?
|
||||||
@ -1346,11 +1426,23 @@ public class CameraScreen: ViewController {
|
|||||||
pipPanGestureRecognizer.delegate = self
|
pipPanGestureRecognizer.delegate = self
|
||||||
self.previewContainerView.addGestureRecognizer(pipPanGestureRecognizer)
|
self.previewContainerView.addGestureRecognizer(pipPanGestureRecognizer)
|
||||||
self.pipPanGestureRecognizer = pipPanGestureRecognizer
|
self.pipPanGestureRecognizer = pipPanGestureRecognizer
|
||||||
|
|
||||||
self.setupCamera()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupCamera() {
|
private func maybeSetupCamera() {
|
||||||
|
if case .allowed = self.cameraAuthorizationStatus, case .allowed = self.microphoneAuthorizationStatus {
|
||||||
|
self.setupCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func requestDeviceAccess() {
|
||||||
|
DeviceAccess.authorizeAccess(to: .camera(.video), { granted in
|
||||||
|
if granted {
|
||||||
|
DeviceAccess.authorizeAccess(to: .microphone(.video))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupCamera() {
|
||||||
guard self.camera == nil else {
|
guard self.camera == nil else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -1461,6 +1553,10 @@ public class CameraScreen: ViewController {
|
|||||||
camera.startCapture()
|
camera.startCapture()
|
||||||
|
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
|
|
||||||
|
if self.hasAppeared {
|
||||||
|
self.maybePresentTooltips()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
@ -1581,7 +1677,7 @@ public class CameraScreen: ViewController {
|
|||||||
|
|
||||||
CATransaction.begin()
|
CATransaction.begin()
|
||||||
CATransaction.setDisableActions(true)
|
CATransaction.setDisableActions(true)
|
||||||
self.requestUpdateLayout(hasAppeared: false, transition: .immediate)
|
self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .immediate)
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
|
|
||||||
self.additionalPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
self.additionalPreviewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
@ -1866,9 +1962,12 @@ public class CameraScreen: ViewController {
|
|||||||
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
|
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
|
||||||
|
|
||||||
let accountManager = self.context.sharedContext.accountManager
|
let accountManager = self.context.sharedContext.accountManager
|
||||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { point, containerFrame in
|
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(4.5), inset: 16.0, shouldDismissOnTouch: { [weak self] point, containerFrame in
|
||||||
if containerFrame.contains(point) {
|
if containerFrame.contains(point) {
|
||||||
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
self?.maybePresentTooltips()
|
||||||
|
}
|
||||||
return .dismiss(consume: true)
|
return .dismiss(consume: true)
|
||||||
}
|
}
|
||||||
return .ignore
|
return .ignore
|
||||||
@ -2006,7 +2105,11 @@ public class CameraScreen: ViewController {
|
|||||||
self.hasAppeared = hasAppeared
|
self.hasAppeared = hasAppeared
|
||||||
transition = transition.withUserData(CameraScreenTransition.finishedAnimateIn)
|
transition = transition.withUserData(CameraScreenTransition.finishedAnimateIn)
|
||||||
|
|
||||||
self.maybePresentTooltips()
|
if self.camera != nil {
|
||||||
|
self.maybePresentTooltips()
|
||||||
|
} else if case .notDetermined = self.cameraAuthorizationStatus {
|
||||||
|
self.requestDeviceAccess()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let componentSize = self.componentHost.update(
|
let componentSize = self.componentHost.update(
|
||||||
@ -2015,6 +2118,8 @@ public class CameraScreen: ViewController {
|
|||||||
CameraScreenComponent(
|
CameraScreenComponent(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
cameraState: self.cameraState,
|
cameraState: self.cameraState,
|
||||||
|
cameraAuthorizationStatus: self.cameraAuthorizationStatus,
|
||||||
|
microphoneAuthorizationStatus: self.microphoneAuthorizationStatus,
|
||||||
hasAppeared: self.hasAppeared,
|
hasAppeared: self.hasAppeared,
|
||||||
isVisible: self.cameraIsActive && !self.hasGallery,
|
isVisible: self.cameraIsActive && !self.hasGallery,
|
||||||
panelWidth: panelWidth,
|
panelWidth: panelWidth,
|
||||||
|
@ -446,6 +446,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
|
|
||||||
let isTablet: Bool
|
let isTablet: Bool
|
||||||
let hasAppeared: Bool
|
let hasAppeared: Bool
|
||||||
|
let hasAccess: Bool
|
||||||
let shutterState: ShutterButtonState
|
let shutterState: ShutterButtonState
|
||||||
let lastGalleryAsset: PHAsset?
|
let lastGalleryAsset: PHAsset?
|
||||||
let tag: AnyObject?
|
let tag: AnyObject?
|
||||||
@ -463,6 +464,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
init(
|
init(
|
||||||
isTablet: Bool,
|
isTablet: Bool,
|
||||||
hasAppeared: Bool,
|
hasAppeared: Bool,
|
||||||
|
hasAccess: Bool,
|
||||||
shutterState: ShutterButtonState,
|
shutterState: ShutterButtonState,
|
||||||
lastGalleryAsset: PHAsset?,
|
lastGalleryAsset: PHAsset?,
|
||||||
tag: AnyObject?,
|
tag: AnyObject?,
|
||||||
@ -479,6 +481,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
) {
|
) {
|
||||||
self.isTablet = isTablet
|
self.isTablet = isTablet
|
||||||
self.hasAppeared = hasAppeared
|
self.hasAppeared = hasAppeared
|
||||||
|
self.hasAccess = hasAccess
|
||||||
self.shutterState = shutterState
|
self.shutterState = shutterState
|
||||||
self.lastGalleryAsset = lastGalleryAsset
|
self.lastGalleryAsset = lastGalleryAsset
|
||||||
self.tag = tag
|
self.tag = tag
|
||||||
@ -501,6 +504,9 @@ final class CaptureControlsComponent: Component {
|
|||||||
if lhs.hasAppeared != rhs.hasAppeared {
|
if lhs.hasAppeared != rhs.hasAppeared {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.hasAccess != rhs.hasAccess {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if lhs.shutterState != rhs.shutterState {
|
if lhs.shutterState != rhs.shutterState {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -944,7 +950,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning ? 0.0 : 1.0)
|
transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !component.isTablet {
|
if !component.isTablet && component.hasAccess {
|
||||||
let flipButtonOriginX = availableSize.width - 48.0 - buttonSideInset
|
let flipButtonOriginX = availableSize.width - 48.0 - buttonSideInset
|
||||||
let flipButtonMaskFrame: CGRect = CGRect(origin: CGPoint(x: availableSize.width / 2.0 - (flipButtonOriginX + 22.0) + 6.0 + self.shutterOffsetX, y: 8.0), size: CGSize(width: 32.0, height: 32.0))
|
let flipButtonMaskFrame: CGRect = CGRect(origin: CGPoint(x: availableSize.width / 2.0 - (flipButtonOriginX + 22.0) + 6.0 + self.shutterOffsetX, y: 8.0), size: CGSize(width: 32.0, height: 32.0))
|
||||||
|
|
||||||
@ -1153,10 +1159,13 @@ final class CaptureControlsComponent: Component {
|
|||||||
|
|
||||||
self.addSubview(shutterButtonView)
|
self.addSubview(shutterButtonView)
|
||||||
}
|
}
|
||||||
|
let alpha: CGFloat = component.hasAccess ? 1.0 : 0.3
|
||||||
transition.setBounds(view: shutterButtonView, bounds: CGRect(origin: .zero, size: shutterButtonFrame.size))
|
transition.setBounds(view: shutterButtonView, bounds: CGRect(origin: .zero, size: shutterButtonFrame.size))
|
||||||
transition.setPosition(view: shutterButtonView, position: shutterButtonFrame.center)
|
transition.setPosition(view: shutterButtonView, position: shutterButtonFrame.center)
|
||||||
transition.setScale(view: shutterButtonView, scale: isTransitioning ? 0.01 : 1.0)
|
transition.setScale(view: shutterButtonView, scale: isTransitioning ? 0.01 : 1.0)
|
||||||
transition.setAlpha(view: shutterButtonView, alpha: isTransitioning ? 0.0 : 1.0)
|
transition.setAlpha(view: shutterButtonView, alpha: isTransitioning ? 0.0 : alpha)
|
||||||
|
|
||||||
|
shutterButtonView.isUserInteractionEnabled = component.hasAccess
|
||||||
}
|
}
|
||||||
|
|
||||||
if let buttonView = self.flipButtonView.view as? CameraButton.View, let contentView = buttonView.contentView.componentView as? FlipButtonContentComponent.View {
|
if let buttonView = self.flipButtonView.view as? CameraButton.View, let contentView = buttonView.contentView.componentView as? FlipButtonContentComponent.View {
|
||||||
|
@ -0,0 +1,224 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramCore
|
||||||
|
import AccountContext
|
||||||
|
import BundleIconComponent
|
||||||
|
import MultilineTextComponent
|
||||||
|
import ButtonComponent
|
||||||
|
import LottieComponent
|
||||||
|
|
||||||
|
final class PlaceholderComponent: Component {
|
||||||
|
typealias EnvironmentType = Empty
|
||||||
|
|
||||||
|
enum Mode {
|
||||||
|
case request
|
||||||
|
case denied
|
||||||
|
}
|
||||||
|
|
||||||
|
let context: AccountContext
|
||||||
|
let mode: Mode
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
context: AccountContext,
|
||||||
|
mode: Mode,
|
||||||
|
action: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.context = context
|
||||||
|
self.mode = mode
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: PlaceholderComponent, rhs: PlaceholderComponent) -> Bool {
|
||||||
|
if lhs.context !== rhs.context {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.mode != rhs.mode {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let animation = ComponentView<Empty>()
|
||||||
|
private let title = ComponentView<Empty>()
|
||||||
|
private let text = ComponentView<Empty>()
|
||||||
|
private let button = ComponentView<Empty>()
|
||||||
|
|
||||||
|
private var component: PlaceholderComponent?
|
||||||
|
private weak var state: EmptyComponentState?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.backgroundColor = UIColor(rgb: 0x1c1c1e)
|
||||||
|
// if #available(iOS 13.0, *) {
|
||||||
|
// self.layer.cornerCurve = .continuous
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: PlaceholderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
self.state = state
|
||||||
|
|
||||||
|
let sideInset: CGFloat = 36.0
|
||||||
|
let animationHeight: CGFloat = 120.0
|
||||||
|
|
||||||
|
let title: String = "Allow Telegram to access your camera and microphone"
|
||||||
|
let text: String = "This lets you share photos and record videos."
|
||||||
|
let buttonTitle: String = "Open Settings"
|
||||||
|
|
||||||
|
let animationSize = self.animation.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(LottieComponent(
|
||||||
|
content: LottieComponent.AppBundleContent(name: "Photos")
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: animationHeight, height: animationHeight)
|
||||||
|
)
|
||||||
|
|
||||||
|
let titleSize = self.title.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: title, font: Font.semibold(17.0), textColor: UIColor.white)),
|
||||||
|
horizontalAlignment: .center,
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 3.0, height: availableSize.height)
|
||||||
|
)
|
||||||
|
|
||||||
|
let textSize = self.text.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
MultilineTextComponent(
|
||||||
|
text: .plain(NSAttributedString(string: text, font: Font.regular(15.0), textColor: UIColor(rgb: 0x98989f))),
|
||||||
|
horizontalAlignment: .center,
|
||||||
|
maximumNumberOfLines: 0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||||
|
)
|
||||||
|
|
||||||
|
let buttonSize = self.button.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(
|
||||||
|
ButtonComponent(
|
||||||
|
background: ButtonComponent.Background(
|
||||||
|
color: UIColor(rgb: 0x007aff),
|
||||||
|
foreground: .white,
|
||||||
|
pressedColor: UIColor(rgb: 0x007aff, alpha: 0.55)
|
||||||
|
),
|
||||||
|
content: AnyComponentWithIdentity(
|
||||||
|
id: buttonTitle,
|
||||||
|
component: AnyComponent(ButtonTextContentComponent(
|
||||||
|
text: buttonTitle,
|
||||||
|
badge: 0,
|
||||||
|
textColor: .white,
|
||||||
|
badgeBackground: .clear,
|
||||||
|
badgeForeground: .clear
|
||||||
|
))
|
||||||
|
),
|
||||||
|
isEnabled: true,
|
||||||
|
displaysProgress: false,
|
||||||
|
action: { [weak self] in
|
||||||
|
if let self {
|
||||||
|
self.component?.action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: CGSize(width: 240.0, height: 50.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
let titleSpacing: CGFloat = 12.0
|
||||||
|
let textSpacing: CGFloat = 14.0
|
||||||
|
let buttonSpacing: CGFloat = 18.0
|
||||||
|
let totalHeight = animationSize.height + titleSpacing + titleSize.height + textSpacing + textSize.height + buttonSpacing + buttonSize.height
|
||||||
|
|
||||||
|
var originY = floorToScreenPixels((availableSize.height - totalHeight) / 2.0)
|
||||||
|
let animationFrame = CGRect(
|
||||||
|
origin: CGPoint(
|
||||||
|
x: floorToScreenPixels((availableSize.width - animationSize.width) / 2.0),
|
||||||
|
y: originY
|
||||||
|
),
|
||||||
|
size: animationSize
|
||||||
|
)
|
||||||
|
if let view = self.animation.view as? LottieComponent.View {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.addSubview(view)
|
||||||
|
Queue.mainQueue().justDispatch {
|
||||||
|
view.playOnce()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.frame = animationFrame
|
||||||
|
}
|
||||||
|
originY += animationSize.height + titleSpacing
|
||||||
|
|
||||||
|
let titleFrame = CGRect(
|
||||||
|
origin: CGPoint(
|
||||||
|
x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0),
|
||||||
|
y: originY
|
||||||
|
),
|
||||||
|
size: titleSize
|
||||||
|
)
|
||||||
|
if let view = self.title.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
view.frame = titleFrame
|
||||||
|
}
|
||||||
|
originY += titleSize.height + textSpacing
|
||||||
|
|
||||||
|
let textFrame = CGRect(
|
||||||
|
origin: CGPoint(
|
||||||
|
x: floorToScreenPixels((availableSize.width - textSize.width) / 2.0),
|
||||||
|
y: originY
|
||||||
|
),
|
||||||
|
size: textSize
|
||||||
|
)
|
||||||
|
if let view = self.text.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
view.frame = textFrame
|
||||||
|
}
|
||||||
|
originY += textSize.height + buttonSpacing
|
||||||
|
|
||||||
|
let buttonFrame = CGRect(
|
||||||
|
origin: CGPoint(
|
||||||
|
x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0),
|
||||||
|
y: originY
|
||||||
|
),
|
||||||
|
size: buttonSize
|
||||||
|
)
|
||||||
|
if let view = self.button.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.addSubview(view)
|
||||||
|
}
|
||||||
|
view.frame = buttonFrame
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeView() -> View {
|
||||||
|
return View()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -221,7 +221,10 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
|
|||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.view.window?.endEditing(true)
|
strongSelf.view.window?.endEditing(true)
|
||||||
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
let packReference: StickerPackReference = .id(id: info.id.id, accessHash: info.accessHash)
|
||||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: strongSelf.interaction.getNavigationController(), sendSticker: { [weak self] fileReference, sourceNode, sourceRect in
|
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }.withUpdated(theme: theme)
|
||||||
|
|
||||||
|
let controller = StickerPackScreen(context: strongSelf.context, updatedPresentationData: (presentationData, .single(presentationData)), mainStickerPack: packReference, stickerPacks: [packReference], actionTitle: stickerActionTitle, parentNavigationController: strongSelf.interaction.getNavigationController(), sendSticker: { [weak self] fileReference, sourceNode, sourceRect in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
return strongSelf.interaction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, [])
|
return strongSelf.interaction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, [])
|
||||||
} else {
|
} else {
|
||||||
|
@ -14,8 +14,13 @@ private func fullEntityMediaPath(_ path: String) -> String {
|
|||||||
|
|
||||||
public final class DrawingStickerEntity: DrawingEntity, Codable {
|
public final class DrawingStickerEntity: DrawingEntity, Codable {
|
||||||
public enum Content: Equatable {
|
public enum Content: Equatable {
|
||||||
|
public enum ImageType: Equatable {
|
||||||
|
case sticker
|
||||||
|
case rectangle
|
||||||
|
case dualPhoto
|
||||||
|
}
|
||||||
case file(TelegramMediaFile)
|
case file(TelegramMediaFile)
|
||||||
case image(UIImage, Bool)
|
case image(UIImage, ImageType)
|
||||||
case video(String, UIImage?, Bool)
|
case video(String, UIImage?, Bool)
|
||||||
case dualVideoReference
|
case dualVideoReference
|
||||||
|
|
||||||
@ -27,9 +32,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case let .image(lhsImage, lhsIsRectangle):
|
case let .image(lhsImage, lhsImageType):
|
||||||
if case let .image(rhsImage, rhsIsRectangle) = rhs {
|
if case let .image(rhsImage, rhsImageType) = rhs {
|
||||||
return lhsImage === rhsImage && lhsIsRectangle == rhsIsRectangle
|
return lhsImage === rhsImage && lhsImageType == rhsImageType
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -56,6 +61,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
case videoImagePath
|
case videoImagePath
|
||||||
case videoMirrored
|
case videoMirrored
|
||||||
case isRectangle
|
case isRectangle
|
||||||
|
case isDualPhoto
|
||||||
case dualVideo
|
case dualVideo
|
||||||
case referenceDrawingSize
|
case referenceDrawingSize
|
||||||
case position
|
case position
|
||||||
@ -121,8 +127,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
|
|
||||||
public var isRectangle: Bool {
|
public var isRectangle: Bool {
|
||||||
switch self.content {
|
switch self.content {
|
||||||
case let .image(_, isRectangle):
|
case let .image(_, imageType):
|
||||||
return isRectangle
|
return imageType == .rectangle
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -157,7 +163,16 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
self.content = .file(file)
|
self.content = .file(file)
|
||||||
} else if let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
|
} else if let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
|
||||||
let isRectangle = try container.decodeIfPresent(Bool.self, forKey: .isRectangle) ?? false
|
let isRectangle = try container.decodeIfPresent(Bool.self, forKey: .isRectangle) ?? false
|
||||||
self.content = .image(image, isRectangle)
|
let isDualPhoto = try container.decodeIfPresent(Bool.self, forKey: .isDualPhoto) ?? false
|
||||||
|
let imageType: Content.ImageType
|
||||||
|
if isDualPhoto {
|
||||||
|
imageType = .dualPhoto
|
||||||
|
} else if isRectangle {
|
||||||
|
imageType = .rectangle
|
||||||
|
} else {
|
||||||
|
imageType = .sticker
|
||||||
|
}
|
||||||
|
self.content = .image(image, imageType)
|
||||||
} else if let videoPath = try container.decodeIfPresent(String.self, forKey: .videoPath) {
|
} else if let videoPath = try container.decodeIfPresent(String.self, forKey: .videoPath) {
|
||||||
var imageValue: UIImage?
|
var imageValue: UIImage?
|
||||||
if let imagePath = try container.decodeIfPresent(String.self, forKey: .videoImagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
|
if let imagePath = try container.decodeIfPresent(String.self, forKey: .videoImagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
|
||||||
@ -182,7 +197,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
switch self.content {
|
switch self.content {
|
||||||
case let .file(file):
|
case let .file(file):
|
||||||
try container.encode(file, forKey: .file)
|
try container.encode(file, forKey: .file)
|
||||||
case let .image(image, isRectangle):
|
case let .image(image, imageType):
|
||||||
let imagePath = "\(self.uuid).png"
|
let imagePath = "\(self.uuid).png"
|
||||||
let fullImagePath = fullEntityMediaPath(imagePath)
|
let fullImagePath = fullEntityMediaPath(imagePath)
|
||||||
if let imageData = image.pngData() {
|
if let imageData = image.pngData() {
|
||||||
@ -190,7 +205,14 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
|
|||||||
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
|
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
|
||||||
try container.encodeIfPresent(imagePath, forKey: .imagePath)
|
try container.encodeIfPresent(imagePath, forKey: .imagePath)
|
||||||
}
|
}
|
||||||
try container.encode(isRectangle, forKey: .isRectangle)
|
switch imageType {
|
||||||
|
case .dualPhoto:
|
||||||
|
try container.encode(true, forKey: .isDualPhoto)
|
||||||
|
case .rectangle:
|
||||||
|
try container.encode(true, forKey: .isRectangle)
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
case let .video(path, image, videoMirrored):
|
case let .video(path, image, videoMirrored):
|
||||||
try container.encode(path, forKey: .videoPath)
|
try container.encode(path, forKey: .videoPath)
|
||||||
let imagePath = "\(self.uuid).jpg"
|
let imagePath = "\(self.uuid).jpg"
|
||||||
|
@ -481,7 +481,8 @@ public final class MediaEditor {
|
|||||||
|
|
||||||
if player == nil {
|
if player == nil {
|
||||||
self.updateRenderChain()
|
self.updateRenderChain()
|
||||||
self.maybeGeneratePersonSegmentation(image)
|
let _ = image
|
||||||
|
// self.maybeGeneratePersonSegmentation(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let player {
|
if let player {
|
||||||
|
@ -1167,13 +1167,13 @@ final class MediaEditorScreenComponent: Component {
|
|||||||
switch data {
|
switch data {
|
||||||
case let .sticker(image, _):
|
case let .sticker(image, _):
|
||||||
if max(image.size.width, image.size.height) > 1.0 {
|
if max(image.size.width, image.size.height) > 1.0 {
|
||||||
let entity = DrawingStickerEntity(content: .image(image, false))
|
let entity = DrawingStickerEntity(content: .image(image, .sticker))
|
||||||
controller.node.interaction?.insertEntity(entity, scale: 1.0)
|
controller.node.interaction?.insertEntity(entity, scale: 1.0)
|
||||||
self.deactivateInput()
|
self.deactivateInput()
|
||||||
}
|
}
|
||||||
case let .images(images):
|
case let .images(images):
|
||||||
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
||||||
let entity = DrawingStickerEntity(content: .image(image, true))
|
let entity = DrawingStickerEntity(content: .image(image, .rectangle))
|
||||||
controller.node.interaction?.insertEntity(entity, scale: 2.5)
|
controller.node.interaction?.insertEntity(entity, scale: 2.5)
|
||||||
self.deactivateInput()
|
self.deactivateInput()
|
||||||
}
|
}
|
||||||
@ -1925,8 +1925,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if let cgImage = additionalImage.cgImage {
|
if let cgImage = additionalImage.cgImage {
|
||||||
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size))
|
context.draw(cgImage, in: CGRect(origin: CGPoint(x: (size.width - additionalImage.size.width) / 2.0, y: (size.height - additionalImage.size.height) / 2.0), size: additionalImage.size))
|
||||||
}
|
}
|
||||||
})
|
}, scale: 1.0)
|
||||||
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, false))
|
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, .dualPhoto))
|
||||||
imageEntity.referenceDrawingSize = storyDimensions
|
imageEntity.referenceDrawingSize = storyDimensions
|
||||||
imageEntity.scale = 1.625
|
imageEntity.scale = 1.625
|
||||||
imageEntity.position = position.getPosition(storyDimensions)
|
imageEntity.position = position.getPosition(storyDimensions)
|
||||||
@ -2730,7 +2730,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { [weak self] image, _ in
|
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { [weak self] image, _ in
|
||||||
if let self, let image {
|
if let self, let image {
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
self.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, true)), scale: 2.5)
|
self.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, .rectangle)), scale: 2.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3862,7 +3862,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
if let self {
|
if let self {
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image ?? UIImage(), dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] coverImage in
|
||||||
if let self {
|
if let self {
|
||||||
Logger.shared.log("Media Editor", "completed with video \(videoResult)")
|
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
||||||
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
self.completion(randomId, .video(video: videoResult, coverImage: coverImage, values: mediaEditor.values, duration: duration, dimensions: mediaEditor.values.resultDimensions), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
@ -3885,7 +3885,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
|
|
||||||
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
|
makeEditorImageComposition(context: self.node.ciContext, account: self.context.account, inputImage: image, dimensions: storyDimensions, values: mediaEditor.values, time: .zero, completion: { [weak self] resultImage in
|
||||||
if let self, let resultImage {
|
if let self, let resultImage {
|
||||||
Logger.shared.log("Media Editor", "completed with image \(resultImage)")
|
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
|
||||||
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
|
self.completion(randomId, .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), caption, self.state.privacy, stickers, { [weak self] finished in
|
||||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||||
self?.dismiss()
|
self?.dismiss()
|
||||||
@ -4110,7 +4110,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
let images = imageItems as! [UIImage]
|
let images = imageItems as! [UIImage]
|
||||||
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
if images.count == 1, let image = images.first, max(image.size.width, image.size.height) > 1.0 {
|
||||||
self.node.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, false)), scale: 2.5)
|
self.node.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, .sticker)), scale: 2.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -978,7 +978,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fadeTransition = Transition.easeInOut(duration: 0.25)
|
let fadeTransition = Transition.easeInOut(duration: 0.25)
|
||||||
if let searchStateContext = self.searchStateContext, case let .search(query) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
|
if let searchStateContext = self.searchStateContext, case let .search(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
|
||||||
let sideInset: CGFloat = 44.0
|
let sideInset: CGFloat = 44.0
|
||||||
let emptyAnimationHeight = 148.0
|
let emptyAnimationHeight = 148.0
|
||||||
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
|
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
|
||||||
@ -1280,10 +1280,14 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if !self.navigationTextFieldState.text.isEmpty {
|
if !self.navigationTextFieldState.text.isEmpty {
|
||||||
if let searchStateContext = self.searchStateContext, searchStateContext.subject == .search(self.navigationTextFieldState.text) {
|
var onlyContacts = false
|
||||||
|
if component.initialPrivacy.base == .closeFriends || component.initialPrivacy.base == .contacts {
|
||||||
|
onlyContacts = true
|
||||||
|
}
|
||||||
|
if let searchStateContext = self.searchStateContext, searchStateContext.subject == .search(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts) {
|
||||||
} else {
|
} else {
|
||||||
self.searchStateDisposable?.dispose()
|
self.searchStateDisposable?.dispose()
|
||||||
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: .search(self.navigationTextFieldState.text))
|
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: .search(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts))
|
||||||
var applyState = false
|
var applyState = false
|
||||||
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
@ -1335,7 +1339,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||||||
sideInset: sideInset,
|
sideInset: sideInset,
|
||||||
title: "Name",
|
title: "Name",
|
||||||
peer: nil,
|
peer: nil,
|
||||||
subtitle: "sub",
|
subtitle: self.searchStateContext != nil ? "" : "sub",
|
||||||
subtitleAccessory: .none,
|
subtitleAccessory: .none,
|
||||||
presence: nil,
|
presence: nil,
|
||||||
selectionState: .editing(isSelected: false, isTinted: false),
|
selectionState: .editing(isSelected: false, isTinted: false),
|
||||||
@ -1753,7 +1757,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
case stories(editing: Bool)
|
case stories(editing: Bool)
|
||||||
case chats
|
case chats
|
||||||
case contacts(EngineStoryPrivacy.Base)
|
case contacts(EngineStoryPrivacy.Base)
|
||||||
case search(String)
|
case search(query: String, onlyContacts: Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate var stateValue: State?
|
fileprivate var stateValue: State?
|
||||||
@ -1889,7 +1893,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||||||
|
|
||||||
self.readySubject.set(true)
|
self.readySubject.set(true)
|
||||||
})
|
})
|
||||||
case let .search(query):
|
case let .search(query, _):
|
||||||
self.stateDisposable = (context.engine.contacts.searchLocalPeers(query: query)
|
self.stateDisposable = (context.engine.contacts.searchLocalPeers(query: query)
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
|> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user