Various improvements

This commit is contained in:
Ilya Laktyushin 2022-05-20 23:11:45 +04:00
parent 10326f1fac
commit 4b41660767
53 changed files with 1357 additions and 522 deletions

View File

@ -7615,3 +7615,6 @@ Sorry for the inconvenience.";
"Settings.Terms_URL" = "https://telegram.org/tos"; "Settings.Terms_URL" = "https://telegram.org/tos";
"Settings.PrivacyPolicy_URL" = "https://telegram.org/privacy"; "Settings.PrivacyPolicy_URL" = "https://telegram.org/privacy";
"Stickers.PremiumPackInfoText" = "This pack contains premium stickers like this one.";
"Stickers.PremiumPackView" = "View";

View File

@ -258,6 +258,7 @@ public enum ResolvedUrl {
case importStickers case importStickers
case startAttach(peerId: PeerId, payload: String?) case startAttach(peerId: PeerId, payload: String?)
case invoice(slug: String, invoice: TelegramMediaInvoice) case invoice(slug: String, invoice: TelegramMediaInvoice)
case premiumOffer(reference: String?)
} }
public enum NavigateToChatKeepStack { public enum NavigateToChatKeepStack {

View File

@ -569,7 +569,7 @@ public class AttachmentController: ViewController {
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0) ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
let targetPosition = self.container.position let targetPosition = self.container.position
let startPosition = targetPosition.offsetBy(dx: 0.0, dy: self.bounds.height) let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
self.container.position = startPosition self.container.position = startPosition
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring) let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -785,7 +785,9 @@ public class AttachmentController: ViewController {
} }
let controllers = self.currentControllers let controllers = self.currentControllers
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size)) if !self.animating {
containerTransition.updateFrame(node: self.container, frame: CGRect(origin: CGPoint(), size: containerRect.size))
}
let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets) let containerLayout = containerLayout.withUpdatedIntrinsicInsets(containerInsets)

View File

@ -331,7 +331,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: { let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
let premiumScreen = PremiumIntroScreen(context: context) let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
replaceImpl?(premiumScreen) replaceImpl?(premiumScreen)
}) })
chatListController?.push(controller) chatListController?.push(controller)

View File

@ -1355,7 +1355,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if data.includePeers.peers.count >= limit { if data.includePeers.peers.count >= limit {
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .chatsInFolder, count: Int32(data.includePeers.peers.count), action: { let controller = PremiumLimitScreen(context: context, subject: .chatsInFolder, count: Int32(data.includePeers.peers.count), action: {
let controller = PremiumIntroScreen(context: context) let controller = PremiumIntroScreen(context: context, source: .chatsPerFolder)
replaceImpl?(controller) replaceImpl?(controller)
}) })
replaceImpl = { [weak controller] c in replaceImpl = { [weak controller] c in

View File

@ -299,7 +299,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
if filters.count >= limit { if filters.count >= limit {
var replaceImpl: ((ViewController) -> Void)? var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(filters.count), action: { let controller = PremiumLimitScreen(context: context, subject: .folders, count: Int32(filters.count), action: {
let controller = PremiumIntroScreen(context: context) let controller = PremiumIntroScreen(context: context, source: .folders)
replaceImpl?(controller) replaceImpl?(controller)
}) })
replaceImpl = { [weak controller] c in replaceImpl = { [weak controller] c in

View File

@ -830,30 +830,44 @@ public final class ChatListNode: ListView {
} }
} }
}, setItemPinned: { [weak self] itemId, _ in }, setItemPinned: { [weak self] itemId, _ in
let location: TogglePeerChatPinnedLocation let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
if let chatListFilter = chatListFilter { |> deliverOnMainQueue).start(next: { [weak self] peer in
location = .filter(chatListFilter.id) let isPremium = peer?.isPremium ?? false
} else { let location: TogglePeerChatPinnedLocation
location = .group(groupId._asGroup()) if let chatListFilter = chatListFilter {
} location = .filter(chatListFilter.id)
let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: itemId) } else {
|> deliverOnMainQueue).start(next: { result in location = .group(groupId._asGroup())
if let strongSelf = self {
switch result {
case .done:
break
case let .limitExceeded(count, _):
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
let premiumScreen = PremiumIntroScreen(context: context)
replaceImpl?(premiumScreen)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.push?(controller)
}
} }
let _ = (context.engine.peers.toggleItemPinned(location: location, itemId: itemId)
|> deliverOnMainQueue).start(next: { result in
if let strongSelf = self {
switch result {
case .done:
break
case let .limitExceeded(count, limit):
if isPremium {
let text: String
if chatListFilter != nil {
text = strongSelf.currentState.presentationData.strings.DialogList_UnknownPinLimitError
} else {
text = strongSelf.currentState.presentationData.strings.DialogList_PinLimitError("\(limit)").string
}
strongSelf.presentAlert?(text)
} else {
var replaceImpl: ((ViewController) -> Void)?
let controller = PremiumLimitScreen(context: context, subject: .pins, count: Int32(count), action: {
let premiumScreen = PremiumIntroScreen(context: context, source: .pinnedChats)
replaceImpl?(premiumScreen)
})
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.push?(controller)
}
}
}
})
}) })
}, setPeerMuted: { [weak self] peerId, _ in }, setPeerMuted: { [weak self] peerId, _ in
guard let strongSelf = self else { guard let strongSelf = self else {

View File

@ -121,6 +121,14 @@ private final class HapticFeedbackImpl {
} }
} }
func warning() {
if let notificationGenerator = self.notificationGenerator {
notificationGenerator.notificationOccurred(.warning)
} else {
}
}
@objc dynamic func f() { @objc dynamic func f() {
} }
} }
@ -205,6 +213,14 @@ public final class HapticFeedback {
} }
} }
} }
public func warning() {
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
self.withImpl { impl in
impl.warning()
}
}
}
} }
@available(iOS 13.0, *) @available(iOS 13.0, *)

View File

@ -361,6 +361,11 @@ open class NavigationController: UINavigationController, ContainableController,
private func updateContainers(layout rawLayout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { private func updateContainers(layout rawLayout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
self.isUpdatingContainers = true self.isUpdatingContainers = true
if let badgeNode = self.badgeNode, let image = badgeNode.image {
badgeNode.isHidden = !rawLayout.deviceMetrics.hasTopNotch || rawLayout.size.width > rawLayout.size.height
badgeNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((rawLayout.size.width - image.size.width) / 2.0), y: 6.0), size: image.size)
}
var layout = rawLayout var layout = rawLayout
if self.ignoreInputHeight { if self.ignoreInputHeight {
@ -631,6 +636,7 @@ open class NavigationController: UINavigationController, ContainableController,
var topVisibleModalContainerWithStatusBar: NavigationModalContainer? var topVisibleModalContainerWithStatusBar: NavigationModalContainer?
var visibleModalCount = 0 var visibleModalCount = 0
var topModalIsFlat = false var topModalIsFlat = false
var topFlatModalHasProgress = false
let isLandscape = layout.orientation == .landscape let isLandscape = layout.orientation == .landscape
var hasVisibleStandaloneModal = false var hasVisibleStandaloneModal = false
var topModalDismissProgress: CGFloat = 0.0 var topModalDismissProgress: CGFloat = 0.0
@ -659,6 +665,17 @@ open class NavigationController: UINavigationController, ContainableController,
effectiveModalTransition = 1.0 effectiveModalTransition = 1.0
} }
if navigationLayout.modal[i].isFlat, let lastController = navigationLayout.modal[i].controllers.last {
lastController.modalStyleOverlayTransitionFactorUpdated = { [weak self] transition in
guard let strongSelf = self else {
return
}
strongSelf.updateContainersNonReentrant(transition: transition)
}
modalStyleOverlayTransitionFactor = max(modalStyleOverlayTransitionFactor, lastController.modalStyleOverlayTransitionFactor)
topFlatModalHasProgress = modalStyleOverlayTransitionFactor > 0.0
}
containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) containerTransition.updateFrame(node: modalContainer, frame: CGRect(origin: CGPoint(), size: layout.size))
modalContainer.update(layout: modalContainer.isFlat ? overlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition) modalContainer.update(layout: modalContainer.isFlat ? overlayLayout : layout, controllers: navigationLayout.modal[i].controllers, coveredByModalTransition: effectiveModalTransition, transition: containerTransition)
@ -862,9 +879,15 @@ open class NavigationController: UINavigationController, ContainableController,
let visibleRootModalDismissProgress: CGFloat let visibleRootModalDismissProgress: CGFloat
var additionalModalFrameProgress: CGFloat var additionalModalFrameProgress: CGFloat
if visibleModalCount == 1 { if visibleModalCount == 1 {
effectiveRootModalDismissProgress = (topModalIsFlat || isLandscape) ? 1.0 : topModalDismissProgress if topFlatModalHasProgress {
visibleRootModalDismissProgress = effectiveRootModalDismissProgress effectiveRootModalDismissProgress = 0.0
additionalModalFrameProgress = 0.0 visibleRootModalDismissProgress = effectiveRootModalDismissProgress
additionalModalFrameProgress = 1.0 - topModalDismissProgress
} else {
effectiveRootModalDismissProgress = ((topModalIsFlat && !topFlatModalHasProgress) || isLandscape) ? 1.0 : topModalDismissProgress
visibleRootModalDismissProgress = effectiveRootModalDismissProgress
additionalModalFrameProgress = 0.0
}
} else if visibleModalCount >= 2 { } else if visibleModalCount >= 2 {
effectiveRootModalDismissProgress = 0.0 effectiveRootModalDismissProgress = 0.0
visibleRootModalDismissProgress = topModalDismissProgress visibleRootModalDismissProgress = topModalDismissProgress
@ -929,7 +952,7 @@ open class NavigationController: UINavigationController, ContainableController,
} }
let maxScale: CGFloat let maxScale: CGFloat
let maxOffset: CGFloat let maxOffset: CGFloat
if topModalIsFlat || isLandscape { if (topModalIsFlat && !topFlatModalHasProgress) || isLandscape {
maxScale = 1.0 maxScale = 1.0
maxOffset = 0.0 maxOffset = 0.0
} else if visibleModalCount <= 1 { } else if visibleModalCount <= 1 {
@ -1219,8 +1242,16 @@ open class NavigationController: UINavigationController, ContainableController,
self.displayNode.addSubnode(inCallStatusBar) self.displayNode.addSubnode(inCallStatusBar)
} }
} }
let badgeNode = ASImageNode()
badgeNode.displaysAsynchronously = false
badgeNode.image = UIImage(bundleImageName: "Components/BadgeTest")
self.badgeNode = badgeNode
self.displayNode.addSubnode(badgeNode)
} }
private var badgeNode: ASImageNode?
public func pushViewController(_ controller: ViewController) { public func pushViewController(_ controller: ViewController) {
self.pushViewController(controller, completion: {}) self.pushViewController(controller, completion: {})
} }

View File

@ -1256,8 +1256,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
strongSelf.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), nil) strongSelf.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_gif", scale: 0.075, colors: [:], title: nil, text: presentationData.strings.Gallery_GifSaved), elevatedLayout: true, animateInAsReplacement: true, action: { _ in return false }), nil)
} }
})) }))
} else if file.mimeType.hasPrefix("image/") || file.mimeType.hasPrefix("video/") { } else if file.mimeType.hasPrefix("image/") {
preferredAction = .saveToCameraRoll preferredAction = .saveToCameraRoll
actionCompletionText = strongSelf.presentationData.strings.Gallery_ImageSaved
} else if file.mimeType.hasPrefix("video/") {
preferredAction = .saveToCameraRoll
actionCompletionText = strongSelf.presentationData.strings.Gallery_VideoSaved
} }
} }
} }
@ -1335,6 +1339,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, UIScroll
shareController.dismissed = { [weak self] _ in shareController.dismissed = { [weak self] _ in
self?.interacting?(false) self?.interacting?(false)
} }
shareController.actionCompleted = { [weak self, weak shareController] in
if let strongSelf = self, let shareController = shareController, shareController.actionIsMediaSaving {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.controllerInteraction?.presentController(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: presentationData.strings.Gallery_ImageSaved), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return true }), nil)
}
}
shareController.completed = { [weak self] peerIds in shareController.completed = { [weak self] peerIds in
if let strongSelf = self { if let strongSelf = self {
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in

View File

@ -356,6 +356,13 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .translate: case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController { if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil) let controller = TranslateScreen(context: strongSelf.context, text: string, fromLanguage: nil)
controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c)
}
controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root))
}
parentController.present(controller, in: .window(.root)) parentController.present(controller, in: .window(.root))
} }
} }

View File

@ -1096,6 +1096,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if canTranslate { if canTranslate {
actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: strings.Conversation_ContextMenuTranslate, accessibilityLabel: strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, fromLanguage: language) let controller = TranslateScreen(context: context, text: text, fromLanguage: language)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)
}
controller.presentController = { [weak self] c in
self?.controller?.present(c, in: .window(.root))
}
self?.present(controller, nil) self?.present(controller, nil)
})) }))
} }

View File

@ -1630,7 +1630,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
if hasNamesToRevoke && selectedType == .publicChannel { if hasNamesToRevoke && selectedType == .publicChannel {
footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: { footerItem = IncreaseLimitFooterItem(theme: presentationData.theme, title: presentationData.strings.Premium_IncreaseLimit, colorful: true, action: {
let controller = PremiumIntroScreen(context: context) let controller = PremiumIntroScreen(context: context, source: .publicLinks)
pushControllerImpl?(controller) pushControllerImpl?(controller)
}) })
} }

View File

@ -326,7 +326,7 @@ public func oldChannelsController(context: AccountContext, updatedPresentationDa
if state.selectedPeers.count > 0 { if state.selectedPeers.count > 0 {
leaveActionImpl?() leaveActionImpl?()
} else { } else {
let controller = PremiumIntroScreen(context: context) let controller = PremiumIntroScreen(context: context, source: .groupsAndChannels)
pushImpl?(controller) pushImpl?(controller)
} }
}) })

View File

@ -3,11 +3,13 @@ import UIKit
import Display import Display
import ComponentFlow import ComponentFlow
import SwiftSignalKit import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import ViewControllerComponent import ViewControllerComponent
import AccountContext import AccountContext
import SolidRoundedButtonComponent import SolidRoundedButtonComponent
import MultilineTextComponent import MultilineTextComponent
import PresentationDataUtils
import PrefixSectionGroupComponent import PrefixSectionGroupComponent
import BundleIconComponent import BundleIconComponent
import SolidRoundedButtonComponent import SolidRoundedButtonComponent
@ -17,6 +19,236 @@ import ConfettiEffect
import TextFormat import TextFormat
import InstantPageCache import InstantPageCache
public enum PremiumSource {
case settings
case stickers
case reactions
case ads
case groupsAndChannels
case pinnedChats
case publicLinks
case savedGifs
case savedStickers
case folders
case chatsPerFolder
case accounts
var identifier: String {
switch self {
case .settings:
return "settings"
case .stickers:
return "premium_stickers"
case .reactions:
return "unique_reactions"
case .ads:
return "no_ads"
case .groupsAndChannels:
return "double_limits__channels"
case .pinnedChats:
return "double_limits__dialog_pinned"
case .publicLinks:
return "double_limits__channels_public"
case .savedGifs:
return "double_limits__saved_gifs"
case .savedStickers:
return "double_limits__stickers_faved"
case .folders:
return "double_limits__dialog_filters"
case .chatsPerFolder:
return "double_limits__dialog_filters_chats"
case .accounts:
return "double_limits__accounts"
}
}
}
private enum PremiumPerk: CaseIterable {
case doubleLimits
case moreUpload
case fasterDownload
case voiceToText
case noAds
case uniqueReactions
case premiumStickers
case advancedChatManagement
case profileBadge
case animatedUserpics
static var allCases: [PremiumPerk] {
return [
.doubleLimits,
.moreUpload,
.fasterDownload,
.voiceToText,
.noAds,
.uniqueReactions,
.premiumStickers,
.advancedChatManagement,
.profileBadge,
.animatedUserpics
]
}
init?(identifier: String) {
for perk in PremiumPerk.allCases {
if perk.identifier == identifier {
self = perk
return
}
}
return nil
}
var identifier: String {
switch self {
case .doubleLimits:
return "double_limits"
case .moreUpload:
return "more_upload"
case .fasterDownload:
return "faster_download"
case .voiceToText:
return "voice_to_text"
case .noAds:
return "no_ads"
case .uniqueReactions:
return "unique_reactions"
case .premiumStickers:
return "premium_stickers"
case .advancedChatManagement:
return "advanced_chat_management"
case .profileBadge:
return "profile_badge"
case .animatedUserpics:
return "animated_userpics"
}
}
func title(strings: PresentationStrings) -> String {
switch self {
case .doubleLimits:
return strings.Premium_DoubledLimits
case .moreUpload:
return strings.Premium_UploadSize
case .fasterDownload:
return strings.Premium_FasterSpeed
case .voiceToText:
return strings.Premium_VoiceToText
case .noAds:
return strings.Premium_NoAds
case .uniqueReactions:
return strings.Premium_Reactions
case .premiumStickers:
return strings.Premium_Stickers
case .advancedChatManagement:
return strings.Premium_ChatManagement
case .profileBadge:
return strings.Premium_Badge
case .animatedUserpics:
return strings.Premium_Avatar
}
}
func subtitle(strings: PresentationStrings) -> String {
switch self {
case .doubleLimits:
return strings.Premium_DoubledLimitsInfo
case .moreUpload:
return strings.Premium_UploadSizeInfo
case .fasterDownload:
return strings.Premium_FasterSpeedInfo
case .voiceToText:
return strings.Premium_VoiceToTextInfo
case .noAds:
return strings.Premium_NoAdsInfo
case .uniqueReactions:
return strings.Premium_ReactionsInfo
case .premiumStickers:
return strings.Premium_StickersInfo
case .advancedChatManagement:
return strings.Premium_ChatManagementInfo
case .profileBadge:
return strings.Premium_BadgeInfo
case .animatedUserpics:
return strings.Premium_AvatarInfo
}
}
var iconName: String {
switch self {
case .doubleLimits:
return "Premium/Perk/Limits"
case .moreUpload:
return "Premium/Perk/Upload"
case .fasterDownload:
return "Premium/Perk/Speed"
case .voiceToText:
return "Premium/Perk/Voice"
case .noAds:
return "Premium/Perk/NoAds"
case .uniqueReactions:
return "Premium/Perk/Reactions"
case .premiumStickers:
return "Premium/Perk/Stickers"
case .advancedChatManagement:
return "Premium/Perk/Chat"
case .profileBadge:
return "Premium/Perk/Badge"
case .animatedUserpics:
return "Premium/Perk/Avatar"
}
}
}
private struct PremiumIntroConfiguration {
static var defaultValue: PremiumIntroConfiguration {
return PremiumIntroConfiguration(perks: [
.doubleLimits,
.moreUpload,
.fasterDownload,
.voiceToText,
.noAds,
.uniqueReactions,
.premiumStickers,
.advancedChatManagement,
.profileBadge,
.animatedUserpics
])
}
let perks: [PremiumPerk]
fileprivate init(perks: [PremiumPerk]) {
self.perks = perks
}
public static func with(appConfiguration: AppConfiguration) -> PremiumIntroConfiguration {
if let data = appConfiguration.data, let values = data["premium_promo_order"] as? [String] {
var perks: [PremiumPerk] = []
for value in values {
if let perk = PremiumPerk(identifier: value) {
if !perks.contains(perk) {
perks.append(perk)
} else {
perks = []
break
}
} else {
perks = []
break
}
}
if perks.count < 4 {
perks = PremiumIntroConfiguration.defaultValue.perks
}
return PremiumIntroConfiguration(perks: perks)
} else {
return .defaultValue
}
}
}
private final class SectionGroupComponent: Component { private final class SectionGroupComponent: Component {
public final class Item: Equatable { public final class Item: Equatable {
public let content: AnyComponentWithIdentity<Empty> public let content: AnyComponentWithIdentity<Empty>
@ -495,6 +727,40 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
return true return true
} }
final class State: ComponentState {
private let context: AccountContext
private var disposable: Disposable?
var configuration = PremiumIntroConfiguration.defaultValue
init(context: AccountContext) {
self.context = context
super.init()
self.disposable = (context.account.postbox.preferencesView(keys: [PreferencesKeys.appConfiguration])
|> map { view -> AppConfiguration in
let appConfiguration: AppConfiguration = view.values[PreferencesKeys.appConfiguration]?.get(AppConfiguration.self) ?? AppConfiguration.defaultValue
return appConfiguration
}
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] appConfiguration in
if let strongSelf = self {
strongSelf.configuration = PremiumIntroConfiguration.with(appConfiguration: appConfiguration)
strongSelf.updated(transition: .immediate)
}
})
}
deinit {
self.disposable?.dispose()
}
}
func makeState() -> State {
return State(context: self.context)
}
static var body: Body { static var body: Body {
let overscroll = Child(Rectangle.self) let overscroll = Child(Rectangle.self)
let fade = Child(RoundedRectangle.self) let fade = Child(RoundedRectangle.self)
@ -510,6 +776,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let state = context.state
let theme = environment.theme let theme = environment.theme
let strings = environment.strings let strings = environment.strings
@ -576,230 +843,52 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
size.height += text.size.height size.height += text.size.height
size.height += 21.0 size.height += 21.0
let gradientColors: [(UIColor, UIColor)] = [
(UIColor(rgb: 0xF28528), UIColor(rgb: 0xEF7633)),
(UIColor(rgb: 0xEA5F43), UIColor(rgb: 0xE7504E)),
(UIColor(rgb: 0xDE4768), UIColor(rgb: 0xD54D82)),
(UIColor(rgb: 0xDE4768), UIColor(rgb: 0xD54D82)),
(UIColor(rgb: 0xC654A8), UIColor(rgb: 0xBE5AC2)),
(UIColor(rgb: 0xAF62E9), UIColor(rgb: 0xA668FF)),
(UIColor(rgb: 0x9674FF), UIColor(rgb: 0x8C7DFF)),
(UIColor(rgb: 0x9674FF), UIColor(rgb: 0x8C7DFF)),
(UIColor(rgb: 0x7B88FF), UIColor(rgb: 0x7091FF)),
(UIColor(rgb: 0x609DFF), UIColor(rgb: 0x56A5FF))
]
var items: [SectionGroupComponent.Item] = []
var i = 0
for perk in state.configuration.perks {
let iconBackgroundColors = gradientColors[i]
items.append(SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: perk.identifier,
component: AnyComponent(
PerkComponent(
iconName: perk.iconName,
iconBackgroundColors: [
iconBackgroundColors.0,
iconBackgroundColors.1
],
title: perk.title(strings: strings),
titleColor: titleColor,
subtitle: perk.subtitle(strings: strings),
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
))
i += 1
}
let section = section.update( let section = section.update(
component: SectionGroupComponent( component: SectionGroupComponent(
items: [ items: items,
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "limits",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Limits",
iconBackgroundColors: [
UIColor(rgb: 0xF28528),
UIColor(rgb: 0xEF7633)
],
title: strings.Premium_DoubledLimits,
titleColor: titleColor,
subtitle: strings.Premium_DoubledLimitsInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "upload",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Upload",
iconBackgroundColors: [
UIColor(rgb: 0xEA5F43),
UIColor(rgb: 0xE7504E)
],
title: strings.Premium_UploadSize,
titleColor: titleColor,
subtitle: strings.Premium_UploadSizeInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "speed",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Speed",
iconBackgroundColors: [
UIColor(rgb: 0xDE4768),
UIColor(rgb: 0xD54D82)
],
title: strings.Premium_FasterSpeed,
titleColor: titleColor,
subtitle: strings.Premium_FasterSpeedInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "voice",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Voice",
iconBackgroundColors: [
UIColor(rgb: 0xDE4768),
UIColor(rgb: 0xD54D82)
],
title: strings.Premium_VoiceToText,
titleColor: titleColor,
subtitle: strings.Premium_VoiceToTextInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "noAds",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/NoAds",
iconBackgroundColors: [
UIColor(rgb: 0xC654A8),
UIColor(rgb: 0xBE5AC2)
],
title: strings.Premium_NoAds,
titleColor: titleColor,
subtitle: strings.Premium_NoAdsInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "reactions",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Reactions",
iconBackgroundColors: [
UIColor(rgb: 0xAF62E9),
UIColor(rgb: 0xA668FF)
],
title: strings.Premium_Reactions,
titleColor: titleColor,
subtitle: strings.Premium_ReactionsInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "stickers",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Stickers",
iconBackgroundColors: [
UIColor(rgb: 0x9674FF),
UIColor(rgb: 0x8C7DFF)
],
title: strings.Premium_Stickers,
titleColor: titleColor,
subtitle: strings.Premium_StickersInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "chat",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Chat",
iconBackgroundColors: [
UIColor(rgb: 0x9674FF),
UIColor(rgb: 0x8C7DFF)
],
title: strings.Premium_ChatManagement,
titleColor: titleColor,
subtitle: strings.Premium_ChatManagementInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "badge",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Badge",
iconBackgroundColors: [
UIColor(rgb: 0x7B88FF),
UIColor(rgb: 0x7091FF)
],
title: strings.Premium_Badge,
titleColor: titleColor,
subtitle: strings.Premium_BadgeInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
SectionGroupComponent.Item(
AnyComponentWithIdentity(
id: "avatar",
component: AnyComponent(
PerkComponent(
iconName: "Premium/Perk/Avatar",
iconBackgroundColors: [
UIColor(rgb: 0x609DFF),
UIColor(rgb: 0x56A5FF)
],
title: strings.Premium_Avatar,
titleColor: titleColor,
subtitle: strings.Premium_AvatarInfo,
subtitleColor: subtitleColor,
arrowColor: arrowColor
)
)
),
action: {
}
),
],
backgroundColor: environment.theme.list.itemBlocksBackgroundColor, backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
selectionColor: environment.theme.list.itemHighlightedBackgroundColor, selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
separatorColor: environment.theme.list.itemBlocksSeparatorColor separatorColor: environment.theme.list.itemBlocksSeparatorColor
@ -808,6 +897,241 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
transition: context.transition transition: context.transition
) )
//
// let section = section.update(
// component: SectionGroupComponent(
// items: [
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "limits",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Limits",
// iconBackgroundColors: [
// UIColor(rgb: 0xF28528),
// UIColor(rgb: 0xEF7633)
// ],
// title: strings.Premium_DoubledLimits,
// titleColor: titleColor,
// subtitle: strings.Premium_DoubledLimitsInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "upload",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Upload",
// iconBackgroundColors: [
// UIColor(rgb: 0xEA5F43),
// UIColor(rgb: 0xE7504E)
// ],
// title: strings.Premium_UploadSize,
// titleColor: titleColor,
// subtitle: strings.Premium_UploadSizeInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "speed",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Speed",
// iconBackgroundColors: [
// UIColor(rgb: 0xDE4768),
// UIColor(rgb: 0xD54D82)
// ],
// title: strings.Premium_FasterSpeed,
// titleColor: titleColor,
// subtitle: strings.Premium_FasterSpeedInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "voice",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Voice",
// iconBackgroundColors: [
// UIColor(rgb: 0xDE4768),
// UIColor(rgb: 0xD54D82)
// ],
// title: strings.Premium_VoiceToText,
// titleColor: titleColor,
// subtitle: strings.Premium_VoiceToTextInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "noAds",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/NoAds",
// iconBackgroundColors: [
// UIColor(rgb: 0xC654A8),
// UIColor(rgb: 0xBE5AC2)
// ],
// title: strings.Premium_NoAds,
// titleColor: titleColor,
// subtitle: strings.Premium_NoAdsInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "reactions",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Reactions",
// iconBackgroundColors: [
// UIColor(rgb: 0xAF62E9),
// UIColor(rgb: 0xA668FF)
// ],
// title: strings.Premium_Reactions,
// titleColor: titleColor,
// subtitle: strings.Premium_ReactionsInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "stickers",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Stickers",
// iconBackgroundColors: [
// UIColor(rgb: 0x9674FF),
// UIColor(rgb: 0x8C7DFF)
// ],
// title: strings.Premium_Stickers,
// titleColor: titleColor,
// subtitle: strings.Premium_StickersInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "chat",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Chat",
// iconBackgroundColors: [
// UIColor(rgb: 0x9674FF),
// UIColor(rgb: 0x8C7DFF)
// ],
// title: strings.Premium_ChatManagement,
// titleColor: titleColor,
// subtitle: strings.Premium_ChatManagementInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "badge",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Badge",
// iconBackgroundColors: [
// UIColor(rgb: 0x7B88FF),
// UIColor(rgb: 0x7091FF)
// ],
// title: strings.Premium_Badge,
// titleColor: titleColor,
// subtitle: strings.Premium_BadgeInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// SectionGroupComponent.Item(
// AnyComponentWithIdentity(
// id: "avatar",
// component: AnyComponent(
// PerkComponent(
// iconName: "Premium/Perk/Avatar",
// iconBackgroundColors: [
// UIColor(rgb: 0x609DFF),
// UIColor(rgb: 0x56A5FF)
// ],
// title: strings.Premium_Avatar,
// titleColor: titleColor,
// subtitle: strings.Premium_AvatarInfo,
// subtitleColor: subtitleColor,
// arrowColor: arrowColor
// )
// )
// ),
// action: {
//
// }
// ),
// ],
// backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
// selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
// separatorColor: environment.theme.list.itemBlocksSeparatorColor
// ),
// environment: {},
// availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
// transition: context.transition
// )
context.add(section context.add(section
.position(CGPoint(x: availableWidth / 2.0, y: size.height + section.size.height / 2.0)) .position(CGPoint(x: availableWidth / 2.0, y: size.height + section.size.height / 2.0))
.clipsToBounds(true) .clipsToBounds(true)
@ -1276,7 +1600,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
return self._ready return self._ready
} }
public init(context: AccountContext, modal: Bool = true) { public init(context: AccountContext, modal: Bool = true, reference: String? = nil, source: PremiumSource? = nil) {
self.context = context self.context = context
var updateInProgressImpl: ((Bool) -> Void)? var updateInProgressImpl: ((Bool) -> Void)?

View File

@ -108,7 +108,7 @@ public final class PremiumReactionsScreen: ViewController {
self.proceedButton.pressed = { [weak self] in self.proceedButton.pressed = { [weak self] in
if let strongSelf = self, let controller = strongSelf.controller, let navigationController = controller.navigationController { if let strongSelf = self, let controller = strongSelf.controller, let navigationController = controller.navigationController {
strongSelf.animateOut() strongSelf.animateOut()
navigationController.pushViewController(PremiumIntroScreen(context: controller.context), animated: true) navigationController.pushViewController(PremiumIntroScreen(context: controller.context, source: .reactions), animated: true)
} }
} }

View File

@ -317,6 +317,7 @@ public final class ShareController: ViewController {
private let accountActiveDisposable = MetaDisposable() private let accountActiveDisposable = MetaDisposable()
private var defaultAction: ShareControllerAction? private var defaultAction: ShareControllerAction?
public private(set) var actionIsMediaSaving = false
public var actionCompleted: (() -> Void)? public var actionCompleted: (() -> Void)?
public var dismissed: ((Bool) -> Void)? public var dismissed: ((Bool) -> Void)?
@ -391,6 +392,7 @@ public final class ShareController: ViewController {
break break
case let .image(representations): case let .image(representations):
if case .saveToCameraRoll = preferredAction { if case .saveToCameraRoll = preferredAction {
self.actionIsMediaSaving = true
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in
self?.saveToCameraRoll(representations: representations) self?.saveToCameraRoll(representations: representations)
self?.actionCompleted?() self?.actionCompleted?()
@ -406,6 +408,7 @@ public final class ShareController: ViewController {
isVideo = file.isVideo isVideo = file.isVideo
} }
if case .saveToCameraRoll = preferredAction, canSave { if case .saveToCameraRoll = preferredAction, canSave {
self.actionIsMediaSaving = true
self.defaultAction = ShareControllerAction(title: isVideo ? self.presentationData.strings.Gallery_SaveVideo : self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in self.defaultAction = ShareControllerAction(title: isVideo ? self.presentationData.strings.Gallery_SaveVideo : self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in
self?.saveToCameraRoll(mediaReference: mediaReference) self?.saveToCameraRoll(mediaReference: mediaReference)
self?.actionCompleted?() self?.actionCompleted?()
@ -413,6 +416,7 @@ public final class ShareController: ViewController {
} }
case let .messages(messages): case let .messages(messages):
if case .saveToCameraRoll = preferredAction { if case .saveToCameraRoll = preferredAction {
self.actionIsMediaSaving = true
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in
self?.saveToCameraRoll(messages: messages) self?.saveToCameraRoll(messages: messages)
self?.actionCompleted?() self?.actionCompleted?()

View File

@ -262,10 +262,14 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
let bounds = self.bounds let bounds = self.bounds
let boundsSide = min(bounds.size.width - 14.0, bounds.size.height - 14.0) let boundsSide = min(bounds.size.width - 14.0, bounds.size.height - 14.0)
let boundingSize = CGSize(width: boundsSide, height: boundsSide) var boundingSize = CGSize(width: boundsSide, height: boundsSide)
if let (_, item) = self.currentState { if let (_, item) = self.currentState {
if let item = item, let dimensions = item.file.dimensions?.cgSize { if let item = item, let dimensions = item.file.dimensions?.cgSize {
if item.file.isPremiumSticker {
boundingSize = CGSize(width: boundingSize.width * 1.1, height: boundingSize.width * 1.1)
}
let imageSize = dimensions.aspectFitted(boundingSize) let imageSize = dimensions.aspectFitted(boundingSize)
let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize) let imageFrame = CGRect(origin: CGPoint(x: floor((bounds.size.width - imageSize.width) / 2.0), y: (bounds.size.height - imageSize.height) / 2.0), size: imageSize)
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))() self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()))()

View File

@ -751,7 +751,7 @@ private final class StickerPackContainer: ASDisplayNode {
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset)) let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset))
let itemsPerRow = 4 let itemsPerRow = 5
let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
let itemWidth = floor(fillingWidth / CGFloat(itemsPerRow)) let itemWidth = floor(fillingWidth / CGFloat(itemsPerRow))
let gridLeftInset = floor((layout.size.width - fillingWidth) / 2.0) let gridLeftInset = floor((layout.size.width - fillingWidth) / 2.0)

View File

@ -65,7 +65,25 @@ public struct PresentationResourcesItemList {
public static func verifiedPeerIcon(_ theme: PresentationTheme) -> UIImage? { public static func verifiedPeerIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListVerifiedPeerIcon.rawValue, { theme in return theme.image(PresentationResourceKey.itemListVerifiedPeerIcon.rawValue, { theme in
return UIImage(bundleImageName: "Item List/PeerVerifiedIcon")?.precomposed() if let backgroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Chat List/PeerVerifiedIconForeground") {
return generateImage(backgroundImage.size, contextGenerator: { size, context in
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveBackgroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.restoreGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
context.setFillColor(theme.chatList.unreadBadgeActiveTextColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
} else {
return nil
}
}) })
} }

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "premiumbadge_16 (1).pdf", "filename" : "premiumbadge_16 (2).pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -10,7 +10,7 @@ stream
/DeviceRGB CS /DeviceRGB CS
/DeviceRGB cs /DeviceRGB cs
q q
1.000000 0.000000 -0.000000 1.000000 1.050293 1.510986 cm 1.000000 0.000000 -0.000000 1.000000 1.050293 2.010986 cm
0.000000 0.000000 0.000000 scn 0.000000 0.000000 0.000000 scn
6.588397 2.211904 m 6.588397 2.211904 m
3.455913 0.292931 l 3.455913 0.292931 l

Binary file not shown.

Before

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 B

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "verifybadge1_16.pdf", "filename" : "verifybadge1_16 (1).pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -0,0 +1,107 @@
%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 3.000000 3.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 8.400000 m
0.000000 8.960052 0.000000 9.240079 0.108993 9.453991 c
0.204867 9.642153 0.357847 9.795134 0.546009 9.891006 c
0.759921 10.000000 1.039948 10.000000 1.600000 10.000000 c
8.400000 10.000000 l
8.960052 10.000000 9.240079 10.000000 9.453991 9.891006 c
9.642153 9.795134 9.795134 9.642153 9.891006 9.453991 c
10.000000 9.240079 10.000000 8.960052 10.000000 8.400000 c
10.000000 1.600000 l
10.000000 1.039948 10.000000 0.759921 9.891006 0.546009 c
9.795134 0.357847 9.642153 0.204866 9.453991 0.108994 c
9.240079 0.000000 8.960052 0.000000 8.400000 0.000000 c
1.600000 0.000000 l
1.039948 0.000000 0.759921 0.000000 0.546009 0.108994 c
0.357847 0.204866 0.204867 0.357847 0.108993 0.546009 c
0.000000 0.759921 0.000000 1.039948 0.000000 1.600000 c
0.000000 8.400000 l
h
f
n
Q
q
0.707107 0.707107 -0.707107 0.707107 10.928923 -1.999968 cm
0.000000 0.000000 0.000000 scn
0.000000 12.542089 m
0.000000 13.102142 0.000000 13.382169 0.108993 13.596081 c
0.204867 13.784243 0.357847 13.937223 0.546009 14.033096 c
0.759921 14.142090 1.039948 14.142090 1.600000 14.142090 c
8.400000 14.142090 l
8.960052 14.142090 9.240079 14.142090 9.453991 14.033096 c
9.642153 13.937223 9.795134 13.784243 9.891006 13.596081 c
10.000000 13.382169 10.000000 13.102142 10.000000 12.542089 c
10.000000 5.742090 l
10.000000 5.182037 10.000000 4.902011 9.891006 4.688099 c
9.795134 4.499937 9.642153 4.346956 9.453991 4.251083 c
9.240079 4.142090 8.960052 4.142090 8.400000 4.142090 c
1.600000 4.142090 l
1.039948 4.142090 0.759921 4.142090 0.546009 4.251083 c
0.357847 4.346956 0.204867 4.499937 0.108993 4.688099 c
0.000000 4.902011 0.000000 5.182037 0.000000 5.742090 c
0.000000 12.542089 l
h
f
n
Q
endstream
endobj
3 0 obj
1811
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.000000 16.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
0000001901 00000 n
0000001924 00000 n
0000002097 00000 n
0000002171 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2230
%%EOF

View File

@ -1,111 +0,0 @@
%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 1.300293 1.044312 cm
0.000000 0.000000 0.000000 scn
-0.030118 6.492163 m
0.081165 6.149670 0.378177 5.852658 0.972203 5.258633 c
1.449829 4.781006 l
1.449829 4.105689 l
1.449829 3.265610 1.449829 2.845571 1.613319 2.524703 c
1.757129 2.242459 1.986600 2.012989 2.268843 1.869179 c
2.589711 1.705688 3.009750 1.705688 3.849829 1.705688 c
4.525146 1.705688 l
5.002711 1.228124 l
5.596736 0.634098 5.893749 0.337086 6.236242 0.225803 c
6.537507 0.127916 6.862028 0.127916 7.163293 0.225803 c
7.505786 0.337086 7.802798 0.634098 8.396824 1.228124 c
8.874389 1.705688 l
9.549829 1.705688 l
10.389908 1.705688 10.809947 1.705688 11.130815 1.869179 c
11.413058 2.012989 11.642529 2.242459 11.786339 2.524703 c
11.949829 2.845571 11.949829 3.265610 11.949829 4.105688 c
11.949829 4.781129 l
12.427332 5.258632 l
12.427341 5.258640 l
13.021361 5.852661 13.318372 6.149672 13.429653 6.492163 c
13.527540 6.793428 13.527540 7.117949 13.429653 7.419214 c
13.318372 7.761705 13.021361 8.058716 12.427344 8.652733 c
12.427333 8.652744 l
11.949829 9.130249 l
11.949829 9.805689 l
11.949829 10.645767 11.949829 11.065806 11.786339 11.386674 c
11.642529 11.668918 11.413058 11.898388 11.130815 12.042198 c
10.809947 12.205688 10.389908 12.205688 9.549829 12.205688 c
8.874389 12.205688 l
8.396824 12.683253 l
7.802798 13.277279 7.505786 13.574291 7.163293 13.685574 c
6.862028 13.783461 6.537507 13.783461 6.236242 13.685574 c
5.893750 13.574291 5.596738 13.277280 5.002716 12.683257 c
5.002711 12.683253 l
4.525146 12.205688 l
3.849829 12.205688 l
3.009750 12.205688 2.589711 12.205688 2.268843 12.042198 c
1.986600 11.898388 1.757129 11.668918 1.613319 11.386674 c
1.449829 11.065806 1.449829 10.645767 1.449829 9.805689 c
1.449829 9.130371 l
0.972203 8.652744 l
0.972199 8.652741 l
0.378176 8.058718 0.081164 7.761706 -0.030118 7.419214 c
-0.128005 7.117949 -0.128005 6.793428 -0.030118 6.492163 c
h
f*
n
Q
endstream
endobj
3 0 obj
1960
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.000000 16.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
0000002050 00000 n
0000002073 00000 n
0000002246 00000 n
0000002320 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2379
%%EOF

View File

@ -1,7 +1,7 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "verifybadge2_16.pdf", "filename" : "verifybadge2_16 (1).pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -0,0 +1,76 @@
%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 4.625000 5.061890 cm
0.000000 0.000000 0.000000 scn
6.530330 4.926815 m
6.823223 4.633921 6.823223 4.159048 6.530330 3.866154 c
3.030330 0.366154 l
2.737437 0.073261 2.262563 0.073261 1.969670 0.366154 c
0.219670 2.116154 l
-0.073223 2.409048 -0.073223 2.883921 0.219670 3.176815 c
0.512563 3.469708 0.987437 3.469708 1.280330 3.176815 c
2.500000 1.957145 l
5.469670 4.926815 l
5.762563 5.219707 6.237437 5.219707 6.530330 4.926815 c
h
f*
n
Q
endstream
endobj
3 0 obj
510
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.000000 16.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
0000000600 00000 n
0000000622 00000 n
0000000795 00000 n
0000000869 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
928
%%EOF

View File

@ -1,92 +0,0 @@
%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.375000 4.311890 cm
0.000000 0.000000 0.000000 scn
0.530330 3.926815 m
0.237437 4.219707 -0.237437 4.219707 -0.530330 3.926815 c
-0.823223 3.633921 -0.823223 3.159048 -0.530330 2.866154 c
0.530330 3.926815 l
h
1.750000 1.646484 m
1.219670 1.116154 l
1.512563 0.823261 1.987437 0.823261 2.280330 1.116154 c
1.750000 1.646484 l
h
5.780330 4.616154 m
6.073223 4.909048 6.073223 5.383921 5.780330 5.676815 c
5.487437 5.969707 5.012563 5.969707 4.719670 5.676815 c
5.780330 4.616154 l
h
-0.530330 2.866154 m
1.219670 1.116154 l
2.280330 2.176815 l
0.530330 3.926815 l
-0.530330 2.866154 l
h
2.280330 1.116154 m
5.780330 4.616154 l
4.719670 5.676815 l
1.219670 2.176815 l
2.280330 1.116154 l
h
f
n
Q
endstream
endobj
3 0 obj
762
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 16.000000 16.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
0000000852 00000 n
0000000874 00000 n
0000001047 00000 n
0000001121 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1180
%%EOF

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -6,17 +6,16 @@
}, },
{ {
"idiom" : "universal", "idiom" : "universal",
"filename" : "ChannelVerifiedIconSmall@2x.png",
"scale" : "2x" "scale" : "2x"
}, },
{ {
"filename" : "AppBadge@3x.png",
"idiom" : "universal", "idiom" : "universal",
"filename" : "ChannelVerifiedIconSmall@3x.png",
"scale" : "3x" "scale" : "3x"
} }
], ],
"info" : { "info" : {
"version" : 1, "author" : "xcode",
"author" : "xcode" "version" : 1
} }
} }

View File

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

View File

@ -0,0 +1,107 @@
%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 1.625244 1.404785 cm
0.000000 0.000000 0.000000 scn
0.020125 7.977180 m
0.168502 7.520524 0.564519 7.124507 1.356553 6.332473 c
1.812012 5.877014 l
1.812012 5.232715 l
1.812012 4.112610 1.812012 3.552557 2.029999 3.124734 c
2.221745 2.748409 2.527707 2.442449 2.904031 2.250702 c
3.331854 2.032715 3.891907 2.032715 5.012012 2.032715 c
5.656311 2.032715 l
6.111846 1.577180 l
6.903880 0.785147 7.299897 0.389130 7.756554 0.240753 c
8.158240 0.110237 8.590935 0.110237 8.992621 0.240753 c
9.449278 0.389130 9.845295 0.785147 10.637329 1.577180 c
11.092864 2.032715 l
11.737012 2.032715 l
12.857117 2.032715 13.417170 2.032715 13.844993 2.250702 c
14.221317 2.442449 14.527278 2.748409 14.719025 3.124734 c
14.937012 3.552557 14.937012 4.112610 14.937012 5.232715 c
14.937012 5.876863 l
15.392622 6.332473 l
16.184656 7.124507 16.580673 7.520524 16.729050 7.977180 c
16.859566 8.378868 16.859566 8.811562 16.729050 9.213249 c
16.580673 9.669906 16.184656 10.065923 15.392622 10.857956 c
14.937012 11.313566 l
14.937012 11.957715 l
14.937012 13.077820 14.937012 13.637873 14.719025 14.065696 c
14.527278 14.442020 14.221317 14.747981 13.844993 14.939728 c
13.417170 15.157715 12.857117 15.157715 11.737012 15.157715 c
11.092864 15.157715 l
10.637329 15.613250 l
9.845295 16.405283 9.449278 16.801300 8.992621 16.949677 c
8.590935 17.080193 8.158240 17.080193 7.756554 16.949677 c
7.299897 16.801300 6.903880 16.405283 6.111846 15.613250 c
5.656311 15.157715 l
5.012012 15.157715 l
3.891907 15.157715 3.331854 15.157715 2.904031 14.939728 c
2.527707 14.747981 2.221745 14.442020 2.029999 14.065696 c
1.812012 13.637873 1.812012 13.077820 1.812012 11.957715 c
1.812012 11.313416 l
1.356553 10.857956 l
0.564519 10.065923 0.168502 9.669906 0.020125 9.213249 c
-0.110391 8.811562 -0.110391 8.378868 0.020125 7.977180 c
h
f*
n
Q
endstream
endobj
3 0 obj
1888
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 20.000000 20.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
0000001978 00000 n
0000002001 00000 n
0000002174 00000 n
0000002248 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
2307
%%EOF

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "verifybadge2_20.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
1.000000 0.000000 -0.000000 1.000000 6.718750 5.801514 cm
0.000000 0.000000 0.000000 scn
0.530330 4.364315 m
0.237437 4.657207 -0.237437 4.657207 -0.530330 4.364315 c
-0.823223 4.071421 -0.823223 3.596547 -0.530330 3.303654 c
0.530330 4.364315 l
h
2.187500 1.646484 m
1.657170 1.116154 l
1.950063 0.823261 2.424937 0.823261 2.717830 1.116154 c
2.187500 1.646484 l
h
7.092830 5.491154 m
7.385724 5.784048 7.385724 6.258921 7.092830 6.551815 c
6.799937 6.844707 6.325063 6.844707 6.032170 6.551815 c
7.092830 5.491154 l
h
-0.530330 3.303654 m
1.657170 1.116154 l
2.717830 2.176814 l
0.530330 4.364315 l
-0.530330 3.303654 l
h
2.717830 1.116154 m
7.092830 5.491154 l
6.032170 6.551815 l
1.657170 2.176814 l
2.717830 1.116154 l
h
f
n
Q
endstream
endobj
3 0 obj
762
endobj
4 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 20.000000 20.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
0000000852 00000 n
0000000874 00000 n
0000001047 00000 n
0000001121 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 6 0 R
/Size 7
>>
startxref
1180
%%EOF

View File

@ -3125,6 +3125,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !onlyHaptic { if !onlyHaptic {
strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected() strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected()
} }
}, displayPremiumStickerTooltip: { [weak self] file, message in
self?.displayPremiumStickerTooltip(file: file, message: message)
}, openPeerContextMenu: { [weak self] peer, messageId, node, rect, gesture in }, openPeerContextMenu: { [weak self] peer, messageId, node, rect, gesture in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
@ -3358,7 +3360,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(false))).start(next: { [weak self] responded in |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(false))).start(next: { [weak self] responded in
if let strongSelf = self { if let strongSelf = self {
if !responded { if !responded {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string), elevatedLayout: false, action: { _ in return false }), in: .current) strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: strongSelf.presentationData.strings.Conversation_InteractiveEmojiSyncTip(EnginePeer(peer).compactDisplayTitle).string, undoText: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
let _ = ApplicationSpecificNotice.incrementInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start() let _ = ApplicationSpecificNotice.incrementInteractiveEmojiSyncTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start()
} }
@ -3486,7 +3488,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}) })
controller.navigationPresentation = .flatModal controller.navigationPresentation = .flatModal
strongSelf.push(controller) strongSelf.push(controller)
// strongSelf.present(controller, in: .window(.root))
strongSelf.currentMenuWebAppController = controller strongSelf.currentMenuWebAppController = controller
} else if simple { } else if simple {
strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestSimpleWebView(botId: peerId, url: url, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme)) strongSelf.messageActionCallbackDisposable.set(((strongSelf.context.engine.messages.requestSimpleWebView(botId: peerId, url: url, themeParams: generateWebAppThemeParams(strongSelf.presentationData.theme))
@ -7952,12 +7953,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self { if let strongSelf = self {
switch result { switch result {
case .generic: case .generic:
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: nil, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites), elevatedLayout: true, action: { _ in return false }), with: nil) strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: nil, text: added ? strongSelf.presentationData.strings.Conversation_StickerAddedToFavorites : strongSelf.presentationData.strings.Conversation_StickerRemovedFromFavorites, undoText: nil), elevatedLayout: true, action: { _ in return false }), with: nil)
case .limitExceeded: case .limitExceeded:
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: strongSelf.presentationData.strings.Premium_MaxFavedStickersTitle("5").string, text: strongSelf.presentationData.strings.Premium_MaxFavedStickersText("10").string), elevatedLayout: true, action: { [weak self] action in strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: stickerFile, title: strongSelf.presentationData.strings.Premium_MaxFavedStickersTitle("5").string, text: strongSelf.presentationData.strings.Premium_MaxFavedStickersText("10").string, undoText: nil), elevatedLayout: true, action: { [weak self] action in
if let strongSelf = self { if let strongSelf = self {
if case .info = action { if case .info = action {
let controller = PremiumIntroScreen(context: strongSelf.context) let controller = PremiumIntroScreen(context: strongSelf.context, source: .savedStickers)
strongSelf.push(controller) strongSelf.push(controller)
return true return true
} }
@ -11176,7 +11177,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
} }
let present = { let present = {
strongSelf.present(attachmentController, in: .window(.root)) attachmentController.navigationPresentation = .flatModal
strongSelf.push(attachmentController)
// strongSelf.present(attachmentController, in: .window(.root))
strongSelf.attachmentController = attachmentController strongSelf.attachmentController = attachmentController
} }
@ -12353,6 +12356,51 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return transformEnqueueMessages(messages, silentPosting: silentPosting) return transformEnqueueMessages(messages, silentPosting: silentPosting)
} }
private func displayPremiumStickerTooltip(file: TelegramMediaFile, message: Message) {
var currentOverlayController: UndoOverlayController?
self.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
currentOverlayController = controller
}
})
self.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
currentOverlayController = controller
}
return true
})
if let currentOverlayController = currentOverlayController {
if case .sticker = currentOverlayController.content {
return
}
currentOverlayController.dismissWithCommitAction()
}
var stickerPackReference: StickerPackReference?
for attribute in file.attributes {
if case let .Sticker(_, packReference, _) = attribute, let packReference = packReference {
stickerPackReference = packReference
break
}
}
if let stickerPackReference = stickerPackReference {
let _ = (self.context.engine.stickers.loadedStickerPack(reference: stickerPackReference, forceActualized: false)
|> deliverOnMainQueue).start(next: { [weak self] stickerPack in
if let strongSelf = self, case let .result(info, _, _) = stickerPack {
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .sticker(context: strongSelf.context, file: file, title: info.title, text: strongSelf.presentationData.strings.Stickers_PremiumPackInfoText, undoText: strongSelf.presentationData.strings.Stickers_PremiumPackView), elevatedLayout: false, action: { [weak self] action in
if let strongSelf = self, action == .undo {
let _ = strongSelf.controllerInteraction?.openMessage(message, .default)
}
return false
}), in: .current)
}
})
}
}
private func displayDiceTooltip(dice: TelegramMediaDice) { private func displayDiceTooltip(dice: TelegramMediaDice) {
guard let _ = dice.value else { guard let _ = dice.value else {
return return

View File

@ -118,6 +118,7 @@ public final class ChatControllerInteraction {
let displayPsa: (String, ASDisplayNode) -> Void let displayPsa: (String, ASDisplayNode) -> Void
let displayDiceTooltip: (TelegramMediaDice) -> Void let displayDiceTooltip: (TelegramMediaDice) -> Void
let animateDiceSuccess: (Bool) -> Void let animateDiceSuccess: (Bool) -> Void
let displayPremiumStickerTooltip: (TelegramMediaFile, Message) -> Void
let openPeerContextMenu: (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void let openPeerContextMenu: (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void
let openMessageReplies: (MessageId, Bool, Bool) -> Void let openMessageReplies: (MessageId, Bool, Bool) -> Void
let openReplyThreadOriginalMessage: (Message) -> Void let openReplyThreadOriginalMessage: (Message) -> Void
@ -218,6 +219,7 @@ public final class ChatControllerInteraction {
displayPsa: @escaping (String, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void,
displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void,
animateDiceSuccess: @escaping (Bool) -> Void, animateDiceSuccess: @escaping (Bool) -> Void,
displayPremiumStickerTooltip: @escaping (TelegramMediaFile, Message) -> Void,
openPeerContextMenu: @escaping (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void, openPeerContextMenu: @escaping (Peer, MessageId?, ASDisplayNode, CGRect, ContextGesture?) -> Void,
openMessageReplies: @escaping (MessageId, Bool, Bool) -> Void, openMessageReplies: @escaping (MessageId, Bool, Bool) -> Void,
openReplyThreadOriginalMessage: @escaping (Message) -> Void, openReplyThreadOriginalMessage: @escaping (Message) -> Void,
@ -304,6 +306,7 @@ public final class ChatControllerInteraction {
self.openMessagePollResults = openMessagePollResults self.openMessagePollResults = openMessagePollResults
self.displayDiceTooltip = displayDiceTooltip self.displayDiceTooltip = displayDiceTooltip
self.animateDiceSuccess = animateDiceSuccess self.animateDiceSuccess = animateDiceSuccess
self.displayPremiumStickerTooltip = displayPremiumStickerTooltip
self.openPeerContextMenu = openPeerContextMenu self.openPeerContextMenu = openPeerContextMenu
self.openMessageReplies = openMessageReplies self.openMessageReplies = openMessageReplies
self.openReplyThreadOriginalMessage = openReplyThreadOriginalMessage self.openReplyThreadOriginalMessage = openReplyThreadOriginalMessage
@ -362,6 +365,7 @@ public final class ChatControllerInteraction {
}, displayPsa: { _, _ in }, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in }, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in }, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in }, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in }, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in }, openReplyThreadOriginalMessage: { _ in

View File

@ -1723,10 +1723,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
} }
if let item = self.item, self.imageNode.frame.contains(location) { if let item = self.item, self.imageNode.frame.contains(location) {
if let _ = self.telegramFile { if let file = self.telegramFile {
return .optionalAction({ if file.isPremiumSticker {
let _ = item.controllerInteraction.openMessage(item.message, .default) return .optionalAction({
}) if self.additionalAnimationNodes.isEmpty {
self.playedPremiumStickerAnimation = false
self.playPremiumStickerAnimation()
} else {
item.controllerInteraction.displayPremiumStickerTooltip(file, item.message)
}
})
} else {
return .optionalAction({
let _ = item.controllerInteraction.openMessage(item.message, .default)
})
}
} else if let dice = self.telegramDice { } else if let dice = self.telegramDice {
return .optionalAction({ return .optionalAction({
item.controllerInteraction.displayDiceTooltip(dice) item.controllerInteraction.displayDiceTooltip(dice)

View File

@ -570,7 +570,13 @@ final class ChatQrCodeScreen: ViewController {
var fileName: String { var fileName: String {
switch self { switch self {
case let .peer(peer): case let .peer(peer):
return "t_me-\(peer.addressName ?? "")" if let addressName = peer.addressName, !addressName.isEmpty {
return "t_me-\(peer.addressName ?? "")"
} else if let peer = peer as? TelegramUser {
return "t_me-\(peer.phone ?? "")"
} else {
return "t_me-\(Int32.random(in: 0 ..< Int32.max))"
}
case let .messages(messages): case let .messages(messages):
if let message = messages.first, let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty { if let message = messages.first, let chatPeer = message.peers[message.id.peerId] as? TelegramChannel, message.id.namespace == Namespaces.Message.Cloud, let addressName = chatPeer.addressName, !addressName.isEmpty {
return "t_me-\(addressName)-\(message.id.id)" return "t_me-\(addressName)-\(message.id.id)"
@ -1510,9 +1516,16 @@ private class QrContentNode: ASDisplayNode, ContentNode {
self.codeStaticIconNode = nil self.codeStaticIconNode = nil
} }
let codeText: String
if let addressName = peer.addressName, !addressName.isEmpty {
codeText = "@\(peer.addressName ?? "")".uppercased()
} else {
codeText = peer.debugDisplayTitle.uppercased()
}
self.codeTextNode = ImmediateTextNode() self.codeTextNode = ImmediateTextNode()
self.codeTextNode.displaysAsynchronously = false self.codeTextNode.displaysAsynchronously = false
self.codeTextNode.attributedText = NSAttributedString(string: "@\(peer.addressName ?? "")".uppercased(), font: Font.with(size: 23.0, design: .round, weight: .bold, traits: []), textColor: .black) self.codeTextNode.attributedText = NSAttributedString(string: codeText, font: Font.with(size: 23.0, design: .round, weight: .bold, traits: []), textColor: .black)
self.codeTextNode.truncationMode = .byCharWrapping self.codeTextNode.truncationMode = .byCharWrapping
self.codeTextNode.maximumNumberOfLines = 2 self.codeTextNode.maximumNumberOfLines = 2
self.codeTextNode.textAlignment = .center self.codeTextNode.textAlignment = .center
@ -1547,8 +1560,17 @@ private class QrContentNode: ASDisplayNode, ContentNode {
self.addSubnode(codeAnimatedIconNode) self.addSubnode(codeAnimatedIconNode)
} }
let codeLink: String
if let addressName = peer.addressName, !addressName.isEmpty {
codeLink = "https://t.me/\(peer.addressName ?? "")"
} else if let peer = peer as? TelegramUser {
codeLink = "https://t.me/+\(peer.phone ?? "")"
} else {
codeLink = ""
}
let codeReadyPromise = ValuePromise<Bool>() let codeReadyPromise = ValuePromise<Bool>()
self.codeImageNode.setSignal(qrCode(string: "https://t.me/\(peer.addressName ?? "")", color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q") |> beforeNext { [weak self] size, _ in self.codeImageNode.setSignal(qrCode(string: codeLink, color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q") |> beforeNext { [weak self] size, _ in
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -518,6 +518,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, displayPsa: { _, _ in }, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in }, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in }, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in }, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in }, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in }, openReplyThreadOriginalMessage: { _ in
@ -958,6 +959,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
#endif #endif
case .settings: case .settings:
break break
case .premiumOffer:
break
case let .joinVoiceChat(peerId, invite): case let .joinVoiceChat(peerId, invite):
strongSelf.presentController(VoiceChatJoinScreen(context: strongSelf.context, peerId: peerId, invite: invite, join: { call in strongSelf.presentController(VoiceChatJoinScreen(context: strongSelf.context, peerId: peerId, invite: invite, join: { call in
}), .window(.root), nil) }), .window(.root), nil)

View File

@ -144,6 +144,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, displayPsa: { _, _ in }, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in }, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in }, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in }, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in }, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in }, openReplyThreadOriginalMessage: { _ in

View File

@ -355,9 +355,9 @@ private final class InlineReactionSearchStickersNode: ASDisplayNode, UIScrollVie
private func updateItemsLayout(width: CGFloat) { private func updateItemsLayout(width: CGFloat) {
self.displayItems.removeAll() self.displayItems.removeAll()
let itemsPerRow = min(8, max(4, Int(width / 80))) let itemsPerRow = min(8, max(5, Int(width / 80)))
let sideInset: CGFloat = 4.0 let sideInset: CGFloat = 2.0
let itemSpacing: CGFloat = 4.0 let itemSpacing: CGFloat = 2.0
let itemSize = floor((width - sideInset * 2.0 - itemSpacing * (CGFloat(itemsPerRow) - 1.0)) / CGFloat(itemsPerRow)) let itemSize = floor((width - sideInset * 2.0 - itemSpacing * (CGFloat(itemsPerRow) - 1.0)) / CGFloat(itemsPerRow))
var columnIndex = 0 var columnIndex = 0

View File

@ -27,6 +27,7 @@ import PeerInfoUI
import Markdown import Markdown
import WebUI import WebUI
import BotPaymentsUI import BotPaymentsUI
import PremiumUI
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
if case .default = navigation { if case .default = navigation {
@ -514,8 +515,19 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
navigationController.setViewControllers(controllers, animated: true) navigationController.setViewControllers(controllers, animated: true)
}) })
} }
break
} }
case let .premiumOffer(reference):
dismissInput()
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { peer in
let isPremium = peer?.isPremium ?? false
if !isPremium {
let controller = PremiumIntroScreen(context: context, reference: reference)
if let navigationController = navigationController {
navigationController.pushViewController(controller, animated: true)
}
}
})
case let .joinVoiceChat(peerId, invite): case let .joinVoiceChat(peerId, invite):
dismissInput() dismissInput()
if let navigationController = navigationController { if let navigationController = navigationController {

View File

@ -724,6 +724,20 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
return return
} }
} }
} else if parsedUrl.host == "premium_offer" {
var reference: String?
if let components = URLComponents(string: "/?" + query) {
if let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "ref" {
reference = value
}
}
}
}
}
handleResolvedUrl(.premiumOffer(reference: reference))
} }
} else { } else {
if parsedUrl.host == "importStickers" { if parsedUrl.host == "importStickers" {
@ -743,6 +757,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
handleResolvedUrl(.settings(section)) handleResolvedUrl(.settings(section))
} }
} }
} else if parsedUrl.host == "premium_offer" {
handleResolvedUrl(.premiumOffer(reference: nil))
} }
} }

View File

@ -136,6 +136,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, displayPsa: { _, _ in }, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in }, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in }, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in }, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in }, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in }, openReplyThreadOriginalMessage: { _ in

View File

@ -2312,15 +2312,36 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} else if case .scam = credibilityIcon { } else if case .scam = credibilityIcon {
image = PresentationResourcesChatList.scamIcon(presentationData.theme, strings: presentationData.strings, type: .regular) image = PresentationResourcesChatList.scamIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
} else if case .verified = credibilityIcon { } else if case .verified = credibilityIcon {
if let sourceImage = UIImage(bundleImageName: "Peer Info/VerifiedIcon") { if let backgroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconForeground") {
image = generateImage(sourceImage.size, contextGenerator: { size, context in image = generateImage(backgroundImage.size, contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size)) if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor) context.clear(CGRect(origin: CGPoint(), size: size))
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 7.0, dy: 7.0)) context.saveGState()
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor) context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
context.clip(to: CGRect(origin: CGPoint(), size: size), mask: sourceImage.cgImage!)
context.fill(CGRect(origin: CGPoint(), size: size)) context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
}) context.fill(CGRect(origin: CGPoint(), size: size))
context.restoreGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
expandedImage = generateImage(backgroundImage.size, contextGenerator: { size, context in
if let backgroundCgImage = backgroundImage.cgImage, let foregroundCgImage = foregroundImage.cgImage {
context.clear(CGRect(origin: CGPoint(), size: size))
context.saveGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: backgroundCgImage)
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 0.75).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.restoreGState()
context.clip(to: CGRect(origin: .zero, size: size), mask: foregroundCgImage)
context.setBlendMode(.clear)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
} else { } else {
image = nil image = nil
} }
@ -2331,17 +2352,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
context.clear(CGRect(origin: CGPoint(), size: size)) context.clear(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage) context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
let colorsArray: [CGColor] = [ context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
UIColor(rgb: 0x6B93FF).cgColor, context.fill(CGRect(origin: CGPoint(), size: size))
UIColor(rgb: 0x6B93FF).cgColor,
UIColor(rgb: 0x976FFF).cgColor,
UIColor(rgb: 0xE46ACE).cgColor,
UIColor(rgb: 0xE46ACE).cgColor
]
var locations: [CGFloat] = [0.0, 0.35, 0.5, 0.65, 1.0]
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray as CFArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: size.height), options: CGGradientDrawingOptions())
} }
}, opaque: false) }, opaque: false)
expandedImage = generateImage(sourceImage.size, contextGenerator: { size, context in expandedImage = generateImage(sourceImage.size, contextGenerator: { size, context in

View File

@ -2303,6 +2303,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, displayPsa: { _, _ in }, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in }, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in }, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in }, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in }, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in }, openReplyThreadOriginalMessage: { _ in
@ -5576,6 +5577,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in actions.append(ContextMenuAction(content: .text(title: presentationData.strings.Conversation_ContextMenuTranslate, accessibilityLabel: presentationData.strings.Conversation_ContextMenuTranslate), action: { [weak self] in
let controller = TranslateScreen(context: context, text: text, fromLanguage: language) let controller = TranslateScreen(context: context, text: text, fromLanguage: language)
controller.pushController = { [weak self] c in
(self?.controller?.navigationController as? NavigationController)?._keepModalDismissProgress = true
self?.controller?.push(c)
}
controller.presentController = { [weak self] c in
self?.controller?.present(c, in: .window(.root))
}
self?.controller?.present(controller, in: .window(.root)) self?.controller?.present(controller, in: .window(.root))
})) }))
} }
@ -6204,7 +6212,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
case .language: case .language:
push(LocalizationListController(context: self.context)) push(LocalizationListController(context: self.context))
case .premium: case .premium:
self.controller?.push(PremiumIntroScreen(context: self.context, modal: false)) self.controller?.push(PremiumIntroScreen(context: self.context, modal: false, source: .settings))
case .stickers: case .stickers:
if let settings = self.data?.globalSettings { if let settings = self.data?.globalSettings {
push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in push(installedStickerPacksController(context: self.context, mode: .general, archivedPacks: settings.archivedStickerPacks, updatedPacks: { [weak self] packs in
@ -7457,10 +7465,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false))
} else { } else {
if self.isSettings { if self.isSettings {
if let addressName = self.data?.peer?.addressName, !addressName.isEmpty { leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false))
}
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false))
rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true))
} else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { } else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData, isContact: self.data?.isContact) {

View File

@ -1313,6 +1313,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, displayPsa: { _, _ in }, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in }, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in }, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in }, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in }, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in }, openReplyThreadOriginalMessage: { _ in

View File

@ -34,7 +34,7 @@ public enum UndoOverlayContent {
case voiceChatRecording(text: String) case voiceChatRecording(text: String)
case voiceChatFlag(text: String) case voiceChatFlag(text: String)
case voiceChatCanSpeak(text: String) case voiceChatCanSpeak(text: String)
case sticker(context: AccountContext, file: TelegramMediaFile, title: String?, text: String) case sticker(context: AccountContext, file: TelegramMediaFile, title: String?, text: String, undoText: String?)
case copy(text: String) case copy(text: String)
case mediaSaved(text: String) case mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String) case paymentSent(currencyValue: String, itemTitle: String)

View File

@ -628,7 +628,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 3 self.originalRemainingSeconds = 3
case let .sticker(context, file, title, text): case let .sticker(context, file, title, text, customUndoText):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil
self.iconCheckNode = nil self.iconCheckNode = nil
@ -702,7 +702,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
isUserInteractionEnabled = true isUserInteractionEnabled = true
} }
displayUndo = false if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = 3 self.originalRemainingSeconds = 3
if let updatedFetchSignal = updatedFetchSignal { if let updatedFetchSignal = updatedFetchSignal {
@ -869,12 +874,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content { switch content {
case .removedChat: case .removedChat:
self.panelWrapperNode.addSubnode(self.timerTextNode) self.panelWrapperNode.addSubnode(self.timerTextNode)
case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .sticker, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal: case .archivedChat, .hidArchive, .revealedArchive, .autoDelete, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward, .gigagroupConversion, .linkRevoked, .voiceChatRecording, .voiceChatFlag, .voiceChatCanSpeak, .copy, .mediaSaved, .paymentSent, .image, .inviteRequestSent, .notificationSoundAdded, .universal:
if self.textNode.tapAttributeAction != nil { if self.textNode.tapAttributeAction != nil {
self.isUserInteractionEnabled = true self.isUserInteractionEnabled = true
} else { } else {
self.isUserInteractionEnabled = false self.isUserInteractionEnabled = false
} }
case let .sticker(_, _, _, _, undoText):
self.isUserInteractionEnabled = undoText != nil
case .dice: case .dice:
self.panelWrapperNode.clipsToBounds = true self.panelWrapperNode.clipsToBounds = true
case .info: case .info:

View File

@ -427,6 +427,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
} }
private let hapticFeedback = HapticFeedback()
private var delayedScriptMessage: WKScriptMessage? private var delayedScriptMessage: WKScriptMessage?
private func handleScriptMessage(_ message: WKScriptMessage) { private func handleScriptMessage(_ message: WKScriptMessage) {
guard let controller = self.controller else { guard let controller = self.controller else {
@ -487,7 +489,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
case "web_app_open_invoice": case "web_app_open_invoice":
if let json = json, let slug = json["slug"] as? String { if let json = json, let slug = json["slug"] as? String {
self.paymentDisposable = (context.engine.payments.fetchBotPaymentInvoice(source: .slug(slug)) self.paymentDisposable = (self.context.engine.payments.fetchBotPaymentInvoice(source: .slug(slug))
|> map(Optional.init) |> map(Optional.init)
|> `catch` { _ -> Signal<TelegramMediaInvoice?, NoError> in |> `catch` { _ -> Signal<TelegramMediaInvoice?, NoError> in
return .single(nil) return .single(nil)
@ -510,6 +512,51 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
}) })
} }
case "web_app_open_link":
if let json = json, let url = json["url"] as? String {
let currentTimestamp = CACurrentMediaTime()
if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 {
self.webView?.lastTouchTimestamp = nil
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
}
}
case "web_app_setup_back_button":
break
case "web_app_trigger_haptic_feedback":
if let json = json, let type = json["type"] as? String {
switch type {
case "impact":
if let impactType = json["impact_style"] as? String {
switch impactType {
case "light":
self.hapticFeedback.impact(.light)
case "medium":
self.hapticFeedback.impact(.medium)
case "heavy":
self.hapticFeedback.impact(.heavy)
default:
break
}
}
case "notification":
if let notificationType = json["notification_type"] as? String {
switch notificationType {
case "success":
self.hapticFeedback.success()
case "error":
self.hapticFeedback.error()
case "warning":
self.hapticFeedback.warning()
default:
break
}
}
case "selection_change":
self.hapticFeedback.tap()
default:
break
}
}
default: default:
break break
} }

View File

@ -132,6 +132,7 @@ final class WebAppWebView: WKWebView {
self.sendEvent(name: "viewport_changed", data: data) self.sendEvent(name: "viewport_changed", data: data)
} }
var lastTouchTimestamp: Double?
private(set) var didTouchOnce = false private(set) var didTouchOnce = false
var onFirstTouch: () -> Void = {} var onFirstTouch: () -> Void = {}
@ -161,6 +162,7 @@ final class WebAppWebView: WKWebView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let result = super.hitTest(point, with: event) let result = super.hitTest(point, with: event)
self.lastTouchTimestamp = CACurrentMediaTime()
if result != nil && !self.didTouchOnce { if result != nil && !self.didTouchOnce {
self.didTouchOnce = true self.didTouchOnce = true
self.onFirstTouch() self.onFirstTouch()