Various improvements

This commit is contained in:
Ilya Laktyushin 2023-07-07 07:02:54 +02:00
parent e6e7d52ebf
commit dc0d4d25bf
20 changed files with 789 additions and 609 deletions

View File

@ -899,7 +899,9 @@ public protocol SharedAccountContext: AnyObject {
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], loadedStickerPacks: [LoadedStickerPack], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
func makeMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController func makeMediaPickerScreen(context: AccountContext, completion: @escaping (Any) -> Void) -> ViewController
func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController

View File

@ -3,6 +3,7 @@ import UIKit
import SwiftSignalKit import SwiftSignalKit
import AVFoundation import AVFoundation
import CoreImage import CoreImage
import TelegramCore
final class CameraSession { final class CameraSession {
private let singleSession: AVCaptureSession? private let singleSession: AVCaptureSession?
@ -83,10 +84,8 @@ final class CameraDeviceContext {
private var preferredMaxDimensions: CMVideoDimensions { private var preferredMaxDimensions: CMVideoDimensions {
if self.additional { if self.additional {
//if case .iPhoneXS = DeviceModel.current { //if case .iPhoneXS = DeviceModel.current {
return CMVideoDimensions(width: 1440, height: 1080) // return CMVideoDimensions(width: 1440, height: 1080)
//} else { return CMVideoDimensions(width: 1920, height: 1440)
// return CMVideoDimensions(width: 1920, height: 1440)
//}
} else { } else {
return CMVideoDimensions(width: 1920, height: 1080) return CMVideoDimensions(width: 1920, height: 1080)
} }
@ -198,13 +197,22 @@ private final class CameraContext {
self.mainDeviceContext.output.processCodes = { [weak self] codes in self.mainDeviceContext.output.processCodes = { [weak self] codes in
self?.detectedCodesPipe.putNext(codes) self?.detectedCodesPipe.putNext(codes)
} }
NotificationCenter.default.addObserver(
self,
selector: #selector(self.sessionRuntimeError),
name: .AVCaptureSessionRuntimeError,
object: self.session.session
)
} }
private var isSessionRunning = false
func startCapture() { func startCapture() {
guard !self.session.session.isRunning else { guard !self.session.session.isRunning else {
return return
} }
self.session.session.startRunning() self.session.session.startRunning()
self.isSessionRunning = self.session.session.isRunning
} }
func stopCapture(invalidate: Bool = false) { func stopCapture(invalidate: Bool = false) {
@ -516,6 +524,27 @@ private final class CameraContext {
var detectedCodes: Signal<[CameraCode], NoError> { var detectedCodes: Signal<[CameraCode], NoError> {
return self.detectedCodesPipe.signal() return self.detectedCodesPipe.signal()
} }
@objc private func sessionInterruptionEnded(notification: NSNotification) {
}
@objc private func sessionRuntimeError(notification: NSNotification) {
guard let errorValue = notification.userInfo?[AVCaptureSessionErrorKey] as? NSError else {
return
}
let error = AVError(_nsError: errorValue)
Logger.shared.log("Camera", "Runtime error: \(error)")
if error.code == .mediaServicesWereReset {
self.queue.async {
if self.isSessionRunning {
self.session.session.startRunning()
self.isSessionRunning = self.session.session.isRunning
}
}
}
}
} }
public final class Camera { public final class Camera {

View File

@ -1,4 +1,5 @@
import AVFoundation import AVFoundation
import TelegramCore
class CameraInput { class CameraInput {
var videoInput: AVCaptureDeviceInput? var videoInput: AVCaptureDeviceInput?
@ -32,6 +33,8 @@ class CameraInput {
} else { } else {
session.session.addInput(videoInput) session.session.addInput(videoInput)
} }
} else {
Logger.shared.log("Camera", "Can't add video input")
} }
} }
} }
@ -45,6 +48,8 @@ class CameraInput {
self.audioInput = audioInput self.audioInput = audioInput
if session.session.canAddInput(audioInput) { if session.session.canAddInput(audioInput) {
session.session.addInput(audioInput) session.session.addInput(audioInput)
} else {
Logger.shared.log("Camera", "Can't add audio input")
} }
} }
} }

View File

@ -5,6 +5,7 @@ import SwiftSignalKit
import CoreImage import CoreImage
import Vision import Vision
import VideoToolbox import VideoToolbox
import TelegramCore
public enum VideoCaptureResult: Equatable { public enum VideoCaptureResult: Equatable {
case finished((String, UIImage, Bool), (String, UIImage, Bool)?, Double, [(Bool, Double)], Double) case finished((String, UIImage, Bool), (String, UIImage, Bool)?, Double, [(Bool, Double)], Double)
@ -124,6 +125,8 @@ final class CameraOutput: NSObject {
session.session.addOutput(self.videoOutput) session.session.addOutput(self.videoOutput)
} }
self.videoOutput.setSampleBufferDelegate(self, queue: self.queue) self.videoOutput.setSampleBufferDelegate(self, queue: self.queue)
} else {
Logger.shared.log("Camera", "Can't add video output")
} }
if audio, session.session.canAddOutput(self.audioOutput) { if audio, session.session.canAddOutput(self.audioOutput) {
session.session.addOutput(self.audioOutput) session.session.addOutput(self.audioOutput)
@ -135,6 +138,8 @@ final class CameraOutput: NSObject {
} else { } else {
session.session.addOutput(self.photoOutput) session.session.addOutput(self.photoOutput)
} }
} else {
Logger.shared.log("Camera", "Can't add photo output")
} }
if metadata, session.session.canAddOutput(self.metadataOutput) { if metadata, session.session.canAddOutput(self.metadataOutput) {
session.session.addOutput(self.metadataOutput) session.session.addOutput(self.metadataOutput)
@ -152,6 +157,8 @@ final class CameraOutput: NSObject {
if session.session.canAddConnection(previewConnection) { if session.session.canAddConnection(previewConnection) {
session.session.addConnection(previewConnection) session.session.addConnection(previewConnection)
self.previewConnection = previewConnection self.previewConnection = previewConnection
} else {
Logger.shared.log("Camera", "Can't add preview connection")
} }
} }
@ -159,6 +166,8 @@ final class CameraOutput: NSObject {
if session.session.canAddConnection(videoConnection) { if session.session.canAddConnection(videoConnection) {
session.session.addConnection(videoConnection) session.session.addConnection(videoConnection)
self.videoConnection = videoConnection self.videoConnection = videoConnection
} else {
Logger.shared.log("Camera", "Can't add video connection")
} }
if photo { if photo {
@ -168,6 +177,8 @@ final class CameraOutput: NSObject {
self.photoConnection = photoConnection self.photoConnection = photoConnection
} }
} }
} else {
Logger.shared.log("Camera", "Can't get video port")
} }
} }
} }

View File

@ -0,0 +1,375 @@
import Foundation
import UIKit
private let snapTimeout = 1.0
class DrawingEntitySnapTool {
enum SnapType {
case centerX
case centerY
case top
case left
case right
case bottom
case rotation(CGFloat?)
static var allPositionTypes: [SnapType] {
return [
.centerX,
.centerY,
.top,
.left,
.right,
.bottom
]
}
}
struct SnapState {
let skipped: CGFloat
let waitForLeave: Bool
}
private var topEdgeState: SnapState?
private var leftEdgeState: SnapState?
private var rightEdgeState: SnapState?
private var bottomEdgeState: SnapState?
private var xState: SnapState?
private var yState: SnapState?
private var rotationState: (angle: CGFloat, skipped: CGFloat, waitForLeave: Bool)?
var onSnapUpdated: (SnapType, Bool) -> Void = { _, _ in }
var previousTopEdgeSnapTimestamp: Double?
var previousLeftEdgeSnapTimestamp: Double?
var previousRightEdgeSnapTimestamp: Double?
var previousBottomEdgeSnapTimestamp: Double?
var previousXSnapTimestamp: Double?
var previousYSnapTimestamp: Double?
var previousRotationSnapTimestamp: Double?
func reset() {
self.topEdgeState = nil
self.leftEdgeState = nil
self.rightEdgeState = nil
self.bottomEdgeState = nil
self.xState = nil
self.yState = nil
for type in SnapType.allPositionTypes {
self.onSnapUpdated(type, false)
}
}
func rotationReset() {
self.rotationState = nil
self.onSnapUpdated(.rotation(nil), false)
}
func maybeSkipFromStart(entityView: DrawingEntityView, position: CGPoint) {
self.topEdgeState = nil
self.leftEdgeState = nil
self.rightEdgeState = nil
self.bottomEdgeState = nil
self.xState = nil
self.yState = nil
let snapXDelta: CGFloat = (entityView.superview?.frame.width ?? 0.0) * 0.02
let snapYDelta: CGFloat = (entityView.superview?.frame.width ?? 0.0) * 0.02
if let snapLocation = (entityView.superview as? DrawingEntitiesView)?.getEntityCenterPosition() {
if position.x > snapLocation.x - snapXDelta && position.x < snapLocation.x + snapXDelta {
self.xState = SnapState(skipped: 0.0, waitForLeave: true)
}
if position.y > snapLocation.y - snapYDelta && position.y < snapLocation.y + snapYDelta {
self.yState = SnapState(skipped: 0.0, waitForLeave: true)
}
}
}
func update(entityView: DrawingEntityView, velocity: CGPoint, delta: CGPoint, updatedPosition: CGPoint, size: CGSize) -> CGPoint {
var updatedPosition = updatedPosition
guard let snapCenterLocation = (entityView.superview as? DrawingEntitiesView)?.getEntityCenterPosition() else {
return updatedPosition
}
let snapEdgeLocations = (entityView.superview as? DrawingEntitiesView)?.getEntityEdgePositions()
let currentTimestamp = CACurrentMediaTime()
let snapDelta: CGFloat = (entityView.superview?.frame.width ?? 0.0) * 0.02
let snapVelocity: CGFloat = snapDelta * 12.0
let snapSkipTranslation: CGFloat = snapDelta * 2.0
let topPoint = updatedPosition.y - size.height / 2.0
let leftPoint = updatedPosition.x - size.width / 2.0
let rightPoint = updatedPosition.x + size.width / 2.0
let bottomPoint = updatedPosition.y + size.height / 2.0
func process(
state: SnapState?,
velocity: CGFloat,
delta: CGFloat,
value: CGFloat,
snapVelocity: CGFloat,
snapToValue: CGFloat?,
snapDelta: CGFloat,
snapSkipTranslation: CGFloat,
previousSnapTimestamp: Double?,
onSnapUpdated: (Bool) -> Void
) -> (
value: CGFloat,
state: SnapState?,
snapTimestamp: Double?
) {
var updatedValue = value
var updatedState = state
var updatedPreviousSnapTimestamp = previousSnapTimestamp
if abs(velocity) < snapVelocity || state?.waitForLeave == true {
if let snapToValue {
if let state {
let skipped = state.skipped
let waitForLeave = state.waitForLeave
if waitForLeave {
if value > snapToValue - snapDelta * 2.0 && value < snapToValue + snapDelta * 2.0 {
} else {
updatedState = nil
}
} else if abs(skipped) < snapSkipTranslation {
updatedState = SnapState(skipped: skipped + delta, waitForLeave: false)
updatedValue = snapToValue
} else {
updatedState = SnapState(skipped: snapSkipTranslation, waitForLeave: true)
onSnapUpdated(false)
}
} else {
if value > snapToValue - snapDelta && value < snapToValue + snapDelta {
if let previousSnapTimestamp, currentTimestamp - previousSnapTimestamp < snapTimeout {
} else {
updatedPreviousSnapTimestamp = currentTimestamp
updatedState = SnapState(skipped: 0.0, waitForLeave: false)
updatedValue = snapToValue
onSnapUpdated(true)
}
}
}
}
} else {
updatedState = nil
onSnapUpdated(false)
}
return (updatedValue, updatedState, updatedPreviousSnapTimestamp)
}
let (updatedXValue, updatedXState, updatedXPreviousTimestamp) = process(
state: self.xState,
velocity: velocity.x,
delta: delta.x,
value: updatedPosition.x,
snapVelocity: snapVelocity,
snapToValue: snapCenterLocation.x,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousXSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.centerX, snapped)
}
)
self.xState = updatedXState
self.previousXSnapTimestamp = updatedXPreviousTimestamp
let (updatedYValue, updatedYState, updatedYPreviousTimestamp) = process(
state: self.yState,
velocity: velocity.y,
delta: delta.y,
value: updatedPosition.y,
snapVelocity: snapVelocity,
snapToValue: snapCenterLocation.y,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousYSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.centerY, snapped)
}
)
self.yState = updatedYState
self.previousYSnapTimestamp = updatedYPreviousTimestamp
if let snapEdgeLocations {
if updatedXState == nil {
let (updatedXLeftEdgeValue, updatedLeftEdgeState, updatedLeftEdgePreviousTimestamp) = process(
state: self.leftEdgeState,
velocity: velocity.x,
delta: delta.x,
value: leftPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.left,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousLeftEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.left, snapped)
}
)
self.leftEdgeState = updatedLeftEdgeState
self.previousLeftEdgeSnapTimestamp = updatedLeftEdgePreviousTimestamp
if updatedLeftEdgeState != nil {
updatedPosition.x = updatedXLeftEdgeValue + size.width / 2.0
self.rightEdgeState = nil
self.previousRightEdgeSnapTimestamp = nil
} else {
let (updatedXRightEdgeValue, updatedRightEdgeState, updatedRightEdgePreviousTimestamp) = process(
state: self.rightEdgeState,
velocity: velocity.x,
delta: delta.x,
value: rightPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.right,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousRightEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.right, snapped)
}
)
self.rightEdgeState = updatedRightEdgeState
self.previousRightEdgeSnapTimestamp = updatedRightEdgePreviousTimestamp
updatedPosition.x = updatedXRightEdgeValue - size.width / 2.0
}
} else {
updatedPosition.x = updatedXValue
}
if updatedYState == nil {
let (updatedYTopEdgeValue, updatedTopEdgeState, updatedTopEdgePreviousTimestamp) = process(
state: self.topEdgeState,
velocity: velocity.y,
delta: delta.y,
value: topPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.top,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousTopEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.top, snapped)
}
)
self.topEdgeState = updatedTopEdgeState
self.previousTopEdgeSnapTimestamp = updatedTopEdgePreviousTimestamp
if updatedTopEdgeState != nil {
updatedPosition.y = updatedYTopEdgeValue + size.height / 2.0
self.bottomEdgeState = nil
self.previousBottomEdgeSnapTimestamp = nil
} else {
let (updatedYBottomEdgeValue, updatedBottomEdgeState, updatedBottomEdgePreviousTimestamp) = process(
state: self.bottomEdgeState,
velocity: velocity.y,
delta: delta.y,
value: bottomPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.bottom,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousBottomEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.bottom, snapped)
}
)
self.bottomEdgeState = updatedBottomEdgeState
self.previousBottomEdgeSnapTimestamp = updatedBottomEdgePreviousTimestamp
updatedPosition.y = updatedYBottomEdgeValue - size.height / 2.0
}
} else {
updatedPosition.y = updatedYValue
}
} else {
updatedPosition.x = updatedXValue
updatedPosition.y = updatedYValue
}
return updatedPosition
}
private let snapRotations: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
func maybeSkipFromStart(entityView: DrawingEntityView, rotation: CGFloat) {
self.rotationState = nil
let snapDelta: CGFloat = 0.25
for snapRotation in self.snapRotations {
let snapRotation = snapRotation * .pi
if rotation > snapRotation - snapDelta && rotation < snapRotation + snapDelta {
self.rotationState = (snapRotation, 0.0, true)
break
}
}
}
func update(entityView: DrawingEntityView, velocity: CGFloat, delta: CGFloat, updatedRotation: CGFloat) -> CGFloat {
var updatedRotation = updatedRotation
if updatedRotation < 0.0 {
updatedRotation = 2.0 * .pi + updatedRotation
} else if updatedRotation > 2.0 * .pi {
while updatedRotation > 2.0 * .pi {
updatedRotation -= 2.0 * .pi
}
}
let currentTimestamp = CACurrentMediaTime()
let snapDelta: CGFloat = 0.01
let snapVelocity: CGFloat = snapDelta * 35.0
let snapSkipRotation: CGFloat = snapDelta * 40.0
if abs(velocity) < snapVelocity || self.rotationState?.waitForLeave == true {
if let (snapRotation, skipped, waitForLeave) = self.rotationState {
if waitForLeave {
if updatedRotation > snapRotation - snapDelta * 2.0 && updatedRotation < snapRotation + snapDelta {
} else {
self.rotationState = nil
}
} else if abs(skipped) < snapSkipRotation {
self.rotationState = (snapRotation, skipped + delta, false)
updatedRotation = snapRotation
} else {
self.rotationState = (snapRotation, snapSkipRotation, true)
self.onSnapUpdated(.rotation(nil), false)
}
} else {
for snapRotation in self.snapRotations {
let snapRotation = snapRotation * .pi
if updatedRotation > snapRotation - snapDelta && updatedRotation < snapRotation + snapDelta {
if let previousRotationSnapTimestamp, currentTimestamp - previousRotationSnapTimestamp < snapTimeout {
} else {
self.previousRotationSnapTimestamp = currentTimestamp
self.rotationState = (snapRotation, 0.0, false)
updatedRotation = snapRotation
self.onSnapUpdated(.rotation(snapRotation), true)
}
break
}
}
}
} else {
self.rotationState = nil
self.onSnapUpdated(.rotation(nil), false)
}
return updatedRotation
}
}

