Various improvements

This commit is contained in:
Ilya Laktyushin 2022-07-16 15:21:52 +02:00
commit f21265881e
53 changed files with 1527 additions and 337 deletions

View File

@ -1 +1 @@
d66a871d990a1084502f4856b3d66c35
462c8b20925f8d3f87f0ab82d25d9c2a

View File

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

View File

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

View File

@ -46,6 +46,7 @@ public enum ChatPanelSearchNavigationAction {
public enum ChatPanelRestrictionInfoSubject {
case mediaRecording
case stickers
case premiumVoiceMessages
}
public enum ChatPanelRestrictionInfoDisplayType {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -240,7 +240,7 @@ enum PremiumPerk: CaseIterable {
case .appIcons:
return "Premium/Perk/AppIcon"
case .animatedEmoji:
return "Premium/Perk/AppIcon"
return "Premium/Perk/Emoji"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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",
],
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Size=24px, Direction=Right.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

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

View File

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

View 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

View File

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

View File

@ -11,6 +11,7 @@ import TelegramNotices
import MergeLists
import AccountContext
import StickerPackPreviewUI
import StickerPeekUI
import PeerInfoUI
import SettingsUI
import ContextUI

View File

@ -10,6 +10,7 @@ import TelegramPresentationData
import TelegramUIPreferences
import MergeLists
import StickerPackPreviewUI
import StickerPeekUI
import OverlayStatusController
import PresentationDataUtils
import SearchBarNode

View File

@ -10,6 +10,7 @@ import TelegramUIPreferences
import MergeLists
import AccountContext
import StickerPackPreviewUI
import StickerPeekUI
import ContextUI
import ChatPresentationInterfaceState
import UndoUI

View File

@ -10,6 +10,7 @@ import TelegramUIPreferences
import MergeLists
import AccountContext
import StickerPackPreviewUI
import StickerPeekUI
import ContextUI
import ChatPresentationInterfaceState
import PremiumUI

View File

@ -9,6 +9,7 @@ import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import StickerPackPreviewUI
import StickerPeekUI
import ContextUI
import ChatPresentationInterfaceState
import PremiumUI

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import LegacyComponents
import MergeLists
import AccountContext
import StickerPackPreviewUI
import StickerPeekUI
import Emoji
import AppBundle
import OverlayStatusController

View File

@ -10,6 +10,7 @@ import TelegramUIPreferences
import MergeLists
import AccountContext
import StickerPackPreviewUI
import StickerPeekUI
import ContextUI
import ChatPresentationInterfaceState
import PremiumUI

View File

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

View File

@ -27,6 +27,7 @@ swift_library(
"//submodules/MoreButtonNode:MoreButtonNode",
"//submodules/BotPaymentsUI:BotPaymentsUI",
"//submodules/PromptUI:PromptUI",
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
],
visibility = [
"//visibility:public",

View File

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