diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4625f9d971..6443c3d4dc 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/AttachmentUI/Sources/AttachmentContainer.swift b/submodules/AttachmentUI/Sources/AttachmentContainer.swift index 04097a26c8..dca2ee0b5a 100644 --- a/submodules/AttachmentUI/Sources/AttachmentContainer.swift +++ b/submodules/AttachmentUI/Sources/AttachmentContainer.swift @@ -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 } diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index f4d5615c43..8564c2c7f0 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -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? diff --git a/submodules/BrowserUI/BUILD b/submodules/BrowserUI/BUILD index 2112ef8200..9bc316a4a8 100644 --- a/submodules/BrowserUI/BUILD +++ b/submodules/BrowserUI/BUILD @@ -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", diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index 71dbde446a..2c0c2250eb 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -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)) diff --git a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift index afe73f5737..ad57dc233e 100644 --- a/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift +++ b/submodules/BrowserUI/Sources/BrowserToolbarComponent.swift @@ -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, diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 0db2b467cb..4caad38b0e 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -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)) diff --git a/submodules/Display/Source/Navigation/MinimizedContainer.swift b/submodules/Display/Source/Navigation/MinimizedContainer.swift index a937bb8b12..8460e77ce3 100644 --- a/submodules/Display/Source/Navigation/MinimizedContainer.swift +++ b/submodules/Display/Source/Navigation/MinimizedContainer.swift @@ -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) diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index 299cf98e7c..8423ad324b 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -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 diff --git a/submodules/Display/Source/ViewController.swift b/submodules/Display/Source/ViewController.swift index 6e97f25dca..b2baf23eb6 100644 --- a/submodules/Display/Source/ViewController.swift +++ b/submodules/Display/Source/ViewController.swift @@ -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 diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 0b2fb3983d..9c39a5d896 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -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 { diff --git a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift index e411e3f03d..e7409fa608 100644 --- a/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/UniversalVideoGalleryItem.swift @@ -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 diff --git a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m index a890bf89e0..94f4f26632 100644 --- a/submodules/LegacyComponents/Sources/TGMediaAssetsController.m +++ b/submodules/LegacyComponents/Sources/TGMediaAssetsController.m @@ -1227,6 +1227,9 @@ else if (groupedId != nil && !hasAnyTimers) dict[@"groupedId"] = groupedId; + if (price != nil) + dict[@"price"] = price; + if (spoiler) { dict[@"spoiler"] = @true; } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index d3f44e84d0..c5cab76799 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -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(selected: false, count: 0)) + private let selectionPromise = ValuePromise(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 { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index 40be90ac32..5aa4d6464d 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -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) { diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index add3b3da13..78caaba0fe 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -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() } } diff --git a/submodules/MediaPlayer/Package.swift b/submodules/MediaPlayer/Package.swift index 35d1875d35..a35bec3fb0 100644 --- a/submodules/MediaPlayer/Package.swift +++ b/submodules/MediaPlayer/Package.swift @@ -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), diff --git a/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift b/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift index ff7bcf55f4..f203973b74 100644 --- a/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift +++ b/submodules/PremiumUI/Sources/IncreaseLimitHeaderItem.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift index 63b3d8b66e..52fd6e0f2c 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReactionsMessageAttribute.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift index 3811abccb7..5821411918 100644 --- a/submodules/TelegramCore/Sources/Network/Network.swift +++ b/submodules/TelegramCore/Sources/Network/Network.swift @@ -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 }) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index 1e2e7e9000..8ca472a617 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift index f8538240fb..0cef10c2f4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/PeersData.swift @@ -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? diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift index 9837949190..3a8881c801 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/InactiveChannels.swift @@ -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 } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 875d3ba68a..333ae2772a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 9cbc52cc4c..a261f2b647 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -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, diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index eaacde7b1e..460de318a1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index 57b30db954..dbe1880c41 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -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) { diff --git a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift index ab887d4bf4..f190635001 100644 --- a/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift +++ b/submodules/TelegramUI/Components/MediaEditor/Sources/DrawingMessageRenderer.swift @@ -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? diff --git a/submodules/TelegramUI/Components/MinimizedContainer/BUILD b/submodules/TelegramUI/Components/MinimizedContainer/BUILD index 7585ab96ad..5f2bbce496 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/BUILD +++ b/submodules/TelegramUI/Components/MinimizedContainer/BUILD @@ -16,6 +16,7 @@ swift_library( "//submodules/TelegramPresentationData", "//submodules/ComponentFlow", "//submodules/AccountContext", + "//submodules/UIKitRuntimeUtils", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift index 29cd7374c2..7c130707a9 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift +++ b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedContainer.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift index 7c3681def0..fde0731161 100644 --- a/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift +++ b/submodules/TelegramUI/Components/MinimizedContainer/Sources/MinimizedHeaderNode.swift @@ -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() diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index cd3e327d0b..6cf96cfe89 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift index 1dc627c391..96e46e2053 100644 --- a/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift +++ b/submodules/TelegramUI/Components/PeerManagement/OldChannelsController/Sources/OldChannelsController.swift @@ -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: { diff --git a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift index c29b55363e..3e6c5fc93d 100644 --- a/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsImageComponent/Sources/StarsImageComponent.swift @@ -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() 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 diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 05869fb375..436374d47e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -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? - 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) diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaAreaMaskView.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaAreaMaskView.swift new file mode 100644 index 0000000000..d2db1f1f21 --- /dev/null +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaAreaMaskView.swift @@ -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) + } + } + } +} diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 675210777b..fd2bef3098 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -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() diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index 68f236209e..8f416abce8 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index f80d8ec604..485b879508 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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() + } + } } } } diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/Contents.json new file mode 100644 index 0000000000..832af97290 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "lockmedia.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/lockmedia.pdf b/submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/lockmedia.pdf new file mode 100644 index 0000000000..ffaa059660 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Premium/Stars/MediaLock.imageset/lockmedia.pdf differ diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index fda61d7b8a..f79c3a2295 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index fb0ac7b444..bb989c7cc0 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift new file mode 100644 index 0000000000..376d6b226a --- /dev/null +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -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)) + } + }) + } +} diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 9b95510f6d..0951822b75 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -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( diff --git a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift index 71709a4294..62e682f00f 100644 --- a/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/Chat/UpdateChatPresentationInterfaceState.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index f7cd0a4b84..6b148f1025 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -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 diff --git a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift index 628ebe7671..5157826e77 100644 --- a/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift +++ b/submodules/TelegramUI/Sources/ChatControllerForwardMessages.swift @@ -42,6 +42,9 @@ extension ChatControllerImpl { filter.insert(.excludeChannels) break } + if let _ = media as? TelegramMediaPaidContent { + filter.insert(.excludeSecretChats) + } } } var attemptSelectionImpl: ((EnginePeer, ChatListDisabledPeerReason) -> Void)? diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index 2dd53831fb..366751ca81 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -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 ) diff --git a/submodules/TelegramUI/Sources/TelegramRootController.swift b/submodules/TelegramUI/Sources/TelegramRootController.swift index 96ce8da3de..b2cedba094 100644 --- a/submodules/TelegramUI/Sources/TelegramRootController.swift +++ b/submodules/TelegramUI/Sources/TelegramRootController.swift @@ -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 diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 31cbfc89ed..50abb887e3 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -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 diff --git a/submodules/WebUI/Sources/WebAppWebView.swift b/submodules/WebUI/Sources/WebAppWebView.swift index 169383d6f7..48c4d5dc8f 100644 --- a/submodules/WebUI/Sources/WebAppWebView.swift +++ b/submodules/WebUI/Sources/WebAppWebView.swift @@ -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