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.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 startAttach(peerId: PeerId, payload: String?)
case invoice(slug: String, invoice: TelegramMediaInvoice)
case premiumOffer(reference: String?)
}
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)
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
let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
@ -785,7 +785,9 @@ public class AttachmentController: ViewController {
}
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)

View File

@ -331,7 +331,7 @@ func chatContextMenuItems(context: AccountContext, peerId: PeerId, promoInfo: Ch
var replaceImpl: ((ViewController) -> Void)?
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)
})
chatListController?.push(controller)

View File

@ -1355,7 +1355,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
if data.includePeers.peers.count >= limit {
var replaceImpl: ((ViewController) -> Void)?
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 = { [weak controller] c in

View File

@ -299,7 +299,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
if filters.count >= limit {
var replaceImpl: ((ViewController) -> Void)?
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 = { [weak controller] c in

View File

@ -830,30 +830,44 @@ public final class ChatListNode: ListView {
}
}
}, setItemPinned: { [weak self] itemId, _ in
let location: TogglePeerChatPinnedLocation
if let chatListFilter = chatListFilter {
location = .filter(chatListFilter.id)
} else {
location = .group(groupId._asGroup())
}
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, _):
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.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
let isPremium = peer?.isPremium ?? false
let location: TogglePeerChatPinnedLocation
if let chatListFilter = chatListFilter {
location = .filter(chatListFilter.id)
} else {
location = .group(groupId._asGroup())
}
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
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() {
}
}
@ -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, *)

View File

@ -361,6 +361,11 @@ open class NavigationController: UINavigationController, ContainableController,
private func updateContainers(layout rawLayout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
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
if self.ignoreInputHeight {
@ -631,6 +636,7 @@ open class NavigationController: UINavigationController, ContainableController,
var topVisibleModalContainerWithStatusBar: NavigationModalContainer?
var visibleModalCount = 0
var topModalIsFlat = false
var topFlatModalHasProgress = false
let isLandscape = layout.orientation == .landscape
var hasVisibleStandaloneModal = false
var topModalDismissProgress: CGFloat = 0.0
@ -659,6 +665,17 @@ open class NavigationController: UINavigationController, ContainableController,
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))
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
var additionalModalFrameProgress: CGFloat
if visibleModalCount == 1 {
effectiveRootModalDismissProgress = (topModalIsFlat || isLandscape) ? 1.0 : topModalDismissProgress
visibleRootModalDismissProgress = effectiveRootModalDismissProgress
additionalModalFrameProgress = 0.0
if topFlatModalHasProgress {
effectiveRootModalDismissProgress = 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 {
effectiveRootModalDismissProgress = 0.0
visibleRootModalDismissProgress = topModalDismissProgress
@ -929,7 +952,7 @@ open class NavigationController: UINavigationController, ContainableController,
}
let maxScale: CGFloat
let maxOffset: CGFloat
if topModalIsFlat || isLandscape {
if (topModalIsFlat && !topFlatModalHasProgress) || isLandscape {
maxScale = 1.0
maxOffset = 0.0
} else if visibleModalCount <= 1 {
@ -1219,8 +1242,16 @@ open class NavigationController: UINavigationController, ContainableController,
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) {
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)
}
}))
} else if file.mimeType.hasPrefix("image/") || file.mimeType.hasPrefix("video/") {
} else if file.mimeType.hasPrefix("image/") {
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
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
if let strongSelf = self {
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in

View File

@ -356,6 +356,13 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
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))
}
}

View File

@ -1096,6 +1096,13 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate {
if canTranslate {
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)
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)
}))
}

View File

@ -1630,7 +1630,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
if hasNamesToRevoke && selectedType == .publicChannel {
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)
})
}

View File

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

View File

