mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various fixes
This commit is contained in:
parent
0b7869688d
commit
5e5f19e6e9
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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