mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit '74de28b1547b9bf729163034bcdc918819a86d85'
# Conflicts: # submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift
This commit is contained in:
commit
66a8c7e4a7
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -96,6 +96,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
|
||||
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>()
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,8 @@ swift_library(
|
||||
"//submodules/StickerPackPreviewUI",
|
||||
"//submodules/Components/AnimatedStickerComponent",
|
||||
"//submodules/OpenInExternalAppUI",
|
||||
"//submodules/MediaPasteboardUI",
|
||||
"//submodules/WebPBinding",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -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
|
||||
))
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user