Merge commit '74de28b1547b9bf729163034bcdc918819a86d85'

# Conflicts:
#	submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift
This commit is contained in:
Ali 2023-07-09 02:09:22 +04:00
commit 66a8c7e4a7
23 changed files with 577 additions and 343 deletions

View File

@ -10,7 +10,7 @@ final class CameraSession {
private let multiSession: Any?
let hasMultiCam: Bool
init() {
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported {
self.multiSession = AVCaptureMultiCamSession()
@ -21,6 +21,7 @@ final class CameraSession {
self.multiSession = nil
self.hasMultiCam = false
}
self.session.sessionPreset = .inputPriority
}
var session: AVCaptureSession {
@ -64,10 +65,10 @@ final class CameraDeviceContext {
self.previewView = previewView
self.device.configure(for: session, position: position, dual: !exclusive || additional)
self.device.configureDeviceFormat(maxDimensions: self.preferredMaxDimensions, maxFramerate: self.preferredMaxFrameRate)
self.input.configure(for: session, device: self.device, audio: audio)
self.output.configure(for: session, device: self.device, input: self.input, previewView: previewView, audio: audio, photo: photo, metadata: metadata)
self.device.configureDeviceFormat(maxDimensions: self.preferredMaxDimensions, maxFramerate: self.preferredMaxFrameRate)
self.output.configureVideoStabilization()
self.device.resetZoom(neutral: self.exclusive || !self.additional)
@ -83,8 +84,6 @@ final class CameraDeviceContext {
private var preferredMaxDimensions: CMVideoDimensions {
if self.additional {
//if case .iPhoneXS = DeviceModel.current {
// return CMVideoDimensions(width: 1440, height: 1080)
return CMVideoDimensions(width: 1920, height: 1440)
} else {
return CMVideoDimensions(width: 1920, height: 1080)

View File

@ -44,11 +44,8 @@ final class CameraDevice {
selectedDevice = device
}
} else {
if #available(iOS 11.1, *), dual, case .front = position, let trueDepthDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInTrueDepthCamera], mediaType: .video, position: position).devices.first {
selectedDevice = trueDepthDevice
}
if selectedDevice == nil {
selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first
selectedDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera, .builtInTelephotoCamera], mediaType: .video, position: position).devices.first
}
}

View File

@ -169,7 +169,7 @@ final class CameraOutput: NSObject {
} else {
Logger.shared.log("Camera", "Can't add video connection")
}
if photo {
let photoConnection = AVCaptureConnection(inputPorts: ports, output: self.photoOutput)
if session.session.canAddConnection(photoConnection) {

View File

@ -689,48 +689,6 @@ public class ContactsController: ViewController {
let controller = ContextController(account: self.context.account, presentationData: self.presentationData, source: .extracted(ContactsTabBarContextExtractedContentSource(controller: self, sourceNode: sourceNode)), items: .single(ContextController.Items(content: .list(items))), recognizer: nil, gesture: gesture)
self.context.sharedContext.mainWindow?.presentInGlobalOverlay(controller)
}
private var storyCameraTransitionInCoordinator: StoryCameraTransitionInCoordinator?
var hasStoryCameraTransition: Bool {
return self.storyCameraTransitionInCoordinator != nil
}
func storyCameraPanGestureChanged(transitionFraction: CGFloat) {
guard let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface else {
return
}
let coordinator: StoryCameraTransitionInCoordinator?
if let current = self.storyCameraTransitionInCoordinator {
coordinator = current
} else {
coordinator = rootController.openStoryCamera(transitionIn: nil, transitionedIn: {}, transitionOut: { [weak self] finished in
guard let self else {
return nil
}
let _ = self
// if finished, let componentView = self.chatListHeaderView() {
// if let (transitionView, _) = componentView.storyPeerListView()?.transitionViewForItem(peerId: self.context.account.peerId) {
// return StoryCameraTransitionOut(
// destinationView: transitionView,
// destinationRect: transitionView.bounds,
// destinationCornerRadius: transitionView.bounds.height * 0.5
// )
// }
// }
return nil
})
self.storyCameraTransitionInCoordinator = coordinator
}
coordinator?.updateTransitionProgress(transitionFraction)
}
func storyCameraPanGestureEnded(transitionFraction: CGFloat, velocity: CGFloat) {
if let coordinator = self.storyCameraTransitionInCoordinator {
coordinator.completeWithTransitionProgressAndVelocity(transitionFraction, velocity)
self.storyCameraTransitionInCoordinator = nil
}
}
}
private final class ContactsTabBarContextExtractedContentSource: ContextExtractedContentSource {

View File

@ -68,10 +68,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let stringsPromise = Promise<PresentationStrings>()
private var isStoryPostingAvailable = false
private var storiesPostingAvailabilityDisposable: Disposable?
weak var controller: ContactsController?
private var initialScrollingOffset: CGFloat?
@ -254,80 +251,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
}
self.openStories?(peer, sourceNode)
}
let storiesPostingAvailability = self.context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> distinctUntilChanged
|> map { appConfiguration -> StoriesConfiguration.PostingAvailability in
let storiesConfiguration = StoriesConfiguration.with(appConfiguration: appConfiguration)
return storiesConfiguration.posting
}
self.storiesPostingAvailabilityDisposable = combineLatest(queue: Queue.mainQueue(),
storiesPostingAvailability,
self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|> map { peer -> Bool in
if case let .user(user) = peer, user.isPremium {
return true
} else {
return false
}
}
|> distinctUntilChanged
).start(next: { [weak self] postingAvailability, isPremium in
if let self {
let isStoryPostingAvailable: Bool
switch postingAvailability {
case .enabled:
isStoryPostingAvailable = true
case .premium:
isStoryPostingAvailable = isPremium
case .disabled:
isStoryPostingAvailable = false
}
self.isStoryPostingAvailable = isStoryPostingAvailable
}
})
}
deinit {
self.presentationDataDisposable?.dispose()
self.storySubscriptionsDisposable?.dispose()
self.storiesPostingAvailabilityDisposable?.dispose()
}
override func didLoad() {
super.didLoad()
let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:)), allowedDirections: { _ in
return [.rightCenter, .rightEdge]
}, edgeWidth: .widthMultiplier(factor: 1.0 / 6.0, min: 22.0, max: 80.0))
panRecognizer.delegate = self
panRecognizer.delaysTouchesBegan = false
panRecognizer.cancelsTouchesInView = true
self.panRecognizer = panRecognizer
self.view.addGestureRecognizer(panRecognizer)
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if let _ = otherGestureRecognizer as? InteractiveTransitionGestureRecognizer {
return false
}
if let _ = otherGestureRecognizer as? UIPanGestureRecognizer {
return true
}
return false
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return self.isStoryPostingAvailable
}
private func updateThemeAndStrings() {
@ -594,38 +522,6 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
placeholderNode.frame = previousFrame
}
}
@objc private func panGesture(_ recognizer: UIPanGestureRecognizer) {
guard let (layout, _) = self.containerLayout else {
return
}
switch recognizer.state {
case .began:
break
case .changed:
let translation = recognizer.translation(in: self.view)
if case .compact = layout.metrics.widthClass {
let cameraIsAlreadyOpened = self.controller?.hasStoryCameraTransition ?? false
if translation.x > 0.0 {
self.controller?.storyCameraPanGestureChanged(transitionFraction: translation.x / layout.size.width)
} else if translation.x <= 0.0 && cameraIsAlreadyOpened {
self.controller?.storyCameraPanGestureChanged(transitionFraction: 0.0)
}
if cameraIsAlreadyOpened {
return
}
}
case .cancelled, .ended:
let translation = recognizer.translation(in: self.view)
let velocity = recognizer.velocity(in: self.view)
let hasStoryCameraTransition = self.controller?.hasStoryCameraTransition ?? false
if hasStoryCameraTransition {
self.controller?.storyCameraPanGestureEnded(transitionFraction: translation.x / layout.size.width, velocity: velocity.x)
}
default:
break
}
}
}
private final class ContactContextExtractedContentSource: ContextExtractedContentSource {

View File

@ -96,6 +96,7 @@ swift_library(
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
"//submodules/TelegramUI/Components/LottieComponent",
],
visibility = [
"//visibility:public",

View File

@ -215,6 +215,10 @@ final class DrawingBubbleEntitySelectionView: DrawingEntitySelectionView {
self.currentHandle = self.layer
entityView.onInteractionUpdated(true)
case .changed:
if self.currentHandle == nil {
self.currentHandle = self.layer
}
let delta = gestureRecognizer.translation(in: entityView.superview)
let velocity = gestureRecognizer.velocity(in: entityView.superview)

View File

@ -2,8 +2,11 @@ import Foundation
import UIKit
import Display
import LegacyComponents
import SwiftSignalKit
import AccountContext
import MediaEditor
import ComponentFlow
import LottieAnimationComponent
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
@ -75,6 +78,7 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
private let xAxisView = UIView()
private let yAxisView = UIView()
private let angleLayer = SimpleShapeLayer()
private let bin = ComponentView<Empty>()
public var onInteractionUpdated: (Bool) -> Void = { _ in }
public var edgePreviewUpdated: (Bool) -> Void = { _ in }
@ -582,17 +586,19 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
@objc private func handleTap(_ gestureRecognzier: UITapGestureRecognizer) {
let location = gestureRecognzier.location(in: self)
if let entityView = self.entity(at: location) {
self.selectEntity(entityView.entity)
}
}
private func entity(at location: CGPoint) -> DrawingEntityView? {
var intersectedViews: [DrawingEntityView] = []
for case let view as DrawingEntityView in self.subviews {
if view.precisePoint(inside: self.convert(location, to: view)) {
intersectedViews.append(view)
}
}
if let entityView = intersectedViews.last {
self.selectEntity(entityView.entity)
}
return intersectedViews.last
}
public func selectEntity(_ entity: DrawingEntity?) {
@ -622,8 +628,9 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
if let selectionView = entityView.makeSelectionView() {
selectionView.tapped = { [weak self, weak entityView] in
if let strongSelf = self, let entityView = entityView {
strongSelf.requestedMenuForEntityView(entityView, strongSelf.subviews.last === entityView)
if let self, let entityView = entityView {
let entityViews = self.subviews.filter { $0 is DrawingEntityView }
self.requestedMenuForEntityView(entityView, entityViews.last === entityView)
}
}
entityView.selectionView = selectionView
@ -679,10 +686,74 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
return false
}
}
public func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
let location = gestureRecognizer.location(in: self)
if let selectedEntityView = self.selectedEntityView, let selectionView = selectedEntityView.selectionView {
selectionView.handlePan(gestureRecognizer)
var isTrappedInBin = false
let scale = 100.0 / selectedEntityView.bounds.size.width
switch gestureRecognizer.state {
case .changed:
if self.updateBin(location: location) {
isTrappedInBin = true
}
case .ended, .cancelled:
let _ = self.updateBin(location: nil)
if selectedEntityView.isTrappedInBin {
selectedEntityView.layer.animateScale(from: scale, to: 0.01, duration: 0.2, removeOnCompletion: false)
selectedEntityView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
self.remove(uuid: selectedEntityView.entity.uuid)
})
self.selectEntity(nil)
Queue.mainQueue().after(0.3, {
self.onInteractionUpdated(false)
})
return
}
default:
break
}
let transition = Transition.easeInOut(duration: 0.2)
if isTrappedInBin, let binView = self.bin.view {
if !selectedEntityView.isTrappedInBin {
let refs = [
self.xAxisView,
self.yAxisView,
self.topEdgeView,
self.leftEdgeView,
self.rightEdgeView,
self.bottomEdgeView
]
for ref in refs {
transition.setAlpha(view: ref, alpha: 0.0)
}
self.edgePreviewUpdated(false)
selectedEntityView.isTrappedInBin = true
transition.setAlpha(view: selectionView, alpha: 0.0)
transition.animatePosition(view: selectionView, from: selectionView.center, to: self.convert(binView.center, to: selectionView.superview))
transition.animateScale(view: selectionView, from: 0.0, to: -0.5, additive: true)
transition.setPosition(view: selectedEntityView, position: binView.center)
let rotation = selectedEntityView.layer.transform.decompose().rotation
var transform = CATransform3DMakeScale(scale, scale, 1.0)
transform = CATransform3DRotate(transform, CGFloat(rotation.z), 0.0, 0.0, 1.0)
transition.setTransform(view: selectedEntityView, transform: transform)
}
} else {
if selectedEntityView.isTrappedInBin {
selectedEntityView.isTrappedInBin = false
transition.setAlpha(view: selectionView, alpha: 1.0)
selectedEntityView.layer.animateScale(from: scale, to: selectedEntityView.entity.scale, duration: 0.13)
}
selectionView.handlePan(gestureRecognizer)
}
} else if gestureRecognizer.numberOfTouches == 1, let viewToSelect = self.entity(at: location) {
self.selectEntity(viewToSelect.entity)
} else if gestureRecognizer.numberOfTouches == 2, let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView {
mediaEntityView.handlePan(gestureRecognizer)
}
@ -703,6 +774,40 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
selectionView.handleRotate(gestureRecognizer)
}
}
private var binWasOpened = false
private func updateBin(location: CGPoint?) -> Bool {
let binSize = CGSize(width: 180.0, height: 180.0)
let binFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.bounds.width - binSize.width) / 2.0), y: self.bounds.height - binSize.height - 20.0), size: binSize)
let wasOpened = self.binWasOpened
var isOpened = false
if let location {
isOpened = binFrame.insetBy(dx: 20.0, dy: 20.0).contains(location)
}
self.binWasOpened = isOpened
if wasOpened != isOpened {
self.hapticFeedback.impact(.medium)
}
let _ = self.bin.update(
transition: .immediate,
component: AnyComponent(EntityBinComponent(isOpened: isOpened)),
environment: {},
containerSize: binSize
)
if let binView = self.bin.view {
if binView.superview == nil {
self.addSubview(binView)
} else if self.subviews.last !== binView {
self.bringSubviewToFront(binView)
}
binView.frame = binFrame
Transition.easeInOut(duration: 0.2).setAlpha(view: binView, alpha: location != nil ? 1.0 : 0.0, delay: location == nil && wasOpened ? 0.4 : 0.0)
}
return isOpened
}
}
protocol DrawingEntityMediaView: DrawingEntityView {
@ -716,6 +821,8 @@ public class DrawingEntityView: UIView {
public let entity: DrawingEntity
var isTracking = false
var isTrappedInBin = false
public weak var selectionView: DrawingEntitySelectionView?
weak var containerView: DrawingEntitiesView?
@ -878,3 +985,97 @@ public class DrawingSelectionContainerView: UIView {
return result
}
}
private final class EntityBinComponent: Component {
typealias EnvironmentType = Empty
let isOpened: Bool
init(
isOpened: Bool
) {
self.isOpened = isOpened
}
static func ==(lhs: EntityBinComponent, rhs: EntityBinComponent) -> Bool {
if lhs.isOpened != rhs.isOpened {
return false
}
return true
}
public final class View: UIView {
private let circle = SimpleShapeLayer()
private let animation = ComponentView<Empty>()
private var component: EntityBinComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .clear
self.circle.strokeColor = UIColor.white.cgColor
self.circle.fillColor = UIColor.clear.cgColor
self.circle.lineWidth = 5.0
self.layer.addSublayer(self.circle)
self.circle.path = CGPath(ellipseIn: CGRect(origin: .zero, size: CGSize(width: 160.0, height: 160.0)).insetBy(dx: 3.0, dy: 3.0), transform: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var wasOpened = false
func update(component: EntityBinComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
self.component = component
self.state = state
if !self.wasOpened {
self.wasOpened = component.isOpened
}
let animationSize = self.animation.update(
transition: transition,
component: AnyComponent(LottieAnimationComponent(
animation: LottieAnimationComponent.AnimationItem(
name: "anim_entitybin",
mode: component.isOpened ? .animating(loop: false) : (self.wasOpened ? .animating(loop: false) : .still(position: .end)),
range: component.isOpened ? (0.0, 0.5) : (0.5, 1.0)
),
colors: [:],
size: CGSize(width: 140.0, height: 140.0)
)),
environment: {},
containerSize: CGSize(width: 140.0, height: 140.0)
)
let animationFrame = CGRect(
origin: CGPoint(x: 20.0, y: 20.0),
size: animationSize
)
if let animationView = self.animation.view {
if animationView.superview == nil {
self.addSubview(animationView)
}
transition.setPosition(view: animationView, position: animationFrame.center)
transition.setBounds(view: animationView, bounds: CGRect(origin: .zero, size: animationFrame.size))
}
let circleSize = CGSize(width: 160.0, height: 160.0)
self.circle.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - circleSize.width) / 2.0), y: floorToScreenPixels((availableSize.height - circleSize.height) / 2.0)), size: CGSize(width: 100.0, height: 100.0))
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

@ -222,6 +222,10 @@ final class DrawingSimpleShapeEntititySelectionView: DrawingEntitySelectionView
self.currentHandle = self.layer
entityView.onInteractionUpdated(true)
case .changed:
if self.currentHandle == nil {
self.currentHandle = self.layer
}
let delta = gestureRecognizer.translation(in: entityView.superview)
let velocity = gestureRecognizer.velocity(in: entityView.superview)

View File

@ -521,6 +521,10 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
self.currentHandle = self.layer
entityView.onInteractionUpdated(true)
case .changed:
if self.currentHandle == nil {
self.currentHandle = self.layer
}
let delta = gestureRecognizer.translation(in: entityView.superview)
let parentLocation = gestureRecognizer.location(in: self.superview)
let velocity = gestureRecognizer.velocity(in: entityView.superview)

View File

@ -785,6 +785,10 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView {
self.currentHandle = self.layer
entityView.onInteractionUpdated(true)
case .changed:
if self.currentHandle == nil {
self.currentHandle = self.layer
}
let delta = gestureRecognizer.translation(in: entityView.superview)
let parentLocation = gestureRecognizer.location(in: self.superview)
let velocity = gestureRecognizer.velocity(in: entityView.superview)

View File

@ -517,3 +517,30 @@ func normalizeDrawingRect(_ rect: CGRect, drawingSize: CGSize) -> CGRect {
}
return rect
}
extension CATransform3D {
func decompose() -> (translation: SIMD3<Float>, rotation: SIMD3<Float>, scale: SIMD3<Float>) {
let m0 = SIMD3<Float>(Float(self.m11), Float(self.m12), Float(self.m13))
let m1 = SIMD3<Float>(Float(self.m21), Float(self.m22), Float(self.m23))
let m2 = SIMD3<Float>(Float(self.m31), Float(self.m32), Float(self.m33))
let m3 = SIMD3<Float>(Float(self.m41), Float(self.m42), Float(self.m43))
let t = m3
let sx = length(m0)
let sy = length(m1)
let sz = length(m2)
let s = SIMD3<Float>(sx, sy, sz)
let rx = m0 / sx
let ry = m1 / sy
let rz = m2 / sz
let pitch = atan2(ry.z, rz.z)
let yaw = atan2(-rx.z, hypot(ry.z, rz.z))
let roll = atan2(rx.y, rx.x)
let r = SIMD3<Float>(pitch, yaw, roll)
return (t, r, s)
}
}

View File

@ -188,6 +188,10 @@ final class DrawingVectorEntititySelectionView: DrawingEntitySelectionView {
}
self.currentHandle = self.layer
case .changed:
if self.currentHandle == nil {
self.currentHandle = self.layer
}
if gestureRecognizer.numberOfTouches > 1 {
return
}

View File

@ -1242,7 +1242,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
case other
}
var disposed: () -> Void = {}
public var disposed: () -> Void = {}
private var didSetReady = false
private let _ready = Promise<Bool>()