View File

@ -3172,39 +3172,41 @@ public final class DrawingToolsInteraction {
} }
func presentEyedropper(retryLaterForVideo: Bool = true, dismissed: @escaping () -> Void) { func presentEyedropper(retryLaterForVideo: Bool = true, dismissed: @escaping () -> Void) {
// self.entitiesView.pause() self.entitiesView.pause()
//
// if self.isVideo && retryLaterForVideo { if self.isVideo && retryLaterForVideo {
// self.updateVideoPlayback(false) self.updateVideoPlayback(false)
// Queue.mainQueue().after(0.1) { Queue.mainQueue().after(0.1) {
// self.presentEyedropper(retryLaterForVideo: false, dismissed: dismissed) self.presentEyedropper(retryLaterForVideo: false, dismissed: dismissed)
// } }
// return return
// } }
//
// guard let currentImage = self.getCurrentImage() else { guard let currentImage = self.getCurrentImage() else {
// self.entitiesView.play() self.entitiesView.play()
// self.updateVideoPlayback(true) self.updateVideoPlayback(true)
// return return
// } }
//
// let sourceImage = generateImage(self.drawingView.imageSize, contextGenerator: { size, context in let sourceImage = generateImage(self.drawingView.imageSize, contextGenerator: { size, context in
// let bounds = CGRect(origin: .zero, size: size) let bounds = CGRect(origin: .zero, size: size)
// if let cgImage = currentImage.cgImage { if let cgImage = currentImage.cgImage {
// context.draw(cgImage, in: bounds) context.draw(cgImage, in: bounds)
// } }
// if let cgImage = self.drawingView.drawingImage?.cgImage { if let cgImage = self.drawingView.drawingImage?.cgImage {
// context.draw(cgImage, in: bounds) context.draw(cgImage, in: bounds)
// } }
// context.translateBy(x: size.width / 2.0, y: size.height / 2.0) context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
// context.scaleBy(x: 1.0, y: -1.0) context.scaleBy(x: 1.0, y: -1.0)
// context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
// self.entitiesView.layer.render(in: context) self.entitiesView.layer.render(in: context)
// }, opaque: true, scale: 1.0) }, opaque: true, scale: 1.0)
// guard let sourceImage = sourceImage else { guard let sourceImage = sourceImage else {
// return return
// } }
//
let _ = sourceImage
// let eyedropperView = EyedropperView(containerSize: controller.contentWrapperView.frame.size, drawingView: self.drawingView, sourceImage: sourceImage) // let eyedropperView = EyedropperView(containerSize: controller.contentWrapperView.frame.size, drawingView: self.drawingView, sourceImage: sourceImage)
// eyedropperView.completed = { [weak self] color in // eyedropperView.completed = { [weak self] color in
// if let self { // if let self {

View File

@ -179,12 +179,46 @@ public final class DrawingStickerEntityView: DrawingEntityView {
self.setNeedsLayout() self.setNeedsLayout()
} }
} else if let image = self.image { } else if let image = self.image {
func drawImageWithOrientation(_ image: UIImage, size: CGSize, in context: CGContext) {
let imageSize: CGSize
switch image.imageOrientation {
case .left, .leftMirrored, .right, .rightMirrored:
imageSize = CGSize(width: size.height, height: size.width)
default:
imageSize = size
}
let imageRect = CGRect(origin: .zero, size: imageSize)
switch image.imageOrientation {
case .down, .downMirrored:
context.translateBy(x: imageSize.width, y: imageSize.height)
context.rotate(by: CGFloat.pi)
case .left, .leftMirrored:
context.translateBy(x: imageSize.width, y: 0)
context.rotate(by: CGFloat.pi / 2)
case .right, .rightMirrored:
context.translateBy(x: 0, y: imageSize.height)
context.rotate(by: -CGFloat.pi / 2)
default:
break
}
// switch image.imageOrientation {
// case .leftMirrored, .rightMirrored:
// context.scaleBy(x: -1, y: 1)
// default:
// context.scaleBy(x: 1, y: -1)
// }
context.draw(image.cgImage!, in: imageRect)
}
self.imageNode.setSignal(.single({ arguments -> DrawingContext? in self.imageNode.setSignal(.single({ arguments -> DrawingContext? in
let context = DrawingContext(size: arguments.drawingSize, opaque: false, clear: true) let context = DrawingContext(size: arguments.drawingSize, opaque: false, clear: true)
context?.withFlippedContext({ ctx in context?.withFlippedContext({ ctx in
if let cgImage = image.cgImage { drawImageWithOrientation(image, size: arguments.drawingSize, in: ctx)
ctx.draw(cgImage, in: CGRect(origin: .zero, size: arguments.drawingSize))
}
}) })
return context return context
})) }))
@ -644,385 +678,15 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
self.border.lineWidth = 2.0 / self.scale self.border.lineWidth = 2.0 / self.scale
if entity.isRectangle { if entity.isRectangle {
let aspectRatio = entity.baseSize.width / entity.baseSize.height
let width: CGFloat = self.bounds.width - inset * 2.0 let width: CGFloat = self.bounds.width - inset * 2.0
let height: CGFloat = self.bounds.height - inset * 2.0 let height: CGFloat = self.bounds.height / aspectRatio - inset * 2.0
let cornerRadius: CGFloat = 12.0 - self.scale let cornerRadius: CGFloat = 12.0 - self.scale
self.border.path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: width, height: height)), cornerRadius: cornerRadius).cgPath self.border.path = UIBezierPath(roundedRect: CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - width) / 2.0), y: floorToScreenPixels((self.bounds.height - height) / 2.0)), size: CGSize(width: width, height: height)), cornerRadius: cornerRadius).cgPath
} else { } else {
self.border.path = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: self.bounds.width - inset * 2.0, height: self.bounds.height - inset * 2.0))).cgPath self.border.path = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: inset, y: inset), size: CGSize(width: self.bounds.width - inset * 2.0, height: self.bounds.height - inset * 2.0))).cgPath
} }
} }
} }
private let snapTimeout = 1.0
class DrawingEntitySnapTool {
enum SnapType {
case centerX
case centerY
case top
case left
case right
case bottom
case rotation(CGFloat?)
static var allPositionTypes: [SnapType] {
return [
.centerX,
.centerY,
.top,
.left,
.right,
.bottom
]
}
}
struct SnapState {
let skipped: CGFloat
let waitForLeave: Bool
}
private var topEdgeState: SnapState?
private var leftEdgeState: SnapState?
private var rightEdgeState: SnapState?
private var bottomEdgeState: SnapState?
private var xState: SnapState?
private var yState: SnapState?
private var rotationState: (angle: CGFloat, skipped: CGFloat, waitForLeave: Bool)?
var onSnapUpdated: (SnapType, Bool) -> Void = { _, _ in }
var previousTopEdgeSnapTimestamp: Double?
var previousLeftEdgeSnapTimestamp: Double?
var previousRightEdgeSnapTimestamp: Double?
var previousBottomEdgeSnapTimestamp: Double?
var previousXSnapTimestamp: Double?
var previousYSnapTimestamp: Double?
var previousRotationSnapTimestamp: Double?
func reset() {
self.topEdgeState = nil
self.leftEdgeState = nil
self.rightEdgeState = nil
self.bottomEdgeState = nil
self.xState = nil
self.yState = nil
for type in SnapType.allPositionTypes {
self.onSnapUpdated(type, false)
}
}
func rotationReset() {
self.rotationState = nil
self.onSnapUpdated(.rotation(nil), false)
}
func maybeSkipFromStart(entityView: DrawingEntityView, position: CGPoint) {
self.topEdgeState = nil
self.leftEdgeState = nil
self.rightEdgeState = nil
self.bottomEdgeState = nil
self.xState = nil
self.yState = nil
let snapXDelta: CGFloat = (entityView.superview?.frame.width ?? 0.0) * 0.02
let snapYDelta: CGFloat = (entityView.superview?.frame.width ?? 0.0) * 0.02
if let snapLocation = (entityView.superview as? DrawingEntitiesView)?.getEntityCenterPosition() {
if position.x > snapLocation.x - snapXDelta && position.x < snapLocation.x + snapXDelta {
self.xState = SnapState(skipped: 0.0, waitForLeave: true)
}
if position.y > snapLocation.y - snapYDelta && position.y < snapLocation.y + snapYDelta {
self.yState = SnapState(skipped: 0.0, waitForLeave: true)
}
}
}
func update(entityView: DrawingEntityView, velocity: CGPoint, delta: CGPoint, updatedPosition: CGPoint, size: CGSize) -> CGPoint {
var updatedPosition = updatedPosition
guard let snapCenterLocation = (entityView.superview as? DrawingEntitiesView)?.getEntityCenterPosition() else {
return updatedPosition
}
let snapEdgeLocations = (entityView.superview as? DrawingEntitiesView)?.getEntityEdgePositions()
let currentTimestamp = CACurrentMediaTime()
let snapDelta: CGFloat = (entityView.superview?.frame.width ?? 0.0) * 0.02
let snapVelocity: CGFloat = snapDelta * 12.0
let snapSkipTranslation: CGFloat = snapDelta * 2.0
let topPoint = updatedPosition.y - size.height / 2.0
let leftPoint = updatedPosition.x - size.width / 2.0
let rightPoint = updatedPosition.x + size.width / 2.0
let bottomPoint = updatedPosition.y + size.height / 2.0
func process(
state: SnapState?,
velocity: CGFloat,
delta: CGFloat,
value: CGFloat,
snapVelocity: CGFloat,
snapToValue: CGFloat?,
snapDelta: CGFloat,
snapSkipTranslation: CGFloat,
previousSnapTimestamp: Double?,
onSnapUpdated: (Bool) -> Void
) -> (
value: CGFloat,
state: SnapState?,
snapTimestamp: Double?
) {
var updatedValue = value
var updatedState = state
var updatedPreviousSnapTimestamp = previousSnapTimestamp
if abs(velocity) < snapVelocity || state?.waitForLeave == true {
if let snapToValue {
if let state {
let skipped = state.skipped
let waitForLeave = state.waitForLeave
if waitForLeave {
if value > snapToValue - snapDelta * 2.0 && value < snapToValue + snapDelta * 2.0 {
} else {
updatedState = nil
}
} else if abs(skipped) < snapSkipTranslation {
updatedState = SnapState(skipped: skipped + delta, waitForLeave: false)
updatedValue = snapToValue
} else {
updatedState = SnapState(skipped: snapSkipTranslation, waitForLeave: true)
onSnapUpdated(false)
}
} else {
if value > snapToValue - snapDelta && value < snapToValue + snapDelta {
if let previousSnapTimestamp, currentTimestamp - previousSnapTimestamp < snapTimeout {
} else {
updatedPreviousSnapTimestamp = currentTimestamp
updatedState = SnapState(skipped: 0.0, waitForLeave: false)
updatedValue = snapToValue
onSnapUpdated(true)
}
}
}
}
} else {
updatedState = nil
onSnapUpdated(false)
}
return (updatedValue, updatedState, updatedPreviousSnapTimestamp)
}
let (updatedXValue, updatedXState, updatedXPreviousTimestamp) = process(
state: self.xState,
velocity: velocity.x,
delta: delta.x,
value: updatedPosition.x,
snapVelocity: snapVelocity,
snapToValue: snapCenterLocation.x,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousXSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.centerX, snapped)
}
)
self.xState = updatedXState
self.previousXSnapTimestamp = updatedXPreviousTimestamp
let (updatedYValue, updatedYState, updatedYPreviousTimestamp) = process(
state: self.yState,
velocity: velocity.y,
delta: delta.y,
value: updatedPosition.y,
snapVelocity: snapVelocity,
snapToValue: snapCenterLocation.y,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousYSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.centerY, snapped)
}
)
self.yState = updatedYState
self.previousYSnapTimestamp = updatedYPreviousTimestamp
if let snapEdgeLocations {
if updatedXState == nil {
let (updatedXLeftEdgeValue, updatedLeftEdgeState, updatedLeftEdgePreviousTimestamp) = process(
state: self.leftEdgeState,
velocity: velocity.x,
delta: delta.x,
value: leftPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.left,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousLeftEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.left, snapped)
}
)
self.leftEdgeState = updatedLeftEdgeState
self.previousLeftEdgeSnapTimestamp = updatedLeftEdgePreviousTimestamp
if updatedLeftEdgeState != nil {
updatedPosition.x = updatedXLeftEdgeValue + size.width / 2.0
self.rightEdgeState = nil
self.previousRightEdgeSnapTimestamp = nil
} else {
let (updatedXRightEdgeValue, updatedRightEdgeState, updatedRightEdgePreviousTimestamp) = process(
state: self.rightEdgeState,
velocity: velocity.x,
delta: delta.x,
value: rightPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.right,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousRightEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.right, snapped)
}
)
self.rightEdgeState = updatedRightEdgeState
self.previousRightEdgeSnapTimestamp = updatedRightEdgePreviousTimestamp
updatedPosition.x = updatedXRightEdgeValue - size.width / 2.0
}
} else {
updatedPosition.x = updatedXValue
}
if updatedYState == nil {
let (updatedYTopEdgeValue, updatedTopEdgeState, updatedTopEdgePreviousTimestamp) = process(
state: self.topEdgeState,
velocity: velocity.y,
delta: delta.y,
value: topPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.top,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousTopEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.top, snapped)
}
)
self.topEdgeState = updatedTopEdgeState
self.previousTopEdgeSnapTimestamp = updatedTopEdgePreviousTimestamp
if updatedTopEdgeState != nil {
updatedPosition.y = updatedYTopEdgeValue + size.height / 2.0
self.bottomEdgeState = nil
self.previousBottomEdgeSnapTimestamp = nil
} else {
let (updatedYBottomEdgeValue, updatedBottomEdgeState, updatedBottomEdgePreviousTimestamp) = process(
state: self.bottomEdgeState,
velocity: velocity.y,
delta: delta.y,
value: bottomPoint,
snapVelocity: snapVelocity,
snapToValue: snapEdgeLocations.bottom,
snapDelta: snapDelta,
snapSkipTranslation: snapSkipTranslation,
previousSnapTimestamp: self.previousBottomEdgeSnapTimestamp,
onSnapUpdated: { [weak self] snapped in
self?.onSnapUpdated(.bottom, snapped)
}
)
self.bottomEdgeState = updatedBottomEdgeState
self.previousBottomEdgeSnapTimestamp = updatedBottomEdgePreviousTimestamp
updatedPosition.y = updatedYBottomEdgeValue - size.height / 2.0
}
} else {
updatedPosition.y = updatedYValue
}
} else {
updatedPosition.x = updatedXValue
updatedPosition.y = updatedYValue
}
return updatedPosition
}
private let snapRotations: [CGFloat] = [0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0]
func maybeSkipFromStart(entityView: DrawingEntityView, rotation: CGFloat) {
self.rotationState = nil
let snapDelta: CGFloat = 0.25
for snapRotation in self.snapRotations {
let snapRotation = snapRotation * .pi
if rotation > snapRotation - snapDelta && rotation < snapRotation + snapDelta {
self.rotationState = (snapRotation, 0.0, true)
break
}
}
}
func update(entityView: DrawingEntityView, velocity: CGFloat, delta: CGFloat, updatedRotation: CGFloat) -> CGFloat {
var updatedRotation = updatedRotation
if updatedRotation < 0.0 {
updatedRotation = 2.0 * .pi + updatedRotation
} else if updatedRotation > 2.0 * .pi {
while updatedRotation > 2.0 * .pi {
updatedRotation -= 2.0 * .pi
}
}
let currentTimestamp = CACurrentMediaTime()
let snapDelta: CGFloat = 0.01
let snapVelocity: CGFloat = snapDelta * 35.0
let snapSkipRotation: CGFloat = snapDelta * 40.0
if abs(velocity) < snapVelocity || self.rotationState?.waitForLeave == true {
if let (snapRotation, skipped, waitForLeave) = self.rotationState {
if waitForLeave {
if updatedRotation > snapRotation - snapDelta * 2.0 && updatedRotation < snapRotation + snapDelta {
} else {
self.rotationState = nil
}
} else if abs(skipped) < snapSkipRotation {
self.rotationState = (snapRotation, skipped + delta, false)
updatedRotation = snapRotation
} else {
self.rotationState = (snapRotation, snapSkipRotation, true)
self.onSnapUpdated(.rotation(nil), false)
}
} else {
for snapRotation in self.snapRotations {
let snapRotation = snapRotation * .pi
if updatedRotation > snapRotation - snapDelta && updatedRotation < snapRotation + snapDelta {
if let previousRotationSnapTimestamp, currentTimestamp - previousRotationSnapTimestamp < snapTimeout {
} else {
self.previousRotationSnapTimestamp = currentTimestamp
self.rotationState = (snapRotation, 0.0, false)
updatedRotation = snapRotation
self.onSnapUpdated(.rotation(snapRotation), true)
}
break
}
}
}
} else {
self.rotationState = nil
self.onSnapUpdated(.rotation(nil), false)
}
return updatedRotation
}
}

