Various improvements

This commit is contained in:
Ilya Laktyushin 2025-02-24 19:08:50 +04:00
parent b2351194d4
commit 4aeab37c70
16 changed files with 250 additions and 33 deletions

View File

@ -13876,3 +13876,11 @@ Sorry for the inconvenience.";
"Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()";
"Chat.InputTextPaidMessagePlaceholder" = "Message for %@";
"Privacy.Messages.Stars_1" = "%@ Star";
"Privacy.Messages.Stars_any" = "%@ Stars";
"Privacy.Messages.Unlock" = "Unlock with Telegram Premium";
"Premium.PaidMessages" = "Paid Messages";
"Premium.PaidMessagesInfo" = "Charge a fee for messages from non-contacts or new senders.";
"Premium.PaidMessages.Proceed" = "About Telegram Premium";

View File

@ -1357,25 +1357,29 @@ public struct StarsSubscriptionConfiguration {
maxFee: 2500,
usdWithdrawRate: 1200,
paidMessageMaxAmount: 10000,
paidMessageCommissionPermille: 850
paidMessageCommissionPermille: 850,
paidMessagesAvailable: false
)
}
public let maxFee: Int64
public let usdWithdrawRate: Int64
public let paidMessageMaxAmount: Int64
public let paidMessageCommissionPermille: Int32
public let paidMessagesAvailable: Bool
fileprivate init(
maxFee: Int64,
usdWithdrawRate: Int64,
paidMessageMaxAmount: Int64,
paidMessageCommissionPermille: Int32
paidMessageCommissionPermille: Int32,
paidMessagesAvailable: Bool
) {
self.maxFee = maxFee
self.usdWithdrawRate = usdWithdrawRate
self.paidMessageMaxAmount = paidMessageMaxAmount
self.paidMessageCommissionPermille = paidMessageCommissionPermille
self.paidMessagesAvailable = paidMessagesAvailable
}
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
@ -1384,11 +1388,14 @@ public struct StarsSubscriptionConfiguration {
let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate
let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount
let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille
let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable
return StarsSubscriptionConfiguration(
maxFee: maxFee,
usdWithdrawRate: usdWithdrawRate,
paidMessageMaxAmount: paidMessageMaxAmount,
paidMessageCommissionPermille: paidMessageCommissionPermille
paidMessageCommissionPermille: paidMessageCommissionPermille,
paidMessagesAvailable: paidMessagesAvailable
)
} else {
return .defaultValue

View File

@ -42,6 +42,7 @@ public enum PremiumIntroSource {
case folderTags
case animatedEmoji
case messageEffects
case paidMessages
}
public enum PremiumGiftSource: Equatable {
@ -79,6 +80,7 @@ public enum PremiumDemoSubject {
case folderTags
case business
case messageEffects
case paidMessages
case businessLocation
case businessHours

View File

@ -347,6 +347,8 @@ public class ItemListCheckboxItemNode: ItemListRevealOptionsItemNode {
iconFrame = CGRect(origin: CGPoint(x: params.leftInset + floor((leftInset - params.leftInset - iconSize.width) / 2.0), y: floor((contentSize.height - iconSize.height) / 2.0)), size: iconSize)
}
strongSelf.imageNode.frame = iconFrame
} else {
strongSelf.imageNode.image = nil
}
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))

View File

@ -141,7 +141,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f;
if (vertical)
startPosition = 2 * visualMargin + visualTotalLength - startPosition;
CGFloat endPosition = visualMargin + visualTotalLength / (_maximumValue - _minimumValue) * (ABS(_minimumValue) + 1.0);
CGFloat endPosition = visualMargin + visualTotalLength / (_maximumValue - _minimumValue) * (ABS(_minimumValue) + _maximumValue);
if (vertical)
endPosition = 2 * visualMargin + visualTotalLength - endPosition;

View File

