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?
|
private let multiSession: Any?
|
||||||
|
|
||||||
let hasMultiCam: Bool
|
let hasMultiCam: Bool
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported {
|
if #available(iOS 13.0, *), AVCaptureMultiCamSession.isMultiCamSupported {
|
||||||
self.multiSession = AVCaptureMultiCamSession()
|
self.multiSession = AVCaptureMultiCamSession()
|
||||||
@ -21,6 +21,7 @@ final class CameraSession {
|
|||||||
self.multiSession = nil
|
self.multiSession = nil
|
||||||
self.hasMultiCam = false
|
self.hasMultiCam = false
|
||||||
}
|
}
|
||||||
|
self.session.sessionPreset = .inputPriority
|
||||||
}
|
}
|
||||||
|
|
||||||
var session: AVCaptureSession {
|
var session: AVCaptureSession {
|
||||||
@ -64,10 +65,10 @@ final class CameraDeviceContext {
|
|||||||
self.previewView = previewView
|
self.previewView = previewView
|
||||||
|
|
||||||
self.device.configure(for: session, position: position, dual: !exclusive || additional)
|
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.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.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.output.configureVideoStabilization()
|
||||||
|
|
||||||
self.device.resetZoom(neutral: self.exclusive || !self.additional)
|
self.device.resetZoom(neutral: self.exclusive || !self.additional)
|
||||||
@ -83,8 +84,6 @@ final class CameraDeviceContext {
|
|||||||
|
|
||||||
private var preferredMaxDimensions: CMVideoDimensions {
|
private var preferredMaxDimensions: CMVideoDimensions {
|
||||||
if self.additional {
|
if self.additional {
|
||||||
//if case .iPhoneXS = DeviceModel.current {
|
|
||||||
// return CMVideoDimensions(width: 1440, height: 1080)
|
|
||||||
return CMVideoDimensions(width: 1920, height: 1440)
|
return CMVideoDimensions(width: 1920, height: 1440)
|
||||||
} else {
|
} else {
|
||||||
return CMVideoDimensions(width: 1920, height: 1080)
|
return CMVideoDimensions(width: 1920, height: 1080)
|
||||||
|
@ -44,11 +44,8 @@ final class CameraDevice {
|
|||||||
selectedDevice = device
|
selectedDevice = device
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
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 {
|
} else {
|
||||||
Logger.shared.log("Camera", "Can't add video connection")
|
Logger.shared.log("Camera", "Can't add video connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
if photo {
|
if photo {
|
||||||
let photoConnection = AVCaptureConnection(inputPorts: ports, output: self.photoOutput)
|
let photoConnection = AVCaptureConnection(inputPorts: ports, output: self.photoOutput)
|
||||||
if session.session.canAddConnection(photoConnection) {
|
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)
|
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)
|
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 {
|
private final class ContactsTabBarContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
|
@ -68,10 +68,7 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
private var presentationDataDisposable: Disposable?
|
private var presentationDataDisposable: Disposable?
|
||||||
private let stringsPromise = Promise<PresentationStrings>()
|
private let stringsPromise = Promise<PresentationStrings>()
|
||||||
|
|
||||||
private var isStoryPostingAvailable = false
|
|
||||||
private var storiesPostingAvailabilityDisposable: Disposable?
|
|
||||||
|
|
||||||
weak var controller: ContactsController?
|
weak var controller: ContactsController?
|
||||||
|
|
||||||
private var initialScrollingOffset: CGFloat?
|
private var initialScrollingOffset: CGFloat?
|
||||||
@ -254,80 +251,11 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
}
|
}
|
||||||
self.openStories?(peer, sourceNode)
|
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 {
|
deinit {
|
||||||
self.presentationDataDisposable?.dispose()
|
self.presentationDataDisposable?.dispose()
|
||||||
self.storySubscriptionsDisposable?.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() {
|
private func updateThemeAndStrings() {
|
||||||
@ -594,38 +522,6 @@ final class ContactsControllerNode: ASDisplayNode, UIGestureRecognizerDelegate {
|
|||||||
placeholderNode.frame = previousFrame
|
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 {
|
private final class ContactContextExtractedContentSource: ContextExtractedContentSource {
|
||||||
|
@ -96,6 +96,7 @@ swift_library(
|
|||||||
"//submodules/TelegramUI/Components/MediaEditor",
|
"//submodules/TelegramUI/Components/MediaEditor",
|
||||||
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
|
"//submodules/ChatPresentationInterfaceState:ChatPresentationInterfaceState",
|
||||||
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
||||||
|
"//submodules/TelegramUI/Components/LottieComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -215,6 +215,10 @@ final class DrawingBubbleEntitySelectionView: DrawingEntitySelectionView {
|
|||||||
self.currentHandle = self.layer
|
self.currentHandle = self.layer
|
||||||
entityView.onInteractionUpdated(true)
|
entityView.onInteractionUpdated(true)
|
||||||
case .changed:
|
case .changed:
|
||||||
|
if self.currentHandle == nil {
|
||||||
|
self.currentHandle = self.layer
|
||||||
|
}
|
||||||
|
|
||||||
let delta = gestureRecognizer.translation(in: entityView.superview)
|
let delta = gestureRecognizer.translation(in: entityView.superview)
|
||||||
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
||||||
|
|
||||||
|
@ -2,8 +2,11 @@ import Foundation
|
|||||||
import UIKit
|
import UIKit
|
||||||
import Display
|
import Display
|
||||||
import LegacyComponents
|
import LegacyComponents
|
||||||
|
import SwiftSignalKit
|
||||||
import AccountContext
|
import AccountContext
|
||||||
import MediaEditor
|
import MediaEditor
|
||||||
|
import ComponentFlow
|
||||||
|
import LottieAnimationComponent
|
||||||
|
|
||||||
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
|
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
|
||||||
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
|
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 xAxisView = UIView()
|
||||||
private let yAxisView = UIView()
|
private let yAxisView = UIView()
|
||||||
private let angleLayer = SimpleShapeLayer()
|
private let angleLayer = SimpleShapeLayer()
|
||||||
|
private let bin = ComponentView<Empty>()
|
||||||
|
|
||||||
public var onInteractionUpdated: (Bool) -> Void = { _ in }
|
public var onInteractionUpdated: (Bool) -> Void = { _ in }
|
||||||
public var edgePreviewUpdated: (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) {
|
@objc private func handleTap(_ gestureRecognzier: UITapGestureRecognizer) {
|
||||||
let location = gestureRecognzier.location(in: self)
|
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] = []
|
var intersectedViews: [DrawingEntityView] = []
|
||||||
for case let view as DrawingEntityView in self.subviews {
|
for case let view as DrawingEntityView in self.subviews {
|
||||||
if view.precisePoint(inside: self.convert(location, to: view)) {
|
if view.precisePoint(inside: self.convert(location, to: view)) {
|
||||||
intersectedViews.append(view)
|
intersectedViews.append(view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return intersectedViews.last
|
||||||
if let entityView = intersectedViews.last {
|
|
||||||
self.selectEntity(entityView.entity)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func selectEntity(_ entity: DrawingEntity?) {
|
public func selectEntity(_ entity: DrawingEntity?) {
|
||||||
@ -622,8 +628,9 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
|
|
||||||
if let selectionView = entityView.makeSelectionView() {
|
if let selectionView = entityView.makeSelectionView() {
|
||||||
selectionView.tapped = { [weak self, weak entityView] in
|
selectionView.tapped = { [weak self, weak entityView] in
|
||||||
if let strongSelf = self, let entityView = entityView {
|
if let self, let entityView = entityView {
|
||||||
strongSelf.requestedMenuForEntityView(entityView, strongSelf.subviews.last === entityView)
|
let entityViews = self.subviews.filter { $0 is DrawingEntityView }
|
||||||
|
self.requestedMenuForEntityView(entityView, entityViews.last === entityView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entityView.selectionView = selectionView
|
entityView.selectionView = selectionView
|
||||||
@ -679,10 +686,74 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
public func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||||
|
let location = gestureRecognizer.location(in: self)
|
||||||
if let selectedEntityView = self.selectedEntityView, let selectionView = selectedEntityView.selectionView {
|
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 {
|
} else if gestureRecognizer.numberOfTouches == 2, let mediaEntityView = self.subviews.first(where: { $0 is DrawingEntityMediaView }) as? DrawingEntityMediaView {
|
||||||
mediaEntityView.handlePan(gestureRecognizer)
|
mediaEntityView.handlePan(gestureRecognizer)
|
||||||
}
|
}
|
||||||
@ -703,6 +774,40 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||||||
selectionView.handleRotate(gestureRecognizer)
|
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 {
|
protocol DrawingEntityMediaView: DrawingEntityView {
|
||||||
@ -716,6 +821,8 @@ public class DrawingEntityView: UIView {
|
|||||||
public let entity: DrawingEntity
|
public let entity: DrawingEntity
|
||||||
var isTracking = false
|
var isTracking = false
|
||||||
|
|
||||||
|
var isTrappedInBin = false
|
||||||
|
|
||||||
public weak var selectionView: DrawingEntitySelectionView?
|
public weak var selectionView: DrawingEntitySelectionView?
|
||||||
weak var containerView: DrawingEntitiesView?
|
weak var containerView: DrawingEntitiesView?
|
||||||
|
|
||||||
@ -878,3 +985,97 @@ public class DrawingSelectionContainerView: UIView {
|
|||||||
return result
|
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
|
self.currentHandle = self.layer
|
||||||
entityView.onInteractionUpdated(true)
|
entityView.onInteractionUpdated(true)
|
||||||
case .changed:
|
case .changed:
|
||||||
|
if self.currentHandle == nil {
|
||||||
|
self.currentHandle = self.layer
|
||||||
|
}
|
||||||
|
|
||||||
let delta = gestureRecognizer.translation(in: entityView.superview)
|
let delta = gestureRecognizer.translation(in: entityView.superview)
|
||||||
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
||||||
|
|
||||||
|
@ -521,6 +521,10 @@ final class DrawingStickerEntititySelectionView: DrawingEntitySelectionView {
|
|||||||
self.currentHandle = self.layer
|
self.currentHandle = self.layer
|
||||||
entityView.onInteractionUpdated(true)
|
entityView.onInteractionUpdated(true)
|
||||||
case .changed:
|
case .changed:
|
||||||
|
if self.currentHandle == nil {
|
||||||
|
self.currentHandle = self.layer
|
||||||
|
}
|
||||||
|
|
||||||
let delta = gestureRecognizer.translation(in: entityView.superview)
|
let delta = gestureRecognizer.translation(in: entityView.superview)
|
||||||
let parentLocation = gestureRecognizer.location(in: self.superview)
|
let parentLocation = gestureRecognizer.location(in: self.superview)
|
||||||
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
||||||
|
@ -785,6 +785,10 @@ final class DrawingTextEntititySelectionView: DrawingEntitySelectionView {
|
|||||||
self.currentHandle = self.layer
|
self.currentHandle = self.layer
|
||||||
entityView.onInteractionUpdated(true)
|
entityView.onInteractionUpdated(true)
|
||||||
case .changed:
|
case .changed:
|
||||||
|
if self.currentHandle == nil {
|
||||||
|
self.currentHandle = self.layer
|
||||||
|
}
|
||||||
|
|
||||||
let delta = gestureRecognizer.translation(in: entityView.superview)
|
let delta = gestureRecognizer.translation(in: entityView.superview)
|
||||||
let parentLocation = gestureRecognizer.location(in: self.superview)
|
let parentLocation = gestureRecognizer.location(in: self.superview)
|
||||||
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
let velocity = gestureRecognizer.velocity(in: entityView.superview)
|
||||||
|
@ -517,3 +517,30 @@ func normalizeDrawingRect(_ rect: CGRect, drawingSize: CGSize) -> CGRect {
|
|||||||
}
|
}
|
||||||
return rect
|
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
|
self.currentHandle = self.layer
|
||||||
case .changed:
|
case .changed:
|
||||||
|
if self.currentHandle == nil {
|
||||||
|
self.currentHandle = self.layer
|
||||||
|
}
|
||||||
|
|
||||||
if gestureRecognizer.numberOfTouches > 1 {
|
if gestureRecognizer.numberOfTouches > 1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1242,7 +1242,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
|
|||||||
case other
|
case other
|
||||||
}
|
}
|
||||||
|
|
||||||
var disposed: () -> Void = {}
|
public var disposed: () -> Void = {}
|
||||||
|
|
||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
private let _ready = Promise<Bool>()
|
private let _ready = Promise<Bool>()
|
||||||
|
@ -1080,7 +1080,17 @@ public class CameraScreen: ViewController {
|
|||||||
fileprivate var previewBlurPromise = ValuePromise<Bool>(false)
|
fileprivate var previewBlurPromise = ValuePromise<Bool>(false)
|
||||||
private let animateFlipAction = ActionSlot<Void>()
|
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
|
fileprivate var hasGallery = false
|
||||||
|
|
||||||
private var presentationData: PresentationData
|
private var presentationData: PresentationData
|
||||||
@ -1302,10 +1312,13 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension())
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.changingPositionDisposable?.dispose()
|
self.changingPositionDisposable?.dispose()
|
||||||
|
self.idleTimerExtensionDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var pipPanGestureRecognizer: UIPanGestureRecognizer?
|
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 location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY + 3.0), size: CGSize())
|
||||||
|
|
||||||
let accountManager = self.context.sharedContext.accountManager
|
let accountManager = self.context.sharedContext.accountManager
|
||||||
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: "Take photos or videos to share with all\nyour contacts or close friends at once."), textAlignment: .center, location: .point(location, .bottom), displayDuration: .custom(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) {
|
if containerFrame.contains(point) {
|
||||||
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
let _ = ApplicationSpecificNotice.incrementStoriesCameraTip(accountManager: accountManager).start()
|
||||||
return .dismiss(consume: true)
|
return .dismiss(consume: true)
|
||||||
@ -2224,6 +2237,8 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.push(controller)
|
self.push(controller)
|
||||||
|
|
||||||
|
self.requestLayout(transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func presentDraftTooltip() {
|
public func presentDraftTooltip() {
|
||||||
|
@ -1102,12 +1102,23 @@ final class CaptureControlsComponent: Component {
|
|||||||
lockMaskFrame = lockMaskFrame.offsetBy(dx: 8.0, dy: 0.0)
|
lockMaskFrame = lockMaskFrame.offsetBy(dx: 8.0, dy: 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.lockView.update(
|
let _ = self.lockView.update(
|
||||||
transition: transition,
|
transition: .immediate,
|
||||||
component: AnyComponent(
|
component: AnyComponent(
|
||||||
LockContentComponent(
|
CameraButton(
|
||||||
maskFrame: lockMaskFrame
|
content: AnyComponentWithIdentity(
|
||||||
|
id: "lock",
|
||||||
|
component: AnyComponent(
|
||||||
|
LockContentComponent(
|
||||||
|
maskFrame: lockMaskFrame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
minSize: hintIconSize,
|
||||||
|
action: {
|
||||||
|
component.flipTapped()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
environment: {},
|
environment: {},
|
||||||
@ -1123,7 +1134,7 @@ final class CaptureControlsComponent: Component {
|
|||||||
transition.setScale(view: lockView, scale: isHolding ? 1.0 : 0.1)
|
transition.setScale(view: lockView, scale: isHolding ? 1.0 : 0.1)
|
||||||
transition.setAlpha(view: lockView, alpha: isHolding ? 1.0 : 0.0)
|
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.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))
|
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)
|
contentView.maskContainerView.frame = contentView.convert(contentView.bounds, to: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let lockView = self.lockView.view as? LockContentComponent.View {
|
if let buttonView = self.lockView.view as? CameraButton.View, let contentView = buttonView.contentView.componentView as? LockContentComponent.View {
|
||||||
if lockView.maskContainerView.superview == nil {
|
if contentView.maskContainerView.superview == nil {
|
||||||
self.addSubview(lockView.maskContainerView)
|
self.addSubview(contentView.maskContainerView)
|
||||||
}
|
}
|
||||||
lockView.maskContainerView.center = lockView.center
|
contentView.maskContainerView.center = buttonView.center
|
||||||
}
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
|
@ -2093,6 +2093,9 @@ public final class MediaEditorScreen: ViewController, UIDropInteractionDelegate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.isInteractingWithEntities = isInteracting
|
self.isInteractingWithEntities = isInteracting
|
||||||
|
if !isInteracting {
|
||||||
|
self.controller?.isSavingAvailable = true
|
||||||
|
}
|
||||||
self.requestUpdate(transition: .easeInOut(duration: 0.2))
|
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 entities = self.node.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||||
|
|
||||||
if let previousSavedValues = self.previousSavedValues, mediaEditor.values == previousSavedValues {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
|
|
||||||
self.previousSavedValues = mediaEditor.values
|
self.previousSavedValues = mediaEditor.values
|
||||||
|
@ -267,9 +267,13 @@ private final class MediaToolsScreenComponent: Component {
|
|||||||
var delay: Double = 0.0
|
var delay: Double = 0.0
|
||||||
for button in buttons {
|
for button in buttons {
|
||||||
if let view = button.view {
|
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.alpha = 0.0
|
||||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: delay)
|
Queue.mainQueue().after(delay, {
|
||||||
view.layer.animateScale(from: 0.1, to: 1.0, duration: 0.2, delay: 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
|
delay += 0.03
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,8 @@ swift_library(
|
|||||||
"//submodules/StickerPackPreviewUI",
|
"//submodules/StickerPackPreviewUI",
|
||||||
"//submodules/Components/AnimatedStickerComponent",
|
"//submodules/Components/AnimatedStickerComponent",
|
||||||
"//submodules/OpenInExternalAppUI",
|
"//submodules/OpenInExternalAppUI",
|
||||||
|
"//submodules/MediaPasteboardUI",
|
||||||
|
"//submodules/WebPBinding",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -173,7 +173,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||||||
isExpired: false,
|
isExpired: false,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
isPending: true,
|
isPending: true,
|
||||||
isCloseFriends: false,
|
isCloseFriends: item.privacy.base == .closeFriends,
|
||||||
isForwardingDisabled: false,
|
isForwardingDisabled: false,
|
||||||
isEdited: 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 let centerInfoItemView = self.centerInfoItem?.view.view {
|
||||||
if centerInfoItemView.convert(centerInfoItemView.bounds, to: self).contains(point) {
|
if centerInfoItemView.convert(centerInfoItemView.bounds, to: self).contains(point) {
|
||||||
return false
|
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 {
|
func update(component: StoryItemSetContainerComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
let isFirstTime = self.component == nil
|
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 {
|
if self.component == nil {
|
||||||
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
|
self.sendMessageContext.setup(context: component.context, view: self, inputPanelExternalState: self.inputPanelExternalState, keyboardInputData: component.keyboardInputData)
|
||||||
}
|
}
|
||||||
@ -1759,7 +1761,11 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.sendMessageContext.toggleInputMode()
|
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,
|
timeoutAction: nil,
|
||||||
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
|
forwardAction: component.slice.item.storyItem.isPublic ? { [weak self] in
|
||||||
@ -1797,7 +1803,23 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.voiceMessagesRestrictedTooltipController = controller
|
self.voiceMessagesRestrictedTooltipController = controller
|
||||||
self.state?.updated(transition: Transition(animation: .curve(duration: 0.2, curve: .easeInOut)))
|
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,
|
audioRecorder: self.sendMessageContext.audioRecorderValue,
|
||||||
videoRecordingStatus: !self.sendMessageContext.hasRecordedVideoPreview ? self.sendMessageContext.videoRecorderValue?.audioStatus : nil,
|
videoRecordingStatus: !self.sendMessageContext.hasRecordedVideoPreview ? self.sendMessageContext.videoRecorderValue?.audioStatus : nil,
|
||||||
@ -2226,8 +2248,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if moreButtonView.superview == nil {
|
if moreButtonView.superview == nil {
|
||||||
self.controlsContainerView.addSubview(moreButtonView)
|
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.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
|
headerRightOffset -= moreButtonSize.width + 15.0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2811,7 +2834,24 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reactionContextNode.premiumReactionsSelected = { [weak self] file in
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2827,6 +2867,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let controller = PremiumIntroScreen(context: context, source: .reactions)
|
let controller = PremiumIntroScreen(context: context, source: .reactions)
|
||||||
replaceImpl?(controller)
|
replaceImpl?(controller)
|
||||||
})
|
})
|
||||||
|
controller.disposed = { [weak self] in
|
||||||
|
self?.updateIsProgressPaused()
|
||||||
|
}
|
||||||
replaceImpl = { [weak controller] c in
|
replaceImpl = { [weak controller] c in
|
||||||
controller?.replace(with: c)
|
controller?.replace(with: c)
|
||||||
}
|
}
|
||||||
@ -2878,7 +2921,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
if let reactionContextNode = self.disappearingReactionContextNode {
|
if let reactionContextNode = self.disappearingReactionContextNode {
|
||||||
if !reactionContextNode.isAnimatingOutToReaction {
|
if !reactionContextNode.isAnimatingOutToReaction {
|
||||||
transition.setFrame(view: reactionContextNode.view, frame: CGRect(origin: CGPoint(), size: availableSize))
|
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 StickerPackPreviewUI
|
||||||
import OpenInExternalAppUI
|
import OpenInExternalAppUI
|
||||||
import SafariServices
|
import SafariServices
|
||||||
|
import MediaPasteboardUI
|
||||||
|
import WebPBinding
|
||||||
|
|
||||||
final class StoryItemSetContainerSendMessage {
|
final class StoryItemSetContainerSendMessage {
|
||||||
enum InputMode {
|
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 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
|
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 {
|
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: inputMediaNode.layer, frame: inputNodeFrame)
|
||||||
transition.setFrame(layer: self.inputMediaNodeBackground, 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(
|
func performSendMessageAction(
|
||||||
view: StoryItemSetContainerComponent.View
|
view: StoryItemSetContainerComponent.View
|
||||||
) {
|
) {
|
||||||
@ -342,8 +376,7 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
let peer = component.slice.peer
|
let peer = component.slice.peer
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
let controller = component.controller() as? StoryContainerScreen
|
||||||
let controller = component.controller()
|
|
||||||
|
|
||||||
if let recordedAudioPreview = self.recordedAudioPreview {
|
if let recordedAudioPreview = self.recordedAudioPreview {
|
||||||
self.recordedAudioPreview = nil
|
self.recordedAudioPreview = nil
|
||||||
@ -370,27 +403,22 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
replyTo: nil,
|
replyTo: nil,
|
||||||
storyId: focusedStoryId,
|
storyId: focusedStoryId,
|
||||||
content: .text(text.string, entities)
|
content: .text(text.string, entities)
|
||||||
) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in
|
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
if let controller {
|
Queue.mainQueue().after(0.3) {
|
||||||
Queue.mainQueue().after(0.3) {
|
if let self, let view {
|
||||||
controller.present(UndoOverlayController(
|
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
inputPanelView.clearSendMessageInput()
|
inputPanelView.clearSendMessageInput()
|
||||||
|
|
||||||
self.currentInputMode = .text
|
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 focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
|
||||||
let peer = component.slice.peer
|
let peer = component.slice.peer
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let controller = component.controller() as? StoryContainerScreen
|
let controller = component.controller() as? StoryContainerScreen
|
||||||
|
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
@ -433,21 +460,10 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
replyTo: nil,
|
replyTo: nil,
|
||||||
storyId: focusedStoryId,
|
storyId: focusedStoryId,
|
||||||
content: .file(fileReference)
|
content: .file(fileReference)
|
||||||
) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in
|
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
if let controller {
|
Queue.mainQueue().after(0.3) {
|
||||||
Queue.mainQueue().after(0.3) {
|
if let self, let view {
|
||||||
controller.present(UndoOverlayController(
|
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -457,8 +473,8 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
} else {
|
} else {
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
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) {
|
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 focusedStoryId = StoryId(peerId: peerId, id: focusedItem.storyItem.id)
|
||||||
let peer = component.slice.peer
|
let peer = component.slice.peer
|
||||||
|
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
|
||||||
let controller = component.controller() as? StoryContainerScreen
|
let controller = component.controller() as? StoryContainerScreen
|
||||||
|
|
||||||
if let navigationController = controller?.navigationController as? NavigationController {
|
if let navigationController = controller?.navigationController as? NavigationController {
|
||||||
@ -498,21 +513,10 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
replyTo: nil,
|
replyTo: nil,
|
||||||
storyId: focusedStoryId,
|
storyId: focusedStoryId,
|
||||||
content: .contextResult(results, result)
|
content: .contextResult(results, result)
|
||||||
) |> deliverOnMainQueue).start(next: { [weak controller, weak view] messageIds in
|
) |> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
if let controller {
|
Queue.mainQueue().after(0.3) {
|
||||||
Queue.mainQueue().after(0.3) {
|
if let self, let view {
|
||||||
controller.present(UndoOverlayController(
|
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -522,8 +526,75 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
} else {
|
} else {
|
||||||
view.state?.updated(transition: .spring(duration: 0.3))
|
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(
|
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 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)])
|
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: [message.withUpdatedReplyToMessageId(nil)])
|
||||||
|> deliverOnMainQueue).start()
|
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
|
if let self, let view {
|
||||||
if let controller = component.controller() {
|
Queue.mainQueue().after(0.3) {
|
||||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
self.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
||||||
controller.present(UndoOverlayController(
|
}
|
||||||
presentationData: presentationData,
|
}
|
||||||
content: .succeed(text: "Message Sent"),
|
})
|
||||||
elevatedLayout: false,
|
|
||||||
animateInAsReplacement: false,
|
|
||||||
action: { _ in return false }
|
|
||||||
), in: .current)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
let _ = currentFilesController.swap(controller)
|
let _ = currentFilesController.swap(controller)
|
||||||
if let controller = controller as? AttachmentContainable, let mediaPickerContext = controller.mediaPickerContext {
|
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 {
|
} else if let peer = peers.first {
|
||||||
let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError>
|
let dataSignal: Signal<(EnginePeer?, DeviceContactExtendedData?), NoError>
|
||||||
switch peer {
|
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: []))
|
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 {
|
} 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
|
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 {
|
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: []))
|
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)
|
}), completed: nil, cancelled: nil)
|
||||||
component.controller()?.push(contactController)
|
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:
|
case .gift:
|
||||||
/*let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
/*let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
||||||
if !premiumGiftOptions.isEmpty {
|
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) {
|
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()) {
|
if !canSendMessagesToPeer(peer._asPeer()) {
|
||||||
return
|
return
|
||||||
@ -2002,56 +2127,6 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
component.controller()?.present(controller, in: .window(.root))
|
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] {
|
private func transformEnqueueMessages(view: StoryItemSetContainerComponent.View, messages: [EnqueueMessage], silentPosting: Bool, scheduleTime: Int32? = nil) -> [EnqueueMessage] {
|
||||||
var focusedStoryId: StoryId?
|
var focusedStoryId: StoryId?
|
||||||
if let component = view.component, let peerId = component.slice.item.peerId {
|
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) {
|
private func sendMessages(view: StoryItemSetContainerComponent.View, peer: EnginePeer, messages: [EnqueueMessage], silentPosting: Bool = false, scheduleTime: Int32? = nil) {
|
||||||
guard let component = view.component, let controller = component.controller() else {
|
guard let component = view.component else {
|
||||||
return
|
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: silentPosting, scheduleTime: scheduleTime))
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self, weak view] messageIds in
|
||||||
let _ = (enqueueMessages(account: component.context.account, peerId: peer.id, messages: self.transformEnqueueMessages(view: view, messages: messages, silentPosting: false))
|
Queue.mainQueue().after(0.3) {
|
||||||
|> deliverOnMainQueue).start(next: { [weak controller] messageIds in
|
if let view {
|
||||||
if let controller {
|
self?.presentMessageSentTooltip(view: view, peer: peer, messageId: messageIds.first.flatMap { $0 })
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -2180,10 +2242,8 @@ final class StoryItemSetContainerSendMessage {
|
|||||||
}
|
}
|
||||||
mappedMessages.append(message)
|
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()
|
completion()
|
||||||
}
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user