View File

@ -912,7 +912,13 @@ public class StickerPickerScreen: ViewController {
useOpaqueTheme: false, useOpaqueTheme: false,
hideBackground: true, hideBackground: true,
stateContext: nil, stateContext: nil,
addImage: nil addImage: { [weak self] in
if let self {
self.controller?.completion(nil)
self.controller?.dismiss(animated: true)
self.controller?.presentGallery()
}
}
) )
var stickerPeekBehavior: EmojiContentPeekBehaviorImpl? var stickerPeekBehavior: EmojiContentPeekBehaviorImpl?
@ -1170,8 +1176,12 @@ public class StickerPickerScreen: ViewController {
useOpaqueTheme: false, useOpaqueTheme: false,
hideBackground: true, hideBackground: true,
stateContext: nil, stateContext: nil,
addImage: { addImage: { [weak self] in
if let self {
self.controller?.completion(nil)
self.controller?.dismiss(animated: true)
self.controller?.presentGallery()
}
} }
) )
@ -1627,6 +1637,8 @@ public class StickerPickerScreen: ViewController {
public var completion: (DrawingStickerEntity.Content?) -> Void = { _ in } public var completion: (DrawingStickerEntity.Content?) -> Void = { _ in }
public var presentGallery: () -> Void = { }
public init(context: AccountContext, inputData: Signal<StickerPickerInputData, NoError>) { public init(context: AccountContext, inputData: Signal<StickerPickerInputData, NoError>) {
self.context = context self.context = context
self.theme = defaultDarkColorPresentationTheme self.theme = defaultDarkColorPresentationTheme

View File

@ -137,6 +137,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
case `default` case `default`
case wallpaper case wallpaper
case story case story
case addImage
} }
case assets(PHAssetCollection?, AssetsMode) case assets(PHAssetCollection?, AssetsMode)
@ -250,7 +251,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.presentationData = controller.presentationData self.presentationData = controller.presentationData
var assetType: PHAssetMediaType? var assetType: PHAssetMediaType?
if case let .assets(_, mode) = controller.subject, case .wallpaper = mode { if case let .assets(_, mode) = controller.subject, [.wallpaper, .addImage].contains(mode) {
assetType = .image assetType = .image
} }
let mediaAssetsContext = MediaAssetsContext(assetType: assetType) let mediaAssetsContext = MediaAssetsContext(assetType: assetType)
@ -404,7 +405,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.gridNode.scrollView.alwaysBounceVertical = true self.gridNode.scrollView.alwaysBounceVertical = true
self.gridNode.scrollView.showsVerticalScrollIndicator = false self.gridNode.scrollView.showsVerticalScrollIndicator = false
if case let .assets(_, mode) = controller.subject, [.wallpaper, .story].contains(mode) { if case let .assets(_, mode) = controller.subject, [.wallpaper .addImage, .story].contains(mode) {
} else { } else {
let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>() let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>()
@ -1379,6 +1380,9 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.titleView.title = presentationData.strings.Attachment_Gallery self.titleView.title = presentationData.strings.Attachment_Gallery
case .wallpaper: case .wallpaper:
self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
case .addImage:
//TODO:localize
self.titleView.title = "Add Image"
} }
} }
} else { } else {
@ -2184,6 +2188,28 @@ public func wallpaperMediaPickerController(
return controller return controller
} }
public func mediaPickerController(
context: AccountContext,
completion: @escaping (Any) -> Void
) -> ViewController {
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
let updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>) = (presentationData, .single(presentationData))
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
return nil
})
controller.requestController = { _, present in
let mediaPickerController = MediaPickerScreen(context: context, updatedPresentationData: updatedPresentationData, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .addImage), mainButtonState: nil, mainButtonAction: nil)
mediaPickerController.customSelection = { controller, result in
completion(result)
controller.dismiss(animated: true)
}
present(mediaPickerController, mediaPickerController.mediaPickerContext)
}
controller.navigationPresentation = .flatModal
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
return controller
}
public func storyMediaPickerController( public func storyMediaPickerController(
context: AccountContext, context: AccountContext,
getSourceRect: @escaping () -> CGRect, getSourceRect: @escaping () -> CGRect,

View File

@ -113,6 +113,7 @@ swift_library(
"//submodules/MediaPickerUI:MediaPickerUI", "//submodules/MediaPickerUI:MediaPickerUI",
"//submodules/ImageBlur:ImageBlur", "//submodules/ImageBlur:ImageBlur",
"//submodules/AttachmentUI:AttachmentUI", "//submodules/AttachmentUI:AttachmentUI",
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -53,6 +53,8 @@ extension SettingsSearchableItemIcon {
return PresentationResourcesSettings.devices return PresentationResourcesSettings.devices
case .premium: case .premium:
return PresentationResourcesSettings.premium return PresentationResourcesSettings.premium
case .stories:
return PresentationResourcesSettings.stories
} }
} }
} }

