mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge commit '136e0fa370'
This commit is contained in:
commit
15880b36a2
52 changed files with 934 additions and 449 deletions
|
|
@ -12466,3 +12466,9 @@ Sorry for the inconvenience.";
|
|||
"WebApp.MinimizedTitleFormat" = "%1$@ & %2$@";
|
||||
"WebApp.MinimizedTitle.Others_1" = "%@ Other";
|
||||
"WebApp.MinimizedTitle.Others_any" = "%@ Others";
|
||||
|
||||
"Stars.SendStars.Title" = "Send Stars";
|
||||
"Stars.SendStars.AmountTitle" = "ENTER AMOUNT";
|
||||
"Stars.SendStars.AmountPlaceholder" = "Stars Amount";
|
||||
"Stars.SendStars.AmountInfo" = "Send %@ or more to highlight your profile in the TOP 3 supporters of this message.";
|
||||
"Stars.SendStars.SendStars" = "Confirm and Send";
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import Display
|
|||
import DirectionalPanGesture
|
||||
import TelegramPresentationData
|
||||
import MapKit
|
||||
import WebKit
|
||||
|
||||
private let overflowInset: CGFloat = 0.0
|
||||
|
||||
|
|
@ -160,6 +161,23 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
|
|||
}
|
||||
return true
|
||||
}
|
||||
if gestureRecognizer is UIPanGestureRecognizer {
|
||||
func findWebViewAncestor(view: UIView?) -> WKWebView? {
|
||||
guard let view else {
|
||||
return nil
|
||||
}
|
||||
if let view = view as? WKWebView {
|
||||
return view
|
||||
} else if view != self.view {
|
||||
return findWebViewAncestor(view: view.superview)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if let otherView = otherGestureRecognizer.view, let _ = findWebViewAncestor(view: otherView) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if gestureRecognizer is UIPanGestureRecognizer && otherGestureRecognizer is UILongPressGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@ public class AttachmentController: ViewController {
|
|||
let panel: AttachmentPanel
|
||||
|
||||
fileprivate var currentType: AttachmentButtonType?
|
||||
private var currentControllers: [AttachmentContainable] = []
|
||||
fileprivate var currentControllers: [AttachmentContainable] = []
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var modalProgress: CGFloat = 0.0
|
||||
|
|
@ -391,33 +391,15 @@ public class AttachmentController: ViewController {
|
|||
|
||||
self.container.interactivelyDismissed = { [weak self] velocity in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout {
|
||||
if let controller = strongSelf.controller, controller.shouldMinimizeOnSwipe?(strongSelf.currentType) == true, let navigationController = controller.navigationController as? NavigationController {
|
||||
|
||||
let delta = layout.size.height - controller.minimizedTopEdgeOffset
|
||||
let damping: CGFloat = 180
|
||||
if let controller = strongSelf.controller, controller.shouldMinimizeOnSwipe?(strongSelf.currentType) == true {
|
||||
var delta = layout.size.height
|
||||
if let minimizedTopEdgeOffset = controller.minimizedTopEdgeOffset {
|
||||
delta -= minimizedTopEdgeOffset
|
||||
}
|
||||
let damping: CGFloat = 180.0
|
||||
let initialVelocity: CGFloat = delta > 0.0 ? velocity / delta : 0.0
|
||||
|
||||
navigationController.minimizeViewController(controller, damping: damping, velocity: initialVelocity, setupContainer: { [weak self] current in
|
||||
let minimizedContainer: MinimizedContainerImpl?
|
||||
if let current = current as? MinimizedContainerImpl {
|
||||
minimizedContainer = current
|
||||
} else if let context = self?.controller?.context {
|
||||
minimizedContainer = MinimizedContainerImpl(context: context, navigationController: navigationController)
|
||||
} else {
|
||||
minimizedContainer = nil
|
||||
}
|
||||
return minimizedContainer
|
||||
}, animated: true)
|
||||
|
||||
strongSelf.dim.isHidden = true
|
||||
|
||||
strongSelf.isMinimizing = true
|
||||
strongSelf.container.update(isExpanded: true, force: true, transition: .immediate)
|
||||
strongSelf.isMinimizing = false
|
||||
|
||||
Queue.mainQueue().after(0.45, {
|
||||
strongSelf.dim.isHidden = false
|
||||
})
|
||||
strongSelf.minimize(damping: damping, initialVelocity: initialVelocity)
|
||||
|
||||
return false
|
||||
} else {
|
||||
|
|
@ -588,6 +570,33 @@ public class AttachmentController: ViewController {
|
|||
}
|
||||
}
|
||||
|
||||
fileprivate func minimize(damping: CGFloat? = nil, initialVelocity: CGFloat? = nil) {
|
||||
guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
navigationController.minimizeViewController(controller, damping: damping, velocity: initialVelocity, setupContainer: { [weak self] current in
|
||||
let minimizedContainer: MinimizedContainerImpl?
|
||||
if let current = current as? MinimizedContainerImpl {
|
||||
minimizedContainer = current
|
||||
} else if let context = self?.controller?.context {
|
||||
minimizedContainer = MinimizedContainerImpl(sharedContext: context.sharedContext)
|
||||
} else {
|
||||
minimizedContainer = nil
|
||||
}
|
||||
return minimizedContainer
|
||||
}, animated: true)
|
||||
|
||||
self.dim.isHidden = true
|
||||
|
||||
self.isMinimizing = true
|
||||
self.container.update(isExpanded: true, force: true, transition: .immediate)
|
||||
self.isMinimizing = false
|
||||
|
||||
Queue.mainQueue().after(0.45, {
|
||||
self.dim.isHidden = false
|
||||
})
|
||||
}
|
||||
|
||||
fileprivate func updateSelectionCount(_ count: Int, animated: Bool = true) {
|
||||
self.selectionCount = count
|
||||
if let layout = self.validLayout {
|
||||
|
|
@ -600,8 +609,12 @@ public class AttachmentController: ViewController {
|
|||
return
|
||||
}
|
||||
if case .ended = recognizer.state {
|
||||
if let controller = self.currentControllers.last {
|
||||
controller.requestDismiss(completion: { [weak self] in
|
||||
if let lastController = self.currentControllers.last {
|
||||
if let controller = self.controller, controller.shouldMinimizeOnSwipe?(self.currentType) == true {
|
||||
self.minimize()
|
||||
return
|
||||
}
|
||||
lastController.requestDismiss(completion: { [weak self] in
|
||||
self?.controller?.dismiss(animated: true)
|
||||
})
|
||||
} else {
|
||||
|
|
@ -954,7 +967,7 @@ public class AttachmentController: ViewController {
|
|||
if fromMenu && !hasButton, let inputContainerHeight = self.inputContainerHeight {
|
||||
panelHeight = inputContainerHeight
|
||||
}
|
||||
if hasPanel || hasButton || (fromMenu && isCompact) {
|
||||
if hasPanel || hasButton {
|
||||
containerInsets.bottom = panelHeight
|
||||
}
|
||||
|
||||
|
|
@ -1094,6 +1107,12 @@ public class AttachmentController: ViewController {
|
|||
self.hasTextInput = false
|
||||
self.requestLayout(transition: .immediate)
|
||||
}
|
||||
|
||||
public func minimizeIfNeeded() {
|
||||
if self.shouldMinimizeOnSwipe?(self.node.currentType) == true {
|
||||
self.node.minimize()
|
||||
}
|
||||
}
|
||||
|
||||
public func updateSelectionCount(_ count: Int) {
|
||||
self.node.updateSelectionCount(count, animated: false)
|
||||
|
|
@ -1145,6 +1164,12 @@ public class AttachmentController: ViewController {
|
|||
return false
|
||||
}
|
||||
|
||||
public override var isMinimized: Bool {
|
||||
didSet {
|
||||
self.mainController.isMinimized = self.isMinimized
|
||||
}
|
||||
}
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
|
|
@ -1160,6 +1185,10 @@ public class AttachmentController: ViewController {
|
|||
self.node.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
|
||||
public var mainController: ViewController {
|
||||
return self.node.currentControllers.first!
|
||||
}
|
||||
|
||||
public final class InputPanelTransition {
|
||||
let inputNode: ASDisplayNode
|
||||
let accessoryPanelNode: ASDisplayNode?
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ swift_library(
|
|||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
"//submodules/Components/BundleIconComponent:BundleIconComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent:BlurredBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/MinimizedContainer",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import BundleIconComponent
|
|||
import TelegramUIPreferences
|
||||
import OpenInExternalAppUI
|
||||
import MultilineTextComponent
|
||||
import MinimizedContainer
|
||||
|
||||
private let settingsTag = GenericComponentViewTag()
|
||||
|
||||
|
|
@ -291,6 +292,7 @@ public class BrowserScreen: ViewController {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.controller?.title = state.title
|
||||
strongSelf.contentState = state
|
||||
strongSelf.requestLayout(transition: .immediate)
|
||||
}).strict()
|
||||
|
|
@ -318,7 +320,7 @@ public class BrowserScreen: ViewController {
|
|||
}
|
||||
self.controller?.present(shareController, in: .window(.root))
|
||||
case .minimize:
|
||||
break
|
||||
self.minimize()
|
||||
case .openIn:
|
||||
self.context.sharedContext.applicationBindings.openUrl(url)
|
||||
case .openSettings:
|
||||
|
|
@ -441,6 +443,23 @@ public class BrowserScreen: ViewController {
|
|||
self.requestLayout(transition: animated ? .easeInOut(duration: 0.2) : .immediate)
|
||||
}
|
||||
|
||||
func minimize() {
|
||||
guard let controller = self.controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
navigationController.minimizeViewController(controller, damping: nil, setupContainer: { [weak self] current in
|
||||
let minimizedContainer: MinimizedContainerImpl?
|
||||
if let current = current as? MinimizedContainerImpl {
|
||||
minimizedContainer = current
|
||||
} else if let context = self?.controller?.context {
|
||||
minimizedContainer = MinimizedContainerImpl(sharedContext: context.sharedContext)
|
||||
} else {
|
||||
minimizedContainer = nil
|
||||
}
|
||||
return minimizedContainer
|
||||
}, animated: true)
|
||||
}
|
||||
|
||||
func openSettings() {
|
||||
guard let referenceView = self.componentHost.findTaggedView(tag: settingsTag) as? ReferenceButtonComponent.View else {
|
||||
return
|
||||
|
|
@ -535,7 +554,8 @@ public class BrowserScreen: ViewController {
|
|||
self.context.sharedContext.applicationBindings.openUrl(openInUrl)
|
||||
}
|
||||
action(.default)
|
||||
}))]
|
||||
}))
|
||||
]
|
||||
|
||||
let contextController = ContextController(presentationData: self.presentationData, source: source, items: .single(ContextController.Items(content: .list(items))))
|
||||
self.controller?.present(contextController, in: .window(.root))
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ final class BrowserToolbarComponent: CombinedComponent {
|
|||
transition.animatePosition(view: view, from: CGPoint(x: 0.0, y: size.height), to: .zero, additive: true)
|
||||
}))
|
||||
.disappear(ComponentTransition.Disappear({ view, transition, completion in
|
||||
transition.animatePosition(view: view, from: .zero, to: CGPoint(x: 0.0, y: size.height), additive: true, completion: { _ in
|
||||
let from = view.center
|
||||
view.center = from.offsetBy(dx: 0.0, dy: size.height)
|
||||
transition.animatePosition(view: view, from: from, to: view.center, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}))
|
||||
|
|
@ -224,12 +226,12 @@ final class NavigationToolbarContentComponent: CombinedComponent {
|
|||
component: Button(
|
||||
content: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Chat/Context Menu/Browser",
|
||||
name: "Instant View/Minimize",
|
||||
tintColor: context.component.textColor
|
||||
)
|
||||
),
|
||||
action: {
|
||||
performAction.invoke(.openIn)
|
||||
performAction.invoke(.minimize)
|
||||
}
|
||||
).minSize(buttonSize),
|
||||
availableSize: buttonSize,
|
||||
|
|
|
|||
|
|
@ -1453,9 +1453,13 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
|
||||
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
|
||||
entries.append(.acceleratedStickers(experimentalSettings.acceleratedStickers))
|
||||
#if DEBUG
|
||||
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
|
||||
#else
|
||||
if sharedContext.applicationBindings.appBuildType == .internal {
|
||||
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
|
||||
}
|
||||
#endif
|
||||
entries.append(.localTranscription(experimentalSettings.localTranscription))
|
||||
if case .internal = sharedContext.applicationBindings.appBuildType {
|
||||
entries.append(.enableReactionOverrides(experimentalSettings.enableReactionOverrides))
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@ import Foundation
|
|||
import AsyncDisplayKit
|
||||
|
||||
public protocol MinimizedContainer: ASDisplayNode {
|
||||
var navigationController: NavigationController? { get set }
|
||||
var controllers: [ViewController] { get }
|
||||
var isExpanded: Bool { get }
|
||||
|
||||
var willMaximize: (() -> Void)? { get set }
|
||||
|
||||
func addController(_ viewController: ViewController, transition: ContainedViewLayoutTransition)
|
||||
func maximizeController(_ viewController: ViewController, animated: Bool, completion: @escaping (Bool) -> Void)
|
||||
func collapse()
|
||||
func dismissAll(completion: @escaping () -> Void)
|
||||
|
||||
func updateLayout(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
|
||||
|
|
|
|||
|
|
@ -150,7 +150,18 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
private var rootModalFrame: NavigationModalFrame?
|
||||
private var modalContainers: [NavigationModalContainer] = []
|
||||
private var overlayContainers: [NavigationOverlayContainer] = []
|
||||
private var minimizedContainer: MinimizedContainer?
|
||||
open var minimizedContainer: MinimizedContainer? {
|
||||
didSet {
|
||||
self.minimizedContainer?.navigationController = self
|
||||
self.minimizedContainer?.willMaximize = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.isMaximizing = true
|
||||
self.updateContainersNonReentrant(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var globalOverlayContainers: [NavigationOverlayContainer] = []
|
||||
private var globalOverlayBelowKeyboardContainerParent: GlobalOverlayContainerParent?
|
||||
|
|
@ -826,6 +837,22 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
layout.additionalInsets.left = max(layout.intrinsicInsets.left, additionalSideInsets.left)
|
||||
layout.additionalInsets.right = max(layout.intrinsicInsets.right, additionalSideInsets.right)
|
||||
|
||||
var updatedSize = layout.size
|
||||
var updatedIntrinsicInsets = layout.intrinsicInsets
|
||||
if case .flat = navigationLayout.root, let minimizedContainer = self.minimizedContainer {
|
||||
if minimizedContainer.supernode !== self.displayNode {
|
||||
if let rootContainer = self.rootContainer, case let .flat(flatContainer) = rootContainer {
|
||||
self.displayNode.insertSubnode(minimizedContainer, aboveSubnode: flatContainer)
|
||||
} else {
|
||||
self.displayNode.insertSubnode(minimizedContainer, at: 0)
|
||||
}
|
||||
}
|
||||
if (layout.inputHeight ?? 0.0).isZero {
|
||||
updatedSize.height -= minimizedContainer.collapsedHeight(layout: layout)
|
||||
updatedIntrinsicInsets.bottom = 0.0
|
||||
}
|
||||
}
|
||||
|
||||
switch navigationLayout.root {
|
||||
case let .flat(controllers):
|
||||
if let rootContainer = self.rootContainer {
|
||||
|
|
@ -839,12 +866,6 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
flatContainer.canHaveKeyboardFocus = false
|
||||
}
|
||||
|
||||
var updatedSize = layout.size
|
||||
var updatedIntrinsicInsets = layout.intrinsicInsets
|
||||
if let minimizedContainer = self.minimizedContainer, (layout.inputHeight ?? 0.0).isZero {
|
||||
updatedSize.height -= minimizedContainer.collapsedHeight(layout: layout)
|
||||
updatedIntrinsicInsets.bottom = 0.0
|
||||
}
|
||||
let updatedLayout = layout.withUpdatedSize(updatedSize).withUpdatedIntrinsicInsets(updatedIntrinsicInsets)
|
||||
transition.updateFrame(node: flatContainer, frame: CGRect(origin: CGPoint(), size: updatedSize))
|
||||
flatContainer.update(layout: updatedLayout, canBeClosed: false, controllers: controllers, transition: transition)
|
||||
|
|
@ -904,8 +925,10 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
self.displayNode.insertSubnode(flatContainer, at: 0)
|
||||
}
|
||||
self.rootContainer = .flat(flatContainer)
|
||||
flatContainer.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
flatContainer.update(layout: layout, canBeClosed: false, controllers: controllers, transition: .immediate)
|
||||
|
||||
let updatedLayout = layout.withUpdatedSize(updatedSize).withUpdatedIntrinsicInsets(updatedIntrinsicInsets)
|
||||
flatContainer.frame = CGRect(origin: CGPoint(), size: updatedSize)
|
||||
flatContainer.update(layout: updatedLayout, canBeClosed: false, controllers: controllers, transition: .immediate)
|
||||
}
|
||||
case let .split(masterControllers, detailControllers):
|
||||
if let rootContainer = self.rootContainer {
|
||||
|
|
@ -931,6 +954,11 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
splitContainer.update(layout: layout, masterControllers: masterControllers, detailControllers: detailControllers, detailsPlaceholderNode: self.detailsPlaceholderNode, transition: .immediate)
|
||||
flatContainer.statusBarStyleUpdated = nil
|
||||
flatContainer.removeFromSupernode()
|
||||
|
||||
if let minimizedContainer = self.minimizedContainer {
|
||||
minimizedContainer.removeFromSupernode()
|
||||
self.minimizedContainer = nil
|
||||
}
|
||||
case let .split(splitContainer):
|
||||
if previousModalContainer == nil {
|
||||
splitContainer.canHaveKeyboardFocus = true
|
||||
|
|
@ -1307,7 +1335,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
if let _ = self.inCallStatusBar {
|
||||
self.inCallNavigate?()
|
||||
} else if let rootContainer = self.rootContainer {
|
||||
if let modalContainer = self.modalContainers.last {
|
||||
if let minimizedContainer = self.minimizedContainer, minimizedContainer.isExpanded {
|
||||
minimizedContainer.collapse()
|
||||
} else if let modalContainer = self.modalContainers.last {
|
||||
modalContainer.container.controllers.last?.scrollToTop?()
|
||||
} else {
|
||||
switch rootContainer {
|
||||
|
|
@ -1554,17 +1584,10 @@ open class NavigationController: UINavigationController, ContainableController,
|
|||
self.isMaximizing = true
|
||||
self.updateContainersNonReentrant(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
self.minimizedContainer?.removeFromSupernode()
|
||||
self.minimizedContainer = minimizedContainer
|
||||
|
||||
if let minimizedContainer {
|
||||
if let modalContainer = self.modalContainers.first {
|
||||
self.displayNode.insertSubnode(minimizedContainer, belowSubnode: modalContainer)
|
||||
} else {
|
||||
self.displayNode.addSubnode(minimizedContainer)
|
||||
}
|
||||
}
|
||||
|
||||
self.updateContainersNonReentrant(transition: transition)
|
||||
}
|
||||
viewController.isMinimized = true
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ public protocol CustomViewControllerNavigationDataSummary: AnyObject {
|
|||
|
||||
private var navigationBarOrigin: CGFloat = 0.0
|
||||
|
||||
public var minimizedTopEdgeOffset: CGFloat = 0.0
|
||||
public var minimizedTopEdgeOffset: CGFloat?
|
||||
public var minimizedBounds: CGRect?
|
||||
open var isMinimized: Bool = false
|
||||
|
||||
|
|
|
|||
|
|
@ -386,7 +386,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
strongSelf.statusNodeContainer.isHidden = true
|
||||
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) {
|
||||
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) && message.paidContent == nil {
|
||||
strongSelf.recognitionDisposable.set((recognizedContent(context: strongSelf.context, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] results in
|
||||
if let strongSelf = self {
|
||||
|
|
|
|||
|
|
@ -1195,7 +1195,9 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
var hintSeekable = false
|
||||
if let contentInfo = item.contentInfo, case let .message(message, _) = contentInfo {
|
||||
if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.id.namespace == Namespaces.Message.Local {
|
||||
if message.paidContent != nil {
|
||||
disablePictureInPicture = true
|
||||
} else if Namespaces.Message.allNonRegular.contains(message.id.namespace) || message.id.namespace == Namespaces.Message.Local {
|
||||
disablePictureInPicture = true
|
||||
} else {
|
||||
let throttledSignal = videoNode.status
|
||||
|
|
|
|||
|
|
@ -1227,6 +1227,9 @@
|
|||
else if (groupedId != nil && !hasAnyTimers)
|
||||
dict[@"groupedId"] = groupedId;
|
||||
|
||||
if (price != nil)
|
||||
dict[@"price"] = price;
|
||||
|
||||
if (spoiler) {
|
||||
dict[@"spoiler"] = @true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,9 +133,10 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||
|
||||
private struct SelectionState: Equatable {
|
||||
let selected: Bool
|
||||
let index: Int?
|
||||
let count: Int
|
||||
}
|
||||
private let selectionPromise = ValuePromise<SelectionState>(SelectionState(selected: false, count: 0))
|
||||
private let selectionPromise = ValuePromise<SelectionState>(SelectionState(selected: false, index: nil, count: 0))
|
||||
private let spoilerDisposable = MetaDisposable()
|
||||
var spoilerNode: SpoilerOverlayNode?
|
||||
var priceNode: PriceNode?
|
||||
|
|
@ -256,14 +257,16 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||
|
||||
if let interaction = self.interaction, let selectionState = interaction.selectionState {
|
||||
let selected = selectionState.isIdentifierSelected(self.identifier)
|
||||
var selectionIndex: Int?
|
||||
if let selectableItem = self.selectableItem {
|
||||
let index = selectionState.index(of: selectableItem)
|
||||
if index != NSNotFound {
|
||||
self.checkNode?.content = .counter(Int(index))
|
||||
selectionIndex = Int(index)
|
||||
}
|
||||
}
|
||||
self.checkNode?.setSelected(selected, animated: animated)
|
||||
self.selectionPromise.set(SelectionState(selected: selected, count: selectionState.selectedItems().count))
|
||||
self.selectionPromise.set(SelectionState(selected: selected, index: selectionIndex, count: selectionState.selectedItems().count))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -292,7 +295,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||
self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.draftNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.priceNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
if animateSpoilerNode {
|
||||
if animateSpoilerNode || self.priceNode != nil {
|
||||
self.spoilerNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
}
|
||||
}
|
||||
|
|
@ -573,7 +576,7 @@ final class MediaPickerGridItemNode: GridItemNode {
|
|||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateHasSpoiler(hasSpoiler, price: selectionState.selected ? price : nil, isSingle: selectionState.count == 1)
|
||||
strongSelf.updateHasSpoiler(hasSpoiler, price: selectionState.selected ? price : nil, isSingle: selectionState.count == 1 || selectionState.index == 1)
|
||||
}))
|
||||
|
||||
if self.currentDraftState != nil {
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||
private let bannedSendPhotos: (Int32, Bool)?
|
||||
private let bannedSendVideos: (Int32, Bool)?
|
||||
private let canBoostToUnrestrict: Bool
|
||||
private let paidMediaAllowed: Bool
|
||||
private let subject: Subject
|
||||
private let saveEditedPhotos: Bool
|
||||
|
||||
|
|
@ -1750,6 +1751,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||
bannedSendPhotos: (Int32, Bool)? = nil,
|
||||
bannedSendVideos: (Int32, Bool)? = nil,
|
||||
canBoostToUnrestrict: Bool = false,
|
||||
paidMediaAllowed: Bool = false,
|
||||
subject: Subject,
|
||||
editingContext: TGMediaEditingContext? = nil,
|
||||
selectionContext: TGMediaSelectionContext? = nil,
|
||||
|
|
@ -1769,6 +1771,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||
self.bannedSendPhotos = bannedSendPhotos
|
||||
self.bannedSendVideos = bannedSendVideos
|
||||
self.canBoostToUnrestrict = canBoostToUnrestrict
|
||||
self.paidMediaAllowed = paidMediaAllowed
|
||||
self.subject = subject
|
||||
self.saveEditedPhotos = saveEditedPhotos
|
||||
self.mainButtonState = mainButtonState
|
||||
|
|
@ -2499,7 +2502,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
|
|||
}
|
||||
|
||||
var isPaidAvailable = false
|
||||
if let peer = self.peer, case let .channel(channel) = peer, case .broadcast = channel.info, selectionCount <= 10 {
|
||||
if self.paidMediaAllowed, selectionCount <= 10 {
|
||||
isPaidAvailable = true
|
||||
}
|
||||
if isSpoilerAvailable || isPaidAvailable || (selectionCount > 0 && isCaptionAboveMediaAvailable) {
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
|
|||
self.didSetupSpoiler = true
|
||||
}
|
||||
|
||||
if hasSpoiler || price != nil {
|
||||
if hasSpoiler {
|
||||
if self.spoilerNode == nil {
|
||||
let spoilerNode = SpoilerOverlayNode(enableAnimations: self.enableAnimations)
|
||||
self.insertSubnode(spoilerNode, aboveSubnode: self.imageNode)
|
||||
|
|
@ -499,6 +499,8 @@ final class PriceNode: ASDisplayNode {
|
|||
|
||||
super.init()
|
||||
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.backgroundNode.addSubnode(self.lockNode)
|
||||
self.backgroundNode.addSubnode(self.iconNode)
|
||||
|
|
@ -890,11 +892,24 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||
self.reorderFeedback = HapticFeedback()
|
||||
}
|
||||
self.reorderFeedback?.impact()
|
||||
|
||||
let priceTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
for (_, node) in self.priceNodes {
|
||||
priceTransition.updateAlpha(node: node, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
|
||||
private func endReordering(point: CGPoint?) {
|
||||
if let reorderNode = self.reorderNode {
|
||||
self.reorderNode = nil
|
||||
|
||||
let completion = {
|
||||
let priceTransition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
|
||||
for (_, node) in self.priceNodes {
|
||||
node.supernode?.view.bringSubviewToFront(node.view)
|
||||
priceTransition.updateAlpha(node: node, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
if let itemNode = reorderNode.itemNode, let point = point {
|
||||
var targetNode: MediaPickerSelectedItemNode?
|
||||
|
|
@ -910,11 +925,13 @@ final class MediaPickerSelectedListNode: ASDisplayNode, ASScrollViewDelegate, AS
|
|||
}
|
||||
reorderNode.animateCompletion(completion: { [weak reorderNode] in
|
||||
reorderNode?.removeFromSupernode()
|
||||
completion()
|
||||
})
|
||||
self.reorderFeedback?.tap()
|
||||
} else {
|
||||
reorderNode.removeFromSupernode()
|
||||
reorderNode.itemNode?.isHidden = false
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@
|
|||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "MediaPlayer",
|
||||
name: "TelegramMediaPlayer",
|
||||
platforms: [.macOS(.v10_13)],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "MediaPlayer",
|
||||
targets: ["MediaPlayer"]),
|
||||
name: "TelegramMediaPlayer",
|
||||
targets: ["TelegramMediaPlayer"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "TelegramCore", path: "../TelegramCore"),
|
||||
|
|
@ -27,7 +27,7 @@ let package = Package(
|
|||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "MediaPlayer",
|
||||
name: "TelegramMediaPlayer",
|
||||
dependencies: [.product(name: "TelegramCore", package: "TelegramCore", condition: nil),
|
||||
.product(name: "Postbox", package: "Postbox", condition: nil),
|
||||
.product(name: "FFMpegBinding", package: "FFMpegBinding", condition: nil),
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class IncreaseLimitHeaderItemNode: ListViewItemNode {
|
|||
badgeIconName: badgeIconName,
|
||||
badgeText: "\(item.count)",
|
||||
badgePosition: CGFloat(item.count) / CGFloat(item.premiumCount),
|
||||
badgeGraphPosition: CGFloat(item.count) / CGFloat(item.premiumCount),
|
||||
badgeGraphPosition: CGFloat(item.limit) / CGFloat(item.premiumCount),
|
||||
isPremiumDisabled: item.isPremiumDisabled
|
||||
))
|
||||
let containerSize = CGSize(width: layout.size.width - params.leftInset - params.rightInset, height: 200.0)
|
||||
|
|
|
|||
|
|
@ -111,6 +111,8 @@ public func mergedMessageReactionsAndPeers(accountPeerId: EnginePeer.Id, account
|
|||
reactions.insert(MessageReaction(value: .custom(MessageReaction.starsReactionId), count: 1000000, chosenOrder: nil), at: 0)
|
||||
}
|
||||
}
|
||||
#else
|
||||
let reactions = attribute.reactions
|
||||
#endif
|
||||
|
||||
return (reactions, recentPeers)
|
||||
|
|
|
|||
|
|
@ -929,7 +929,11 @@ public final class Network: NSObject, MTRequestMessageServiceDelegate {
|
|||
datacenterId = id
|
||||
isCdn = true
|
||||
}
|
||||
return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground)
|
||||
if datacenterId != 0 {
|
||||
return strongSelf.makeWorker(datacenterId: datacenterId, isCdn: isCdn, isMedia: isMedia, tag: tag, continueInBackground: continueInBackground)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ public struct CachedChannelFlags: OptionSet {
|
|||
public static let translationHidden = CachedChannelFlags(rawValue: 1 << 8)
|
||||
public static let adsRestricted = CachedChannelFlags(rawValue: 1 << 9)
|
||||
public static let canViewRevenue = CachedChannelFlags(rawValue: 1 << 10)
|
||||
public static let paidMediaAllowed = CachedChannelFlags(rawValue: 1 << 11)
|
||||
}
|
||||
|
||||
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {
|
||||
|
|
|
|||
|
|
@ -1096,6 +1096,34 @@ public extension TelegramEngine.EngineData.Item {
|
|||
}
|
||||
}
|
||||
|
||||
public struct PaidMediaAllowed: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Bool
|
||||
|
||||
fileprivate var id: EnginePeer.Id
|
||||
public var mapKey: EnginePeer.Id {
|
||||
return self.id
|
||||
}
|
||||
|
||||
public init(id: EnginePeer.Id) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
var key: PostboxViewKey {
|
||||
return .cachedPeerData(peerId: self.id)
|
||||
}
|
||||
|
||||
func extract(view: PostboxView) -> Result {
|
||||
guard let view = view as? CachedPeerDataView else {
|
||||
preconditionFailure()
|
||||
}
|
||||
if let cachedData = view.cachedPeerData as? CachedChannelData {
|
||||
return cachedData.flags.contains(.paidMediaAllowed)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct BoostsToUnrestrict: TelegramEngineDataItem, TelegramEngineMapKeyDataItem, PostboxViewDataItem {
|
||||
public typealias Result = Int32?
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func _internal_inactiveChannelList(network: Network) -> Signal<[InactiveChannel]
|
|||
}
|
||||
var inactive: [InactiveChannel] = []
|
||||
for (i, channel) in channels.enumerated() {
|
||||
inactive.append(InactiveChannel(peer: channel, lastActivityDate: dates[i], participantsCount: participantsCounts[channel.id]))
|
||||
inactive.append(InactiveChannel(peer: channel, lastActivityDate: i < dates.count ? dates[i] : 0, participantsCount: participantsCounts[channel.id]))
|
||||
}
|
||||
return inactive
|
||||
}
|
||||
|
|
|
|||
|
|
@ -589,6 +589,9 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
|||
if (flags2 & Int32(1 << 12)) != 0 {
|
||||
channelFlags.insert(.canViewRevenue)
|
||||
}
|
||||
if (flags2 & Int32(1 << 14)) != 0 {
|
||||
channelFlags.insert(.paidMediaAllowed)
|
||||
}
|
||||
|
||||
let sendAsPeerId = defaultSendAs?.peerId
|
||||
|
||||
|
|
|
|||
|
|
@ -2948,7 +2948,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
|||
contentSize.height += totalContentNodesHeight
|
||||
|
||||
if let paidContent = item.message.media.first(where: { $0 is TelegramMediaPaidContent }) as? TelegramMediaPaidContent, let media = paidContent.extendedMedia.first {
|
||||
var isLocked = false
|
||||
if case .preview = media {
|
||||
isLocked = true
|
||||
} else if item.presentationData.isPreview {
|
||||
isLocked = true
|
||||
}
|
||||
if isLocked {
|
||||
let sizeAndApply = unlockButtonLayout(ChatMessageUnlockMediaNode.Arguments(
|
||||
presentationData: item.presentationData,
|
||||
strings: item.presentationData.strings,
|
||||
|
|
|
|||
|
|
@ -1140,11 +1140,21 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
|
||||
if let extendedMedia {
|
||||
switch extendedMedia {
|
||||
case let .preview(_, immediateThumbnailData, _):
|
||||
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
||||
media = thumbnailMedia
|
||||
case let .full(fullMedia):
|
||||
case let .preview(_, immediateThumbnailData, _):
|
||||
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
||||
media = thumbnailMedia
|
||||
case let .full(fullMedia):
|
||||
if presentationData.isPreview {
|
||||
if let image = fullMedia as? TelegramMediaImage {
|
||||
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: image.immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
||||
media = thumbnailMedia
|
||||
} else if let video = fullMedia as? TelegramMediaFile {
|
||||
let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: video.immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
|
||||
media = thumbnailMedia
|
||||
}
|
||||
} else {
|
||||
media = fullMedia
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1475,7 +1485,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
|
||||
}
|
||||
}
|
||||
if let extendedMedia, case let .full(fullMedia) = extendedMedia {
|
||||
if let extendedMedia, case let .full(fullMedia) = extendedMedia, !presentationData.isPreview {
|
||||
isExtendedMedia = true
|
||||
media = fullMedia
|
||||
}
|
||||
|
|
@ -2079,7 +2089,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
if let invoice = media as? TelegramMediaInvoice, let selectedMedia = invoice.extendedMedia {
|
||||
extendedMedia = selectedMedia
|
||||
} else if let paidContent = media as? TelegramMediaPaidContent {
|
||||
let selectedMediaIndex = mediaIndex ?? 0
|
||||
let selectedMediaIndex = self.mediaIndex ?? 0
|
||||
if selectedMediaIndex < paidContent.extendedMedia.count {
|
||||
extendedMedia = paidContent.extendedMedia[selectedMediaIndex]
|
||||
}
|
||||
|
|
@ -2366,6 +2376,11 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
icon = .lock
|
||||
}
|
||||
displaySpoiler = true
|
||||
} else if let _ = extendedMedia, isPreview {
|
||||
if let invoice, invoice.currency != "XTR" {
|
||||
icon = .lock
|
||||
}
|
||||
displaySpoiler = true
|
||||
} else if message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute }) {
|
||||
displaySpoiler = true
|
||||
} else if isSecretMedia {
|
||||
|
|
@ -2427,9 +2442,6 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
}
|
||||
}
|
||||
}
|
||||
// if let paidContent {
|
||||
// viewText = "⭐️\(paidContent.amount)"
|
||||
// }
|
||||
self.extendedMediaOverlayNode?.update(size: self.imageNode.frame.size, text: viewText, imageSignal: self.currentBlurredImageSignal, imageFrame: self.imageNode.view.convert(self.imageNode.bounds, to: self.extendedMediaOverlayNode?.view), corners: self.currentImageArguments?.corners)
|
||||
} else if let extendedMediaOverlayNode = self.extendedMediaOverlayNode {
|
||||
self.extendedMediaOverlayNode = nil
|
||||
|
|
|
|||
|
|
@ -186,9 +186,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = selectedMediaIndex
|
||||
|
||||
|
||||
if let extendedMedia, case let .full(media) = extendedMedia {
|
||||
if let telegramImage = media as? TelegramMediaImage {
|
||||
if shouldDownloadMediaAutomatically(settings: item.controllerInteraction.automaticMediaDownloadSettings, peerType: item.associatedData.automaticDownloadPeerType, networkType: item.associatedData.automaticDownloadNetworkType, authorPeerId: item.message.author?.id, contactsPeerIds: item.associatedData.contactsPeerIds, media: telegramImage) {
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ public final class DrawingMessageRenderer {
|
|||
mainRadius: presentationData.chatBubbleCorners.mainRadius,
|
||||
auxiliaryRadius: presentationData.chatBubbleCorners.auxiliaryRadius,
|
||||
mergeBubbleCorners: presentationData.chatBubbleCorners.mergeBubbleCorners,
|
||||
hasTails: false
|
||||
hasTails: !self.isLink
|
||||
)
|
||||
|
||||
let avatarHeaderItem: ListViewItemHeader?
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ swift_library(
|
|||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/UIKitRuntimeUtils",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import SwiftSignalKit
|
|||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import AccountContext
|
||||
import UIKitRuntimeUtils
|
||||
|
||||
private let minimizedNavigationHeight: CGFloat = 44.0
|
||||
private let minimizedTopMargin: CGFloat = 3.0
|
||||
|
|
@ -42,6 +43,8 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
}
|
||||
}
|
||||
|
||||
var isReady = false
|
||||
|
||||
let item: Item
|
||||
private let containerNode: ASDisplayNode
|
||||
private let headerNode: MinimizedHeaderNode
|
||||
|
|
@ -49,6 +52,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
private let shadowNode: ASImageNode
|
||||
|
||||
private var controllerView: UIView?
|
||||
fileprivate let snapshotContainerView = UIView()
|
||||
fileprivate var snapshotView: UIView?
|
||||
fileprivate var blurredSnapshotView: UIView?
|
||||
|
||||
var tapped: (() -> Void)?
|
||||
var highlighted: ((Bool) -> Void)?
|
||||
|
|
@ -87,6 +93,8 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
self.dimCoverNode.backgroundColor = UIColor.black
|
||||
self.dimCoverNode.isUserInteractionEnabled = false
|
||||
|
||||
self.snapshotContainerView.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
|
@ -97,13 +105,21 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
self.shadowNode.image = shadowImage
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
if let snapshotView = self.item.controller.displayNode.view.snapshotView(afterScreenUpdates: false) {
|
||||
self.controllerView = snapshotView
|
||||
self.containerNode.view.addSubview(snapshotView)
|
||||
} else {
|
||||
self.controllerView = self.item.controller.displayNode.view
|
||||
self.containerNode.view.addSubview(self.item.controller.displayNode.view)
|
||||
self.controllerView = self.item.controller.displayNode.view
|
||||
self.containerNode.view.addSubview(self.item.controller.displayNode.view)
|
||||
|
||||
Queue.mainQueue().after(0.45) {
|
||||
self.isReady = true
|
||||
if !self.isDismissed, let snapshotView = self.controllerView?.snapshotView(afterScreenUpdates: false) {
|
||||
self.containerNode.view.addSubview(self.snapshotContainerView)
|
||||
self.snapshotView = snapshotView
|
||||
self.controllerView?.removeFromSuperview()
|
||||
self.controllerView = nil
|
||||
self.snapshotContainerView.addSubview(snapshotView)
|
||||
self.requestLayout(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
self.addSubnode(self.headerNode)
|
||||
self.addSubnode(self.dimCoverNode)
|
||||
self.addSubnode(self.shadowNode)
|
||||
|
|
@ -159,7 +175,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
}
|
||||
|
||||
@objc func tapLongTapOrDoubleTapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) {
|
||||
guard let (_, insets, _) = self.validLayout else {
|
||||
guard let (_, insets, _) = self.validLayout, self.isReady else {
|
||||
return
|
||||
}
|
||||
switch recognizer.state {
|
||||
|
|
@ -181,6 +197,13 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
}
|
||||
}
|
||||
|
||||
private func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
guard let (size, insets, isExpanded) = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.updateLayout(size: size, insets: insets, isExpanded: isExpanded, transition: transition)
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, insets: UIEdgeInsets, isExpanded: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (size, insets, isExpanded)
|
||||
|
||||
|
|
@ -189,7 +212,11 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
topInset += 10.0
|
||||
}
|
||||
self.containerNode.frame = CGRect(origin: .zero, size: size)
|
||||
self.containerNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -topInset, 0.0)
|
||||
if let _ = self.item.controller.minimizedTopEdgeOffset {
|
||||
self.containerNode.subnodeTransform = CATransform3DMakeTranslation(0.0, -topInset, 0.0)
|
||||
}
|
||||
|
||||
self.snapshotContainerView.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
self.shadowNode.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.height - topInset))
|
||||
|
||||
|
|
@ -203,9 +230,49 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
transition.updateFrame(node: self.headerNode, frame: headerFrame)
|
||||
transition.updateFrame(node: self.dimCoverNode, frame: CGRect(origin: .zero, size: size))
|
||||
|
||||
if let controllerView = self.controllerView {
|
||||
let controllerFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - controllerView.bounds.size.width) / 2.0), y: 0.0), size: controllerView.bounds.size)
|
||||
transition.updateFrame(view: controllerView, frame: controllerFrame)
|
||||
if let snapshotView = self.snapshotView {
|
||||
var snapshotFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - snapshotView.bounds.size.width) / 2.0), y: 0.0), size: snapshotView.bounds.size)
|
||||
|
||||
var requiresBlur = false
|
||||
var blurFrame = snapshotFrame
|
||||
if snapshotView.frame.width * 1.1 < size.width {
|
||||
if let _ = self.item.controller.minimizedTopEdgeOffset {
|
||||
snapshotFrame = snapshotFrame.offsetBy(dx: 0.0, dy: -66.0)
|
||||
}
|
||||
blurFrame = CGRect(origin: CGPoint(x: 0.0, y: snapshotFrame.minY), size: CGSize(width: size.width, height: snapshotFrame.height))
|
||||
requiresBlur = true
|
||||
} else if snapshotView.frame.width > size.width * 1.5 {
|
||||
if let _ = self.item.controller.minimizedTopEdgeOffset {
|
||||
snapshotFrame = snapshotFrame.offsetBy(dx: 0.0, dy: 66.0)
|
||||
}
|
||||
blurFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - snapshotView.frame.width) / 2.0), y: snapshotFrame.minY), size: CGSize(width: snapshotFrame.width, height: size.height))
|
||||
requiresBlur = true
|
||||
}
|
||||
|
||||
if requiresBlur {
|
||||
let blurredSnapshotView: UIView?
|
||||
if let current = self.blurredSnapshotView {
|
||||
blurredSnapshotView = current
|
||||
} else {
|
||||
blurredSnapshotView = snapshotView.snapshotView(afterScreenUpdates: false)
|
||||
if let blurredSnapshotView {
|
||||
if let blurFilter = makeBlurFilter() {
|
||||
blurFilter.setValue(20.0 as NSNumber, forKey: "inputRadius")
|
||||
blurFilter.setValue(true as NSNumber, forKey: "inputNormalizeEdges")
|
||||
blurredSnapshotView.layer.filters = [blurFilter]
|
||||
}
|
||||
self.snapshotContainerView.insertSubview(blurredSnapshotView, at: 0)
|
||||
self.blurredSnapshotView = blurredSnapshotView
|
||||
}
|
||||
}
|
||||
blurredSnapshotView?.frame = blurFrame
|
||||
} else if let blurredSnapshotView = self.blurredSnapshotView {
|
||||
self.blurredSnapshotView = nil
|
||||
blurredSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
blurredSnapshotView.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
transition.updateFrame(view: snapshotView, frame: snapshotFrame)
|
||||
}
|
||||
|
||||
if !self.isDismissed {
|
||||
|
|
@ -214,14 +281,14 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private weak var navigationController: NavigationController?
|
||||
private let sharedContext: SharedAccountContext
|
||||
public weak var navigationController: NavigationController?
|
||||
private var items: [Item] = []
|
||||
|
||||
private var presentationData: PresentationData
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
var isExpanded: Bool = false
|
||||
public private(set) var isExpanded: Bool = false
|
||||
public var willMaximize: (() -> Void)?
|
||||
|
||||
private let bottomEdgeView: UIImageView
|
||||
|
|
@ -239,10 +306,13 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
private var isApplyingTransition = false
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
public init(context: AccountContext, navigationController: NavigationController) {
|
||||
self.context = context
|
||||
self.navigationController = navigationController
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
public var controllers: [ViewController] {
|
||||
return self.items.map { $0.controller }
|
||||
}
|
||||
|
||||
public init(sharedContext: SharedAccountContext) {
|
||||
self.sharedContext = sharedContext
|
||||
self.presentationData = sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.bottomEdgeView = UIImageView()
|
||||
self.bottomEdgeView.contentMode = .scaleToFill
|
||||
|
|
@ -275,7 +345,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
self.view.addSubview(self.dimView)
|
||||
self.view.addSubview(self.scrollView)
|
||||
|
||||
self.presentationDataDisposable = (self.context.sharedContext.presentationData
|
||||
self.presentationDataDisposable = (self.sharedContext.presentationData
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] presentationData in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -340,6 +410,9 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
}
|
||||
|
||||
@objc func expandPanGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
|
||||
guard let lastItem = self.items.last, let itemNode = self.itemNodes[lastItem.id], itemNode.isReady else {
|
||||
return
|
||||
}
|
||||
let translation = gestureRecognizer.translation(in: self.view)
|
||||
if translation.y < -10.0 {
|
||||
gestureRecognizer.isEnabled = false
|
||||
|
|
@ -426,6 +499,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
case maximize(itemId: AnyHashable)
|
||||
case dismiss(itemId: AnyHashable)
|
||||
case dismissAll
|
||||
case collapse
|
||||
|
||||
func matches(item: Item) -> Bool {
|
||||
switch self {
|
||||
|
|
@ -435,6 +509,8 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
return item.id == itemId
|
||||
case .dismissAll:
|
||||
return true
|
||||
case .collapse:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -472,7 +548,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
}
|
||||
|
||||
public func expand() {
|
||||
guard !self.items.isEmpty && !self.isExpanded else {
|
||||
guard !self.items.isEmpty && !self.isExpanded && self.currentTransition == nil else {
|
||||
return
|
||||
}
|
||||
if self.items.count == 1, let item = self.items.first {
|
||||
|
|
@ -482,12 +558,33 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
public func collapse() {
|
||||
self.isExpanded = false
|
||||
self.currentTransition = .collapse
|
||||
self.requestUpdate(transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
guard self.isExpanded else {
|
||||
guard self.isExpanded, let layout = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.requestUpdate(transition: .immediate)
|
||||
|
||||
let contentOffset = scrollView.contentOffset
|
||||
if scrollView.contentOffset.y < -64.0, let lastItemId = self.items.last?.id, let itemNode = self.itemNodes[lastItemId] {
|
||||
let velocity = scrollView.panGestureRecognizer.velocity(in: self.view).y
|
||||
let distance = layout.size.height - self.collapsedHeight(layout: layout) - itemNode.frame.minY
|
||||
let initialVelocity = distance != 0.0 ? abs(velocity / distance) : 0.0
|
||||
|
||||
self.isExpanded = false
|
||||
scrollView.isScrollEnabled = false
|
||||
scrollView.panGestureRecognizer.isEnabled = false
|
||||
scrollView.panGestureRecognizer.isEnabled = true
|
||||
scrollView.contentOffset = contentOffset
|
||||
self.currentTransition = .collapse
|
||||
self.requestUpdate(transition: .animated(duration: 0.4, curve: .customSpring(damping: 180.0, initialVelocity: initialVelocity)))
|
||||
}
|
||||
}
|
||||
|
||||
private func requestUpdate(transition: ContainedViewLayoutTransition, completion: @escaping (Transition) -> Void = { _ in }) {
|
||||
|
|
@ -692,7 +789,7 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
itemFrame = effectiveItemFrame
|
||||
itemTransform = effectiveItemTransform
|
||||
|
||||
itemNode.isCovered = index == self.items.count - 2
|
||||
itemNode.isCovered = index <= self.items.count - 2
|
||||
}
|
||||
|
||||
itemNode.bounds = CGRect(origin: .zero, size: itemFrame.size)
|
||||
|
|
@ -739,7 +836,10 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
|
||||
itemNode.animateIn()
|
||||
|
||||
var initialOffset = insets.top + itemNode.item.controller.minimizedTopEdgeOffset
|
||||
var initialOffset = insets.top
|
||||
if let minimizedTopEdgeOffset = itemNode.item.controller.minimizedTopEdgeOffset {
|
||||
initialOffset += minimizedTopEdgeOffset
|
||||
}
|
||||
if layout.size.width < layout.size.height {
|
||||
initialOffset += 10.0
|
||||
}
|
||||
|
|
@ -767,13 +867,31 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
|
||||
itemNode.animateOut()
|
||||
if itemInsets.left > 0.0 {
|
||||
itemNode.updateLayout(size: layout.size, insets: itemInsets, isExpanded: true, transition: transition)
|
||||
transition.updateBounds(node: itemNode, bounds: CGRect(origin: .zero, size: layout.size))
|
||||
}
|
||||
transition.updateTransform(node: itemNode, transform: CATransform3DIdentity)
|
||||
transition.updatePosition(node: itemNode, position: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0 + topInset + self.scrollView.contentOffset.y), completion: { _ in
|
||||
self.isApplyingTransition = false
|
||||
if self.currentTransition == currentTransition {
|
||||
self.currentTransition = nil
|
||||
}
|
||||
|
||||
completion(currentTransition)
|
||||
|
||||
if let _ = itemNode.snapshotView {
|
||||
let snapshotContainerView = itemNode.snapshotContainerView
|
||||
snapshotContainerView.layer.allowsGroupOpacity = true
|
||||
snapshotContainerView.center = CGPoint(x: itemNode.item.controller.displayNode.view.bounds.width / 2.0, y: snapshotContainerView.bounds.height / 2.0)
|
||||
itemNode.item.controller.displayNode.view.addSubview(snapshotContainerView)
|
||||
Queue.mainQueue().after(0.15, {
|
||||
snapshotContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
snapshotContainerView.removeFromSuperview()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
self.itemNodes[itemId] = nil
|
||||
itemNode.removeFromSupernode()
|
||||
dimView.removeFromSuperview()
|
||||
|
|
@ -833,6 +951,14 @@ public class MinimizedContainerImpl: ASDisplayNode, MinimizedContainer, ASScroll
|
|||
completion(currentTransition)
|
||||
})
|
||||
transition.updatePosition(layer: self.scrollView.layer, position: self.scrollView.center.offsetBy(dx: 0.0, dy: dismissOffset))
|
||||
case .collapse:
|
||||
transition.updateBounds(layer: self.scrollView.layer, bounds: CGRect(origin: .zero, size: self.scrollView.bounds.size), completion: { _ in
|
||||
self.isApplyingTransition = false
|
||||
if self.currentTransition == currentTransition {
|
||||
self.currentTransition = nil
|
||||
}
|
||||
completion(currentTransition)
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ import SwiftSignalKit
|
|||
import TelegramPresentationData
|
||||
|
||||
final class MinimizedHeaderNode: ASDisplayNode {
|
||||
var theme: NavigationControllerTheme
|
||||
var theme: NavigationControllerTheme {
|
||||
didSet {
|
||||
self.minimizedBackgroundNode.backgroundColor = self.theme.navigationBar.opaqueBackgroundColor
|
||||
self.minimizedCloseButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Close"), color: self.theme.navigationBar.primaryTextColor), for: .normal)
|
||||
}
|
||||
}
|
||||
let strings: PresentationStrings
|
||||
|
||||
private let minimizedBackgroundNode: ASDisplayNode
|
||||
|
|
@ -83,7 +88,7 @@ final class MinimizedHeaderNode: ASDisplayNode {
|
|||
self.minimizedTitleNode = ImmediateTextNode()
|
||||
|
||||
self.minimizedCloseButton = HighlightableButtonNode()
|
||||
self.minimizedCloseButton.setImage(UIImage(bundleImageName: "Instant View/Close"), for: .normal)
|
||||
self.minimizedCloseButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Instant View/Close"), color: self.theme.navigationBar.primaryTextColor), for: .normal)
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
|
|||
|
|
@ -5296,6 +5296,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
if let navigationController = controller.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||
for controller in minimizedContainer.controllers {
|
||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == bot.peer.id && mainController.source == .settings {
|
||||
navigationController.maximizeViewController(controller, animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = self.presentationData
|
||||
let proceed: (Bool) -> Void = { [weak self] installed in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ private struct OldChannelsState: Equatable {
|
|||
private func oldChannelsEntries(presentationData: PresentationData, state: OldChannelsState, isPremium: Bool, isPremiumDisabled: Bool, limit: Int32, premiumLimit: Int32, peers: [InactiveChannel]?, intent: OldChannelsControllerIntent) -> [OldChannelsEntry] {
|
||||
var entries: [OldChannelsEntry] = []
|
||||
|
||||
let count = max(limit, Int32(peers?.count ?? 0))
|
||||
let count = max(isPremium ? premiumLimit : limit, Int32(peers?.count ?? 0))
|
||||
var text: String?
|
||||
if count >= premiumLimit {
|
||||
switch intent {
|
||||
|
|
@ -359,7 +359,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
|
|||
}
|
||||
|
||||
let footerItem: IncreaseLimitFooterItem?
|
||||
if (state.isSearching || premiumConfiguration.isPremiumDisabled) && state.selectedPeers.count == 0 {
|
||||
if (state.isSearching || premiumConfiguration.isPremiumDisabled || isPremium) && state.selectedPeers.count == 0 {
|
||||
footerItem = nil
|
||||
} else {
|
||||
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: buttonText, colorful: colorful, action: {
|
||||
|
|
|
|||
|
|
@ -347,6 +347,7 @@ public final class StarsImageComponent: Component {
|
|||
private var dustNode: MediaDustNode?
|
||||
private var button: UIControl?
|
||||
|
||||
private var lockView: UIImageView?
|
||||
private var countView = ComponentView<Empty>()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
|
@ -641,6 +642,19 @@ public final class StarsImageComponent: Component {
|
|||
imageFrameNode.frame = imageFrame.insetBy(dx: -2.0, dy: -2.0)
|
||||
}
|
||||
|
||||
var totalLabelWidth: CGFloat = 0.0
|
||||
let labelSpacing: CGFloat = 4.0
|
||||
let lockView: UIImageView
|
||||
if let current = self.lockView {
|
||||
lockView = current
|
||||
} else {
|
||||
lockView = UIImageView(image: UIImage(bundleImageName: "Premium/Stars/MediaLock"))
|
||||
containerNode.view.addSubview(lockView)
|
||||
}
|
||||
if let icon = lockView.image {
|
||||
totalLabelWidth += icon.size.width
|
||||
}
|
||||
|
||||
if extendedMedia.count > 1 {
|
||||
let countSize = self.countView.update(
|
||||
transition: .immediate,
|
||||
|
|
@ -650,7 +664,9 @@ public final class StarsImageComponent: Component {
|
|||
environment: {},
|
||||
containerSize: imageFrame.size
|
||||
)
|
||||
let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - countSize.width) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - countSize.height) / 2.0)), size: countSize)
|
||||
let iconWidth = totalLabelWidth
|
||||
totalLabelWidth += countSize.width + labelSpacing
|
||||
let countFrame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - totalLabelWidth) / 2.0) + iconWidth + labelSpacing, y: imageFrame.minY + floorToScreenPixels((imageFrame.height - countSize.height) / 2.0)), size: countSize)
|
||||
if let countView = self.countView.view {
|
||||
if countView.superview == nil {
|
||||
containerNode.view.addSubview(countView)
|
||||
|
|
@ -658,6 +674,8 @@ public final class StarsImageComponent: Component {
|
|||
countView.frame = countFrame
|
||||
}
|
||||
}
|
||||
|
||||
lockView.frame = CGRect(origin: CGPoint(x: imageFrame.minX + floorToScreenPixels((imageFrame.width - totalLabelWidth) / 2.0), y: imageFrame.minY + floorToScreenPixels((imageFrame.height - lockView.bounds.height) / 2.0)), size: lockView.bounds.size)
|
||||
case let .transactionPeer(peer):
|
||||
if case let .peer(peer) = peer {
|
||||
let avatarNode: ImageNode
|
||||
|
|
|
|||
|
|
@ -129,6 +129,14 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
minAmount = 1
|
||||
maxAmount = configuration.maxPaidMediaAmount
|
||||
case .reaction:
|
||||
titleString = environment.strings.Stars_SendStars_Title
|
||||
amountTitle = environment.strings.Stars_SendStars_AmountTitle
|
||||
amountPlaceholder = environment.strings.Stars_SendStars_AmountPlaceholder
|
||||
|
||||
minAmount = 1
|
||||
//TODO:
|
||||
maxAmount = configuration.maxPaidMediaAmount
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
|
|
@ -142,7 +150,16 @@ private final class SheetContent: CombinedComponent {
|
|||
contentSize.height += title.size.height
|
||||
contentSize.height += 40.0
|
||||
|
||||
if case let .withdraw(starsState) = component.mode {
|
||||
let balance: Int64?
|
||||
if case .reaction = component.mode {
|
||||
balance = state.balance
|
||||
} else if case let .withdraw(starsState) = component.mode {
|
||||
balance = starsState.balances.availableBalance
|
||||
} else {
|
||||
balance = nil
|
||||
}
|
||||
|
||||
if let balance {
|
||||
let balanceTitle = balanceTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
|
|
@ -158,7 +175,7 @@ private final class SheetContent: CombinedComponent {
|
|||
let balanceValue = balanceValue.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: presentationStringsFormattedNumber(Int32(starsState.balances.availableBalance), environment.dateTimeFormat.groupingSeparator),
|
||||
string: presentationStringsFormattedNumber(Int32(balance), environment.dateTimeFormat.groupingSeparator),
|
||||
font: Font.semibold(16.0),
|
||||
textColor: theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
|
|
@ -185,17 +202,18 @@ private final class SheetContent: CombinedComponent {
|
|||
)
|
||||
}
|
||||
|
||||
let amountFont = Font.regular(13.0)
|
||||
let amountTextColor = theme.list.freeTextColor
|
||||
let amountMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), bold: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), link: MarkdownAttributeSet(font: amountFont, textColor: theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
|
||||
}
|
||||
let amountFooter: AnyComponent<Empty>?
|
||||
if case .paidMedia = component.mode {
|
||||
let amountFont = Font.regular(13.0)
|
||||
let amountTextColor = theme.list.freeTextColor
|
||||
let amountMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), bold: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), link: MarkdownAttributeSet(font: amountFont, textColor: theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
switch component.mode {
|
||||
case .paidMedia:
|
||||
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_PaidContent_AmountInfo, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
|
||||
}
|
||||
if let range = amountInfoString.string.range(of: ">"), let chevronImage = state.cachedChevronImage?.0 {
|
||||
amountInfoString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: amountInfoString.string))
|
||||
}
|
||||
|
|
@ -214,7 +232,13 @@ private final class SheetContent: CombinedComponent {
|
|||
component.context.sharedContext.openExternalUrl(context: component.context, urlContext: .generic, url: strings.Stars_PaidContent_AmountInfo_URL, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
|
||||
}
|
||||
))
|
||||
} else {
|
||||
case let .reaction(starsToTop):
|
||||
let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendStars_AmountInfo("\(starsToTop ?? 0)").string, attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
amountFooter = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(amountInfoString),
|
||||
maximumNumberOfLines: 0
|
||||
))
|
||||
default:
|
||||
amountFooter = nil
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +320,7 @@ private final class SheetContent: CombinedComponent {
|
|||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(buttonAttributedString)))
|
||||
),
|
||||
isEnabled: true,
|
||||
isEnabled: (state.amount ?? 0) > 0,
|
||||
displaysProgress: false,
|
||||
action: { [weak state] in
|
||||
if let controller = controller() as? StarsWithdrawScreen, let amount = state?.amount {
|
||||
|
|
@ -328,33 +352,56 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
final class State: ComponentState {
|
||||
private let context: AccountContext
|
||||
private let mode: StarsWithdrawScreen.Mode
|
||||
|
||||
fileprivate var amount: Int64?
|
||||
|
||||
fileprivate var balance: Int64?
|
||||
private var stateDisposable: Disposable?
|
||||
|
||||
var cachedCloseImage: (UIImage, PresentationTheme)?
|
||||
var cachedStarImage: (UIImage, PresentationTheme)?
|
||||
var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
amount: Int64?
|
||||
mode: StarsWithdrawScreen.Mode
|
||||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
|
||||
var amount: Int64?
|
||||
switch mode {
|
||||
case let .withdraw(stats):
|
||||
amount = stats.balances.availableBalance
|
||||
case let .paidMedia(initialValue):
|
||||
amount = initialValue
|
||||
case .reaction:
|
||||
amount = nil
|
||||
}
|
||||
|
||||
self.amount = amount
|
||||
|
||||
super.init()
|
||||
|
||||
if case .reaction = self.mode, let starsContext = context.starsContext {
|
||||
self.stateDisposable = (starsContext.state
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] state in
|
||||
if let self, let balance = state?.balance {
|
||||
self.balance = balance
|
||||
self.updated()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.stateDisposable?.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
func makeState() -> State {
|
||||
var amount: Int64?
|
||||
switch self.mode {
|
||||
case let .withdraw(stats):
|
||||
amount = stats.balances.availableBalance
|
||||
case let .paidMedia(initialValue):
|
||||
amount = initialValue
|
||||
}
|
||||
return State(context: self.context, amount: amount)
|
||||
return State(context: self.context, mode: self.mode)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -449,6 +496,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||
public enum Mode: Equatable {
|
||||
case withdraw(StarsRevenueStats)
|
||||
case paidMedia(Int64?)
|
||||
case reaction(Int64?)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
|
|
@ -638,12 +686,11 @@ private final class AmountFieldComponent: Component {
|
|||
|
||||
if let component = self.component {
|
||||
let amount: Int64?
|
||||
if !newText.isEmpty, let value = Int64(newText) {
|
||||
if !newText.isEmpty, let value = Int64(normalizeArabicNumeralString(newText, type: .western)) {
|
||||
amount = value
|
||||
} else {
|
||||
amount = nil
|
||||
}
|
||||
|
||||
if let amount, let maxAmount = component.maxValue, amount > maxAmount {
|
||||
textField.text = "\(maxAmount)"
|
||||
self.textChanged(self.textField)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
|
||||
final class MediaAreaMaskLayer: CALayer {
|
||||
private var params: (referenceSize: CGSize, mediaAreas: [MediaArea])?
|
||||
|
||||
func update(referenceSize: CGSize, mediaAreas: [MediaArea], borderMaskLayer: CALayer?) {
|
||||
guard referenceSize != self.params?.referenceSize && mediaAreas != self.params?.mediaAreas else {
|
||||
return
|
||||
}
|
||||
|
||||
for mediaArea in mediaAreas {
|
||||
let size = CGSize(width: mediaArea.coordinates.width / 100.0 * referenceSize.width, height: mediaArea.coordinates.height / 100.0 * referenceSize.height)
|
||||
let position = CGPoint(x: mediaArea.coordinates.x / 100.0 * referenceSize.width, y: mediaArea.coordinates.y / 100.0 * referenceSize.height)
|
||||
let cornerRadius: CGFloat
|
||||
if let radius = mediaArea.coordinates.cornerRadius {
|
||||
cornerRadius = radius / 100.0 * size.width
|
||||
} else {
|
||||
cornerRadius = size.height * 0.18
|
||||
}
|
||||
|
||||
let layer = CALayer()
|
||||
layer.backgroundColor = UIColor.white.cgColor
|
||||
layer.bounds = CGRect(origin: .zero, size: size)
|
||||
layer.position = position
|
||||
layer.cornerRadius = cornerRadius
|
||||
layer.transform = CATransform3DMakeRotation(mediaArea.coordinates.rotation * Double.pi / 180.0, 0.0, 0.0, 1.0)
|
||||
self.addSublayer(layer)
|
||||
|
||||
if let borderMaskLayer {
|
||||
let borderLayer = CAShapeLayer()
|
||||
borderLayer.strokeColor = UIColor.white.cgColor
|
||||
borderLayer.fillColor = UIColor.clear.cgColor
|
||||
borderLayer.lineWidth = 2.0
|
||||
borderLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
|
||||
borderLayer.bounds = CGRect(origin: .zero, size: size)
|
||||
borderLayer.position = position
|
||||
borderLayer.transform = layer.transform
|
||||
borderMaskLayer.addSublayer(borderLayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -917,7 +917,18 @@ final class StoryItemContentComponent: Component {
|
|||
mediaAreasEffectView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
if !component.item.mediaAreas.isEmpty {
|
||||
|
||||
let shimmeringMediaAreas: [MediaArea] = component.item.mediaAreas.filter { mediaArea in
|
||||
if case .link = mediaArea {
|
||||
return true
|
||||
} else if case .venue = mediaArea {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if !shimmeringMediaAreas.isEmpty {
|
||||
let mediaAreasEffectView: StoryItemLoadingEffectView
|
||||
if let current = self.mediaAreasEffectView {
|
||||
mediaAreasEffectView = current
|
||||
|
|
@ -928,44 +939,14 @@ final class StoryItemContentComponent: Component {
|
|||
}
|
||||
mediaAreasEffectView.update(size: availableSize, transition: transition)
|
||||
|
||||
let maskLayer: CALayer
|
||||
if let current = mediaAreasEffectView.layer.mask {
|
||||
let maskLayer: MediaAreaMaskLayer
|
||||
if let current = mediaAreasEffectView.layer.mask as? MediaAreaMaskLayer {
|
||||
maskLayer = current
|
||||
} else {
|
||||
maskLayer = CALayer()
|
||||
maskLayer = MediaAreaMaskLayer()
|
||||
mediaAreasEffectView.layer.mask = maskLayer
|
||||
}
|
||||
|
||||
if (maskLayer.sublayers ?? []).isEmpty {
|
||||
let referenceSize = availableSize
|
||||
for mediaArea in component.item.mediaAreas {
|
||||
guard case .venue = mediaArea else {
|
||||
continue
|
||||
}
|
||||
let size = CGSize(width: mediaArea.coordinates.width / 100.0 * referenceSize.width, height: mediaArea.coordinates.height / 100.0 * referenceSize.height)
|
||||
let position = CGPoint(x: mediaArea.coordinates.x / 100.0 * referenceSize.width, y: mediaArea.coordinates.y / 100.0 * referenceSize.height)
|
||||
let cornerRadius = size.height * 0.18
|
||||
|
||||
let layer = CALayer()
|
||||
layer.backgroundColor = UIColor.white.cgColor
|
||||
layer.bounds = CGRect(origin: .zero, size: size)
|
||||
layer.position = position
|
||||
layer.cornerRadius = cornerRadius
|
||||
maskLayer.addSublayer(layer)
|
||||
|
||||
let borderLayer = CAShapeLayer()
|
||||
borderLayer.strokeColor = UIColor.white.cgColor
|
||||
borderLayer.fillColor = UIColor.clear.cgColor
|
||||
borderLayer.lineWidth = 2.0
|
||||
borderLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
|
||||
borderLayer.bounds = CGRect(origin: .zero, size: size)
|
||||
borderLayer.position = position
|
||||
mediaAreasEffectView.borderMaskLayer.addSublayer(borderLayer)
|
||||
|
||||
layer.transform = CATransform3DMakeRotation(mediaArea.coordinates.rotation * Double.pi / 180.0, 0.0, 0.0, 1.0)
|
||||
borderLayer.transform = layer.transform
|
||||
}
|
||||
}
|
||||
maskLayer.update(referenceSize: availableSize, mediaAreas: shimmeringMediaAreas, borderMaskLayer: mediaAreasEffectView.borderMaskLayer)
|
||||
} else if let mediaAreasEffectView = self.mediaAreasEffectView {
|
||||
self.mediaAreasEffectView = nil
|
||||
mediaAreasEffectView.removeFromSuperview()
|
||||
|
|
|
|||
|
|
@ -913,9 +913,6 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state, let component = self.component, let itemLayout = self.itemLayout {
|
||||
|
||||
if let _ = self.sendMessageContext.menuController {
|
||||
return
|
||||
}
|
||||
if self.displayLikeReactions {
|
||||
self.displayLikeReactions = false
|
||||
self.sendMessageContext.currentInputMode = .text
|
||||
|
|
|
|||
|
|
@ -3347,32 +3347,7 @@ final class StoryItemSetContainerSendMessage {
|
|||
var actions: [ContextMenuAction] = []
|
||||
switch mediaArea {
|
||||
case let .venue(coordinates, venue):
|
||||
let action = { [weak controller, weak view] in
|
||||
let _ = view
|
||||
/*let subject = EngineMessage(stableId: 0, stableVersion: 0, id: EngineMessage.Id(peerId: PeerId(0), namespace: 0, id: 0), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: [.geo(TelegramMediaMap(latitude: venue.latitude, longitude: venue.longitude, heading: nil, accuracyRadius: nil, geoPlace: nil, venue: venue.venue, liveBroadcastingTimeout: nil, liveProximityNotificationRadius: nil))], peers: [:], associatedMessages: [:], associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
let locationController = LocationViewController(
|
||||
context: context,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
subject: subject,
|
||||
isStoryLocation: true,
|
||||
params: LocationViewParams(
|
||||
sendLiveLocation: { _ in },
|
||||
stopLiveLocation: { _ in },
|
||||
openUrl: { url in
|
||||
context.sharedContext.applicationBindings.openUrl(url)
|
||||
},
|
||||
openPeer: { _ in }
|
||||
)
|
||||
)
|
||||
view?.updateModalTransitionFactor(1.0, transition: .animated(duration: 0.5, curve: .spring))
|
||||
locationController.dismissed = { [weak view] in
|
||||
view?.updateModalTransitionFactor(0.0, transition: .animated(duration: 0.5, curve: .spring))
|
||||
Queue.mainQueue().after(0.5, {
|
||||
view?.updateIsProgressPaused()
|
||||
})
|
||||
}
|
||||
controller?.push(locationController)*/
|
||||
|
||||
let action = { [weak controller] in
|
||||
let searchController = context.sharedContext.makeStorySearchController(context: context, scope: .location(coordinates: coordinates, venue: venue), listContext: nil)
|
||||
controller?.push(searchController)
|
||||
}
|
||||
|
|
@ -3475,12 +3450,16 @@ final class StoryItemSetContainerSendMessage {
|
|||
let node = controller.displayNode
|
||||
let menuController = makeContextMenuController(actions: actions, blurred: true)
|
||||
menuController.centerHorizontally = true
|
||||
menuController.dismissed = { [weak self, weak view] in
|
||||
menuController.dismissed = { [weak self, weak view, weak menuController] in
|
||||
if let self, let view {
|
||||
self.selectedMediaArea = nil
|
||||
Queue.mainQueue().after(0.1) {
|
||||
self.menuController = nil
|
||||
view.updateIsProgressPaused()
|
||||
if self.menuController === menuController {
|
||||
self.selectedMediaArea = nil
|
||||
Queue.mainQueue().after(0.1) {
|
||||
if self.menuController === menuController {
|
||||
self.menuController = nil
|
||||
view.updateIsProgressPaused()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lockmedia.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/lockmedia.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/lockmedia.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -178,6 +178,7 @@ final class SharedApplicationContext {
|
|||
let notificationManager: SharedNotificationManager
|
||||
let wakeupManager: SharedWakeupManager
|
||||
let overlayMediaController: ViewController & OverlayMediaController
|
||||
var minimizedContainer: MinimizedContainer?
|
||||
|
||||
init(sharedContext: SharedAccountContextImpl, notificationManager: SharedNotificationManager, wakeupManager: SharedWakeupManager) {
|
||||
self.sharedContext = sharedContext
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import ChatListUI
|
|||
import StoryContainerScreen
|
||||
import ChatMessageNotificationItem
|
||||
import PhoneNumberFormat
|
||||
import AttachmentUI
|
||||
|
||||
final class UnauthorizedApplicationContext {
|
||||
let sharedContext: SharedAccountContextImpl
|
||||
|
|
@ -168,6 +169,10 @@ final class AuthorizedApplicationContext {
|
|||
self.notificationController = NotificationContainerController(context: context)
|
||||
|
||||
self.rootController = TelegramRootController(context: context)
|
||||
self.rootController.minimizedContainer = self.sharedApplicationContext.minimizedContainer
|
||||
self.rootController.minimizedContainerUpdated = { [weak self] minimizedContainer in
|
||||
self?.sharedApplicationContext.minimizedContainer = minimizedContainer
|
||||
}
|
||||
|
||||
self.rootController.globalOverlayControllersUpdated = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -432,6 +437,10 @@ final class AuthorizedApplicationContext {
|
|||
return false
|
||||
}
|
||||
|
||||
if let topContoller = strongSelf.rootController.topViewController as? AttachmentController {
|
||||
topContoller.minimizeIfNeeded()
|
||||
}
|
||||
|
||||
for controller in strongSelf.rootController.viewControllers {
|
||||
if let controller = controller as? ChatControllerImpl, controller.chatLocation.peerId == chatLocation.peerId, (controller.chatLocation.threadId == nil || controller.chatLocation.threadId == chatLocation.threadId) {
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -0,0 +1,253 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import ChatPresentationInterfaceState
|
||||
import ChatControllerInteraction
|
||||
import WebUI
|
||||
import AttachmentUI
|
||||
import AccountContext
|
||||
import TelegramNotices
|
||||
import PresentationDataUtils
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func openWebApp(buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource) {
|
||||
guard let peerId = self.chatLocation.peerId, let peer = self.presentationInterfaceState.renderedPeer?.peer else {
|
||||
return
|
||||
}
|
||||
self.chatDisplayNode.dismissInput()
|
||||
|
||||
let botName: String
|
||||
let botAddress: String
|
||||
if case let .inline(bot) = source {
|
||||
botName = bot.compactDisplayTitle
|
||||
botAddress = bot.addressName ?? ""
|
||||
} else {
|
||||
botName = EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||
botAddress = peer.addressName ?? ""
|
||||
}
|
||||
|
||||
if source == .generic {
|
||||
self.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if !$0.contains(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.append(.requestInProgress)
|
||||
return updatedContexts.sorted()
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let updateProgress = { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if let index = $0.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let openWebView = {
|
||||
if source == .menu {
|
||||
self.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(true)
|
||||
// return state.updatedShowWebView(true).updatedForceInputCommandsHidden(true)
|
||||
}
|
||||
|
||||
if let currentMenuWebAppController = self.currentMenuWebAppController {
|
||||
if currentMenuWebAppController.isMinimized {
|
||||
(currentMenuWebAppController.navigationController as? NavigationController)?.maximizeViewController(currentMenuWebAppController, animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let navigationController = self.navigationController as? NavigationController, let minimizedContainer = navigationController.minimizedContainer {
|
||||
for controller in minimizedContainer.controllers {
|
||||
if let controller = controller as? AttachmentController, let mainController = controller.mainController as? WebAppController, mainController.botId == peerId && mainController.source == .menu {
|
||||
navigationController.maximizeViewController(controller, animated: true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let params = WebAppParameters(source: .menu, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false)
|
||||
let controller = standaloneWebAppController(context: self.context, updatedPresentationData: self.updatedPresentationData, params: params, threadId: self.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
|
||||
self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
|
||||
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
|
||||
if let strongSelf = self {
|
||||
if let chatTypes {
|
||||
let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
|
||||
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
||||
if let strongSelf = self {
|
||||
completion()
|
||||
controller?.dismiss()
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
} else {
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
}, getInputContainerNode: { [weak self] in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout, case .compact = layout.metrics.widthClass {
|
||||
return (strongSelf.chatDisplayNode.getWindowInputAccessoryHeight(), strongSelf.chatDisplayNode.inputPanelContainerNode, {
|
||||
return strongSelf.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil)
|
||||
})
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, completion: { [weak self] in
|
||||
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}, willDismiss: { [weak self] in
|
||||
self?.interfaceInteraction?.updateShowWebView { _ in
|
||||
return false
|
||||
}
|
||||
}, didDismiss: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let isFocused = strongSelf.chatDisplayNode.textInputPanelNode?.isFocused ?? false
|
||||
strongSelf.chatDisplayNode.insertSubnode(strongSelf.chatDisplayNode.inputPanelContainerNode, aboveSubnode: strongSelf.chatDisplayNode.inputContextPanelContainer)
|
||||
if isFocused {
|
||||
strongSelf.chatDisplayNode.textInputPanelNode?.ensureFocused()
|
||||
}
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(false)
|
||||
}
|
||||
}
|
||||
}, getNavigationController: { [weak self] in
|
||||
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
self.push(controller)
|
||||
self.currentMenuWebAppController = controller
|
||||
} else if simple {
|
||||
var isInline = false
|
||||
var botId = peerId
|
||||
var botName = botName
|
||||
var botAddress = ""
|
||||
if case let .inline(bot) = source {
|
||||
isInline = true
|
||||
botId = bot.id
|
||||
botName = bot.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
|
||||
botAddress = bot.addressName ?? ""
|
||||
}
|
||||
|
||||
self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: isInline ? .inline : .generic, themeParams: generateWebAppThemeParams(self.presentationData.theme))
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] url in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: isInline ? .inline : .simple, peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false)
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
|
||||
self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
|
||||
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
|
||||
if let strongSelf = self {
|
||||
if let chatTypes {
|
||||
let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
|
||||
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
||||
if let strongSelf = self {
|
||||
completion()
|
||||
controller?.dismiss()
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
} else {
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
}, getNavigationController: { [weak self] in
|
||||
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.currentWebAppController = controller
|
||||
strongSelf.push(controller)
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(self.presentationData.theme), fromMenu: buttonText == "Menu", replyToMessageId: nil, threadId: self.chatLocation.threadId)
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false)
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
|
||||
self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
|
||||
}, completion: { [weak self] in
|
||||
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}, getNavigationController: { [weak self] in
|
||||
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.currentWebAppController = controller
|
||||
strongSelf.push(controller)
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var botPeer = EnginePeer(peer)
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: self.context.sharedContext.accountManager, peerId: botPeer.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if value {
|
||||
openWebView()
|
||||
} else {
|
||||
let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: botPeer, completion: { _ in
|
||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
|
||||
openWebView()
|
||||
}, showMore: nil)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -134,6 +134,7 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
|||
|
||||
var hasOther = false
|
||||
var hasNotOwnMessages = false
|
||||
var hasPaid = false
|
||||
for message in messages {
|
||||
if let author = message.effectiveAuthor {
|
||||
if !uniquePeerIds.contains(author.id) {
|
||||
|
|
@ -159,6 +160,8 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
|||
if !message.text.isEmpty {
|
||||
hasCaptions = true
|
||||
}
|
||||
} else if media is TelegramMediaPaidContent {
|
||||
hasPaid = true
|
||||
}
|
||||
}
|
||||
if !isDice && !isMusic {
|
||||
|
|
@ -170,6 +173,9 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
|||
if case let .peer(peerId) = selfController.chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
canHideNames = false
|
||||
}
|
||||
if hasPaid {
|
||||
canHideNames = false
|
||||
}
|
||||
let hideNames = forwardOptions.hideNames
|
||||
let hideCaptions = forwardOptions.hideCaptions
|
||||
|
||||
|
|
@ -194,7 +200,7 @@ private func chatForwardOptions(selfController: ChatControllerImpl, sourceNode:
|
|||
})))
|
||||
}
|
||||
|
||||
if hasCaptions {
|
||||
if hasCaptions && !hasPaid {
|
||||
items.append(.action(ContextMenuActionItem(text: hideCaptions ? presentationData.strings.Conversation_ForwardOptions_ShowCaption : presentationData.strings.Conversation_ForwardOptions_HideCaption, icon: { _ in
|
||||
return nil
|
||||
}, iconAnimation: ContextMenuActionItem.IconAnimation(
|
||||
|
|
|
|||
|
|
@ -574,15 +574,7 @@ func updateChatPresentationInterfaceStateImpl(
|
|||
controller.updateVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
if let currentMenuWebAppController = selfController.currentMenuWebAppController, !selfController.presentationInterfaceState.showWebView {
|
||||
selfController.currentMenuWebAppController = nil
|
||||
if let currentMenuWebAppController = currentMenuWebAppController as? AttachmentController {
|
||||
currentMenuWebAppController.ensureUnfocused = false
|
||||
}
|
||||
currentMenuWebAppController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
selfController.presentationInterfaceStatePromise.set(selfController.presentationInterfaceState)
|
||||
|
||||
if case .tag = selfController.chatDisplayNode.historyNode.tag {
|
||||
|
|
|
|||
|
|
@ -3714,227 +3714,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||
}
|
||||
strongSelf.openResolved(result: .join(joinHash), sourceMessageId: nil)
|
||||
}, openWebView: { [weak self] buttonText, url, simple, source in
|
||||
guard let strongSelf = self, let peerId = strongSelf.chatLocation.peerId, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
|
||||
let botName: String
|
||||
let botAddress: String
|
||||
if case let .inline(bot) = source {
|
||||
botName = bot.compactDisplayTitle
|
||||
botAddress = bot.addressName ?? ""
|
||||
} else {
|
||||
botName = EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
|
||||
botAddress = peer.addressName ?? ""
|
||||
}
|
||||
|
||||
if source == .generic {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if !$0.contains(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.append(.requestInProgress)
|
||||
return updatedContexts.sorted()
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let updateProgress = { [weak self] in
|
||||
Queue.mainQueue().async {
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, {
|
||||
return $0.updatedTitlePanelContext {
|
||||
if let index = $0.firstIndex(where: {
|
||||
switch $0 {
|
||||
case .requestInProgress:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}) {
|
||||
var updatedContexts = $0
|
||||
updatedContexts.remove(at: index)
|
||||
return updatedContexts
|
||||
}
|
||||
return $0
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let openWebView = {
|
||||
if source == .menu {
|
||||
strongSelf.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(true)
|
||||
// return state.updatedShowWebView(true).updatedForceInputCommandsHidden(true)
|
||||
}
|
||||
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: .menu, peerId: peerId, botId: peerId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false)
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
|
||||
self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
|
||||
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
|
||||
if let strongSelf = self {
|
||||
if let chatTypes {
|
||||
let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
|
||||
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
||||
if let strongSelf = self {
|
||||
completion()
|
||||
controller?.dismiss()
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
} else {
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
}, getInputContainerNode: { [weak self] in
|
||||
if let strongSelf = self, let layout = strongSelf.validLayout, case .compact = layout.metrics.widthClass {
|
||||
return (strongSelf.chatDisplayNode.getWindowInputAccessoryHeight(), strongSelf.chatDisplayNode.inputPanelContainerNode, {
|
||||
return strongSelf.chatDisplayNode.textInputPanelNode?.makeAttachmentMenuTransition(accessoryPanelNode: nil)
|
||||
})
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}, completion: { [weak self] in
|
||||
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}, willDismiss: { [weak self] in
|
||||
self?.interfaceInteraction?.updateShowWebView { _ in
|
||||
return false
|
||||
}
|
||||
}, didDismiss: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
let isFocused = strongSelf.chatDisplayNode.textInputPanelNode?.isFocused ?? false
|
||||
strongSelf.chatDisplayNode.insertSubnode(strongSelf.chatDisplayNode.inputPanelContainerNode, aboveSubnode: strongSelf.chatDisplayNode.inputContextPanelContainer)
|
||||
if isFocused {
|
||||
strongSelf.chatDisplayNode.textInputPanelNode?.ensureFocused()
|
||||
}
|
||||
|
||||
strongSelf.updateChatPresentationInterfaceState(interactive: false) { state in
|
||||
return state.updatedForceInputCommandsHidden(false)
|
||||
}
|
||||
}
|
||||
}, getNavigationController: { [weak self] in
|
||||
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.push(controller)
|
||||
strongSelf.currentMenuWebAppController = controller
|
||||
} else if simple {
|
||||
var isInline = false
|
||||
var botId = peerId
|
||||
var botName = botName
|
||||
var botAddress = ""
|
||||
if case let .inline(bot) = source {
|
||||
isInline = true
|
||||
botId = bot.id
|
||||
botName = bot.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)
|
||||
botAddress = bot.addressName ?? ""
|
||||
}
|
||||
|
||||
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestSimpleWebView(botId: botId, url: url, source: isInline ? .inline : .generic, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme))
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] url in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: isInline ? .inline : .simple, peerId: peerId, botId: botId, botName: botName, url: url, queryId: nil, payload: nil, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false)
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
|
||||
self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
|
||||
}, requestSwitchInline: { [weak self] query, chatTypes, completion in
|
||||
if let strongSelf = self {
|
||||
if let chatTypes {
|
||||
let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
|
||||
controller.peerSelected = { [weak self, weak controller] peer, _ in
|
||||
if let strongSelf = self {
|
||||
completion()
|
||||
controller?.dismiss()
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
} else {
|
||||
strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
|
||||
}
|
||||
}
|
||||
}, getNavigationController: { [weak self] in
|
||||
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.currentWebAppController = controller
|
||||
strongSelf.push(controller)
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestWebView(peerId: peerId, botId: peerId, url: !url.isEmpty ? url : nil, payload: nil, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme), fromMenu: buttonText == "Menu", replyToMessageId: nil, threadId: strongSelf.chatLocation.threadId)
|
||||
|> afterDisposed {
|
||||
updateProgress()
|
||||
})
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] result in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let context = strongSelf.context
|
||||
let params = WebAppParameters(source: .generic, peerId: peerId, botId: peerId, botName: botName, url: result.url, queryId: result.queryId, payload: nil, buttonText: buttonText, keepAliveSignal: result.keepAliveSignal, forceHasSettings: false)
|
||||
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url, concealed, commit in
|
||||
self?.openUrl(url, concealed: concealed, forceExternal: true, commit: commit)
|
||||
}, completion: { [weak self] in
|
||||
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
}, getNavigationController: { [weak self] in
|
||||
return self?.effectiveNavigationController ?? context.sharedContext.mainWindow?.viewController as? NavigationController
|
||||
})
|
||||
controller.navigationPresentation = .flatModal
|
||||
strongSelf.currentWebAppController = controller
|
||||
strongSelf.push(controller)
|
||||
}, error: { [weak self] error in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
})]), in: .window(.root))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
var botPeer = EnginePeer(peer)
|
||||
if case let .inline(bot) = source {
|
||||
botPeer = bot
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id)
|
||||
|> deliverOnMainQueue).startStandalone(next: { value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
if value {
|
||||
openWebView()
|
||||
} else {
|
||||
let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: botPeer, completion: { _ in
|
||||
let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: botPeer.id).startStandalone()
|
||||
openWebView()
|
||||
}, showMore: nil)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
}
|
||||
})
|
||||
self.openWebApp(buttonText: buttonText, url: url, simple: simple, source: source)
|
||||
}, activateAdAction: { [weak self] messageId, progress in
|
||||
guard let self, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId), let adAttribute = message.adAttribute else {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ extension ChatControllerImpl {
|
|||
filter.insert(.excludeChannels)
|
||||
break
|
||||
}
|
||||
if let _ = media as? TelegramMediaPaidContent {
|
||||
filter.insert(.excludeSecretChats)
|
||||
}
|
||||
}
|
||||
}
|
||||
var attemptSelectionImpl: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ extension ChatControllerImpl {
|
|||
}
|
||||
|
||||
func presentAttachmentMenu(subject: AttachMenuSubject) {
|
||||
guard self.audioRecorderValue == nil && self.videoRecorderValue == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = self.context
|
||||
let inputIsActive = self.presentationInterfaceState.inputMode == .text
|
||||
|
||||
|
|
@ -1166,6 +1170,10 @@ extension ChatControllerImpl {
|
|||
if case .scheduledMessages = self.presentationInterfaceState.subject {
|
||||
isScheduledMessages = true
|
||||
}
|
||||
var paidMediaAllowed = false
|
||||
if let cachedData = self.peerView?.cachedData as? CachedChannelData, cachedData.flags.contains(.paidMediaAllowed) {
|
||||
paidMediaAllowed = true
|
||||
}
|
||||
let controller = MediaPickerScreen(
|
||||
context: self.context,
|
||||
updatedPresentationData: self.updatedPresentationData,
|
||||
|
|
@ -1176,6 +1184,7 @@ extension ChatControllerImpl {
|
|||
bannedSendPhotos: bannedSendPhotos,
|
||||
bannedSendVideos: bannedSendVideos,
|
||||
canBoostToUnrestrict: (self.presentationInterfaceState.boostsToUnrestrict ?? 0) > 0 && bannedSendPhotos?.1 != true && bannedSendVideos?.1 != true,
|
||||
paidMediaAllowed: paidMediaAllowed,
|
||||
subject: subject,
|
||||
saveEditedPhotos: saveEditedPhotos
|
||||
)
|
||||
|
|
|
|||
|
|
@ -88,6 +88,15 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
|
||||
private var applicationInFocusDisposable: Disposable?
|
||||
private var storyUploadEventsDisposable: Disposable?
|
||||
|
||||
override public var minimizedContainer: MinimizedContainer? {
|
||||
didSet {
|
||||
self.minimizedContainer?.navigationController = self
|
||||
self.minimizedContainerUpdated(self.minimizedContainer)
|
||||
}
|
||||
}
|
||||
|
||||
public var minimizedContainerUpdated: (MinimizedContainer?) -> Void = { _ in }
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
|
|
|
|||
|
|
@ -1771,9 +1771,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
fileprivate let moreButtonNode: MoreButtonNode
|
||||
|
||||
private let context: AccountContext
|
||||
private let source: WebAppParameters.Source
|
||||
public let source: WebAppParameters.Source
|
||||
private let peerId: PeerId
|
||||
private let botId: PeerId
|
||||
public let botId: PeerId
|
||||
private let botName: String
|
||||
private let url: String?
|
||||
private let queryId: Int64?
|
||||
|
|
@ -1935,16 +1935,6 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
|
||||
let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) })
|
||||
|
||||
// items.append(.action(ContextMenuActionItem(text: "Minimize", icon: { theme in
|
||||
// return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/HideArchive"), color: theme.contextMenu.primaryColor)
|
||||
// }, action: { [weak self] c, _ in
|
||||
// c?.dismiss(completion: nil)
|
||||
//
|
||||
// if let self, let parentController = self.parentController(), let navigationController = self.getNavigationController() {
|
||||
// navigationController.minimizeViewController(parentController, animated: true)
|
||||
// }
|
||||
// })))
|
||||
|
||||
if hasSettings {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
|
||||
|
|
@ -2094,13 +2084,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
}
|
||||
}
|
||||
|
||||
public func shouldDismissImmediately() -> Bool {
|
||||
if self.controllerNode.needDismissConfirmation {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
public override var isMinimized: Bool {
|
||||
didSet {
|
||||
if self.isMinimized != oldValue && self.isMinimized {
|
||||
self.controllerNode.webView?.hideScrollIndicators()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func shouldDismissImmediately() -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class WebAppPickerContext: AttachmentMediaPickerContext {
|
||||
|
|
@ -2182,7 +2176,6 @@ public func standaloneWebAppController(
|
|||
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: .peer(id: params.peerId), buttons: [.standalone], initialButton: .standalone, fromMenu: params.source == .menu, hasTextInput: false, makeEntityInputView: {
|
||||
return nil
|
||||
})
|
||||
// controller.getInputContainerNode = getInputContainerNode
|
||||
controller.requestController = { _, present in
|
||||
let webAppController = WebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, replyToMessageId: nil, threadId: threadId)
|
||||
webAppController.openUrl = openUrl
|
||||
|
|
|
|||
|
|
@ -194,6 +194,22 @@ final class WebAppWebView: WKWebView {
|
|||
}
|
||||
}
|
||||
|
||||
func hideScrollIndicators() {
|
||||
var hiddenViews: [UIView] = []
|
||||
for view in self.scrollView.subviews.reversed() {
|
||||
let minSize = min(view.frame.width, view.frame.height)
|
||||
if minSize < 4.0 {
|
||||
view.isHidden = true
|
||||
hiddenViews.append(view)
|
||||
}
|
||||
}
|
||||
Queue.mainQueue().after(2.0) {
|
||||
for view in hiddenViews {
|
||||
view.isHidden = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendEvent(name: String, data: String?) {
|
||||
let script = "window.TelegramGameProxy.receiveEvent(\"\(name)\", \(data ?? "null"))"
|
||||
self.evaluateJavaScript(script, completionHandler: { _, _ in
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue