Various improvements

This commit is contained in:
Ilya Laktyushin 2024-08-30 16:07:11 +04:00
parent 5978a5278d
commit 707d5137ca
20 changed files with 609 additions and 159 deletions

View File

@ -12876,3 +12876,7 @@ Sorry for the inconvenience.";
"BoostGift.Group.StarsDateInfo" = "Choose when %@ of your group will be randomly selected to receive Stars.";
"BoostGift.StarsDateInfo" = "Choose when %@ of your channel will be randomly selected to receive Stars.";
"BoostGift.PrepaidGiveawayStarsCount_1" = "%@ Telegram Premium";
"BoostGift.PrepaidGiveawayStarsCount_any" = "%@ Telegram Premium";
"BoostGift.PrepaidGiveawayStarsMonths" = "%@-month subscriptions";

View File

@ -30,6 +30,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
private let sourceLocation: InstantPageSourceLocation
private let preloadedResouces: [Any]?
private var originalContent: BrowserContent?
private let url: String
private var webPage: TelegramMediaWebpage?
@ -102,6 +103,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.sourceLocation = sourceLocation
self.preloadedResouces = preloadedResouces
self.originalContent = originalContent
self.url = url
self.uuid = UUID()
@ -904,6 +906,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
anchor = String(baseUrl[anchorRange.upperBound...]).removingPercentEncoding
baseUrl = String(baseUrl[..<anchorRange.lowerBound])
}
if !baseUrl.hasPrefix("http://") && !baseUrl.hasPrefix("https://") {
if let updatedUrl = URL(string: baseUrl, relativeTo: URL(string: "/", relativeTo: URL(string: self.url))) {
baseUrl = updatedUrl.absoluteString
}
}
if let webPage = self.webPage, case let .Loaded(content) = webPage.content, let page = content.instantPage, page.url == baseUrl || baseUrl.isEmpty, let anchor = anchor {
self.scrollToAnchor(anchor)
@ -914,7 +922,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.loadProgress.set(0.02)
self.loadWebpageDisposable.set(nil)
self.resolveUrlDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url.url, skipUrlAuth: true)
self.resolveUrlDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: baseUrl, skipUrlAuth: true)
|> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self {
strongSelf.loadProgress.set(0.07)
@ -997,8 +1005,15 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
private func openUrlIn(_ url: InstantPageUrlItem) {
var baseUrl = url.url
if !baseUrl.hasPrefix("http://") && !baseUrl.hasPrefix("https://") {
if let updatedUrl = URL(string: baseUrl, relativeTo: URL(string: "/", relativeTo: URL(string: self.url))) {
baseUrl = updatedUrl.absoluteString
}
}
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let actionSheet = OpenInActionSheetController(context: self.context, item: .url(url: url.url), openUrl: { [weak self] url in
let actionSheet = OpenInActionSheetController(context: self.context, item: .url(url: baseUrl), openUrl: { [weak self] url in
if let self {
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {})
}
@ -1182,11 +1197,18 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}
case .longTap:
if let url = self.urlForTapLocation(location) {
let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: url.url)).count > 1
var baseUrl = url.url
if !baseUrl.hasPrefix("http://") && !baseUrl.hasPrefix("https://") {
if let updatedUrl = URL(string: baseUrl, relativeTo: URL(string: "/", relativeTo: URL(string: self.url))) {
baseUrl = updatedUrl.absoluteString
}
}
let canOpenIn = availableOpenInOptions(context: self.context, item: .url(url: baseUrl)).count > 1
let openText = canOpenIn ? self.presentationData.strings.Conversation_FileOpenIn : self.presentationData.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(instantPageTheme: self.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: url.url),
ActionSheetTextItem(title: baseUrl),
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
@ -1199,11 +1221,11 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
}),
ActionSheetButtonItem(title: self.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
UIPasteboard.general.string = url.url
UIPasteboard.general.string = baseUrl
}),
ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
if let link = URL(string: url.url) {
if let link = URL(string: baseUrl) {
let _ = try? SSReadingList.default()?.addItem(with: link, title: nil, previewText: nil)
}
})

View File

