Various fixes

This commit is contained in:
Ilya Laktyushin 2024-12-28 14:41:57 +04:00
parent 3a41864868
commit aaf2d40024
12 changed files with 424 additions and 220 deletions

View File

@ -13587,3 +13587,17 @@ Sorry for the inconvenience.";
"VideoChat.IncomingVideoQuality.Title" = "Receive Video Quality";
"ChatList.EmptyResult.SearchInAll" = "Search in All Messages";
"Gift.Options.GiftSelf.Title" = "Buy a Gift";
"Gift.Options.GiftSelf.Text" = "Buy yourself a gift to display on your page or reserve for later.\n\nLimited-edition gifts upgraded to collectibles can be gifted to others later.";
"Gift.SendSelf.Title" = "Gift to Myself";
"Gift.SendSelf.HideMyName" = "Hide My Name";
"Gift.SendSelf.HideMyName.Info" = "Hide my name and message from visitors to my profile.";
"Notification.StarGift.Self.Title" = "Saved Gift";
"Notification.StarGift.Self.Bought" = "You bought a gift for %@";
"Notification.StarGift.Self.Bought" = "You can display this gift on your page or turn it into a unique collectible and send to others.";
"Gift.View.Self.Title" = "Saved Gift";
"Gift.View.Self.Description" = "You can display this gift on your page or turn it into a unique collectible and send to others.";

View File

@ -1887,6 +1887,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
var query: String?
}
let previousRecentlySearchedPeersState = Atomic<SearchedPeersState?>(value: nil)
let hadAnySearchMessages = Atomic<Bool>(value: false)
let foundItems: Signal<([ChatListSearchEntry], Bool)?, NoError> = combineLatest(queue: .mainQueue(), searchQuery, searchOptions, self.searchScopePromise.get(), downloadItems)
|> mapToSignal { [weak self] query, options, searchScope, downloadItems -> Signal<([ChatListSearchEntry], Bool)?, NoError> in
@ -2992,9 +2993,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
}
let hadAnySearchMessagesBefore = hadAnySearchMessages.with { $0 }
var existingMessageIds = Set<MessageId>()
if foundRemoteMessages.1 && searchScope != .everywhere {
for i in 0 ..< 5 {
if foundRemoteMessages.1 && (searchScope != .everywhere || hadAnySearchMessagesBefore) {
for i in 0 ..< 6 {
entries.append(.messagePlaceholder(Int32(i), presentationData, searchScope))
index += 1
}
@ -3029,7 +3031,10 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
index += 1
}
}
if !hasAnyMessages {
if hasAnyMessages {
let _ = hadAnySearchMessages.swap(true)
} else {
switch searchScope {
case .everywhere:
break

View File

@ -4721,7 +4721,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
let dateFrame = strongSelf.dateNode.frame
shapes.append(.roundedRectLine(startPoint: CGPoint(x: dateFrame.maxX - dateLineWidth, y: dateFrame.minY + 3.0), width: dateLineWidth, diameter: lineDiameter))
shimmerNode.update(backgroundColor: item.presentationData.theme.list.itemBlocksBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size)
shimmerNode.update(backgroundColor: item.presentationData.theme.list.plainBackgroundColor, foregroundColor: item.presentationData.theme.list.mediaPlaceholderColor, shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), shapes: shapes, size: shimmerNode.frame.size)
} else if let shimmerNode = strongSelf.placeholderNode {
strongSelf.placeholderNode = nil
shimmerNode.removeFromSupernode()

View File

@ -1441,6 +1441,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-242427324] = { return Api.upload.File.parse_fileCdnRedirect($0) }
dict[568808380] = { return Api.upload.WebFile.parse_webFile($0) }
dict[997004590] = { return Api.users.UserFull.parse_userFull($0) }
dict[1658259128] = { return Api.users.Users.parse_users($0) }
dict[828000628] = { return Api.users.Users.parse_usersSlice($0) }
return dict
}()
@ -2550,6 +2552,8 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.users.UserFull:
_1.serialize(buffer, boxed)
case let _1 as Api.users.Users:
_1.serialize(buffer, boxed)
default:
break
}

View File