View File

@ -21,6 +21,7 @@ import NotificationPeerExceptionController
import QrCodeUI import QrCodeUI
import PremiumUI import PremiumUI
import StorageUsageScreen import StorageUsageScreen
import PeerInfoStoryGridScreen
enum SettingsSearchableItemIcon { enum SettingsSearchableItemIcon {
case profile case profile
@ -41,6 +42,7 @@ enum SettingsSearchableItemIcon {
case deleteAccount case deleteAccount
case devices case devices
case premium case premium
case stories
} }
public enum SettingsSearchableItemId: Hashable { public enum SettingsSearchableItemId: Hashable {
@ -62,6 +64,7 @@ public enum SettingsSearchableItemId: Hashable {
case deleteAccount(Int32) case deleteAccount(Int32)
case devices(Int32) case devices(Int32)
case premium(Int32) case premium(Int32)
case stories(Int32)
private var namespace: Int32 { private var namespace: Int32 {
switch self { switch self {
@ -101,6 +104,8 @@ public enum SettingsSearchableItemId: Hashable {
return 18 return 18
case .premium: case .premium:
return 19 return 19
case .stories:
return 20
} }
} }
@ -123,7 +128,8 @@ public enum SettingsSearchableItemId: Hashable {
let .chatFolders(id), let .chatFolders(id),
let .deleteAccount(id), let .deleteAccount(id),
let .devices(id), let .devices(id),
let .premium(id): let .premium(id),
let .stories(id):
return id return id
} }
} }
@ -172,6 +178,8 @@ public enum SettingsSearchableItemId: Hashable {
self = .devices(id) self = .devices(id)
case 19: case 19:
self = .premium(id) self = .premium(id)
case 20:
self = .stories(id)
default: default:
return nil return nil
} }
@ -340,6 +348,25 @@ private func premiumSearchableItems(context: AccountContext) -> [SettingsSearcha
return result return result
} }
private func storiesSearchableItems(context: AccountContext) -> [SettingsSearchableItem] {
let icon: SettingsSearchableItemIcon = .stories
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
var result: [SettingsSearchableItem] = []
//TODO:localize
result.append(SettingsSearchableItem(id: .stories(0), title: "My Stories", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .saved))
}))
result.append(SettingsSearchableItem(id: .stories(1), title: "Stories Archive", alternate: synonyms(strings.SettingsSearch_Synonyms_Premium), icon: icon, breadcrumbs: [], present: { context, _, present in
present(.push, PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: .archive))
}))
return result
}
private func callSearchableItems(context: AccountContext) -> [SettingsSearchableItem] { private func callSearchableItems(context: AccountContext) -> [SettingsSearchableItem] {
let icon: SettingsSearchableItemIcon = .calls let icon: SettingsSearchableItemIcon = .calls
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
@ -1008,6 +1035,9 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList
let premiumItems = premiumSearchableItems(context: context) let premiumItems = premiumSearchableItems(context: context)
allItems.append(contentsOf: premiumItems) allItems.append(contentsOf: premiumItems)
let storiesItems = storiesSearchableItems(context: context)
allItems.append(contentsOf: storiesItems)
if watchAppInstalled { if watchAppInstalled {
let watch = SettingsSearchableItem(id: .watch(0), title: strings.Settings_AppleWatch, alternate: synonyms(strings.SettingsSearch_Synonyms_Watch), icon: .watch, breadcrumbs: [], present: { context, _, present in let watch = SettingsSearchableItem(id: .watch(0), title: strings.Settings_AppleWatch, alternate: synonyms(strings.SettingsSearch_Synonyms_Watch), icon: .watch, breadcrumbs: [], present: { context, _, present in

View File

@ -1370,7 +1370,7 @@ public class CameraScreen: ViewController {
private var isDismissing = false private var isDismissing = false
@objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) { @objc private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let controller = self.controller else { guard let controller = self.controller, let layout = self.validLayout else {
return return
} }
let translation = gestureRecognizer.translation(in: gestureRecognizer.view) let translation = gestureRecognizer.translation(in: gestureRecognizer.view)
@ -1380,7 +1380,7 @@ public class CameraScreen: ViewController {
case .changed: case .changed:
if self.componentExternalState.isRecording { if self.componentExternalState.isRecording {
} else { } else if case .compact = layout.metrics.widthClass {
if translation.x < -10.0 || self.isDismissing { if translation.x < -10.0 || self.isDismissing {
self.isDismissing = true self.isDismissing = true
let transitionFraction = 1.0 - max(0.0, translation.x * -1.0) / self.frame.width let transitionFraction = 1.0 - max(0.0, translation.x * -1.0) / self.frame.width
@ -2179,7 +2179,7 @@ public class CameraScreen: ViewController {
if let current = self.galleryController { if let current = self.galleryController {
controller = current controller = current
} else { } else {
controller = self.context.sharedContext.makeMediaPickerScreen(context: self.context, getSourceRect: { [weak self] in controller = self.context.sharedContext.makeStoryMediaPickerScreen(context: self.context, getSourceRect: { [weak self] in
if let self { if let self {
if let galleryButton = self.node.componentHost.findTaggedView(tag: galleryButtonTag) { if let galleryButton = self.node.componentHost.findTaggedView(tag: galleryButtonTag) {
return galleryButton.convert(galleryButton.bounds, to: self.view).offsetBy(dx: 0.0, dy: -15.0) return galleryButton.convert(galleryButton.bounds, to: self.view).offsetBy(dx: 0.0, dy: -15.0)

View File

@ -588,6 +588,18 @@ public final class EntityKeyboardComponent: Component {
} }
).minSize(CGSize(width: 38.0, height: 38.0))))) ).minSize(CGSize(width: 38.0, height: 38.0)))))
} }
if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage {
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent(
name: "Media Editor/AddImage",
tintColor: component.theme.chat.inputMediaPanel.panelIconColor,
maxSize: nil
)),
action: {
addImage()
}
).minSize(CGSize(width: 38.0, height: 38.0)))))
}
} }
let deleteBackwards = component.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards let deleteBackwards = component.emojiContent?.inputInteractionHolder.inputInteraction?.deleteBackwards
@ -689,9 +701,8 @@ public final class EntityKeyboardComponent: Component {
component.switchToTextInput() component.switchToTextInput()
} }
).minSize(CGSize(width: 38.0, height: 38.0))))) ).minSize(CGSize(width: 38.0, height: 38.0)))))
} } else if let addImage = component.emojiContent?.inputInteractionHolder.inputInteraction?.addImage {
if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage { contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(Button(
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "image", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent( content: AnyComponent(BundleIconComponent(
name: "Media Editor/AddImage", name: "Media Editor/AddImage",
tintColor: component.theme.chat.inputMediaPanel.panelIconColor, tintColor: component.theme.chat.inputMediaPanel.panelIconColor,

View File

@ -1843,6 +1843,8 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
switch subject { switch subject {
case .image, .video: case .image, .video:
isSavingAvailable = !controller.isEditingStory isSavingAvailable = !controller.isEditingStory
case .draft:
isSavingAvailable = true
default: default:
isSavingAvailable = false isSavingAvailable = false
} }
@ -2704,34 +2706,27 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.saveTooltip = tooltipController self.saveTooltip = tooltipController
} }
} }
private weak var storyArchiveTooltip: ViewController? func presentGallery() {
func presentStoryArchiveTooltip(sourceView: UIView) {
guard let controller = self.controller else { guard let controller = self.controller else {
return return
} }
let galleryController = self.context.sharedContext.makeMediaPickerScreen(context: self.context, completion: { [weak self] result in
if let storyArchiveTooltip = self.storyArchiveTooltip { guard let self, let asset = result as? PHAsset else {
storyArchiveTooltip.dismiss(animated: true) return
self.storyArchiveTooltip = nil }
}
let options = PHImageRequestOptions()
let parentFrame = self.view.convert(self.bounds, to: nil) options.deliveryMode = .highQualityFormat
let absoluteFrame = sourceView.convert(sourceView.bounds, to: nil).offsetBy(dx: -parentFrame.minX, dy: 0.0) PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { [weak self] image, _ in
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 5.0), size: CGSize()) if let self, let image {
Queue.mainQueue().async {
let text: String self.interaction?.insertEntity(DrawingStickerEntity(content: .image(image, true)), scale: 2.5)
if controller.state.privacy.pin { }
text = "Story will be kept on your page." }
} else { }
text = "Story will disappear in 24 hours."
}
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), location: .point(location, .bottom), displayDuration: .default, inset: 7.0, cornerRadius: 9.0, shouldDismissOnTouch: { _, _ in
return .ignore
}) })
self.storyArchiveTooltip = tooltipController controller.push(galleryController)
self.controller?.present(tooltipController, in: .current)
} }
func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) { func updateModalTransitionFactor(_ value: CGFloat, transition: ContainedViewLayoutTransition) {
@ -2886,6 +2881,11 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
self.updateModalTransitionFactor(transitionFactor, transition: transition) self.updateModalTransitionFactor(transitionFactor, transition: transition)
} }
} }
controller.presentGallery = { [weak self] in
if let self {
self.presentGallery()
}
}
self.stickerScreen = controller self.stickerScreen = controller
self.controller?.present(controller, in: .window(.root)) self.controller?.present(controller, in: .window(.root))
return return