View File

@ -1080,7 +1080,17 @@ public class CameraScreen: ViewController {
fileprivate var previewBlurPromise = ValuePromise<Bool>(false)
private let animateFlipAction = ActionSlot<Void>()
fileprivate var cameraIsActive = true
private let idleTimerExtensionDisposable = MetaDisposable()
fileprivate var cameraIsActive = true {
didSet {
if self.cameraIsActive {
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
} else {
self.idleTimerExtensionDisposable.set(nil)
}
}
}
fileprivate var hasGallery = false
private var presentationData: PresentationData
@ -1302,10 +1312,13 @@ public class CameraScreen: ViewController {
}
}
}
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
}
deinit {
self.changingPositionDisposable?.dispose()
self.idleTimerExtensionDisposable.dispose()
}
private var pipPanGestureRecognizer: UIPanGestureRecognizer?
@ -1731,7 +1744,7 @@ 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(3.0), 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: { point, containerFrame in
if containerFrame.contains(point) {
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
return .dismiss(consume: true)
@ -2224,6 +2237,8 @@ public class CameraScreen: ViewController {
}
}
self.push(controller)
self.requestLayout(transition: .immediate)
}
public func presentDraftTooltip() {

View File

@ -1102,12 +1102,23 @@ final class CaptureControlsComponent: Component {
lockMaskFrame = lockMaskFrame.offsetBy(dx: 8.0, dy: 0.0)
}
}
let _ = self.lockView.update(
transition: transition,
transition: .immediate,
component: AnyComponent(
LockContentComponent(
maskFrame: lockMaskFrame
CameraButton(
content: AnyComponentWithIdentity(
id: "lock",
component: AnyComponent(
LockContentComponent(
maskFrame: lockMaskFrame
)
)
),
minSize: hintIconSize,
action: {
component.flipTapped()
}
)
),
environment: {},
@ -1123,7 +1134,7 @@ final class CaptureControlsComponent: Component {
transition.setScale(view: lockView, scale: isHolding ? 1.0 : 0.1)
transition.setAlpha(view: lockView, alpha: isHolding ? 1.0 : 0.0)
if let lockMaskView = lockView as? LockContentComponent.View {
if let buttonView = lockView as? CameraButton.View, let lockMaskView = buttonView.contentView.componentView as? LockContentComponent.View {
transition.setAlpha(view: lockMaskView.maskContainerView, alpha: isHolding ? 1.0 : 0.0)
transition.setSublayerTransform(layer: lockMaskView.maskContainerView.layer, transform: isHolding ? CATransform3DIdentity : CATransform3DMakeScale(0.1, 0.1, 1.0))
}
@ -1155,11 +1166,11 @@ final class CaptureControlsComponent: Component {
contentView.maskContainerView.frame = contentView.convert(contentView.bounds, to: self)
}
if let lockView = self.lockView.view as? LockContentComponent.View {
if lockView.maskContainerView.superview == nil {
self.addSubview(lockView.maskContainerView)
if let buttonView = self.lockView.view as? CameraButton.View, let contentView = buttonView.contentView.componentView as? LockContentComponent.View {
if contentView.maskContainerView.superview == nil {
self.addSubview(contentView.maskContainerView)
}
lockView.maskContainerView.center = lockView.center
contentView.maskContainerView.center = buttonView.center
}
return size

View File

@ -2093,6 +2093,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
}
}
self.isInteractingWithEntities = isInteracting
if !isInteracting {
self.controller?.isSavingAvailable = true
}
self.requestUpdate(transition: .easeInOut(duration: 0.2))
}
},
@ -3853,11 +3856,7 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
let entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
if let previousSavedValues = self.previousSavedValues, mediaEditor.values == previousSavedValues {
return
}
self.hapticFeedback.impact(.light)
self.previousSavedValues = mediaEditor.values

View File

@ -267,9 +267,13 @@ private final class MediaToolsScreenComponent: Component {
var delay: Double = 0.0
for button in buttons {
if let view = button.view {
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: delay, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: delay)
view.alpha = 0.0
Queue.mainQueue().after(delay, {
view.alpha = 1.0
view.layer.animatePosition(from: CGPoint(x: 0.0, y: 64.0), to: .zero, duration: 0.3, delay: 0.0, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.0)
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: 0.0)
})
delay += 0.03
}
}

View File

@ -74,6 +74,8 @@ swift_library(
"//submodules/StickerPackPreviewUI",
"//submodules/Components/AnimatedStickerComponent",
"//submodules/OpenInExternalAppUI",
"//submodules/MediaPasteboardUI",
"//submodules/WebPBinding",
],
visibility = [
"//visibility:public",

View File

@ -173,7 +173,7 @@ public final class StoryContentContextImpl: StoryContentContext {
isExpired: false,
isPublic: false,
isPending: true,
isCloseFriends: false,
isCloseFriends: item.privacy.base == .closeFriends,
isForwardingDisabled: false,
isEdited: false
))

View File

@ -583,6 +583,12 @@ public final class StoryItemSetContainerComponent: Component {
}
}
if let inputMediaView = self.sendMessageContext.inputMediaNode {
if inputMediaView.frame.contains(point) {
return false
}
}
if let centerInfoItemView = self.centerInfoItem?.view.view {
if centerInfoItemView.convert(centerInfoItemView.bounds, to: self).contains(point) {
return false
@ -1579,11 +1585,7 @@ public final class StoryItemSetContainerComponent: Component {
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
let isFirstTime = self.component == nil
// if let hint = transition.userData(TextFieldComponent.AnimationHint.self), case .textFocusChanged = hint.kind, !hasFirstResponder(self) {
// self.sendMessageContext.currentInputMode = .text
// }
if self.component == nil {
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
}
@ -1759,7 +1761,11 @@ public final class StoryItemSetContainerComponent: Component {
return
}
self.sendMessageContext.toggleInputMode()
self.state?.updated(transition: .immediate)
if !hasFirstResponder(self) {
self.state?.updated(transition: .spring(duration: 0.4))
} else {
self.state?.updated(transition: .immediate)
}
},
timeoutAction: nil,
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
@ -1797,7 +1803,23 @@ public final class StoryItemSetContainerComponent: Component {
self.voiceMessagesRestrictedTooltipController = controller
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
},
paste: { _ in
paste: { [weak self] data in
guard let self else {
return
}
switch data {
case let .images(images):
self.sendMessageContext.presentMediaPasteboard(view: self, subjects: images.map { .image($0) })
case let .video(data):
let tempFilePath = NSTemporaryDirectory() + "\(Int64.random(in: 0...Int64.max)).mp4"
let url = NSURL(fileURLWithPath: tempFilePath) as URL
try? data.write(to: url)
self.sendMessageContext.presentMediaPasteboard(view: self, subjects: [.video(url)])
case let .gif(data):
self.sendMessageContext.enqueueGifData(view: self, data: data)
case let .sticker(image, isMemoji):
self.sendMessageContext.enqueueStickerImage(view: self, image: image, isMemoji: isMemoji)
}
},
audioRecorder: self.sendMessageContext.audioRecorderValue,
videoRecordingStatus: !self.sendMessageContext.hasRecordedVideoPreview ? self.sendMessageContext.videoRecorderValue?.audioStatus : nil,
@ -2226,8 +2248,9 @@ public final class StoryItemSetContainerComponent: Component {
if moreButtonView.superview == nil {
self.controlsContainerView.addSubview(moreButtonView)
}
moreButtonView.isUserInteractionEnabled = !component.slice.item.storyItem.isPending
transition.setFrame(view: moreButtonView, frame: CGRect(origin: CGPoint(x: headerRightOffset - moreButtonSize.width, y: 2.0), size: moreButtonSize))
transition.setAlpha(view: moreButtonView, alpha: component.slice.item.storyItem.isPending ? 0.0 : 1.0)
transition.setAlpha(view: moreButtonView, alpha: component.slice.item.storyItem.isPending ? 0.5 : 1.0)
headerRightOffset -= moreButtonSize.width + 15.0
}
@ -2811,7 +2834,24 @@ public final class StoryItemSetContainerComponent: Component {
}
reactionContextNode.premiumReactionsSelected = { [weak self] file in
guard let self, let file, let component = self.component else {
guard let self, let component = self.component else {
return
}
guard let file else {
let context = component.context
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumDemoScreen(context: context, subject: .uniqueReactions, action: {
let controller = PremiumIntroScreen(context: context, source: .reactions)
replaceImpl?(controller)
})
controller.disposed = { [weak self] in
self?.updateIsProgressPaused()
}
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
component.controller()?.push(controller)
return
}
@ -2827,6 +2867,9 @@ public final class StoryItemSetContainerComponent: Component {
let controller = PremiumIntroScreen(context: context, source: .reactions)
replaceImpl?(controller)
})
controller.disposed = { [weak self] in
self?.updateIsProgressPaused()
}
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
@ -2878,7 +2921,7 @@ public final class StoryItemSetContainerComponent: Component {
if let reactionContextNode = self.disappearingReactionContextNode {
if !reactionContextNode.isAnimatingOutToReaction {
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
reactionContextNode.updateLayout(size: availableSize, insets: UIEdgeInsets(), anchorRect: reactionsAnchorRect, centerAligned: true, isCoveredByInput: false, isAnimatingOut: false, transition: transition.containedViewLayoutTransition)
}
}

View File

@ -38,6 +38,8 @@ import TextFieldComponent
import StickerPackPreviewUI
import OpenInExternalAppUI
import SafariServices
import MediaPasteboardUI
import WebPBinding
final class StoryItemSetContainerSendMessage {
enum InputMode {
@ -252,9 +254,12 @@ final class StoryItemSetContainerSendMessage {
let heightAndOverflow = inputMediaNode.updateLayout(width: availableSize.width, leftInset: 0.0, rightInset: 0.0, bottomInset: bottomInset, standardInputHeight: deviceMetrics.standardInputHeight(inLandscape: false), inputHeight: inputHeight, maximumHeight: availableSize.height, inputPanelHeight: 0.0, transition: .immediate, interfaceState: presentationInterfaceState, layoutMetrics: metrics, deviceMetrics: deviceMetrics, isVisible: true, isExpanded: false)
let inputNodeHeight = heightAndOverflow.0
var inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight))
let inputNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - inputNodeHeight), size: CGSize(width: availableSize.width, height: inputNodeHeight))
if self.needsInputActivation {
inputNodeFrame = inputNodeFrame.offsetBy(dx: 0.0, dy: inputNodeHeight)
let inputNodeFrame = inputNodeFrame.offsetBy(dx: 0.0, dy: inputNodeHeight)
Transition.immediate.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
Transition.immediate.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeFrame)
}
transition.setFrame(layer: inputMediaNode.layer, frame: inputNodeFrame)
transition.setFrame(layer: self.inputMediaNodeBackground, frame: inputNodeFrame)
@ -326,6 +331,35 @@ final class StoryItemSetContainerSendMessage {
}
}
private func presentMessageSentTooltip(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messageId: EngineMessage.Id?) {
guard let component = view.component, let controller = component.controller() as? StoryContainerScreen else {
return
}
if let tooltipScreen = self.tooltipScreen {
tooltipScreen.dismiss(animated: true)
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let tooltipScreen = UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: messageId != nil ? "View in Chat" : "", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view, weak self] action in
if case .undo = action, let messageId {
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
}
self?.tooltipScreen = nil
view?.updateIsProgressPaused()
return false
}
)
controller.present(tooltipScreen, in: .current)
self.tooltipScreen = tooltipScreen
view.updateIsProgressPaused()
}
func performSendMessageAction(
view: StoryItemSetContainerComponent.View
) {
@ -342,8 +376,7 @@ final class StoryItemSetContainerSendMessage {
}
let peer = component.slice.peer
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let controller = component.controller()
let controller = component.controller() as? StoryContainerScreen
if let recordedAudioPreview = self.recordedAudioPreview {
self.recordedAudioPreview = nil
@ -370,27 +403,22 @@ final class StoryItemSetContainerSendMessage {
replyTo: nil,
storyId: focusedStoryId,
content: .text(text.string, entities)
) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in
if let controller {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
if case .undo = action, let messageId = messageIds.first {
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
}
return false
}
), in: .current)
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
inputPanelView.clearSendMessageInput()
self.currentInputMode = .text
view.endEditing(true)
if hasFirstResponder(view) {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}
}
}
@ -407,7 +435,6 @@ final class StoryItemSetContainerSendMessage {
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
let peer = component.slice.peer
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let controller = component.controller() as? StoryContainerScreen
if let navigationController = controller?.navigationController as? NavigationController {
@ -433,21 +460,10 @@ final class StoryItemSetContainerSendMessage {
replyTo: nil,
storyId: focusedStoryId,
content: .file(fileReference)
) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in
if let controller {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
if case .undo = action, let messageId = messageIds.first {
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
}
return false
}
), in: .current)
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
@ -457,8 +473,8 @@ final class StoryItemSetContainerSendMessage {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}
func performSendContextResultAction(view: StoryItemSetContainerComponent.View, results: ChatContextResultCollection, result: ChatContextResult) {
@ -472,7 +488,6 @@ final class StoryItemSetContainerSendMessage {
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
let peer = component.slice.peer
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let controller = component.controller() as? StoryContainerScreen
if let navigationController = controller?.navigationController as? NavigationController {
@ -498,21 +513,10 @@ final class StoryItemSetContainerSendMessage {
replyTo: nil,
storyId: focusedStoryId,
content: .contextResult(results, result)
) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in
if let controller {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
if case .undo = action, let messageId = messageIds.first {
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
}
return false
}
), in: .current)
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let self, let view {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
@ -522,8 +526,75 @@ final class StoryItemSetContainerSendMessage {
view.endEditing(true)
} else {
view.state?.updated(transition: .spring(duration: 0.3))
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}
controller?.requestLayout(forceUpdate: true, transition: .animated(duration: 0.3, curve: .spring))
}
func enqueueGifData(view: StoryItemSetContainerComponent.View, data: Data) {
guard let component = view.component else {
return
}
let peer = component.slice.peer
let _ = (legacyEnqueueGifMessage(account: component.context.account, data: data) |> deliverOnMainQueue).start(next: { [weak self, weak view] message in
if let self, let view {
self.sendMessages(view: view, peer: peer, messages: [message])
}
})
}
func enqueueStickerImage(view: StoryItemSetContainerComponent.View, image: UIImage, isMemoji: Bool) {
guard let component = view.component else {
return
}
let peer = component.slice.peer
let size = image.size.aspectFitted(CGSize(width: 512.0, height: 512.0))
func scaleImage(_ image: UIImage, size: CGSize, boundiingSize: CGSize) -> UIImage? {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
let format = UIGraphicsImageRendererFormat()
format.scale = 1.0
let renderer = UIGraphicsImageRenderer(size: size, format: format)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: size))
}
} else {
return TGScaleImageToPixelSize(image, size)
}
}
func convertToWebP(image: UIImage, targetSize: CGSize?, targetBoundingSize: CGSize?, quality: CGFloat) -> Signal<Data, NoError> {
var image = image
if let targetSize = targetSize, let scaledImage = scaleImage(image, size: targetSize, boundiingSize: targetSize) {
image = scaledImage
}
return Signal { subscriber in
if let data = try? WebP.convert(toWebP: image, quality: quality * 100.0) {
subscriber.putNext(data)
}
subscriber.putCompletion()
return EmptyDisposable
} |> runOn(Queue.concurrentDefaultQueue())
}
let _ = (convertToWebP(image: image, targetSize: size, targetBoundingSize: size, quality: 0.9) |> deliverOnMainQueue).start(next: { [weak self, weak view] data in
if let self, let view, !data.isEmpty {
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
component.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
var fileAttributes: [TelegramMediaFileAttribute] = []
fileAttributes.append(.FileName(fileName: "sticker.webp"))
fileAttributes.append(.Sticker(displayText: "", packReference: nil, maskData: nil))
fileAttributes.append(.ImageSize(size: PixelDimensions(size)))
let media = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: Int64.random(in: Int64.min ... Int64.max)), partialReference: nil, resource: resource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "image/webp", size: Int64(data.count), attributes: fileAttributes)
let message = EnqueueMessage.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
self.sendMessages(view: view, peer: peer, messages: [message], silentPosting: false)
}
})
}
func setMediaRecordingActive(
@ -1160,18 +1231,13 @@ final class StoryItemSetContainerSendMessage {
}
let message: EnqueueMessage = .message(text: "", attributes: [], inlineStickers: [:], mediaReference: mediaReference, replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)])
|> deliverOnMainQueue).start()
if let controller = component.controller() {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .succeed(text: "Message Sent"),
elevatedLayout: false,
animateInAsReplacement: false,
action: { _ in return false }
), in: .current)
}
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
if let self, let view {
Queue.mainQueue().after(0.3) {
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
})
let _ = currentFilesController.swap(controller)
if let controller = controller as? AttachmentContainable, let mediaPickerContext = controller.mediaPickerContext {
@ -1275,7 +1341,7 @@ final class StoryItemSetContainerSendMessage {
}
}
self.sendMessages(view: view, peer: peer, messages: self.transformEnqueueMessages(view: view, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
self.sendMessages(view: view, peer: peer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
} else if let peer = peers.first {
let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError>
switch peer {
@ -1330,7 +1396,7 @@ final class StoryItemSetContainerSendMessage {
}
enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
self.sendMessages(view: view, peer: targetPeer, messages: self.transformEnqueueMessages(view: view, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
} else {
let contactController = component.context.sharedContext.makeDeviceContactInfoController(context: component.context, subject: .filter(peer: peerAndContactData.0?._asPeer(), contactId: nil, contactData: contactData, completion: { [weak self, weak view] peer, contactData in
guard let self, let view else {
@ -1349,7 +1415,7 @@ final class StoryItemSetContainerSendMessage {
}
enqueueMessages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), replyToMessageId: nil, replyToStoryId: focusedStoryId, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
self.sendMessages(view: view, peer: targetPeer, messages: self.transformEnqueueMessages(view: view, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime))
self.sendMessages(view: view, peer: targetPeer, messages: enqueueMessages, silentPosting: silent, scheduleTime: scheduleTime)
}
}), completed: nil, cancelled: nil)
component.controller()?.push(contactController)
@ -1357,10 +1423,6 @@ final class StoryItemSetContainerSendMessage {
}))
}
}))
case .poll:
let controller = self.configurePollCreation(view: view, peer: peer, targetMessageId: nil)
completion(controller, controller?.mediaPickerContext)
self.controllerNavigationDisposable.set(nil)
case .gift:
/*let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
if !premiumGiftOptions.isEmpty {
@ -1773,6 +1835,69 @@ final class StoryItemSetContainerSendMessage {
})
}
func presentMediaPasteboard(view: StoryItemSetContainerComponent.View, subjects: [MediaPickerScreen.Subject.Media]) {
guard let component = view.component else {
return
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return
}
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
guard let inputPanelView = view.inputPanel.view as? MessageInputPanelComponent.View else {
return
}
var inputText = NSAttributedString(string: "")
switch inputPanelView.getSendMessageInput() {
case let .text(text):
inputText = text
}
let peer = component.slice.peer
let theme = defaultDarkPresentationTheme
let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>) = (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) })
let controller = mediaPasteboardScreen(
context: component.context,
updatedPresentationData: updatedPresentationData,
peer: peer,
subjects: subjects,
presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in
if let self {
self.presentMediaPicker(
view: view,
peer: peer,
replyToMessageId: nil,
replyToStoryId: focusedStoryId,
subject: subject,
saveEditedPhotos: saveEditedPhotos,
bannedSendPhotos: bannedSendPhotos,
bannedSendVideos: bannedSendVideos,
present: { controller, mediaPickerContext in
if !inputText.string.isEmpty {
mediaPickerContext?.setCaption(inputText)
}
present(controller, mediaPickerContext)
},
updateMediaPickerContext: { _ in },
completion: { [weak self, weak view] signals, silentPosting, scheduleTime, getAnimatedTransitionSource, completion in
guard let self, let view else {
return
}
if !inputText.string.isEmpty {
self.clearInputText(view: view)
}
self.enqueueMediaMessages(view: view, peer: peer, replyToMessageId: nil, replyToStoryId: focusedStoryId, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion)
}
)
}
},
getSourceRect: nil
)
controller.navigationPresentation = .flatModal
component.controller()?.push(controller)
}
private func enqueueChatContextResult(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, storyId: StoryId?, results: ChatContextResultCollection, result: ChatContextResult, hideVia: Bool = false, closeMediaInput: Bool = false, silentPosting: Bool = false, resetTextInputState: Bool = true) {
if !canSendMessagesToPeer(peer._asPeer()) {
return
@ -2002,56 +2127,6 @@ final class StoryItemSetContainerSendMessage {
component.controller()?.present(controller, in: .window(.root))
}
private func configurePollCreation(view: StoryItemSetContainerComponent.View, peer: EnginePeer, targetMessageId: EngineMessage.Id?, isQuiz: Bool? = nil) -> CreatePollControllerImpl? {
guard let component = view.component else {
return nil
}
let focusedItem = component.slice.item
guard let peerId = focusedItem.peerId else {
return nil
}
let focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
let theme = component.theme
return createPollController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), peer: peer, isQuiz: isQuiz, completion: { [weak self, weak view] poll in
guard let self, let view else {
return
}
let replyMessageId = targetMessageId
/*strongSelf.chatDisplayNode.setupSendActionOnViewUpdate({
if let strongSelf = self {
strongSelf.chatDisplayNode.collapseInput()
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: false, {
$0.updatedInterfaceState { $0.withUpdatedReplyMessageId(nil) }
})
}
}, nil)*/
let message: EnqueueMessage = .message(
text: "",
attributes: [],
inlineStickers: [:],
mediaReference: .standalone(media: TelegramMediaPoll(
pollId: EngineMedia.Id(namespace: Namespaces.Media.LocalPoll, id: Int64.random(in: Int64.min ... Int64.max)),
publicity: poll.publicity,
kind: poll.kind,
text: poll.text,
options: poll.options,
correctAnswers: poll.correctAnswers,
results: poll.results,
isClosed: false,
deadlineTimeout: poll.deadlineTimeout
)),
replyToMessageId: nil,
replyToStoryId: focusedStoryId,
localGroupingKey: nil,
correlationId: nil,
bubbleUpEmojiOrStickersets: []
)
self.sendMessages(view: view, peer: peer, messages: [message.withUpdatedReplyToMessageId(replyMessageId)])
})
}
private func transformEnqueueMessages(view: StoryItemSetContainerComponent.View, messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] {
var focusedStoryId: StoryId?
if let component = view.component, let peerId = component.slice.item.peerId {
@ -2094,28 +2169,15 @@ final class StoryItemSetContainerSendMessage {
}
}
private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], media: Bool = false, commit: Bool = false) {
guard let component = view.component, let controller = component.controller() else {
private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], silentPosting: Bool = false, scheduleTime: Int32? = nil) {
guard let component = view.component else {
return
}
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: self.transformEnqueueMessages(view: view, messages: messages, silentPosting: false))
|> deliverOnMainQueue).start(next: { [weak controller] messageIds in
if let controller {
Queue.mainQueue().after(0.3) {
controller.present(UndoOverlayController(
presentationData: presentationData,
content: .actionSucceeded(title: "", text: "Message Sent", cancel: "View in Chat", destructive: false),
elevatedLayout: false,
animateInAsReplacement: false,
action: { [weak view] action in
if case .undo = action, let messageId = messageIds.first {
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
}
return false
}
), in: .current)
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: self.transformEnqueueMessages(view: view, messages: messages, silentPosting: silentPosting, scheduleTime: scheduleTime))
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
Queue.mainQueue().after(0.3) {
if let view {
self?.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
}
}
})
@ -2180,10 +2242,8 @@ final class StoryItemSetContainerSendMessage {
}
mappedMessages.append(message)
}
let messages = strongSelf.transformEnqueueMessages(view: view, messages: mappedMessages, silentPosting: silentPosting, scheduleTime: scheduleTime)
strongSelf.sendMessages(view: view, peer: peer, messages: messages.map { $0.withUpdatedReplyToMessageId(replyToMessageId).withUpdatedReplyToStoryId(replyToStoryId) }, media: true)
strongSelf.sendMessages(view: view, peer: peer, messages: mappedMessages.map { $0.withUpdatedReplyToMessageId(replyToMessageId).withUpdatedReplyToStoryId(replyToStoryId) }, silentPosting: silentPosting, scheduleTime: scheduleTime)
completion()
}

File diff suppressed because one or more lines are too long