Merge commit '5e5f19e6e98298c95fefe548ec0de31752af9c55'

This commit is contained in:
Ali 2023-07-14 14:20:51 +04:00
commit a0032f4fee
19 changed files with 714 additions and 95 deletions

View File

@ -2062,7 +2062,7 @@
"StickerPack.Share" = "Share";
"StickerPack.Send" = "Send Sticker";
"StickerPack.Select" = "Select Sticker";
"StickerPack.AddSticker" = "Add Sticker";
"StickerPack.RemoveStickerCount_1" = "Remove 1 Sticker";
"StickerPack.RemoveStickerCount_2" = "Remove 2 Stickers";

View File

@ -57,6 +57,37 @@ enum DeviceModel: CaseIterable, Equatable {
.iPodTouch5,
.iPodTouch6,
.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,
.iPhone14Plus,
.iPhone14Pro,
@ -72,9 +103,40 @@ enum DeviceModel: CaseIterable, Equatable {
case iPodTouch6
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 iPhoneXS
case iPhoneXSMax
case iPhoneXR
case iPhone11
case iPhone11Pro
case iPhone11ProMax
case iPhoneSE2ndGen
case iPhone12
case iPhone12Mini
case iPhone12Pro
@ -85,6 +147,8 @@ enum DeviceModel: CaseIterable, Equatable {
case iPhone13Pro
case iPhone13ProMax
case iPhoneSE3rdGen
case iPhone14
case iPhone14Plus
case iPhone14Pro
@ -108,10 +172,56 @@ enum DeviceModel: CaseIterable, Equatable {
return ["iPod7,1"]
case .iPodTouch7:
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:
return ["iPhone11,2"]
return ["iPhone10,3", "iPhone10,6"]
case .iPhoneXS:
return ["iPhone11,2"]
case .iPhoneXSMax:
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:
return ["iPhone13,2"]
case .iPhone12Mini:
@ -128,6 +238,8 @@ enum DeviceModel: CaseIterable, Equatable {
return ["iPhone14,2"]
case .iPhone13ProMax:
return ["iPhone14,3"]
case .iPhoneSE3rdGen:
return ["iPhone14,6"]
case .iPhone14:
return ["iPhone14,7"]
case .iPhone14Plus:
@ -157,10 +269,56 @@ enum DeviceModel: CaseIterable, Equatable {
return "iPod touch 6G"
case .iPodTouch7:
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:
return "iPhone X"
case .iPhoneXS:
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:
return "iPhone 12"
case .iPhone12Mini:
@ -177,6 +335,8 @@ enum DeviceModel: CaseIterable, Equatable {
return "iPhone 13 Pro"
case .iPhone13ProMax:
return "iPhone 13 Pro Max"
case .iPhoneSE3rdGen:
return "iPhone SE (3rd gen)"
case .iPhone14:
return "iPhone 14"
case .iPhone14Plus:

View File

@ -82,6 +82,16 @@ public final class DeviceAccess {
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? {
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:
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 {
case let .camera(cameraSubject):
let status = AVCaptureDevice.authorizationStatus(for: .video)
@ -262,6 +332,7 @@ public final class DeviceAccess {
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
Queue.mainQueue().async {
completion(response)
self.cameraPromise.set(.single(response))
if !response, let presentationData = presentationData {
let text: String
switch cameraSubject {
@ -331,6 +402,7 @@ public final class DeviceAccess {
displayNotificationFromBackground(text)
}
}
self.microphonePromise.set(.single(granted))
}
})
}

View File

@ -694,6 +694,8 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
if let selectedEntityView = self.selectedEntityView, let selectionView = selectedEntityView.selectionView {
if !self.hasBin {
selectionView.handlePan(gestureRecognizer)
} else if let stickerEntity = selectedEntityView.entity as? DrawingStickerEntity, case .dualVideoReference = stickerEntity.content {
selectionView.handlePan(gestureRecognizer)
} else {
var isTrappedInBin = false
let scale = 100.0 / selectedEntityView.bounds.size.width

View File

@ -2930,7 +2930,7 @@ public class DrawingScreen: ViewController, TGPhotoDrawingInterfaceController, U
}
let images = imageItems as! [UIImage]
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)
}
}

View File

@ -289,7 +289,7 @@ private final class StickerSelectionComponent: Component {
interaction: interaction,
inputNodeInteraction: inputNodeInteraction,
mode: mappedMode,
stickerActionTitle: presentationData.strings.StickerPack_Select,
stickerActionTitle: presentationData.strings.StickerPack_AddSticker,
trendingGifsPromise: trendingGifsPromise,
cancel: {
},
@ -585,7 +585,7 @@ public class StickerPickerScreen: ViewController {
CTLineDraw(line, context)
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)
}

View File

@ -261,6 +261,7 @@ func assetImage(asset: PHAsset, targetSize: CGSize, exact: Bool, deliveryMode: P
if let info = info {
if let cancelled = info[PHImageCancelledKey] as? Bool, cancelled {
subscriber.putCompletion()
return
}
if let degradedValue = info[PHImageResultIsDegradedKey] as? Bool, degradedValue {

View File

@ -125,23 +125,27 @@ final class MediaPickerGridItemNode: GridItemNode {
override init() {
self.backgroundNode = ASImageNode()
self.backgroundNode.contentMode = .scaleToFill
self.backgroundNode.isLayerBacked = true
self.imageNode = ImageNode()
self.imageNode.clipsToBounds = true
self.imageNode.contentMode = .scaleAspectFill
self.imageNode.isLayerBacked = false
self.imageNode.isLayerBacked = true
self.imageNode.animateFirstTransition = false
self.gradientNode = ASImageNode()
self.gradientNode.displaysAsynchronously = false
self.gradientNode.displayWithoutProcessing = true
self.gradientNode.image = maskImage
self.gradientNode.isLayerBacked = true
self.typeIconNode = ASImageNode()
self.typeIconNode.displaysAsynchronously = false
self.typeIconNode.displayWithoutProcessing = true
self.typeIconNode.isLayerBacked = true
self.durationNode = ImmediateTextNode()
self.durationNode.isLayerBacked = true
self.draftNode = ImmediateTextNode()
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
|> mapToSignal { result in
if let result = result {
@ -519,18 +523,22 @@ final class MediaPickerGridItemNode: GridItemNode {
self.addSubnode(self.typeIconNode)
self.setNeedsLayout()
}
} else if asset.mediaType == .video {
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaSlomo")
} else if asset.mediaSubtypes.contains(.videoTimelapse) {
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaTimelapse")
} else {
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaVideo")
}
if asset.mediaType == .video {
if !asset.isFavorite {
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaSlomo")
} 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)
if self.typeIconNode.supernode == nil {
if self.durationNode.supernode == nil {
self.addSubnode(self.gradientNode)
self.addSubnode(self.typeIconNode)
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))
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.typeIconNode.frame = CGRect(x: 0.0, y: self.bounds.height - 20.0, width: 19.0, height: 19.0)
self.activateAreaNode.frame = self.bounds
@ -619,11 +627,11 @@ final class MediaPickerGridItemNode: GridItemNode {
func transitionView(snapshot: Bool) -> UIView {
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)
return view
} else {
return self.imageNode.view
return self.view
}
}

View File

@ -478,7 +478,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
let throttledContentOffsetSignal = self.fastScrollContentOffset.get()
|> 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
|> deliverOnMainQueue).start(next: { [weak self] contentOffset in
@ -1266,7 +1266,8 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
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 {
return
}

View File

@ -464,13 +464,16 @@ private final class StickerPackContainer: ASDisplayNode {
var menuItems: [ContextMenuItem] = []
if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
if strongSelf.sendSticker != nil {
var iconName: String
let actionTitle: String
if let title = strongSelf.controller?.actionTitle {
actionTitle = title
iconName = "Chat/Context Menu/Add"
} else {
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 animationNode = (peekController.contentNode as? StickerPreviewPeekContentNode)?.animationNode {
let _ = strongSelf.sendSticker?(.standalone(media: item.file), animationNode.view, animationNode.bounds)

View File

@ -71,12 +71,16 @@ swift_library(
"//submodules/Components/BlurredBackgroundComponent",
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/Components/BundleIconComponent:BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TooltipUI",
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/Components/MetalImageView",
"//submodules/TelegramUI/Components/CameraButtonComponent",
"//submodules/Utils/VolumeButtons",
"//submodules/TelegramNotices",
"//submodules/DeviceAccess",
],
visibility = [
"//visibility:public",

View File

@ -21,6 +21,7 @@ import BundleIconComponent
import CameraButtonComponent
import VolumeButtons
import TelegramNotices
import DeviceAccess
let videoRedColor = UIColor(rgb: 0xff3b30)
@ -87,6 +88,8 @@ private final class CameraScreenComponent: CombinedComponent {
let context: AccountContext
let cameraState: CameraState
let cameraAuthorizationStatus: AccessType
let microphoneAuthorizationStatus: AccessType
let hasAppeared: Bool
let isVisible: Bool
let panelWidth: CGFloat
@ -101,6 +104,8 @@ private final class CameraScreenComponent: CombinedComponent {
init(
context: AccountContext,
cameraState: CameraState,
cameraAuthorizationStatus: AccessType,
microphoneAuthorizationStatus: AccessType,
hasAppeared: Bool,
isVisible: Bool,
panelWidth: CGFloat,
@ -114,6 +119,8 @@ private final class CameraScreenComponent: CombinedComponent {
) {
self.context = context
self.cameraState = cameraState
self.cameraAuthorizationStatus = cameraAuthorizationStatus
self.microphoneAuthorizationStatus = microphoneAuthorizationStatus
self.hasAppeared = hasAppeared
self.isVisible = isVisible
self.panelWidth = panelWidth
@ -133,6 +140,12 @@ private final class CameraScreenComponent: CombinedComponent {
if lhs.cameraState != rhs.cameraState {
return false
}
if lhs.cameraAuthorizationStatus != rhs.cameraAuthorizationStatus {
return false
}
if lhs.microphoneAuthorizationStatus != rhs.microphoneAuthorizationStatus {
return false
}
if lhs.hasAppeared != rhs.hasAppeared {
return false
}
@ -166,11 +179,7 @@ private final class CameraScreenComponent: CombinedComponent {
return image
}
}
private var cameraAuthorizationStatus: AVAuthorizationStatus = .notDetermined
private var microphoneAuthorizationStatus: AVAuthorizationStatus = .notDetermined
private var galleryAuthorizationStatus: PHAuthorizationStatus = .notDetermined
private let context: AccountContext
private let present: (ViewController) -> Void
private let completion: ActionSlot<Signal<CameraScreen.Result, NoError>>
@ -180,7 +189,7 @@ private final class CameraScreenComponent: CombinedComponent {
private let getController: () -> CameraScreen?
private var resultDisposable = MetaDisposable()
private var mediaAssetsContext: MediaAssetsContext?
fileprivate var lastGalleryAsset: PHAsset?
private var lastGalleryAssetsDisposable: Disposable?
@ -502,6 +511,7 @@ private final class CameraScreenComponent: CombinedComponent {
}
static var body: Body {
let placeholder = Child(PlaceholderComponent.self)
let cancelButton = Child(CameraButton.self)
let captureControls = Child(CaptureControlsComponent.self)
let zoomControl = Child(ZoomComponent.self)
@ -535,12 +545,51 @@ private final class CameraScreenComponent: CombinedComponent {
let panelWidth = min(component.panelWidth, 185.0)
var controlsBottomInset: CGFloat = 0.0
let previewHeight = floorToScreenPixels(availableSize.width * 1.77778)
if !isTablet {
let previewHeight = floorToScreenPixels(availableSize.width * 1.77778)
if availableSize.height < previewHeight + 30.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 {
@ -593,7 +642,8 @@ private final class CameraScreenComponent: CombinedComponent {
let captureControls = captureControls.update(
component: CaptureControlsComponent(
isTablet: isTablet,
hasAppeared: component.hasAppeared,
hasAppeared: component.hasAppeared && hasAllRequiredAccess,
hasAccess: hasAllRequiredAccess,
shutterState: shutterState,
lastGalleryAsset: state.lastGalleryAsset,
tag: captureControlsTag,
@ -739,51 +789,53 @@ private final class CameraScreenComponent: CombinedComponent {
)
}
let flashButton = flashButton.update(
component: CameraButton(
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(
if hasAllRequiredAccess {
let flashButton = flashButton.update(
component: CameraButton(
content: AnyComponentWithIdentity(
id: "dual",
component: AnyComponent(
DualIconComponent(isSelected: component.cameraState.isDualCameraEnabled)
)
),
content: flashContentComponent,
action: { [weak state] in
if let state {
state.toggleDualCamera()
state.toggleFlashMode()
}
}
).tagged(dualButtonTag),
).tagged(flashButtonTag),
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))
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(
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(
component: CameraButton(
content: AnyComponentWithIdentity(
@ -807,6 +859,8 @@ private final class CameraScreenComponent: CombinedComponent {
)
context.add(flipButton
.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
if isTablet {
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) {
self.controller = controller
@ -1312,12 +1371,33 @@ public class CameraScreen: ViewController {
}
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 {
self.cameraStateDisposable?.dispose()
self.changingPositionDisposable?.dispose()
self.idleTimerExtensionDisposable.dispose()
self.authorizationStatusDisposables.dispose()
}
private var pipPanGestureRecognizer: UIPanGestureRecognizer?
@ -1346,11 +1426,23 @@ public class CameraScreen: ViewController {
pipPanGestureRecognizer.delegate = self
self.previewContainerView.addGestureRecognizer(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 {
return
}
@ -1461,6 +1553,10 @@ public class CameraScreen: ViewController {
camera.startCapture()
self.camera = camera
if self.hasAppeared {
self.maybePresentTooltips()
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
@ -1581,7 +1677,7 @@ public class CameraScreen: ViewController {
CATransaction.begin()
CATransaction.setDisableActions(true)
self.requestUpdateLayout(hasAppeared: false, transition: .immediate)
self.requestUpdateLayout(hasAppeared: self.hasAppeared, transition: .immediate)
CATransaction.commit()
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 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) {
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
Queue.mainQueue().justDispatch {
self?.maybePresentTooltips()
}
return .dismiss(consume: true)
}
return .ignore
@ -2006,7 +2105,11 @@ public class CameraScreen: ViewController {
self.hasAppeared = hasAppeared
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(
@ -2015,6 +2118,8 @@ public class CameraScreen: ViewController {
CameraScreenComponent(
context: self.context,
cameraState: self.cameraState,
cameraAuthorizationStatus: self.cameraAuthorizationStatus,
microphoneAuthorizationStatus: self.microphoneAuthorizationStatus,
hasAppeared: self.hasAppeared,
isVisible: self.cameraIsActive && !self.hasGallery,
panelWidth: panelWidth,

View File

@ -446,6 +446,7 @@ final class CaptureControlsComponent: Component {
let isTablet: Bool
let hasAppeared: Bool
let hasAccess: Bool
let shutterState: ShutterButtonState
let lastGalleryAsset: PHAsset?
let tag: AnyObject?
@ -463,6 +464,7 @@ final class CaptureControlsComponent: Component {
init(
isTablet: Bool,
hasAppeared: Bool,
hasAccess: Bool,
shutterState: ShutterButtonState,
lastGalleryAsset: PHAsset?,
tag: AnyObject?,
@ -479,6 +481,7 @@ final class CaptureControlsComponent: Component {
) {
self.isTablet = isTablet
self.hasAppeared = hasAppeared
self.hasAccess = hasAccess
self.shutterState = shutterState
self.lastGalleryAsset = lastGalleryAsset
self.tag = tag
@ -501,6 +504,9 @@ final class CaptureControlsComponent: Component {
if lhs.hasAppeared != rhs.hasAppeared {
return false
}
if lhs.hasAccess != rhs.hasAccess {
return false
}
if lhs.shutterState != rhs.shutterState {
return false
}
@ -944,7 +950,7 @@ final class CaptureControlsComponent: Component {
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 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)
}
let alpha: CGFloat = component.hasAccess ? 1.0 : 0.3
transition.setBounds(view: shutterButtonView, bounds: CGRect(origin: .zero, size: shutterButtonFrame.size))
transition.setPosition(view: shutterButtonView, position: shutterButtonFrame.center)
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 {

View File

@ -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)
}
}

View File

@ -221,7 +221,10 @@ final class StickerPaneSearchContentNode: ASDisplayNode, PaneSearchContentNode {
if let strongSelf = self {
strongSelf.view.window?.endEditing(true)
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 {
return strongSelf.interaction.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil, [])
} else {

View File

@ -14,8 +14,13 @@ private func fullEntityMediaPath(_ path: String) -> String {
public final class DrawingStickerEntity: DrawingEntity, Codable {
public enum Content: Equatable {
public enum ImageType: Equatable {
case sticker
case rectangle
case dualPhoto
}
case file(TelegramMediaFile)
case image(UIImage, Bool)
case image(UIImage, ImageType)
case video(String, UIImage?, Bool)
case dualVideoReference
@ -27,9 +32,9 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
} else {
return false
}
case let .image(lhsImage, lhsIsRectangle):
if case let .image(rhsImage, rhsIsRectangle) = rhs {
return lhsImage === rhsImage && lhsIsRectangle == rhsIsRectangle
case let .image(lhsImage, lhsImageType):
if case let .image(rhsImage, rhsImageType) = rhs {
return lhsImage === rhsImage && lhsImageType == rhsImageType
} else {
return false
}
@ -56,6 +61,7 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
case videoImagePath
case videoMirrored
case isRectangle
case isDualPhoto
case dualVideo
case referenceDrawingSize
case position
@ -121,8 +127,8 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
public var isRectangle: Bool {
switch self.content {
case let .image(_, isRectangle):
return isRectangle
case let .image(_, imageType):
return imageType == .rectangle
default:
return false
}
@ -157,7 +163,16 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
self.content = .file(file)
} 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
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) {
var imageValue: UIImage?
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 {
case let .file(file):
try container.encode(file, forKey: .file)
case let .image(image, isRectangle):
case let .image(image, imageType):
let imagePath = "\(self.uuid).png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() {
@ -190,7 +205,14 @@ public final class DrawingStickerEntity: DrawingEntity, Codable {
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
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):
try container.encode(path, forKey: .videoPath)
let imagePath = "\(self.uuid).jpg"

View File

@ -481,7 +481,8 @@ public final class MediaEditor {
if player == nil {
self.updateRenderChain()
self.maybeGeneratePersonSegmentation(image)
let _ = image
// self.maybeGeneratePersonSegmentation(image)
}
if let player {

View File

@ -1167,13 +1167,13 @@ final class MediaEditorScreenComponent: Component {
switch data {
case let .sticker(image, _):
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)
self.deactivateInput()
}
case let .images(images):
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)
self.deactivateInput()
}
@ -1925,8 +1925,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
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))
}
})
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, false))
}, scale: 1.0)
let imageEntity = DrawingStickerEntity(content: .image(image ?? additionalImage, .dualPhoto))
imageEntity.referenceDrawingSize = storyDimensions
imageEntity.scale = 1.625
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
if let self, let image {
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 {
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 {
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?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
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
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?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
self?.dismiss()
@ -4110,7 +4110,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
let images = imageItems as! [UIImage]
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)
}
}
}

View File

@ -978,7 +978,7 @@ final class ShareWithPeersScreenComponent: Component {
}
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 emptyAnimationHeight = 148.0
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
@ -1280,10 +1280,14 @@ final class ShareWithPeersScreenComponent: Component {
)
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 {
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
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
guard let self else {
@ -1335,7 +1339,7 @@ final class ShareWithPeersScreenComponent: Component {
sideInset: sideInset,
title: "Name",
peer: nil,
subtitle: "sub",
subtitle: self.searchStateContext != nil ? "" : "sub",
subtitleAccessory: .none,
presence: nil,
selectionState: .editing(isSelected: false, isTinted: false),
@ -1753,7 +1757,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
case stories(editing: Bool)
case chats
case contacts(EngineStoryPrivacy.Base)
case search(String)
case search(query: String, onlyContacts: Bool)
}
fileprivate var stateValue: State?
@ -1889,7 +1893,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
self.readySubject.set(true)
})
case let .search(query):
case let .search(query, _):
self.stateDisposable = (context.engine.contacts.searchLocalPeers(query: query)
|> deliverOnMainQueue).start(next: { [weak self] peers in
guard let self else {