@ -56,3 +56,75 @@ public extension Api.users {
}
}
public extension Api.users {
enum Users: TypeConstructorDescription {
case users(users: [Api.User])
case usersSlice(count: Int32, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .users(let users):
if boxed {
buffer.appendInt32(1658259128)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .usersSlice(let count, let users):
if boxed {
buffer.appendInt32(828000628)
}
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .users(let users):
return ("users", [("users", users as Any)])
case .usersSlice(let count, let users):
return ("usersSlice", [("count", count as Any), ("users", users as Any)])
}
}
public static func parse_users(_ reader: BufferReader) -> Users? {
var _1: [Api.User]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
if _c1 {
return Api.users.Users.users(users: _1!)
}
else {
return nil
}
}
public static func parse_usersSlice(_ reader: BufferReader) -> Users? {
var _1: Int32?
_1 = reader.readInt32()
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.users.Users.usersSlice(count: _1!, users: _2!)
}
else {
return nil
}
}
}
}

View File

@ -2382,6 +2382,22 @@ public extension Api.functions.bots {
})
}
}
public extension Api.functions.bots {
static func getBotRecommendations(flags: Int32, bot: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.users.Users>) {
let buffer = Buffer()
buffer.appendInt32(676707937)
serializeInt32(flags, buffer: buffer, boxed: false)
bot.serialize(buffer, true)
return (FunctionDescription(name: "bots.getBotRecommendations", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.users.Users? in
let reader = BufferReader(buffer)
var result: Api.users.Users?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.users.Users
}
return result
})
}
}
public extension Api.functions.bots {
static func getPopularAppBots(offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.bots.PopularAppBots>) {
let buffer = Buffer()

View File

@ -14,6 +14,7 @@ swift_library(
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/AnimatedTextComponent",
"//submodules/ActivityIndicator",
"//submodules/ShimmerEffect",
"//submodules/Components/BundleIconComponent",
],
visibility = [

View File

@ -5,6 +5,7 @@ import ComponentFlow
import AnimatedTextComponent
import ActivityIndicator
import BundleIconComponent
import ShimmerEffect
public final class ButtonBadgeComponent: Component {
let fillColor: UIColor
@ -341,17 +342,20 @@ public final class ButtonComponent: Component {
public var foreground: UIColor
public var pressedColor: UIColor
public var cornerRadius: CGFloat
public var isShimmering: Bool
public init(
color: UIColor,
foreground: UIColor,
pressedColor: UIColor,
cornerRadius: CGFloat = 10.0
cornerRadius: CGFloat = 10.0,
isShimmering: Bool = false
) {
self.color = color
self.foreground = foreground
self.pressedColor = pressedColor
self.cornerRadius = cornerRadius
self.isShimmering = isShimmering
}
}
@ -416,6 +420,7 @@ public final class ButtonComponent: Component {
private var component: ButtonComponent?
private weak var componentState: EmptyComponentState?
private var shimmeringView: ButtonShimmeringView?
private var contentItem: ContentItem?
private var activityIndicator: ActivityIndicator?
@ -468,7 +473,7 @@ public final class ButtonComponent: Component {
} else if !component.isEnabled && component.tintWhenDisabled {
contentAlpha = 0.7
}
var previousContentItem: ContentItem?
let contentItem: ContentItem
var contentItemTransition = transition
@ -544,6 +549,25 @@ public final class ButtonComponent: Component {
}
}
if component.background.isShimmering {
let shimmeringView: ButtonShimmeringView
var shimmeringTransition = transition
if let current = self.shimmeringView {
shimmeringView = current
} else {
shimmeringTransition = .immediate
shimmeringView = ButtonShimmeringView(frame: .zero)
self.insertSubview(shimmeringView, at: 0)
}
shimmeringView.update(size: availableSize, color: component.background.color, cornerRadius: component.background.cornerRadius, transition: shimmeringTransition)
shimmeringTransition.setFrame(view: shimmeringView, frame: CGRect(origin: .zero, size: availableSize))
} else if let shimmeringView = self.shimmeringView {
self.shimmeringView = nil
shimmeringView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { _ in
shimmeringView.removeFromSuperview()
})
}
return availableSize
}
}
@ -556,3 +580,61 @@ public final class ButtonComponent: Component {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private class ButtonShimmeringView: UIView {
private var shimmerView = ShimmerEffectForegroundView()
private var borderView = UIView()
private var borderMaskView = UIView()
private var borderShimmerView = ShimmerEffectForegroundView()
override init(frame: CGRect) {
self.borderView.isUserInteractionEnabled = false
self.borderMaskView.layer.borderWidth = 1.0 + UIScreenPixel
self.borderMaskView.layer.borderColor = UIColor.white.cgColor
self.borderView.mask = self.borderMaskView
self.borderView.addSubview(self.borderShimmerView)
super.init(frame: frame)
self.addSubview(self.shimmerView)
self.addSubview(self.borderView)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
func update(size: CGSize, color: UIColor, cornerRadius: CGFloat, transition: ComponentTransition) {
let alpha: CGFloat
let borderAlpha: CGFloat
let compositingFilter: String?
if color.lightness > 0.5 {
alpha = 0.5
borderAlpha = 0.75
compositingFilter = "overlayBlendMode"
} else {
alpha = 0.2
borderAlpha = 0.3
compositingFilter = nil
}
self.shimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(alpha), gradientSize: 70.0, globalTimeOffset: false, duration: 4.0, horizontal: true)
self.shimmerView.layer.compositingFilter = compositingFilter
self.borderShimmerView.update(backgroundColor: .clear, foregroundColor: color.withAlphaComponent(borderAlpha), gradientSize: 70.0, globalTimeOffset: false, duration: 4.0, horizontal: true)
self.borderShimmerView.layer.compositingFilter = compositingFilter
self.borderMaskView.layer.cornerRadius = cornerRadius
let bounds = CGRect(origin: .zero, size: size)
transition.setFrame(view: self.shimmerView, frame: bounds)
transition.setFrame(view: self.borderView, frame: bounds)
transition.setFrame(view: self.borderMaskView, frame: bounds)
transition.setFrame(view: self.borderShimmerView, frame: bounds)
self.shimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: size.width * 4.0, y: 0.0), size: size), within: CGSize(width: size.width * 9.0, height: size.height))
self.borderShimmerView.updateAbsoluteRect(CGRect(origin: CGPoint(x: size.width * 4.0, y: 0.0), size: size), within: CGSize(width: size.width * 9.0, height: size.height))
}
}