@ -93,7 +93,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
case chargeForMessagesInfo(PresentationTheme, String)
case messagePriceHeader(PresentationTheme, String)
case messagePrice(PresentationTheme, Int64, String)
case messagePrice(PresentationTheme, Int64, Int64, String)
case messagePriceInfo(PresentationTheme, String)
case unrestrictBoostersSwitch(PresentationTheme, String, Bool)
@ -241,8 +241,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
} else {
return false
}
case let .messagePrice(lhsTheme, lhsValue, lhsPrice):
if case let .messagePrice(rhsTheme, rhsValue, rhsPrice) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsPrice == rhsPrice {
case let .messagePrice(lhsTheme, lhsValue, lhsMaxValue, lhsPrice):
if case let .messagePrice(rhsTheme, rhsValue, rhsMaxValue, rhsPrice) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsMaxValue == rhsMaxValue, lhsPrice == rhsPrice {
return true
} else {
return false
@ -424,8 +424,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section)
case let .messagePriceHeader(_, value):
return ItemListSectionHeaderItem(presentationData: presentationData, text: value, sectionId: self.section)
case let .messagePrice(_, value, price):
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in
case let .messagePrice(_, value, maxValue, price):
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value in
arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0))
})
case let .messagePriceInfo(_, value):
@ -733,7 +733,7 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen
price = "\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice))
entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, price))
entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, configuration.paidMessageMaxAmount, price))
entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo("\(configuration.paidMessageCommissionPermille / 10)", price).string))
}
}

View File