View File

@ -1476,7 +1476,7 @@ final class ShareWithPeersScreenComponent: Component {
let inset: CGFloat let inset: CGFloat
if case let .stories(editing) = component.stateContext.subject { if case let .stories(editing) = component.stateContext.subject {
if editing { if editing {
inset = 430.0 inset = 446.0
} else { } else {
inset = 605.0 inset = 605.0
} }

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "photo_24.pdf", "filename" : "image_30.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -0,0 +1,160 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 5.335022 5.334991 cm
1.000000 1.000000 1.000000 scn
7.065000 19.330017 m
7.035460 19.330017 l
5.940372 19.330021 5.077796 19.330025 4.383656 19.273312 c
3.675523 19.215456 3.084329 19.095276 2.547134 18.821562 c
1.669358 18.374313 0.955704 17.660660 0.508455 16.782883 c
0.234740 16.245687 0.114562 15.654494 0.056705 14.946362 c
-0.000008 14.252222 -0.000005 13.389645 0.000000 12.294557 c
0.000000 12.265017 l
0.000000 7.065017 l
0.000000 7.035477 l
-0.000005 5.940390 -0.000008 5.077813 0.056705 4.383673 c
0.114562 3.675540 0.234740 3.084345 0.508455 2.547152 c
0.955704 1.669374 1.669358 0.955721 2.547134 0.508471 c
3.084329 0.234756 3.675523 0.114578 4.383656 0.056721 c
5.077746 0.000010 5.940250 0.000013 7.035228 0.000017 c
7.035275 0.000017 l
7.035323 0.000017 l
7.035370 0.000017 l
7.065000 0.000017 l
12.265000 0.000017 l
12.294630 0.000017 l
12.294676 0.000017 l
12.294722 0.000017 l
12.294767 0.000017 l
13.389750 0.000013 14.252252 0.000010 14.946344 0.056721 c
15.654477 0.114578 16.245672 0.234756 16.782866 0.508471 c
17.660643 0.955721 18.374296 1.669374 18.821547 2.547152 c
19.095261 3.084345 19.215439 3.675540 19.273296 4.383673 c
19.330008 5.077765 19.330004 5.940269 19.330000 7.035251 c
19.330000 7.035296 l
19.330000 7.035342 l
19.330000 7.035388 l
19.330000 7.065018 l
19.330000 12.265018 l
19.330000 12.294647 l
19.330000 12.294695 l
19.330000 12.294742 l
19.330000 12.294788 l
19.330004 13.389767 19.330008 14.252271 19.273296 14.946362 c
19.215439 15.654494 19.095261 16.245687 18.821547 16.782883 c
18.374296 17.660660 17.660643 18.374313 16.782866 18.821562 c
16.245672 19.095276 15.654477 19.215456 14.946344 19.273312 c
14.252204 19.330025 13.389627 19.330021 12.294539 19.330017 c
12.264999 19.330017 l
7.065000 19.330017 l
h
3.150942 17.636524 m
3.469394 17.798782 3.866076 17.896591 4.491960 17.947729 c
5.125607 17.999500 5.933921 18.000017 7.065000 18.000017 c
12.264999 18.000017 l
13.396078 18.000017 14.204392 17.999500 14.838039 17.947729 c
15.463923 17.896591 15.860606 17.798782 16.179058 17.636524 c
16.806580 17.316786 17.316771 16.806597 17.636507 16.179075 c
17.798767 15.860623 17.896576 15.463942 17.947712 14.838057 c
17.999483 14.204411 18.000000 13.396095 18.000000 12.265018 c
18.000000 7.065018 l
18.000000 6.309615 17.999769 5.698176 17.984194 5.186587 c
15.466902 7.867111 l
14.693245 8.690934 13.378143 8.669102 12.632261 7.820047 c
11.520865 6.554921 l
6.709533 11.633550 l
5.937637 12.448328 4.633699 12.427637 3.888045 11.588776 c
1.330000 8.710975 l
1.330000 12.265017 l
1.330000 13.396095 1.330517 14.204410 1.382288 14.838057 c
1.433425 15.463942 1.531234 15.860623 1.693493 16.179075 c
2.013231 16.806597 2.523421 17.316786 3.150942 17.636524 c
h
14.497394 6.956641 m
17.768957 3.472939 l
17.730608 3.355179 17.686632 3.249336 17.636507 3.150959 c
17.316771 2.523438 16.806580 2.013247 16.179058 1.693510 c
16.167240 1.687489 16.155315 1.681555 16.143274 1.675711 c
12.439418 5.585338 l
13.631460 6.942265 l
13.859314 7.201635 14.261054 7.208306 14.497394 6.956641 c
h
1.330082 6.709152 m
1.330711 5.761131 1.336117 5.057091 1.382288 4.491978 c
1.433425 3.866094 1.531234 3.469411 1.693493 3.150959 c
2.013231 2.523438 2.523421 2.013247 3.150942 1.693510 c
3.469394 1.531250 3.866076 1.433441 4.491960 1.382305 c
5.125607 1.330534 5.933922 1.330017 7.065000 1.330017 c
12.265000 1.330017 l
13.261761 1.330017 14.007863 1.330418 14.604662 1.365944 c
5.744016 10.718849 l
5.508215 10.967751 5.109884 10.961430 4.882100 10.705172 c
1.330082 6.709152 l
h
14.040000 12.165017 m
15.075534 12.165017 15.915000 13.004483 15.915000 14.040017 c
15.915000 15.075551 15.075534 15.915017 14.040000 15.915017 c
13.004466 15.915017 12.165000 15.075551 12.165000 14.040017 c
12.165000 13.004483 13.004466 12.165017 14.040000 12.165017 c
h
f*
n
Q
endstream
endobj
3 0 obj
3865
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003955 00000 n
0000003978 00000 n
0000004151 00000 n
0000004225 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4284
%%EOF

View File

@ -1,154 +0,0 @@
%PDF-1.7
1 0 obj
<< >>
endobj
2 0 obj
<< /Length 3 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 3.835083 3.834961 cm
1.000000 1.000000 1.000000 scn
5.465000 16.330017 m
5.436178 16.330017 l
5.436174 16.330017 l
4.620535 16.330023 3.967872 16.330027 3.440454 16.286936 c
2.899074 16.242702 2.431364 16.149773 2.001125 15.930555 c
1.311511 15.579180 0.750837 15.018506 0.399461 14.328892 c
0.180244 13.898653 0.087314 13.430943 0.043081 12.889563 c
-0.000010 12.362144 -0.000006 11.709482 0.000000 10.893843 c
0.000000 10.893839 l
0.000000 10.865017 l
0.000000 5.465017 l
0.000000 5.436195 l
-0.000006 4.620555 -0.000010 3.967890 0.043081 3.440471 c
0.087314 2.899090 0.180244 2.431380 0.399461 2.001142 c
0.750837 1.311528 1.311511 0.750854 2.001125 0.399478 c
2.431364 0.180260 2.899074 0.087330 3.440454 0.043098 c
3.967863 0.000008 4.620512 0.000011 5.436130 0.000017 c
5.436194 0.000017 l
5.465001 0.000017 l
10.865000 0.000017 l
10.893806 0.000017 l
10.893871 0.000017 l
11.709489 0.000011 12.362138 0.000008 12.889546 0.043098 c
13.430927 0.087330 13.898637 0.180260 14.328876 0.399478 c
15.018489 0.750854 15.579163 1.311528 15.930539 2.001142 c
16.149757 2.431380 16.242687 2.899091 16.286919 3.440471 c
16.330009 3.967880 16.330006 4.620529 16.330000 5.436146 c
16.330000 5.436212 l
16.330000 5.465017 l
16.330000 10.865017 l
16.330000 10.893824 l
16.330000 10.893888 l
16.330006 11.709505 16.330009 12.362154 16.286919 12.889563 c
16.242687 13.430943 16.149757 13.898653 15.930539 14.328892 c
15.579163 15.018506 15.018489 15.579180 14.328876 15.930555 c
13.898637 16.149773 13.430926 16.242702 12.889546 16.286936 c
12.362126 16.330029 11.709461 16.330023 10.893822 16.330017 c
10.865000 16.330017 l
5.465000 16.330017 l
h
2.604932 14.745517 m
2.816429 14.853280 3.089626 14.923841 3.548759 14.961352 c
4.015654 14.999499 4.613948 15.000017 5.465000 15.000017 c
10.865000 15.000017 l
11.716052 15.000017 12.314346 14.999499 12.781241 14.961352 c
13.240374 14.923841 13.513572 14.853280 13.725068 14.745517 c
14.164426 14.521652 14.521636 14.164443 14.745501 13.725084 c
14.853263 13.513588 14.923823 13.240391 14.961336 12.781259 c
14.999483 12.314363 15.000000 11.716068 15.000000 10.865017 c
15.000000 5.465017 l
15.000000 5.137442 14.999924 4.847313 14.997654 4.587710 c
12.903418 6.817746 l
12.230759 7.534022 11.087341 7.515037 10.438833 6.776826 c
9.645941 5.874260 l
5.897148 9.831320 l
5.226022 10.539732 4.092310 10.521740 3.444000 9.792391 c
1.330000 7.414142 l
1.330000 10.865017 l
1.330000 11.716068 1.330518 12.314363 1.368664 12.781259 c
1.406177 13.240391 1.476737 13.513588 1.584500 13.725084 c
1.808364 14.164443 2.165574 14.521652 2.604932 14.745517 c
h
11.933909 5.907277 m
14.833861 2.819281 l
14.807792 2.739742 14.778378 2.669474 14.745501 2.604949 c
14.521636 2.165591 14.164426 1.808381 13.725068 1.584517 c
13.714809 1.579343 l
10.564494 4.904676 l
11.438032 5.899043 l
11.568513 6.047573 11.798571 6.051393 11.933909 5.907277 c
h
1.330001 5.412228 m
1.330038 4.589128 1.331311 4.005958 1.368664 3.548775 c
1.406177 3.089643 1.476737 2.816445 1.584500 2.604949 c
1.808364 2.165591 2.165574 1.808381 2.604932 1.584517 c
2.816429 1.476754 3.089626 1.406194 3.548759 1.368681 c
4.015654 1.330534 4.613949 1.330017 5.465001 1.330017 c
10.865000 1.330017 l
11.357291 1.330017 11.765009 1.330190 12.111642 1.337719 c
4.931631 8.916620 l
4.796600 9.059153 4.568496 9.055533 4.438055 8.908787 c
1.330001 5.412228 l
h
11.664969 10.165024 m
12.493397 10.165024 13.164969 10.836597 13.164969 11.665024 c
13.164969 12.493451 12.493397 13.165024 11.664969 13.165024 c
10.836542 13.165024 10.164969 12.493451 10.164969 11.665024 c
10.164969 10.836597 10.836542 10.165024 11.664969 10.165024 c
h
f*
n
Q
endstream
endobj
3 0 obj
3694
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
/Resources 1 0 R
/Contents 2 0 R
/Parent 5 0 R
>>
endobj
5 0 obj
<< /Kids [ 4 0 R ]
/Count 1
/Type /Pages
>>
endobj
6 0 obj
<< /Pages 5 0 R
/Type /Catalog
>>
endobj
xref
0 7
0000000000 65535 f
0000000010 00000 n
0000000034 00000 n
0000003784 00000 n
0000003807 00000 n
0000003980 00000 n
0000004054 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
4113
%%EOF

View File

@ -1843,7 +1843,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker) return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, loadedStickerPacks: loadedStickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
} }
public func makeMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController { public func makeMediaPickerScreen(context: AccountContext, completion: @escaping (Any) -> Void) -> ViewController {
return mediaPickerController(context: context, completion: completion)
}
public func makeStoryMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController {
return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed) return storyMediaPickerController(context: context, getSourceRect: getSourceRect, completion: completion, dismissed: dismissed)
} }