View File

@ -480,6 +480,8 @@ final class GiftOptionsScreenComponent: Component {
let _ = bottomContentInset
let _ = sectionSpacing
let isSelfGift = component.peerId == component.context.account.peerId
var contentHeight: CGFloat = 0.0
contentHeight += environment.navigationHeight - 56.0 + 188.0
@ -614,7 +616,7 @@ final class GiftOptionsScreenComponent: Component {
let premiumTitleSize = self.premiumTitle.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: strings.Gift_Options_Premium_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
text: .plain(NSAttributedString(string: isSelfGift ? strings.Gift_Options_GiftSelf_Title : strings.Gift_Options_Premium_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center
)),
environment: {},
@ -635,7 +637,7 @@ final class GiftOptionsScreenComponent: Component {
})
let peerName = state.peer?.compactDisplayTitle ?? ""
let premiumDescriptionString = parseMarkdownIntoAttributedString(strings.Gift_Options_Premium_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
let premiumDescriptionString = parseMarkdownIntoAttributedString(isSelfGift ? strings.Gift_Options_GiftSelf_Text : strings.Gift_Options_Premium_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = premiumDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 {
premiumDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: premiumDescriptionString.string))
}
@ -689,188 +691,191 @@ final class GiftOptionsScreenComponent: Component {
let optionSpacing: CGFloat = 10.0
let optionWidth = (availableSize.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
if let premiumProducts = state.premiumProducts {
let premiumOptionSize = CGSize(width: optionWidth, height: 178.0)
var validIds: [AnyHashable] = []
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize)
for product in premiumProducts {
let itemId = AnyHashable(product.id)
validIds.append(itemId)
if isSelfGift {
contentHeight += 6.0
} else {
if let premiumProducts = state.premiumProducts {
let premiumOptionSize = CGSize(width: optionWidth, height: 178.0)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.premiumItems[itemId] {
visibleItem = current
} else {
visibleItem = ComponentView()
if !transition.animation.isImmediate {
itemTransition = .immediate
}
self.premiumItems[itemId] = visibleItem
}
let title: String
switch product.months {
case 6:
title = strings.Gift_Options_Premium_Months(6)
case 12:
title = strings.Gift_Options_Premium_Years(1)
default:
title = strings.Gift_Options_Premium_Months(3)
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
peer: nil,
subject: .premium(months: product.months, price: product.price),
title: title,
subtitle: strings.Gift_Options_Premium_Premium,
ribbon: product.discount.flatMap {
GiftItemComponent.Ribbon(
text: "-\($0)%",
color: .red
)
},
isLoading: self.inProgressPremiumGift == product.id
)
),
effectAlignment: .center,
action: { [weak self] in
if let self, let component = self.component {
if let controller = controller() as? GiftOptionsScreen {
let mainController: ViewController
if let parentController = controller.parentController() {
mainController = parentController
} else {
mainController = controller
}
let giftController = GiftSetupScreen(
context: component.context,
peerId: component.peerId,
subject: .premium(product),
completion: component.completion
)
mainController.push(giftController)
}
}
},
animateAlpha: false
)
),
environment: {},
containerSize: premiumOptionSize
)
if let itemView = visibleItem.view {
if itemView.superview == nil {
self.scrollView.addSubview(itemView)
if !transition.animation.isImmediate {
transition.animateAlpha(view: itemView, from: 0.0, to: 1.0)
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
itemFrame.origin.x += itemFrame.width + optionSpacing
if itemFrame.maxX > availableSize.width {
itemFrame.origin.x = sideInset
itemFrame.origin.y += premiumOptionSize.height + optionSpacing
}
}
var removeIds: [AnyHashable] = []
for (id, item) in self.premiumItems {
if !validIds.contains(id) {
removeIds.append(id)
if let itemView = item.view {
if !transition.animation.isImmediate {
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
itemView.removeFromSuperview()
})
} else {
itemView.removeFromSuperview()
}
}
}
}
for id in removeIds {
self.premiumItems.removeValue(forKey: id)
}
contentHeight += ceil(CGFloat(premiumProducts.count) / 3.0) * premiumOptionSize.height
contentHeight += 66.0
}
let starsTitleSize = self.starsTitle.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: strings.Gift_Options_Gift_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
if let starsTitleView = self.starsTitle.view {
if starsTitleView.superview == nil {
self.addSubview(starsTitleView)
}
transition.setBounds(view: starsTitleView, bounds: CGRect(origin: .zero, size: starsTitleSize))
}
let starsDescriptionString = parseMarkdownIntoAttributedString(strings.Gift_Options_Gift_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = starsDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 {
starsDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: starsDescriptionString.string))
}
let starsDescriptionSize = self.starsDescription.update(
transition: transition,
component: AnyComponent(BalancedTextComponent(
text: .plain(starsDescriptionString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2,
highlightColor: accentColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
var validIds: [AnyHashable] = []
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: premiumOptionSize)
for product in premiumProducts {
let itemId = AnyHashable(product.id)
validIds.append(itemId)
var itemTransition = transition
let visibleItem: ComponentView<Empty>
if let current = self.premiumItems[itemId] {
visibleItem = current
} else {
return nil
}
},
tapAction: { [weak self] _, _ in
guard let self, let component = self.component, let environment = self.environment else {
return
}
let introController = component.context.sharedContext.makeStarsIntroScreen(context: component.context)
if let controller = environment.controller() as? GiftOptionsScreen {
let mainController: ViewController
if let parentController = controller.parentController() {
mainController = parentController
} else {
mainController = controller
visibleItem = ComponentView()
if !transition.animation.isImmediate {
itemTransition = .immediate
}
mainController.push(introController)
self.premiumItems[itemId] = visibleItem
}
let title: String
switch product.months {
case 6:
title = strings.Gift_Options_Premium_Months(6)
case 12:
title = strings.Gift_Options_Premium_Years(1)
default:
title = strings.Gift_Options_Premium_Months(3)
}
let _ = visibleItem.update(
transition: itemTransition,
component: AnyComponent(
PlainButtonComponent(
content: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
peer: nil,
subject: .premium(months: product.months, price: product.price),
title: title,
subtitle: strings.Gift_Options_Premium_Premium,
ribbon: product.discount.flatMap {
GiftItemComponent.Ribbon(
text: "-\($0)%",
color: .red
)
},
isLoading: self.inProgressPremiumGift == product.id
)
),
effectAlignment: .center,
action: { [weak self] in
if let self, let component = self.component {
if let controller = controller() as? GiftOptionsScreen {
let mainController: ViewController
if let parentController = controller.parentController() {
mainController = parentController
} else {
mainController = controller
}
let giftController = GiftSetupScreen(
context: component.context,
peerId: component.peerId,
subject: .premium(product),
completion: component.completion
)
mainController.push(giftController)
}
}
},
animateAlpha: false
)
),
environment: {},
containerSize: premiumOptionSize
)
if let itemView = visibleItem.view {
if itemView.superview == nil {
self.scrollView.addSubview(itemView)
if !transition.animation.isImmediate {
transition.animateAlpha(view: itemView, from: 0.0, to: 1.0)
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
}
itemFrame.origin.x += itemFrame.width + optionSpacing
if itemFrame.maxX > availableSize.width {
itemFrame.origin.x = sideInset
itemFrame.origin.y += premiumOptionSize.height + optionSpacing
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 1000.0)
)
let starsDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - starsDescriptionSize.width) / 2.0), y: contentHeight), size: starsDescriptionSize)
if let starsDescriptionView = self.starsDescription.view {
if starsDescriptionView.superview == nil {
self.scrollView.addSubview(starsDescriptionView)
var removeIds: [AnyHashable] = []
for (id, item) in self.premiumItems {
if !validIds.contains(id) {
removeIds.append(id)
if let itemView = item.view {
if !transition.animation.isImmediate {
itemView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
itemView.removeFromSuperview()
})
} else {
itemView.removeFromSuperview()
}
}
}
}
for id in removeIds {
self.premiumItems.removeValue(forKey: id)
}
contentHeight += ceil(CGFloat(premiumProducts.count) / 3.0) * premiumOptionSize.height
contentHeight += 66.0
}
transition.setFrame(view: starsDescriptionView, frame: starsDescriptionFrame)
let starsTitleSize = self.starsTitle.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: strings.Gift_Options_Gift_Title, font: Font.bold(28.0), textColor: theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 100.0)
)
if let starsTitleView = self.starsTitle.view {
if starsTitleView.superview == nil {
self.addSubview(starsTitleView)
}
transition.setBounds(view: starsTitleView, bounds: CGRect(origin: .zero, size: starsTitleSize))
}
let starsDescriptionString = parseMarkdownIntoAttributedString(strings.Gift_Options_Gift_Text(peerName).string, attributes: markdownAttributes).mutableCopy() as! NSMutableAttributedString
if let range = starsDescriptionString.string.range(of: ">"), let chevronImage = self.chevronImage?.0 {
starsDescriptionString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: starsDescriptionString.string))
}
let starsDescriptionSize = self.starsDescription.update(
transition: transition,
component: AnyComponent(BalancedTextComponent(
text: .plain(starsDescriptionString),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.2,
highlightColor: accentColor.withAlphaComponent(0.1),
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { [weak self] _, _ in
guard let self, let component = self.component, let environment = self.environment else {
return
}
let introController = component.context.sharedContext.makeStarsIntroScreen(context: component.context)
if let controller = environment.controller() as? GiftOptionsScreen {
let mainController: ViewController
if let parentController = controller.parentController() {
mainController = parentController
} else {
mainController = controller
}
mainController.push(introController)
}
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width, height: 1000.0)
)
let starsDescriptionFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - starsDescriptionSize.width) / 2.0), y: contentHeight), size: starsDescriptionSize)
if let starsDescriptionView = self.starsDescription.view {
if starsDescriptionView.superview == nil {
self.scrollView.addSubview(starsDescriptionView)
}
transition.setFrame(view: starsDescriptionView, frame: starsDescriptionFrame)
}
contentHeight += starsDescriptionSize.height
contentHeight += 16.0
}
contentHeight += starsDescriptionSize.height
contentHeight += 16.0
var tabSelectorItems: [TabSelectorComponent.Item] = []
tabSelectorItems.append(TabSelectorComponent.Item(
@ -1022,41 +1027,43 @@ final class GiftOptionsScreenComponent: Component {
}
self.peer = peer
if availableProducts.isEmpty {
var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions {
premiumProducts.append(
PremiumGiftProduct(
giftOption: CachedPremiumGiftOption(
months: option.months,
currency: option.currency,
amount: option.amount,
botUrl: "",
storeProductId: option.storeProductId
),
storeProduct: nil,
discount: nil
if peerId != context.account.peerId {
if availableProducts.isEmpty {
var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions {
premiumProducts.append(
PremiumGiftProduct(
giftOption: CachedPremiumGiftOption(
months: option.months,
currency: option.currency,
amount: option.amount,
botUrl: "",
storeProductId: option.storeProductId
),
storeProduct: nil,
discount: nil
)
)
)
}
self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months })
} else {
let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
}
var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions {
if let product = availableProducts.first(where: { $0.id == option.storeProductId }), !product.isSubscription {
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(option.months) / Float(shortestOptionPrice.0)
let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0)
premiumProducts.append(PremiumGiftProduct(giftOption: option, storeProduct: product, discount: discountValue > 0 ? discountValue : nil))
}
self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months })
} else {
let shortestOptionPrice: (Int64, NSDecimalNumber)
if let product = availableProducts.first(where: { $0.id.hasSuffix(".monthly") }) {
shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue)
} else {
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
}
var premiumProducts: [PremiumGiftProduct] = []
for option in premiumOptions {
if let product = availableProducts.first(where: { $0.id == option.storeProductId }), !product.isSubscription {
let fraction = Float(product.priceCurrencyAndAmount.amount) / Float(option.months) / Float(shortestOptionPrice.0)
let discountValue = Int(round((1.0 - fraction) * 20.0) * 5.0)
premiumProducts.append(PremiumGiftProduct(giftOption: option, storeProduct: product, discount: discountValue > 0 ? discountValue : nil))
}
}
self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months })
}
self.premiumProducts = premiumProducts.sorted(by: { $0.months < $1.months })
}
self.starGifts = starGifts?.compactMap { gift in

View File

@ -616,10 +616,12 @@ final class GiftSetupScreenComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let isSelfGift = component.peerId == component.context.account.peerId
let navigationTitleSize = self.navigationTitle.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: environment.strings.Gift_Send_TitleTo(peerName).string, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
text: .plain(NSAttributedString(string: isSelfGift ? environment.strings.Gift_SendSelf_Title : environment.strings.Gift_Send_TitleTo(peerName).string, font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
horizontalAlignment: .center
)),
environment: {},
@ -946,7 +948,7 @@ final class GiftSetupScreenComponent: Component {
header: nil,
footer: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string,
string: isSelfGift ? environment.strings.Gift_SendSelf_HideMyName_Info : environment.strings.Gift_Send_HideMyName_Info(peerName, peerName).string,
font: Font.regular(presentationData.listsFontSize.itemListBaseHeaderFontSize),
textColor: environment.theme.list.freeTextColor
)),
@ -958,7 +960,7 @@ final class GiftSetupScreenComponent: Component {
title: AnyComponent(VStack([
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: environment.strings.Gift_Send_HideMyName,
string: isSelfGift ? environment.strings.Gift_SendSelf_HideMyName : environment.strings.Gift_Send_HideMyName,
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
textColor: environment.theme.list.itemPrimaryTextColor
)),

View File

@ -1432,7 +1432,8 @@ private final class GiftViewSheetContent: CombinedComponent {
color: theme.list.itemCheckColors.fillColor,
foreground: theme.list.itemCheckColors.foregroundColor,
pressedColor: theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9),
cornerRadius: 10.0
cornerRadius: 10.0,
isShimmering: true
),
content: AnyComponentWithIdentity(
id: AnyHashable("freeUpgrade"),

View File

@ -130,7 +130,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let optionSpacing: CGFloat = 10.0
let sideInset = params.sideInset + 16.0
let itemsInRow = min(starsProducts.count, 3)
let itemsInRow = max(1, min(starsProducts.count, 3))
let optionWidth = (params.size.width - sideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
let starsOptionSize = CGSize(width: optionWidth, height: optionWidth)