@ -211,6 +211,7 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R
result.append(parseRichText(string))
} else if let item = item as? [String: Any], let tag = item["tag"] as? String {
var text: RichText?
var addLineBreak = false
switch tag {
case "b", "strong":
text = .bold(parseRichText(item, &media))
@ -273,6 +274,9 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R
flags: []
)
text = .image(id: id, dimensions: PixelDimensions(width: width, height: height))
if width > 100 {
addLineBreak = true
}
}
case "br":
if let last = result.last {
@ -284,6 +288,9 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R
if var text {
text = applyAnchor(text, item: item)
result.append(text)
if addLineBreak {
result.append(.plain("\n"))
}
}
}
}
@ -298,15 +305,130 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R
}
private func trimStart(_ input: RichText) -> RichText {
return input
var text = input
switch input {
case .empty:
text = .empty
case let .plain(string):
text = .plain(string.replacingOccurrences(of: "^[ \t\r\n]+", with: "", options: .regularExpression, range: nil))
case let .bold(richText):
text = .bold(trimStart(richText))
case let .italic(richText):
text = .italic(trimStart(richText))
case let .underline(richText):
text = .underline(trimStart(richText))
case let .strikethrough(richText):
text = .strikethrough(trimStart(richText))
case let .fixed(richText):
text = .fixed(trimStart(richText))
case let .url(richText, url, webpageId):
text = .url(text: trimStart(richText), url: url, webpageId: webpageId)
case let .email(richText, email):
text = .email(text: trimStart(richText), email: email)
case let .subscript(richText):
text = .subscript(trimStart(richText))
case let .superscript(richText):
text = .superscript(trimStart(richText))
case let .marked(richText):
text = .marked(trimStart(richText))
case let .phone(richText, phone):
text = .phone(text: trimStart(richText), phone: phone)
case let .anchor(richText, name):
text = .anchor(text: trimStart(richText), name: name)
case var .concat(array):
if !array.isEmpty {
array[0] = trimStart(array[0])
text = .concat(array)
}
case .image:
break
}
return text
}
private func trimEnd(_ input: RichText) -> RichText {
return input
var text = input
switch input {
case .empty:
text = .empty
case let .plain(string):
text = .plain(string.replacingOccurrences(of: "[ \t\r\n]+$", with: "", options: .regularExpression, range: nil))
case let .bold(richText):
text = .bold(trimStart(richText))
case let .italic(richText):
text = .italic(trimStart(richText))
case let .underline(richText):
text = .underline(trimStart(richText))
case let .strikethrough(richText):
text = .strikethrough(trimStart(richText))
case let .fixed(richText):
text = .fixed(trimStart(richText))
case let .url(richText, url, webpageId):
text = .url(text: trimStart(richText), url: url, webpageId: webpageId)
case let .email(richText, email):
text = .email(text: trimStart(richText), email: email)
case let .subscript(richText):
text = .subscript(trimStart(richText))
case let .superscript(richText):
text = .superscript(trimStart(richText))
case let .marked(richText):
text = .marked(trimStart(richText))
case let .phone(richText, phone):
text = .phone(text: trimStart(richText), phone: phone)
case let .anchor(richText, name):
text = .anchor(text: trimStart(richText), name: name)
case var .concat(array):
if !array.isEmpty {
array[array.count - 1] = trimStart(array[array.count - 1])
text = .concat(array)
}
case .image:
break
}
return text
}
private func trim(_ input: RichText) -> RichText {
return input
var text = input
switch input {
case .empty:
text = .empty
case let .plain(string):
text = .plain(string.trimmingCharacters(in: .whitespacesAndNewlines))
case let .bold(richText):
text = .bold(trimStart(richText))
case let .italic(richText):
text = .italic(trimStart(richText))
case let .underline(richText):
text = .underline(trimStart(richText))
case let .strikethrough(richText):
text = .strikethrough(trimStart(richText))
case let .fixed(richText):
text = .fixed(trimStart(richText))
case let .url(richText, url, webpageId):
text = .url(text: trimStart(richText), url: url, webpageId: webpageId)
case let .email(richText, email):
text = .email(text: trimStart(richText), email: email)
case let .subscript(richText):
text = .subscript(trimStart(richText))
case let .superscript(richText):
text = .superscript(trimStart(richText))
case let .marked(richText):
text = .marked(trimStart(richText))
case let .phone(richText, phone):
text = .phone(text: trimStart(richText), phone: phone)
case let .anchor(richText, name):
text = .anchor(text: trimStart(richText), name: name)
case var .concat(array):
if !array.isEmpty {
array[0] = trimStart(array[0])
array[array.count - 1] = trimEnd(array[array.count - 1])
text = .concat(array)
}
case .image:
break
}
return text
}
private func addNewLine(_ input: RichText) -> RichText {
@ -341,8 +463,10 @@ private func addNewLine(_ input: RichText) -> RichText {
case let .anchor(richText, name):
text = .anchor(text: addNewLine(richText), name: name)
case var .concat(array):
array[array.count - 1] = addNewLine(array[array.count - 1])
text = .concat(array)
if !array.isEmpty {
array[array.count - 1] = addNewLine(array[array.count - 1])
text = .concat(array)
}
case .image:
break
}
@ -442,7 +566,7 @@ private func parseDetails(_ item: [String: Any], _ url: String, _ media: inout [
)
}
private func parseList(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
private func parseList(_ input: [String: Any], _ url: String, _ media: inout [MediaId: Media]) -> InstantPageBlock? {
guard let content = input["content"] as? [Any], let tag = input["tag"] as? String else {
return nil
}
@ -451,9 +575,40 @@ private func parseList(_ input: [String: Any], _ media: inout [MediaId: Media])
guard let item = item as? [String: Any], let tag = item["tag"] as? String, tag == "li" else {
continue
}
items.append(.text(trim(parseRichText(item, &media)), nil))
var parseAsBlocks = false
if let subcontent = item["content"] as? [Any] {
for item in subcontent {
if let item = item as? [String: Any], let tag = item["tag"] as? String, ["ul", "ol"].contains(tag) {
parseAsBlocks = true
}
}
if parseAsBlocks {
let blocks = parsePageBlocks(subcontent, url, &media)
if !blocks.isEmpty {
items.append(.blocks(blocks, nil))
}
} else {
items.append(.text(trim(parseRichText(item, &media)), nil))
}
}
}
let ordered = tag == "ol"
var allEmpty = true
for item in items {
if case let .text(text, _) = item {
if case .empty = text {
} else {
allEmpty = false
break
}
} else {
allEmpty = false
break
}
}
guard !allEmpty else {
return nil
}
return .list(items: items, ordered: ordered)
}
@ -511,6 +666,36 @@ private func parseImage(_ input: [String: Any], _ media: inout [MediaId: Media])
)
}
private func parseVideo(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
guard let src = input["src"] as? String else {
return nil
}
let width: Int32
if let value = input["width"] as? String, let intValue = Int32(value) {
width = intValue
} else {
width = 0
}
let height: Int32
if let value = input["height"] as? String, let intValue = Int32(value) {
height = intValue
} else {
height = 0
}
return .webEmbed(
url: src,
html: nil,
dimensions: PixelDimensions(width: width, height: height),
caption: InstantPageCaption(text: .empty, credit: .empty),
stretchToWidth: true,
allowScrolling: false,
coverId: nil
)
}
private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
guard let content = input["content"] as? [Any] else {
return nil
@ -519,9 +704,19 @@ private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media]
var caption: RichText?
for item in content {
if let item = item as? [String: Any], let tag = item["tag"] as? String {
if tag == "img" {
if tag == "p", let content = item["content"] as? [Any] {
for item in content {
if let item = item as? [String: Any], let tag = item["tag"] as? String {
if tag == "iframe" {
block = parseVideo(item, &media)
}
}
}
} else if tag == "iframe" {
block = parseVideo(item, &media)
} else if tag == "img" {
block = parseImage(item, &media)
} else if tag == "figurecaption" {
} else if tag == "figcaption" {
caption = trim(parseRichText(item, &media))
}
}
@ -539,7 +734,7 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi
var result: [InstantPageBlock] = []
for item in input {
if let string = item as? String {
result.append(.paragraph(parseRichText(string)))
result.append(.paragraph(trim(parseRichText(string))))
} else if let item = item as? [String: Any], let tag = item["tag"] as? String {
let content = item["content"] as? [Any]
switch tag {
@ -557,7 +752,10 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi
if let image = parseImage(item, &media) {
result.append(image)
}
break
case "iframe":
if let video = parseVideo(item, &media) {
result.append(video)
}
case "figure":
if let figure = parseFigure(item, &media) {
result.append(figure)
@ -565,7 +763,7 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi
case "table":
result.append(parseTable(item, &media))
case "ul", "ol":
if let list = parseList(item, &media) {
if let list = parseList(item, url, &media) {
result.append(list)
}
case "hr":

View File

@ -753,7 +753,7 @@ func layoutTextItemWithString(_ string: NSAttributedString, boundingWidth: CGFlo
let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length)
string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in
if let id = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaIdAttribute)] as? Int64, let dimensions = attributes[NSAttributedString.Key.init(rawValue: InstantPageMediaDimensionsAttribute)] as? PixelDimensions {
var imageFrame = CGRect(origin: CGPoint(), size: dimensions.cgSize)
var imageFrame = CGRect(origin: CGPoint(), size: dimensions.cgSize.fitted(CGSize(width: boundingWidth, height: boundingWidth)))
let xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels((fontLineHeight - imageFrame.size.height) / 2.0)

View File

@ -459,15 +459,26 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .prepaid(_, title, subtitle, prepaidGiveaway):
let color: GiftOptionItem.Icon.Color
switch prepaidGiveaway.months {
case 3:
color = .green
case 6:
color = .blue
case 12:
color = .red
default:
color = .blue
switch prepaidGiveaway.prize {
case let .premium(months):
switch months {
case 3:
color = .green
case 6:
color = .blue
case 12:
color = .red
default:
color = .blue
}
case let .stars(amount):
if amount <= 1000 {
color = .green
} else if amount < 2500 {
color = .blue
} else {
color = .red
}
}
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, sectionId: self.section, action: nil)
case let .starsHeader(_, text, additionalText):
@ -760,7 +771,18 @@ private func createGiveawayControllerEntries(
entries.append(.giftStars(presentationData.theme, presentationData.strings.BoostGift_Prize_Stars, presentationData.strings.BoostGift_CreateGiveawayInfo, state.mode == .starsGiveaway))
case let .prepaid(prepaidGiveaway):
entries.append(.prepaidHeader(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayTitle))
entries.append(.prepaid(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity), presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(prepaidGiveaway.months)").string, prepaidGiveaway))
let title: String
let text: String
switch prepaidGiveaway.prize {
case let .premium(months):
title = presentationData.strings.BoostGift_PrepaidGiveawayCount(prepaidGiveaway.quantity)
text = presentationData.strings.BoostGift_PrepaidGiveawayMonths("\(months)").string
case let .stars(stars):
//TODO:localize
title = "\(stars) Telegram Stars"
text = "among \(prepaidGiveaway.quantity) winners"
}
entries.append(.prepaid(presentationData.theme, title, text, prepaidGiveaway))
}
if case .starsGiveaway = state.mode, !starsGiveawayOptions.isEmpty {
@ -988,10 +1010,20 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let actionsDisposable = DisposableSet()
let initialSubscriptions: Int32
let initialStars: Int64
let initialWinners: Int32
if case let .prepaid(prepaidGiveaway) = subject {
if case let .stars(stars) = prepaidGiveaway.prize {
initialStars = stars
} else {
initialStars = 500
}
initialSubscriptions = prepaidGiveaway.quantity
initialWinners = prepaidGiveaway.quantity
} else {
initialSubscriptions = 5
initialStars = 500
initialWinners = 5
}
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
@ -1009,7 +1041,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let minDate = currentTime + 60 * 1
let maxDate = currentTime + context.userLimits.maxGiveawayPeriodSeconds
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, stars: 500, winners: 5, time: expiryTime)
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, stars: initialStars, winners: initialWinners, time: expiryTime)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState)

View File

@ -29,7 +29,7 @@ final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem {
func isEqual(to: ItemListControllerHeaderItem) -> Bool {
if let item = to as? CreateGiveawayHeaderItem {
return self.theme === item.theme && self.title == item.title && self.text == item.text
return self.theme === item.theme && self.title == item.title && self.text == item.text && self.isStars == item.isStars
} else {
return false
}
@ -198,16 +198,32 @@ class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode {
self.backgroundNode.update(size: CGSize(width: layout.size.width, height: navigationBarHeight), transition: transition)
let colors: [UIColor]
let particleColor: UIColor?
if self.item.isStars {
colors = [
UIColor(rgb: 0xe57d02),
UIColor(rgb: 0xf09903),
UIColor(rgb: 0xf9b004),
UIColor(rgb: 0xfdd219)
]
particleColor = UIColor(rgb: 0xf9b004)
} else {
colors = [
UIColor(rgb: 0x6a94ff),
UIColor(rgb: 0x9472fd),
UIColor(rgb: 0xe26bd3)
]
particleColor = nil
}
let component = AnyComponent(PremiumStarComponent(
theme: self.item.theme,
isIntro: true,
isVisible: true,
hasIdleAnimations: true,
colors: [
UIColor(rgb: 0x6a94ff),
UIColor(rgb: 0x9472fd),
UIColor(rgb: 0xe26bd3)
]
colors: colors,
particleColor: particleColor
))
let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0)

View File

@ -1038,15 +1038,26 @@ private enum StatsEntry: ItemListNodeEntry {
})
case let .boostPrepaid(_, _, title, subtitle, prepaidGiveaway):
let color: GiftOptionItem.Icon.Color
switch prepaidGiveaway.months {
case 3:
color = .green
case 6:
color = .blue
case 12:
color = .red
default:
color = .blue
switch prepaidGiveaway.prize {
case let .premium(months):
switch months {
case 3:
color = .green
case 6:
color = .blue
case 12:
color = .red
default:
color = .blue
}
case let .stars(amount):
if amount <= 1000 {
color = .green
} else if amount < 2500 {
color = .blue
} else {
color = .red
}
}
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: .image(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, titleBadge: "\(prepaidGiveaway.quantity * 4)", subtitle: subtitle, label: nil, sectionId: self.section, action: {
arguments.createPrepaidGiveaway(prepaidGiveaway)
@ -1423,7 +1434,17 @@ private func boostsEntries(
entries.append(.boostPrepaidTitle(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysTitle))
var i: Int32 = 0
for giveaway in boostData.prepaidGiveaways {
entries.append(.boostPrepaid(i, presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawayCount(giveaway.quantity), presentationData.strings.Stats_Boosts_PrepaidGiveawayMonths("\(giveaway.months)").string, giveaway))
let title: String
let text: String
switch giveaway.prize {
case let .premium(months):
title = presentationData.strings.Stats_Boosts_PrepaidGiveawayCount(giveaway.quantity)
text = presentationData.strings.Stats_Boosts_PrepaidGiveawayMonths("\(months)").string
case let .stars(stars):
title = "\(stars) Telegram Stars"
text = "among \(giveaway.quantity) winners"
}
entries.append(.boostPrepaid(i, presentationData.theme, title, text, giveaway))
i += 1
}
entries.append(.boostPrepaidInfo(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysInfo))

View File

@ -80,7 +80,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1821253126] = { return Api.Birthday.parse_birthday($0) }
dict[-1132882121] = { return Api.Bool.parse_boolFalse($0) }
dict[-1720552011] = { return Api.Bool.parse_boolTrue($0) }
dict[706514033] = { return Api.Boost.parse_boost($0) }
dict[1262359766] = { return Api.Boost.parse_boost($0) }
dict[-1778593322] = { return Api.BotApp.parse_botApp($0) }
dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) }
dict[-1989921868] = { return Api.BotBusinessConnection.parse_botBusinessConnection($0) }
@ -553,7 +553,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-935499028] = { return Api.MessageAction.parse_messageActionGiftPremium($0) }
dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) }
dict[-1475391004] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($0) }
dict[715107781] = { return Api.MessageAction.parse_messageActionGiveawayResults($0) }
dict[-2015170219] = { return Api.MessageAction.parse_messageActionGiveawayResults($0) }
dict[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) }
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($0) }
@ -746,6 +746,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) }
dict[1596792306] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) }
dict[-1303143084] = { return Api.PrepaidGiveaway.parse_prepaidGiveaway($0) }
dict[-1973402371] = { return Api.PrepaidGiveaway.parse_prepaidStarsGiveaway($0) }
dict[-1534675103] = { return Api.PrivacyKey.parse_privacyKeyAbout($0) }
dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) }
dict[536913176] = { return Api.PrivacyKey.parse_privacyKeyBirthday($0) }

