mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Various fixes
This commit is contained in:
parent
3a41864868
commit
aaf2d40024
@ -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.";
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -14,6 +14,7 @@ swift_library(
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/ActivityIndicator",
|
||||
"//submodules/ShimmerEffect",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
],
|
||||
visibility = [
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)),
|
||||
|
@ -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"),
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user