This commit is contained in:
Isaac 2024-06-28 19:36:38 +02:00
commit 15880b36a2
52 changed files with 934 additions and 449 deletions

View file

@ -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";

View file

@ -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
}

View file

@ -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?

View file

@ -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",

View file

@ -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))

View file

@ -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,

View file

@ -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))

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -1227,6 +1227,9 @@
else if (groupedId != nil && !hasAnyTimers)
dict[@"groupedId"] = groupedId;
if (price != nil)
dict[@"price"] = price;
if (spoiler) {
dict[@"spoiler"] = @true;
}

View file

@ -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 {

View file

@ -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) {

View file

@ -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()
}
}

View file

@ -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),

View file

@ -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)

View file

@ -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)

View file

@ -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
})

View file

@ -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 {

View file

@ -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?

View file

@ -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
}

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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) {

View file

@ -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?

View file

@ -16,6 +16,7 @@ swift_library(
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/AccountContext",
"//submodules/UIKitRuntimeUtils",
],
visibility = [
"//visibility:public",

View file

@ -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
}

View file

@ -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()

View file

@ -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 {

View file

@ -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: {

View file

@ -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

View file

@ -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)

View file

@ -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)
}
}
}
}

View file

@ -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()

View file

@ -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

View file

@ -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()
}
}
}
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "lockmedia.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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

View file

@ -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

View file

@ -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))
}
})
}
}

View file

@ -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(

View file

@ -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 {

View file

@ -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

View file

@ -42,6 +42,9 @@ extension ChatControllerImpl {
filter.insert(.excludeChannels)
break
}
if let _ = media as? TelegramMediaPaidContent {
filter.insert(.excludeSecretChats)
}
}
}
var attemptSelectionImpl: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?

View file

@ -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
)

View file

@ -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

View file

@ -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

View file

@ -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