@ -3,11 +3,13 @@ import UIKit
import Display
import ComponentFlow
import SwiftSignalKit
import TelegramCore
import TelegramPresentationData
import PresentationDataUtils
import ViewControllerComponent
import AccountContext
import SolidRoundedButtonComponent
import MultilineTextComponent
import PresentationDataUtils
import PrefixSectionGroupComponent
import BundleIconComponent
import SolidRoundedButtonComponent
@ -17,6 +19,236 @@ import ConfettiEffect
import TextFormat
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 {
public final class Item: Equatable {
public let content: AnyComponentWithIdentity<Empty>
@ -495,6 +727,40 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
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 {
let overscroll = Child(Rectangle.self)
let fade = Child(RoundedRectangle.self)
@ -510,6 +776,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
let scrollEnvironment = context.environment[ScrollChildEnvironment.self].value
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
let state = context.state
let theme = environment.theme
let strings = environment.strings
@ -576,230 +843,52 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
size.height += text.size.height
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(
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: {
}
),
],
items: items,
backgroundColor: environment.theme.list.itemBlocksBackgroundColor,
selectionColor: environment.theme.list.itemHighlightedBackgroundColor,
separatorColor: environment.theme.list.itemBlocksSeparatorColor
@ -808,6 +897,241 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude),
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
.position(CGPoint(x: availableWidth / 2.0, y: size.height + section.size.height / 2.0))
.clipsToBounds(true)
@ -1276,7 +1600,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
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
var updateInProgressImpl: ((Bool) -> Void)?

View File

@ -108,7 +108,7 @@ public final class PremiumReactionsScreen: ViewController {
self.proceedButton.pressed = { [weak self] in
if let strongSelf = self, let controller = strongSelf.controller, let navigationController = controller.navigationController {
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 var defaultAction: ShareControllerAction?
public private(set) var actionIsMediaSaving = false
public var actionCompleted: (() -> Void)?
public var dismissed: ((Bool) -> Void)?
@ -391,6 +392,7 @@ public final class ShareController: ViewController {
break
case let .image(representations):
if case .saveToCameraRoll = preferredAction {
self.actionIsMediaSaving = true
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Gallery_SaveImage, action: { [weak self] in
self?.saveToCameraRoll(representations: representations)
self?.actionCompleted?()
@ -406,6 +408,7 @@ public final class ShareController: ViewController {
isVideo = file.isVideo
}
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?.saveToCameraRoll(mediaReference: mediaReference)
self?.actionCompleted?()
@ -413,6 +416,7 @@ public final class ShareController: ViewController {
}
case let .messages(messages):
if case .saveToCameraRoll = preferredAction {
self.actionIsMediaSaving = true
self.defaultAction = ShareControllerAction(title: self.presentationData.strings.Preview_SaveToCameraRoll, action: { [weak self] in
self?.saveToCameraRoll(messages: messages)
self?.actionCompleted?()

View File

@ -262,10 +262,14 @@ final class StickerPackPreviewGridItemNode: GridItemNode {
let bounds = self.bounds
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 = 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 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()))()

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 itemsPerRow = 4
let itemsPerRow = 5
let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
let itemWidth = floor(fillingWidth / CGFloat(itemsPerRow))
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? {
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" : [
{
"filename" : "premiumbadge_16 (1).pdf",
"filename" : "premiumbadge_16 (2).pdf",
"idiom" : "universal"
}
],

View File

@ -10,7 +10,7 @@ stream
/DeviceRGB CS
/DeviceRGB cs
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
6.588397 2.211904 m
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" : [
{
"filename" : "verifybadge1_16.pdf",
"filename" : "verifybadge1_16 (1).pdf",
"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" : [
{
"filename" : "verifybadge2_16.pdf",
"filename" : "verifybadge2_16 (1).pdf",
"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",
"filename" : "ChannelVerifiedIconSmall@2x.png",
"scale" : "2x"
},
{
"filename" : "AppBadge@3x.png",
"idiom" : "universal",
"filename" : "ChannelVerifiedIconSmall@3x.png",
"scale" : "3x"
}
],
"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 {
strongSelf.chatDisplayNode.animateQuizCorrectOptionSelected()
}
}, displayPremiumStickerTooltip: { [weak self] file, message in
self?.displayPremiumStickerTooltip(file: file, message: message)
}, openPeerContextMenu: { [weak self] peer, messageId, node, rect, gesture in
guard let strongSelf = self else {
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
if let strongSelf = self {
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()
}
@ -3486,7 +3488,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
controller.navigationPresentation = .flatModal
strongSelf.push(controller)
// strongSelf.present(controller, in: .window(.root))
strongSelf.currentMenuWebAppController = controller
} else if simple {
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 {
switch result {
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:
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 case .info = action {
let controller = PremiumIntroScreen(context: strongSelf.context)
let controller = PremiumIntroScreen(context: strongSelf.context, source: .savedStickers)
strongSelf.push(controller)
return true
}
@ -11176,7 +11177,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
let present = {
strongSelf.present(attachmentController, in: .window(.root))
attachmentController.navigationPresentation = .flatModal
strongSelf.push(attachmentController)
// strongSelf.present(attachmentController, in: .window(.root))
strongSelf.attachmentController = attachmentController
}
@ -12353,6 +12356,51 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
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) {
guard let _ = dice.value else {
return

View File

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

View File

@ -1723,10 +1723,21 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if let item = self.item, self.imageNode.frame.contains(location) {
if let _ = self.telegramFile {
return .optionalAction({
let _ = item.controllerInteraction.openMessage(item.message, .default)
})
if let file = self.telegramFile {
if file.isPremiumSticker {
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 {
return .optionalAction({
item.controllerInteraction.displayDiceTooltip(dice)

View File

@ -570,7 +570,13 @@ final class ChatQrCodeScreen: ViewController {
var fileName: String {
switch self {
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):
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)"
@ -1510,9 +1516,16 @@ private class QrContentNode: ASDisplayNode, ContentNode {
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.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.maximumNumberOfLines = 2
self.codeTextNode.textAlignment = .center
@ -1547,8 +1560,17 @@ private class QrContentNode: ASDisplayNode, ContentNode {
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>()
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 {
return
}

View File

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

View File

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

View File

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

View File

@ -27,6 +27,7 @@ import PeerInfoUI
import Markdown
import WebUI
import BotPaymentsUI
import PremiumUI
private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer {
if case .default = navigation {
@ -514,8 +515,19 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
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):
dismissInput()
if let navigationController = navigationController {

View File

@ -724,6 +724,20 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
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 {
if parsedUrl.host == "importStickers" {
@ -743,6 +757,8 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
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
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in

View File

@ -2312,15 +2312,36 @@ final class PeerInfoHeaderNode: ASDisplayNode {
} else if case .scam = credibilityIcon {
image = PresentationResourcesChatList.scamIcon(presentationData.theme, strings: presentationData.strings, type: .regular)
} else if case .verified = credibilityIcon {
if let sourceImage = UIImage(bundleImageName: "Peer Info/VerifiedIcon") {
image = generateImage(sourceImage.size, contextGenerator: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(presentationData.theme.list.itemCheckColors.foregroundColor.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: 7.0, dy: 7.0))
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
context.clip(to: CGRect(origin: CGPoint(), size: size), mask: sourceImage.cgImage!)
context.fill(CGRect(origin: CGPoint(), size: size))
})
if let backgroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconBackground"), let foregroundImage = UIImage(bundleImageName: "Peer Info/VerifiedIconForeground") {
image = 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(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 {
image = nil
}
@ -2331,17 +2352,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
context.clear(CGRect(origin: CGPoint(), size: size))
context.clip(to: CGRect(origin: .zero, size: size), mask: cgImage)
let colorsArray: [CGColor] = [
UIColor(rgb: 0x6B93FF).cgColor,
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())
context.setFillColor(presentationData.theme.list.itemCheckColors.fillColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
}, opaque: false)
expandedImage = generateImage(sourceImage.size, contextGenerator: { size, context in

View File

@ -2303,6 +2303,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}, displayPsa: { _, _ in
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ 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
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))
}))
}
@ -6204,7 +6212,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
case .language:
push(LocalizationListController(context: self.context))
case .premium:
self.controller?.push(PremiumIntroScreen(context: self.context, modal: false))
self.controller?.push(PremiumIntroScreen(context: self.context, modal: false, source: .settings))
case .stickers:
if let settings = self.data?.globalSettings {
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))
} else {
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: .search, isForExpandedView: true))
} 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
}, displayDiceTooltip: { _ in
}, animateDiceSuccess: { _ in
}, displayPremiumStickerTooltip: { _, _ in
}, openPeerContextMenu: { _, _, _, _, _ in
}, openMessageReplies: { _, _, _ in
}, openReplyThreadOriginalMessage: { _ in

View File

@ -34,7 +34,7 @@ public enum UndoOverlayContent {
case voiceChatRecording(text: String)
case voiceChatFlag(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 mediaSaved(text: String)
case paymentSent(currencyValue: String, itemTitle: String)

View File

@ -628,7 +628,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false
self.originalRemainingSeconds = 3
case let .sticker(context, file, title, text):
case let .sticker(context, file, title, text, customUndoText):
self.avatarNode = nil
self.iconNode = nil
self.iconCheckNode = nil
@ -702,7 +702,12 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
isUserInteractionEnabled = true
}
displayUndo = false
if let customUndoText = customUndoText {
undoText = customUndoText
displayUndo = true
} else {
displayUndo = false
}
self.originalRemainingSeconds = 3
if let updatedFetchSignal = updatedFetchSignal {
@ -869,12 +874,14 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
switch content {
case .removedChat:
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 {
self.isUserInteractionEnabled = true
} else {
self.isUserInteractionEnabled = false
}
case let .sticker(_, _, _, _, undoText):
self.isUserInteractionEnabled = undoText != nil
case .dice:
self.panelWrapperNode.clipsToBounds = true
case .info:

View File

@ -427,6 +427,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
}
private let hapticFeedback = HapticFeedback()
private var delayedScriptMessage: WKScriptMessage?
private func handleScriptMessage(_ message: WKScriptMessage) {
guard let controller = self.controller else {
@ -487,7 +489,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
case "web_app_open_invoice":
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)
|> `catch` { _ -> Signal<TelegramMediaInvoice?, NoError> in
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:
break
}

View File

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