View File

@ -962,13 +962,13 @@ public extension Api {
}
public extension Api {
enum Boost: TypeConstructorDescription {
case boost(flags: Int32, id: String, userId: Int64?, giveawayMsgId: Int32?, date: Int32, expires: Int32, usedGiftSlug: String?, multiplier: Int32?)
case boost(flags: Int32, id: String, userId: Int64?, giveawayMsgId: Int32?, date: Int32, expires: Int32, usedGiftSlug: String?, multiplier: Int32?, stars: Int64?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .boost(let flags, let id, let userId, let giveawayMsgId, let date, let expires, let usedGiftSlug, let multiplier):
case .boost(let flags, let id, let userId, let giveawayMsgId, let date, let expires, let usedGiftSlug, let multiplier, let stars):
if boxed {
buffer.appendInt32(706514033)
buffer.appendInt32(1262359766)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, buffer: buffer, boxed: false)
@ -978,14 +978,15 @@ public extension Api {
serializeInt32(expires, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 4) != 0 {serializeString(usedGiftSlug!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 5) != 0 {serializeInt32(multiplier!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 6) != 0 {serializeInt64(stars!, buffer: buffer, boxed: false)}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .boost(let flags, let id, let userId, let giveawayMsgId, let date, let expires, let usedGiftSlug, let multiplier):
return ("boost", [("flags", flags as Any), ("id", id as Any), ("userId", userId as Any), ("giveawayMsgId", giveawayMsgId as Any), ("date", date as Any), ("expires", expires as Any), ("usedGiftSlug", usedGiftSlug as Any), ("multiplier", multiplier as Any)])
case .boost(let flags, let id, let userId, let giveawayMsgId, let date, let expires, let usedGiftSlug, let multiplier, let stars):
return ("boost", [("flags", flags as Any), ("id", id as Any), ("userId", userId as Any), ("giveawayMsgId", giveawayMsgId as Any), ("date", date as Any), ("expires", expires as Any), ("usedGiftSlug", usedGiftSlug as Any), ("multiplier", multiplier as Any), ("stars", stars as Any)])
}
}
@ -1006,6 +1007,8 @@ public extension Api {
if Int(_1!) & Int(1 << 4) != 0 {_7 = parseString(reader) }
var _8: Int32?
if Int(_1!) & Int(1 << 5) != 0 {_8 = reader.readInt32() }
var _9: Int64?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt64() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
@ -1014,8 +1017,9 @@ public extension Api {
let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.Boost.boost(flags: _1!, id: _2!, userId: _3, giveawayMsgId: _4, date: _5!, expires: _6!, usedGiftSlug: _7, multiplier: _8)
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
return Api.Boost.boost(flags: _1!, id: _2!, userId: _3, giveawayMsgId: _4, date: _5!, expires: _6!, usedGiftSlug: _7, multiplier: _8, stars: _9)
}
else {
return nil

View File

@ -990,7 +990,7 @@ public extension Api {
case messageActionGiftPremium(flags: Int32, currency: String, amount: Int64, months: Int32, cryptoCurrency: String?, cryptoAmount: Int64?)
case messageActionGiftStars(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case messageActionGiveawayLaunch(flags: Int32, stars: Int64?)
case messageActionGiveawayResults(winnersCount: Int32, unclaimedCount: Int32)
case messageActionGiveawayResults(flags: Int32, winnersCount: Int32, unclaimedCount: Int32)
case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?)
case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32)
case messageActionHistoryClear
@ -1183,10 +1183,11 @@ public extension Api {
serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(stars!, buffer: buffer, boxed: false)}
break
case .messageActionGiveawayResults(let winnersCount, let unclaimedCount):
case .messageActionGiveawayResults(let flags, let winnersCount, let unclaimedCount):
if boxed {
buffer.appendInt32(715107781)
buffer.appendInt32(-2015170219)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(winnersCount, buffer: buffer, boxed: false)
serializeInt32(unclaimedCount, buffer: buffer, boxed: false)
break
@ -1436,8 +1437,8 @@ public extension Api {
return ("messageActionGiftStars", [("flags", flags as Any), ("currency", currency as Any), ("amount", amount as Any), ("stars", stars as Any), ("cryptoCurrency", cryptoCurrency as Any), ("cryptoAmount", cryptoAmount as Any), ("transactionId", transactionId as Any)])
case .messageActionGiveawayLaunch(let flags, let stars):
return ("messageActionGiveawayLaunch", [("flags", flags as Any), ("stars", stars as Any)])
case .messageActionGiveawayResults(let winnersCount, let unclaimedCount):
return ("messageActionGiveawayResults", [("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any)])
case .messageActionGiveawayResults(let flags, let winnersCount, let unclaimedCount):
return ("messageActionGiveawayResults", [("flags", flags as Any), ("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any)])
case .messageActionGroupCall(let flags, let call, let duration):
return ("messageActionGroupCall", [("flags", flags as Any), ("call", call as Any), ("duration", duration as Any)])
case .messageActionGroupCallScheduled(let call, let scheduleDate):
@ -1794,10 +1795,13 @@ public extension Api {
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageAction.messageActionGiveawayResults(winnersCount: _1!, unclaimedCount: _2!)
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.MessageAction.messageActionGiveawayResults(flags: _1!, winnersCount: _2!, unclaimedCount: _3!)
}
else {
return nil

View File

@ -1013,6 +1013,7 @@ public extension Api {
public extension Api {
enum PrepaidGiveaway: TypeConstructorDescription {
case prepaidGiveaway(id: Int64, months: Int32, quantity: Int32, date: Int32)
case prepaidStarsGiveaway(id: Int64, stars: Int64, quantity: Int32, date: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -1025,6 +1026,15 @@ public extension Api {
serializeInt32(quantity, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
case .prepaidStarsGiveaway(let id, let stars, let quantity, let date):
if boxed {
buffer.appendInt32(-1973402371)
}
serializeInt64(id, buffer: buffer, boxed: false)
serializeInt64(stars, buffer: buffer, boxed: false)
serializeInt32(quantity, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
break
}
}
@ -1032,6 +1042,8 @@ public extension Api {
switch self {
case .prepaidGiveaway(let id, let months, let quantity, let date):
return ("prepaidGiveaway", [("id", id as Any), ("months", months as Any), ("quantity", quantity as Any), ("date", date as Any)])
case .prepaidStarsGiveaway(let id, let stars, let quantity, let date):
return ("prepaidStarsGiveaway", [("id", id as Any), ("stars", stars as Any), ("quantity", quantity as Any), ("date", date as Any)])
}
}
@ -1055,6 +1067,26 @@ public extension Api {
return nil
}
}
public static func parse_prepaidStarsGiveaway(_ reader: BufferReader) -> PrepaidGiveaway? {
var _1: Int64?
_1 = reader.readInt64()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: Int32?
_4 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.PrepaidGiveaway.prepaidStarsGiveaway(id: _1!, stars: _2!, quantity: _3!, date: _4!)
}
else {
return nil
}
}
}
}

View File

@ -137,8 +137,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 2)) != 0, boostPeerId: boostPeer?.peerId, months: months, currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount))
case let .messageActionGiveawayLaunch(_, stars):
return TelegramMediaAction(action: .giveawayLaunched(stars: stars))
case let .messageActionGiveawayResults(winners, unclaimed):
return TelegramMediaAction(action: .giveawayResults(winners: winners, unclaimed: unclaimed))
case let .messageActionGiveawayResults(flags, winners, unclaimed):
return TelegramMediaAction(action: .giveawayResults(winners: winners, unclaimed: unclaimed, stars: (flags & (1 << 0)) != 0))
case let .messageActionBoostApply(boosts):
return TelegramMediaAction(action: .boostsApplied(boosts: boosts))
case let .messageActionPaymentRefunded(_, peer, currency, totalAmount, payload, charge):

View File

@ -279,7 +279,7 @@ private final class ChannelBoostersContextImpl {
var resultBoosts: [ChannelBoostersContext.State.Boost] = []
for boost in boosts {
switch boost {
case let .boost(flags, id, userId, giveawayMessageId, date, expires, usedGiftSlug, multiplier):
case let .boost(flags, id, userId, giveawayMessageId, date, expires, usedGiftSlug, multiplier, stars):
var boostFlags: ChannelBoostersContext.State.Boost.Flags = []
var boostPeer: EnginePeer?
if let userId = userId {
@ -297,7 +297,7 @@ private final class ChannelBoostersContextImpl {
if (flags & (1 << 3)) != 0 {
boostFlags.insert(.isUnclaimed)
}
resultBoosts.append(ChannelBoostersContext.State.Boost(flags: boostFlags, id: id, peer: boostPeer, date: date, expires: expires, multiplier: multiplier ?? 1, slug: usedGiftSlug, giveawayMessageId: giveawayMessageId.flatMap { EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
resultBoosts.append(ChannelBoostersContext.State.Boost(flags: boostFlags, id: id, peer: boostPeer, date: date, expires: expires, multiplier: multiplier ?? 1, stars: stars, slug: usedGiftSlug, giveawayMessageId: giveawayMessageId.flatMap { EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: $0) }))
}
}
if populateCache {
@ -388,6 +388,7 @@ public final class ChannelBoostersContext {
public var date: Int32
public var expires: Int32
public var multiplier: Int32
public var stars: Int64?
public var slug: String?
public var giveawayMessageId: EngineMessage.Id?
}

View File

@ -125,7 +125,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?)
case giveawayLaunched(stars: Int64?)
case joinedChannel
case giveawayResults(winners: Int32, unclaimed: Int32)
case giveawayResults(winners: Int32, unclaimed: Int32, stars: Bool)
case boostsApplied(boosts: Int32)
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String)
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
@ -235,7 +235,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 38:
self = .joinedChannel
case 39:
self = .giveawayResults(winners: decoder.decodeInt32ForKey("winners", orElse: 0), unclaimed: decoder.decodeInt32ForKey("unclaimed", orElse: 0))
self = .giveawayResults(winners: decoder.decodeInt32ForKey("winners", orElse: 0), unclaimed: decoder.decodeInt32ForKey("unclaimed", orElse: 0), stars: decoder.decodeBoolForKey("stars", orElse: false))
case 40:
self = .boostsApplied(boosts: decoder.decodeInt32ForKey("boosts", orElse: 0))
case 41:
@ -470,10 +470,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
}
case .joinedChannel:
encoder.encodeInt32(38, forKey: "_rawValue")
case let .giveawayResults(winners, unclaimed):
case let .giveawayResults(winners, unclaimed, stars):
encoder.encodeInt32(39, forKey: "_rawValue")
encoder.encodeInt32(winners, forKey: "winners")
encoder.encodeInt32(unclaimed, forKey: "unclaimed")
encoder.encodeBool(stars, forKey: "stars")
case let .boostsApplied(boosts):
encoder.encodeInt32(40, forKey: "_rawValue")
encoder.encodeInt32(boosts, forKey: "boosts")

View File

@ -89,8 +89,13 @@ public enum PremiumGiveawayInfo: Equatable {
}
public struct PrepaidGiveaway: Equatable {
public enum Prize: Equatable {
case premium(months: Int32)
case stars(stars: Int64)
}
public let id: Int64
public let months: Int32
public let prize: Prize
public let quantity: Int32
public let date: Int32
}
@ -304,7 +309,12 @@ extension PrepaidGiveaway {
switch apiPrepaidGiveaway {
case let .prepaidGiveaway(id, months, quantity, date):
self.id = id
self.months = months
self.prize = .premium(months: months)
self.quantity = quantity
self.date = date
case let .prepaidStarsGiveaway(id, stars, quantity, date):
self.id = id
self.prize = .stars(stars: stars)
self.quantity = quantity
self.date = date
}

View File

@ -981,7 +981,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
case .joinedChannel:
attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor)
case let .giveawayResults(winners, unclaimed):
case let .giveawayResults(winners, unclaimed, _):
if winners == 0 {
attributedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
} else if unclaimed > 0 {

View File

@ -1728,7 +1728,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
}
if let range = attributedString.string.range(of: "*") {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .stars(tinted: false)), range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
}
@ -5967,7 +5967,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
}
var addedPrivacy = false
if let privacyPolicyUrl = (data.cachedData as? CachedUserData)?.botInfo?.privacyPolicyUrl {
var privacyPolicyUrl: String?
if let cachedData = (data.cachedData as? CachedUserData), let botInfo = cachedData.botInfo {
if let url = botInfo.privacyPolicyUrl {
privacyPolicyUrl = url
} else if botInfo.commands.contains(where: { $0.text == "privacy" }) {
} else {
privacyPolicyUrl = presentationData.strings.WebApp_PrivacyPolicy_URL
}
}
if let privacyPolicyUrl {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in

View File

@ -290,6 +290,101 @@ public final class PremiumStarComponent: Component {
}
}
private func updateColors(animated: Bool = false) {
guard let component = self.component, let colors = component.colors, let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
if animated {
UIView.animate(withDuration: 0.25, animations: {
node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors)
})
} else {
node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors)
}
let names: [String] = [
"particles_left",
"particles_right",
"particles_left_bottom",
"particles_right_bottom",
"particles_center"
]
let starNames: [String] = [
"coins_left",
"coins_right"
]
if let particleColor = component.particleColor {
for name in starNames {
if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first {
if animated {
particleSystem.warmupDuration = 0.0
}
particleSystem.particleIntensity = 1.0
particleSystem.particleIntensityVariation = 0.05
particleSystem.particleColor = particleColor
particleSystem.particleColorVariation = SCNVector4Make(0.07, 0.0, 0.1, 0.0)
node.isHidden = false
if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] {
let animation = CAKeyframeAnimation()
if let existing = colorController.animation as? CAKeyframeAnimation {
animation.keyTimes = existing.keyTimes
animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? []
} else {
animation.values = [ 0.0, 1.0, 1.0, 0.0 ]
}
let opacityController = SCNParticlePropertyController(animation: animation)
particleSystem.propertyControllers = [
.size: sizeController,
.opacity: opacityController
]
}
}
}
} else {
if animated {
for name in starNames {
if let node = scene.rootNode.childNode(withName: name, recursively: false) {
node.isHidden = true
}
}
}
}
for name in names {
if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first {
if let particleColor = component.particleColor {
particleSystem.particleIntensity = min(1.0, 2.0 * particleSystem.particleIntensity)
particleSystem.particleIntensityVariation = 0.05
particleSystem.particleColor = particleColor
particleSystem.particleColorVariation = SCNVector4Make(0.1, 0.0, 0.12, 0.0)
} else {
particleSystem.particleColorVariation = SCNVector4Make(0.12, 0.03, 0.035, 0.0)
if animated {
particleSystem.particleColor = UIColor(rgb: 0xaa69ea)
}
}
if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] {
let animation = CAKeyframeAnimation()
if let existing = colorController.animation as? CAKeyframeAnimation {
animation.keyTimes = existing.keyTimes
animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? []
} else {
animation.values = [ 0.0, 1.0, 1.0, 0.0 ]
}
let opacityController = SCNParticlePropertyController(animation: animation)
particleSystem.propertyControllers = [
.size: sizeController,
.opacity: opacityController
]
}
}
}
}
private var didSetup = false
private func setup() {
guard !self.didSetup, let scene = loadCompressedScene(name: "star2", version: sceneVersion) else {
@ -300,78 +395,7 @@ public final class PremiumStarComponent: Component {
self.sceneView.scene = scene
self.sceneView.delegate = self
if let component = self.component, let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors =
component.colors {
node.geometry?.materials.first?.diffuse.contents = generateDiffuseTexture(colors: colors)
let names: [String] = [
"particles_left",
"particles_right",
"particles_left_bottom",
"particles_right_bottom",
"particles_center"
]
let starNames: [String] = [
"coins_left",
"coins_right"
]
if let particleColor = component.particleColor {
for name in starNames {
if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first {
particleSystem.particleIntensity = 1.0
particleSystem.particleIntensityVariation = 0.05
particleSystem.particleColor = particleColor
particleSystem.particleColorVariation = SCNVector4Make(0.07, 0.0, 0.1, 0.0)
node.isHidden = false
if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] {
let animation = CAKeyframeAnimation()
if let existing = colorController.animation as? CAKeyframeAnimation {
animation.keyTimes = existing.keyTimes
animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? []
} else {
animation.values = [ 0.0, 1.0, 1.0, 0.0 ]
}
let opacityController = SCNParticlePropertyController(animation: animation)
particleSystem.propertyControllers = [
.size: sizeController,
.opacity: opacityController
]
}
}
}
}
for name in names {
if let node = scene.rootNode.childNode(withName: name, recursively: false), let particleSystem = node.particleSystems?.first {
if let particleColor = component.particleColor {
particleSystem.particleIntensity = min(1.0, 2.0 * particleSystem.particleIntensity)
particleSystem.particleIntensityVariation = 0.05
particleSystem.particleColor = particleColor
particleSystem.particleColorVariation = SCNVector4Make(0.1, 0.0, 0.12, 0.0)
} else {
particleSystem.particleColorVariation = SCNVector4Make(0.12, 0.03, 0.035, 0.0)
}
if let propertyControllers = particleSystem.propertyControllers, let sizeController = propertyControllers[.size], let colorController = propertyControllers[.color] {
let animation = CAKeyframeAnimation()
if let existing = colorController.animation as? CAKeyframeAnimation {
animation.keyTimes = existing.keyTimes
animation.values = existing.values?.compactMap { ($0 as? UIColor)?.alpha } ?? []
} else {
animation.values = [ 0.0, 1.0, 1.0, 0.0 ]
}
let opacityController = SCNParticlePropertyController(animation: animation)
particleSystem.propertyControllers = [
.size: sizeController,
.opacity: opacityController
]
}
}
}
}
self.updateColors()
if self.animateFrom != nil {
let _ = self.sceneView.snapshot()
@ -515,10 +539,6 @@ public final class PremiumStarComponent: Component {
return
}
// if let material = node.geometry?.materials.first {
// material.metalness.intensity = 0.4
// }
let animation = CABasicAnimation(keyPath: "contentsTransform")
animation.fillMode = .forwards
animation.fromValue = NSValue(scnMatrix4: initial)
@ -536,7 +556,13 @@ public final class PremiumStarComponent: Component {
node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer")
}
private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) {
private func playAppearanceAnimation(
velocity: CGFloat? = nil,
smallAngle: Bool = false,
mirror: Bool = false,
explode: Bool = false,
force: Bool = false
) {
guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return
}
@ -621,7 +647,7 @@ public final class PremiumStarComponent: Component {
}
var from = node.presentation.eulerAngles
if abs(from.y - .pi * 2.0) < 0.001 {
if abs(from.y) - .pi * 2.0 < 0.05 {
from.y = 0.0
}
node.removeAnimation(forKey: "tapRotate")
@ -633,6 +659,7 @@ public final class PremiumStarComponent: Component {
if mirror {
toValue *= -1
}
let to = SCNVector3(x: 0.0, y: toValue, z: 0.0)
let distance = rad2deg(to.y - from.y)
@ -657,10 +684,16 @@ public final class PremiumStarComponent: Component {
}
func update(component: PremiumStarComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component
self.component = component
self.setup()
if let previousComponent, component.colors != previousComponent.colors {
self.updateColors(animated: true)
self.playAppearanceAnimation(velocity: nil, mirror: component.colors?.contains(UIColor(rgb: 0xe57d02)) == true, explode: true, force: true)
}
if let _ = component.particleColor {
self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor
}

View File

@ -110,6 +110,7 @@ private final class SheetContent: CombinedComponent {
let titleString: String
let amountTitle: String
let amountPlaceholder: String
let amountLabel: String?
let minAmount: Int64?
let maxAmount: Int64?
@ -124,6 +125,7 @@ private final class SheetContent: CombinedComponent {
minAmount = configuration.minWithdrawAmount
maxAmount = status.balances.availableBalance
amountLabel = nil
case .paidMedia:
titleString = environment.strings.Stars_PaidContent_Title
amountTitle = environment.strings.Stars_PaidContent_AmountTitle
@ -131,14 +133,22 @@ private final class SheetContent: CombinedComponent {
minAmount = 1
maxAmount = configuration.maxPaidMediaAmount
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > 0 {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
amountLabel = "\(formatTonUsdValue(amount, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
} else {
amountLabel = nil
}
case .reaction:
titleString = environment.strings.Stars_SendStars_Title
amountTitle = environment.strings.Stars_SendStars_AmountTitle
amountPlaceholder = environment.strings.Stars_SendStars_AmountPlaceholder
minAmount = 1
//TODO:
maxAmount = configuration.maxPaidMediaAmount
amountLabel = nil
}
let title = title.update(
@ -264,11 +274,13 @@ private final class SheetContent: CombinedComponent {
component: AnyComponent(
AmountFieldComponent(
textColor: theme.list.itemPrimaryTextColor,
secondaryColor: theme.list.itemSecondaryTextColor,
placeholderColor: theme.list.itemPlaceholderTextColor,
value: state.amount,
minValue: minAmount,
maxValue: maxAmount,
placeholderText: amountPlaceholder,
labelText: amountLabel,
amountUpdated: { [weak state] amount in
state?.amount = amount
state?.updated()
@ -576,30 +588,36 @@ private final class AmountFieldComponent: Component {
typealias EnvironmentType = Empty
let textColor: UIColor
let secondaryColor: UIColor
let placeholderColor: UIColor
let value: Int64?
let minValue: Int64?
let maxValue: Int64?
let placeholderText: String
let labelText: String?
let amountUpdated: (Int64?) -> Void
let tag: AnyObject?
init(
textColor: UIColor,
secondaryColor: UIColor,
placeholderColor: UIColor,
value: Int64?,
minValue: Int64?,
maxValue: Int64?,
placeholderText: String,
labelText: String?,
amountUpdated: @escaping (Int64?) -> Void,
tag: AnyObject? = nil
) {
self.textColor = textColor
self.secondaryColor = secondaryColor
self.placeholderColor = placeholderColor
self.value = value
self.minValue = minValue
self.maxValue = maxValue
self.placeholderText = placeholderText
self.labelText = labelText
self.amountUpdated = amountUpdated
self.tag = tag
}
@ -608,6 +626,9 @@ private final class AmountFieldComponent: Component {
if lhs.textColor != rhs.textColor {
return false
}
if lhs.secondaryColor != rhs.secondaryColor {
return false
}
if lhs.placeholderColor != rhs.placeholderColor {
return false
}
@ -623,6 +644,9 @@ private final class AmountFieldComponent: Component {
if lhs.placeholderText != rhs.placeholderText {
return false
}
if lhs.labelText != rhs.labelText {
return false
}
return true
}
@ -640,6 +664,7 @@ private final class AmountFieldComponent: Component {
private let placeholderView: ComponentView<Empty>
private let iconView: UIImageView
private let textField: TextFieldNodeView
private let labelView: ComponentView<Empty>
private var component: AmountFieldComponent?
private weak var state: EmptyComponentState?
@ -647,6 +672,7 @@ private final class AmountFieldComponent: Component {
override init(frame: CGRect) {
self.placeholderView = ComponentView<Empty>()
self.textField = TextFieldNodeView(frame: .zero)
self.labelView = ComponentView<Empty>()
self.iconView = UIImageView(image: UIImage(bundleImageName: "Premium/Stars/StarLarge"))
@ -740,6 +766,7 @@ private final class AmountFieldComponent: Component {
let size = CGSize(width: availableSize.width, height: 44.0)
let sideInset: CGFloat = 15.0
var leftInset: CGFloat = 15.0
if let icon = self.iconView.image {
leftInset += icon.size.width + 6.0
@ -765,10 +792,34 @@ private final class AmountFieldComponent: Component {
}
placeholderComponentView.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((size.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize)
placeholderComponentView.isHidden = !(self.textField.text ?? "").isEmpty
}
if let labelText = component.labelText {
let labelSize = self.labelView.update(
transition: .immediate,
component: AnyComponent(
Text(
text: labelText,
font: Font.regular(17.0),
color: component.secondaryColor
)
),
environment: {},
containerSize: availableSize
)
if let labelView = self.labelView.view {
if labelView.superview == nil {
self.insertSubview(labelView, at: 0)
}
labelView.frame = CGRect(origin: CGPoint(x: size.width - sideInset - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0) + 1.0 - UIScreenPixel), size: labelSize)
}
} else if let labelView = self.labelView.view, labelView.superview != nil {
labelView.removeFromSuperview()
}
self.textField.frame = CGRect(x: leftInset, y: 0.0, width: size.width - 30.0, height: 44.0)
return size
@ -808,15 +859,17 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor
private struct StarsWithdrawConfiguration {
static var defaultValue: StarsWithdrawConfiguration {
return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil)
return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil, usdWithdrawRate: nil)
}
let minWithdrawAmount: Int64?
let maxPaidMediaAmount: Int64?
let usdWithdrawRate: Double?
fileprivate init(minWithdrawAmount: Int64?, maxPaidMediaAmount: Int64?) {
fileprivate init(minWithdrawAmount: Int64?, maxPaidMediaAmount: Int64?, usdWithdrawRate: Double?) {
self.minWithdrawAmount = minWithdrawAmount
self.maxPaidMediaAmount = maxPaidMediaAmount
self.usdWithdrawRate = usdWithdrawRate
}
static func with(appConfiguration: AppConfiguration) -> StarsWithdrawConfiguration {
@ -829,8 +882,12 @@ private struct StarsWithdrawConfiguration {
if let value = data["stars_paid_post_amount_max"] as? Double {
maxPaidMediaAmount = Int64(value)
}
var usdWithdrawRate: Double?
if let value = data["stars_usd_withdraw_rate_x1000"] as? Double {
usdWithdrawRate = value
}
return StarsWithdrawConfiguration(minWithdrawAmount: minWithdrawAmount, maxPaidMediaAmount: maxPaidMediaAmount)
return StarsWithdrawConfiguration(minWithdrawAmount: minWithdrawAmount, maxPaidMediaAmount: maxPaidMediaAmount, usdWithdrawRate: usdWithdrawRate)
} else {
return .defaultValue
}

View File

@ -7,12 +7,16 @@
try {
const docStr = new XMLSerializer().serializeToString(document);
const clean = DOMPurify.sanitize(docStr, {WHOLE_DOCUMENT: true});
const clean = DOMPurify.sanitize(docStr, {WHOLE_DOCUMENT: true, ADD_TAGS: ["iframe"]});
const cleanDoc = new DOMParser().parseFromString(clean, "text/html");
const readability = new Readability(cleanDoc)
const result = readability.parse()
if (result.length && result.length < 1000) {
return null
}
const doc = new DOMParser().parseFromString(result.content, 'text/html').body
const parse = e => [...(e.childNodes || [])].map(node => {