mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
commit
f21265881e
@ -1 +1 @@
|
||||
d66a871d990a1084502f4856b3d66c35
|
||||
462c8b20925f8d3f87f0ab82d25d9c2a
|
||||
|
@ -7844,6 +7844,12 @@ Sorry for the inconvenience.";
|
||||
"StickerPack.RemoveEmojiCount_1" = "Remove 1 Emoji";
|
||||
"StickerPack.RemoveEmojiCount_any" = "Remove %@ Emoji";
|
||||
|
||||
"StickerPack.AddEmojiPacksCount_1" = "Add 1 Emoji Pack";
|
||||
"StickerPack.AddEmojiPacksCount_any" = "Add %@ Emoji Packs";
|
||||
|
||||
"StickerPack.RemoveEmojiPacksCount_1" = "Remove 1 Emoji Packs";
|
||||
"StickerPack.RemoveEmojiPacksCount_any" = "Remove %@ Emoji Packs";
|
||||
|
||||
"Gallery.AirPlay" = "AirPlay";
|
||||
"Gallery.AirPlayPlaceholder" = "This video is playing on the TV using AirPlay";
|
||||
|
||||
@ -7852,6 +7858,8 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Privacy.VoiceMessages" = "Voice Messages";
|
||||
|
||||
"Privacy.VoiceMessages.Tooltip" = "Only subscribers of [Telegram Premium]() can restrict receiving voice messages.";
|
||||
|
||||
"Privacy.VoiceMessages.WhoCanSend" = "WHO CAN SEND ME VOICE MESSAGES";
|
||||
"Privacy.VoiceMessages.CustomHelp" = "You can restrict who can send you voice messages with granular precision.";
|
||||
"Privacy.VoiceMessages.AlwaysAllow.Title" = "Always Allow";
|
||||
@ -7861,6 +7869,22 @@ Sorry for the inconvenience.";
|
||||
"Premium.AnimatedEmoji" = "Animated Emoji";
|
||||
"Premium.AnimatedEmojiInfo" = "Include animated emoji from different emoji sets in any message you send.";
|
||||
|
||||
"ChatContextMenu.EmojiSetSingle" = "This message contains %@ emoji.";
|
||||
"ChatContextMenu.EmojiSet_1" = "This message contains emoji from %@ pack.";
|
||||
"ChatContextMenu.EmojiSet_many" = "This message contains emoji from %@ packs.";
|
||||
"ChatContextMenu.EmojiSetSingle" = "This message contains\n#[%@]() emoji.";
|
||||
"ChatContextMenu.EmojiSet_1" = "This message contains emoji from [%@ pack]().";
|
||||
"ChatContextMenu.EmojiSet_any" = "This message contains emoji from [%@ packs]().";
|
||||
|
||||
"EmojiPack.Title" = "Emoji";
|
||||
"EmojiPack.Emoji_1" = "%@ emoji";
|
||||
"EmojiPack.Emoji_any" = "%@ emoji";
|
||||
"EmojiPack.Add" = "Add";
|
||||
"EmojiPack.Added" = "Added";
|
||||
|
||||
"EmojiPackActionInfo.AddedTitle" = "Emoji Added";
|
||||
"EmojiPackActionInfo.AddedText" = "%@ has been added to your emoji.";
|
||||
"EmojiPackActionInfo.RemovedTitle" = "Emoji Removed";
|
||||
"EmojiPackActionInfo.RemovedText" = "%@ is no longer in your emoji.";
|
||||
|
||||
"WebApp.ShareMyPhoneNumber" = "Share My Phone Number";
|
||||
"WebApp.ShareMyPhoneNumberConfirmation" = "Are you sure you want to share your phone number **%1$@** with **%2$@**?";
|
||||
|
||||
"Conversation.VoiceMessagesRestricted" = "%@ doesn't accept voice and video messages";
|
||||
|
@ -744,6 +744,8 @@ public protocol SharedAccountContext: AnyObject {
|
||||
|
||||
func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController
|
||||
|
||||
func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController
|
||||
|
||||
func navigateToCurrentCall()
|
||||
var hasOngoingCall: ValuePromise<Bool> { get }
|
||||
var immediateHasOngoingCall: Bool { get }
|
||||
|
@ -46,6 +46,7 @@ public enum ChatPanelSearchNavigationAction {
|
||||
public enum ChatPanelRestrictionInfoSubject {
|
||||
case mediaRecording
|
||||
case stickers
|
||||
case premiumVoiceMessages
|
||||
}
|
||||
|
||||
public enum ChatPanelRestrictionInfoDisplayType {
|
||||
|
@ -13,12 +13,15 @@ swift_library(
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/TextSelectionNode:TextSelectionNode",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/ReactionSelectionNode:ReactionSelectionNode",
|
||||
"//submodules/Markdown:Markdown",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1,9 +1,13 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TextSelectionNode
|
||||
import Markdown
|
||||
import AppBundle
|
||||
import TextFormat
|
||||
import TextNodeWithEntities
|
||||
|
||||
private final class ContextActionsSelectionGestureRecognizer: UIPanGestureRecognizer {
|
||||
var updateLocation: ((CGPoint, Bool) -> Void)?
|
||||
@ -340,17 +344,34 @@ private final class InnerActionsContainerNode: ASDisplayNode {
|
||||
final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
private let presentationData: PresentationData
|
||||
private var effectView: UIVisualEffectView?
|
||||
private let textNode: TextNode
|
||||
private let highlightBackgroundNode: ASDisplayNode
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let textNode: TextNodeWithEntities
|
||||
private var textSelectionNode: TextSelectionNode?
|
||||
private let iconNode: ASImageNode
|
||||
|
||||
private let text: String
|
||||
private var arguments: TextNodeWithEntities.Arguments?
|
||||
private var file: TelegramMediaFile?
|
||||
private let targetSelectionIndex: Int?
|
||||
|
||||
private var action: (() -> Void)?
|
||||
var requestDismiss: () -> Void = {}
|
||||
|
||||
init(presentationData: PresentationData, tip: ContextController.Tip) {
|
||||
self.presentationData = presentationData
|
||||
self.textNode = TextNode()
|
||||
|
||||
self.highlightBackgroundNode = ASDisplayNode()
|
||||
self.highlightBackgroundNode.isAccessibilityElement = false
|
||||
self.highlightBackgroundNode.alpha = 0.0
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
|
||||
self.textNode = TextNodeWithEntities()
|
||||
self.textNode.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.textNode.displaysAsynchronously = true
|
||||
|
||||
var isUserInteractionEnabled = false
|
||||
var icon: UIImage?
|
||||
switch tip {
|
||||
case .textSelection:
|
||||
@ -372,10 +393,14 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
self.text = isChannel ? self.presentationData.strings.Conversation_CopyProtectionInfoChannel : self.presentationData.strings.Conversation_CopyProtectionInfoGroup
|
||||
self.targetSelectionIndex = nil
|
||||
icon = UIImage(bundleImageName: "Chat/Context Menu/ReportCopyright")
|
||||
case let .animatedEmoji(packs):
|
||||
self.text = packs.count == 1 ? self.presentationData.strings.ChatContextMenu_EmojiSetSingle("name").string : self.presentationData.strings.ChatContextMenu_EmojiSet(Int32(packs.count))
|
||||
case let .animatedEmoji(text, arguments, file, action):
|
||||
self.action = action
|
||||
self.text = text ?? ""
|
||||
self.arguments = arguments
|
||||
self.file = file
|
||||
self.targetSelectionIndex = nil
|
||||
icon = UIImage(bundleImageName: "Chat/Context Menu/ReportCopyright")
|
||||
icon = nil //UIImage(bundleImageName: "Chat/Context Menu/Arrow")
|
||||
isUserInteractionEnabled = text != nil
|
||||
}
|
||||
|
||||
self.iconNode = ASImageNode()
|
||||
@ -390,18 +415,43 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
|
||||
self.backgroundColor = presentationData.theme.contextMenu.backgroundColor
|
||||
|
||||
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: presentationData.theme.contextMenu.primaryColor.withAlphaComponent(0.15), knob: presentationData.theme.contextMenu.primaryColor, knobDiameter: 8.0), strings: presentationData.strings, textNode: self.textNode, updateIsActive: { _ in
|
||||
let textSelectionNode = TextSelectionNode(theme: TextSelectionTheme(selection: presentationData.theme.contextMenu.primaryColor.withAlphaComponent(0.15), knob: presentationData.theme.contextMenu.primaryColor, knobDiameter: 8.0), strings: presentationData.strings, textNode: self.textNode.textNode, updateIsActive: { _ in
|
||||
}, present: { _, _ in
|
||||
}, rootNode: self, performAction: { _, _ in
|
||||
})
|
||||
self.textSelectionNode = textSelectionNode
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.highlightBackgroundNode)
|
||||
self.addSubnode(self.textNode.textNode)
|
||||
self.addSubnode(self.iconNode)
|
||||
|
||||
self.textSelectionNode.flatMap(self.addSubnode)
|
||||
|
||||
self.addSubnode(textSelectionNode.highlightAreaNode)
|
||||
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
strongSelf.highlightBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
let previousAlpha = strongSelf.highlightBackgroundNode.alpha
|
||||
strongSelf.highlightBackgroundNode.alpha = 0.0
|
||||
strongSelf.highlightBackgroundNode.layer.animateAlpha(from: previousAlpha, to: 0.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.isUserInteractionEnabled = isUserInteractionEnabled
|
||||
}
|
||||
|
||||
@objc func pressed() {
|
||||
self.requestDismiss()
|
||||
self.action?()
|
||||
}
|
||||
|
||||
func updateLayout(widthClass: ContainerViewLayoutSizeClass, width: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
@ -436,15 +486,37 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
let iconSideInset: CGFloat = 12.0
|
||||
|
||||
let textFont = Font.regular(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
|
||||
let boldTextFont = Font.bold(floor(presentationData.listsFontSize.baseDisplaySize * 14.0 / 17.0))
|
||||
let textColor = self.presentationData.theme.contextMenu.primaryColor
|
||||
let accentColor = self.presentationData.theme.contextMenu.badgeFillColor
|
||||
|
||||
let iconSize = self.iconNode.image?.size ?? CGSize(width: 16.0, height: 16.0)
|
||||
|
||||
let text = self.text.replacingOccurrences(of: "#", with: "# ")
|
||||
let attributedText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: textColor), bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor), link: MarkdownAttributeSet(font: boldTextFont, textColor: accentColor), linkAttribute: { _ in
|
||||
return nil
|
||||
})))
|
||||
if let file = self.file {
|
||||
let range = (attributedText.string as NSString).range(of: "#")
|
||||
if range.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(stickerPack: nil, fileId: file.fileId.id, file: file), range: range)
|
||||
}
|
||||
}
|
||||
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: self.text, font: textFont, textColor: self.presentationData.theme.contextMenu.primaryColor), backgroundColor: nil, minimumNumberOfLines: 0, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - horizontalInset * 2.0 - iconSize.width - 8.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets(), lineColor: nil, textShadowColor: nil, textStroke: nil))
|
||||
let _ = textApply()
|
||||
let shimmeringForegroundColor: UIColor
|
||||
if presentationData.theme.overallDarkAppearance {
|
||||
let backgroundColor = presentationData.theme.contextMenu.backgroundColor.blitOver(presentationData.theme.list.plainBackgroundColor, alpha: 1.0)
|
||||
shimmeringForegroundColor = presentationData.theme.contextMenu.primaryColor.blitOver(backgroundColor, alpha: 0.1)
|
||||
} else {
|
||||
shimmeringForegroundColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.07)
|
||||
}
|
||||
|
||||
let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, minimumNumberOfLines: 0, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - horizontalInset * 2.0 - iconSize.width - 8.0, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets(), lineColor: nil, textShadowColor: nil, textStroke: nil))
|
||||
let _ = textApply(self.arguments?.withUpdatedPlaceholderColor(shimmeringForegroundColor))
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: horizontalInset, y: verticalInset), size: textLayout.size)
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
transition.updateFrame(node: self.textNode.textNode, frame: textFrame)
|
||||
|
||||
let size = CGSize(width: width, height: textLayout.size.height + verticalInset * 2.0)
|
||||
|
||||
@ -460,6 +532,10 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
|
||||
transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
|
||||
self.highlightBackgroundNode.backgroundColor = presentationData.theme.contextMenu.itemHighlightedBackgroundColor
|
||||
self.highlightBackgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
@ -536,7 +612,9 @@ final class ContextActionsContainerNode: ASDisplayNode {
|
||||
self.actionsNode = InnerActionsContainerNode(presentationData: presentationData, items: itemList, getController: getController, actionSelected: actionSelected, requestLayout: requestLayout, feedbackTap: feedbackTap, blurBackground: blurBackground)
|
||||
if let tip = items.tip {
|
||||
let textSelectionTipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
|
||||
textSelectionTipNode.isUserInteractionEnabled = false
|
||||
textSelectionTipNode.requestDismiss = {
|
||||
getController()?.dismiss(completion: nil)
|
||||
}
|
||||
self.textSelectionTipNode = textSelectionTipNode
|
||||
} else {
|
||||
self.textSelectionTipNode = nil
|
||||
|
@ -8,6 +8,7 @@ import ReactionSelectionNode
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TextNodeWithEntities
|
||||
|
||||
private let animationDurationFactor: Double = 1.0
|
||||
|
||||
@ -1355,7 +1356,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
private func setItems(items: ContextController.Items, minHeight: ContextController.ActionsHeight?, previousActionsTransition: ContextController.PreviousActionsTransition) {
|
||||
if let presentationNode = self.presentationNode {
|
||||
presentationNode.replaceItems(items: items, animated: self.didCompleteAnimationIn)
|
||||
let disableAnimations = self.getController()?.immediateItemsTransitionAnimation == true
|
||||
presentationNode.replaceItems(items: items, animated: self.didCompleteAnimationIn && !disableAnimations)
|
||||
|
||||
if !self.didSetItemsReady {
|
||||
self.didSetItemsReady = true
|
||||
@ -2226,7 +2228,7 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
||||
case textSelection
|
||||
case messageViewsPrivacy
|
||||
case messageCopyProtection(isChannel: Bool)
|
||||
case animatedEmoji(packs: [StickerPackItem])
|
||||
case animatedEmoji(text: String?, arguments: TextNodeWithEntities.Arguments?, file: TelegramMediaFile?, action: (() -> Void)?)
|
||||
}
|
||||
|
||||
public final class ActionsHeight {
|
||||
|
@ -807,6 +807,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
final class ItemContainer: ASDisplayNode {
|
||||
let getController: () -> ContextControllerProtocol?
|
||||
let requestUpdate: (ContainedViewLayoutTransition) -> Void
|
||||
let node: ContextControllerActionsStackItemNode
|
||||
let dimNode: ASDisplayNode
|
||||
@ -826,6 +827,7 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
reactionItems: (context: AccountContext, reactionItems: [ReactionContextItem])?,
|
||||
positionLock: CGFloat?
|
||||
) {
|
||||
self.getController = getController
|
||||
self.requestUpdate = requestUpdate
|
||||
self.node = item.node(
|
||||
getController: getController,
|
||||
@ -885,7 +887,9 @@ final class ContextControllerActionsStackNode: ASDisplayNode {
|
||||
if self.tipNode == nil {
|
||||
updatedTransition = .immediate
|
||||
let tipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip)
|
||||
tipNode.isUserInteractionEnabled = false
|
||||
tipNode.requestDismiss = { [weak self] in
|
||||
self?.getController()?.dismiss(completion: nil)
|
||||
}
|
||||
self.tipNode = tipNode
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,7 @@ public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
self.label.displaysAsynchronously = false
|
||||
self.label.truncationType = .end
|
||||
self.label.isAccessibilityElement = false
|
||||
self.label.textAlignment = .center
|
||||
|
||||
self.accessibilityArea = AccessibilityAreaNode()
|
||||
self.accessibilityArea.accessibilityTraits = .staticText
|
||||
|
@ -2667,7 +2667,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
}
|
||||
let baseNavigationController = strongSelf.baseNavigationController()
|
||||
baseNavigationController?.view.endEditing(true)
|
||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { info, items, action in
|
||||
let controller = StickerPackScreen(context: strongSelf.context, mainStickerPack: packs[0], stickerPacks: Array(packs.prefix(1)), sendSticker: nil, actionPerformed: { info, items, action in
|
||||
let animateInAsReplacement = false
|
||||
switch action {
|
||||
case .add:
|
||||
|
@ -20,6 +20,7 @@ public enum ItemListDisclosureLabelStyle {
|
||||
case text
|
||||
case detailText
|
||||
case coloredText(UIColor)
|
||||
// case textWithIcon(UIColor)
|
||||
case multilineDetailText
|
||||
case badge(UIColor)
|
||||
case color(UIColor)
|
||||
|
@ -905,7 +905,7 @@ private final class DemoSheetContent: CombinedComponent {
|
||||
content: AnyComponent(PhoneDemoComponent(
|
||||
context: component.context,
|
||||
position: .top,
|
||||
videoFile: configuration.videos["profile_badge"],
|
||||
videoFile: configuration.videos["animated_emoji"],
|
||||
decoration: .emoji
|
||||
)),
|
||||
title: strings.Premium_AnimatedEmoji,
|
||||
|
@ -240,7 +240,7 @@ enum PremiumPerk: CaseIterable {
|
||||
case .appIcons:
|
||||
return "Premium/Perk/AppIcon"
|
||||
case .animatedEmoji:
|
||||
return "Premium/Perk/AppIcon"
|
||||
return "Premium/Perk/Emoji"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ import TelegramNotices
|
||||
import LocalAuth
|
||||
import AppBundle
|
||||
import PasswordSetupUI
|
||||
import UndoUI
|
||||
import PremiumUI
|
||||
|
||||
private final class PrivacyAndSecurityControllerArguments {
|
||||
let account: Account
|
||||
@ -130,11 +132,11 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
|
||||
return 8
|
||||
case .voiceCallPrivacy:
|
||||
return 9
|
||||
case .forwardPrivacy:
|
||||
return 10
|
||||
case .groupPrivacy:
|
||||
return 11
|
||||
case .voiceMessagePrivacy:
|
||||
return 10
|
||||
case .forwardPrivacy:
|
||||
return 11
|
||||
case .groupPrivacy:
|
||||
return 12
|
||||
case .selectivePrivacyInfo:
|
||||
return 13
|
||||
@ -447,18 +449,18 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
|
||||
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.presence)))
|
||||
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.profilePhoto)))
|
||||
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceCalls)))
|
||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages)))
|
||||
entries.append(.forwardPrivacy(presentationData.theme, presentationData.strings.Privacy_Forwards, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.forwards)))
|
||||
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.groupInvitations)))
|
||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.voiceMessages)))
|
||||
|
||||
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
||||
} else {
|
||||
entries.append(.lastSeenPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_LastSeen, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.profilePhotoPrivacy(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.voiceCallPrivacy(presentationData.theme, presentationData.strings.Privacy_Calls, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.forwardPrivacy(presentationData.theme, presentationData.strings.Privacy_Forwards, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.groupPrivacy(presentationData.theme, presentationData.strings.Privacy_GroupsAndChannels, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.voiceMessagePrivacy(presentationData.theme, presentationData.strings.Privacy_VoiceMessages, presentationData.strings.Channel_NotificationLoading))
|
||||
entries.append(.selectivePrivacyInfo(presentationData.theme, presentationData.strings.PrivacyLastSeenSettings_GroupsAndChannelsHelp))
|
||||
}
|
||||
|
||||
@ -738,26 +740,43 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
}
|
||||
}))
|
||||
}, openVoiceMessagePrivacy: {
|
||||
let signal = privacySettingsPromise.get()
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let signal = combineLatest(
|
||||
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)),
|
||||
privacySettingsPromise.get()
|
||||
)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in
|
||||
if let info = info {
|
||||
pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _ in
|
||||
if let currentInfoDisposable = currentInfoDisposable {
|
||||
let applySetting: Signal<Void, NoError> = privacySettingsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { value -> Signal<Void, NoError> in
|
||||
if let value = value {
|
||||
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout)))
|
||||
}
|
||||
return .complete()
|
||||
currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] peer, info in
|
||||
let isPremium = peer?.isPremium ?? false
|
||||
|
||||
if isPremium {
|
||||
if let info = info {
|
||||
pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .voiceMessages, current: info.voiceMessages, updated: { updated, _, _ in
|
||||
if let currentInfoDisposable = currentInfoDisposable {
|
||||
let applySetting: Signal<Void, NoError> = privacySettingsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> mapToSignal { value -> Signal<Void, NoError> in
|
||||
if let value = value {
|
||||
privacySettingsPromise.set(.single(AccountPrivacySettings(presence: value.presence, groupInvitations: value.groupInvitations, voiceCalls: value.voiceCalls, voiceCallsP2P: value.voiceCallsP2P, profilePhoto: value.profilePhoto, forwards: value.forwards, phoneNumber: value.phoneNumber, phoneDiscoveryEnabled: value.phoneDiscoveryEnabled, voiceMessages: updated, automaticallyArchiveAndMuteNonContacts: value.automaticallyArchiveAndMuteNonContacts, accountRemovalTimeout: value.accountRemovalTimeout)))
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
currentInfoDisposable.set(applySetting.start())
|
||||
}
|
||||
currentInfoDisposable.set(applySetting.start())
|
||||
}), true)
|
||||
}
|
||||
} else {
|
||||
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.Privacy_VoiceMessages_Tooltip), elevatedLayout: false, animateInAsReplacement: false, action: { action in
|
||||
if action == .info {
|
||||
let controller = PremiumIntroScreen(context: context, source: .settings)
|
||||
pushControllerImpl?(controller, true)
|
||||
return true
|
||||
}
|
||||
}), true)
|
||||
return false
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}, openPasscode: {
|
||||
@ -953,7 +972,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|
||||
(controller?.navigationController as? NavigationController)?.replaceTopController(c, animated: true)
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c in
|
||||
controller?.present(c, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
controller?.present(c, in: .window(.root), with: nil)
|
||||
}
|
||||
getNavigationControllerImpl = { [weak controller] in
|
||||
return (controller?.navigationController as? NavigationController)
|
||||
|
@ -1230,7 +1230,7 @@ public func installedStickerPacksController(context: AccountContext, mode: Insta
|
||||
packs.insert(packReference, at: 0)
|
||||
}
|
||||
if let mainStickerPack = mainStickerPack {
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: mainStickerPack, stickerPacks: packs, parentNavigationController: controller?.navigationController as? NavigationController, actionPerformed: { info, items, action in
|
||||
presentControllerImpl?(StickerPackScreen(context: context, mode: .settings, mainStickerPack: mainStickerPack, stickerPacks: [mainStickerPack], parentNavigationController: controller?.navigationController as? NavigationController, actionPerformed: { info, items, action in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = navigationControllerImpl?() {
|
||||
|
@ -35,6 +35,10 @@ swift_library(
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/TelegramUI/Components/EntityKeyboard:EntityKeyboard",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/StickerPeekUI:StickerPeekUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -0,0 +1,364 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import StickerResources
|
||||
import AccountContext
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import TelegramPresentationData
|
||||
import ShimmerEffect
|
||||
import EntityKeyboard
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
private let nativeItemSize = 36.0
|
||||
private let minItemsPerRow = 8
|
||||
private let verticalSpacing = 9.0
|
||||
private let minSpacing = 9.0
|
||||
private let containerInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0)
|
||||
|
||||
class ItemLayout {
|
||||
let width: CGFloat
|
||||
let itemsCount: Int
|
||||
let hasTitle: Bool
|
||||
let itemsPerRow: Int
|
||||
let visibleItemSize: CGFloat
|
||||
let horizontalSpacing: CGFloat
|
||||
let itemTopOffset: CGFloat
|
||||
let height: CGFloat
|
||||
|
||||
init(width: CGFloat, itemsCount: Int, hasTitle: Bool) {
|
||||
self.width = width
|
||||
self.itemsCount = itemsCount
|
||||
self.hasTitle = hasTitle
|
||||
|
||||
let itemHorizontalSpace = width - containerInsets.left - containerInsets.right
|
||||
self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (nativeItemSize + minSpacing)))
|
||||
|
||||
self.visibleItemSize = floor((itemHorizontalSpace - CGFloat(self.itemsPerRow - 1) * minSpacing) / CGFloat(self.itemsPerRow))
|
||||
|
||||
self.horizontalSpacing = floor((itemHorizontalSpace - visibleItemSize * CGFloat(self.itemsPerRow)) / CGFloat(self.itemsPerRow - 1))
|
||||
|
||||
let numRowsInGroup = (itemsCount + (self.itemsPerRow - 1)) / self.itemsPerRow
|
||||
|
||||
self.itemTopOffset = hasTitle ? 61.0 : 0.0
|
||||
self.height = itemTopOffset + CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing
|
||||
}
|
||||
|
||||
func frame(itemIndex: Int) -> CGRect {
|
||||
let row = itemIndex / self.itemsPerRow
|
||||
let column = itemIndex % self.itemsPerRow
|
||||
|
||||
return CGRect(
|
||||
origin: CGPoint(
|
||||
x: containerInsets.left + CGFloat(column) * (self.visibleItemSize + self.horizontalSpacing),
|
||||
y: self.itemTopOffset + CGFloat(row) * (self.visibleItemSize + verticalSpacing)
|
||||
),
|
||||
size: CGSize(
|
||||
width: self.visibleItemSize,
|
||||
height: self.visibleItemSize
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
final class StickerPackEmojisItem: GridItem {
|
||||
let context: AccountContext
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
let interaction: StickerPackPreviewInteraction
|
||||
let info: StickerPackCollectionInfo
|
||||
let items: [StickerPackItem]
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let title: String?
|
||||
let isInstalled: Bool?
|
||||
let isEmpty: Bool
|
||||
|
||||
let section: GridSection? = nil
|
||||
let fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)?
|
||||
|
||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, interaction: StickerPackPreviewInteraction, info: StickerPackCollectionInfo, items: [StickerPackItem], theme: PresentationTheme, strings: PresentationStrings, title: String?, isInstalled: Bool?, isEmpty: Bool) {
|
||||
self.context = context
|
||||
self.animationCache = animationCache
|
||||
self.animationRenderer = animationRenderer
|
||||
self.interaction = interaction
|
||||
self.info = info
|
||||
self.items = items
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.title = title
|
||||
self.isInstalled = isInstalled
|
||||
self.isEmpty = isEmpty
|
||||
|
||||
self.fillsRowWithDynamicHeight = { width in
|
||||
let layout = ItemLayout(width: width, itemsCount: items.count, hasTitle: title != nil)
|
||||
return layout.height
|
||||
}
|
||||
}
|
||||
|
||||
func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode {
|
||||
let node = StickerPackEmojisItemNode()
|
||||
node.setup(item: self)
|
||||
return node
|
||||
}
|
||||
|
||||
func update(node: GridItemNode) {
|
||||
guard let node = node as? StickerPackEmojisItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
node.setup(item: self)
|
||||
}
|
||||
}
|
||||
|
||||
private let textFont = Font.regular(20.0)
|
||||
|
||||
final class StickerPackEmojisItemNode: GridItemNode {
|
||||
private var item: StickerPackEmojisItem?
|
||||
private var itemLayout: ItemLayout?
|
||||
|
||||
private var shimmerHostView: PortalSourceView?
|
||||
private var standaloneShimmerEffect: StandaloneShimmerEffect?
|
||||
|
||||
private var boundsChangeTrackerLayer = SimpleLayer()
|
||||
|
||||
private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:]
|
||||
private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:]
|
||||
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let subtitleNode: ImmediateTextNode
|
||||
private let buttonNode: HighlightableButtonNode
|
||||
|
||||
override init() {
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.subtitleNode = ImmediateTextNode()
|
||||
self.buttonNode = HighlightableButtonNode(pointerStyle: nil)
|
||||
self.buttonNode.clipsToBounds = true
|
||||
self.buttonNode.cornerRadius = 14.0
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
|
||||
if item.isInstalled == true {
|
||||
item.interaction.removeStickerPack(item.info)
|
||||
} else {
|
||||
item.interaction.addStickerPack(item.info, item.items)
|
||||
}
|
||||
}
|
||||
|
||||
override var isVisibleInGrid: Bool {
|
||||
didSet {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let shimmerHostView = PortalSourceView()
|
||||
self.shimmerHostView = shimmerHostView
|
||||
|
||||
let standaloneShimmerEffect = StandaloneShimmerEffect()
|
||||
self.standaloneShimmerEffect = standaloneShimmerEffect
|
||||
|
||||
shimmerHostView.alpha = 0.0
|
||||
self.view.addSubview(shimmerHostView)
|
||||
|
||||
let boundsChangeTrackerLayer = SimpleLayer()
|
||||
boundsChangeTrackerLayer.opacity = 0.0
|
||||
self.layer.addSublayer(boundsChangeTrackerLayer)
|
||||
boundsChangeTrackerLayer.didEnterHierarchy = { [weak self] in
|
||||
self?.standaloneShimmerEffect?.updateLayer()
|
||||
}
|
||||
self.boundsChangeTrackerLayer = boundsChangeTrackerLayer
|
||||
}
|
||||
|
||||
private var setupTimestamp: Double?
|
||||
func setup(item: StickerPackEmojisItem) {
|
||||
self.item = item
|
||||
|
||||
if let title = item.title {
|
||||
let isInstalled = item.isInstalled ?? false
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: item.theme.actionSheet.primaryTextColor, paragraphAlignment: .natural)
|
||||
self.subtitleNode.attributedText = NSAttributedString(string: item.strings.EmojiPack_Emoji(Int32(item.items.count)), font: Font.regular(15.0), textColor: item.theme.actionSheet.secondaryTextColor, paragraphAlignment: .natural)
|
||||
|
||||
self.buttonNode.setAttributedTitle(NSAttributedString(string: isInstalled ? item.strings.EmojiPack_Added.uppercased() : item.strings.EmojiPack_Add.uppercased(), font: Font.semibold(15.0), textColor: isInstalled ? item.theme.list.itemCheckColors.fillColor : item.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center), for: .normal)
|
||||
self.buttonNode.backgroundColor = isInstalled ? item.theme.list.itemCheckColors.fillColor.withAlphaComponent(0.08) : item.theme.list.itemCheckColors.fillColor
|
||||
}
|
||||
|
||||
self.updateVisibleItems(attemptSynchronousLoads: false, transition: .immediate)
|
||||
|
||||
let shimmerBackgroundColor = item.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.08)
|
||||
let shimmerForegroundColor = item.theme.list.itemBlocksBackgroundColor.withMultipliedAlpha(0.15)
|
||||
self.standaloneShimmerEffect?.update(background: shimmerBackgroundColor, foreground: shimmerForegroundColor)
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
func updateVisibleItems(attemptSynchronousLoads: Bool, transition: ContainedViewLayoutTransition) {
|
||||
guard let item = self.item, !self.frame.width.isZero else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = item.context
|
||||
let animationCache = item.animationCache
|
||||
let animationRenderer = item.animationRenderer
|
||||
let theme = item.theme
|
||||
let items = item.items
|
||||
var validIds = Set<EmojiPagerContentComponent.View.ItemLayer.Key>()
|
||||
|
||||
let itemLayout: ItemLayout
|
||||
if let current = self.itemLayout, current.width == self.frame.width && current.itemsCount == items.count && current.hasTitle == (item.title != nil) {
|
||||
itemLayout = current
|
||||
} else {
|
||||
itemLayout = ItemLayout(width: self.frame.width, itemsCount: items.count, hasTitle: item.title != nil)
|
||||
self.itemLayout = itemLayout
|
||||
}
|
||||
|
||||
for index in 0 ..< items.count {
|
||||
let item = items[index]
|
||||
let itemId = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: 0, fileId: item.file.fileId, staticEmoji: nil)
|
||||
validIds.insert(itemId)
|
||||
|
||||
let itemDimensions = item.file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
|
||||
let itemNativeFitSize = itemDimensions.fitted(CGSize(width: nativeItemSize, height: nativeItemSize))
|
||||
let itemVisibleFitSize = itemDimensions.fitted(CGSize(width: itemLayout.visibleItemSize, height: itemLayout.visibleItemSize))
|
||||
|
||||
var updateItemLayerPlaceholder = false
|
||||
var itemTransition = transition
|
||||
let itemLayer: EmojiPagerContentComponent.View.ItemLayer
|
||||
if let current = self.visibleItemLayers[itemId] {
|
||||
itemLayer = current
|
||||
} else {
|
||||
updateItemLayerPlaceholder = true
|
||||
itemTransition = .immediate
|
||||
|
||||
itemLayer = EmojiPagerContentComponent.View.ItemLayer(
|
||||
item: EmojiPagerContentComponent.Item(file: item.file, staticEmoji: nil, subgroupId: nil),
|
||||
context: context,
|
||||
groupId: "pack-\(Int(nativeItemSize))",
|
||||
attemptSynchronousLoad: attemptSynchronousLoads,
|
||||
file: item.file,
|
||||
staticEmoji: nil,
|
||||
cache: animationCache,
|
||||
renderer: animationRenderer,
|
||||
placeholderColor: theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1),
|
||||
blurredBadgeColor: theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(0.5),
|
||||
displayPremiumBadgeIfAvailable: false,
|
||||
pointSize: itemNativeFitSize,
|
||||
onUpdateDisplayPlaceholder: { [weak self] displayPlaceholder in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if displayPlaceholder {
|
||||
if let itemLayer = strongSelf.visibleItemLayers[itemId] {
|
||||
let placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView
|
||||
if let current = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
placeholderView = current
|
||||
} else {
|
||||
placeholderView = EmojiPagerContentComponent.View.ItemPlaceholderView(
|
||||
context: context,
|
||||
file: item.file,
|
||||
shimmerView: strongSelf.shimmerHostView,
|
||||
color: nil,
|
||||
size: itemNativeFitSize
|
||||
)
|
||||
strongSelf.visibleItemPlaceholderViews[itemId] = placeholderView
|
||||
strongSelf.view.insertSubview(placeholderView, at: 0)
|
||||
}
|
||||
placeholderView.frame = itemLayer.frame
|
||||
placeholderView.update(size: placeholderView.bounds.size)
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
} else {
|
||||
if let placeholderView = strongSelf.visibleItemPlaceholderViews[itemId] {
|
||||
strongSelf.visibleItemPlaceholderViews.removeValue(forKey: itemId)
|
||||
placeholderView.removeFromSuperview()
|
||||
|
||||
strongSelf.updateShimmerIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
self.layer.addSublayer(itemLayer)
|
||||
self.visibleItemLayers[itemId] = itemLayer
|
||||
}
|
||||
|
||||
var itemFrame = itemLayout.frame(itemIndex: index)
|
||||
|
||||
itemFrame.origin.x += floor((itemFrame.width - itemVisibleFitSize.width) / 2.0)
|
||||
itemFrame.origin.y += floor((itemFrame.height - itemVisibleFitSize.height) / 2.0)
|
||||
itemFrame.size = itemVisibleFitSize
|
||||
|
||||
let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
|
||||
let itemBounds = CGRect(origin: CGPoint(), size: itemFrame.size)
|
||||
itemTransition.updatePosition(layer: itemLayer, position: itemPosition)
|
||||
itemTransition.updateBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
|
||||
if let placeholderView = self.visibleItemPlaceholderViews[itemId] {
|
||||
if placeholderView.layer.position != itemPosition || placeholderView.layer.bounds != itemBounds {
|
||||
itemTransition.updateFrame(view: placeholderView, frame: itemFrame)
|
||||
placeholderView.update(size: itemFrame.size)
|
||||
}
|
||||
} else if updateItemLayerPlaceholder {
|
||||
if itemLayer.displayPlaceholder {
|
||||
itemLayer.onUpdateDisplayPlaceholder(true)
|
||||
}
|
||||
}
|
||||
|
||||
itemLayer.isVisibleForAnimations = true
|
||||
}
|
||||
}
|
||||
|
||||
private func updateShimmerIfNeeded() {
|
||||
if self.visibleItemPlaceholderViews.isEmpty {
|
||||
self.standaloneShimmerEffect?.layer = nil
|
||||
} else {
|
||||
self.standaloneShimmerEffect?.layer = self.shimmerHostView?.layer
|
||||
}
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
if let _ = self.item {
|
||||
var buttonSize = self.buttonNode.calculateSizeThatFits(self.frame.size)
|
||||
buttonSize.width += 24.0
|
||||
buttonSize.height = 28.0
|
||||
|
||||
let titleSize = self.titleNode.updateLayout(CGSize(width: self.frame.width - 60.0, height: self.frame.height))
|
||||
let subtitleSize = self.subtitleNode.updateLayout(CGSize(width: self.frame.width - 60.0, height: self.frame.height))
|
||||
|
||||
self.titleNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 10.0), size: titleSize)
|
||||
self.subtitleNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 33.0), size: subtitleSize)
|
||||
|
||||
self.buttonNode.frame = CGRect(origin: CGPoint(x: self.frame.width - buttonSize.width - 16.0, y: 17.0), size: buttonSize)
|
||||
}
|
||||
|
||||
self.shimmerHostView?.frame = CGRect(origin: CGPoint(), size: self.frame.size)
|
||||
self.updateVisibleItems(attemptSynchronousLoads: false, transition: .immediate)
|
||||
}
|
||||
|
||||
func transitionNode() -> ASDisplayNode? {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import ActivityIndicator
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import ContextUI
|
||||
import StickerPeekUI
|
||||
|
||||
private struct StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
let index: Int
|
||||
@ -142,7 +143,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol
|
||||
|
||||
super.init()
|
||||
|
||||
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false)
|
||||
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in })
|
||||
|
||||
self.backgroundColor = nil
|
||||
self.isOpaque = false
|
||||
|
@ -11,13 +11,19 @@ import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import TelegramPresentationData
|
||||
import ShimmerEffect
|
||||
import StickerPeekUI
|
||||
|
||||
final class StickerPackPreviewInteraction {
|
||||
var previewedItem: StickerPreviewPeekItem?
|
||||
var playAnimatedStickers: Bool
|
||||
|
||||
init(playAnimatedStickers: Bool) {
|
||||
let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void
|
||||
let removeStickerPack: (StickerPackCollectionInfo) -> Void
|
||||
|
||||
init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void) {
|
||||
self.playAnimatedStickers = playAnimatedStickers
|
||||
self.addStickerPack = addStickerPack
|
||||
self.removeStickerPack = removeStickerPack
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,14 +18,20 @@ import TextFormat
|
||||
import PremiumUI
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
import StickerPeekUI
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
|
||||
private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool)
|
||||
case emojis(index: Int, stableId: Int, info: StickerPackCollectionInfo, items: [StickerPackItem], title: String?, isInstalled: Bool?)
|
||||
|
||||
var stableId: Int {
|
||||
switch self {
|
||||
case let .sticker(_, stableId, _, _, _, _):
|
||||
return stableId
|
||||
case let .emojis(_, stableId, _, _, _, _):
|
||||
return stableId
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,6 +39,8 @@ private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
switch self {
|
||||
case let .sticker(index, _, _, _, _, _):
|
||||
return index
|
||||
case let .emojis(index, _, _, _, _, _):
|
||||
return index
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,10 +48,12 @@ private enum StickerPackPreviewGridEntry: Comparable, Identifiable {
|
||||
return lhs.index < rhs.index
|
||||
}
|
||||
|
||||
func item(account: Account, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings) -> GridItem {
|
||||
func item(context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) -> GridItem {
|
||||
switch self {
|
||||
case let .sticker(_, _, stickerItem, isEmpty, isPremium, isLocked):
|
||||
return StickerPackPreviewGridItem(account: account, stickerItem: stickerItem, interaction: interaction, theme: theme, isPremium: isPremium, isLocked: isLocked, isEmpty: isEmpty)
|
||||
return StickerPackPreviewGridItem(account: context.account, stickerItem: stickerItem, interaction: interaction, theme: theme, isPremium: isPremium, isLocked: isLocked, isEmpty: isEmpty)
|
||||
case let .emojis(_, _, info, items, title, isInstalled):
|
||||
return StickerPackEmojisItem(context: context, animationCache: animationCache, animationRenderer: animationRenderer, interaction: interaction, info: info, items: items, theme: theme, strings: strings, title: title, isInstalled: isInstalled, isEmpty: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,12 +64,12 @@ private struct StickerPackPreviewGridTransaction {
|
||||
let updates: [GridNodeUpdateItem]
|
||||
let scrollToItem: GridNodeScrollToItem?
|
||||
|
||||
init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], account: Account, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, scrollToItem: GridNodeScrollToItem?) {
|
||||
init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], context: AccountContext, interaction: StickerPackPreviewInteraction, theme: PresentationTheme, strings: PresentationStrings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, scrollToItem: GridNodeScrollToItem?) {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list)
|
||||
|
||||
self.deletions = deleteIndices
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction, theme: theme, strings: strings), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction, theme: theme, strings: strings)) }
|
||||
self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer), previousIndex: $0.2) }
|
||||
self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(context: context, interaction: interaction, theme: theme, strings: strings, animationCache: animationCache, animationRenderer: animationRenderer)) }
|
||||
|
||||
self.scrollToItem = scrollToItem
|
||||
}
|
||||
@ -81,7 +91,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
private let context: AccountContext
|
||||
private weak var controller: StickerPackScreenImpl?
|
||||
private var presentationData: PresentationData
|
||||
private let stickerPack: StickerPackReference
|
||||
private let stickerPacks: [StickerPackReference]
|
||||
private let decideNextAction: (StickerPackContainer, StickerPackAction) -> StickerPackNextAction
|
||||
private let requestDismiss: () -> Void
|
||||
private let presentInGlobalOverlay: (ViewController, Any?) -> Void
|
||||
@ -108,7 +118,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
private var enqueuedTransactions: [StickerPackPreviewGridTransaction] = []
|
||||
|
||||
private var itemsDisposable: Disposable?
|
||||
private var currentContents: [LoadedStickerPack]?
|
||||
private(set) var currentStickerPack: (StickerPackCollectionInfo, [StickerPackItem], Bool)?
|
||||
private(set) var currentStickerPacks: [(StickerPackCollectionInfo, [StickerPackItem], Bool)] = []
|
||||
private var didReceiveStickerPackResult = false
|
||||
|
||||
private let isReadyValue = Promise<Bool>()
|
||||
@ -129,12 +141,16 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
private weak var peekController: PeekController?
|
||||
|
||||
init(index: Int, context: AccountContext, presentationData: PresentationData, stickerPack: StickerPackReference, decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, openMention: @escaping (String) -> Void, controller: StickerPackScreenImpl?) {
|
||||
var onLoading: () -> Void = {}
|
||||
var onReady: () -> Void = {}
|
||||
var onError: () -> Void = {}
|
||||
|
||||
init(index: Int, context: AccountContext, presentationData: PresentationData, stickerPacks: [StickerPackReference], decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, openMention: @escaping (String) -> Void, controller: StickerPackScreenImpl?) {
|
||||
self.index = index
|
||||
self.context = context
|
||||
self.controller = controller
|
||||
self.presentationData = presentationData
|
||||
self.stickerPack = stickerPack
|
||||
self.stickerPacks = stickerPacks
|
||||
self.decideNextAction = decideNextAction
|
||||
self.requestDismiss = requestDismiss
|
||||
self.presentInGlobalOverlay = presentInGlobalOverlay
|
||||
@ -183,7 +199,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.moreButtonNode = MoreButtonNode(theme: self.presentationData.theme)
|
||||
self.moreButtonNode.iconNode.enqueueState(.more, animated: false)
|
||||
|
||||
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true)
|
||||
var addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> Void)?
|
||||
var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)?
|
||||
self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true, addStickerPack: { info, items in
|
||||
addStickerPackImpl?(info, items)
|
||||
}, removeStickerPack: { info in
|
||||
removeStickerPackImpl?(info)
|
||||
})
|
||||
|
||||
super.init()
|
||||
|
||||
@ -301,7 +323,11 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
return updatedOffset
|
||||
}
|
||||
|
||||
self.itemsDisposable = combineLatest(queue: Queue.mainQueue(), context.engine.stickers.loadedStickerPack(reference: stickerPack, forceActualized: false), context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))).start(next: { [weak self] contents, peer in
|
||||
let loadedStickerPacks = combineLatest(stickerPacks.map {
|
||||
context.engine.stickers.loadedStickerPack(reference: $0, forceActualized: false)
|
||||
})
|
||||
|
||||
self.itemsDisposable = combineLatest(queue: Queue.mainQueue(), loadedStickerPacks, context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))).start(next: { [weak self] contents, peer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -335,6 +361,40 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
}
|
||||
|
||||
self.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
|
||||
|
||||
addStickerPackImpl = { [weak self] info, items in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let index = strongSelf.currentStickerPacks.firstIndex(where: { $0.0.id == info.id }) {
|
||||
strongSelf.currentStickerPacks[index].2 = true
|
||||
|
||||
var contents: [LoadedStickerPack] = []
|
||||
for (info, items, isInstalled) in strongSelf.currentStickerPacks {
|
||||
contents.append(.result(info: info, items: items, installed: isInstalled))
|
||||
}
|
||||
strongSelf.updateStickerPackContents(contents, hasPremium: false)
|
||||
|
||||
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start()
|
||||
}
|
||||
}
|
||||
|
||||
removeStickerPackImpl = { [weak self] info in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let index = strongSelf.currentStickerPacks.firstIndex(where: { $0.0.id == info.id }) {
|
||||
strongSelf.currentStickerPacks[index].2 = false
|
||||
|
||||
var contents: [LoadedStickerPack] = []
|
||||
for (info, items, isInstalled) in strongSelf.currentStickerPacks {
|
||||
contents.append(.result(info: info, items: items, installed: isInstalled))
|
||||
}
|
||||
strongSelf.updateStickerPackContents(contents, hasPremium: false)
|
||||
|
||||
let _ = strongSelf.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -442,7 +502,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
self.titleNode.linkHighlightColor = self.presentationData.theme.actionSheet.controlAccentColor.withAlphaComponent(0.5)
|
||||
|
||||
if let currentContents = self.currentContents {
|
||||
if let currentContents = self.currentContents?.first {
|
||||
let buttonColor: UIColor
|
||||
var buttonFont: UIFont = Font.semibold(17.0)
|
||||
switch currentContents {
|
||||
@ -459,8 +519,8 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.buttonNode.setTitle(self.buttonNode.attributedTitle(for: .normal)?.string ?? "", with: buttonFont, with: buttonColor, for: .normal)
|
||||
}
|
||||
|
||||
if !self.currentEntries.isEmpty {
|
||||
let transaction = StickerPackPreviewGridTransaction(previousList: self.currentEntries, list: self.currentEntries, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, scrollToItem: nil)
|
||||
if !self.currentEntries.isEmpty, let controller = self.controller {
|
||||
let transaction = StickerPackPreviewGridTransaction(previousList: self.currentEntries, list: self.currentEntries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: nil)
|
||||
self.enqueueTransaction(transaction)
|
||||
}
|
||||
|
||||
@ -476,12 +536,37 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc private func morePressed(node: ContextReferenceContentNode, gesture: ContextGesture?) {
|
||||
guard let controller = self.controller, let (info, _, _) = self.currentStickerPack else {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
let strings = self.presentationData.strings
|
||||
|
||||
let link = "https://t.me/addstickers/\(info.shortName)"
|
||||
let text: String
|
||||
let shareSubject: ShareControllerSubject
|
||||
if !self.currentStickerPacks.isEmpty {
|
||||
var links: String = ""
|
||||
for (info, _, _) in self.currentStickerPacks {
|
||||
if !links.isEmpty {
|
||||
links += "\n"
|
||||
}
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
links += "https://t.me/addemoji/\(info.shortName)"
|
||||
} else {
|
||||
links += "https://t.me/addstickers/\(info.shortName)"
|
||||
}
|
||||
}
|
||||
text = links
|
||||
shareSubject = .text(text)
|
||||
} else if let (info, _, _) = self.currentStickerPack {
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = "https://t.me/addemoji/\(info.shortName)"
|
||||
} else {
|
||||
text = "https://t.me/addstickers/\(info.shortName)"
|
||||
}
|
||||
shareSubject = .url(text)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
items.append(.action(ContextMenuActionItem(text: strings.StickerPack_Share, icon: { theme in
|
||||
@ -491,7 +576,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
|
||||
if let strongSelf = self {
|
||||
let parentNavigationController = strongSelf.controller?.parentNavigationController
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .url(link))
|
||||
let shareController = ShareController(context: strongSelf.context, subject: shareSubject)
|
||||
shareController.actionCompleted = { [weak parentNavigationController] in
|
||||
if let parentNavigationController = parentNavigationController, let controller = parentNavigationController.topViewController as? ViewController {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -506,7 +591,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
}, action: { [weak self] _, f in
|
||||
f(.default)
|
||||
|
||||
UIPasteboard.general.string = link
|
||||
UIPasteboard.general.string = text
|
||||
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
@ -523,30 +608,50 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
guard let (info, items, installed) = self.currentStickerPack else {
|
||||
self.requestDismiss()
|
||||
return
|
||||
}
|
||||
|
||||
let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings])
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
if !self.currentStickerPacks.isEmpty {
|
||||
var installedCount = 0
|
||||
for (_, _, isInstalled) in self.currentStickerPacks {
|
||||
if isInstalled {
|
||||
installedCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
if installedCount == self.currentStickerPacks.count {
|
||||
for (info, _, _) in self.currentStickerPacks {
|
||||
let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
// guard let (positionInList, _) = indexAndItems else {
|
||||
// return
|
||||
// }
|
||||
// if dismissed {
|
||||
// actionPerformed?(info, items, .remove(positionInList: positionInList))
|
||||
// }
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for (info, items, isInstalled) in self.currentStickerPacks {
|
||||
if !isInstalled {
|
||||
let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start()
|
||||
// if dismissed {
|
||||
// actionPerformed?(info, items, .add)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
self.requestDismiss()
|
||||
} else if let (info, items, installed) = self.currentStickerPack {
|
||||
var dismissed = false
|
||||
switch strongSelf.decideNextAction(strongSelf, installed ? .remove : .add) {
|
||||
switch self.decideNextAction(self, installed ? .remove : .add) {
|
||||
case .dismiss:
|
||||
strongSelf.requestDismiss()
|
||||
self.requestDismiss()
|
||||
dismissed = true
|
||||
case .navigatedNext, .ignored:
|
||||
strongSelf.updateStickerPackContents(.result(info: info, items: items, installed: !installed), hasPremium: false)
|
||||
self.updateStickerPackContents([.result(info: info, items: items, installed: !installed)], hasPremium: false)
|
||||
}
|
||||
|
||||
let actionPerformed = strongSelf.controller?.actionPerformed
|
||||
let actionPerformed = self.controller?.actionPerformed
|
||||
if installed {
|
||||
let _ = (strongSelf.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
|
||||
let _ = (self.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete)
|
||||
|> deliverOnMainQueue).start(next: { indexAndItems in
|
||||
guard let (positionInList, _) = indexAndItems else {
|
||||
return
|
||||
@ -556,12 +661,14 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let _ = strongSelf.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start()
|
||||
let _ = self.context.engine.stickers.addStickerPackInteractively(info: info, items: items).start()
|
||||
if dismissed {
|
||||
actionPerformed?(info, items, .add)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateButtonBackgroundAlpha() {
|
||||
@ -579,12 +686,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.actionAreaSeparatorNode.alpha = backgroundAlpha
|
||||
}
|
||||
|
||||
var onLoading: () -> Void = {}
|
||||
var onReady: () -> Void = {}
|
||||
var onError: () -> Void = {}
|
||||
|
||||
private var currentContents: LoadedStickerPack?
|
||||
private func updateStickerPackContents(_ contents: LoadedStickerPack, hasPremium: Bool) {
|
||||
private func updateStickerPackContents(_ contents: [LoadedStickerPack], hasPremium: Bool) {
|
||||
self.currentContents = contents
|
||||
self.didReceiveStickerPackResult = true
|
||||
|
||||
@ -593,139 +695,208 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
var updateLayout = false
|
||||
|
||||
var scrollToItem: GridNodeScrollToItem?
|
||||
let titleFont = Font.semibold(17.0)
|
||||
|
||||
switch contents {
|
||||
case .fetching:
|
||||
if contents.count > 1 {
|
||||
self.onLoading()
|
||||
entries = []
|
||||
self.buttonNode.setTitle(self.presentationData.strings.Channel_NotificationLoading, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemDisabledTextColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
|
||||
for _ in 0 ..< 16 {
|
||||
var stableId: Int?
|
||||
inner: for entry in self.currentEntries {
|
||||
if case let .sticker(index, currentStableId, stickerItem, _, _, _) = entry, stickerItem == nil, index == entries.count {
|
||||
stableId = currentStableId
|
||||
break inner
|
||||
var loadedCount = 0
|
||||
var error = false
|
||||
for content in contents {
|
||||
if case .result = content {
|
||||
loadedCount += 1
|
||||
} else if case .none = content {
|
||||
error = true
|
||||
}
|
||||
}
|
||||
|
||||
if error {
|
||||
self.onError()
|
||||
} else if loadedCount == contents.count {
|
||||
self.onReady()
|
||||
|
||||
if !contents.isEmpty && self.currentStickerPack == nil {
|
||||
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false)
|
||||
}
|
||||
}
|
||||
|
||||
if self.titleNode.attributedText == nil {
|
||||
if let titlePlaceholderNode = self.titlePlaceholderNode {
|
||||
self.titlePlaceholderNode = nil
|
||||
titlePlaceholderNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedStableId: Int
|
||||
if let stableId = stableId {
|
||||
resolvedStableId = stableId
|
||||
} else {
|
||||
resolvedStableId = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
}
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.EmojiPack_Title, font: titleFont, textColor: self.presentationData.theme.actionSheet.primaryTextColor, paragraphAlignment: .center)
|
||||
updateLayout = true
|
||||
|
||||
self.nextStableId += 1
|
||||
entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: nil, isEmpty: false, isPremium: false, isLocked: false))
|
||||
}
|
||||
if self.titlePlaceholderNode == nil {
|
||||
let titlePlaceholderNode = ShimmerEffectNode()
|
||||
self.titlePlaceholderNode = titlePlaceholderNode
|
||||
self.titleContainer.addSubnode(titlePlaceholderNode)
|
||||
}
|
||||
case .none:
|
||||
self.onError()
|
||||
self.controller?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
self.controller?.dismiss(animated: true, completion: nil)
|
||||
case let .result(info, items, installed):
|
||||
self.onReady()
|
||||
if !items.isEmpty && self.currentStickerPack == nil {
|
||||
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false)
|
||||
}
|
||||
}
|
||||
|
||||
self.currentStickerPack = (info, items, installed)
|
||||
|
||||
if self.titleNode.attributedText == nil {
|
||||
if let titlePlaceholderNode = self.titlePlaceholderNode {
|
||||
self.titlePlaceholderNode = nil
|
||||
titlePlaceholderNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
let titleFont = Font.semibold(17.0)
|
||||
let entities = generateTextEntities(info.title, enabledTypes: [.mention])
|
||||
self.titleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont, message: nil)
|
||||
|
||||
updateLayout = true
|
||||
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
var generalItems: [StickerPackItem] = []
|
||||
var premiumItems: [StickerPackItem] = []
|
||||
|
||||
for item in items {
|
||||
if item.file.isPremiumSticker {
|
||||
premiumItems.append(item)
|
||||
} else {
|
||||
generalItems.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
let addItem: (StickerPackItem, Bool, Bool) -> Void = { item, isPremium, isLocked in
|
||||
var stableId: Int?
|
||||
inner: for entry in self.currentEntries {
|
||||
if case let .sticker(_, currentStableId, stickerItem, _, _, _) = entry, let stickerItem = stickerItem, stickerItem.file.fileId == item.file.fileId {
|
||||
stableId = currentStableId
|
||||
break inner
|
||||
var currentStickerPacks: [(StickerPackCollectionInfo, [StickerPackItem], Bool)] = []
|
||||
|
||||
var index = 0
|
||||
var installedCount = 0
|
||||
for content in contents {
|
||||
if case let .result(info, items, isInstalled) = content {
|
||||
entries.append(.emojis(index: index, stableId: index, info: info, items: items, title: info.title, isInstalled: isInstalled))
|
||||
if isInstalled {
|
||||
installedCount += 1
|
||||
}
|
||||
currentStickerPacks.append((info, items, isInstalled))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
let resolvedStableId: Int
|
||||
if let stableId = stableId {
|
||||
resolvedStableId = stableId
|
||||
self.currentStickerPacks = currentStickerPacks
|
||||
|
||||
if installedCount == contents.count {
|
||||
let text = self.presentationData.strings.StickerPack_RemoveEmojiPacksCount(Int32(contents.count))
|
||||
self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
} else {
|
||||
resolvedStableId = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
}
|
||||
entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: item, isEmpty: false, isPremium: isPremium, isLocked: isLocked))
|
||||
}
|
||||
|
||||
for item in generalItems {
|
||||
addItem(item, false, false)
|
||||
}
|
||||
|
||||
if !premiumConfiguration.isPremiumDisabled {
|
||||
if !premiumItems.isEmpty {
|
||||
for item in premiumItems {
|
||||
addItem(item, true, !hasPremium)
|
||||
}
|
||||
let text = self.presentationData.strings.StickerPack_AddEmojiPacksCount(Int32(contents.count - installedCount))
|
||||
self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal)
|
||||
let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11)
|
||||
self.buttonNode.setBackgroundImage(roundedAccentBackground, for: [])
|
||||
}
|
||||
}
|
||||
|
||||
if installed {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveStickerCount(Int32(entries.count))
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveEmojiCount(Int32(entries.count))
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_RemoveMaskCount(Int32(entries.count))
|
||||
}
|
||||
self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal)
|
||||
} else if let contents = contents.first {
|
||||
switch contents {
|
||||
case .fetching:
|
||||
self.onLoading()
|
||||
entries = []
|
||||
self.buttonNode.setTitle(self.presentationData.strings.Channel_NotificationLoading, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemDisabledTextColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
} else {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddStickerCount(Int32(entries.count))
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddEmojiCount(Int32(entries.count))
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_AddMaskCount(Int32(entries.count))
|
||||
|
||||
for _ in 0 ..< 16 {
|
||||
var stableId: Int?
|
||||
inner: for entry in self.currentEntries {
|
||||
if case let .sticker(index, currentStableId, stickerItem, _, _, _) = entry, stickerItem == nil, index == entries.count {
|
||||
stableId = currentStableId
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
let resolvedStableId: Int
|
||||
if let stableId = stableId {
|
||||
resolvedStableId = stableId
|
||||
} else {
|
||||
resolvedStableId = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
}
|
||||
|
||||
self.nextStableId += 1
|
||||
entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: nil, isEmpty: false, isPremium: false, isLocked: false))
|
||||
}
|
||||
if self.titlePlaceholderNode == nil {
|
||||
let titlePlaceholderNode = ShimmerEffectNode()
|
||||
self.titlePlaceholderNode = titlePlaceholderNode
|
||||
self.titleContainer.addSubnode(titlePlaceholderNode)
|
||||
}
|
||||
case .none:
|
||||
self.onError()
|
||||
self.controller?.present(textAlertController(context: self.context, title: nil, text: self.presentationData.strings.StickerPack_ErrorNotFound, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
self.controller?.dismiss(animated: true, completion: nil)
|
||||
case let .result(info, items, installed):
|
||||
self.onReady()
|
||||
if !items.isEmpty && self.currentStickerPack == nil {
|
||||
if let _ = self.validLayout, abs(self.expandScrollProgress - 1.0) < .ulpOfOne {
|
||||
scrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: false)
|
||||
}
|
||||
}
|
||||
|
||||
self.currentStickerPack = (info, items, installed)
|
||||
|
||||
if self.titleNode.attributedText == nil {
|
||||
if let titlePlaceholderNode = self.titlePlaceholderNode {
|
||||
self.titlePlaceholderNode = nil
|
||||
titlePlaceholderNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
|
||||
let entities = generateTextEntities(info.title, enabledTypes: [.mention])
|
||||
self.titleNode.attributedText = stringWithAppliedEntities(info.title, entities: entities, baseColor: self.presentationData.theme.actionSheet.primaryTextColor, linkColor: self.presentationData.theme.actionSheet.controlAccentColor, baseFont: titleFont, linkFont: titleFont, boldFont: titleFont, italicFont: titleFont, boldItalicFont: titleFont, fixedFont: titleFont, blockQuoteFont: titleFont, message: nil)
|
||||
|
||||
updateLayout = true
|
||||
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
entries.append(.emojis(index: 0, stableId: 0, info: info, items: items, title: nil, isInstalled: nil))
|
||||
} else {
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
var generalItems: [StickerPackItem] = []
|
||||
var premiumItems: [StickerPackItem] = []
|
||||
|
||||
for item in items {
|
||||
if item.file.isPremiumSticker {
|
||||
premiumItems.append(item)
|
||||
} else {
|
||||
generalItems.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
let addItem: (StickerPackItem, Bool, Bool) -> Void = { item, isPremium, isLocked in
|
||||
var stableId: Int?
|
||||
inner: for entry in self.currentEntries {
|
||||
if case let .sticker(_, currentStableId, stickerItem, _, _, _) = entry, let stickerItem = stickerItem, stickerItem.file.fileId == item.file.fileId {
|
||||
stableId = currentStableId
|
||||
break inner
|
||||
}
|
||||
}
|
||||
let resolvedStableId: Int
|
||||
if let stableId = stableId {
|
||||
resolvedStableId = stableId
|
||||
} else {
|
||||
resolvedStableId = self.nextStableId
|
||||
self.nextStableId += 1
|
||||
}
|
||||
entries.append(.sticker(index: entries.count, stableId: resolvedStableId, stickerItem: item, isEmpty: false, isPremium: isPremium, isLocked: isLocked))
|
||||
}
|
||||
|
||||
for item in generalItems {
|
||||
addItem(item, false, false)
|
||||
}
|
||||
|
||||
if !premiumConfiguration.isPremiumDisabled {
|
||||
if !premiumItems.isEmpty {
|
||||
for item in premiumItems {
|
||||
addItem(item, true, !hasPremium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if installed {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveStickerCount(Int32(entries.count))
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_RemoveEmojiCount(Int32(items.count))
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_RemoveMaskCount(Int32(entries.count))
|
||||
}
|
||||
self.buttonNode.setTitle(text, with: Font.regular(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal)
|
||||
self.buttonNode.setBackgroundImage(nil, for: [])
|
||||
} else {
|
||||
let text: String
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddStickerCount(Int32(entries.count))
|
||||
} else if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
text = self.presentationData.strings.StickerPack_AddEmojiCount(Int32(items.count))
|
||||
} else {
|
||||
text = self.presentationData.strings.StickerPack_AddMaskCount(Int32(entries.count))
|
||||
}
|
||||
self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal)
|
||||
let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11)
|
||||
self.buttonNode.setBackgroundImage(roundedAccentBackground, for: [])
|
||||
}
|
||||
self.buttonNode.setTitle(text, with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal)
|
||||
let roundedAccentBackground = generateImage(CGSize(width: 22.0, height: 22.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height)))
|
||||
})?.stretchableImage(withLeftCapWidth: 11, topCapHeight: 11)
|
||||
self.buttonNode.setBackgroundImage(roundedAccentBackground, for: [])
|
||||
}
|
||||
|
||||
}
|
||||
let previousEntries = self.currentEntries
|
||||
self.currentEntries = entries
|
||||
@ -751,8 +922,10 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
self.updateLayout(layout: layout, transition: .immediate)
|
||||
}
|
||||
|
||||
let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, account: self.context.account, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, scrollToItem: scrollToItem)
|
||||
self.enqueueTransaction(transaction)
|
||||
if let controller = self.controller {
|
||||
let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, context: self.context, interaction: self.interaction, theme: self.presentationData.theme, strings: self.presentationData.strings, animationCache: controller.animationCache, animationRenderer: controller.animationRenderer, scrollToItem: scrollToItem)
|
||||
self.enqueueTransaction(transaction)
|
||||
}
|
||||
}
|
||||
|
||||
var topContentInset: CGFloat {
|
||||
@ -792,6 +965,19 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
var buttonHeight: CGFloat = 50.0
|
||||
var actionAreaTopInset: CGFloat = 8.0
|
||||
var actionAreaBottomInset: CGFloat = 16.0
|
||||
if !self.currentStickerPacks.isEmpty {
|
||||
var installedCount = 0
|
||||
for (_, _, isInstalled) in self.currentStickerPacks {
|
||||
if isInstalled {
|
||||
installedCount += 1
|
||||
}
|
||||
}
|
||||
if installedCount == self.currentStickerPacks.count {
|
||||
buttonHeight = 42.0
|
||||
actionAreaTopInset = 1.0
|
||||
actionAreaBottomInset = 2.0
|
||||
}
|
||||
}
|
||||
if let (_, _, isInstalled) = self.currentStickerPack, isInstalled {
|
||||
buttonHeight = 42.0
|
||||
actionAreaTopInset = 1.0
|
||||
@ -817,9 +1003,21 @@ private final class StickerPackContainer: ASDisplayNode {
|
||||
let itemWidth = floor(fillingWidth / CGFloat(itemsPerRow))
|
||||
let gridLeftInset = floor((layout.size.width - fillingWidth) / 2.0)
|
||||
let contentHeight: CGFloat
|
||||
if let (_, items, _) = self.currentStickerPack {
|
||||
let rowCount = items.count / itemsPerRow + ((items.count % itemsPerRow) == 0 ? 0 : 1)
|
||||
contentHeight = itemWidth * CGFloat(rowCount)
|
||||
if !self.currentStickerPacks.isEmpty {
|
||||
var packsHeight = 0.0
|
||||
for stickerPack in currentStickerPacks {
|
||||
let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count, hasTitle: true)
|
||||
packsHeight += layout.height
|
||||
}
|
||||
contentHeight = packsHeight
|
||||
} else if let (info, items, _) = self.currentStickerPack {
|
||||
if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks {
|
||||
let layout = ItemLayout(width: fillingWidth, itemsCount: items.count, hasTitle: false)
|
||||
contentHeight = layout.height
|
||||
} else {
|
||||
let rowCount = items.count / itemsPerRow + ((items.count % itemsPerRow) == 0 ? 0 : 1)
|
||||
contentHeight = itemWidth * CGFloat(rowCount)
|
||||
}
|
||||
} else {
|
||||
contentHeight = gridFrame.size.height
|
||||
}
|
||||
@ -1036,20 +1234,21 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
transition.updateFrame(node: self.containerContainingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let expandProgress: CGFloat
|
||||
if self.stickerPacks.count == 1 {
|
||||
expandProgress = 1.0
|
||||
} else {
|
||||
expandProgress = self.containers[self.selectedStickerPackIndex]?.expandProgress ?? 0.0
|
||||
}
|
||||
let expandProgress: CGFloat = 1.0
|
||||
// if self.stickerPacks.count == 1 {
|
||||
// expandProgress = 1.0
|
||||
// } else {
|
||||
// expandProgress = self.containers[self.selectedStickerPackIndex]?.expandProgress ?? 0.0
|
||||
// }
|
||||
let scaledInset: CGFloat = 12.0
|
||||
let scaledDistance: CGFloat = 4.0
|
||||
let minScale = (layout.size.width - scaledInset * 2.0) / layout.size.width
|
||||
let containerScale = expandProgress * 1.0 + (1.0 - expandProgress) * minScale
|
||||
|
||||
let containerVerticalOffset: CGFloat = (1.0 - expandProgress) * scaledInset * 2.0
|
||||
|
||||
for i in 0 ..< self.stickerPacks.count {
|
||||
|
||||
// for i in 0 ..< self.stickerPacks.count {
|
||||
let i = 0
|
||||
let indexOffset = i - self.selectedStickerPackIndex
|
||||
var scaledOffset: CGFloat = 0.0
|
||||
scaledOffset = -CGFloat(indexOffset) * (1.0 - expandProgress) * (scaledInset * 2.0) + CGFloat(indexOffset) * scaledDistance
|
||||
@ -1065,7 +1264,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
wasAdded = true
|
||||
containerTransition = .immediate
|
||||
let index = i
|
||||
container = StickerPackContainer(index: index, context: context, presentationData: self.presentationData, stickerPack: self.stickerPacks[i], decideNextAction: { [weak self] container, action in
|
||||
container = StickerPackContainer(index: index, context: context, presentationData: self.presentationData, stickerPacks: self.stickerPacks, decideNextAction: { [weak self] container, action in
|
||||
guard let strongSelf = self, let layout = strongSelf.validLayout else {
|
||||
return .dismiss
|
||||
}
|
||||
@ -1149,7 +1348,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode {
|
||||
self.containers.removeValue(forKey: i)
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
|
||||
if firstTime {
|
||||
if !self.containers.isEmpty {
|
||||
@ -1323,6 +1522,10 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
private let openMentionDisposable = MetaDisposable()
|
||||
|
||||
private var alreadyDidAppear: Bool = false
|
||||
private var animatedIn: Bool = false
|
||||
|
||||
let animationCache: AnimationCache
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, stickerPacks: [StickerPackReference], selectedStickerPackIndex: Int = 0, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? = nil) {
|
||||
self.context = context
|
||||
@ -1333,6 +1536,19 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
self.sendSticker = sendSticker
|
||||
self.actionPerformed = actionPerformed
|
||||
|
||||
self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: {
|
||||
return TempBox.shared.tempFile(fileName: "file").path
|
||||
})
|
||||
|
||||
let animationRenderer: MultiAnimationRenderer
|
||||
/*if #available(iOS 13.0, *) {
|
||||
animationRenderer = MultiAnimationMetalRendererImpl()
|
||||
} else {*/
|
||||
animationRenderer = MultiAnimationRendererImpl()
|
||||
//}
|
||||
|
||||
self.animationRenderer = animationRenderer
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
@ -1421,13 +1637,16 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
}
|
||||
|
||||
if strongSelf.alreadyDidAppear {
|
||||
strongSelf.controllerNode.animateIn()
|
||||
|
||||
} else {
|
||||
strongSelf.isReady = true
|
||||
}
|
||||
|
||||
self?.controllerNode.isHidden = false
|
||||
self?.controllerNode.animateIn()
|
||||
strongSelf.controllerNode.isHidden = false
|
||||
if !strongSelf.animatedIn {
|
||||
strongSelf.animatedIn = true
|
||||
strongSelf.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1469,6 +1688,7 @@ public final class StickerPackScreenImpl: ViewController {
|
||||
self.alreadyDidAppear = true
|
||||
|
||||
if self.isReady {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
}
|
||||
@ -1487,7 +1707,7 @@ public enum StickerPackScreenPerformedAction {
|
||||
}
|
||||
|
||||
public func StickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: StickerPackPreviewControllerMode = .default, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, actionPerformed: ((StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction) -> Void)? = nil, dismissed: (() -> Void)? = nil) -> ViewController {
|
||||
let stickerPacks = [mainStickerPack]
|
||||
//let stickerPacks = [mainStickerPack]
|
||||
let controller = StickerPackScreenImpl(context: context, stickerPacks: stickerPacks, selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, parentNavigationController: parentNavigationController, sendSticker: sendSticker, actionPerformed: actionPerformed)
|
||||
controller.dismissed = dismissed
|
||||
return controller
|
||||
|
36
submodules/StickerPeekUI/BUILD
Normal file
36
submodules/StickerPeekUI/BUILD
Normal file
@ -0,0 +1,36 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "StickerPeekUI",
|
||||
module_name = "StickerPeekUI",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
"//submodules/Display:Display",
|
||||
"//submodules/Postbox:Postbox",
|
||||
"//submodules/TelegramCore:TelegramCore",
|
||||
"//submodules/TelegramPresentationData:TelegramPresentationData",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/TelegramUIPreferences:TelegramUIPreferences",
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
"//submodules/AlertUI:AlertUI",
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/ActivityIndicator:ActivityIndicator",
|
||||
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -3502,7 +3502,7 @@ func replayFinalState(
|
||||
}
|
||||
}
|
||||
switch set {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
namespace = Namespaces.ItemCollection.CloudMaskPacks
|
||||
} else if (flags & (1 << 7)) != 0 {
|
||||
|
@ -59,16 +59,38 @@ public enum RestoreAppStoreReceiptError {
|
||||
}
|
||||
|
||||
func _internal_canPurchasePremium(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Bool, NoError> {
|
||||
return account.network.request(Api.functions.payments.canPurchasePremium())
|
||||
|> map { result -> Bool in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
return true
|
||||
case .boolFalse:
|
||||
return false
|
||||
var purposeSignal: Signal<Api.InputStorePaymentPurpose, NoError>
|
||||
switch purpose {
|
||||
case .subscription, .restore:
|
||||
var flags: Int32 = 0
|
||||
if case .restore = purpose {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
purposeSignal = .single(.inputStorePaymentPremiumSubscription(flags: flags))
|
||||
case let .gift(peerId, currency, amount):
|
||||
purposeSignal = account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer -> Signal<Api.InputStorePaymentPurpose, NoError> in
|
||||
if let inputUser = apiInputUser(peer) {
|
||||
return .single(.inputStorePaymentGiftPremium(userId: inputUser, currency: currency, amount: amount))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<Bool, NoError> in
|
||||
return.single(false)
|
||||
|
||||
return purposeSignal
|
||||
|> mapToSignal { purpose -> Signal<Bool, NoError> in
|
||||
return account.network.request(Api.functions.payments.canPurchasePremium(purpose: purpose))
|
||||
|> map { result -> Bool in
|
||||
switch result {
|
||||
case .boolTrue:
|
||||
return true
|
||||
case .boolFalse:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> `catch` { _ -> Signal<Bool, NoError> in
|
||||
return.single(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -544,7 +544,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
let stickerPack: StickerPackCollectionInfo? = stickerSet.flatMap { apiSet -> StickerPackCollectionInfo in
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
switch apiSet {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
namespace = Namespaces.ItemCollection.CloudMaskPacks
|
||||
} else if (flags & (1 << 7)) != 0 {
|
||||
|
@ -170,7 +170,7 @@ func _internal_createStickerSet(account: Account, title: String, shortName: Stri
|
||||
case let .stickerSet(set, packs, documents):
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
switch set {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
namespace = Namespaces.ItemCollection.CloudMaskPacks
|
||||
} else if (flags & (1 << 7)) != 0 {
|
||||
|
@ -51,7 +51,7 @@ func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: Sti
|
||||
case let .stickerSet(set, packs, documents):
|
||||
let namespace: ItemCollectionId.Namespace
|
||||
switch set {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
namespace = Namespaces.ItemCollection.CloudMaskPacks
|
||||
} else if (flags & (1 << 7)) != 0 {
|
||||
|
@ -32,7 +32,7 @@ func telegramStickerPackThumbnailRepresentationFromApiSizes(datacenterId: Int32,
|
||||
extension StickerPackCollectionInfo {
|
||||
convenience init(apiSet: Api.StickerSet, namespace: ItemCollectionId.Namespace) {
|
||||
switch apiSet {
|
||||
case let .stickerSet(flags, _, id, accessHash, title, shortName, thumbs, thumbDcId, thumbVersion, count, nHash):
|
||||
case let .stickerSet(flags, _, id, accessHash, title, shortName, thumbs, thumbDcId, thumbVersion, thumbDocumentId, count, nHash):
|
||||
var setFlags: StickerPackCollectionInfoFlags = StickerPackCollectionInfoFlags()
|
||||
if (flags & (1 << 2)) != 0 {
|
||||
setFlags.insert(.isOfficial)
|
||||
@ -57,6 +57,8 @@ extension StickerPackCollectionInfo {
|
||||
thumbnailRepresentation = representations.first
|
||||
immediateThumbnailData = data
|
||||
}
|
||||
|
||||
let _ = thumbDocumentId
|
||||
|
||||
self.init(id: ItemCollectionId(namespace: namespace, id: id), flags: setFlags, accessHash: accessHash, title: title, shortName: shortName, thumbnail: thumbnailRepresentation, immediateThumbnailData: immediateThumbnailData, hash: nHash, count: count)
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ func _internal_requestStickerSet(postbox: Postbox, network: Network, reference:
|
||||
info = StickerPackCollectionInfo(apiSet: set, namespace: Namespaces.ItemCollection.CloudStickerPacks)
|
||||
|
||||
switch set {
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .stickerSet(flags, _, _, _, _, _, _, _, _, _, _, _):
|
||||
installed = (flags & (1 << 0) != 0)
|
||||
}
|
||||
|
||||
|
@ -157,32 +157,46 @@ public extension TelegramEngine {
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func resolveInlineSticker(fileId: Int64) -> Signal<TelegramMediaFile?, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> TelegramMediaFile? in
|
||||
return transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile
|
||||
public func resolveInlineStickers(fileIds: [Int64]) -> Signal<[Int64: TelegramMediaFile], NoError> {
|
||||
return self.account.postbox.transaction { transaction -> [Int64: TelegramMediaFile] in
|
||||
var cachedFiles: [Int64: TelegramMediaFile] = [:]
|
||||
for fileId in fileIds {
|
||||
if let file = transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile {
|
||||
cachedFiles[fileId] = file
|
||||
}
|
||||
}
|
||||
return cachedFiles
|
||||
}
|
||||
|> mapToSignal { cachedFile -> Signal<TelegramMediaFile?, NoError> in
|
||||
if let cachedFile = cachedFile {
|
||||
return .single(cachedFile)
|
||||
|> mapToSignal { cachedFiles -> Signal<[Int64: TelegramMediaFile], NoError> in
|
||||
if cachedFiles.count == fileIds.count {
|
||||
return .single(cachedFiles)
|
||||
}
|
||||
|
||||
return self.account.network.request(Api.functions.messages.getCustomEmojiDocuments(documentId: [fileId]))
|
||||
var unknownIds = Set<Int64>()
|
||||
for fileId in fileIds {
|
||||
if cachedFiles[fileId] == nil {
|
||||
unknownIds.insert(fileId)
|
||||
}
|
||||
}
|
||||
|
||||
return self.account.network.request(Api.functions.messages.getCustomEmojiDocuments(documentId: Array(unknownIds)))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<[Api.Document]?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<TelegramMediaFile?, NoError> in
|
||||
|> mapToSignal { result -> Signal<[Int64: TelegramMediaFile], NoError> in
|
||||
guard let result = result else {
|
||||
return .single(nil)
|
||||
return .single(cachedFiles)
|
||||
}
|
||||
return self.account.postbox.transaction { transaction -> TelegramMediaFile? in
|
||||
return self.account.postbox.transaction { transaction -> [Int64: TelegramMediaFile] in
|
||||
var resultFiles: [Int64: TelegramMediaFile] = cachedFiles
|
||||
for document in result {
|
||||
if let file = telegramMediaFileFromApiDocument(document) {
|
||||
resultFiles[file.fileId.id] = file
|
||||
transaction.storeMediaIfNotPresent(media: file)
|
||||
}
|
||||
}
|
||||
|
||||
return transaction.getMedia(MediaId(namespace: Namespaces.Media.CloudFile, id: fileId)) as? TelegramMediaFile
|
||||
return resultFiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,12 +71,12 @@ public final class InlineStickerItemLayer: MultiAnimationRenderTarget {
|
||||
if let file = file {
|
||||
self.updateFile(file: file, attemptSynchronousLoad: attemptSynchronousLoad)
|
||||
} else {
|
||||
self.infoDisposable = (context.engine.stickers.resolveInlineSticker(fileId: emoji.fileId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] file in
|
||||
self.infoDisposable = (context.engine.stickers.resolveInlineStickers(fileIds: [emoji.fileId])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] files in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if let file = file {
|
||||
if let file = files[emoji.fileId] {
|
||||
strongSelf.updateFile(file: file, attemptSynchronousLoad: false)
|
||||
}
|
||||
})
|
||||
|
@ -36,7 +36,7 @@ swift_library(
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
"//submodules/ContextUI:ContextUI",
|
||||
"//submodules/PremiumUI:PremiumUI",
|
||||
"//submodules/StickerPackPreviewUI:StickerPackPreviewUI",
|
||||
"//submodules/StickerPeekUI:StickerPeekUI",
|
||||
"//submodules/UndoUI:UndoUI",
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
"//submodules/Components/SolidRoundedButtonComponent:SolidRoundedButtonComponent",
|
||||
|
@ -20,7 +20,7 @@ import StickerResources
|
||||
import AppBundle
|
||||
import ContextUI
|
||||
import PremiumUI
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import UndoUI
|
||||
import AudioToolbox
|
||||
import SolidRoundedButtonComponent
|
||||
@ -457,12 +457,12 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
final class ItemPlaceholderView: UIView {
|
||||
public final class ItemPlaceholderView: UIView {
|
||||
private let shimmerView: PortalSourceView?
|
||||
private var placeholderView: PortalView?
|
||||
private let placeholderMaskLayer: SimpleLayer
|
||||
|
||||
init(
|
||||
public init(
|
||||
context: AccountContext,
|
||||
file: TelegramMediaFile,
|
||||
shimmerView: PortalSourceView?,
|
||||
@ -503,7 +503,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(size: CGSize) {
|
||||
public func update(size: CGSize) {
|
||||
if let placeholderView = self.placeholderView {
|
||||
placeholderView.view.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
@ -511,11 +511,21 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
final class ItemLayer: MultiAnimationRenderTarget {
|
||||
struct Key: Hashable {
|
||||
public final class ItemLayer: MultiAnimationRenderTarget {
|
||||
public struct Key: Hashable {
|
||||
var groupId: AnyHashable
|
||||
var fileId: MediaId?
|
||||
var staticEmoji: String?
|
||||
|
||||
public init(
|
||||
groupId: AnyHashable,
|
||||
fileId: MediaId?,
|
||||
staticEmoji: String?
|
||||
) {
|
||||
self.groupId = groupId
|
||||
self.fileId = fileId
|
||||
self.staticEmoji = staticEmoji
|
||||
}
|
||||
}
|
||||
|
||||
let item: Item
|
||||
@ -536,10 +546,10 @@ public final class EmojiPagerContentComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
private(set) var displayPlaceholder: Bool = false
|
||||
let onUpdateDisplayPlaceholder: (Bool) -> Void
|
||||
public private(set) var displayPlaceholder: Bool = false
|
||||
public let onUpdateDisplayPlaceholder: (Bool) -> Void
|
||||
|
||||
init(
|
||||
public init(
|
||||
item: Item,
|
||||
context: AccountContext,
|
||||
groupId: String,
|
||||
@ -696,7 +706,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.fetchDisposable?.dispose()
|
||||
}
|
||||
|
||||
override public func action(forKey event: String) -> CAAction? {
|
||||
public override func action(forKey event: String) -> CAAction? {
|
||||
if event == kCAOnOrderIn {
|
||||
self.isInHierarchyValue = true
|
||||
} else if event == kCAOnOrderOut {
|
||||
@ -712,7 +722,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
self.shouldBeAnimating = shouldBePlaying
|
||||
}
|
||||
|
||||
override func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||
public override func updateDisplayPlaceholder(displayPlaceholder: Bool) {
|
||||
if self.displayPlaceholder == displayPlaceholder {
|
||||
return
|
||||
}
|
||||
@ -943,7 +953,7 @@ public final class EmojiPagerContentComponent: Component {
|
||||
switch attribute {
|
||||
case let .CustomEmoji(_, _, packReference):
|
||||
if let packReference = packReference {
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: component.inputInteraction.navigationController(), sendSticker: { file, sourceView, sourceRect in
|
||||
let controller = context.sharedContext.makeStickerPackScreen(context: context, updatedPresentationData: nil, mainStickerPack: packReference, stickerPacks: [packReference], parentNavigationController: component.inputInteraction.navigationController(), sendSticker: { file, sourceView, sourceRect in
|
||||
//return component.inputInteraction.sendSticker(file, false, false, nil, false, sourceNode, sourceRect, nil)
|
||||
return false
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ public func cacheStillSticker(path: String, width: Int, height: Int, writer: Ani
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: path)), let image = WebP.convert(fromWebP: data) {
|
||||
writer.add(with: { surface in
|
||||
let context = DrawingContext(size: CGSize(width: CGFloat(surface.width), height: CGFloat(surface.height)), scale: 1.0, opaque: false, clear: true, bytesPerRow: surface.bytesPerRow)
|
||||
context.withContext { c in
|
||||
context.withFlippedContext { c in
|
||||
UIGraphicsPushContext(c)
|
||||
c.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: context.size))
|
||||
UIGraphicsPopContext()
|
||||
|
@ -60,6 +60,16 @@ public final class TextNodeWithEntities {
|
||||
self.placeholderColor = placeholderColor
|
||||
self.attemptSynchronous = attemptSynchronous
|
||||
}
|
||||
|
||||
public func withUpdatedPlaceholderColor(_ color: UIColor) -> Arguments {
|
||||
return Arguments(
|
||||
context: self.context,
|
||||
cache: self.cache,
|
||||
renderer: self.renderer,
|
||||
placeholderColor: self.placeholderColor,
|
||||
attemptSynchronous: self.attemptSynchronous
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public let textNode: TextNode
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Arrow.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Arrow.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Size=24px, Direction=Right.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
-0.000000 1.000000 1.000000 0.000000 1.540161 6.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
-0.470226 7.930065 m
|
||||
-0.729925 7.670366 -0.729925 7.249311 -0.470226 6.989613 c
|
||||
-0.210527 6.729914 0.210527 6.729914 0.470226 6.989613 c
|
||||
-0.470226 7.930065 l
|
||||
h
|
||||
6.000000 13.459839 m
|
||||
6.470226 13.930065 l
|
||||
6.210527 14.189764 5.789473 14.189764 5.529774 13.930065 c
|
||||
6.000000 13.459839 l
|
||||
h
|
||||
11.529774 6.989613 m
|
||||
11.789473 6.729914 12.210527 6.729914 12.470226 6.989613 c
|
||||
12.729925 7.249311 12.729925 7.670366 12.470226 7.930065 c
|
||||
11.529774 6.989613 l
|
||||
h
|
||||
0.470226 6.989613 m
|
||||
6.470226 12.989613 l
|
||||
5.529774 13.930065 l
|
||||
-0.470226 7.930065 l
|
||||
0.470226 6.989613 l
|
||||
h
|
||||
5.529774 12.989613 m
|
||||
11.529774 6.989613 l
|
||||
12.470226 7.930065 l
|
||||
6.470226 13.930065 l
|
||||
5.529774 12.989613 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
783
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 24.000000 24.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000873 00000 n
|
||||
0000000895 00000 n
|
||||
0000001068 00000 n
|
||||
0000001142 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1201
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Premium/Perk/Emoji.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Perk/Emoji.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Emoji (1).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
120
submodules/TelegramUI/Images.xcassets/Premium/Perk/Emoji.imageset/Emoji (1).pdf
vendored
Normal file
120
submodules/TelegramUI/Images.xcassets/Premium/Perk/Emoji.imageset/Emoji (1).pdf
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 5.000000 5.000000 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
0.000000 10.000000 m
|
||||
0.000000 5.332758 3.197396 1.412308 7.521235 0.309603 c
|
||||
7.192342 0.790955 7.000000 1.373016 7.000000 2.000000 c
|
||||
7.000000 3.113091 l
|
||||
5.212887 3.938812 4.297566 5.421120 3.859255 6.432051 c
|
||||
3.686586 6.830300 4.118104 7.180214 4.520657 7.017826 c
|
||||
5.607882 6.579245 7.514322 6.000000 10.000001 6.000000 c
|
||||
12.485676 6.000000 14.392107 6.579244 15.479328 7.017824 c
|
||||
15.881880 7.180212 16.313400 6.830300 16.140732 6.432050 c
|
||||
15.702424 5.421119 14.787108 3.938814 13.000000 3.113092 c
|
||||
13.000000 2.000000 l
|
||||
13.000000 1.373016 12.807658 0.790955 12.478765 0.309603 c
|
||||
16.802603 1.412308 20.000000 5.332758 20.000000 10.000000 c
|
||||
20.000000 15.522848 15.522847 20.000000 10.000000 20.000000 c
|
||||
4.477152 20.000000 0.000000 15.522848 0.000000 10.000000 c
|
||||
h
|
||||
14.000001 9.250000 m
|
||||
15.242641 9.250000 16.250000 10.257360 16.250000 11.500000 c
|
||||
16.250000 12.742640 15.242641 13.750000 14.000001 13.750000 c
|
||||
12.757360 13.750000 11.750001 12.742640 11.750001 11.500000 c
|
||||
11.750001 10.257360 12.757360 9.250000 14.000001 9.250000 c
|
||||
h
|
||||
14.000001 8.250000 m
|
||||
15.794927 8.250000 17.250000 9.705074 17.250000 11.500000 c
|
||||
17.250000 13.294926 15.794927 14.750000 14.000001 14.750000 c
|
||||
12.205075 14.750000 10.750001 13.294926 10.750001 11.500000 c
|
||||
10.750001 9.705074 12.205075 8.250000 14.000001 8.250000 c
|
||||
h
|
||||
15.000001 11.500000 m
|
||||
15.000001 10.947716 14.552285 10.500000 14.000001 10.500000 c
|
||||
13.447717 10.500000 13.000001 10.947716 13.000001 11.500000 c
|
||||
13.000001 12.052284 13.447717 12.500000 14.000001 12.500000 c
|
||||
14.552285 12.500000 15.000001 12.052284 15.000001 11.500000 c
|
||||
h
|
||||
8.982612 10.447145 m
|
||||
8.552257 10.964127 7.794065 11.500000 6.500000 11.500000 c
|
||||
5.205936 11.500000 4.447744 10.964127 4.017390 10.447145 c
|
||||
3.744092 10.118834 4.103591 9.767232 4.509996 9.898819 c
|
||||
5.063556 10.078053 5.781778 10.250000 6.500000 10.250000 c
|
||||
7.218223 10.250000 7.936446 10.078053 8.490005 9.898819 c
|
||||
8.896410 9.767232 9.255910 10.118834 8.982612 10.447145 c
|
||||
h
|
||||
9.000000 5.000000 m
|
||||
8.447716 5.000000 8.000000 4.552284 8.000000 4.000000 c
|
||||
8.000000 2.000000 l
|
||||
8.000000 0.895432 8.895431 0.000000 10.000000 0.000000 c
|
||||
11.104569 0.000000 12.000000 0.895432 12.000000 2.000000 c
|
||||
12.000000 4.000000 l
|
||||
12.000000 4.552284 11.552284 5.000000 11.000000 5.000000 c
|
||||
10.714355 5.000000 10.473413 4.787300 10.437983 4.503861 c
|
||||
10.137049 2.096386 l
|
||||
10.116884 1.935076 9.883116 1.935074 9.862951 2.096386 c
|
||||
9.562017 4.503861 l
|
||||
9.526587 4.787299 9.285645 5.000000 9.000000 5.000000 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2574
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002664 00000 n
|
||||
0000002687 00000 n
|
||||
0000002860 00000 n
|
||||
0000002934 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2993
|
||||
%%EOF
|
@ -78,6 +78,8 @@ import ChatTextLinkEditUI
|
||||
import WebUI
|
||||
import PremiumUI
|
||||
import ImageTransparency
|
||||
import StickerPackPreviewUI
|
||||
import TextNodeWithEntities
|
||||
|
||||
#if DEBUG
|
||||
import os.signpost
|
||||
@ -955,6 +957,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if strongSelf.presentationInterfaceState.interfaceState.selectionState != nil {
|
||||
return
|
||||
}
|
||||
let presentationData = strongSelf.presentationData
|
||||
|
||||
strongSelf.dismissAllTooltips()
|
||||
|
||||
@ -1003,7 +1006,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
var tip: ContextController.Tip?
|
||||
|
||||
|
||||
if tip == nil {
|
||||
var isAction = false
|
||||
for media in message.media {
|
||||
@ -1036,7 +1039,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
actions.context = strongSelf.context
|
||||
|
||||
|
||||
let premiumConfiguration = PremiumConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
if canAddMessageReactions(message: topMessage), let availableReactions = availableReactions, let allowedReactions = allowedReactions {
|
||||
@ -1084,7 +1087,84 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
strongSelf.chatDisplayNode.messageTransitionNode.dismissMessageReactionContexts()
|
||||
|
||||
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message, selectAll: selectAll)), items: .single(actions), recognizer: recognizer, gesture: gesture)
|
||||
let presentationContext = strongSelf.controllerInteraction?.presentationContext
|
||||
|
||||
var disableTransitionAnimations = false
|
||||
var actionsSignal: Signal<ContextController.Items, NoError> = .single(actions)
|
||||
if actions.tip == nil, let entitiesAttribute = message.textEntitiesAttribute {
|
||||
var emojiFileIds: [Int64] = []
|
||||
for entity in entitiesAttribute.entities {
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
emojiFileIds.append(fileId)
|
||||
}
|
||||
}
|
||||
if !emojiFileIds.isEmpty {
|
||||
tip = .animatedEmoji(text: nil, arguments: nil, file: nil, action: nil)
|
||||
disableTransitionAnimations = true
|
||||
|
||||
actionsSignal = .single(actions)
|
||||
|> then(
|
||||
context.engine.stickers.resolveInlineStickers(fileIds: emojiFileIds)
|
||||
|> mapToSignal { files -> Signal<ContextController.Items, NoError> in
|
||||
var packReferences = Set<StickerPackReference>()
|
||||
for (_, file) in files {
|
||||
loop: for attribute in file.attributes {
|
||||
if case let .CustomEmoji(_, _, packReference) = attribute, let packReference = packReference {
|
||||
packReferences.insert(packReference)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let action = {
|
||||
guard let packReference = packReferences.first, let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: strongSelf.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(packReferences), parentNavigationController: strongSelf.effectiveNavigationController)
|
||||
strongSelf.present(controller, in: .window(.root))
|
||||
}
|
||||
|
||||
if packReferences.count > 1 {
|
||||
actions.tip = .animatedEmoji(text: presentationData.strings.ChatContextMenu_EmojiSet(Int32(packReferences.count)), arguments: nil, file: nil, action: action)
|
||||
return .single(actions)
|
||||
} else if let reference = packReferences.first {
|
||||
return context.engine.stickers.loadedStickerPack(reference: reference, forceActualized: false)
|
||||
|> filter { result in
|
||||
if case .result = result {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result in
|
||||
if case let .result(info, items, _) = result, let presentationContext = presentationContext {
|
||||
actions.tip = .animatedEmoji(
|
||||
text: presentationData.strings.ChatContextMenu_EmojiSetSingle(info.title).string,
|
||||
arguments: TextNodeWithEntities.Arguments(
|
||||
context: context,
|
||||
cache: presentationContext.animationCache,
|
||||
renderer: presentationContext.animationRenderer,
|
||||
placeholderColor: .clear,
|
||||
attemptSynchronous: true
|
||||
),
|
||||
file: items.first?.file,
|
||||
action: action)
|
||||
return .single(actions)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
actions.tip = nil
|
||||
return .single(actions)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let controller = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .extracted(ChatMessageContextExtractedContentSource(chatNode: strongSelf.chatDisplayNode, engine: strongSelf.context.engine, message: message, selectAll: selectAll)), items: actionsSignal, recognizer: recognizer, gesture: gesture)
|
||||
controller.immediateItemsTransitionAnimation = disableTransitionAnimations
|
||||
controller.getOverlayViews = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return []
|
||||
@ -7298,16 +7378,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
|
||||
let context = strongSelf.context
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = PremiumDemoScreen(context: context, subject: .voiceToText, action: {
|
||||
let controller = PremiumIntroScreen(context: context, source: .settings)
|
||||
replaceImpl?(controller)
|
||||
})
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
}
|
||||
strongSelf.push(controller)
|
||||
strongSelf.interfaceInteraction?.displayRestrictedInfo(.premiumVoiceMessages, .tooltip)
|
||||
return
|
||||
}
|
||||
|
||||
@ -7393,7 +7464,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
switch subject {
|
||||
case .stickers:
|
||||
subjectFlags = .banSendStickers
|
||||
case .mediaRecording:
|
||||
case .mediaRecording, .premiumVoiceMessages:
|
||||
subjectFlags = .banSendMedia
|
||||
}
|
||||
|
||||
@ -7429,6 +7500,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
} else {
|
||||
banDescription = strongSelf.presentationInterfaceState.strings.Conversation_DefaultRestrictedMedia
|
||||
}
|
||||
case .premiumVoiceMessages:
|
||||
banDescription = ""
|
||||
}
|
||||
if strongSelf.recordingModeFeedback == nil {
|
||||
strongSelf.recordingModeFeedback = HapticFeedback()
|
||||
@ -7448,7 +7521,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
rectValue.origin.y = actionRect.minY
|
||||
rect = rectValue
|
||||
}
|
||||
case .mediaRecording:
|
||||
case .mediaRecording, .premiumVoiceMessages:
|
||||
rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||
}
|
||||
|
||||
@ -7476,7 +7549,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
|
||||
if case .mediaRecording = subject, strongSelf.presentationInterfaceState.hasActiveGroupCall {
|
||||
if case .premiumVoiceMessages = subject {
|
||||
let rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||
if let rect = rect {
|
||||
strongSelf.mediaRestrictedTooltipController?.dismiss()
|
||||
let text: String
|
||||
if let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer.flatMap({ EnginePeer($0) }) {
|
||||
text = strongSelf.presentationInterfaceState.strings.Conversation_VoiceMessagesRestricted(peer.compactDisplayTitle).string
|
||||
} else {
|
||||
text = ""
|
||||
}
|
||||
let tooltipController = TooltipController(content: .text(text), baseFontSize: strongSelf.presentationData.listsFontSize.baseDisplaySize)
|
||||
strongSelf.mediaRestrictedTooltipController = tooltipController
|
||||
strongSelf.mediaRestrictedTooltipControllerMode = false
|
||||
tooltipController.dismissed = { [weak tooltipController] _ in
|
||||
if let strongSelf = self, let tooltipController = tooltipController, strongSelf.mediaRestrictedTooltipController === tooltipController {
|
||||
strongSelf.mediaRestrictedTooltipController = nil
|
||||
}
|
||||
}
|
||||
strongSelf.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: {
|
||||
if let strongSelf = self {
|
||||
return (strongSelf.chatDisplayNode, rect)
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
} else if case .mediaRecording = subject, strongSelf.presentationInterfaceState.hasActiveGroupCall {
|
||||
let rect = strongSelf.chatDisplayNode.frameForInputActionButton()
|
||||
if let rect = rect {
|
||||
strongSelf.mediaRestrictedTooltipController?.dismiss()
|
||||
|
@ -11,6 +11,7 @@ import TelegramNotices
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import PeerInfoUI
|
||||
import SettingsUI
|
||||
import ContextUI
|
||||
|
@ -10,6 +10,7 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import OverlayStatusController
|
||||
import PresentationDataUtils
|
||||
import SearchBarNode
|
||||
|
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import ContextUI
|
||||
import ChatPresentationInterfaceState
|
||||
import UndoUI
|
||||
|
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import ContextUI
|
||||
import ChatPresentationInterfaceState
|
||||
import PremiumUI
|
||||
|
@ -9,6 +9,7 @@ import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import ContextUI
|
||||
import ChatPresentationInterfaceState
|
||||
import PremiumUI
|
||||
|
@ -79,6 +79,8 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
return true
|
||||
case let .stickerPack(reference):
|
||||
let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { info, items, action in
|
||||
let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
|
||||
|
||||
let presentationData = params.context.sharedContext.currentPresentationData.with { $0 }
|
||||
var animateInAsReplacement = false
|
||||
if let navigationController = params.navigationController {
|
||||
@ -91,11 +93,11 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
}
|
||||
switch action {
|
||||
case .add:
|
||||
params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: params.context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in
|
||||
params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: params.context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { _ in
|
||||
return true
|
||||
}))
|
||||
case let .remove(positionInList):
|
||||
params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: params.context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in
|
||||
params.navigationController?.presentOverlay(controller: UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: params.context), elevatedLayout: true, animateInAsReplacement: animateInAsReplacement, action: { action in
|
||||
if case .undo = action {
|
||||
let _ = params.context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
|
||||
}
|
||||
|
@ -165,58 +165,25 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
}
|
||||
case let .stickerPack(name, _):
|
||||
dismissInput()
|
||||
/*if false {
|
||||
var mainStickerPack: StickerPackReference?
|
||||
var stickerPacks: [StickerPackReference] = []
|
||||
if let message = contentContext as? Message {
|
||||
let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue)
|
||||
if let matches = dataDetector?.matches(in: message.text, options: [], range: NSRange(message.text.startIndex ..< message.text.endIndex, in: message.text)) {
|
||||
for match in matches {
|
||||
guard let stringRange = Range(match.range, in: message.text) else {
|
||||
continue
|
||||
}
|
||||
let urlText = String(message.text[stringRange])
|
||||
if let resultName = parseStickerPackUrl(urlText) {
|
||||
stickerPacks.append(.name(resultName))
|
||||
if resultName == name {
|
||||
mainStickerPack = .name(resultName)
|
||||
}
|
||||
}
|
||||
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: .name(name), stickerPacks: [.name(name)], parentNavigationController: navigationController, sendSticker: sendSticker, actionPerformed: { info, items, action in
|
||||
let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
|
||||
|
||||
switch action {
|
||||
case .add:
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), nil)
|
||||
case let .remove(positionInList):
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedText(info.title).string : presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
|
||||
}
|
||||
if mainStickerPack == nil {
|
||||
mainStickerPack = .name(name)
|
||||
stickerPacks.insert(.name(name), at: 0)
|
||||
}
|
||||
} else {
|
||||
mainStickerPack = .name(name)
|
||||
stickerPacks = [.name(name)]
|
||||
}
|
||||
} else {
|
||||
mainStickerPack = .name(name)
|
||||
stickerPacks = [.name(name)]
|
||||
return true
|
||||
}), nil)
|
||||
}
|
||||
if let mainStickerPack = mainStickerPack, !stickerPacks.isEmpty {
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, parentNavigationController: navigationController, sendSticker: sendSticker)
|
||||
present(controller, nil)
|
||||
}
|
||||
} else {*/
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: .name(name), stickerPacks: [.name(name)], parentNavigationController: navigationController, sendSticker: sendSticker, actionPerformed: { info, items, action in
|
||||
switch action {
|
||||
case .add:
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_AddedTitle, text: presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
|
||||
return true
|
||||
}), nil)
|
||||
case let .remove(positionInList):
|
||||
present(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: presentationData.strings.StickerPackActionInfo_RemovedTitle, text: presentationData.strings.StickerPackActionInfo_RemovedText(info.title).string, undo: true, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in
|
||||
if case .undo = action {
|
||||
let _ = context.engine.stickers.addStickerPackInteractively(info: info, items: items, positionInList: positionInList).start()
|
||||
}
|
||||
return true
|
||||
}), nil)
|
||||
}
|
||||
})
|
||||
})
|
||||
present(controller, nil)
|
||||
//}
|
||||
case let .instantView(webpage, anchor):
|
||||
navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor))
|
||||
case let .join(link):
|
||||
|
@ -26,6 +26,7 @@ import AppLock
|
||||
import WallpaperBackgroundNode
|
||||
import InAppPurchaseManager
|
||||
import PremiumUI
|
||||
import StickerPackPreviewUI
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1502,6 +1503,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
return PremiumIntroScreen(context: context, source: mappedSource)
|
||||
}
|
||||
|
||||
public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController {
|
||||
return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker)
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
|
||||
|
@ -11,6 +11,7 @@ import LegacyComponents
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import Emoji
|
||||
import AppBundle
|
||||
import OverlayStatusController
|
||||
|
@ -10,6 +10,7 @@ import TelegramUIPreferences
|
||||
import MergeLists
|
||||
import AccountContext
|
||||
import StickerPackPreviewUI
|
||||
import StickerPeekUI
|
||||
import ContextUI
|
||||
import ChatPresentationInterfaceState
|
||||
import PremiumUI
|
||||
|
@ -181,11 +181,18 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(14.0), textColor: undoTextColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { contents in
|
||||
return ("URL", contents)
|
||||
}), textAlignment: .natural)
|
||||
self.textNode.attributedText = attributedText
|
||||
self.textNode.maximumNumberOfLines = 2
|
||||
displayUndo = false
|
||||
self.originalRemainingSeconds = Double(max(5, min(8, text.count / 14)))
|
||||
|
||||
if text.contains("](") {
|
||||
isUserInteractionEnabled = true
|
||||
}
|
||||
case let .actionSucceeded(title, text, cancel):
|
||||
self.avatarNode = nil
|
||||
self.iconNode = nil
|
||||
|
@ -27,6 +27,7 @@ swift_library(
|
||||
"//submodules/MoreButtonNode:MoreButtonNode",
|
||||
"//submodules/BotPaymentsUI:BotPaymentsUI",
|
||||
"//submodules/PromptUI:PromptUI",
|
||||
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -20,6 +20,7 @@ import UrlHandling
|
||||
import MoreButtonNode
|
||||
import BotPaymentsUI
|
||||
import PromptUI
|
||||
import PhoneNumberFormat
|
||||
|
||||
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
|
||||
|
||||
@ -798,6 +799,40 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
if let json = json, let needConfirmation = json["need_confirmation"] as? Bool {
|
||||
self.needDismissConfirmation = needConfirmation
|
||||
}
|
||||
case "web_app_request_phone":
|
||||
let _ = (self.context.account.postbox.loadedPeerWithId(self.context.account.peerId)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] accountPeer in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
guard let user = accountPeer as? TelegramUser, let phoneNumber = user.phone else {
|
||||
return
|
||||
}
|
||||
|
||||
let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData)
|
||||
var items: [ActionSheetItem] = []
|
||||
items.append(ActionSheetTextItem(title: strongSelf.presentationData.strings.WebApp_ShareMyPhoneNumberConfirmation(formatPhoneNumber(phoneNumber), strongSelf.controller?.botName ?? "").string, parseMarkdown: true))
|
||||
items.append(ActionSheetButtonItem(title: strongSelf.presentationData.strings.WebApp_ShareMyPhoneNumber, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.sendPhoneRequestedEvent(phone: phoneNumber)
|
||||
}))
|
||||
|
||||
actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [
|
||||
ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
|
||||
actionSheet?.dismissAnimated()
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.sendPhoneRequestedEvent(phone: nil)
|
||||
})
|
||||
])])
|
||||
strongSelf.controller?.present(actionSheet, in: .window(.root))
|
||||
})
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -919,6 +954,14 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
self.webView?.sendEvent(name: "popup_closed", data: paramsString)
|
||||
}
|
||||
|
||||
fileprivate func sendPhoneRequestedEvent(phone: String?) {
|
||||
var paramsString: String?
|
||||
if let phone = phone {
|
||||
paramsString = "{phone_number: \"\(phone)\"}"
|
||||
}
|
||||
self.webView?.sendEvent(name: "phone_requested", data: paramsString)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var controllerNode: Node {
|
||||
|
Loading…
x
Reference in New Issue
Block a user