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.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.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 sourceLocation: InstantPageSourceLocation
private let preloadedResouces: [Any]? private let preloadedResouces: [Any]?
private var originalContent: BrowserContent? private var originalContent: BrowserContent?
private let url: String
private var webPage: TelegramMediaWebpage? private var webPage: TelegramMediaWebpage?
@ -102,6 +103,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.sourceLocation = sourceLocation self.sourceLocation = sourceLocation
self.preloadedResouces = preloadedResouces self.preloadedResouces = preloadedResouces
self.originalContent = originalContent self.originalContent = originalContent
self.url = url
self.uuid = UUID() self.uuid = UUID()
@ -904,6 +906,12 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
anchor = String(baseUrl[anchorRange.upperBound...]).removingPercentEncoding anchor = String(baseUrl[anchorRange.upperBound...]).removingPercentEncoding
baseUrl = String(baseUrl[..<anchorRange.lowerBound]) 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 { 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) self.scrollToAnchor(anchor)
@ -914,7 +922,7 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
self.loadProgress.set(0.02) self.loadProgress.set(0.02)
self.loadWebpageDisposable.set(nil) 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 |> deliverOnMainQueue).start(next: { [weak self] result in
if let strongSelf = self { if let strongSelf = self {
strongSelf.loadProgress.set(0.07) strongSelf.loadProgress.set(0.07)
@ -997,8 +1005,15 @@ final class BrowserInstantPageContent: UIView, BrowserContent, UIScrollViewDeleg
} }
private func openUrlIn(_ url: InstantPageUrlItem) { 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 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 { if let self {
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: presentationData, navigationController: nil, dismissInput: {}) 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: case .longTap:
if let url = self.urlForTapLocation(location) { 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 openText = canOpenIn ? self.presentationData.strings.Conversation_FileOpenIn : self.presentationData.strings.Conversation_LinkDialogOpen
let actionSheet = ActionSheetController(instantPageTheme: self.theme) let actionSheet = ActionSheetController(instantPageTheme: self.theme)
actionSheet.setItemGroups([ActionSheetItemGroup(items: [ actionSheet.setItemGroups([ActionSheetItemGroup(items: [
ActionSheetTextItem(title: url.url), ActionSheetTextItem(title: baseUrl),
ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in ActionSheetButtonItem(title: openText, color: .accent, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
if let strongSelf = self { 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 ActionSheetButtonItem(title: self.presentationData.strings.ShareMenu_CopyShareLink, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() actionSheet?.dismissAnimated()
UIPasteboard.general.string = url.url UIPasteboard.general.string = baseUrl
}), }),
ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in ActionSheetButtonItem(title: self.presentationData.strings.Conversation_AddToReadingList, color: .accent, action: { [weak actionSheet] in
actionSheet?.dismissAnimated() 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) 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)) result.append(parseRichText(string))
} else if let item = item as? [String: Any], let tag = item["tag"] as? String { } else if let item = item as? [String: Any], let tag = item["tag"] as? String {
var text: RichText? var text: RichText?
var addLineBreak = false
switch tag { switch tag {
case "b", "strong": case "b", "strong":
text = .bold(parseRichText(item, &media)) text = .bold(parseRichText(item, &media))
@ -273,6 +274,9 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R
flags: [] flags: []
) )
text = .image(id: id, dimensions: PixelDimensions(width: width, height: height)) text = .image(id: id, dimensions: PixelDimensions(width: width, height: height))
if width > 100 {
addLineBreak = true
}
} }
case "br": case "br":
if let last = result.last { if let last = result.last {
@ -284,6 +288,9 @@ private func parseRichText(_ input: [Any], _ media: inout [MediaId: Media]) -> R
if var text { if var text {
text = applyAnchor(text, item: item) text = applyAnchor(text, item: item)
result.append(text) 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 { 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 { 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 { 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 { private func addNewLine(_ input: RichText) -> RichText {
@ -341,8 +463,10 @@ private func addNewLine(_ input: RichText) -> RichText {
case let .anchor(richText, name): case let .anchor(richText, name):
text = .anchor(text: addNewLine(richText), name: name) text = .anchor(text: addNewLine(richText), name: name)
case var .concat(array): case var .concat(array):
array[array.count - 1] = addNewLine(array[array.count - 1]) if !array.isEmpty {
text = .concat(array) array[array.count - 1] = addNewLine(array[array.count - 1])
text = .concat(array)
}
case .image: case .image:
break 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 { guard let content = input["content"] as? [Any], let tag = input["tag"] as? String else {
return nil 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 { guard let item = item as? [String: Any], let tag = item["tag"] as? String, tag == "li" else {
continue 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" 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) 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? { private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media]) -> InstantPageBlock? {
guard let content = input["content"] as? [Any] else { guard let content = input["content"] as? [Any] else {
return nil return nil
@ -519,9 +704,19 @@ private func parseFigure(_ input: [String: Any], _ media: inout [MediaId: Media]
var caption: RichText? var caption: RichText?
for item in content { for item in content {
if let item = item as? [String: Any], let tag = item["tag"] as? String { 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) block = parseImage(item, &media)
} else if tag == "figurecaption" { } else if tag == "figcaption" {
caption = trim(parseRichText(item, &media)) caption = trim(parseRichText(item, &media))
} }
} }
@ -539,7 +734,7 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi
var result: [InstantPageBlock] = [] var result: [InstantPageBlock] = []
for item in input { for item in input {
if let string = item as? String { 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 { } else if let item = item as? [String: Any], let tag = item["tag"] as? String {
let content = item["content"] as? [Any] let content = item["content"] as? [Any]
switch tag { switch tag {
@ -557,7 +752,10 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi
if let image = parseImage(item, &media) { if let image = parseImage(item, &media) {
result.append(image) result.append(image)
} }
break case "iframe":
if let video = parseVideo(item, &media) {
result.append(video)
}
case "figure": case "figure":
if let figure = parseFigure(item, &media) { if let figure = parseFigure(item, &media) {
result.append(figure) result.append(figure)
@ -565,7 +763,7 @@ private func parsePageBlocks(_ input: [Any], _ url: String, _ media: inout [Medi
case "table": case "table":
result.append(parseTable(item, &media)) result.append(parseTable(item, &media))
case "ul", "ol": case "ul", "ol":
if let list = parseList(item, &media) { if let list = parseList(item, url, &media) {
result.append(list) result.append(list)
} }
case "hr": 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) let runRange = NSMakeRange(cfRunRange.location == kCFNotFound ? NSNotFound : cfRunRange.location, cfRunRange.length)
string.enumerateAttributes(in: runRange, options: []) { attributes, range, _ in 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 { 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 xOffset = CTLineGetOffsetForStringIndex(line, CTRunGetStringRange(run).location, nil)
let yOffset = fontLineHeight.isZero ? 0.0 : floorToScreenPixels((fontLineHeight - imageFrame.size.height) / 2.0) 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) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .prepaid(_, title, subtitle, prepaidGiveaway): case let .prepaid(_, title, subtitle, prepaidGiveaway):
let color: GiftOptionItem.Icon.Color let color: GiftOptionItem.Icon.Color
switch prepaidGiveaway.months { switch prepaidGiveaway.prize {
case 3: case let .premium(months):
color = .green switch months {
case 6: case 3:
color = .blue color = .green
case 12: case 6:
color = .red color = .blue
default: case 12:
color = .blue 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) 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): 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)) entries.append(.giftStars(presentationData.theme, presentationData.strings.BoostGift_Prize_Stars, presentationData.strings.BoostGift_CreateGiveawayInfo, state.mode == .starsGiveaway))
case let .prepaid(prepaidGiveaway): case let .prepaid(prepaidGiveaway):
entries.append(.prepaidHeader(presentationData.theme, presentationData.strings.BoostGift_PrepaidGiveawayTitle)) 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 { if case .starsGiveaway = state.mode, !starsGiveawayOptions.isEmpty {
@ -988,10 +1010,20 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let actionsDisposable = DisposableSet() let actionsDisposable = DisposableSet()
let initialSubscriptions: Int32 let initialSubscriptions: Int32
let initialStars: Int64
let initialWinners: Int32
if case let .prepaid(prepaidGiveaway) = subject { if case let .prepaid(prepaidGiveaway) = subject {
if case let .stars(stars) = prepaidGiveaway.prize {
initialStars = stars
} else {
initialStars = 500
}
initialSubscriptions = prepaidGiveaway.quantity initialSubscriptions = prepaidGiveaway.quantity
initialWinners = prepaidGiveaway.quantity
} else { } else {
initialSubscriptions = 5 initialSubscriptions = 5
initialStars = 500
initialWinners = 5
} }
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
@ -1009,7 +1041,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
let minDate = currentTime + 60 * 1 let minDate = currentTime + 60 * 1
let maxDate = currentTime + context.userLimits.maxGiveawayPeriodSeconds 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 statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)

View File

@ -29,7 +29,7 @@ final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem {
func isEqual(to: ItemListControllerHeaderItem) -> Bool { func isEqual(to: ItemListControllerHeaderItem) -> Bool {
if let item = to as? CreateGiveawayHeaderItem { 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 { } else {
return false return false
} }
@ -198,16 +198,32 @@ class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode {
self.backgroundNode.update(size: CGSize(width: layout.size.width, height: navigationBarHeight), transition: transition) 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( let component = AnyComponent(PremiumStarComponent(
theme: self.item.theme, theme: self.item.theme,
isIntro: true, isIntro: true,
isVisible: true, isVisible: true,
hasIdleAnimations: true, hasIdleAnimations: true,
colors: [ colors: colors,
UIColor(rgb: 0x6a94ff), particleColor: particleColor
UIColor(rgb: 0x9472fd),
UIColor(rgb: 0xe26bd3)
]
)) ))
let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0) 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): case let .boostPrepaid(_, _, title, subtitle, prepaidGiveaway):
let color: GiftOptionItem.Icon.Color let color: GiftOptionItem.Icon.Color
switch prepaidGiveaway.months { switch prepaidGiveaway.prize {
case 3: case let .premium(months):
color = .green switch months {
case 6: case 3:
color = .blue color = .green
case 12: case 6:
color = .red color = .blue
default: case 12:
color = .blue 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: { 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) arguments.createPrepaidGiveaway(prepaidGiveaway)
@ -1423,7 +1434,17 @@ private func boostsEntries(
entries.append(.boostPrepaidTitle(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysTitle)) entries.append(.boostPrepaidTitle(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysTitle))
var i: Int32 = 0 var i: Int32 = 0
for giveaway in boostData.prepaidGiveaways { 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 i += 1
} }
entries.append(.boostPrepaidInfo(presentationData.theme, presentationData.strings.Stats_Boosts_PrepaidGiveawaysInfo)) 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[1821253126] = { return Api.Birthday.parse_birthday($0) }
dict[-1132882121] = { return Api.Bool.parse_boolFalse($0) } dict[-1132882121] = { return Api.Bool.parse_boolFalse($0) }
dict[-1720552011] = { return Api.Bool.parse_boolTrue($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[-1778593322] = { return Api.BotApp.parse_botApp($0) }
dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) } dict[1571189943] = { return Api.BotApp.parse_botAppNotModified($0) }
dict[-1989921868] = { return Api.BotBusinessConnection.parse_botBusinessConnection($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[-935499028] = { return Api.MessageAction.parse_messageActionGiftPremium($0) }
dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) } dict[1171632161] = { return Api.MessageAction.parse_messageActionGiftStars($0) }
dict[-1475391004] = { return Api.MessageAction.parse_messageActionGiveawayLaunch($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[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) }
dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) } dict[-1281329567] = { return Api.MessageAction.parse_messageActionGroupCallScheduled($0) }
dict[-1615153660] = { return Api.MessageAction.parse_messageActionHistoryClear($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[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) }
dict[1596792306] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) } dict[1596792306] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) }
dict[-1303143084] = { return Api.PrepaidGiveaway.parse_prepaidGiveaway($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[-1534675103] = { return Api.PrivacyKey.parse_privacyKeyAbout($0) }
dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) } dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) }
dict[536913176] = { return Api.PrivacyKey.parse_privacyKeyBirthday($0) } dict[536913176] = { return Api.PrivacyKey.parse_privacyKeyBirthday($0) }

View File

@ -962,13 +962,13 @@ public extension Api {
} }
public extension Api { public extension Api {
enum Boost: TypeConstructorDescription { 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { 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 { if boxed {
buffer.appendInt32(706514033) buffer.appendInt32(1262359766)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(id, buffer: buffer, boxed: false) serializeString(id, buffer: buffer, boxed: false)
@ -978,14 +978,15 @@ public extension Api {
serializeInt32(expires, buffer: buffer, boxed: false) serializeInt32(expires, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 4) != 0 {serializeString(usedGiftSlug!, 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 << 5) != 0 {serializeInt32(multiplier!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 6) != 0 {serializeInt64(stars!, buffer: buffer, boxed: false)}
break break
} }
} }
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { 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):
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)]) 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) } if Int(_1!) & Int(1 << 4) != 0 {_7 = parseString(reader) }
var _8: Int32? var _8: Int32?
if Int(_1!) & Int(1 << 5) != 0 {_8 = reader.readInt32() } 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 _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
@ -1014,8 +1017,9 @@ public extension Api {
let _c6 = _6 != nil let _c6 = _6 != nil
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil let _c8 = (Int(_1!) & Int(1 << 5) == 0) || _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil
return Api.Boost.boost(flags: _1!, id: _2!, userId: _3, giveawayMsgId: _4, date: _5!, expires: _6!, usedGiftSlug: _7, multiplier: _8) 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 { else {
return nil 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 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 messageActionGiftStars(flags: Int32, currency: String, amount: Int64, stars: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
case messageActionGiveawayLaunch(flags: Int32, stars: Int64?) 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 messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?)
case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32) case messageActionGroupCallScheduled(call: Api.InputGroupCall, scheduleDate: Int32)
case messageActionHistoryClear case messageActionHistoryClear
@ -1183,10 +1183,11 @@ public extension Api {
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(stars!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 0) != 0 {serializeInt64(stars!, buffer: buffer, boxed: false)}
break break
case .messageActionGiveawayResults(let winnersCount, let unclaimedCount): case .messageActionGiveawayResults(let flags, let winnersCount, let unclaimedCount):
if boxed { if boxed {
buffer.appendInt32(715107781) buffer.appendInt32(-2015170219)
} }
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(winnersCount, buffer: buffer, boxed: false) serializeInt32(winnersCount, buffer: buffer, boxed: false)
serializeInt32(unclaimedCount, buffer: buffer, boxed: false) serializeInt32(unclaimedCount, buffer: buffer, boxed: false)
break 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)]) 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): case .messageActionGiveawayLaunch(let flags, let stars):
return ("messageActionGiveawayLaunch", [("flags", flags as Any), ("stars", stars as Any)]) return ("messageActionGiveawayLaunch", [("flags", flags as Any), ("stars", stars as Any)])
case .messageActionGiveawayResults(let winnersCount, let unclaimedCount): case .messageActionGiveawayResults(let flags, let winnersCount, let unclaimedCount):
return ("messageActionGiveawayResults", [("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any)]) return ("messageActionGiveawayResults", [("flags", flags as Any), ("winnersCount", winnersCount as Any), ("unclaimedCount", unclaimedCount as Any)])
case .messageActionGroupCall(let flags, let call, let duration): case .messageActionGroupCall(let flags, let call, let duration):
return ("messageActionGroupCall", [("flags", flags as Any), ("call", call as Any), ("duration", duration as Any)]) return ("messageActionGroupCall", [("flags", flags as Any), ("call", call as Any), ("duration", duration as Any)])
case .messageActionGroupCallScheduled(let call, let scheduleDate): case .messageActionGroupCallScheduled(let call, let scheduleDate):
@ -1794,10 +1795,13 @@ public extension Api {
_1 = reader.readInt32() _1 = reader.readInt32()
var _2: Int32? var _2: Int32?
_2 = reader.readInt32() _2 = reader.readInt32()
var _3: Int32?
_3 = reader.readInt32()
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
if _c1 && _c2 { let _c3 = _3 != nil
return Api.MessageAction.messageActionGiveawayResults(winnersCount: _1!, unclaimedCount: _2!) if _c1 && _c2 && _c3 {
return Api.MessageAction.messageActionGiveawayResults(flags: _1!, winnersCount: _2!, unclaimedCount: _3!)
} }
else { else {
return nil return nil

View File

@ -1013,6 +1013,7 @@ public extension Api {
public extension Api { public extension Api {
enum PrepaidGiveaway: TypeConstructorDescription { enum PrepaidGiveaway: TypeConstructorDescription {
case prepaidGiveaway(id: Int64, months: Int32, quantity: Int32, date: Int32) 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) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
@ -1025,6 +1026,15 @@ public extension Api {
serializeInt32(quantity, buffer: buffer, boxed: false) serializeInt32(quantity, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false)
break 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 { switch self {
case .prepaidGiveaway(let id, let months, let quantity, let date): 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)]) 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 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)) 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): case let .messageActionGiveawayLaunch(_, stars):
return TelegramMediaAction(action: .giveawayLaunched(stars: stars)) return TelegramMediaAction(action: .giveawayLaunched(stars: stars))
case let .messageActionGiveawayResults(winners, unclaimed): case let .messageActionGiveawayResults(flags, winners, unclaimed):
return TelegramMediaAction(action: .giveawayResults(winners: winners, unclaimed: unclaimed)) return TelegramMediaAction(action: .giveawayResults(winners: winners, unclaimed: unclaimed, stars: (flags & (1 << 0)) != 0))
case let .messageActionBoostApply(boosts): case let .messageActionBoostApply(boosts):
return TelegramMediaAction(action: .boostsApplied(boosts: boosts)) return TelegramMediaAction(action: .boostsApplied(boosts: boosts))
case let .messageActionPaymentRefunded(_, peer, currency, totalAmount, payload, charge): case let .messageActionPaymentRefunded(_, peer, currency, totalAmount, payload, charge):

View File

@ -279,7 +279,7 @@ private final class ChannelBoostersContextImpl {
var resultBoosts: [ChannelBoostersContext.State.Boost] = [] var resultBoosts: [ChannelBoostersContext.State.Boost] = []
for boost in boosts { for boost in boosts {
switch boost { 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 boostFlags: ChannelBoostersContext.State.Boost.Flags = []
var boostPeer: EnginePeer? var boostPeer: EnginePeer?
if let userId = userId { if let userId = userId {
@ -297,7 +297,7 @@ private final class ChannelBoostersContextImpl {
if (flags & (1 << 3)) != 0 { if (flags & (1 << 3)) != 0 {
boostFlags.insert(.isUnclaimed) 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 { if populateCache {
@ -388,6 +388,7 @@ public final class ChannelBoostersContext {
public var date: Int32 public var date: Int32
public var expires: Int32 public var expires: Int32
public var multiplier: Int32 public var multiplier: Int32
public var stars: Int64?
public var slug: String? public var slug: String?
public var giveawayMessageId: EngineMessage.Id? 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 giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32, currency: String?, amount: Int64?, cryptoCurrency: String?, cryptoAmount: Int64?)
case giveawayLaunched(stars: Int64?) case giveawayLaunched(stars: Int64?)
case joinedChannel case joinedChannel
case giveawayResults(winners: Int32, unclaimed: Int32) case giveawayResults(winners: Int32, unclaimed: Int32, stars: Bool)
case boostsApplied(boosts: Int32) case boostsApplied(boosts: Int32)
case paymentRefunded(peerId: PeerId, currency: String, totalAmount: Int64, payload: Data?, transactionId: String) 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?) 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: case 38:
self = .joinedChannel self = .joinedChannel
case 39: 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: case 40:
self = .boostsApplied(boosts: decoder.decodeInt32ForKey("boosts", orElse: 0)) self = .boostsApplied(boosts: decoder.decodeInt32ForKey("boosts", orElse: 0))
case 41: case 41:
@ -470,10 +470,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} }
case .joinedChannel: case .joinedChannel:
encoder.encodeInt32(38, forKey: "_rawValue") encoder.encodeInt32(38, forKey: "_rawValue")
case let .giveawayResults(winners, unclaimed): case let .giveawayResults(winners, unclaimed, stars):
encoder.encodeInt32(39, forKey: "_rawValue") encoder.encodeInt32(39, forKey: "_rawValue")
encoder.encodeInt32(winners, forKey: "winners") encoder.encodeInt32(winners, forKey: "winners")
encoder.encodeInt32(unclaimed, forKey: "unclaimed") encoder.encodeInt32(unclaimed, forKey: "unclaimed")
encoder.encodeBool(stars, forKey: "stars")
case let .boostsApplied(boosts): case let .boostsApplied(boosts):
encoder.encodeInt32(40, forKey: "_rawValue") encoder.encodeInt32(40, forKey: "_rawValue")
encoder.encodeInt32(boosts, forKey: "boosts") encoder.encodeInt32(boosts, forKey: "boosts")

View File

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

View File

@ -981,7 +981,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
case .joinedChannel: case .joinedChannel:
attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor)
case let .giveawayResults(winners, unclaimed): case let .giveawayResults(winners, unclaimed, _):
if winners == 0 { if winners == 0 {
attributedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) attributedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil }))
} else if unclaimed > 0 { } 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)) attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
} }
if let range = attributedString.string.range(of: "*") { 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)) attributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedString.string))
} }
@ -5967,7 +5967,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
} }
var addedPrivacy = false 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 items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_BotPrivacy, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor) generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, f in }, 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 var didSetup = false
private func setup() { private func setup() {
guard !self.didSetup, let scene = loadCompressedScene(name: "star2", version: sceneVersion) else { 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.scene = scene
self.sceneView.delegate = self self.sceneView.delegate = self
if let component = self.component, let node = scene.rootNode.childNode(withName: "star", recursively: false), let colors = self.updateColors()
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
]
}
}
}
}
if self.animateFrom != nil { if self.animateFrom != nil {
let _ = self.sceneView.snapshot() let _ = self.sceneView.snapshot()
@ -515,10 +539,6 @@ public final class PremiumStarComponent: Component {
return return
} }
// if let material = node.geometry?.materials.first {
// material.metalness.intensity = 0.4
// }
let animation = CABasicAnimation(keyPath: "contentsTransform") let animation = CABasicAnimation(keyPath: "contentsTransform")
animation.fillMode = .forwards animation.fillMode = .forwards
animation.fromValue = NSValue(scnMatrix4: initial) animation.fromValue = NSValue(scnMatrix4: initial)
@ -536,7 +556,13 @@ public final class PremiumStarComponent: Component {
node.geometry?.materials.first?.emission.addAnimation(group, forKey: "shimmer") 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 { guard let scene = self.sceneView.scene, let node = scene.rootNode.childNode(withName: "star", recursively: false) else {
return return
} }
@ -621,7 +647,7 @@ public final class PremiumStarComponent: Component {
} }
var from = node.presentation.eulerAngles 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 from.y = 0.0
} }
node.removeAnimation(forKey: "tapRotate") node.removeAnimation(forKey: "tapRotate")
@ -633,6 +659,7 @@ public final class PremiumStarComponent: Component {
if mirror { if mirror {
toValue *= -1 toValue *= -1
} }
let to = SCNVector3(x: 0.0, y: toValue, z: 0.0) let to = SCNVector3(x: 0.0, y: toValue, z: 0.0)
let distance = rad2deg(to.y - from.y) 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 { func update(component: PremiumStarComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
let previousComponent = self.component
self.component = component self.component = component
self.setup() 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 { if let _ = component.particleColor {
self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor self.sceneView.backgroundColor = component.theme.list.blocksBackgroundColor
} }

View File

@ -110,6 +110,7 @@ private final class SheetContent: CombinedComponent {
let titleString: String let titleString: String
let amountTitle: String let amountTitle: String
let amountPlaceholder: String let amountPlaceholder: String
let amountLabel: String?
let minAmount: Int64? let minAmount: Int64?
let maxAmount: Int64? let maxAmount: Int64?
@ -124,6 +125,7 @@ private final class SheetContent: CombinedComponent {
minAmount = configuration.minWithdrawAmount minAmount = configuration.minWithdrawAmount
maxAmount = status.balances.availableBalance maxAmount = status.balances.availableBalance
amountLabel = nil
case .paidMedia: case .paidMedia:
titleString = environment.strings.Stars_PaidContent_Title titleString = environment.strings.Stars_PaidContent_Title
amountTitle = environment.strings.Stars_PaidContent_AmountTitle amountTitle = environment.strings.Stars_PaidContent_AmountTitle
@ -131,14 +133,22 @@ private final class SheetContent: CombinedComponent {
minAmount = 1 minAmount = 1
maxAmount = configuration.maxPaidMediaAmount 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: case .reaction:
titleString = environment.strings.Stars_SendStars_Title titleString = environment.strings.Stars_SendStars_Title
amountTitle = environment.strings.Stars_SendStars_AmountTitle amountTitle = environment.strings.Stars_SendStars_AmountTitle
amountPlaceholder = environment.strings.Stars_SendStars_AmountPlaceholder amountPlaceholder = environment.strings.Stars_SendStars_AmountPlaceholder
minAmount = 1 minAmount = 1
//TODO:
maxAmount = configuration.maxPaidMediaAmount maxAmount = configuration.maxPaidMediaAmount
amountLabel = nil
} }
let title = title.update( let title = title.update(
@ -264,11 +274,13 @@ private final class SheetContent: CombinedComponent {
component: AnyComponent( component: AnyComponent(
AmountFieldComponent( AmountFieldComponent(
textColor: theme.list.itemPrimaryTextColor, textColor: theme.list.itemPrimaryTextColor,
secondaryColor: theme.list.itemSecondaryTextColor,
placeholderColor: theme.list.itemPlaceholderTextColor, placeholderColor: theme.list.itemPlaceholderTextColor,
value: state.amount, value: state.amount,
minValue: minAmount, minValue: minAmount,
maxValue: maxAmount, maxValue: maxAmount,
placeholderText: amountPlaceholder, placeholderText: amountPlaceholder,
labelText: amountLabel,
amountUpdated: { [weak state] amount in amountUpdated: { [weak state] amount in
state?.amount = amount state?.amount = amount
state?.updated() state?.updated()
@ -576,30 +588,36 @@ private final class AmountFieldComponent: Component {
typealias EnvironmentType = Empty typealias EnvironmentType = Empty
let textColor: UIColor let textColor: UIColor
let secondaryColor: UIColor
let placeholderColor: UIColor let placeholderColor: UIColor
let value: Int64? let value: Int64?
let minValue: Int64? let minValue: Int64?
let maxValue: Int64? let maxValue: Int64?
let placeholderText: String let placeholderText: String
let labelText: String?
let amountUpdated: (Int64?) -> Void let amountUpdated: (Int64?) -> Void
let tag: AnyObject? let tag: AnyObject?
init( init(
textColor: UIColor, textColor: UIColor,
secondaryColor: UIColor,
placeholderColor: UIColor, placeholderColor: UIColor,
value: Int64?, value: Int64?,
minValue: Int64?, minValue: Int64?,
maxValue: Int64?, maxValue: Int64?,
placeholderText: String, placeholderText: String,
labelText: String?,
amountUpdated: @escaping (Int64?) -> Void, amountUpdated: @escaping (Int64?) -> Void,
tag: AnyObject? = nil tag: AnyObject? = nil
) { ) {
self.textColor = textColor self.textColor = textColor
self.secondaryColor = secondaryColor
self.placeholderColor = placeholderColor self.placeholderColor = placeholderColor
self.value = value self.value = value
self.minValue = minValue self.minValue = minValue
self.maxValue = maxValue self.maxValue = maxValue
self.placeholderText = placeholderText self.placeholderText = placeholderText
self.labelText = labelText
self.amountUpdated = amountUpdated self.amountUpdated = amountUpdated
self.tag = tag self.tag = tag
} }
@ -608,6 +626,9 @@ private final class AmountFieldComponent: Component {
if lhs.textColor != rhs.textColor { if lhs.textColor != rhs.textColor {
return false return false
} }
if lhs.secondaryColor != rhs.secondaryColor {
return false
}
if lhs.placeholderColor != rhs.placeholderColor { if lhs.placeholderColor != rhs.placeholderColor {
return false return false
} }
@ -623,6 +644,9 @@ private final class AmountFieldComponent: Component {
if lhs.placeholderText != rhs.placeholderText { if lhs.placeholderText != rhs.placeholderText {
return false return false
} }
if lhs.labelText != rhs.labelText {
return false
}
return true return true
} }
@ -640,6 +664,7 @@ private final class AmountFieldComponent: Component {
private let placeholderView: ComponentView<Empty> private let placeholderView: ComponentView<Empty>
private let iconView: UIImageView private let iconView: UIImageView
private let textField: TextFieldNodeView private let textField: TextFieldNodeView
private let labelView: ComponentView<Empty>
private var component: AmountFieldComponent? private var component: AmountFieldComponent?
private weak var state: EmptyComponentState? private weak var state: EmptyComponentState?
@ -647,6 +672,7 @@ private final class AmountFieldComponent: Component {
override init(frame: CGRect) { override init(frame: CGRect) {
self.placeholderView = ComponentView<Empty>() self.placeholderView = ComponentView<Empty>()
self.textField = TextFieldNodeView(frame: .zero) self.textField = TextFieldNodeView(frame: .zero)
self.labelView = ComponentView<Empty>()
self.iconView = UIImageView(image: UIImage(bundleImageName: "Premium/Stars/StarLarge")) 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 size = CGSize(width: availableSize.width, height: 44.0)
let sideInset: CGFloat = 15.0
var leftInset: CGFloat = 15.0 var leftInset: CGFloat = 15.0
if let icon = self.iconView.image { if let icon = self.iconView.image {
leftInset += icon.size.width + 6.0 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.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 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) self.textField.frame = CGRect(x: leftInset, y: 0.0, width: size.width - 30.0, height: 44.0)
return size return size
@ -808,15 +859,17 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor
private struct StarsWithdrawConfiguration { private struct StarsWithdrawConfiguration {
static var defaultValue: StarsWithdrawConfiguration { static var defaultValue: StarsWithdrawConfiguration {
return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil) return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil, usdWithdrawRate: nil)
} }
let minWithdrawAmount: Int64? let minWithdrawAmount: Int64?
let maxPaidMediaAmount: 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.minWithdrawAmount = minWithdrawAmount
self.maxPaidMediaAmount = maxPaidMediaAmount self.maxPaidMediaAmount = maxPaidMediaAmount
self.usdWithdrawRate = usdWithdrawRate
} }
static func with(appConfiguration: AppConfiguration) -> StarsWithdrawConfiguration { static func with(appConfiguration: AppConfiguration) -> StarsWithdrawConfiguration {
@ -829,8 +882,12 @@ private struct StarsWithdrawConfiguration {
if let value = data["stars_paid_post_amount_max"] as? Double { if let value = data["stars_paid_post_amount_max"] as? Double {
maxPaidMediaAmount = Int64(value) 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 { } else {
return .defaultValue return .defaultValue
} }

View File

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