@ -1099,6 +1099,25 @@ private final class DemoSheetContent: CombinedComponent {
)
)
availableItems[.paidMessages] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.paidMessages,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: component.context,
position: .top,
videoFile: configuration.videos["paid_messages"],
decoration: .badgeStars
)),
title: strings.Premium_PaidMessages,
text: strings.Premium_PaidMessagesInfo,
textColor: textColor
)
)
)
)
let index: Int = 0
var items: [DemoPagerComponent.Item] = []
if let item = availableItems.first(where: { $0.value.content.id == component.subject as AnyHashable }) {
@ -1195,6 +1214,8 @@ private final class DemoSheetContent: CombinedComponent {
text = strings.Premium_FolderTagsStandaloneInfo
case .messageEffects:
text = strings.Premium_MessageEffectsInfo
case .paidMessages:
text = strings.Premium_PaidMessagesInfo
default:
text = ""
}
@ -1279,6 +1300,9 @@ private final class DemoSheetContent: CombinedComponent {
case .emojiStatus:
buttonText = strings.Premium_EmojiStatus_Proceed
buttonAnimationName = "premium_unlock"
case .paidMessages:
buttonText = strings.Premium_PaidMessages_Proceed
buttonAnimationName = "premium_unlock"
default:
buttonText = strings.Common_OK
}
@ -1468,6 +1492,7 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
case business
case folderTags
case messageEffects
case paidMessages
case businessLocation
case businessHours
@ -1526,6 +1551,8 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
return .folderTags
case .messageEffects:
return .messageEffects
case .paidMessages:
return .paidMessages
case .businessLocation:
return .businessLocation
case .businessHours:

View File

@ -433,6 +433,7 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
UIColor(rgb: 0xdb374b),
UIColor(rgb: 0xcb3e6d),
UIColor(rgb: 0xbc4395),
UIColor(rgb: 0xbc4395),
UIColor(rgb: 0xab4ac4),
UIColor(rgb: 0xab4ac4),
UIColor(rgb: 0xa34cd7),
@ -538,6 +539,8 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent {
demoSubject = .messagePrivacy
case .messageEffects:
demoSubject = .messageEffects
case .paidMessages:
demoSubject = .paidMessages
case .business:
demoSubject = .business
default:

View File

@ -302,6 +302,12 @@ public enum PremiumSource: Equatable {
} else {
return false
}
case .paidMessages:
if case .messageEffects = rhs {
return true
} else {
return false
}
}
}
@ -349,6 +355,7 @@ public enum PremiumSource: Equatable {
case messageTags
case folderTags
case messageEffects
case paidMessages
var identifier: String? {
switch self {
@ -442,6 +449,8 @@ public enum PremiumSource: Equatable {
return "folder_tags"
case .messageEffects:
return "effects"
case .paidMessages:
return "paid_messages"
}
}
}
@ -470,6 +479,7 @@ public enum PremiumPerk: CaseIterable {
case business
case folderTags
case messageEffects
case paidMessages
case businessLocation
case businessHours
@ -504,7 +514,8 @@ public enum PremiumPerk: CaseIterable {
.messagePrivacy,
.folderTags,
.business,
.messageEffects
.messageEffects,
.paidMessages
]
}
@ -578,6 +589,8 @@ public enum PremiumPerk: CaseIterable {
return "folder_tags"
case .messageEffects:
return "effects"
case .paidMessages:
return "paid_messages"
case .business:
return "business"
case .businessLocation:
@ -647,6 +660,8 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_Business
case .messageEffects:
return strings.Premium_MessageEffects
case .paidMessages:
return strings.Premium_PaidMessages
case .businessLocation:
return strings.Business_Location
case .businessHours:
@ -714,6 +729,8 @@ public enum PremiumPerk: CaseIterable {
return strings.Premium_BusinessInfo
case .messageEffects:
return strings.Premium_MessageEffectsInfo
case .paidMessages:
return strings.Premium_PaidMessagesInfo
case .businessLocation:
return strings.Business_LocationInfo
case .businessHours:
@ -781,7 +798,8 @@ public enum PremiumPerk: CaseIterable {
return "Premium/Perk/Business"
case .messageEffects:
return "Premium/Perk/MessageEffects"
case .paidMessages:
return "Premium/Perk/PaidMessages"
case .businessLocation:
return "Premium/BusinessPerk/Location"
case .businessHours:
@ -819,6 +837,7 @@ struct PremiumIntroConfiguration {
.colors,
.wallpapers,
.profileBadge,
.paidMessages,
.messagePrivacy,
.advancedChatManagement,
.noAds,
@ -867,6 +886,12 @@ struct PremiumIntroConfiguration {
perks = PremiumIntroConfiguration.defaultValue.perks
}
#if DEBUG
if !perks.contains(.paidMessages) {
perks.append(.paidMessages)
}
#endif
var businessPerks: [PremiumPerk] = []
if let values = data["business_promo_order"] as? [String] {
for value in values {
@ -1859,6 +1884,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
UIColor(rgb: 0xdb374b),
UIColor(rgb: 0xcb3e6d),
UIColor(rgb: 0xbc4395),
UIColor(rgb: 0xbc4395),
UIColor(rgb: 0xab4ac4),
UIColor(rgb: 0xab4ac4),
UIColor(rgb: 0xa34cd7),
@ -2092,6 +2118,8 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
demoSubject = .messagePrivacy
case .messageEffects:
demoSubject = .messageEffects
case .paidMessages:
demoSubject = .paidMessages
case .business:
demoSubject = .business
let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()

View File

@ -842,6 +842,24 @@ public class PremiumLimitsListScreen: ViewController {
)
)
)
availableItems[.paidMessages] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.paidMessages,
component: AnyComponent(
PageComponent(
content: AnyComponent(PhoneDemoComponent(
context: context,
position: .top,
videoFile: videos["paid_messages"],
decoration: .badgeStars
)),
title: strings.Premium_PaidMessages,
text: strings.Premium_PaidMessagesInfo,
textColor: textColor
)
)
)
)
availableItems[.business] = DemoPagerComponent.Item(
AnyComponentWithIdentity(
id: PremiumDemoScreen.Subject.business,

View File

@ -19,19 +19,22 @@ private final class IncomingMessagePrivacyScreenArguments {
let disabledValuePressed: () -> Void
let infoLinkAction: () -> Void
let openExceptions: () -> Void
let openPremiumInfo: () -> Void
init(
context: AccountContext,
updateValue: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void,
disabledValuePressed: @escaping () -> Void,
infoLinkAction: @escaping () -> Void,
openExceptions: @escaping () -> Void
openExceptions: @escaping () -> Void,
openPremiumInfo: @escaping () -> Void
) {
self.context = context
self.updateValue = updateValue
self.disabledValuePressed = disabledValuePressed
self.infoLinkAction = infoLinkAction
self.openExceptions = openExceptions
self.openPremiumInfo = openPremiumInfo
}
}
@ -49,7 +52,7 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
case optionChargeForMessages(value: GlobalPrivacySettings.NonContactChatsPrivacy, isEnabled: Bool)
case footer(value: GlobalPrivacySettings.NonContactChatsPrivacy)
case priceHeader
case price(value: Int64, price: String)
case price(value: Int64, maxValue: Int64, price: String, isEnabled: Bool)
case priceInfo(commission: Int32, value: String)
case exceptionsHeader
case exceptions(count: Int)
@ -128,12 +131,8 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
if case .paidMessages = value {
isChecked = true
}
return ItemListCheckboxItem(presentationData: presentationData, icon: isEnabled ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ChargeForMessages, style: .left, checked: isChecked, zeroSeparatorInsets: false, sectionId: self.section, action: {
if isEnabled {
arguments.updateValue(.paidMessages(StarsAmount(value: 400, nanos: 0)))
} else {
arguments.disabledValuePressed()
}
return ItemListCheckboxItem(presentationData: presentationData, icon: isEnabled || isChecked ? nil : generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/Lock"), color: presentationData.theme.list.itemSecondaryTextColor), iconPlacement: .check, title: presentationData.strings.Privacy_Messages_ChargeForMessages, style: .left, checked: isChecked, zeroSeparatorInsets: false, sectionId: self.section, action: {
arguments.updateValue(.paidMessages(StarsAmount(value: 400, nanos: 0)))
})
case let .footer(value):
let text: String
@ -149,9 +148,11 @@ private enum GlobalAutoremoveEntry: ItemListNodeEntry {
})
case .priceHeader:
return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.Privacy_Messages_MessagePrice, sectionId: self.section)
case let .price(value, price):
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 1, maxValue: 10000, value: value, price: price, sectionId: self.section, updated: { value in
case let .price(value, maxValue, price, isEnabled):
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: isEnabled, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value in
arguments.updateValue(.paidMessages(StarsAmount(value: value, nanos: 0)))
}, openPremiumInfo: {
arguments.openPremiumInfo()
})
case let .priceInfo(commission, value):
return ItemListTextItem(presentationData: presentationData, text: .markdown(presentationData.strings.Privacy_Messages_MessagePriceInfo("\(commission)", value).string), sectionId: self.section)
@ -178,7 +179,9 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD
entries.append(.header)
entries.append(.optionEverybody(value: state.updatedValue))
entries.append(.optionPremium(value: state.updatedValue, isEnabled: enableSetting))
entries.append(.optionChargeForMessages(value: state.updatedValue, isEnabled: isPremium))
if configuration.paidMessagesAvailable {
entries.append(.optionChargeForMessages(value: state.updatedValue, isEnabled: isPremium))
}
if case let .paidMessages(amount) = state.updatedValue {
entries.append(.footer(value: state.updatedValue))
@ -188,11 +191,14 @@ private func incomingMessagePrivacyScreenEntries(presentationData: PresentationD
let price = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
entries.append(.price(value: amount.value, price: price))
entries.append(.price(value: amount.value, maxValue: configuration.paidMessageMaxAmount, price: price, isEnabled: isPremium))
entries.append(.priceInfo(commission: configuration.paidMessageCommissionPermille / 10, value: price))
entries.append(.exceptionsHeader)
entries.append(.exceptions(count: state.disableFor.count))
entries.append(.exceptionsInfo)
if isPremium {
entries.append(.exceptionsHeader)
entries.append(.exceptions(count: state.disableFor.count))
entries.append(.exceptionsInfo)
}
} else {
entries.append(.footer(value: state.updatedValue))
entries.append(.info)
@ -347,6 +353,17 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
})
pushControllerImpl?(controller)
}
},
openPremiumInfo: {
var replaceImpl: ((ViewController) -> Void)?
let controller = context.sharedContext.makePremiumDemoController(context: context, subject: .paidMessages, forceDark: false, action: {
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .paidMessages, forceDark: false, dismissed: nil)
replaceImpl?(controller)
}, dismissed: nil)
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
pushControllerImpl?(controller)
}
)
@ -414,7 +431,12 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP
controller?.push(c)
}
controller.attemptNavigation = { _ in
update(stateValue.with({ $0 }).updatedValue)
let updatedValue = stateValue.with({ $0 }).updatedValue
if !context.isPremium, case .paidMessages = updatedValue {
} else {
update(updatedValue)
}
return true
}
dismissImpl = { [weak controller] in

View File

@ -19,6 +19,10 @@ swift_library(
"//submodules/TelegramPresentationData",
"//submodules/ItemListUI",
"//submodules/LegacyComponents",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",

View File

@ -8,6 +8,10 @@ import TelegramPresentationData
import LegacyComponents
import ItemListUI
import PresentationDataUtils
import ComponentFlow
import ButtonComponent
import BundleIconComponent
import MultilineTextComponent
private let textFont = Font.with(size: 17.0, traits: .monospacedNumbers)
private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers)
@ -15,22 +19,26 @@ private let smallTextFont = Font.with(size: 13.0, traits: .monospacedNumbers)
public final class MessagePriceItem: ListViewItem, ItemListItem {
let theme: PresentationTheme
let strings: PresentationStrings
let isEnabled: Bool
let minValue: Int64
let maxValue: Int64
let value: Int64
let price: String
public let sectionId: ItemListSectionId
let updated: (Int64) -> Void
let openPremiumInfo: (() -> Void)?
public init(theme: PresentationTheme, strings: PresentationStrings, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void) {
public init(theme: PresentationTheme, strings: PresentationStrings, isEnabled: Bool, minValue: Int64, maxValue: Int64, value: Int64, price: String, sectionId: ItemListSectionId, updated: @escaping (Int64) -> Void, openPremiumInfo: (() -> Void)? = nil) {
self.theme = theme
self.strings = strings
self.isEnabled = isEnabled
self.minValue = minValue
self.maxValue = maxValue
self.value = value
self.price = price
self.sectionId = sectionId
self.updated = updated
self.openPremiumInfo = openPremiumInfo
}
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
@ -155,6 +163,9 @@ private class MessagePriceItemNode: ListViewItemNode {
private let rightTextNode: ImmediateTextNode
private let centerLeftTextNode: ImmediateTextNode
private let centerRightTextNode: ImmediateTextNode
private let lockIconNode: ASImageNode
private let button: ComponentView<Empty>
private var amount: Amount = Amount(realValue: 1, maxRealValue: 1000, maxSliderValue: 1000, isLogarithmic: true)
@ -178,12 +189,18 @@ private class MessagePriceItemNode: ListViewItemNode {
self.centerLeftTextNode = ImmediateTextNode()
self.centerRightTextNode = ImmediateTextNode()
self.lockIconNode = ASImageNode()
self.lockIconNode.displaysAsynchronously = false
self.button = ComponentView<Empty>()
super.init(layerBacked: false, dynamicBounce: false)
self.addSubnode(self.leftTextNode)
self.addSubnode(self.rightTextNode)
self.addSubnode(self.centerLeftTextNode)
self.addSubnode(self.centerRightTextNode)
self.addSubnode(self.lockIconNode)
}
override func didLoad() {
@ -224,11 +241,15 @@ private class MessagePriceItemNode: ListViewItemNode {
themeUpdated = true
}
let contentSize: CGSize
var contentSize: CGSize
let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel
contentSize = CGSize(width: params.width, height: 88.0)
if !item.isEnabled {
contentSize.height = 166.0
}
insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -290,8 +311,7 @@ private class MessagePriceItemNode: ListViewItemNode {
strongSelf.leftTextNode.attributedText = NSAttributedString(string: "\(item.minValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
strongSelf.rightTextNode.attributedText = NSAttributedString(string: "\(item.maxValue)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor)
//TODO:localize
let centralLeftText = "\(item.value) Stars"
let centralLeftText = item.strings.Privacy_Messages_Stars(Int32(item.value))
strongSelf.centerLeftTextNode.attributedText = NSAttributedString(string: centralLeftText, font: textFont, textColor: item.theme.list.itemPrimaryTextColor)
strongSelf.centerRightTextNode.attributedText = NSAttributedString(string: item.price, font: smallTextFont, textColor: item.theme.list.itemSecondaryTextColor)
@ -323,6 +343,66 @@ private class MessagePriceItemNode: ListViewItemNode {
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 18.0, y: 36.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 18.0 * 2.0, height: 44.0))
}
strongSelf.lockIconNode.isHidden = item.isEnabled
if !item.isEnabled {
if themeUpdated {
strongSelf.lockIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Stickers/SmallLock"), color: item.theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.5))
}
if let image = strongSelf.lockIconNode.image {
strongSelf.lockIconNode.frame = CGRect(origin: CGPoint(x: centerLeftFrame.minX - image.size.width - 1.0, y: 12.0 + UIScreenPixel), size: image.size)
}
let sideInset: CGFloat = 16.0
let buttonSize = CGSize(width: params.width - params.leftInset - params.rightInset - sideInset * 2.0, height: 50.0)
let _ = strongSelf.button.update(
transition: .immediate,
component: AnyComponent(
ButtonComponent(
background: ButtonComponent.Background(
color: item.theme.list.itemCheckColors.fillColor,
foreground: item.theme.list.itemCheckColors.foregroundColor,
pressedColor: item.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
),
content: AnyComponentWithIdentity(
id: AnyHashable("unlock"),
component: AnyComponent(
HStack([
AnyComponentWithIdentity(
id: AnyHashable("icon"),
component: AnyComponent(BundleIconComponent(name: "Chat/Stickers/Lock", tintColor: item.theme.list.itemCheckColors.foregroundColor))
),
AnyComponentWithIdentity(
id: AnyHashable("label"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: item.strings.Privacy_Messages_Unlock, font: Font.semibold(17.0), textColor: item.theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
)
], spacing: 3.0)
)
),
isEnabled: true,
tintWhenDisabled: false,
allowActionWhenDisabled: false,
displaysProgress: false,
action: { [weak self] in
guard let self, let item = self.item else {
return
}
item.openPremiumInfo?()
}
)
),
environment: {},
containerSize: buttonSize
)
if let buttonView = strongSelf.button.view {
if buttonView.superview == nil {
strongSelf.view.addSubview(buttonView)
}
buttonView.frame = CGRect(origin: CGPoint(x: params.leftInset + sideInset, y: contentSize.height - buttonSize.height - sideInset), size: buttonSize)
}
} else if let buttonView = strongSelf.button.view, buttonView.superview != nil {
buttonView.removeFromSuperview()
}
}
})
}

View File

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

View File

@ -2486,6 +2486,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSource = .messageEffects
case .animatedEmoji:
mappedSource = .animatedEmoji
case .paidMessages:
mappedSource = .paidMessages
}
let controller = PremiumIntroScreen(context: context, source: mappedSource, modal: modal, forceDark: forceDark)
controller.wasDismissed = dismissed
@ -2542,6 +2544,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
mappedSubject = .folderTags
case .messageEffects:
mappedSubject = .messageEffects
case .paidMessages:
mappedSubject = .paidMessages
case .business:
mappedSubject = .business
buttonText = presentationData.strings.Chat_EmptyStateIntroFooterPremiumActionButton