Merge commit 'b3d4c97da4'
This commit is contained in:
commit
001a5ed5b1
40 changed files with 1028 additions and 322 deletions
|
|
@ -16166,3 +16166,5 @@ Error: %8$@";
|
|||
"Attachment.PublicMusic" = "PUBLIC CHATS";
|
||||
|
||||
"PeerInfo.UnofficialSecurityRisk" = "%@ uses an unofficial Telegram client – messages to this user may be less secure.";
|
||||
|
||||
"Gallery.Live" = "LIVE";
|
||||
|
|
|
|||
|
|
@ -537,12 +537,12 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
self.statusBar?.statusBarStyle = .White
|
||||
}
|
||||
self.navigationBar?.alpha = transition
|
||||
self.footerNode.alpha = transition
|
||||
|
||||
if let currentThumbnailContainerNode = self.currentThumbnailContainerNode, let layout = self.containerLayout?.1, layout.size.width < layout.size.height {
|
||||
currentThumbnailContainerNode.alpha = transition
|
||||
}
|
||||
}
|
||||
self.footerNode.alpha = transition
|
||||
|
||||
self.updateDismissTransition(transition)
|
||||
self.updateDistanceFromEquilibrium(distanceFromEquilibrium)
|
||||
|
|
|
|||
|
|
@ -128,6 +128,20 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
|||
private var pagingEnabledDisposable: Disposable?
|
||||
|
||||
private var edgeLongTapTimer: Foundation.Timer?
|
||||
|
||||
private func isInteractiveControlView(_ view: UIView?) -> Bool {
|
||||
var currentView = view
|
||||
while let view = currentView {
|
||||
if view is UIControl {
|
||||
return true
|
||||
}
|
||||
if let _ = view.asyncdisplaykit_node as? ASButtonNode {
|
||||
return true
|
||||
}
|
||||
currentView = view.superview
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public init(pageGap: CGFloat, disableTapNavigation: Bool) {
|
||||
self.pageGap = pageGap
|
||||
|
|
@ -220,7 +234,7 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
|
|||
return .fail
|
||||
}
|
||||
|
||||
if let result = strongSelf.hitTest(point, with: nil), let _ = result.asyncdisplaykit_node as? ASButtonNode {
|
||||
if strongSelf.isInteractiveControlView(strongSelf.hitTest(point, with: nil)) {
|
||||
return .fail
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import MultilineTextComponent
|
|||
import BundleIconComponent
|
||||
import VideoPlaybackControlsComponent
|
||||
import PhotoResources
|
||||
import GlassBackgroundComponent
|
||||
|
||||
public enum UniversalVideoGalleryItemContentInfo {
|
||||
case message(Message, GalleryMediaSubject?)
|
||||
|
|
@ -221,6 +222,8 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
private var context: AccountContext?
|
||||
|
||||
private var adView = ComponentView<Empty>()
|
||||
private var livePhotoButton: LivePhotoButton?
|
||||
private var livePhotoButtonIsPlaying = false
|
||||
|
||||
private var message: Message?
|
||||
private var adContext: AdMessagesHistoryContext?
|
||||
|
|
@ -233,7 +236,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
var presentPremiumDemo: (() -> Void)?
|
||||
var openMoreMenu: ((UIView, Message) -> Void)?
|
||||
|
||||
private var validLayout: (size: CGSize, metrics: LayoutMetrics, insets: UIEdgeInsets)?
|
||||
private var validLayout: (size: CGSize, metrics: LayoutMetrics, insets: UIEdgeInsets, isHidden: Bool)?
|
||||
|
||||
deinit {
|
||||
self.adDisposable.dispose()
|
||||
|
|
@ -280,34 +283,61 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
}
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: false, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: validLayout.isHidden, transition: .immediate)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func setLivePhotoButton(context: AccountContext, isVisible: Bool, isPlaying: Bool, pressed: @escaping () -> Void) {
|
||||
self.livePhotoButtonIsPlaying = isPlaying
|
||||
|
||||
if isVisible {
|
||||
let livePhotoButton: LivePhotoButton
|
||||
if let current = self.livePhotoButton {
|
||||
livePhotoButton = current
|
||||
} else {
|
||||
livePhotoButton = LivePhotoButton(context: context)
|
||||
self.livePhotoButton = livePhotoButton
|
||||
}
|
||||
livePhotoButton.pressed = pressed
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: validLayout.isHidden, transition: .immediate)
|
||||
}
|
||||
} else if let livePhotoButton = self.livePhotoButton {
|
||||
self.livePhotoButton = nil
|
||||
livePhotoButton.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
var timer: SwiftSignalKit.Timer?
|
||||
var hiddenMessages = Set<MessageId>()
|
||||
var isAnimatingOut = false
|
||||
var reportedMessages = Set<Data>()
|
||||
|
||||
override func updateLayout(size: CGSize, metrics: LayoutMetrics, insets: UIEdgeInsets, isHidden: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, metrics, insets)
|
||||
self.validLayout = (size, metrics, insets, isHidden)
|
||||
|
||||
if self.timer == nil {
|
||||
if self.timer == nil && self.adState != nil {
|
||||
self.timer = SwiftSignalKit.Timer(timeout: 0.5, repeat: true, completion: { [weak self] progress in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: false, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: validLayout.isHidden, transition: .immediate)
|
||||
}
|
||||
}, queue: Queue.mainQueue())
|
||||
self.timer?.start()
|
||||
}
|
||||
|
||||
let isLandscape = size.width > size.height
|
||||
let _ = isLandscape
|
||||
|
||||
if let livePhotoButton = self.livePhotoButton {
|
||||
let livePhotoButtonSize = livePhotoButton.update(isPlaying: self.livePhotoButtonIsPlaying)
|
||||
if livePhotoButton.superview == nil {
|
||||
self.view.addSubview(livePhotoButton)
|
||||
}
|
||||
transition.updateFrame(view: livePhotoButton, frame: CGRect(origin: CGPoint(x: 16.0, y: insets.top + 10.0), size: livePhotoButtonSize))
|
||||
}
|
||||
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent())
|
||||
var currentAd: (Int32, Message?)?
|
||||
|
||||
|
|
@ -349,7 +379,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
if available {
|
||||
self.hiddenMessages.insert(adMessage.id)
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: false, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: validLayout.isHidden, transition: .immediate)
|
||||
}
|
||||
} else {
|
||||
self.presentPremiumDemo?()
|
||||
|
|
@ -359,7 +389,7 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
if let self, let ad = adMessage.adAttribute {
|
||||
self.hiddenMessages.insert(adMessage.id)
|
||||
if let validLayout = self.validLayout {
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: false, transition: .immediate)
|
||||
self.updateLayout(size: validLayout.size, metrics: validLayout.metrics, insets: validLayout.insets, isHidden: validLayout.isHidden, transition: .immediate)
|
||||
}
|
||||
context.engine.messages.markAdAction(opaqueId: ad.opaqueId, media: false, fullscreen: false)
|
||||
self.performAction?(.url(url: ad.url, concealed: false, forceExternal: true, dismiss: false))
|
||||
|
|
@ -398,7 +428,9 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
}
|
||||
|
||||
override func animateIn(previousContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
if let livePhotoButton = self.livePhotoButton {
|
||||
livePhotoButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
override func animateOut(nextContentNode: GalleryOverlayContentNode?, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
|
|
@ -406,6 +438,12 @@ private final class UniversalVideoGalleryItemOverlayNode: GalleryOverlayContentN
|
|||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if let livePhotoButton = self.livePhotoButton {
|
||||
let buttonPoint = self.view.convert(point, to: livePhotoButton)
|
||||
if let result = livePhotoButton.hitTest(buttonPoint, with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
if let adView = self.adView.view, adView.frame.contains(point) {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
|
@ -893,7 +931,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private let statusButtonNode: HighlightableButtonNode
|
||||
private let statusNode: RadialStatusNode
|
||||
private var statusNodeShouldBeHidden = true
|
||||
private var livePhotoIconNode: ASImageNode?
|
||||
|
||||
private let playbackControls = ComponentView<Empty>()
|
||||
|
||||
|
|
@ -963,9 +1000,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var activeEdgeRateIndicator: ComponentView<Empty>?
|
||||
|
||||
private var isAnimatingOut: Bool = false
|
||||
|
||||
private var isLivePhoto = false
|
||||
private var didAutoplayLivePhotoOnce = false
|
||||
private var isPlayingLivePhotoByHolding = false
|
||||
private var isLivePhotoPlaybackActive = false
|
||||
private var isLivePhotoGestureActive = false
|
||||
|
||||
init(context: AccountContext, presentationData: PresentationData, performAction: @escaping (GalleryControllerInteractionTapAction) -> Void, openActionOptions: @escaping (GalleryControllerInteractionTapAction, Message) -> Void, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.context = context
|
||||
|
|
@ -1275,7 +1314,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
let transition = ComponentTransition(transition)
|
||||
transition.setFrame(view: playbackControlsView, frame: playbackControlsFrame)
|
||||
}
|
||||
|
||||
|
||||
if let pictureInPictureNode = self.pictureInPictureNode {
|
||||
if let item = self.item {
|
||||
var placeholderSize = item.content.dimensions.fitted(layout.size)
|
||||
|
|
@ -1374,10 +1413,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self.statusButtonNode.isHidden = true
|
||||
}
|
||||
|
||||
self.resetLivePhotoPlayback()
|
||||
self.dismissOnOrientationChange = item.landscape
|
||||
self.isLivePhoto = false
|
||||
self.didAutoplayLivePhotoOnce = false
|
||||
self.isPlayingLivePhotoByHolding = false
|
||||
self.isLivePhotoPlaybackActive = false
|
||||
|
||||
var hasLinkedStickers = false
|
||||
if let content = item.content as? NativeVideoContent {
|
||||
|
|
@ -1716,7 +1756,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
initialBuffering = true
|
||||
}
|
||||
isPaused = !whilePlaying
|
||||
if strongSelf.isPlayingLivePhotoByHolding {
|
||||
if strongSelf.isLivePhotoPlaybackActive {
|
||||
isPaused = false
|
||||
}
|
||||
var isStreaming = false
|
||||
|
|
@ -1961,7 +2001,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
|
||||
if strongSelf.isLivePhoto {
|
||||
if !strongSelf.isPlayingLivePhotoByHolding {
|
||||
if !strongSelf.isLivePhotoPlaybackActive {
|
||||
strongSelf.setLivePhotoVideoVisible(false, animated: true)
|
||||
}
|
||||
} else if let snapshotView = videoNode?.view.snapshotView(afterScreenUpdates: false) {
|
||||
|
|
@ -1973,7 +2013,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
if !strongSelf.isLivePhoto {
|
||||
videoNode?.seek(0.0)
|
||||
} else if strongSelf.isPlayingLivePhotoByHolding {
|
||||
} else if strongSelf.isLivePhotoPlaybackActive {
|
||||
videoNode?.playOnceWithSound(playAndRecord: false, actionAtEnd: .loop)
|
||||
}
|
||||
|
||||
|
|
@ -2058,24 +2098,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
} : nil)))
|
||||
}
|
||||
|
||||
if self.isLivePhoto {
|
||||
let livePhotoIconNode: ASImageNode
|
||||
if let current = self.livePhotoIconNode {
|
||||
livePhotoIconNode = current
|
||||
} else {
|
||||
livePhotoIconNode = ASImageNode()
|
||||
livePhotoIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Editor/LiveOn"), color: .white)
|
||||
//self.addSubnode(livePhotoIconNode)
|
||||
self.livePhotoIconNode = livePhotoIconNode
|
||||
}
|
||||
|
||||
if let icon = livePhotoIconNode.image {
|
||||
livePhotoIconNode.frame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: icon.size)
|
||||
}
|
||||
} else if let livePhotoIconNode = self.livePhotoIconNode {
|
||||
self.livePhotoIconNode = nil
|
||||
livePhotoIconNode.removeFromSupernode()
|
||||
}
|
||||
self.updateLivePhotoButton()
|
||||
}
|
||||
|
||||
override func controlsVisibilityUpdated(isVisible: Bool, animated: Bool) {
|
||||
|
|
@ -2169,26 +2192,41 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
private func updateLivePhotoHoldPlayback(isActive: Bool, animated: Bool) {
|
||||
guard self.isLivePhoto, let videoNode = self.videoNode else {
|
||||
private func updateLivePhotoPlayback(isActive: Bool, animated: Bool) {
|
||||
guard self.isLivePhoto, self.isLivePhotoPlaybackActive != isActive, let videoNode = self.videoNode else {
|
||||
return
|
||||
}
|
||||
guard self.isPlayingLivePhotoByHolding != isActive else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isPlayingLivePhotoByHolding = isActive
|
||||
|
||||
self.isLivePhotoPlaybackActive = isActive
|
||||
self.updateLivePhotoButton()
|
||||
if isActive {
|
||||
self.setLivePhotoVideoVisible(true, animated: animated)
|
||||
videoNode.seek(0.0)
|
||||
videoNode.playOnceWithSound(playAndRecord: false)
|
||||
videoNode.playOnceWithSound(playAndRecord: false, actionAtEnd: .loop)
|
||||
} else {
|
||||
videoNode.pause()
|
||||
videoNode.seek(0.0)
|
||||
self.setLivePhotoVideoVisible(false, animated: animated)
|
||||
}
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private func resetLivePhotoPlayback() {
|
||||
self.updateLivePhotoPlayback(isActive: false, animated: false)
|
||||
}
|
||||
|
||||
private func updateLivePhotoButton() {
|
||||
self.overlayContentNode.setLivePhotoButton(context: self.context, isVisible: self.isLivePhoto, isPlaying: self.isLivePhotoPlaybackActive, pressed: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.updateLivePhotoPlayback(isActive: !self.isLivePhotoPlaybackActive, animated: true)
|
||||
})
|
||||
}
|
||||
|
||||
override func centralityUpdated(isCentral: Bool) {
|
||||
super.centralityUpdated(isCentral: isCentral)
|
||||
|
||||
|
|
@ -2228,7 +2266,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
self.updateLivePhotoHoldPlayback(isActive: false, animated: false)
|
||||
self.resetLivePhotoPlayback()
|
||||
self.isPlayingPromise.set(false)
|
||||
self.isPlaying = false
|
||||
|
||||
|
|
@ -2265,17 +2303,19 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
if self.skipInitialPause {
|
||||
self.skipInitialPause = false
|
||||
} else {
|
||||
self.updateLivePhotoHoldPlayback(isActive: false, animated: false)
|
||||
self.resetLivePhotoPlayback()
|
||||
self.ignorePauseStatus = true
|
||||
videoNode.pause()
|
||||
videoNode.seek(0.0)
|
||||
}
|
||||
} else {
|
||||
self.updateLivePhotoHoldPlayback(isActive: false, animated: false)
|
||||
self.resetLivePhotoPlayback()
|
||||
if let status = self.playerStatusValue {
|
||||
self.maybeStorePlaybackStatus(status: status)
|
||||
}
|
||||
videoNode.continuePlayingWithoutSound()
|
||||
if !self.isLivePhoto {
|
||||
videoNode.continuePlayingWithoutSound()
|
||||
}
|
||||
}
|
||||
self.updateDisplayPlaceholder()
|
||||
} else if !item.fromPlayingVideo {
|
||||
|
|
@ -2382,7 +2422,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
private var actionAtEnd: MediaPlayerPlayOnceWithSoundActionAtEnd {
|
||||
if self.isLivePhoto {
|
||||
return self.isPlayingLivePhotoByHolding ? .loop : .stop
|
||||
return self.isLivePhotoPlaybackActive ? .loop : .stop
|
||||
}
|
||||
if let item = self.item {
|
||||
if !item.isSecret, let content = item.content as? NativeVideoContent, content.duration <= 30 {
|
||||
|
|
@ -4082,7 +4122,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
return
|
||||
}
|
||||
if let edge, case .middle = edge {
|
||||
self.updateLivePhotoHoldPlayback(isActive: true, animated: true)
|
||||
guard self.isLivePhoto else {
|
||||
return
|
||||
}
|
||||
if !self.isLivePhotoPlaybackActive {
|
||||
self.updateLivePhotoPlayback(isActive: true, animated: true)
|
||||
}
|
||||
self.isLivePhotoGestureActive = true
|
||||
} else if let edge, case .right = edge {
|
||||
let effectiveRate: Double
|
||||
if let current = self.activeEdgeRateState {
|
||||
|
|
@ -4097,7 +4143,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
videoNode.setBaseRate(effectiveRate)
|
||||
} else {
|
||||
self.updateLivePhotoHoldPlayback(isActive: false, animated: true)
|
||||
if self.isLivePhotoGestureActive {
|
||||
self.updateLivePhotoPlayback(isActive: false, animated: true)
|
||||
}
|
||||
self.isLivePhotoGestureActive = false
|
||||
|
||||
if let (initialRate, _) = self.activeEdgeRateState {
|
||||
self.activeEdgeRateState = nil
|
||||
videoNode.setBaseRate(initialRate)
|
||||
|
|
@ -4154,3 +4204,87 @@ final class HeaderContextReferenceContentSource: ContextReferenceContentSource {
|
|||
private func normalizeValue(_ value: CGFloat) -> CGFloat {
|
||||
return round(value * 10.0) / 10.0
|
||||
}
|
||||
|
||||
private final class LivePhotoButton: UIView {
|
||||
private let context: AccountContext
|
||||
|
||||
private let backgroundView: GlassBackgroundView
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let label = ComponentView<Empty>()
|
||||
private let button = HighlightTrackingButton()
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
return self.bounds.insetBy(dx: -16.0, dy: -16.0).contains(point)
|
||||
}
|
||||
|
||||
var pressed: () -> Void = {}
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
||||
self.backgroundView = GlassBackgroundView()
|
||||
|
||||
super.init(frame: .zero)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.backgroundView.contentView.addSubview(self.button)
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
func update(isPlaying: Bool = false) -> CGSize {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let iconName: String = isPlaying ? "Media Gallery/LivePhotoPlaying" : "Media Gallery/LivePhotoPlay"
|
||||
let labelText: String = presentationData.strings.Gallery_Live
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(name: iconName, tintColor: .white)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 18.0, height: 18.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: 8.0, y: floorToScreenPixels((30.0 - iconSize.height) / 2.0)), size: iconSize)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = iconFrame
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
Text(text: labelText.uppercased(), font: Font.regular(12.0), color: .white)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 18.0)
|
||||
)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: 28.0, y: floorToScreenPixels((30.0 - labelSize.height) / 2.0)), size: labelSize)
|
||||
if let labelView = self.label.view {
|
||||
if labelView.superview == nil {
|
||||
labelView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(labelView)
|
||||
}
|
||||
labelView.frame = labelFrame
|
||||
}
|
||||
|
||||
let size = CGSize(width: 21.0 + labelSize.width + 18.0, height: 30.0)
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate)
|
||||
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
||||
self.button.frame = CGRect(origin: .zero, size: size).insetBy(dx: -16.0, dy: -16.0)
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -562,7 +562,14 @@ public class ItemListSwitchItemNode: ListViewItemNode, ItemListItemNode {
|
|||
let iconOrigin: CGPoint
|
||||
switch item.systemStyle {
|
||||
case .glass:
|
||||
iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 16.0 + UIScreenPixel : switchFrame.minX + 16.0 - UIScreenPixel, y: switchFrame.minY + 8.0)
|
||||
let offset: CGPoint
|
||||
if #available(iOS 26.0, *) {
|
||||
offset = .zero
|
||||
} else {
|
||||
offset = CGPoint(x: 5.0, y: 1.0)
|
||||
}
|
||||
|
||||
iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 16.0 + UIScreenPixel + offset.x : switchFrame.minX + 16.0 - UIScreenPixel - offset.x, y: switchFrame.minY + 8.0 + offset.y)
|
||||
case .legacy:
|
||||
iconOrigin = CGPoint(x: item.value ? switchFrame.maxX - icon.size.width - 11.0 : switchFrame.minX + 11.0, y: switchFrame.minY + 9.0)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,6 +59,74 @@ static TGMediaLivePhotoMode TGMediaAssetsResolvedLivePhotoMode(TGMediaEditingCon
|
|||
return editingContext.isForceLivePhotoEnabled ? TGMediaLivePhotoModeLive : TGMediaLivePhotoModeOff;
|
||||
}
|
||||
|
||||
static TGMediaLivePhotoMode TGMediaAssetsEffectiveLivePhotoSendMode(TGMediaEditingContext *editingContext, NSObject<TGMediaEditableItem> *item, id<TGMediaEditAdjustments> adjustments)
|
||||
{
|
||||
TGMediaLivePhotoMode livePhotoMode = TGMediaAssetsResolvedLivePhotoMode(editingContext, item);
|
||||
if (livePhotoMode == TGMediaLivePhotoModeLive && adjustments.paintingData.hasAnimation)
|
||||
return TGMediaLivePhotoModeLoop;
|
||||
|
||||
return livePhotoMode;
|
||||
}
|
||||
|
||||
static NSString *TGMediaAssetsLivePhotoPaintingImagePath(TGPaintingData *paintingData)
|
||||
{
|
||||
if (paintingData.imagePath.length > 0)
|
||||
return paintingData.imagePath;
|
||||
|
||||
UIImage *paintingImage = paintingData.image;
|
||||
if (paintingImage == nil)
|
||||
return nil;
|
||||
|
||||
NSData *paintingImageData = UIImagePNGRepresentation(paintingImage);
|
||||
if (paintingImageData == nil)
|
||||
return nil;
|
||||
|
||||
int64_t randomId = 0;
|
||||
arc4random_buf(&randomId, sizeof(randomId));
|
||||
NSString *filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"livephoto_painting_%llx.png", randomId]];
|
||||
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
|
||||
if (![paintingImageData writeToFile:filePath atomically:true])
|
||||
return nil;
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
static TGVideoEditAdjustments *TGMediaAssetsPatchedLivePhotoAdjustments(PGPhotoEditorValues *values, TGMediaVideoConversionPreset preset, bool bounce, bool sendAsGif)
|
||||
{
|
||||
TGVideoEditAdjustments *videoAdjustments = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:values preset:preset bounce:bounce sendAsGif:sendAsGif];
|
||||
TGPaintingData *paintingData = values.paintingData;
|
||||
NSString *paintingImagePath = TGMediaAssetsLivePhotoPaintingImagePath(paintingData);
|
||||
if (paintingImagePath.length == 0)
|
||||
return videoAdjustments;
|
||||
|
||||
NSMutableDictionary *adjustmentsDictionary = [[videoAdjustments dictionary] mutableCopy];
|
||||
if (adjustmentsDictionary == nil)
|
||||
return videoAdjustments;
|
||||
|
||||
adjustmentsDictionary[@"paintingImagePath"] = paintingImagePath;
|
||||
if (paintingData.stickers.count > 0)
|
||||
adjustmentsDictionary[@"stickersData"] = paintingData.stickers;
|
||||
|
||||
TGVideoEditAdjustments *dictionaryAdjustments = [TGVideoEditAdjustments editAdjustmentsWithDictionary:adjustmentsDictionary];
|
||||
TGPaintingData *patchedPaintingData = dictionaryAdjustments.paintingData;
|
||||
if (patchedPaintingData == nil) {
|
||||
if (paintingData.entitiesData != nil) {
|
||||
patchedPaintingData = [TGPaintingData dataWithPaintingImagePath:paintingImagePath entitiesData:paintingData.entitiesData hasAnimation:paintingData.hasAnimation stickers:paintingData.stickers];
|
||||
} else {
|
||||
patchedPaintingData = [TGPaintingData dataWithPaintingImagePath:paintingImagePath];
|
||||
}
|
||||
}
|
||||
|
||||
TGVideoEditAdjustments *patchedAdjustments = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:videoAdjustments.originalSize cropRect:videoAdjustments.cropRect cropOrientation:videoAdjustments.cropOrientation cropRotation:videoAdjustments.cropRotation cropLockedAspectRatio:videoAdjustments.cropLockedAspectRatio cropMirrored:videoAdjustments.cropMirrored trimStartValue:videoAdjustments.trimStartValue trimEndValue:videoAdjustments.trimEndValue toolValues:videoAdjustments.toolValues paintingData:patchedPaintingData sendAsGif:videoAdjustments.sendAsGif preset:videoAdjustments.preset];
|
||||
if (patchedAdjustments == nil)
|
||||
return videoAdjustments;
|
||||
|
||||
if (videoAdjustments.bounce != patchedAdjustments.bounce)
|
||||
[patchedAdjustments setValue:@(videoAdjustments.bounce) forKey:@"bounce"];
|
||||
|
||||
return patchedAdjustments;
|
||||
}
|
||||
|
||||
@interface TGMediaPickerAccessView: UIView
|
||||
{
|
||||
TGMediaAssetsPallete *_pallete;
|
||||
|
|
@ -1022,7 +1090,6 @@ static TGMediaLivePhotoMode TGMediaAssetsResolvedLivePhotoMode(TGMediaEditingCon
|
|||
}
|
||||
|
||||
NSAttributedString *caption = [editingContext captionForItem:asset];
|
||||
TGMediaLivePhotoMode livePhotoMode = TGMediaAssetsResolvedLivePhotoMode(editingContext, asset);
|
||||
|
||||
if (editingContext.isForcedCaption) {
|
||||
if (grouping && num > 0) {
|
||||
|
|
@ -1091,6 +1158,7 @@ static TGMediaLivePhotoMode TGMediaAssetsResolvedLivePhotoMode(TGMediaEditingCon
|
|||
else
|
||||
{
|
||||
id<TGMediaEditAdjustments> adjustments = [editingContext adjustmentsForItem:asset];
|
||||
TGMediaLivePhotoMode livePhotoMode = TGMediaAssetsEffectiveLivePhotoSendMode(editingContext, asset, adjustments);
|
||||
NSNumber *timer = [editingContext timerForItem:asset];
|
||||
|
||||
SSignal *inlineSignal = [inlineThumbnailSignal(asset) map:^id(UIImage *image)
|
||||
|
|
@ -1222,20 +1290,20 @@ static TGMediaLivePhotoMode TGMediaAssetsResolvedLivePhotoMode(TGMediaEditingCon
|
|||
|
||||
if (livePhotoMode == TGMediaLivePhotoModeBounce) {
|
||||
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) {
|
||||
dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:adjustments preset:TGMediaVideoConversionPresetCompressedHigh bounce:true sendAsGif:true];
|
||||
dict[@"adjustments"] = TGMediaAssetsPatchedLivePhotoAdjustments((PGPhotoEditorValues *)adjustments, TGMediaVideoConversionPresetCompressedHigh, true, true);
|
||||
} else {
|
||||
dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions preset:TGMediaVideoConversionPresetCompressedHigh bounce:true];
|
||||
}
|
||||
} else if (livePhotoMode == TGMediaLivePhotoModeLoop) {
|
||||
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) {
|
||||
dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:adjustments preset:TGMediaVideoConversionPresetCompressedHigh bounce:false sendAsGif:true];
|
||||
dict[@"adjustments"] = TGMediaAssetsPatchedLivePhotoAdjustments((PGPhotoEditorValues *)adjustments, TGMediaVideoConversionPresetCompressedHigh, false, true);
|
||||
} else {
|
||||
dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithOriginalSize:dimensions preset:TGMediaVideoConversionPresetCompressedHigh bounce:false];
|
||||
}
|
||||
} else {
|
||||
dict[@"livePhoto"] = @true;
|
||||
if ([adjustments isKindOfClass:[PGPhotoEditorValues class]]) {
|
||||
dict[@"adjustments"] = [TGVideoEditAdjustments editAdjustmentsWithPhotoEditorValues:adjustments preset:TGMediaVideoConversionPresetCompressedMedium bounce:false sendAsGif:false];
|
||||
dict[@"adjustments"] = TGMediaAssetsPatchedLivePhotoAdjustments((PGPhotoEditorValues *)adjustments, TGMediaVideoConversionPresetCompressedMedium, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -211,17 +211,17 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
self.addSubnode(self.descriptionNode)
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ topic: (title: NSAttributedString, showIcon: Bool, iconId: Int64?, iconColor: Int32)?) -> (CGSize, () -> Void) {
|
||||
func asyncLayout() -> (_ context: AccountContext, _ constrainedWidth: CGFloat, _ theme: PresentationTheme, _ authorTitle: NSAttributedString?, _ truncateType: CTLineTruncationType, _ topic: (title: NSAttributedString, showIcon: Bool, iconId: Int64?, iconColor: Int32)?) -> (CGSize, () -> Void) {
|
||||
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
let makeTopicTitleLayout = TextNode.asyncLayout(self.topicTitleNode)
|
||||
|
||||
return { [weak self] context, constrainedWidth, theme, authorTitle, topic in
|
||||
return { [weak self] context, constrainedWidth, theme, authorTitle, truncateType, topic in
|
||||
var maxTitleWidth = constrainedWidth
|
||||
if let _ = topic {
|
||||
maxTitleWidth = floor(constrainedWidth * 0.7)
|
||||
}
|
||||
|
||||
let descriptionLayout = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: authorTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .middle, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
let descriptionLayout = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: authorTitle, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: truncateType, constrainedSize: CGSize(width: maxTitleWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
|
||||
|
||||
var remainingWidth = constrainedWidth - descriptionLayout.0.size.width
|
||||
|
||||
|
|
@ -524,7 +524,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
}
|
||||
|
||||
if isExtracted {
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.plainBackgroundColor)
|
||||
strongSelf.extractedBackgroundImageNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: item.presentationData.theme.theme.list.itemModalBlocksBackgroundColor)
|
||||
}
|
||||
|
||||
if let extractedRect = strongSelf.extractedRect, let nonExtractedRect = strongSelf.nonExtractedRect {
|
||||
|
|
@ -1036,19 +1036,22 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
reorderInset = sizeAndApply.0
|
||||
}
|
||||
|
||||
let contentRightInset = rightInset + rightOffset + reorderInset
|
||||
var contentRightInset = rightInset + rightOffset + reorderInset
|
||||
if !item.isGlobalSearchResult {
|
||||
contentRightInset += 16.0
|
||||
}
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let dateText = stringForRelativeTimestamp(strings: item.presentationData.strings, relativeTimestamp: item.message?.timestamp ?? 0, relativeTo: timestamp, dateTimeFormat: item.presentationData.dateTimeFormat)
|
||||
let dateAttributedString = NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
let dateAttributedString = !item.isGlobalSearchResult ? NSAttributedString() : NSAttributedString(string: dateText, font: dateFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
|
||||
let (dateNodeLayout, dateNodeApply) = dateNodeMakeLayout(TextNodeLayoutArguments(attributedString: dateAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - contentRightInset - 12.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(item.context, params.width - leftInset - leftOffset - contentRightInset - dateNodeLayout.size.width - 4.0, item.presentationData.theme.theme, titleText, titleExtraData)
|
||||
let (titleNodeLayout, titleNodeApply) = titleNodeMakeLayout(item.context, params.width - leftInset - leftOffset - contentRightInset - dateNodeLayout.size.width - 4.0, item.presentationData.theme.theme, titleText, isAudio ? .end : .middle, titleExtraData)
|
||||
|
||||
let (textNodeLayout, textNodeApply) = textNodeMakeLayout(TextNodeLayoutArguments(attributedString: captionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - leftOffset - contentRightInset - 30.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(item.context, params.width - leftInset - leftOffset - contentRightInset - 30.0, item.presentationData.theme.theme, descriptionText, descriptionExtraData)
|
||||
let (descriptionNodeLayout, descriptionNodeApply) = descriptionNodeMakeLayout(item.context, params.width - leftInset - leftOffset - contentRightInset - 30.0, item.presentationData.theme.theme, descriptionText, .middle, descriptionExtraData)
|
||||
|
||||
var (extensionTextLayout, extensionTextApply) = extensionIconTextMakeLayout(TextNodeLayoutArguments(attributedString: extensionText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 38.0, height: CGFloat.infinity), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
if extensionTextLayout.truncated, let text = extensionText?.string {
|
||||
|
|
@ -1168,7 +1171,7 @@ public final class ListMessageFileItemNode: ListMessageNode {
|
|||
strongSelf.backgroundNode = backgroundNode
|
||||
strongSelf.insertSubnode(backgroundNode, at: 0)
|
||||
}
|
||||
backgroundNode.backgroundColor = item.canReorder ? item.presentationData.theme.theme.list.plainBackgroundColor : item.presentationData.theme.theme.list.itemBlocksBackgroundColor
|
||||
backgroundNode.backgroundColor = item.canReorder ? item.presentationData.theme.theme.list.itemModalBlocksBackgroundColor : item.presentationData.theme.theme.list.itemBlocksBackgroundColor
|
||||
}
|
||||
|
||||
strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor
|
||||
|
|
|
|||
|
|
@ -672,7 +672,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode {
|
|||
}
|
||||
|
||||
let authorText = NSAttributedString(string: authorString, font: authorFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor)
|
||||
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(item.context, params.width - leftInset - leftOffset - contentRightInset - 30.0, item.presentationData.theme.theme, authorText, forumThreadTitle)
|
||||
let (authorNodeLayout, authorNodeApply) = authorNodeMakeLayout(item.context, params.width - leftInset - leftOffset - contentRightInset - 30.0, item.presentationData.theme.theme, authorText, .middle, forumThreadTitle)
|
||||
|
||||
var contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
|
||||
if !authorString.isEmpty {
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ final class LivePhotoButton: UIView, TGLivePhotoButton {
|
|||
let size = CGSize(width: 19.0 + labelSize.width + 16.0, height: 18.0)
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: true, tintColor: .init(kind: .panel), isInteractive: true, transition: .immediate)
|
||||
self.backgroundView.frame = CGRect(origin: .zero, size: size)
|
||||
self.button.frame = CGRect(origin: .zero, size: size)
|
||||
self.button.frame = CGRect(origin: .zero, size: size).insetBy(dx: -16.0, dy: -16.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2787,124 +2787,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// if let cancelButton = self.cancelButton {
|
||||
// if cancelButton.view == nil {
|
||||
// buttonTransition = .immediate
|
||||
// }
|
||||
// let cancelButtonSize = cancelButton.update(
|
||||
// transition: buttonTransition,
|
||||
// component: AnyComponent(GlassBarButtonComponent(
|
||||
// size: barButtonSize,
|
||||
// backgroundColor: nil,
|
||||
// isDark: self.presentationData.theme.overallDarkAppearance,
|
||||
// state: .glass,
|
||||
// component: AnyComponentWithIdentity(id: isBack ? "back" : "close", component: AnyComponent(
|
||||
// BundleIconComponent(
|
||||
// name: isBack ? "Navigation/Back" : "Navigation/Close",
|
||||
// tintColor: self.presentationData.theme.chat.inputPanel.panelControlColor
|
||||
// )
|
||||
// )),
|
||||
// action: { [weak self] _ in
|
||||
// self?.cancelPressed()
|
||||
// }
|
||||
// )),
|
||||
// environment: {},
|
||||
// containerSize: barButtonSize
|
||||
// )
|
||||
// let cancelButtonFrame = CGRect(origin: CGPoint(x: barButtonSideInset + layout.safeInsets.left, y: barButtonSideInset), size: cancelButtonSize)
|
||||
// if let view = cancelButton.view {
|
||||
// if view.superview == nil {
|
||||
// self.view.addSubview(view)
|
||||
// }
|
||||
// view.bounds = CGRect(origin: .zero, size: cancelButtonFrame.size)
|
||||
// view.center = cancelButtonFrame.center
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if moreIsVisible, case .glass = self.style {
|
||||
// let moreButton: ComponentView<Empty>
|
||||
// if let current = self.rightButton {
|
||||
// moreButton = current
|
||||
// } else {
|
||||
// moreButton = ComponentView<Empty>()
|
||||
// self.rightButton = moreButton
|
||||
// }
|
||||
//
|
||||
// let buttonComponent: AnyComponent<Empty>
|
||||
// let rightButtonSize: CGSize
|
||||
// if self.hasSelectButton {
|
||||
// buttonComponent = AnyComponent(GlassBarButtonComponent(
|
||||
// size: nil,
|
||||
// backgroundColor: nil,
|
||||
// isDark: self.presentationData.theme.overallDarkAppearance,
|
||||
// state: .glass,
|
||||
// component: AnyComponentWithIdentity(id: "select", component: AnyComponent(
|
||||
// Text(text: self.presentationData.strings.Common_Select, font: Font.semibold(17.0), color: self.presentationData.theme.chat.inputPanel.panelControlColor)
|
||||
// )),
|
||||
// action: { [weak self] _ in
|
||||
// self?.selectPressed()
|
||||
// })
|
||||
// )
|
||||
// rightButtonSize = CGSize(width: 160.0, height: 44.0)
|
||||
// } else {
|
||||
// buttonComponent = AnyComponent(GlassBarButtonComponent(
|
||||
// size: barButtonSize,
|
||||
// backgroundColor: nil,
|
||||
// isDark: self.presentationData.theme.overallDarkAppearance,
|
||||
// state: .glass,
|
||||
// component: AnyComponentWithIdentity(id: "more", component: AnyComponent(
|
||||
// LottieComponent(
|
||||
// content: LottieComponent.AppBundleContent(
|
||||
// name: "anim_morewide"
|
||||
// ),
|
||||
// color: self.presentationData.theme.chat.inputPanel.panelControlColor,
|
||||
// size: CGSize(width: 34.0, height: 34.0),
|
||||
// playOnce: self.moreButtonPlayOnce
|
||||
// )
|
||||
// )),
|
||||
// action: { [weak self] view in
|
||||
// self?.searchOrMorePressed(view: view, gesture: nil)
|
||||
// self?.moreButtonPlayOnce.invoke(Void())
|
||||
// })
|
||||
// )
|
||||
// rightButtonSize = barButtonSize
|
||||
// }
|
||||
//
|
||||
// let moreButtonSize = moreButton.update(
|
||||
// transition: buttonTransition,
|
||||
// component: buttonComponent,
|
||||
// environment: {},
|
||||
// containerSize: rightButtonSize
|
||||
// )
|
||||
// let moreButtonFrame = CGRect(origin: CGPoint(x: layout.size.width - moreButtonSize.width - barButtonSideInset - layout.safeInsets.right, y: barButtonSideInset), size: moreButtonSize)
|
||||
// if let view = moreButton.view {
|
||||
// if view.superview == nil {
|
||||
// self.view.addSubview(view)
|
||||
// if transition.isAnimated {
|
||||
// let transition = ComponentTransition(transition)
|
||||
// transition.animateAlpha(view: view, from: 0.0, to: 1.0)
|
||||
// transition.animateScale(view: view, from: 0.1, to: 1.0)
|
||||
// }
|
||||
// }
|
||||
// view.bounds = CGRect(origin: .zero, size: moreButtonFrame.size)
|
||||
// view.center = moreButtonFrame.center
|
||||
// }
|
||||
// } else if let moreButton = self.rightButton {
|
||||
// self.rightButton = nil
|
||||
// if let view = moreButton.view {
|
||||
// if transition.isAnimated {
|
||||
// let transition = ComponentTransition(transition)
|
||||
// view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
// view.removeFromSuperview()
|
||||
// })
|
||||
// transition.animateScale(view: view, from: 1.0, to: 0.1)
|
||||
// } else {
|
||||
// view.removeFromSuperview()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if case .assets(_, .story) = self.subject {
|
||||
if self.selectionCount > 0 {
|
||||
let text = self.presentationData.strings.MediaPicker_CreateStory(self.selectionCount)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ private final class SheetContent: CombinedComponent {
|
|||
let mode: PremiumBoostLevelsScreen.Mode
|
||||
let status: ChannelBoostStatus?
|
||||
let boostState: InternalBoostState.DisplayData?
|
||||
let bottomInset: CGFloat
|
||||
let boost: () -> Void
|
||||
let copyLink: (String) -> Void
|
||||
let dismiss: () -> Void
|
||||
|
|
@ -52,6 +53,7 @@ private final class SheetContent: CombinedComponent {
|
|||
mode: PremiumBoostLevelsScreen.Mode,
|
||||
status: ChannelBoostStatus?,
|
||||
boostState: InternalBoostState.DisplayData?,
|
||||
bottomInset: CGFloat,
|
||||
boost: @escaping () -> Void,
|
||||
copyLink: @escaping (String) -> Void,
|
||||
dismiss: @escaping () -> Void,
|
||||
|
|
@ -67,6 +69,7 @@ private final class SheetContent: CombinedComponent {
|
|||
self.mode = mode
|
||||
self.status = status
|
||||
self.boostState = boostState
|
||||
self.bottomInset = bottomInset
|
||||
self.boost = boost
|
||||
self.copyLink = copyLink
|
||||
self.dismiss = dismiss
|
||||
|
|
@ -98,6 +101,9 @@ private final class SheetContent: CombinedComponent {
|
|||
if lhs.boostState != rhs.boostState {
|
||||
return false
|
||||
}
|
||||
if lhs.bottomInset != rhs.bottomInset {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +140,6 @@ private final class SheetContent: CombinedComponent {
|
|||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let theme = environment.theme
|
||||
let strings = environment.strings
|
||||
|
||||
let state = context.state
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
|
|
@ -775,6 +780,7 @@ private final class SheetContent: CombinedComponent {
|
|||
contentSize.height += levels.size.height + 80.0
|
||||
contentSize.height += 60.0
|
||||
}
|
||||
contentSize.height += component.bottomInset
|
||||
|
||||
return contentSize
|
||||
}
|
||||
|
|
@ -826,8 +832,8 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
|
|||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
private let peerId: EnginePeer.Id
|
||||
private let mode: PremiumBoostLevelsScreen.Mode
|
||||
private let status: ChannelBoostStatus?
|
||||
fileprivate let mode: PremiumBoostLevelsScreen.Mode
|
||||
fileprivate let status: ChannelBoostStatus?
|
||||
private let myBoostStatus: MyBoostStatus?
|
||||
private let openPeer: ((EnginePeer) -> Void)?
|
||||
private let forceDark: Bool
|
||||
|
|
@ -838,7 +844,7 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
|
|||
private(set) var memberPeer: EnginePeer?
|
||||
private var peerDisposable: Disposable?
|
||||
|
||||
private var currentMyBoostCount: Int32 = 0
|
||||
fileprivate var currentMyBoostCount: Int32 = 0
|
||||
private var myBoostCount: Int32 = 0
|
||||
private var availableBoosts: [MyBoostStatus.Boost] = []
|
||||
private var occupiedBoosts: [MyBoostStatus.Boost] = []
|
||||
|
|
@ -1235,7 +1241,9 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
|
|||
let state = context.state
|
||||
let theme = environment.theme.withModalBlocksBackground()
|
||||
let strings = environment.strings
|
||||
|
||||
|
||||
state.controller = controller() as? PremiumBoostLevelsScreen
|
||||
|
||||
let dismiss: (Bool) -> Void = { animated in
|
||||
if animated {
|
||||
animateOut.invoke(Action { _ in
|
||||
|
|
@ -1338,6 +1346,24 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
|
|||
)
|
||||
}
|
||||
|
||||
var bottomButtonTitle: String?
|
||||
if case .user = state.mode, state.status?.nextLevelBoosts != nil {
|
||||
if state.currentMyBoostCount > 0 {
|
||||
bottomButtonTitle = strings.ChannelBoost_BoostAgain
|
||||
} else if state.isGroup == true {
|
||||
bottomButtonTitle = strings.GroupBoost_BoostGroup
|
||||
} else {
|
||||
bottomButtonTitle = strings.ChannelBoost_BoostChannel
|
||||
}
|
||||
}
|
||||
let bottomButtonInsets = ContainerViewLayout.concentricInsets(bottomInset: environment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let contentBottomInset: CGFloat
|
||||
if bottomButtonTitle != nil {
|
||||
contentBottomInset = bottomButtonInsets.bottom + 52.0 + 16.0
|
||||
} else {
|
||||
contentBottomInset = 0.0
|
||||
}
|
||||
|
||||
let sheet = sheet.update(
|
||||
component: ResizableSheetComponent<EnvironmentType>(
|
||||
content: AnyComponent<EnvironmentType>(SheetContent(
|
||||
|
|
@ -1348,6 +1374,7 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
|
|||
mode: context.component.mode,
|
||||
status: context.component.status,
|
||||
boostState: state.boostState,
|
||||
bottomInset: contentBottomInset,
|
||||
boost: { [weak state] in
|
||||
state?.updateBoostState()
|
||||
},
|
||||
|
|
@ -1385,31 +1412,33 @@ private final class PremiumBoostLevelsSheetComponent: CombinedComponent {
|
|||
)
|
||||
),
|
||||
rightItem: rightItem,
|
||||
bottomItem: "".isEmpty ? nil : AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
ButtonTextContentComponent(
|
||||
text: strings.Common_OK,
|
||||
badge: 0,
|
||||
textColor: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: theme.list.itemCheckColors.fillColor
|
||||
bottomItem: bottomButtonTitle.map { title in
|
||||
AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
style: .glass,
|
||||
color: theme.list.itemCheckColors.fillColor,
|
||||
foreground: theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(
|
||||
ButtonTextContentComponent(
|
||||
text: title,
|
||||
badge: 0,
|
||||
textColor: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: theme.list.itemCheckColors.fillColor
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
action: {
|
||||
dismiss(true)
|
||||
}
|
||||
),
|
||||
action: { [weak state] in
|
||||
state?.updateBoostState()
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
},
|
||||
backgroundColor: .color(theme.list.modalBlocksBackgroundColor),
|
||||
animateOut: animateOut
|
||||
),
|
||||
|
|
|
|||
|
|
@ -279,12 +279,10 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
self.scrollNode.addSubnode(self.previousItemNode)
|
||||
self.scrollNode.addSubnode(self.nextItemNode)
|
||||
|
||||
self.addSubnode(self.closeButton)
|
||||
self.addSubnode(self.rateButton)
|
||||
self.addSubnode(self.accessibilityAreaNode)
|
||||
|
||||
self.actionButton.addSubnode(self.playPauseIconNode)
|
||||
self.addSubnode(self.actionButton)
|
||||
|
||||
self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
|
@ -296,6 +294,9 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
|
||||
self.addSubnode(self.scrubbingNode)
|
||||
|
||||
self.addSubnode(self.actionButton)
|
||||
self.addSubnode(self.closeButton)
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
||||
self.actionButton.highligthedChanged = { [weak self] highlighted in
|
||||
|
|
|
|||
|
|
@ -175,6 +175,11 @@ private func findMediaResource(media: Media, previousMedia: Media?, resource: Me
|
|||
return representation.resource
|
||||
}
|
||||
}
|
||||
if let video = image.video {
|
||||
if let resource = findMediaResource(media: video, previousMedia: previousMedia, resource: resource) {
|
||||
return resource
|
||||
}
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if areResourcesEqual(file.resource, resource) {
|
||||
return file.resource
|
||||
|
|
|
|||
|
|
@ -5457,6 +5457,7 @@ func replayFinalState(
|
|||
isMy: item.isMy,
|
||||
myReaction: updatedReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -5492,6 +5493,7 @@ func replayFinalState(
|
|||
isMy: item.isMy,
|
||||
myReaction: MessageReaction.Reaction(apiReaction: reaction),
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
|
|||
|
|
@ -31,6 +31,9 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox, force:
|
|||
copyOrMoveResourceData(from: fromLargestRepresentation.resource, to: toLargestRepresentation.resource, mediaBox: postbox.mediaBox)
|
||||
}
|
||||
}
|
||||
if let fromVideo = fromImage.video, let toVideo = toImage.video {
|
||||
applyMediaResourceChanges(from: fromVideo, to: toVideo, postbox: postbox, force: true)
|
||||
}
|
||||
} else if let fromFile = from as? TelegramMediaFile, let toFile = to as? TelegramMediaFile {
|
||||
if !skipPreviews {
|
||||
if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) {
|
||||
|
|
|
|||
|
|
@ -354,6 +354,7 @@ private final class StoryStatsPublicForwardsContextImpl {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
|
|||
|
|
@ -563,6 +563,7 @@ public final class EngineStoryViewListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
),
|
||||
|
|
@ -604,6 +605,7 @@ public final class EngineStoryViewListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -645,6 +647,7 @@ public final class EngineStoryViewListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -767,6 +770,7 @@ public final class EngineStoryViewListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
),
|
||||
|
|
|
|||
|
|
@ -112,8 +112,9 @@ public extension Stories {
|
|||
case period
|
||||
case randomId
|
||||
case forwardInfo
|
||||
case uploadInfo
|
||||
case folders
|
||||
case music
|
||||
case uploadInfo
|
||||
}
|
||||
|
||||
public let target: PendingTarget
|
||||
|
|
@ -131,6 +132,7 @@ public extension Stories {
|
|||
public let randomId: Int64
|
||||
public let forwardInfo: PendingForwardInfo?
|
||||
public let folders: [Int64]
|
||||
public let music: TelegramMediaFile?
|
||||
public let uploadInfo: StoryUploadInfo?
|
||||
|
||||
public init(
|
||||
|
|
@ -149,6 +151,7 @@ public extension Stories {
|
|||
randomId: Int64,
|
||||
forwardInfo: PendingForwardInfo?,
|
||||
folders: [Int64],
|
||||
music: TelegramMediaFile?,
|
||||
uploadInfo: StoryUploadInfo?
|
||||
) {
|
||||
self.target = target
|
||||
|
|
@ -166,6 +169,7 @@ public extension Stories {
|
|||
self.randomId = randomId
|
||||
self.forwardInfo = forwardInfo
|
||||
self.folders = folders
|
||||
self.music = music
|
||||
self.uploadInfo = uploadInfo
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +201,12 @@ public extension Stories {
|
|||
|
||||
self.folders = try container.decodeIfPresent([Int64].self, forKey: .folders) ?? []
|
||||
|
||||
if let musicData = try container.decodeIfPresent(Data.self, forKey: .music) {
|
||||
self.music = PostboxDecoder(buffer: MemoryBuffer(data: musicData)).decodeRootObject() as? TelegramMediaFile
|
||||
} else {
|
||||
self.music = nil
|
||||
}
|
||||
|
||||
self.uploadInfo = try container.decodeIfPresent(StoryUploadInfo.self, forKey: .uploadInfo)
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +236,13 @@ public extension Stories {
|
|||
try container.encode(self.period, forKey: .period)
|
||||
try container.encode(self.randomId, forKey: .randomId)
|
||||
try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo)
|
||||
|
||||
if let music = self.music {
|
||||
let musicEncoder = PostboxEncoder()
|
||||
musicEncoder.encodeRootObject(music)
|
||||
try container.encode(musicEncoder.makeData(), forKey: .music)
|
||||
}
|
||||
|
||||
try container.encode(self.folders, forKey: .folders)
|
||||
try container.encodeIfPresent(self.uploadInfo, forKey: .uploadInfo)
|
||||
}
|
||||
|
|
@ -270,6 +287,9 @@ public extension Stories {
|
|||
if lhs.folders != rhs.folders {
|
||||
return false
|
||||
}
|
||||
if lhs.music != rhs.music {
|
||||
return false
|
||||
}
|
||||
if lhs.uploadInfo != rhs.uploadInfo {
|
||||
return false
|
||||
}
|
||||
|
|
@ -519,7 +539,7 @@ final class PendingStoryManager {
|
|||
let partTotalProgress = 1.0 / Float(uploadInfo.total)
|
||||
pendingItemContext.progress = Float(uploadInfo.index) * partTotalProgress
|
||||
}
|
||||
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, toPeerId: toPeerId, stableId: stableId, media: firstItem.media, mediaAreas: firstItem.mediaAreas, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), folders: firstItem.folders, randomId: firstItem.randomId, forwardInfo: firstItem.forwardInfo)
|
||||
pendingItemContext.disposable = (_internal_uploadStoryImpl(postbox: self.postbox, network: self.network, accountPeerId: self.accountPeerId, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.revalidationContext, auxiliaryMethods: self.auxiliaryMethods, toPeerId: toPeerId, stableId: stableId, media: firstItem.media, mediaAreas: firstItem.mediaAreas, text: firstItem.text, entities: firstItem.entities, embeddedStickers: firstItem.embeddedStickers, pin: firstItem.pin, privacy: firstItem.privacy, isForwardingDisabled: firstItem.isForwardingDisabled, period: Int(firstItem.period), folders: firstItem.folders, music: firstItem.music, randomId: firstItem.randomId, forwardInfo: firstItem.forwardInfo)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] event in
|
||||
guard let `self` = self else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -262,6 +262,7 @@ public enum Stories {
|
|||
case isMy
|
||||
case myReaction
|
||||
case forwardInfo
|
||||
case music
|
||||
case authorId
|
||||
case folderIds
|
||||
}
|
||||
|
|
@ -287,6 +288,7 @@ public enum Stories {
|
|||
public let isMy: Bool
|
||||
public let myReaction: MessageReaction.Reaction?
|
||||
public let forwardInfo: ForwardInfo?
|
||||
public let music: TelegramMediaFile?
|
||||
public let authorId: PeerId?
|
||||
public let folderIds: [Int64]?
|
||||
|
||||
|
|
@ -316,6 +318,7 @@ public enum Stories {
|
|||
isMy: Bool,
|
||||
myReaction: MessageReaction.Reaction?,
|
||||
forwardInfo: ForwardInfo?,
|
||||
music: TelegramMediaFile?,
|
||||
authorId: PeerId?,
|
||||
folderIds: [Int64]?
|
||||
) {
|
||||
|
|
@ -340,6 +343,7 @@ public enum Stories {
|
|||
self.isMy = isMy
|
||||
self.myReaction = myReaction
|
||||
self.forwardInfo = forwardInfo
|
||||
self.music = music
|
||||
self.authorId = authorId
|
||||
self.folderIds = folderIds
|
||||
}
|
||||
|
|
@ -388,6 +392,13 @@ public enum Stories {
|
|||
self.isMy = try container.decodeIfPresent(Bool.self, forKey: .isMy) ?? false
|
||||
self.myReaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .myReaction)
|
||||
self.forwardInfo = try container.decodeIfPresent(ForwardInfo.self, forKey: .forwardInfo)
|
||||
|
||||
if let musicData = try container.decodeIfPresent(Data.self, forKey: .music) {
|
||||
self.music = PostboxDecoder(buffer: MemoryBuffer(data: musicData)).decodeRootObject() as? TelegramMediaFile
|
||||
} else {
|
||||
self.music = nil
|
||||
}
|
||||
|
||||
self.authorId = try container.decodeIfPresent(Int64.self, forKey: .authorId).flatMap { PeerId($0) }
|
||||
self.folderIds = try container.decodeIfPresent([Int64].self, forKey: .folderIds)
|
||||
}
|
||||
|
|
@ -430,6 +441,14 @@ public enum Stories {
|
|||
try container.encode(self.isMy, forKey: .isMy)
|
||||
try container.encodeIfPresent(self.myReaction, forKey: .myReaction)
|
||||
try container.encodeIfPresent(self.forwardInfo, forKey: .forwardInfo)
|
||||
|
||||
if let music = self.music {
|
||||
let encoder = PostboxEncoder()
|
||||
encoder.encodeRootObject(music)
|
||||
let musicData = encoder.makeData()
|
||||
try container.encode(musicData, forKey: .music)
|
||||
}
|
||||
|
||||
try container.encodeIfPresent(self.authorId?.toInt64(), forKey: .authorId)
|
||||
try container.encodeIfPresent(self.folderIds, forKey: .folderIds)
|
||||
}
|
||||
|
|
@ -504,6 +523,15 @@ public enum Stories {
|
|||
if lhs.forwardInfo != rhs.forwardInfo {
|
||||
return false
|
||||
}
|
||||
if let lhsMusic = lhs.music, let rhsMusic = rhs.music {
|
||||
if !lhsMusic.isEqual(to: rhsMusic) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if (lhs.music == nil) != (rhs.music == nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if lhs.authorId != rhs.authorId {
|
||||
return false
|
||||
}
|
||||
|
|
@ -1035,7 +1063,23 @@ public struct StoryUploadInfo: Codable, Equatable {
|
|||
}
|
||||
}
|
||||
|
||||
func _internal_uploadStory(account: Account, target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, folders: [Int64], uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
|
||||
func _internal_uploadStory(
|
||||
account: Account,
|
||||
target: Stories.PendingTarget,
|
||||
media: EngineStoryInputMedia,
|
||||
mediaAreas: [MediaArea],
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
pin: Bool,
|
||||
privacy: EngineStoryPrivacy,
|
||||
isForwardingDisabled: Bool,
|
||||
period: Int,
|
||||
randomId: Int64,
|
||||
forwardInfo: Stories.PendingForwardInfo?,
|
||||
folders: [Int64],
|
||||
music: TelegramMediaFile?,
|
||||
uploadInfo: StoryUploadInfo? = nil
|
||||
) -> Signal<Int32, NoError> {
|
||||
let inputMedia = prepareUploadStoryContent(account: account, media: media)
|
||||
|
||||
return (account.postbox.transaction { transaction in
|
||||
|
|
@ -1065,6 +1109,7 @@ func _internal_uploadStory(account: Account, target: Stories.PendingTarget, medi
|
|||
randomId: randomId,
|
||||
forwardInfo: forwardInfo,
|
||||
folders: folders,
|
||||
music: music,
|
||||
uploadInfo: uploadInfo
|
||||
))
|
||||
transaction.setLocalStoryState(state: CodableEntry(currentState))
|
||||
|
|
@ -1113,6 +1158,7 @@ func _internal_cancelStoryUpload(account: Account, stableId: Int32) {
|
|||
randomId: currentState.items[i].randomId,
|
||||
forwardInfo: currentState.items[i].forwardInfo,
|
||||
folders: currentState.items[i].folders,
|
||||
music: currentState.items[i].music,
|
||||
uploadInfo: StoryUploadInfo(
|
||||
groupingId: groupingId,
|
||||
index: newIndex,
|
||||
|
|
@ -1203,6 +1249,7 @@ func _internal_beginStoryLivestream(account: Account, peerId: EnginePeer.Id, rtm
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -1279,6 +1326,7 @@ func _internal_uploadStoryImpl(
|
|||
isForwardingDisabled: Bool,
|
||||
period: Int,
|
||||
folders: [Int64],
|
||||
music: TelegramMediaFile?,
|
||||
randomId: Int64,
|
||||
forwardInfo: Stories.PendingForwardInfo?
|
||||
) -> Signal<PendingStoryUploadResult, NoError> {
|
||||
|
|
@ -1373,7 +1421,11 @@ func _internal_uploadStoryImpl(
|
|||
flags |= 1 << 8
|
||||
}
|
||||
|
||||
//var apiMusic: Api.InputDocument?
|
||||
var apiMusic: Api.InputDocument?
|
||||
if let resource = music?.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference {
|
||||
flags |= 1 << 9
|
||||
apiMusic = .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)))
|
||||
}
|
||||
|
||||
return network.request(Api.functions.stories.sendStory(
|
||||
flags: flags,
|
||||
|
|
@ -1388,7 +1440,7 @@ func _internal_uploadStoryImpl(
|
|||
fwdFromId: fwdFromId,
|
||||
fwdFromStory: fwdFromStory,
|
||||
albums: folders.isEmpty ? nil : folders.map(Int32.init(clamping:)),
|
||||
music: nil
|
||||
music: apiMusic
|
||||
))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
|
|
@ -1441,6 +1493,7 @@ func _internal_uploadStoryImpl(
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: fromId?.peerId,
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -1861,6 +1914,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -1894,6 +1948,7 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2092,6 +2147,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2124,6 +2180,7 @@ func _internal_updateStoriesArePinned(account: Account, peerId: PeerId, ids: [In
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2251,7 +2308,7 @@ extension Stories.StoredItem {
|
|||
init?(apiStoryItem: Api.StoryItem, existingItem: Stories.Item? = nil, peerId: PeerId, transaction: Transaction) {
|
||||
switch apiStoryItem {
|
||||
case let .storyItem(storyItemData):
|
||||
let (flags, id, date, fromId, forwardFrom, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction, albums) = (storyItemData.flags, storyItemData.id, storyItemData.date, storyItemData.fromId, storyItemData.fwdFrom, storyItemData.expireDate, storyItemData.caption, storyItemData.entities, storyItemData.media, storyItemData.mediaAreas, storyItemData.privacy, storyItemData.views, storyItemData.sentReaction, storyItemData.albums)
|
||||
let (flags, id, date, fromId, forwardFrom, expireDate, caption, entities, media, mediaAreas, privacy, views, sentReaction, albums, music) = (storyItemData.flags, storyItemData.id, storyItemData.date, storyItemData.fromId, storyItemData.fwdFrom, storyItemData.expireDate, storyItemData.caption, storyItemData.entities, storyItemData.media, storyItemData.mediaAreas, storyItemData.privacy, storyItemData.views, storyItemData.sentReaction, storyItemData.albums, storyItemData.music)
|
||||
var folderIds: [Int64]?
|
||||
if let albums {
|
||||
folderIds = albums.map(Int64.init)
|
||||
|
|
@ -2350,6 +2407,12 @@ extension Stories.StoredItem {
|
|||
break
|
||||
}
|
||||
|
||||
|
||||
var parsedMusic: TelegramMediaFile?
|
||||
if let music {
|
||||
parsedMusic = telegramMediaFileFromApiDocument(music, altDocuments: nil)
|
||||
}
|
||||
|
||||
let item = Stories.Item(
|
||||
id: id,
|
||||
timestamp: date,
|
||||
|
|
@ -2372,6 +2435,7 @@ extension Stories.StoredItem {
|
|||
isMy: mergedIsMy,
|
||||
myReaction: mergedMyReaction,
|
||||
forwardInfo: mergedForwardInfo,
|
||||
music: parsedMusic,
|
||||
authorId: fromId?.peerId,
|
||||
folderIds: folderIds
|
||||
)
|
||||
|
|
@ -2453,6 +2517,7 @@ func _internal_getStoryById(accountPeerId: PeerId, postbox: Postbox, network: Ne
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2953,6 +3018,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
|||
isMy: item.isMy,
|
||||
myReaction: reaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -2988,6 +3054,7 @@ func _internal_setStoryReaction(account: Account, peerId: EnginePeer.Id, id: Int
|
|||
isMy: item.isMy,
|
||||
myReaction: reaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -3065,6 +3132,7 @@ func _internal_sendStoryStars(account: Account, peerId: EnginePeer.Id, id: Int32
|
|||
isMy: item.isMy,
|
||||
myReaction: .stars,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -3100,6 +3168,7 @@ func _internal_sendStoryStars(account: Account, peerId: EnginePeer.Id, id: Int32
|
|||
isMy: item.isMy,
|
||||
myReaction: .stars,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
|
|||
|
|
@ -85,10 +85,37 @@ public final class EngineStoryItem: Equatable {
|
|||
public let isMy: Bool
|
||||
public let myReaction: MessageReaction.Reaction?
|
||||
public let forwardInfo: ForwardInfo?
|
||||
public let music: EngineMedia?
|
||||
public let author: EnginePeer?
|
||||
public let folderIds: [Int64]?
|
||||
|
||||
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, alternativeMediaList: [EngineMedia], mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isContacts: Bool, isSelectedContacts: Bool, isForwardingDisabled: Bool, isEdited: Bool, isMy: Bool, myReaction: MessageReaction.Reaction?, forwardInfo: ForwardInfo?, author: EnginePeer?, folderIds: [Int64]?) {
|
||||
public init(
|
||||
id: Int32,
|
||||
timestamp: Int32,
|
||||
expirationTimestamp: Int32,
|
||||
media: EngineMedia,
|
||||
alternativeMediaList: [EngineMedia],
|
||||
mediaAreas: [MediaArea],
|
||||
text: String,
|
||||
entities: [MessageTextEntity],
|
||||
views: Views?,
|
||||
privacy: EngineStoryPrivacy?,
|
||||
isPinned: Bool,
|
||||
isExpired: Bool,
|
||||
isPublic: Bool,
|
||||
isPending: Bool,
|
||||
isCloseFriends: Bool,
|
||||
isContacts: Bool,
|
||||
isSelectedContacts: Bool,
|
||||
isForwardingDisabled: Bool,
|
||||
isEdited: Bool,
|
||||
isMy: Bool,
|
||||
myReaction: MessageReaction.Reaction?,
|
||||
forwardInfo: ForwardInfo?,
|
||||
music: EngineMedia?,
|
||||
author: EnginePeer?,
|
||||
folderIds: [Int64]?
|
||||
) {
|
||||
self.id = id
|
||||
self.timestamp = timestamp
|
||||
self.expirationTimestamp = expirationTimestamp
|
||||
|
|
@ -111,6 +138,7 @@ public final class EngineStoryItem: Equatable {
|
|||
self.isMy = isMy
|
||||
self.myReaction = myReaction
|
||||
self.forwardInfo = forwardInfo
|
||||
self.music = music
|
||||
self.author = author
|
||||
self.folderIds = folderIds
|
||||
}
|
||||
|
|
@ -182,6 +210,9 @@ public final class EngineStoryItem: Equatable {
|
|||
if lhs.forwardInfo != rhs.forwardInfo {
|
||||
return false
|
||||
}
|
||||
if lhs.music != rhs.music {
|
||||
return false
|
||||
}
|
||||
if lhs.author != rhs.author {
|
||||
return false
|
||||
}
|
||||
|
|
@ -239,9 +270,9 @@ public extension EngineStoryItem {
|
|||
isForwardingDisabled: self.isForwardingDisabled,
|
||||
isEdited: self.isEdited,
|
||||
isMy: self.isMy,
|
||||
|
||||
myReaction: self.myReaction,
|
||||
forwardInfo: self.forwardInfo?.storedForwardInfo,
|
||||
music: self.music?._asMedia() as? TelegramMediaFile,
|
||||
authorId: self.author?.id,
|
||||
folderIds: self.folderIds
|
||||
)
|
||||
|
|
@ -744,6 +775,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -966,6 +998,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -1161,6 +1194,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
), peer: nil)
|
||||
|
|
@ -1211,6 +1245,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
), peer: nil)
|
||||
|
|
@ -1263,6 +1298,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
), peer: nil))
|
||||
|
|
@ -1321,6 +1357,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
), peer: nil))
|
||||
|
|
@ -1656,6 +1693,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
return .unknown(name: name, isModified: isModified)
|
||||
}
|
||||
},
|
||||
music: item.music?._asMedia() as? TelegramMediaFile,
|
||||
authorId: item.author?.id,
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2001,6 +2039,7 @@ public final class PeerStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2201,6 +2240,7 @@ public final class SearchStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -2351,6 +2391,7 @@ public final class SearchStoryListContext: StoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, peers: peers) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
),
|
||||
|
|
@ -2413,6 +2454,7 @@ public final class SearchStoryListContext: StoryListContext {
|
|||
isMy: item.storyItem.isMy,
|
||||
myReaction: reaction,
|
||||
forwardInfo: item.storyItem.forwardInfo,
|
||||
music: item.storyItem.music,
|
||||
author: item.storyItem.author,
|
||||
folderIds: item.storyItem.folderIds
|
||||
),
|
||||
|
|
@ -2543,6 +2585,7 @@ public final class PeerExpiringStoryListContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -3008,6 +3051,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
isMy: false,
|
||||
myReaction: nil,
|
||||
forwardInfo: nil,
|
||||
music: nil,
|
||||
author: nil,
|
||||
folderIds: nil
|
||||
),
|
||||
|
|
@ -3058,6 +3102,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
isMy: false,
|
||||
myReaction: nil,
|
||||
forwardInfo: nil,
|
||||
music: nil,
|
||||
author: nil,
|
||||
folderIds: nil
|
||||
),
|
||||
|
|
@ -3171,6 +3216,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
isMy: false,
|
||||
myReaction: nil,
|
||||
forwardInfo: nil,
|
||||
music: nil,
|
||||
author: nil,
|
||||
folderIds: nil
|
||||
),
|
||||
|
|
@ -3249,6 +3295,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
isMy: false,
|
||||
myReaction: nil,
|
||||
forwardInfo: nil,
|
||||
music: nil,
|
||||
author: nil,
|
||||
folderIds: nil
|
||||
),
|
||||
|
|
@ -3313,6 +3360,7 @@ public final class BotPreviewStoryListContext: StoryListContext {
|
|||
isMy: false,
|
||||
myReaction: nil,
|
||||
forwardInfo: nil,
|
||||
music: nil,
|
||||
author: nil,
|
||||
folderIds: nil
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1469,6 +1469,7 @@ public extension TelegramEngine {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo,
|
||||
music: item.music,
|
||||
authorId: item.authorId,
|
||||
folderIds: item.folderIds
|
||||
))
|
||||
|
|
@ -1484,8 +1485,8 @@ public extension TelegramEngine {
|
|||
}
|
||||
}
|
||||
|
||||
public func uploadStory(target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, folders: [Int64], uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
|
||||
return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, uploadInfo: uploadInfo)
|
||||
public func uploadStory(target: Stories.PendingTarget, media: EngineStoryInputMedia, mediaAreas: [MediaArea], text: String, entities: [MessageTextEntity], pin: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, period: Int, randomId: Int64, forwardInfo: Stories.PendingForwardInfo?, folders: [Int64], music: TelegramMediaFile?, uploadInfo: StoryUploadInfo? = nil) -> Signal<Int32, NoError> {
|
||||
return _internal_uploadStory(account: self.account, target: target, media: media, mediaAreas: mediaAreas, text: text, entities: entities, pin: pin, privacy: privacy, isForwardingDisabled: isForwardingDisabled, period: period, randomId: randomId, forwardInfo: forwardInfo, folders: folders, music: music, uploadInfo: uploadInfo)
|
||||
}
|
||||
|
||||
public func beginStoryLivestream(peerId: EnginePeer.Id, rtmp: Bool, privacy: EngineStoryPrivacy, isForwardingDisabled: Bool, messagesEnabled: Bool, sendPaidMessageStars: Int64?) -> Signal<EngineStoryItem?, NoError> {
|
||||
|
|
|
|||
|
|
@ -500,12 +500,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
|
||||
let searchQuery = self.searchQuery.get()
|
||||
|> mapToSignal { query -> Signal<String?, NoError> in
|
||||
if let query = query, !query.isEmpty {
|
||||
return (.complete() |> delay(0.6, queue: Queue.mainQueue()))
|
||||
|> then(.single(query))
|
||||
} else {
|
||||
return .single(query)
|
||||
}
|
||||
return .single(query)
|
||||
}
|
||||
|
||||
let expandedSectionsPromise = self.expandedSectionsPromise
|
||||
|
|
@ -525,6 +520,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
shared = .single(nil)
|
||||
|> then(
|
||||
context.engine.messages.searchMessages(location: .sentMedia(tags: [.file]), query: query, state: nil)
|
||||
|> delay(0.6, queue: Queue.mainQueue())
|
||||
|> map { result -> [Message]? in
|
||||
return result.0.messages
|
||||
}
|
||||
|
|
@ -535,13 +531,15 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
shared = .single(nil)
|
||||
|> then(
|
||||
context.engine.messages.searchMessages(location: .general(scope: .everywhere, tags: [.music], minDate: nil, maxDate: nil), query: query, state: nil)
|
||||
|> delay(0.6, queue: Queue.mainQueue())
|
||||
|> map { result -> [Message]? in
|
||||
return result.0.messages
|
||||
return result.0.messages.filter { !$0.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) }
|
||||
}
|
||||
)
|
||||
savedMusic = .single(nil)
|
||||
|> then(
|
||||
savedMusicContext!.state
|
||||
|> delay(0.6, queue: Queue.mainQueue())
|
||||
|> map { state in
|
||||
let peerId = context.account.peerId
|
||||
var messages: [Message] = []
|
||||
|
|
@ -571,13 +569,12 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
return messages
|
||||
}
|
||||
)
|
||||
|
||||
let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty, trimmedQuery.count >= 3 {
|
||||
|
||||
if let data = context.currentAppConfiguration.with({ $0 }).data, let searchBot = data["music_search_username"] as? String, !searchBot.isEmpty, query.count >= 3 {
|
||||
globalMusic = .single(nil)
|
||||
|> then(
|
||||
context.engine.peers.resolvePeerByName(name: searchBot, referrer: nil)
|
||||
|> delay(0.6, queue: Queue.mainQueue())
|
||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||
guard case let .result(result) = result else {
|
||||
return .complete()
|
||||
|
|
@ -588,7 +585,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
guard let peer = peer else {
|
||||
return .single(nil)
|
||||
}
|
||||
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: trimmedQuery, offset: "")
|
||||
return context.engine.messages.requestChatContextResults(botId: peer.id, peerId: context.account.peerId, query: query, offset: "")
|
||||
|> map { results -> ChatContextResultCollection? in
|
||||
return results?.results
|
||||
}
|
||||
|
|
@ -618,7 +615,7 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
}
|
||||
)
|
||||
} else {
|
||||
globalMusic = .single(nil)
|
||||
globalMusic = .single([])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -670,13 +667,22 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
}
|
||||
}
|
||||
|
||||
if let globalMusic, !globalMusic.isEmpty {
|
||||
index = 0
|
||||
section += 1
|
||||
|
||||
entries.append(.header(title: presentationData.strings.Attachment_PublicMusic, section: section))
|
||||
for message in globalMusic {
|
||||
entries.append(.file(index: index, message: message, section: section))
|
||||
if let globalMusic {
|
||||
if !globalMusic.isEmpty {
|
||||
index = 0
|
||||
section += 1
|
||||
|
||||
entries.append(.header(title: presentationData.strings.Attachment_PublicMusic, section: section))
|
||||
for message in globalMusic {
|
||||
entries.append(.file(index: index, message: message, section: section))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
} else if messages.isEmpty {
|
||||
entries.append(.header(title: " ", section: 0))
|
||||
var index: Int32 = 0
|
||||
for _ in 0 ..< 16 {
|
||||
entries.append(.file(index: index, message: nil, section: 0))
|
||||
index += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -694,6 +700,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
}
|
||||
|
||||
let previousSearchItems = Atomic<[AttachmentFileSearchEntry]?>(value: nil)
|
||||
let previousHadSharedItems = Atomic<Bool>(value: false)
|
||||
let previousHadSavedItems = Atomic<Bool>(value: false)
|
||||
let previousHadGlobalItems = Atomic<Bool>(value: false)
|
||||
self.searchDisposable.set((combineLatest(searchQuery, foundItems, self.presentationDataPromise.get())
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] query, entries, presentationData in
|
||||
|
|
@ -702,17 +710,30 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
updateActivity(false)
|
||||
let firstTime = previousEntries == nil
|
||||
|
||||
var hasSharedItems = false
|
||||
var hasSavedItems = false
|
||||
var hasGlobalItems = false
|
||||
|
||||
if let entries {
|
||||
for entry in entries {
|
||||
if case let .header(_, section) = entry, section == 2 {
|
||||
hasGlobalItems = true
|
||||
if case let .header(title, section) = entry, title != " " {
|
||||
if section == 0 {
|
||||
hasSharedItems = true
|
||||
}
|
||||
if section == 1 {
|
||||
hasSavedItems = true
|
||||
}
|
||||
if section == 2 {
|
||||
hasGlobalItems = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let hadSharedItems = previousHadSharedItems.swap(hasSharedItems)
|
||||
let hadSavedItems = previousHadSavedItems.swap(hasSavedItems)
|
||||
let hadGlobalItems = previousHadGlobalItems.swap(hasGlobalItems)
|
||||
|
||||
let crossfade = hadGlobalItems != hasGlobalItems
|
||||
let crossfade = hadSharedItems != hasSharedItems || hadSavedItems != hasSavedItems || hadGlobalItems != hasGlobalItems
|
||||
|
||||
let transition = attachmentFileSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries ?? [], isSearching: entries != nil, isEmpty: entries?.isEmpty ?? false, query: query ?? "", context: context, presentationData: presentationData, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, interaction: interaction, mode: mode, crossfade: crossfade)
|
||||
strongSelf.enqueueTransition(transition, firstTime: firstTime)
|
||||
|
|
@ -751,7 +772,8 @@ public final class AttachmentFileSearchContainerNode: SearchDisplayControllerCon
|
|||
}
|
||||
|
||||
override public func searchTextUpdated(text: String) {
|
||||
self.searchQuery.set(.single(!text.isEmpty ? text : nil))
|
||||
let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
self.searchQuery.set(.single(!trimmedText.isEmpty ? trimmedText : nil))
|
||||
}
|
||||
|
||||
private func enqueueTransition(_ transition: AttachmentFileSearchContainerTransition, firstTime: Bool) {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
var automaticDownload: InteractiveMediaNodeAutodownloadMode = .none
|
||||
var automaticPlayback: Bool = false
|
||||
var contentMode: InteractiveMediaNodeContentMode = .aspectFit
|
||||
var isLivePhoto = false
|
||||
|
||||
if let updatingMedia = item.attributes.updatingMedia, case let .update(mediaReference) = updatingMedia.media {
|
||||
selectedMedia = mediaReference.media
|
||||
|
|
@ -136,6 +137,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
|
||||
if let _ = telegramImage.video {
|
||||
automaticPlayback = true
|
||||
isLivePhoto = true
|
||||
}
|
||||
} else if let telegramStory = media as? TelegramMediaStory {
|
||||
selectedMedia = telegramStory
|
||||
|
|
@ -432,7 +434,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
var wideLayout = true
|
||||
if case let .mosaic(_, wide) = position {
|
||||
wideLayout = wide
|
||||
automaticPlayback = automaticPlayback && wide
|
||||
automaticPlayback = automaticPlayback && (wide || isLivePhoto)
|
||||
}
|
||||
|
||||
var updatedPosition: ChatMessageBubbleContentPosition = position
|
||||
|
|
|
|||
|
|
@ -66,23 +66,27 @@ public struct MediaAudioTrack: Codable, Equatable {
|
|||
case artist
|
||||
case title
|
||||
case duration
|
||||
case file
|
||||
}
|
||||
|
||||
public let path: String
|
||||
public let artist: String?
|
||||
public let title: String?
|
||||
public let duration: Double
|
||||
public let file: TelegramMediaFile?
|
||||
|
||||
public init(
|
||||
path: String,
|
||||
artist: String?,
|
||||
title: String?,
|
||||
duration: Double
|
||||
duration: Double,
|
||||
file: TelegramMediaFile? = nil
|
||||
) {
|
||||
self.path = path
|
||||
self.artist = artist
|
||||
self.title = title
|
||||
self.duration = duration
|
||||
self.file = file
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5163,7 +5163,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
return
|
||||
}
|
||||
|
||||
self.insertAudio(path: copyPath, fileName: fileName)
|
||||
self.insertAudio(path: copyPath, fileName: fileName, file: file.media as? TelegramMediaFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -5224,7 +5224,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}), in: .window(.root))
|
||||
}
|
||||
|
||||
private func insertAudio(path: String, fileName: String, dispose: (() -> Void)? = nil) {
|
||||
private func insertAudio(path: String, fileName: String, file: TelegramMediaFile? = nil, dispose: (() -> Void)? = nil) {
|
||||
guard let mediaEditor = self.mediaEditor else {
|
||||
return
|
||||
}
|
||||
|
|
@ -5300,7 +5300,15 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
audioTrimRange = 0 ..< min(15, audioDuration)
|
||||
}
|
||||
|
||||
mediaEditor.setAudioTrack(MediaAudioTrack(path: fileName, artist: artist, title: title, duration: audioDuration), trimRange: audioTrimRange, offset: audioOffset)
|
||||
var passFile = false
|
||||
if let file {
|
||||
for attribute in file.attributes {
|
||||
if case let .Audio(_, _, title, _, _) = attribute, let title, !title.isEmpty {
|
||||
passFile = true
|
||||
}
|
||||
}
|
||||
}
|
||||
mediaEditor.setAudioTrack(MediaAudioTrack(path: fileName, artist: artist, title: title, duration: audioDuration, file: passFile ? file : nil), trimRange: audioTrimRange, offset: audioOffset)
|
||||
|
||||
mediaEditor.seek(mediaEditor.values.videoTrimRange?.lowerBound ?? 0.0, andPlay: true)
|
||||
|
||||
|
|
@ -6706,6 +6714,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
public let coverTimestamp: Double?
|
||||
public let options: MediaEditorResultPrivacy
|
||||
public let stickers: [TelegramMediaFile]
|
||||
public let music: TelegramMediaFile?
|
||||
public let randomId: Int64
|
||||
|
||||
init() {
|
||||
|
|
@ -6715,6 +6724,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.coverTimestamp = nil
|
||||
self.options = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false, folderIds: [])
|
||||
self.stickers = []
|
||||
self.music = nil
|
||||
self.randomId = 0
|
||||
}
|
||||
|
||||
|
|
@ -6725,6 +6735,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
coverTimestamp: Double? = nil,
|
||||
options: MediaEditorResultPrivacy = MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false, folderIds: []),
|
||||
stickers: [TelegramMediaFile] = [],
|
||||
music: TelegramMediaFile? = nil,
|
||||
randomId: Int64 = 0
|
||||
) {
|
||||
self.media = media
|
||||
|
|
@ -6733,6 +6744,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.coverTimestamp = coverTimestamp
|
||||
self.options = options
|
||||
self.stickers = stickers
|
||||
self.music = music
|
||||
self.randomId = randomId
|
||||
}
|
||||
}
|
||||
|
|
@ -7766,6 +7778,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
coverTimestamp: nil,
|
||||
options: MediaEditorResultPrivacy(sendAsPeerId: nil, privacy: EngineStoryPrivacy(base: .everyone, additionallyIncludePeers: []), timeout: 0, isForwardingDisabled: false, pin: false, folderIds: []),
|
||||
stickers: [],
|
||||
music: nil,
|
||||
randomId: 0
|
||||
)], { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ extension MediaEditorScreenImpl {
|
|||
if self.isEmbeddedEditor && !(hasAnyChanges || hasEntityChanges) {
|
||||
self.saveDraft(id: randomId, isEdit: true)
|
||||
|
||||
self.completion([MediaEditorScreenImpl.Result(media: nil, mediaAreas: [], caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId)], { [weak self] finished in
|
||||
self.completion([MediaEditorScreenImpl.Result(media: nil, mediaAreas: [], caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, music: mediaEditor.values.audioTrack?.file, randomId: randomId)], { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
|
@ -464,7 +464,7 @@ extension MediaEditorScreenImpl {
|
|||
return
|
||||
}
|
||||
Logger.shared.log("MediaEditor", "Completed with video \(videoResult)")
|
||||
self.completion([MediaEditorScreenImpl.Result(media: .video(video: videoResult, coverImage: coverImage, values: values, duration: duration, dimensions: values.resultDimensions), mediaAreas: mediaAreas, caption: caption, coverTimestamp: values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId)], { [weak self] finished in
|
||||
self.completion([MediaEditorScreenImpl.Result(media: .video(video: videoResult, coverImage: coverImage, values: values, duration: duration, dimensions: values.resultDimensions), mediaAreas: mediaAreas, caption: caption, coverTimestamp: values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, music: values.audioTrack?.file, randomId: randomId)], { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
|
@ -506,7 +506,7 @@ extension MediaEditorScreenImpl {
|
|||
return
|
||||
}
|
||||
Logger.shared.log("MediaEditor", "Completed with image \(resultImage)")
|
||||
self.completion([MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), mediaAreas: mediaAreas, caption: caption, coverTimestamp: nil, options: self.state.privacy, stickers: stickers, randomId: randomId)], { [weak self] finished in
|
||||
self.completion([MediaEditorScreenImpl.Result(media: .image(image: resultImage, dimensions: PixelDimensions(resultImage.size)), mediaAreas: mediaAreas, caption: caption, coverTimestamp: nil, options: self.state.privacy, stickers: stickers, music: values.audioTrack?.file, randomId: randomId)], { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
self?.dismiss()
|
||||
Queue.mainQueue().justDispatch {
|
||||
|
|
@ -636,7 +636,7 @@ extension MediaEditorScreenImpl {
|
|||
}
|
||||
guard let avAsset else {
|
||||
Queue.mainQueue().async {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -683,18 +683,19 @@ extension MediaEditorScreenImpl {
|
|||
coverTimestamp: itemMediaEditor.values.coverImageTimestamp,
|
||||
options: self.state.privacy,
|
||||
stickers: stickers,
|
||||
music: itemMediaEditor.values.audioTrack?.file,
|
||||
randomId: randomId
|
||||
)
|
||||
completion(result)
|
||||
} else {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
}
|
||||
} else {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -736,7 +737,7 @@ extension MediaEditorScreenImpl {
|
|||
return
|
||||
}
|
||||
guard let image else {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
return
|
||||
}
|
||||
itemMediaEditor.replaceSource(image, additionalImage: nil, time: .zero, mirror: false)
|
||||
|
|
@ -765,15 +766,16 @@ extension MediaEditorScreenImpl {
|
|||
coverTimestamp: nil,
|
||||
options: self.state.privacy,
|
||||
stickers: stickers,
|
||||
music: itemMediaEditor.values.audioTrack?.file,
|
||||
randomId: randomId
|
||||
)
|
||||
completion(result)
|
||||
} else {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion(self.createEmptyResult(randomId: randomId))
|
||||
completion(self.createEmptyResult(randomId: randomId, music: itemMediaEditor.values.audioTrack?.file))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -816,7 +818,6 @@ extension MediaEditorScreenImpl {
|
|||
mode: .default,
|
||||
subject: editorSubject,
|
||||
values: values,
|
||||
hasHistogram: false,
|
||||
isStandalone: true
|
||||
)
|
||||
}
|
||||
|
|
@ -840,7 +841,7 @@ extension MediaEditorScreenImpl {
|
|||
}
|
||||
}
|
||||
|
||||
private func createEmptyResult(randomId: Int64) -> MediaEditorScreenImpl.Result {
|
||||
private func createEmptyResult(randomId: Int64, music: TelegramMediaFile? = nil) -> MediaEditorScreenImpl.Result {
|
||||
let emptyImage = UIImage()
|
||||
return MediaEditorScreenImpl.Result(
|
||||
media: .image(
|
||||
|
|
@ -852,12 +853,11 @@ extension MediaEditorScreenImpl {
|
|||
coverTimestamp: nil,
|
||||
options: self.state.privacy,
|
||||
stickers: [],
|
||||
music: music,
|
||||
randomId: randomId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func updateMediaEditorEntities() {
|
||||
guard let mediaEditor = self.node.mediaEditor else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -280,12 +280,10 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
self.scrollNode.addSubnode(self.previousItemNode)
|
||||
self.scrollNode.addSubnode(self.nextItemNode)
|
||||
|
||||
self.addSubnode(self.closeButton)
|
||||
self.addSubnode(self.rateButton)
|
||||
self.addSubnode(self.accessibilityAreaNode)
|
||||
|
||||
self.actionButton.addSubnode(self.playPauseIconNode)
|
||||
self.addSubnode(self.actionButton)
|
||||
|
||||
self.closeButton.addTarget(self, action: #selector(self.closeButtonPressed), forControlEvents: .touchUpInside)
|
||||
self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
|
@ -297,6 +295,9 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
|
|||
|
||||
self.addSubnode(self.scrubbingNode)
|
||||
|
||||
self.addSubnode(self.actionButton)
|
||||
self.addSubnode(self.closeButton)
|
||||
|
||||
self.actionButton.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ public final class PlainButtonComponent: Component {
|
|||
public let animateAlpha: Bool
|
||||
public let animateScale: Bool
|
||||
public let animateContents: Bool
|
||||
public let rasterizeOnScaleAnimation: Bool
|
||||
public let tag: AnyObject?
|
||||
|
||||
public init(
|
||||
|
|
@ -33,6 +34,7 @@ public final class PlainButtonComponent: Component {
|
|||
animateAlpha: Bool = true,
|
||||
animateScale: Bool = true,
|
||||
animateContents: Bool = true,
|
||||
rasterizeOnScaleAnimation: Bool = true,
|
||||
tag: AnyObject? = nil
|
||||
) {
|
||||
self.content = content
|
||||
|
|
@ -45,6 +47,7 @@ public final class PlainButtonComponent: Component {
|
|||
self.animateAlpha = animateAlpha
|
||||
self.animateScale = animateScale
|
||||
self.animateContents = animateContents
|
||||
self.rasterizeOnScaleAnimation = rasterizeOnScaleAnimation
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
|
|
@ -76,6 +79,9 @@ public final class PlainButtonComponent: Component {
|
|||
if lhs.animateContents != rhs.animateContents {
|
||||
return false
|
||||
}
|
||||
if lhs.rasterizeOnScaleAnimation != rhs.rasterizeOnScaleAnimation {
|
||||
return false
|
||||
}
|
||||
if lhs.tag !== rhs.tag {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,3 +206,109 @@ public final class ForwardInfoPanelComponent: Component {
|
|||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class MusicPanelComponent: Component {
|
||||
public let context: AccountContext
|
||||
public let file: TelegramMediaFile
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
file: TelegramMediaFile
|
||||
) {
|
||||
self.context = context
|
||||
self.file = file
|
||||
}
|
||||
|
||||
public static func ==(lhs: MusicPanelComponent, rhs: MusicPanelComponent) -> Bool {
|
||||
if lhs.file != rhs.file {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
public let backgroundView: UIImageView
|
||||
private let blurBackgroundView: UIVisualEffectView
|
||||
private var iconView = UIImageView()
|
||||
private var title = ComponentView<Empty>()
|
||||
private var text = ComponentView<Empty>()
|
||||
|
||||
private var component: MusicPanelComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
if #available(iOS 13.0, *) {
|
||||
self.blurBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark))
|
||||
} else {
|
||||
self.blurBackgroundView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||
}
|
||||
self.blurBackgroundView.clipsToBounds = true
|
||||
|
||||
self.backgroundView = UIImageView()
|
||||
self.backgroundView.image = generateStretchableFilledCircleImage(radius: 4.0, color: UIColor(white: 0.0, alpha: 0.4))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.iconView.image = UIImage(bundleImageName: "Media Editor/SmallAudio")
|
||||
self.addSubview(self.iconView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: MusicPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var titleOffset: CGFloat = 0.0
|
||||
if let image = self.iconView.image {
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 10.0, y: 5.0), size: image.size)
|
||||
}
|
||||
titleOffset += 29.0
|
||||
|
||||
let text = NSMutableAttributedString()
|
||||
for attribute in component.file.attributes {
|
||||
if case let .Audio(_, _, title, performer, _) = attribute, let title, !title.isEmpty {
|
||||
text.append(NSAttributedString(string: title, font: Font.semibold(13.0), textColor: .white))
|
||||
if let performer, !performer.isEmpty {
|
||||
text.append(NSAttributedString(string: " • ", font: Font.semibold(13.0), textColor: .white.withAlphaComponent(0.28)))
|
||||
text.append(NSAttributedString(string: performer, font: Font.regular(13.0), textColor: .white))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(text),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - titleOffset - 20.0, height: availableSize.height)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: titleOffset, y: 5.0 - UIScreenPixel), size: titleSize)
|
||||
if let view = self.title.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = titleFrame
|
||||
}
|
||||
|
||||
let size = CGSize(width: titleSize.width + 39.0, height: 26.0)
|
||||
self.blurBackgroundView.frame = CGRect(origin: .zero, size: size)
|
||||
self.insertSubview(self.blurBackgroundView, at: 0)
|
||||
self.blurBackgroundView.layer.cornerRadius = size.height / 2.0
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: forwardInfo,
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
@ -356,6 +357,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||
isMy: true,
|
||||
myReaction: nil,
|
||||
forwardInfo: pendingForwardsInfo[item.randomId],
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: nil,
|
||||
folderIds: item.folders
|
||||
))
|
||||
|
|
@ -1367,6 +1369,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
|||
isMy: itemValue.isMy,
|
||||
myReaction: itemValue.myReaction,
|
||||
forwardInfo: forwardInfo,
|
||||
music: itemValue.music.flatMap(EngineMedia.init),
|
||||
author: itemValue.authorId.flatMap { peers[$0].flatMap(EnginePeer.init) },
|
||||
folderIds: itemValue.folderIds
|
||||
)
|
||||
|
|
@ -2308,6 +2311,7 @@ private func getCachedStory(storyId: StoryId, transaction: Transaction) -> Engin
|
|||
isMy: item.isMy,
|
||||
myReaction: item.myReaction,
|
||||
forwardInfo: item.forwardInfo.flatMap { EngineStoryItem.ForwardInfo($0, transaction: transaction) },
|
||||
music: item.music.flatMap(EngineMedia.init),
|
||||
author: item.authorId.flatMap { transaction.getPeer($0).flatMap(EnginePeer.init) },
|
||||
folderIds: item.folderIds
|
||||
)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
let author: EnginePeer
|
||||
let forwardInfo: EngineStoryItem.ForwardInfo?
|
||||
let forwardInfoStory: Signal<EngineStoryItem?, NoError>?
|
||||
let music: EngineMedia?
|
||||
let entities: [MessageTextEntity]
|
||||
let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
let action: (Action) -> Void
|
||||
|
|
@ -69,6 +70,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
let textSelectionAction: (NSAttributedString, TextSelectionAction) -> Void
|
||||
let controller: () -> ViewController?
|
||||
let openStory: (EnginePeer, EngineStoryItem?) -> Void
|
||||
let openMusic: (FileMediaReference, UIView) -> Void
|
||||
|
||||
init(
|
||||
externalState: ExternalState,
|
||||
|
|
@ -79,13 +81,15 @@ final class StoryContentCaptionComponent: Component {
|
|||
author: EnginePeer,
|
||||
forwardInfo: EngineStoryItem.ForwardInfo?,
|
||||
forwardInfoStory: Signal<EngineStoryItem?, NoError>?,
|
||||
music: EngineMedia?,
|
||||
entities: [MessageTextEntity],
|
||||
entityFiles: [EngineMedia.Id: TelegramMediaFile],
|
||||
action: @escaping (Action) -> Void,
|
||||
longTapAction: @escaping (Action) -> Void,
|
||||
textSelectionAction: @escaping (NSAttributedString, TextSelectionAction) -> Void,
|
||||
controller: @escaping () -> ViewController?,
|
||||
openStory: @escaping (EnginePeer, EngineStoryItem?) -> Void
|
||||
openStory: @escaping (EnginePeer, EngineStoryItem?) -> Void,
|
||||
openMusic: @escaping (FileMediaReference, UIView) -> Void
|
||||
) {
|
||||
self.externalState = externalState
|
||||
self.context = context
|
||||
|
|
@ -94,6 +98,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
self.author = author
|
||||
self.forwardInfo = forwardInfo
|
||||
self.forwardInfoStory = forwardInfoStory
|
||||
self.music = music
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.entityFiles = entityFiles
|
||||
|
|
@ -102,6 +107,7 @@ final class StoryContentCaptionComponent: Component {
|
|||
self.textSelectionAction = textSelectionAction
|
||||
self.controller = controller
|
||||
self.openStory = openStory
|
||||
self.openMusic = openMusic
|
||||
}
|
||||
|
||||
static func ==(lhs: StoryContentCaptionComponent, rhs: StoryContentCaptionComponent) -> Bool {
|
||||
|
|
@ -123,6 +129,9 @@ final class StoryContentCaptionComponent: Component {
|
|||
if lhs.forwardInfo != rhs.forwardInfo {
|
||||
return false
|
||||
}
|
||||
if lhs.music != rhs.music {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
|
|
@ -181,6 +190,8 @@ final class StoryContentCaptionComponent: Component {
|
|||
private let scrollBottomFullMaskView: UIView
|
||||
private let scrollTopMaskView: UIImageView
|
||||
|
||||
private var musicPanel: ComponentView<Empty>?
|
||||
|
||||
private var forwardInfoPanel: ComponentView<Empty>?
|
||||
private var forwardInfoDisposable: Disposable?
|
||||
private var forwardInfoStory: EngineStoryItem?
|
||||
|
|
@ -310,10 +321,10 @@ final class StoryContentCaptionComponent: Component {
|
|||
|
||||
let contentItem = self.isExpanded ? self.expandedText : self.collapsedText
|
||||
|
||||
if let textView = contentItem.textNode?.textNode.view {
|
||||
let textLocalPoint = self.convert(point, to: textView)
|
||||
if textLocalPoint.y >= -7.0 {
|
||||
return self.textSelectionNode?.view ?? textView
|
||||
if let musicView = self.musicPanel?.view {
|
||||
let musicLocalPoint = self.convert(point, to: musicView)
|
||||
if let result = musicView.hitTest(musicLocalPoint, with: nil) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,6 +335,13 @@ final class StoryContentCaptionComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if let textView = contentItem.textNode?.textNode.view {
|
||||
let textLocalPoint = self.convert(point, to: textView)
|
||||
if textLocalPoint.y >= -7.0 {
|
||||
return self.textSelectionNode?.view ?? textView
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -611,7 +629,8 @@ final class StoryContentCaptionComponent: Component {
|
|||
self.state = state
|
||||
|
||||
let sideInset: CGFloat = 16.0
|
||||
let verticalInset: CGFloat = 7.0
|
||||
let verticalInset: CGFloat = 8.0
|
||||
let bottomPanelSpacing: CGFloat = 5.0
|
||||
let textContainerSize = CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height - verticalInset * 2.0)
|
||||
|
||||
var baseQuoteSecondaryTintColor: UIColor?
|
||||
|
|
@ -710,6 +729,57 @@ final class StoryContentCaptionComponent: Component {
|
|||
let visibleTextHeight = collapsedTextLayout.0.size.height - textInsets.top - textInsets.bottom
|
||||
let textOverflowHeight: CGFloat = expandedTextLayout.0.size.height - textInsets.top - textInsets.bottom - visibleTextHeight
|
||||
let scrollContentSize = CGSize(width: availableSize.width, height: availableSize.height + textOverflowHeight)
|
||||
let hasBottomStackContent = !component.text.isEmpty || component.forwardInfo != nil
|
||||
var bottomContentOffset: CGFloat = 0.0
|
||||
|
||||
if let music = component.music?._asMedia() as? TelegramMediaFile {
|
||||
let musicPanel: ComponentView<Empty>
|
||||
if let current = self.musicPanel {
|
||||
musicPanel = current
|
||||
} else {
|
||||
musicPanel = ComponentView<Empty>()
|
||||
self.musicPanel = musicPanel
|
||||
}
|
||||
|
||||
let musicPanelSize = musicPanel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
MusicPanelComponent(
|
||||
context: component.context,
|
||||
file: music
|
||||
)
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let sourceView = self.musicPanel?.view {
|
||||
self.component?.openMusic(.standalone(media: music), sourceView)
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
bottomContentOffset = musicPanelSize.height + (hasBottomStackContent ? bottomPanelSpacing : 0.0)
|
||||
let musicPanelFrame = CGRect(origin: CGPoint(x: sideInset - 8.0, y: availableSize.height - verticalInset - musicPanelSize.height), size: musicPanelSize)
|
||||
if let view = musicPanel.view {
|
||||
if view.superview == nil {
|
||||
self.scrollView.addSubview(view)
|
||||
transition.animateAlpha(view: view, from: 0.0, to: 1.0)
|
||||
}
|
||||
view.frame = musicPanelFrame
|
||||
}
|
||||
} else if let musicPanel = self.musicPanel {
|
||||
self.musicPanel = nil
|
||||
musicPanel.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
let contentBottomY = availableSize.height - verticalInset - bottomContentOffset
|
||||
|
||||
if let forwardInfo = component.forwardInfo {
|
||||
let authorName: String
|
||||
|
|
@ -772,8 +842,6 @@ final class StoryContentCaptionComponent: Component {
|
|||
fillsWidth: false
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
minSize: nil,
|
||||
action: { [weak self] in
|
||||
if let self, case let .known(peer, _, _) = forwardInfo {
|
||||
self.component?.openStory(peer, self.forwardInfoStory)
|
||||
|
|
@ -786,13 +854,14 @@ final class StoryContentCaptionComponent: Component {
|
|||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let forwardInfoPanelFrame = CGRect(origin: CGPoint(x: sideInset, y: availableSize.height - visibleTextHeight - verticalInset - forwardInfoPanelSize.height - 10.0), size: forwardInfoPanelSize)
|
||||
let forwardInfoPanelFrame = CGRect(origin: CGPoint(x: sideInset, y: contentBottomY - visibleTextHeight - forwardInfoPanelSize.height - bottomPanelSpacing), size: forwardInfoPanelSize)
|
||||
if let view = forwardInfoPanel.view {
|
||||
if view.superview == nil {
|
||||
self.scrollView.addSubview(view)
|
||||
|
|
@ -807,8 +876,8 @@ final class StoryContentCaptionComponent: Component {
|
|||
}
|
||||
|
||||
|
||||
let collapsedTextFrame = CGRect(origin: CGPoint(x: sideInset - textInsets.left, y: availableSize.height - visibleTextHeight - verticalInset - textInsets.top), size: collapsedTextLayout.0.size)
|
||||
let expandedTextFrame = CGRect(origin: CGPoint(x: sideInset - textInsets.left, y: availableSize.height - visibleTextHeight - verticalInset - textInsets.top), size: expandedTextLayout.0.size)
|
||||
let collapsedTextFrame = CGRect(origin: CGPoint(x: sideInset - textInsets.left, y: contentBottomY - visibleTextHeight - textInsets.top), size: collapsedTextLayout.0.size)
|
||||
let expandedTextFrame = CGRect(origin: CGPoint(x: sideInset - textInsets.left, y: contentBottomY - visibleTextHeight - textInsets.top), size: expandedTextLayout.0.size)
|
||||
|
||||
var spoilerExpandRect: CGRect?
|
||||
if let location = self.displayContentsUnderSpoilers.location {
|
||||
|
|
|
|||
|
|
@ -4523,7 +4523,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
if !isUnsupported, !component.slice.item.storyItem.text.isEmpty || component.slice.item.storyItem.forwardInfo != nil {
|
||||
if !isUnsupported, !component.slice.item.storyItem.text.isEmpty || component.slice.item.storyItem.forwardInfo != nil || component.slice.item.storyItem.music != nil {
|
||||
var captionItemTransition = transition
|
||||
let captionItem: CaptionItem
|
||||
if let current = self.captionItem {
|
||||
|
|
@ -4557,6 +4557,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
author: component.slice.effectivePeer,
|
||||
forwardInfo: component.slice.item.storyItem.forwardInfo,
|
||||
forwardInfoStory: forwardInfoStory,
|
||||
music: component.slice.item.storyItem.music,
|
||||
entities: enableEntities ? component.slice.item.storyItem.entities : [],
|
||||
entityFiles: component.slice.item.entityFiles,
|
||||
action: { [weak self] action in
|
||||
|
|
@ -4708,6 +4709,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
openMusic: { [weak self] file, sourceView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.performMusicAction(file: file, sourceView: sourceView, gesture: nil)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -7540,6 +7547,144 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
})
|
||||
}
|
||||
|
||||
private func performMusicAction(file: FileMediaReference, sourceView: UIView, gesture: ContextGesture?) {
|
||||
guard let component = self.component, let controller = component.controller() else {
|
||||
return
|
||||
}
|
||||
|
||||
self.dismissAllTooltips()
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: component.theme)
|
||||
|
||||
let items = component.context.engine.peers.savedMusicIds()
|
||||
|> take(1)
|
||||
|> map { [weak self] savedIds -> ContextController.Items in
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/DownloadTone"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in
|
||||
if let self {
|
||||
var subActions: [ContextMenuItem] = []
|
||||
// subActions.append(
|
||||
// .action(ContextMenuActionItem(text: presentationData.strings.Common_Back, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.contextMenu.primaryColor) }, iconPosition: .left, action: { c, _ in
|
||||
// c?.popItems()
|
||||
// }))
|
||||
// )
|
||||
// subActions.append(.separator)
|
||||
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Profile, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/User"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = component.context.engine.peers.addSavedMusic(file: file).start()
|
||||
|
||||
guard let controller = component.controller() as? StoryContainerScreen else {
|
||||
return
|
||||
}
|
||||
|
||||
let overlayController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .universalImage(
|
||||
image: generateTintedImage(image: UIImage(bundleImageName: "Peer Info/SavedMusic"), color: .white)!,
|
||||
size: nil,
|
||||
title: nil,
|
||||
text: presentationData.strings.MediaPlayer_SavedMusic_AddedToProfile,
|
||||
customUndoText: presentationData.strings.MediaPlayer_SavedMusic_AddedToProfile_View,
|
||||
timeout: 3.0
|
||||
),
|
||||
action: { [weak self] action in
|
||||
guard let self, let component = self.component, case .undo = action else {
|
||||
return false
|
||||
}
|
||||
let _ = (component.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: component.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let component = self.component, let peer else {
|
||||
return
|
||||
}
|
||||
guard let controller = component.controller() as? StoryContainerScreen else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
if let controller = component.context.sharedContext.makePeerInfoController(
|
||||
context: component.context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .myProfile,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(overlayController, in: .current)
|
||||
}))
|
||||
)
|
||||
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_SavedMessages, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Fave"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
|
||||
let _ = component.context.engine.messages.enqueueOutgoingMessage(to: component.context.account.peerId, replyTo: nil, content: .file(file)).start()
|
||||
|
||||
guard let controller = component.controller() as? StoryContainerScreen else {
|
||||
return
|
||||
}
|
||||
|
||||
let overlayController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .forward(savedMessages: true, text: presentationData.strings.MediaPlayer_AudioForwardedToSavedMesagesTooltip),
|
||||
action: { _ in
|
||||
return true
|
||||
}
|
||||
)
|
||||
controller.present(overlayController, in: .current)
|
||||
}))
|
||||
)
|
||||
|
||||
let noAction: ((ContextMenuActionItem.Action) -> Void)? = nil
|
||||
subActions.append(
|
||||
.action(ContextMenuActionItem(text: presentationData.strings.MediaPlayer_ContextMenu_SaveTo_Info, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: noAction))
|
||||
)
|
||||
|
||||
c?.pushItems(items: .single(ContextController.Items(content: .list(subActions))))
|
||||
}
|
||||
}))
|
||||
)
|
||||
return ContextController.Items(content: .list(items))
|
||||
}
|
||||
|
||||
let contextController = makeContextController(
|
||||
presentationData: presentationData,
|
||||
source: .reference(HeaderContextReferenceContentSource(controller: controller, sourceView: sourceView, position: .top)),
|
||||
items: items,
|
||||
gesture: gesture
|
||||
)
|
||||
contextController.dismissed = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.contextController = nil
|
||||
self.updateIsProgressPaused()
|
||||
}
|
||||
self.contextController = contextController
|
||||
self.updateIsProgressPaused()
|
||||
controller.present(contextController, in: .window(.root))
|
||||
}
|
||||
|
||||
private func beginPictureInPicture() {
|
||||
guard let component = self.component, let visibleItem = self.visibleItems[component.slice.item.id] else {
|
||||
return
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlay.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlay.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play1.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlay.imageset/play1.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlay.imageset/play1.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlaying.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlaying.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "play2.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlaying.imageset/play2.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Gallery/LivePhotoPlaying.imageset/play2.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -188,26 +188,26 @@ extension ChatControllerImpl {
|
|||
break
|
||||
}
|
||||
|
||||
if !isPaidMessages {
|
||||
for bot in attachMenuBots.reversed() {
|
||||
var peerType = peerType
|
||||
if bot.peer.id == peer.id {
|
||||
peerType.insert(.sameBot)
|
||||
peerType.remove(.bot)
|
||||
}
|
||||
let button: AttachmentButtonType = .app(bot)
|
||||
if !bot.peerTypes.intersection(peerType).isEmpty {
|
||||
buttons.insert(button, at: 1)
|
||||
|
||||
if case let .bot(botId, _, _) = subject {
|
||||
if initialButton == nil && bot.peer.id == botId {
|
||||
initialButton = button
|
||||
}
|
||||
for bot in attachMenuBots.reversed() {
|
||||
var peerType = peerType
|
||||
if bot.peer.id == peer.id {
|
||||
peerType.insert(.sameBot)
|
||||
peerType.remove(.bot)
|
||||
}
|
||||
let button: AttachmentButtonType = .app(bot)
|
||||
if !bot.peerTypes.intersection(peerType).isEmpty {
|
||||
buttons.insert(button, at: 1)
|
||||
|
||||
if case let .bot(botId, _, _) = subject {
|
||||
if initialButton == nil && bot.peer.id == botId {
|
||||
initialButton = button
|
||||
}
|
||||
}
|
||||
allButtons.insert(button, at: 1)
|
||||
}
|
||||
allButtons.insert(button, at: 1)
|
||||
}
|
||||
|
||||
if !isPaidMessages {
|
||||
if context.isPremium, shortcutMessageList.items.count > 0, let user = peer as? TelegramUser, user.botInfo == nil {
|
||||
if let index = buttons.firstIndex(where: { $0 == .location }) {
|
||||
buttons.insert(.quickReply, at: index + 1)
|
||||
|
|
|
|||
|
|
@ -868,7 +868,8 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, itemOffsetInsets: itemOffsetInsets, duration: duration, curve: curve)
|
||||
self.historyNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
|
||||
if let replacementHistoryNode = self.replacementHistoryNode {
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, duration: 0.0, curve: .Default(duration: nil))
|
||||
transition.updateFrame(node: replacementHistoryNode, frame: CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listNodeSize))
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, itemOffsetInsets: itemOffsetInsets, duration: 0.0, curve: .Default(duration: nil))
|
||||
replacementHistoryNode.updateLayout(transition: transition, updateSizeAndInsets: updateSizeAndInsets)
|
||||
}
|
||||
|
||||
|
|
@ -962,7 +963,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.updateHistoryContentOffset(self.historyNode.visibleContentOffset(), transition: transition)
|
||||
|
||||
var layout = layout
|
||||
layout.intrinsicInsets.bottom = controlsHeight + 8.0
|
||||
layout.intrinsicInsets.bottom = controlsHeight + (self.historyNode.hasAnyMessages ? 0.0 : 8.0)
|
||||
self.getParentController()?.presentationContext.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
|
||||
|
|
@ -1091,13 +1092,13 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
let leftOverlayFrame = CGRect(origin: CGPoint(x: 0.0, y: topOverlayFrame.maxY - 1.0), size: CGSize(width: sideInset, height: layout.size.height))
|
||||
let rightOverlayFrame = CGRect(origin: CGPoint(x: layout.size.width - sideInset, y: topOverlayFrame.maxY - 1.0), size: CGSize(width: sideInset, height: layout.size.height))
|
||||
|
||||
self.historyFrameNode.frame = frameFrame
|
||||
transition.updateFrame(node: self.historyFrameNode, frame: frameFrame)
|
||||
self.historyFrameTopOverlayClipNode.frame = topOverlayFrame
|
||||
self.historyFrameTopOverlayNode.frame = CGRect(origin: .zero, size: CGSize(width: topOverlayFrame.width, height: 78.0))
|
||||
self.historyFrameLeftOverlayNode.frame = leftOverlayFrame
|
||||
self.historyFrameRightOverlayNode.frame = rightOverlayFrame
|
||||
if let image = self.historyFrameTopMaskNode.image {
|
||||
self.historyFrameTopMaskNode.frame = CGRect(origin: CGPoint(x: sideInset, y: topOverlayFrame.maxY), size: CGSize(width: layout.size.width - sideInset * 2.0, height: image.size.height))
|
||||
self.historyFrameTopMaskNode.frame = CGRect(origin: CGPoint(x: sideInset, y: topOverlayFrame.maxY - UIScreenPixel), size: CGSize(width: layout.size.width - sideInset * 2.0, height: image.size.height))
|
||||
}
|
||||
self.historyFrameTopMaskNode.isHidden = self.controlsNode.hasPlainBackground
|
||||
|
||||
|
|
@ -1157,23 +1158,33 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
historyNode.updateFloatingHeaderOffset = { [weak self] offset, _ in
|
||||
self?.replacementHistoryNodeFloatingOffset = offset
|
||||
}
|
||||
self.replacementHistoryNodeFloatingOffset = nil
|
||||
self.replacementHistoryNode = historyNode
|
||||
if let layout = self.validLayout {
|
||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||
let controlsHeight = self.controlsNode.frame.height
|
||||
|
||||
var insets = UIEdgeInsets()
|
||||
insets.left = 16.0
|
||||
insets.right = 16.0
|
||||
insets.bottom = 0.0
|
||||
|
||||
let listTopInset = layoutTopInset
|
||||
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
|
||||
|
||||
let headerHeight = self.effectiveHeaderHeight
|
||||
let listTopInset = layoutTopInset + headerHeight
|
||||
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - controlsHeight)
|
||||
|
||||
insets.top = max(0.0, listNodeSize.height - floor(62.0 * 3.5))
|
||||
|
||||
var itemOffsetInsets = insets
|
||||
if let playlistLocation = self.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
|
||||
itemOffsetInsets.top = 0.0
|
||||
itemOffsetInsets.bottom = 0.0
|
||||
insets = itemOffsetInsets
|
||||
}
|
||||
|
||||
historyNode.frame = CGRect(origin: CGPoint(x: 0.0, y: listTopInset), size: listNodeSize)
|
||||
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, duration: 0.0, curve: .Default(duration: nil))
|
||||
|
||||
let updateSizeAndInsets = ListViewUpdateSizeAndInsets(size: listNodeSize, insets: insets, itemOffsetInsets: itemOffsetInsets, duration: 0.0, curve: .Default(duration: nil))
|
||||
historyNode.updateLayout(transition: .immediate, updateSizeAndInsets: updateSizeAndInsets)
|
||||
}
|
||||
self.replacementHistoryNodeReadyDisposable.set((historyNode.historyState.get() |> take(1) |> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
||||
|
|
@ -1194,21 +1205,15 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.setupReordering()
|
||||
self.updateHistoryContentOffset(replacementHistoryNode.visibleContentOffset(), transition: .immediate)
|
||||
|
||||
if let validLayout = self.validLayout, let offset = self.replacementHistoryNodeFloatingOffset, let previousOffset = self.floatingHeaderOffset {
|
||||
if let offset = self.replacementHistoryNodeFloatingOffset, let previousOffset = self.floatingHeaderOffset {
|
||||
let offsetDelta = offset - previousOffset
|
||||
|
||||
let layoutTopInset: CGFloat = max(validLayout.statusBarHeight ?? 0.0, validLayout.safeInsets.top)
|
||||
|
||||
let controlsBottomOffset = max(layoutTopInset, offset)
|
||||
|
||||
|
||||
let previousBackgroundNode = ASDisplayNode()
|
||||
previousBackgroundNode.isLayerBacked = true
|
||||
previousBackgroundNode.backgroundColor = self.historyBackgroundContentNode.backgroundColor
|
||||
self.contentNode.insertSubnode(previousBackgroundNode, belowSubnode: previousHistoryNode)
|
||||
previousBackgroundNode.frame = self.historyBackgroundNode.frame
|
||||
|
||||
previousBackgroundNode.layer.animateFrame(from: previousBackgroundNode.frame, to: CGRect(origin: CGPoint(x: 0.0, y: controlsBottomOffset), size: validLayout.size), duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
||||
self.updateFloatingHeaderOffset(offset: offset, transition: .animated(duration: 0.4, curve: .spring))
|
||||
previousHistoryNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousHistoryNode] _ in
|
||||
previousHistoryNode?.removeFromSupernode()
|
||||
|
|
@ -1237,8 +1242,12 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
}
|
||||
switch strongSelf.historyNode.visibleContentOffset() {
|
||||
case let .known(value):
|
||||
if value <= -10.0 {
|
||||
strongSelf.requestDismiss()
|
||||
if let playlistLocation = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .savedMusic(_, _, canReorder) = playlistLocation, canReorder {
|
||||
|
||||
} else {
|
||||
if value <= -10.0 {
|
||||
strongSelf.requestDismiss()
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
|
@ -1251,14 +1260,16 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
|
||||
if let layout = self.validLayout {
|
||||
let layoutTopInset: CGFloat = max(layout.statusBarHeight ?? 0.0, layout.safeInsets.top)
|
||||
let controlsHeight = self.controlsNode.frame.height
|
||||
|
||||
var insets = UIEdgeInsets()
|
||||
insets.left = 16.0
|
||||
insets.right = 16.0
|
||||
insets.bottom = 0.0
|
||||
|
||||
let listTopInset = layoutTopInset
|
||||
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset)
|
||||
let headerHeight = self.effectiveHeaderHeight
|
||||
let listTopInset = layoutTopInset + headerHeight
|
||||
let listNodeSize = CGSize(width: layout.size.width, height: layout.size.height - listTopInset - controlsHeight)
|
||||
|
||||
insets.top = max(0.0, listNodeSize.height - floor(62.0 * 3.5))
|
||||
|
||||
|
|
@ -1277,6 +1288,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu
|
|||
self.historyNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
}
|
||||
|
||||
self.replacementHistoryNodeFloatingOffset = nil
|
||||
self.updateHistoryContentOffset(self.historyNode.visibleContentOffset(), transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -464,6 +464,10 @@ public func pollResultsController(context: AccountContext, messageId: EngineMess
|
|||
resultsContext.state
|
||||
)
|
||||
|> map { presentationData, state, resultsState -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var presentationData = presentationData
|
||||
let updatedTheme = presentationData.theme.withModalBlocksBackground()
|
||||
presentationData = presentationData.withUpdated(theme: updatedTheme)
|
||||
|
||||
var isEmpty = false
|
||||
for (_, optionState) in resultsState.options {
|
||||
if !optionState.hasLoadedOnce {
|
||||
|
|
|
|||
|
|
@ -770,6 +770,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
randomId: result.randomId,
|
||||
forwardInfo: forwardInfo,
|
||||
folders: folders,
|
||||
music: result.music,
|
||||
uploadInfo: results.count > 1 ? StoryUploadInfo(groupingId: groupingId, index: index, total: Int32(results.count)) : nil
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { stableId in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue