Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

# Conflicts:
#	.gitignore
This commit is contained in:
Mikhail Filimonov 2025-02-25 12:59:17 +04:00
commit 84a17115fa
221 changed files with 7564 additions and 2298 deletions

1
.gitignore vendored
View file

@ -69,3 +69,4 @@ submodules/OpusBinding/SharedHeaders/*
submodules/FFMpegBinding/SharedHeaders/*
submodules/OpenSSLEncryptionProvider/SharedHeaders/*
submodules/TelegramCore/FlatSerialization/Sources/*
buildServer.json

19
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "sweetpad-lldb",
"request": "attach",
"name": "Attach to running app (SweetPad)",
"preLaunchTask": "sweetpad: launch",
"codelldbAttributes": {
"initCommands": [
"command source ~/.lldbinit-Xcode"
]
}
}
]
}

5
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"sweetpad.build.xcodeWorkspacePath": "Telegram/Telegram.xcodeproj/project.xcworkspace",
"lldb.library": "/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Versions/A/LLDB",
"lldb.launch.expressions": "native"
}

19
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,19 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "sweetpad",
"action": "launch",
"problemMatcher": [
"$sweetpad-watch", // ! Required for debugging
"$sweetpad-xcodebuild-default",
"$sweetpad-xcbeautify-errors",
"$sweetpad-xcbeautify-warnings"
],
"label": "sweetpad: launch",
"detail": "Build and Launch the app",
"isBackground": true // ! Required for debugging
}
]
}

View file

@ -13819,12 +13819,12 @@ Sorry for the inconvenience.";
"GroupInfo.Permissions.ChargeForMessages" = "Charge for Messages";
"GroupInfo.Permissions.ChargeForMessagesInfo" = "If you turn this on, regular members of the group will have to pay Stars to send messages.";
"GroupInfo.Permissions.MessagePrice" = "SET YOUR PRICE PER MESSAGE";
"GroupInfo.Permissions.MessagePriceInfo" = "Your group will receive 85% of the selected fee (%1$@) for each incoming message.";
"GroupInfo.Permissions.MessagePriceInfo" = "Your group will receive %1$@% of the selected fee (%2$@) for each incoming message.";
"Privacy.Messages.ChargeForMessages" = "Charge for Messages";
"Privacy.Messages.ChargeForMessagesInfo" = "Charge a fee for messages from people outide your contacts or those you haven't messaged first.";
"Privacy.Messages.MessagePrice" = "SET YOUR PRICE PER MESSAGE";
"Privacy.Messages.MessagePriceInfo" = "Your will receive 85% of the selected fee (%1$@) for each incoming message.";
"Privacy.Messages.MessagePriceInfo" = "Your will receive %1$@% of the selected fee (%2$@) for each incoming message.";
"Privacy.Messages.RemoveFeeHeader" = "EXCEPTIONS";
"Privacy.Messages.RemoveFee" = "Remove Fee";
@ -13839,9 +13839,15 @@ Sorry for the inconvenience.";
"Notification.PaidMessage.Stars_1" = "%@ Star";
"Notification.PaidMessage.Stars_any" = "%@ Stars";
"Notification.PaidMessage.Messages_1" = "%@ message";
"Notification.PaidMessage.Messages_any" = "%@ messages";
"Notification.PaidMessage" = "%1$@ paid %2$@ to send a message";
"Notification.PaidMessageYou" = "You paid %1$@ to send a message";
"Notification.PaidMessageMany" = "%1$@ paid %2$@ to send %3$@";
"Notification.PaidMessageYouMany" = "You paid %1$@ to send %2$@";
"Stars.Transfer.Terms" = "By purchasing you agree to the [Terms of Service]().";
"Stars.Transfer.Terms_URL" = "https://telegram.org/tos/stars";
@ -13850,10 +13856,31 @@ Sorry for the inconvenience.";
"Settings.Privacy.Messages.ValuePaid" = "Paid";
"Stars.Transaction.PaidMessage_1" = "Fee for %@ Message";
"Stars.Transaction.PaidMessage_anu" = "Fee for %@ Messages";
"Stars.Transaction.PaidMessage_any" = "Fee for %@ Messages";
"Stars.Transaction.PaidMessage.Text" = "You receive **%@%** of the price that you charge for each incoming message. [Change Fee >]()";
"Stars.Transaction.Paid" = "Paid";
"Stars.Intro.Transaction.PaidMessage_1" = "Fee for %@ Message";
"Stars.Intro.Transaction.PaidMessage_any" = "Fee for %@ Messages";
"Stars.Purchase.SendMessageInfo" = "Buy Stars to send a message to **%@**.";
"Stars.Purchase.SendGroupMessageInfo" = "Buy Stars to send a message in **%@**.";
"Gift.Options.Gift.Transfer" = "Transfer";
"Gift.Options.Gift.Filter.MyGifts" = "My Gifts";
"Gift.Options.Premium.OrStars" = "or %@";
"Gift.Send.PayWithStars" = "Pay with %@";
"Gift.Send.PayWithStars.Info" = "Your balance is **%@**. [Get More Stars >]()";
"Chat.PanelCustomStatusShortInfo" = "%@ is a mark for [Premium subscribers >]()";
"Chat.InputTextPaidMessagePlaceholder" = "Message for %@";
"Privacy.Messages.Stars_1" = "%@ Star";
"Privacy.Messages.Stars_any" = "%@ Stars";
"Privacy.Messages.Unlock" = "Unlock with Telegram Premium";
"Premium.PaidMessages" = "Paid Messages";
"Premium.PaidMessagesInfo" = "Charge a fee for messages from non-contacts or new senders.";
"Premium.PaidMessages.Proceed" = "About Telegram Premium";

View file

@ -1116,6 +1116,8 @@ public protocol SharedAccountContext: AnyObject {
func makeMiniAppListScreenInitialData(context: AccountContext) -> Signal<MiniAppListScreenInitialData, NoError>
func makeMiniAppListScreen(context: AccountContext, initialData: MiniAppListScreenInitialData) -> ViewController
func makeIncomingMessagePrivacyScreen(context: AccountContext, value: GlobalPrivacySettings.NonContactChatsPrivacy, exceptions: SelectivePrivacySettings, update: @escaping (GlobalPrivacySettings.NonContactChatsPrivacy) -> Void) -> ViewController
func openWebApp(context: AccountContext, parentController: ViewController, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, botPeer: EnginePeer, chatPeer: EnginePeer?, threadId: Int64?, buttonText: String, url: String, simple: Bool, source: ChatOpenWebViewSource, skipTermsOfService: Bool, payload: String?)
func makeAffiliateProgramSetupScreenInitialData(context: AccountContext, peerId: EnginePeer.Id, mode: AffiliateProgramSetupScreenMode) -> Signal<AffiliateProgramSetupScreenInitialData, NoError>
@ -1351,20 +1353,50 @@ public struct StickersSearchConfiguration {
public struct StarsSubscriptionConfiguration {
static var defaultValue: StarsSubscriptionConfiguration {
return StarsSubscriptionConfiguration(maxFee: 2500, usdWithdrawRate: 1200)
return StarsSubscriptionConfiguration(
maxFee: 2500,
usdWithdrawRate: 1200,
paidMessageMaxAmount: 10000,
paidMessageCommissionPermille: 850,
paidMessagesAvailable: false
)
}
public let maxFee: Int64
public let usdWithdrawRate: Int64
public let paidMessageMaxAmount: Int64
public let paidMessageCommissionPermille: Int32
public let paidMessagesAvailable: Bool
public let maxFee: Int64?
public let usdWithdrawRate: Int64?
fileprivate init(maxFee: Int64?, usdWithdrawRate: Int64?) {
fileprivate init(
maxFee: Int64,
usdWithdrawRate: Int64,
paidMessageMaxAmount: Int64,
paidMessageCommissionPermille: Int32,
paidMessagesAvailable: Bool
) {
self.maxFee = maxFee
self.usdWithdrawRate = usdWithdrawRate
self.paidMessageMaxAmount = paidMessageMaxAmount
self.paidMessageCommissionPermille = paidMessageCommissionPermille
self.paidMessagesAvailable = paidMessagesAvailable
}
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
if let data = appConfiguration.data, let value = data["stars_subscription_amount_max"] as? Double, let usdRate = data["stars_usd_withdraw_rate_x1000"] as? Double {
return StarsSubscriptionConfiguration(maxFee: Int64(value), usdWithdrawRate: Int64(usdRate))
if let data = appConfiguration.data {
let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee
let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate
let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount
let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille
let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable
return StarsSubscriptionConfiguration(
maxFee: maxFee,
usdWithdrawRate: usdWithdrawRate,
paidMessageMaxAmount: paidMessageMaxAmount,
paidMessageCommissionPermille: paidMessageCommissionPermille,
paidMessagesAvailable: paidMessagesAvailable
)
} else {
return .defaultValue
}

View file

@ -142,6 +142,8 @@ public protocol MediaManager: AnyObject {
var universalVideoManager: UniversalVideoManager { get }
var overlayMediaManager: OverlayMediaManager { get }
var currentPictureInPictureNode: AnyObject? { get set }
var globalMediaPlayerState: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> { get }
var musicMediaPlayerState: Signal<(Account, SharedMediaPlayerItemPlaybackStateOrLoading, MediaManagerPlayerType)?, NoError> { get }
var activeGlobalMediaPlayerAccountId: Signal<(AccountRecordId, Bool)?, NoError> { get }

View file

@ -42,6 +42,7 @@ public enum PremiumIntroSource {
case folderTags
case animatedEmoji
case messageEffects
case paidMessages
}
public enum PremiumGiftSource: Equatable {
@ -79,6 +80,7 @@ public enum PremiumDemoSubject {
case folderTags
case business
case messageEffects
case paidMessages
case businessLocation
case businessHours
@ -134,6 +136,7 @@ public enum StarsPurchasePurpose: Equatable {
case unlockMedia(requiredStars: Int64)
case starGift(peerId: EnginePeer.Id, requiredStars: Int64)
case upgradeStarGift(requiredStars: Int64)
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
}
public struct PremiumConfiguration {

View file

@ -76,5 +76,5 @@ public enum ShareControllerSubject {
case image([ImageRepresentationWithReference])
case media(AnyMediaReference, MediaParameters?)
case mapMedia(TelegramMediaMap)
case fromExternal(([PeerId], [PeerId: Int64], String, ShareControllerAccountContext, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
case fromExternal(Int, ([PeerId], [PeerId: Int64], [PeerId: StarsAmount], String, ShareControllerAccountContext, Bool) -> Signal<ShareControllerExternalStatus, ShareControllerError>)
}

View file

@ -35,6 +35,7 @@ swift_library(
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
"//submodules/TelegramUI/Components/TextNodeWithEntities:TextNodeWithEntities",
"//submodules/TelegramUI/Components/Chat/ChatInputTextNode",
"//submodules/AnimatedCountLabelNode",
],
visibility = [
"//visibility:public",

View file

@ -8,6 +8,7 @@ import ContextUI
import ChatPresentationInterfaceState
import ComponentFlow
import AccountContext
import AnimatedCountLabelNode
final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageActionSheetControllerSourceSendButtonNode {
private let strings: PresentationStrings
@ -17,7 +18,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
let sendButton: HighlightTrackingButtonNode
var sendButtonHasApplyIcon = false
var animatingSendButton = false
let textNode: ImmediateTextNode
let textNode: ImmediateAnimatedCountLabelNode
private var theme: PresentationTheme
@ -46,8 +47,7 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
self.backgroundNode.clipsToBounds = true
self.sendButton = HighlightTrackingButtonNode(pointerStyle: nil)
self.textNode = ImmediateTextNode()
self.textNode.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: theme.chat.inputPanel.actionControlForegroundColor)
self.textNode = ImmediateAnimatedCountLabelNode()
self.textNode.isUserInteractionEnabled = false
super.init()
@ -104,8 +104,6 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
func updateTheme(theme: PresentationTheme, wallpaper: TelegramWallpaper) {
self.backgroundNode.backgroundColor = theme.chat.inputPanel.actionControlFillColor
self.textNode.attributedText = NSAttributedString(string: self.strings.MediaPicker_Send, font: Font.semibold(17.0), textColor: theme.chat.inputPanel.actionControlForegroundColor)
}
private var absoluteRect: (CGRect, CGSize)?
@ -113,20 +111,44 @@ final class AttachmentTextInputActionButtonsNode: ASDisplayNode, ChatSendMessage
self.absoluteRect = (rect, containerSize)
}
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, minimized: Bool, interfaceState: ChatPresentationInterfaceState) -> CGSize {
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition, minimized: Bool, text: String, interfaceState: ChatPresentationInterfaceState) -> CGSize {
self.validLayout = size
let width: CGFloat
let textSize = self.textNode.updateLayout(CGSize(width: 100.0, height: 100.0))
var titleOffset: CGFloat = 0.0
var segments: [AnimatedCountLabelNode.Segment] = []
var buttonInset: CGFloat = 18.0
if text.hasPrefix("⭐️") {
let font = Font.with(size: 17.0, design: .round, weight: .semibold, traits: .monospacedNumbers)
let badgeString = NSMutableAttributedString(string: "⭐️ ", font: font, textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor)
if let range = badgeString.string.range(of: "⭐️") {
badgeString.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(interfaceState.theme)!, range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: badgeString.string))
}
segments.append(.text(0, badgeString))
for char in text {
if let intValue = Int(String(char)) {
segments.append(.number(intValue, NSAttributedString(string: String(char), font: font, textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor)))
}
}
titleOffset -= 2.0
buttonInset = 14.0
} else {
segments.append(.text(0, NSAttributedString(string: text, font: Font.semibold(17.0), textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor)))
}
self.textNode.segments = segments
let textSize = self.textNode.updateLayout(size: CGSize(width: 100.0, height: 100.0), animated: transition.isAnimated)
if minimized {
width = 44.0
} else {
width = textSize.width + 36.0
width = textSize.width + buttonInset * 2.0
}
let buttonSize = CGSize(width: width, height: size.height)
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - textSize.width) / 2.0), y: floorToScreenPixels((buttonSize.height - textSize.height) / 2.0)), size: textSize))
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - textSize.width) / 2.0) + titleOffset, y: floorToScreenPixels((buttonSize.height - textSize.height) / 2.0)), size: textSize))
transition.updateAlpha(node: self.textNode, alpha: minimized ? 0.0 : 1.0)
transition.updateAlpha(node: self.sendButton.imageNode, alpha: minimized ? 1.0 : 0.0)

View file

@ -750,7 +750,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
}
self.theme = interfaceState.theme
self.actionButtons.updateTheme(theme: interfaceState.theme, wallpaper: interfaceState.chatWallpaper)
let textFieldMinHeight = calclulateTextFieldMinHeight(interfaceState, metrics: metrics)
@ -957,7 +957,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
var textBackgroundInset: CGFloat = 0.0
let actionButtonsSize: CGSize
if let presentationInterfaceState = self.presentationInterfaceState {
actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: !self.isAttachment || inputHasText, interfaceState: presentationInterfaceState)
let isMinimized: Bool
let text: String
if let sendPaidMessageStars = presentationInterfaceState.sendPaidMessageStars {
isMinimized = false
let count = max(1, presentationInterfaceState.interfaceState.forwardMessageIds?.count ?? 1)
text = "⭐️\(sendPaidMessageStars.value * Int64(count))"
} else {
isMinimized = !self.isAttachment || inputHasText
text = presentationInterfaceState.strings.MediaPicker_Send
}
actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), transition: transition, minimized: isMinimized, text: text, interfaceState: presentationInterfaceState)
textBackgroundInset = 44.0 - actionButtonsSize.width
} else {
actionButtonsSize = CGSize(width: 44.0, height: minimalHeight)

View file

@ -1141,7 +1141,7 @@ public class AttachmentController: ViewController, MinimizableController {
}
let isEffecitvelyCollapsedUpdated = (self.selectionCount > 0) != (self.panel.isSelecting)
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, elevateProgress: !hasPanel && !hasButton, transition: transition)
let panelHeight = self.panel.update(layout: containerLayout, buttons: self.controller?.buttons ?? [], isSelecting: self.selectionCount > 0, selectionCount: self.selectionCount, elevateProgress: !hasPanel && !hasButton, transition: transition)
if hasPanel || hasButton {
containerInsets.bottom = panelHeight
}

View file

@ -824,6 +824,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private var presentationData: PresentationData
private var updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
private var presentationDataDisposable: Disposable?
private var peerDisposable: Disposable?
private var iconDisposables: [MediaId: Disposable] = [:]
@ -852,6 +853,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private var buttons: [AttachmentButtonType] = []
private var selectedIndex: Int = 0
private(set) var isSelecting: Bool = false
private var selectionCount: Int = 0
private var _isButtonVisible: Bool = false
var isButtonVisible: Bool {
return self.mainButtonState.isVisible || self.secondaryButtonState.isVisible
@ -1167,7 +1170,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [],
canMakePaidContent: canMakePaidContent,
currentPrice: currentPrice,
hasTimers: hasTimers
hasTimers: hasTimers,
sendPaidMessageStars: strongSelf.presentationInterfaceState.sendPaidMessageStars
)),
hasEntityKeyboard: hasEntityKeyboard,
gesture: gesture,
@ -1258,14 +1262,35 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.updateChatPresentationInterfaceState({ $0.updatedTheme(presentationData.theme) })
if let layout = strongSelf.validLayout {
let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isSelecting: strongSelf.isSelecting, elevateProgress: strongSelf.elevateProgress, transition: .immediate)
let _ = strongSelf.update(layout: layout, buttons: strongSelf.buttons, isSelecting: strongSelf.isSelecting, selectionCount: strongSelf.selectionCount, elevateProgress: strongSelf.elevateProgress, transition: .immediate)
}
}
}).strict()
if let peerId = chatLocation?.peerId {
self.peerDisposable = ((self.context.account.viewTracker.peerView(peerId)
|> map { view -> StarsAmount? in
if let data = view.cachedData as? CachedUserData {
return data.sendPaidMessageStars
} else if let channel = peerViewMainPeer(view) as? TelegramChannel {
return channel.sendPaidMessageStars
} else {
return nil
}
}
|> distinctUntilChanged
|> deliverOnMainQueue).start(next: { [weak self] amount in
guard let self else {
return
}
self.updateChatPresentationInterfaceState({ $0.updatedSendPaidMessageStars(amount) })
}))
}
}
deinit {
self.presentationDataDisposable?.dispose()
self.peerDisposable?.dispose()
for (_, disposable) in self.iconDisposables {
disposable.dispose()
}
@ -1308,16 +1333,19 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.updateChatPresentationInterfaceState(transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate, f, completion: completion)
}
private func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
private func updateChatPresentationInterfaceState(update: Bool = true, transition: ContainedViewLayoutTransition, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
let presentationInterfaceState = f(self.presentationInterfaceState)
let updateInputTextState = self.presentationInterfaceState.interfaceState.effectiveInputState != presentationInterfaceState.interfaceState.effectiveInputState
self.presentationInterfaceState = presentationInterfaceState
if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated)
self.textUpdated(presentationInterfaceState.interfaceState.effectiveInputState.inputText)
if update {
if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated)
self.textUpdated(presentationInterfaceState.interfaceState.effectiveInputState.inputText)
}
}
}
@ -1672,10 +1700,23 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
}
func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, elevateProgress: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
func update(layout: ContainerViewLayout, buttons: [AttachmentButtonType], isSelecting: Bool, selectionCount: Int, elevateProgress: Bool, transition: ContainedViewLayoutTransition) -> CGFloat {
self.validLayout = layout
self.buttons = buttons
self.elevateProgress = elevateProgress
if selectionCount != self.selectionCount {
self.selectionCount = selectionCount
self.updateChatPresentationInterfaceState(update: false, transition: .immediate, { state in
var selectedMessages: [EngineMessage.Id] = []
for i in 0 ..< selectionCount {
selectedMessages.append(EngineMessage.Id(peerId: PeerId(0), namespace: Namespaces.Message.Local, id: Int32(i)))
}
return state.updatedInterfaceState { state in
return state.withUpdatedForwardMessageIds(selectedMessages)
}
})
}
let isButtonVisibleUpdated = self._isButtonVisible != self.mainButtonState.isVisible
self._isButtonVisible = self.mainButtonState.isVisible

View file

@ -23,6 +23,7 @@ public enum SendMessageActionSheetControllerParams {
public let canMakePaidContent: Bool
public let currentPrice: Int64?
public let hasTimers: Bool
public let sendPaidMessageStars: StarsAmount?
public init(
isScheduledMessages: Bool,
@ -34,7 +35,8 @@ public enum SendMessageActionSheetControllerParams {
forwardMessageIds: [EngineMessage.Id],
canMakePaidContent: Bool,
currentPrice: Int64?,
hasTimers: Bool
hasTimers: Bool,
sendPaidMessageStars: StarsAmount?
) {
self.isScheduledMessages = isScheduledMessages
self.mediaPreview = mediaPreview
@ -46,6 +48,7 @@ public enum SendMessageActionSheetControllerParams {
self.canMakePaidContent = canMakePaidContent
self.currentPrice = currentPrice
self.hasTimers = hasTimers
self.sendPaidMessageStars = sendPaidMessageStars
}
}

View file

@ -455,6 +455,9 @@ final class ChatSendMessageContextScreenComponent: Component {
if sendMessage.hasTimers {
canSchedule = false
}
if let _ = sendMessage.sendPaidMessageStars {
canSchedule = false
}
canMakePaidContent = sendMessage.canMakePaidContent
currentPrice = sendMessage.currentPrice
case .editMessage:

View file

@ -30,6 +30,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
public let textShadowColor: UIColor?
public let textStroke: (UIColor, CGFloat)?
public let highlightColor: UIColor?
public let highlightInset: UIEdgeInsets
public let handleSpoilers: Bool
public let manualVisibilityControl: Bool
public let resetAnimationsOnVisibilityChange: Bool
@ -53,6 +54,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
textShadowColor: UIColor? = nil,
textStroke: (UIColor, CGFloat)? = nil,
highlightColor: UIColor? = nil,
highlightInset: UIEdgeInsets = .zero,
handleSpoilers: Bool = false,
manualVisibilityControl: Bool = false,
resetAnimationsOnVisibilityChange: Bool = false,
@ -75,6 +77,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.textShadowColor = textShadowColor
self.textStroke = textStroke
self.highlightColor = highlightColor
self.highlightInset = highlightInset
self.highlightAction = highlightAction
self.handleSpoilers = handleSpoilers
self.manualVisibilityControl = manualVisibilityControl
@ -144,6 +147,10 @@ public final class MultilineTextWithEntitiesComponent: Component {
return false
}
if lhs.highlightInset != rhs.highlightInset {
return false
}
return true
}
@ -189,6 +196,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.textNode.textShadowColor = component.textShadowColor
self.textNode.textStroke = component.textStroke
self.textNode.linkHighlightColor = component.highlightColor
self.textNode.linkHighlightInset = component.highlightInset
self.textNode.highlightAttributeAction = component.highlightAction
self.textNode.tapAttributeAction = component.tapAction
self.textNode.longTapAttributeAction = component.longTapAction

View file

@ -1793,6 +1793,25 @@ public class ContactsPeerItemNode: ItemListRevealOptionsItemNode {
strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated)
}
if item.isAd {
let adButton: HighlightableButtonNode
if let current = strongSelf.adButton {
adButton = current
} else {
adButton = HighlightableButtonNode()
adButton.setImage(UIImage(bundleImageName: "Components/AdMock"), for: .normal)
strongSelf.addSubnode(adButton)
strongSelf.adButton = adButton
adButton.addTarget(strongSelf, action: #selector(strongSelf.adButtonPressed), forControlEvents: .touchUpInside)
}
adButton.frame = CGRect(origin: CGPoint(x: params.width - 20.0 - 31.0 - 13.0, y: 11.0), size: CGSize(width: 31.0, height: 15.0))
} else if let adButton = strongSelf.adButton {
strongSelf.adButton = nil
adButton.removeFromSupernode()
}
strongSelf.updateEnableGestures()
}
})

View file

@ -1287,7 +1287,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
self.dismiss(forceAway: false)
}
private func dismiss(forceAway: Bool) {
func dismiss(forceAway: Bool) {
var animatedOutNode = true
var animatedOutInterface = false
@ -1340,7 +1340,7 @@ public class GalleryController: ViewController, StandalonePresentableController,
}, pushController: { [weak self] c in
self?.baseNavigationController?.pushViewController(c)
self?.dismiss(forceAway: true)
}, dismissController: { [weak self] in
}, dismissController: { [weak self] in
self?.dismiss(forceAway: true)
}, replaceRootController: { [weak self] controller, ready in
if let strongSelf = self {
@ -1403,32 +1403,51 @@ public class GalleryController: ViewController, StandalonePresentableController,
self?.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.galleryNode.beginCustomDismiss = { [weak self] simpleAnimation in
self.galleryNode.beginCustomDismiss = { [weak self] animationType in
if let strongSelf = self {
strongSelf.actionInteraction?.updateCanReadHistory(true)
strongSelf._hiddenMedia.set(.single(nil))
let animatedOutNode = !simpleAnimation
if let chatController = strongSelf.baseNavigationController?.topViewController as? ChatController {
chatController.updatePushedTransition(0.0, transition: .animated(duration: 0.45, curve: .customSpring(damping: 180.0, initialVelocity: 0.0)))
if let hiddenMediaManagerIndex = strongSelf.hiddenMediaManagerIndex {
strongSelf.hiddenMediaManagerIndex = nil
strongSelf.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
}
strongSelf.galleryNode.animateOut(animateContent: animatedOutNode, completion: {
})
switch animationType {
case .default, .simpleAnimation:
let animatedOutNode = animationType != .simpleAnimation
if let chatController = strongSelf.baseNavigationController?.topViewController as? ChatController {
chatController.updatePushedTransition(0.0, transition: .animated(duration: 0.45, curve: .customSpring(damping: 180.0, initialVelocity: 0.0)))
}
strongSelf.galleryNode.animateOut(animateContent: animatedOutNode, completion: {
})
case .pip:
break
}
}
}
self.galleryNode.completeCustomDismiss = { [weak self] isPictureInPicture in
guard let self else {
return
}
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex {
self.hiddenMediaManagerIndex = nil
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
}
if isPictureInPicture {
if let chatController = self?.baseNavigationController?.topViewController as? ChatController {
if let chatController = self.baseNavigationController?.topViewController as? ChatController {
chatController.updatePushedTransition(0.0, transition: .animated(duration: 0.45, curve: .customSpring(damping: 180.0, initialVelocity: 0.0)))
}
} else {
self?._hiddenMedia.set(.single(nil))
self._hiddenMedia.set(.single(nil))
}
self?.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
self.galleryNode.controlsVisibilityChanged = { [weak self] visible in
@ -1666,6 +1685,22 @@ public class GalleryController: ViewController, StandalonePresentableController,
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex {
self.hiddenMediaManagerIndex = nil
self.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
}
let context = self.context
let mediaManager = context.sharedContext.mediaManager
self.hiddenMediaManagerIndex = mediaManager.galleryHiddenMediaManager.addSource(self._hiddenMedia.get()
|> map { messageIdAndMedia in
if let (messageId, media) = messageIdAndMedia {
return .chat(context.account.id, messageId, media)
} else {
return nil
}
})
var nodeAnimatesItself = false
if let centralItemNode = self.galleryNode.pager.centralItemNode() {
@ -1851,4 +1886,23 @@ public class GalleryController: ViewController, StandalonePresentableController,
keyShortcuts.append(contentsOf: itemNodeShortcuts)
return keyShortcuts
}
public static func maybeExpandPIP(context: AccountContext, messageId: EngineMessage.Id) -> Bool {
guard let currentPictureInPictureNode = context.sharedContext.mediaManager.currentPictureInPictureNode as? UniversalVideoGalleryItemNode else {
return false
}
guard let item = currentPictureInPictureNode.item else {
return false
}
guard case let .message(message, _) = item.contentInfo else {
return false
}
if message.id != messageId {
return false
}
currentPictureInPictureNode.expandPIP()
return true
}
}

View file

@ -8,6 +8,12 @@ import AccountContext
import UndoUI
open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGestureRecognizerDelegate {
public enum CustomDismissType {
case `default`
case simpleAnimation
case pip
}
private let context: AccountContext
public var statusBar: StatusBar?
@ -27,7 +33,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
public var scrollView: UIScrollView
public var pager: GalleryPagerNode
public var beginCustomDismiss: (Bool) -> Void = { _ in }
public var beginCustomDismiss: (GalleryControllerNode.CustomDismissType) -> Void = { _ in }
public var completeCustomDismiss: (Bool) -> Void = { _ in }
public var baseNavigationController: () -> NavigationController? = { return nil }
public var galleryController: () -> ViewController? = { return nil }
@ -128,9 +134,9 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
}
}
self.pager.beginCustomDismiss = { [weak self] simpleAnimation in
self.pager.beginCustomDismiss = { [weak self] animationType in
if let strongSelf = self {
strongSelf.beginCustomDismiss(simpleAnimation)
strongSelf.beginCustomDismiss(animationType)
}
}

View file

@ -30,7 +30,7 @@ open class GalleryItemNode: ASDisplayNode {
public var controlsVisibility: () -> Bool = { return true }
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
public var dismiss: () -> Void = { }
public var beginCustomDismiss: (Bool) -> Void = { _ in }
public var beginCustomDismiss: (GalleryControllerNode.CustomDismissType) -> Void = { _ in }
public var completeCustomDismiss: (Bool) -> Void = { _ in }
public var baseNavigationController: () -> NavigationController? = { return nil }
public var galleryController: () -> ViewController? = { return nil }

View file

@ -117,7 +117,7 @@ public final class GalleryPagerNode: ASDisplayNode, ASScrollViewDelegate, ASGest
public var controlsVisibility: () -> Bool = { return true }
public var updateOrientation: (UIInterfaceOrientation) -> Void = { _ in }
public var dismiss: () -> Void = { }
public var beginCustomDismiss: (Bool) -> Void = { _ in }
public var beginCustomDismiss: (GalleryControllerNode.CustomDismissType) -> Void = { _ in }
public var completeCustomDismiss: (Bool) -> Void = { _ in }
public var baseNavigationController: () -> NavigationController? = { return nil }
public var galleryController: () -> ViewController? = { return nil }

View file

@ -368,6 +368,86 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(), size: statusSize))
}
private func setupImageRecognition(_ generate: @escaping (TransformImageArguments) -> DrawingContext?, dimensions: PixelDimensions) {
guard let message = self.message, !message.isCopyProtected() && message.paidContent == nil else {
return
}
let displaySize = dimensions.cgSize.fitted(CGSize(width: 1280.0, height: 1280.0)).dividedByScreenScale().integralFloor
self.recognitionDisposable.set((recognizedContent(context: self.context, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self {
strongSelf.recognizedContentNode?.removeFromSupernode()
if !results.isEmpty {
let size = strongSelf.imageNode.bounds.size
let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
}
}, performAction: { [weak self] string, action in
guard let strongSelf = self else {
return
}
switch action {
case .copy:
UIPasteboard.general.string = string
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
controller.present(tooltipController, in: .window(.root))
}
case .share:
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
controller.present(shareController, in: .window(.root))
}
case .lookup:
let controller = UIReferenceLibraryViewController(term: string)
if let window = strongSelf.baseNavigationController()?.view.window {
controller.popoverPresentationController?.sourceView = window
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
window.rootViewController?.present(controller, animated: true)
}
case .speak:
if let speechHolder = speakText(context: strongSelf.context, text: string) {
speechHolder.completion = { [weak self, weak speechHolder] in
if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder {
strongSelf.currentSpeechHolder = nil
}
}
strongSelf.currentSpeechHolder = speechHolder
}
case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil)
controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c)
}
controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root))
}
parentController.present(controller, in: .window(.root))
}
}
})
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
guard let strongSelf = self, let message = strongSelf.message else {
return
}
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
}
recognizedContentNode.alpha = 0.0
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
strongSelf.imageNode.addSubnode(recognizedContentNode)
strongSelf.recognizedContentNode = recognizedContentNode
strongSelf.recognitionOverlayContentNode.transitionIn()
}
}
}))
}
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool, isSecret: Bool) {
self.message = message
self.translateToLanguage = translateToLanguage
@ -392,83 +472,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
case .medium, .full:
strongSelf.statusNodeContainer.isHidden = true
Queue.concurrentDefaultQueue().async {
if let message = strongSelf.message, !message.isCopyProtected() && !imageReference.media.flags.contains(.hasStickers) && message.paidContent == nil {
strongSelf.recognitionDisposable.set((recognizedContent(context: strongSelf.context, image: { return generate(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))?.generateImage() }, messageId: message.id)
|> deliverOnMainQueue).start(next: { [weak self] results in
if let strongSelf = self {
strongSelf.recognizedContentNode?.removeFromSupernode()
if !results.isEmpty {
let size = strongSelf.imageNode.bounds.size
let recognizedContentNode = RecognizedContentContainer(size: size, recognitions: results, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, present: { [weak self] c, a in
if let strongSelf = self {
strongSelf.galleryController()?.presentInGlobalOverlay(c, with: a)
}
}, performAction: { [weak self] string, action in
guard let strongSelf = self else {
return
}
switch action {
case .copy:
UIPasteboard.general.string = string
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with({ $0 })
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .copy(text: presentationData.strings.Conversation_TextCopied), elevatedLayout: true, animateInAsReplacement: false, action: { _ in return false })
controller.present(tooltipController, in: .window(.root))
}
case .share:
if let controller = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let shareController = ShareController(context: strongSelf.context, subject: .text(string), externalShare: true, immediateExternalShare: false, updatedPresentationData: (strongSelf.context.sharedContext.currentPresentationData.with({ $0 }), strongSelf.context.sharedContext.presentationData))
controller.present(shareController, in: .window(.root))
}
case .lookup:
let controller = UIReferenceLibraryViewController(term: string)
if let window = strongSelf.baseNavigationController()?.view.window {
controller.popoverPresentationController?.sourceView = window
controller.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0))
window.rootViewController?.present(controller, animated: true)
}
case .speak:
if let speechHolder = speakText(context: strongSelf.context, text: string) {
speechHolder.completion = { [weak self, weak speechHolder] in
if let strongSelf = self, strongSelf.currentSpeechHolder == speechHolder {
strongSelf.currentSpeechHolder = nil
}
}
strongSelf.currentSpeechHolder = speechHolder
}
case .translate:
if let parentController = strongSelf.baseNavigationController()?.topViewController as? ViewController {
let controller = TranslateScreen(context: strongSelf.context, text: string, canCopy: true, fromLanguage: nil)
controller.pushController = { [weak parentController] c in
(parentController?.navigationController as? NavigationController)?._keepModalDismissProgress = true
parentController?.push(c)
}
controller.presentController = { [weak parentController] c in
parentController?.present(c, in: .window(.root))
}
parentController.present(controller, in: .window(.root))
}
}
})
recognizedContentNode.barcodeAction = { [weak self] payload, rect in
guard let strongSelf = self, let message = strongSelf.message else {
return
}
strongSelf.footerContentNode.openActionOptions?(.url(url: payload, concealed: true), message)
}
recognizedContentNode.alpha = 0.0
recognizedContentNode.frame = CGRect(origin: CGPoint(), size: size)
recognizedContentNode.update(size: strongSelf.imageNode.bounds.size, transition: .immediate)
strongSelf.imageNode.addSubnode(recognizedContentNode)
strongSelf.recognizedContentNode = recognizedContentNode
strongSelf.recognitionOverlayContentNode.transitionIn()
}
}
}))
if !imageReference.media.flags.contains(.hasStickers) {
Queue.concurrentDefaultQueue().async {
strongSelf.setupImageRecognition(generate, dimensions: largestSize.dimensions)
}
}
case .none, .blurred:
strongSelf.statusNodeContainer.isHidden = false
}
@ -682,7 +690,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
if let message = self.message {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
if let self, let peer, let navigationController = self.baseNavigationController() {
self.beginCustomDismiss(true)
self.beginCustomDismiss(.simpleAnimation)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false)))
@ -714,7 +722,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
if let peer, let message = self.message, canSendMessagesToPeer(peer._asPeer()) {
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
if let self, let navigationController = self.baseNavigationController() {
self.beginCustomDismiss(true)
self.beginCustomDismiss(.simpleAnimation)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: true)))
@ -819,6 +827,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
largestSize = PixelDimensions(width: largestSize.height, height: largestSize.width)
}
}
self.imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets()))()
/*if largestSize.width > 2600 || largestSize.height > 2600 {
@ -833,7 +842,18 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
strongSelf.updateImageFromFile(path: data.path)
}))
} else {*/
self.imageNode.setSignal(chatMessageImageFile(account: context.account, userLocation: userLocation, fileReference: fileReference, thumbnail: false), dispatchOnDisplayLink: false)
let signal = chatMessageImageFile(account: context.account, userLocation: userLocation, fileReference: fileReference, thumbnail: false)
|> afterNext({ [weak self] generate in
guard let self else {
return
}
Queue.concurrentDefaultQueue().async {
self.setupImageRecognition(generate, dimensions: largestSize)
}
})
self.imageNode.setSignal(signal, dispatchOnDisplayLink: false)
//}
self.zoomableContent = (largestSize.cgSize, self.imageNode)

View file

@ -730,309 +730,6 @@ final class SettingsHeaderButton: HighlightableButtonNode {
}
}
@available(iOS 15.0, *)
private final class PictureInPictureContentImpl: NSObject, PictureInPictureContent, AVPictureInPictureControllerDelegate {
private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
private let node: UniversalVideoNode
private var statusDisposable: Disposable?
private var status: MediaPlayerStatus?
weak var pictureInPictureController: AVPictureInPictureController?
private var previousIsPlaying = false
init(node: UniversalVideoNode) {
self.node = node
super.init()
var invalidatedStateOnce = false
self.statusDisposable = (self.node.status
|> deliverOnMainQueue).start(next: { [weak self] status in
guard let strongSelf = self else {
return
}
strongSelf.status = status
if let status {
let isPlaying = status.status == .playing
if !invalidatedStateOnce {
invalidatedStateOnce = true
strongSelf.pictureInPictureController?.invalidatePlaybackState()
} else if strongSelf.previousIsPlaying != isPlaying {
strongSelf.previousIsPlaying = isPlaying
strongSelf.pictureInPictureController?.invalidatePlaybackState()
}
}
}).strict()
}
deinit {
self.statusDisposable?.dispose()
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
self.node.togglePlayPause()
}
public func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
guard let status = self.status else {
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)))
}
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration - status.timestamp, preferredTimescale: CMTimeScale(30.0)))
}
public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
guard let status = self.status else {
return false
}
switch status.status {
case .playing:
return false
case .buffering, .paused:
return true
}
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
let node = self.node
let _ = (self.node.status
|> take(1)
|> deliverOnMainQueue).start(next: { [weak node] status in
if let node = node, let timestamp = status?.timestamp, let duration = status?.duration {
let nextTimestamp = timestamp + skipInterval.seconds
if nextTimestamp > duration {
node.seek(0.0)
node.pause()
} else {
node.seek(min(duration, nextTimestamp))
}
}
completionHandler()
})
}
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
return false
}
}
private weak var overlayController: OverlayMediaController?
private weak var mediaManager: MediaManager?
private var pictureInPictureController: AVPictureInPictureController?
private var contentDelegate: PlaybackDelegate?
private let node: UniversalVideoNode
private let willBegin: (PictureInPictureContentImpl) -> Void
private let didEnd: (PictureInPictureContentImpl) -> Void
private let expand: (@escaping () -> Void) -> Void
private var pictureInPictureTimer: SwiftSignalKit.Timer?
private var didExpand: Bool = false
private var hiddenMediaManagerIndex: Int?
private var messageRemovedDisposable: Disposable?
private var isNativePictureInPictureActiveDisposable: Disposable?
init(context: AccountContext, overlayController: OverlayMediaController, mediaManager: MediaManager, accountId: AccountRecordId, hiddenMedia: (MessageId, Media)?, videoNode: UniversalVideoNode, canSkip: Bool, willBegin: @escaping (PictureInPictureContentImpl) -> Void, didEnd: @escaping (PictureInPictureContentImpl) -> Void, expand: @escaping (@escaping () -> Void) -> Void) {
self.overlayController = overlayController
self.mediaManager = mediaManager
self.node = videoNode
self.willBegin = willBegin
self.didEnd = didEnd
self.expand = expand
self.node.setCanPlaybackWithoutHierarchy(true)
super.init()
if let videoLayer = videoNode.getVideoLayer() {
let contentDelegate = PlaybackDelegate(node: self.node)
self.contentDelegate = contentDelegate
let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: videoLayer, playbackDelegate: contentDelegate))
self.pictureInPictureController = pictureInPictureController
contentDelegate.pictureInPictureController = pictureInPictureController
pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = false
pictureInPictureController.requiresLinearPlayback = !canSkip
pictureInPictureController.delegate = self
self.pictureInPictureController = pictureInPictureController
let timer = SwiftSignalKit.Timer(timeout: 0.005, repeat: true, completion: { [weak self] in
guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else {
return
}
if pictureInPictureController.isPictureInPicturePossible {
strongSelf.pictureInPictureTimer?.invalidate()
strongSelf.pictureInPictureTimer = nil
pictureInPictureController.startPictureInPicture()
}
}, queue: .mainQueue())
self.pictureInPictureTimer = timer
timer.start()
} else {
var currentIsNativePictureInPictureActive = false
self.isNativePictureInPictureActiveDisposable = (videoNode.isNativePictureInPictureActive
|> deliverOnMainQueue).startStrict(next: { [weak self] isNativePictureInPictureActive in
guard let self else {
return
}
if currentIsNativePictureInPictureActive == isNativePictureInPictureActive {
return
}
currentIsNativePictureInPictureActive = isNativePictureInPictureActive
if isNativePictureInPictureActive {
Queue.mainQueue().after(0.0, { [weak self] in
guard let self else {
return
}
self.willBegin(self)
if let overlayController = self.overlayController {
overlayController.setPictureInPictureContentHidden(content: self, isHidden: true)
}
self.didEnd(self)
})
} else {
self.expand { [weak self] in
guard let self else {
return
}
self.didExpand = true
if let overlayController = self.overlayController {
overlayController.setPictureInPictureContentHidden(content: self, isHidden: false)
self.node.alpha = 0.02
}
guard let overlayController = self.overlayController else {
return
}
overlayController.removePictureInPictureContent(content: self)
self.node.canAttachContent = false
if self.didExpand {
return
}
self.node.continuePlayingWithoutSound()
}
}
})
let _ = videoNode.enterNativePictureInPicture()
}
if let hiddenMedia = hiddenMedia {
self.hiddenMediaManagerIndex = mediaManager.galleryHiddenMediaManager.addSource(Signal<(MessageId, Media)?, NoError>.single(hiddenMedia)
|> map { messageIdAndMedia in
if let (messageId, media) = messageIdAndMedia {
return .chat(accountId, messageId, media)
} else {
return nil
}
})
}
if let (messageId, _) = hiddenMedia {
self.messageRemovedDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: messageId))
|> map { message -> Bool in
if let _ = message {
return false
} else {
return true
}
}
|> filter { $0 }
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] _ in
guard let strongSelf = self else {
return
}
overlayController.removePictureInPictureContent(content: strongSelf)
strongSelf.node.canAttachContent = false
})
}
}
deinit {
self.messageRemovedDisposable?.dispose()
self.isNativePictureInPictureActiveDisposable?.dispose()
self.pictureInPictureTimer?.invalidate()
self.node.setCanPlaybackWithoutHierarchy(false)
if let hiddenMediaManagerIndex = self.hiddenMediaManagerIndex, let mediaManager = self.mediaManager {
mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaManagerIndex)
}
}
func invalidatePlaybackState() {
self.pictureInPictureController?.invalidatePlaybackState()
}
var videoNode: ASDisplayNode {
return self.node
}
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
Queue.mainQueue().after(0.1, { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.willBegin(strongSelf)
if let overlayController = strongSelf.overlayController {
overlayController.setPictureInPictureContentHidden(content: strongSelf, isHidden: true)
}
})
}
public func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.didEnd(self)
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) {
print(error)
}
public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
}
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
guard let overlayController = self.overlayController else {
return
}
overlayController.removePictureInPictureContent(content: self)
self.node.canAttachContent = false
if self.didExpand {
return
}
self.node.continuePlayingWithoutSound()
}
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
self.expand { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.didExpand = true
if let overlayController = strongSelf.overlayController {
overlayController.setPictureInPictureContentHidden(content: strongSelf, isHidden: false)
strongSelf.node.alpha = 0.02
}
completionHandler(true)
}
}
}
@available(iOS 15.0, *)
private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPictureControllerDelegate {
private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
@ -1079,7 +776,7 @@ private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPict
guard let status = self.status else {
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)))
}
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration - status.timestamp, preferredTimescale: CMTimeScale(30.0)))
return CMTimeRange(start: CMTime(seconds: 0.0, preferredTimescale: CMTimeScale(30.0)), duration: CMTime(seconds: status.duration, preferredTimescale: CMTimeScale(30.0)))
}
public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
@ -1230,13 +927,18 @@ private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPict
}
func invalidatePlaybackState() {
self.pictureInPictureController?.invalidatePlaybackState()
guard let pictureInPictureController = self.pictureInPictureController else {
return
}
if pictureInPictureController.isPictureInPictureActive {
pictureInPictureController.invalidatePlaybackState()
}
}
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
self.node.setCanPlaybackWithoutHierarchy(true)
if let hiddenMedia = self.hiddenMedia, let mediaManager = self.mediaManager {
if let hiddenMedia = self.hiddenMedia, let mediaManager = self.mediaManager, !"".isEmpty {
let accountId = self.accountId
self.hiddenMediaManagerIndex = mediaManager.galleryHiddenMediaManager.addSource(Signal<(MessageId, Media)?, NoError>.single(hiddenMedia)
|> map { messageIdAndMedia in
@ -1282,6 +984,14 @@ private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPict
completionHandler(true)
}
}
public func requestExpand() {
self.pictureInPictureController?.stopPictureInPicture()
}
public func stop() {
self.pictureInPictureController?.stopPictureInPicture()
}
}
final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
@ -1331,7 +1041,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
private var requiresDownload = false
private var item: UniversalVideoGalleryItem?
private(set) var item: UniversalVideoGalleryItem?
private var playbackRate: Double?
private var videoQuality: UniversalVideoContentVideoQuality = .auto
private let playbackRatePromise = ValuePromise<Double>()
@ -2180,8 +1890,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let strongSelf = self, !isAnimated {
if #available(iOS 15.0, *) {
if let pictureInPictureContent = strongSelf.pictureInPictureContent as? PictureInPictureContentImpl {
pictureInPictureContent.invalidatePlaybackState()
if let nativePictureInPictureContent = strongSelf.nativePictureInPictureContent as? NativePictureInPictureContentImpl {
nativePictureInPictureContent.invalidatePlaybackState()
}
}
@ -2358,6 +2068,10 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if hadPreviousValue {
videoNode.canAttachContent = isVisible
if isVisible {
if let currentPictureInPictureNode = self.context.sharedContext.mediaManager.currentPictureInPictureNode as? UniversalVideoGalleryItemNode, let currentItem = currentPictureInPictureNode.item, case let .message(currentMessage, _) = currentItem.contentInfo, case let .message(message, _) = item.contentInfo, currentMessage.id == message.id {
self.skipInitialPause = true
}
if self.skipInitialPause {
self.skipInitialPause = false
} else {
@ -2397,7 +2111,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
override func activateAsInitial() {
if let videoNode = self.videoNode, self.isCentral == true {
if let videoNode = self.videoNode, self.isCentral == true, !self.initiallyActivated {
self.initiallyActivated = true
var isAnimated = false
@ -3117,7 +2831,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
}
if customUnembedWhenPortrait(overlayNode) {
self.beginCustomDismiss(false)
self.beginCustomDismiss(.default)
self.statusNode.isHidden = true
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
self?.completeCustomDismiss(false)
@ -3181,9 +2895,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
guard let self, let controller = self.galleryController(), let navigationController = self.baseNavigationController() else {
return
}
self.activePictureInPictureNavigationController = navigationController
self.activePictureInPictureController = controller
self.context.sharedContext.mediaManager.currentPictureInPictureNode = self
self.beginCustomDismiss(.pip)
controller.view.alpha = 0.0
controller.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, completion: { [weak self] _ in
self?.completeCustomDismiss(true)
@ -3201,6 +2918,11 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
guard let self else {
return
}
if self.context.sharedContext.mediaManager.currentPictureInPictureNode === self {
self.context.sharedContext.mediaManager.currentPictureInPictureNode = nil
}
if let videoNode = self.videoNode {
videoNode.setNativePictureInPictureIsActive(false)
}
@ -3227,6 +2949,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
activePictureInPictureNavigationController.currentWindow?.present(activePictureInPictureController, on: .root, blockInteraction: false, completion: {
})
activePictureInPictureController.presentationArguments = previousPresentationArguments
self.updateControlsVisibility(false)
activePictureInPictureController.view.alpha = 1.0
activePictureInPictureController.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.35, completion: { _ in
@ -3240,6 +2963,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
}
@objc func pictureInPictureButtonPressed() {
if let currentPictureInPictureNode = self.context.sharedContext.mediaManager.currentPictureInPictureNode as? UniversalVideoGalleryItemNode, let currentItem = currentPictureInPictureNode.item, case let .message(currentMessage, _) = currentItem.contentInfo, case let .message(message, _) = self.item?.contentInfo, currentMessage.id == message.id {
if let controller = self.galleryController() as? GalleryController {
controller.dismiss(forceAway: true)
}
return
}
if #available(iOS 15.0, *) {
if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl {
addAppLogEvent(postbox: self.context.account.postbox, type: "pip_btn", peerId: self.context.account.peerId)
@ -3247,197 +2977,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return
}
}
var isNativePictureInPictureSupported = false
switch self.item?.contentInfo {
case let .message(message, _):
for media in message.media {
if let media = media as? TelegramMediaFile, media.isVideo {
if message.id.namespace == Namespaces.Message.Cloud {
isNativePictureInPictureSupported = true
}
}
}
default:
break
}
if let item = self.item, let videoNode = self.videoNode, let overlayController = self.context.sharedContext.mediaManager.overlayMediaManager.controller {
videoNode.setContinuePlayingWithoutSoundOnLostAudioSession(false)
let context = self.context
let baseNavigationController = self.baseNavigationController()
let playbackRate = self.playbackRate
if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported(), isNativePictureInPictureSupported {
self.disablePictureInPicturePlaceholder = true
let overlayVideoNode = UniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: self.context.sharedContext.mediaManager.audioSession, manager: self.context.sharedContext.mediaManager.universalVideoManager, decoration: GalleryVideoDecoration(), content: item.content, priority: .overlay)
let absoluteRect = videoNode.view.convert(videoNode.view.bounds, to: nil)
overlayVideoNode.frame = absoluteRect
overlayVideoNode.updateLayout(size: absoluteRect.size, transition: .immediate)
overlayVideoNode.canAttachContent = true
var hiddenMedia: (MessageId, Media)? = nil
switch item.contentInfo {
case let .message(message, _):
for media in message.media {
if let media = media as? TelegramMediaImage {
hiddenMedia = (message.id, media)
} else if let media = media as? TelegramMediaFile, media.isVideo {
hiddenMedia = (message.id, media)
}
}
default:
break
}
let content = PictureInPictureContentImpl(context: self.context, overlayController: overlayController, mediaManager: self.context.sharedContext.mediaManager, accountId: self.context.account.id, hiddenMedia: hiddenMedia, videoNode: overlayVideoNode, canSkip: true, willBegin: { [weak self] content in
guard let strongSelf = self else {
return
}
strongSelf.beginCustomDismiss(true)
}, didEnd: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.completeCustomDismiss(false)
}, expand: { [weak baseNavigationController] completion in
guard let contentInfo = item.contentInfo else {
return
}
switch contentInfo {
case let .message(message, _):
let gallery = GalleryController(context: context, source: .peerMessagesAtId(messageId: message.id, chatLocation: .peer(id: message.id.peerId), customTag: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil)), playbackRate: playbackRate, replaceRootController: { [weak baseNavigationController] controller, ready in
if let baseNavigationController = baseNavigationController {
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
}
}, baseNavigationController: baseNavigationController)
gallery.temporaryDoNotWaitForReady = true
gallery.useSimpleAnimation = true
baseNavigationController?.view.endEditing(true)
(baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { id, media in
return nil
}))
gallery.onDidAppear = {
completion()
}
case .webPage:
break
}
})
self.pictureInPictureContent = content
self.context.sharedContext.mediaManager.overlayMediaManager.controller?.setPictureInPictureContent(content: content, absoluteRect: absoluteRect)
} else {
let context = self.context
let baseNavigationController = self.baseNavigationController()
let mediaManager = self.context.sharedContext.mediaManager
var expandImpl: (() -> Void)?
let shouldBeDismissed: Signal<Bool, NoError>
if let contentInfo = item.contentInfo, case let .message(message, _) = contentInfo {
shouldBeDismissed = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: message.id))
|> map { message -> Bool in
if let _ = message {
return false
} else {
return true
}
}
|> distinctUntilChanged
} else {
shouldBeDismissed = .single(false)
}
let overlayNode = OverlayUniversalVideoNode(context: self.context, postbox: self.context.account.postbox, audioSession: context.sharedContext.mediaManager.audioSession, manager: context.sharedContext.mediaManager.universalVideoManager, content: item.content, shouldBeDismissed: shouldBeDismissed, expand: {
expandImpl?()
}, close: { [weak mediaManager] in
mediaManager?.setOverlayVideoNode(nil)
})
let playbackRate = self.playbackRate
expandImpl = { [weak overlayNode] in
guard let contentInfo = item.contentInfo, let overlayNode = overlayNode else {
return
}
switch contentInfo {
case let .message(message, messageIndex):
let source: GalleryControllerItemSource
if let _ = message.paidContent {
source = .standaloneMessage(message, messageIndex)
} else {
source = .peerMessagesAtId(messageId: message.id, chatLocation: .peer(id: message.id.peerId), customTag: nil, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>(value: nil))
}
let gallery = GalleryController(context: context, source: source, playbackRate: playbackRate, replaceRootController: { controller, ready in
if let baseNavigationController = baseNavigationController {
baseNavigationController.replaceTopController(controller, animated: false, ready: ready)
}
}, baseNavigationController: baseNavigationController)
gallery.temporaryDoNotWaitForReady = true
baseNavigationController?.view.endEditing(true)
(baseNavigationController?.topViewController as? ViewController)?.present(gallery, in: .window(.root), with: GalleryControllerPresentationArguments(transitionArguments: { [weak overlayNode] id, media in
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
return (overlayNode?.view.snapshotContentTree(), nil)
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
guard let context = context, let overlayNode = overlayNode else {
return
}
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
overlaySupernode?.view.addSubview(view)
}
overlayNode.canAttachContent = false
})
} else if let info = context.sharedContext.mediaManager.galleryHiddenMediaManager.findTarget(messageId: id, media: media) {
return GalleryTransitionArguments(transitionNode: (info.1, info.1.bounds, {
return info.2()
}), addToTransitionSurface: info.0)
}
return nil
}))
case let .webPage(_, _, expandFromPip):
if let expandFromPip = expandFromPip, let baseNavigationController = baseNavigationController {
expandFromPip({ [weak overlayNode] in
if let overlayNode = overlayNode, let overlaySupernode = overlayNode.supernode {
return GalleryTransitionArguments(transitionNode: (overlayNode, overlayNode.bounds, { [weak overlayNode] in
return (overlayNode?.view.snapshotContentTree(), nil)
}), addToTransitionSurface: { [weak context, weak overlaySupernode, weak overlayNode] view in
guard let context = context, let overlayNode = overlayNode else {
return
}
if context.sharedContext.mediaManager.hasOverlayVideoNode(overlayNode) {
overlaySupernode?.view.addSubview(view)
}
overlayNode.canAttachContent = false
})
}
return nil
}, baseNavigationController, { [weak baseNavigationController] c, a in
(baseNavigationController?.topViewController as? ViewController)?.present(c, in: .window(.root), with: a)
})
}
}
}
context.sharedContext.mediaManager.setOverlayVideoNode(overlayNode)
if overlayNode.supernode != nil {
self.beginCustomDismiss(false)
self.statusNode.isHidden = true
self.animateOut(toOverlay: overlayNode, completion: { [weak self] in
self?.completeCustomDismiss(false)
})
}
}
func expandPIP() {
if #available(iOS 15.0, *) {
if let nativePictureInPictureContent = self.nativePictureInPictureContent as? NativePictureInPictureContentImpl {
nativePictureInPictureContent.requestExpand()
}
}
}
@ -3997,7 +3542,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
return
}
if let navigationController = strongSelf.baseNavigationController() {
strongSelf.beginCustomDismiss(true)
strongSelf.beginCustomDismiss(.simpleAnimation)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false)))
@ -4050,7 +3595,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if let peer, let (message, _, _) = strongSelf.contentInfo(), canSendMessagesToPeer(peer._asPeer()) {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuReply, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reply"), color: theme.contextMenu.primaryColor)}, action: { [weak self] _, f in
if let self, let navigationController = self.baseNavigationController() {
self.beginCustomDismiss(true)
self.beginCustomDismiss(.simpleAnimation)
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), subject: .message(id: .id(message.id), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: true)))

View file

@ -144,7 +144,7 @@ final class GameControllerNode: ViewControllerTracingNode {
if eventName == "share_game" || eventName == "share_score" {
if let (botPeer, gameName) = self.shareData(), let addressName = botPeer.addressName, !addressName.isEmpty, !gameName.isEmpty {
if eventName == "share_score" {
self.present(ShareController(context: self.context, subject: .fromExternal({ [weak self] peerIds, threadIds, text, account, _ in
self.present(ShareController(context: self.context, subject: .fromExternal(1, { [weak self] peerIds, threadIds, requireStars, text, account, _ in
if let strongSelf = self, let message = strongSelf.message, let account = account as? ShareControllerAppAccountContext {
let signals = peerIds.map { TelegramEngine(account: account.context.account).messages.forwardGameWithScore(messageId: message.id, to: $0, threadId: threadIds[$0], as: nil) }
return .single(.preparing(false))

View file

@ -493,13 +493,10 @@ private func inviteLinkEditControllerEntries(invite: ExportedInvitation?, state:
if state.subscriptionEnabled {
var label: String = ""
if let subscriptionFee = state.subscriptionFee, subscriptionFee > StarsAmount.zero {
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
label = presentationData.strings.InviteLink_Create_FeePerMonth("\(formatTonUsdValue(subscriptionFee.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))").string
}
entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, configuration.maxFee.flatMap({ StarsAmount(value: $0, nanos: 0) })))
entries.append(.subscriptionFee(presentationData.theme, presentationData.strings.InviteLink_Create_FeePlaceholder, isEditingEnabled, state.subscriptionFee, label, StarsAmount(value: configuration.maxFee, nanos: 0)))
}
let infoText: String
if let _ = invite, state.subscriptionEnabled {

View file

@ -594,10 +594,7 @@ public final class InviteLinkViewController: ViewController {
guard let peer else {
return
}
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
let subscriptionController = context.sharedContext.makeStarsSubscriptionScreen(context: context, peer: peer, pricing: pricing, importer: importer, usdRate: usdRate)
self?.controller?.push(subscriptionController)
})
@ -834,11 +831,8 @@ public final class InviteLinkViewController: ViewController {
context.account.postbox.loadedPeerWithId(adminId)
) |> deliverOnMainQueue).start(next: { [weak self] presentationData, state, requestsState, creatorPeer in
if let strongSelf = self {
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
var entries: [InviteLinkViewEntry] = []
entries.append(.link(presentationData.theme, invite))

View file

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

View file

@ -47,6 +47,8 @@
@property (nonatomic, readonly) bool inhibitEditing;
@property (nonatomic, assign) int64_t sendPaidMessageStars;
+ (instancetype)contextForCaptionsOnly;
- (SSignal *)imageSignalForItem:(NSObject<TGMediaEditableItem> *)item;

View file

@ -17,6 +17,13 @@
@end
@protocol TGPhotoSendStarsButtonView <NSObject>
- (void)updateFrame:(CGRect)frame;
- (CGSize)updateCount:(int64_t)count;
@end
@protocol TGCaptionPanelView <NSObject>
@ -122,7 +129,7 @@
@property (nonatomic, copy) void (^ _Nullable editCover)(CGSize dimensions, void(^_Nonnull completion)(UIImage * _Nonnull));
- (UIView<TGPhotoSendStarsButtonView> *_Nonnull)sendStarsButtonAction:(void(^_Nonnull)(void))action;
- (UIView<TGPhotoSolidRoundedButtonView> *_Nonnull)solidRoundedButton:(NSString *_Nonnull)title action:(void(^_Nonnull)(void))action;
- (id<TGPhotoDrawingAdapter> _Nonnull)drawingAdapter:(CGSize)size originalSize:(CGSize)originalSize isVideo:(bool)isVideo isAvatar:(bool)isAvatar entitiesView:(UIView<TGPhotoDrawingEntitiesView> * _Nullable)entitiesView;

View file

@ -2,6 +2,8 @@
#import <LegacyComponents/LegacyComponentsContext.h>
@protocol TGPhotoPaintStickersContext;
typedef NS_OPTIONS(NSUInteger, TGPhotoEditorTab) {
TGPhotoEditorNoneTab = 0,
TGPhotoEditorCropTab = 1 << 0,
@ -53,7 +55,9 @@ typedef enum
@property (nonatomic, assign) TGPhotoEditorBackButton backButtonType;
@property (nonatomic, assign) TGPhotoEditorDoneButton doneButtonType;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground;
@property (nonatomic, assign) int64_t sendPaidMessageStars;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext;
- (void)transitionInAnimated:(bool)animated;
- (void)transitionInAnimated:(bool)animated transparent:(bool)transparent;

View file

@ -429,13 +429,13 @@
TGPhotoEditorDoneButton doneButton = isScheduledMessages ? TGPhotoEditorDoneButtonSchedule : TGPhotoEditorDoneButtonSend;
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false];
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false stickersContext:editingContext.sendPaidMessageStars > 0 ? stickersContext : nil];
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
_portraitToolbarView.donePressed = toolbarDonePressed;
_portraitToolbarView.doneLongPressed = toolbarDoneLongPressed;
[_wrapperView addSubview:_portraitToolbarView];
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false];
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false stickersContext:nil];
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
_landscapeToolbarView.donePressed = toolbarDonePressed;
_landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed;
@ -1227,6 +1227,10 @@
if (_ignoreSelectionUpdates)
return;
NSUInteger finalCount = MAX(1, selectedCount);
_portraitToolbarView.sendPaidMessageStars = (_editingContext.sendPaidMessageStars * finalCount);
[_portraitToolbarView setNeedsLayout];
if (counterVisible)
{
bool animateCount = animated && !(counterVisible && _photoCounterButton.internalHidden);

View file

@ -293,7 +293,7 @@
TGPhotoEditorBackButton backButton = TGPhotoEditorBackButtonCancel;
TGPhotoEditorDoneButton doneButton = TGPhotoEditorDoneButtonCheck;
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true];
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true stickersContext:nil];
[_portraitToolbarView setToolbarTabs:_availableTabs animated:false];
[_portraitToolbarView setActiveTab:_currentTab];
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
@ -302,7 +302,7 @@
_portraitToolbarView.tabPressed = toolbarTabPressed;
[_wrapperView addSubview:_portraitToolbarView];
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true];
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:backButton doneButton:doneButton solidBackground:true stickersContext:nil];
[_landscapeToolbarView setToolbarTabs:_availableTabs animated:false];
[_landscapeToolbarView setActiveTab:_currentTab];
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;

View file

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

View file

@ -10,6 +10,8 @@
#import "TGMediaAssetsController.h"
#import "TGPhotoPaintStickersContext.h"
@interface TGPhotoToolbarView ()
{
id<LegacyComponentsContext> _context;
@ -19,6 +21,7 @@
UIView *_buttonsWrapperView;
TGModernButton *_cancelButton;
TGModernButton *_doneButton;
UIView<TGPhotoSendStarsButtonView> *_starsDoneButton;
UILabel *_infoLabel;
@ -32,7 +35,7 @@
@implementation TGPhotoToolbarView
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context backButton:(TGPhotoEditorBackButton)backButton doneButton:(TGPhotoEditorDoneButton)doneButton solidBackground:(bool)solidBackground stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext
{
self = [super initWithFrame:CGRectZero];
if (self != nil)
@ -56,12 +59,24 @@
[_cancelButton addTarget:self action:@selector(cancelButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_backgroundView addSubview:_cancelButton];
_doneButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, buttonSize.width, buttonSize.height)];
_doneButton.exclusiveTouch = true;
_doneButton.adjustsImageWhenHighlighted = false;
[self setDoneButtonType:doneButton];
[_doneButton addTarget:self action:@selector(doneButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_backgroundView addSubview:_doneButton];
if (stickersContext != nil) {
__weak TGPhotoToolbarView *weakSelf = self;
_starsDoneButton = [stickersContext sendStarsButtonAction:^{
__strong TGPhotoToolbarView *strongSelf = weakSelf;
if (strongSelf == nil)
return;
[strongSelf doneButtonPressed];
}];
_starsDoneButton.exclusiveTouch = true;
[_backgroundView addSubview:_starsDoneButton];
} else {
_doneButton = [[TGModernButton alloc] initWithFrame:CGRectMake(0, 0, buttonSize.width, buttonSize.height)];
_doneButton.exclusiveTouch = true;
_doneButton.adjustsImageWhenHighlighted = false;
[self setDoneButtonType:doneButton];
[_doneButton addTarget:self action:@selector(doneButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[_backgroundView addSubview:_doneButton];
}
_longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(doneButtonLongPressed:)];
_longPressGestureRecognizer.minimumPressDuration = 0.4;
@ -704,6 +719,11 @@
if (_doneButton.frame.size.width > 49.0f)
offset = 60.0f;
if (_starsDoneButton != nil) {
CGSize buttonSize = [_starsDoneButton updateCount:_sendPaidMessageStars];
[_starsDoneButton updateFrame:CGRectMake(self.frame.size.width - buttonSize.width - 2.0, 49.0f - offset + 2.0f, buttonSize.width, buttonSize.height)];
}
_doneButton.frame = CGRectMake(self.frame.size.width - offset, 49.0f - offset, _doneButton.frame.size.width, _doneButton.frame.size.height);
_infoLabel.frame = CGRectMake(49.0f + 10.0f, 0.0f, self.frame.size.width - (49.0f + 10.0f) * 2.0f, 49.0f);

View file

@ -30,6 +30,7 @@ swift_library(
"//submodules/DrawingUI:DrawingUI",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/TelegramUI/Components/MediaEditor",
"//submodules/AnimatedCountLabelNode",
],
visibility = [
"//visibility:public",

View file

@ -11,6 +11,8 @@ import StickerResources
import SolidRoundedButtonNode
import MediaEditor
import DrawingUI
import TelegramPresentationData
import AnimatedCountLabelNode
protocol LegacyPaintEntity {
var position: CGPoint { get }
@ -607,12 +609,109 @@ public final class LegacyPaintStickersContext: NSObject, TGPhotoPaintStickersCon
return button
}
public func sendStarsButtonAction(_ action: @escaping () -> Void) -> any UIView & TGPhotoSendStarsButtonView {
let button = SendStarsButtonView()
button.pressed = action
return button
}
public func drawingEntitiesView(with size: CGSize) -> UIView & TGPhotoDrawingEntitiesView {
let view = DrawingEntitiesView(context: self.context, size: size)
return view
}
}
private class SendStarsButtonView: HighlightTrackingButton, TGPhotoSendStarsButtonView {
private let backgroundView: UIView
private let textNode: ImmediateAnimatedCountLabelNode
fileprivate var pressed: (() -> Void)?
override init(frame: CGRect) {
self.backgroundView = UIView()
self.textNode = ImmediateAnimatedCountLabelNode()
self.textNode.isUserInteractionEnabled = false
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.addSubview(self.textNode.view)
self.highligthedChanged = { [weak self] highlighted in
guard let self else {
return
}
if highlighted {
self.backgroundView.layer.removeAnimation(forKey: "opacity")
self.backgroundView.alpha = 0.4
self.textNode.layer.removeAnimation(forKey: "opacity")
self.textNode.alpha = 0.4
} else {
self.backgroundView.alpha = 1.0
self.backgroundView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
self.textNode.alpha = 1.0
self.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
}
}
}
required init?(coder: NSCoder) {
preconditionFailure()
}
func updateFrame(_ frame: CGRect) {
let transition: ContainedViewLayoutTransition
if self.frame.width.isZero {
transition = .immediate
} else {
transition = .animated(duration: 0.4, curve: .spring)
}
transition.updateFrame(view: self, frame: frame)
}
func updateCount(_ count: Int64) -> CGSize {
let text = "\(count)"
let transition: ContainedViewLayoutTransition
if self.backgroundView.frame.width.isZero {
transition = .immediate
} else {
transition = .animated(duration: 0.4, curve: .spring)
}
var segments: [AnimatedCountLabelNode.Segment] = []
let font = Font.with(size: 17.0, design: .round, weight: .semibold, traits: .monospacedNumbers)
let badgeString = NSMutableAttributedString(string: "⭐️ ", font: font, textColor: .white)
if let range = badgeString.string.range(of: "⭐️") {
badgeString.addAttribute(.attachment, value: PresentationResourcesChat.chatPlaceholderStarIcon(defaultDarkPresentationTheme)!, range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: badgeString.string))
}
segments.append(.text(0, badgeString))
for char in text {
if let intValue = Int(String(char)) {
segments.append(.number(intValue, NSAttributedString(string: String(char), font: font, textColor: .white)))
}
}
self.textNode.segments = segments
let buttonInset: CGFloat = 14.0
let textSize = self.textNode.updateLayout(size: CGSize(width: 100.0, height: 100.0), animated: transition.isAnimated)
let width = textSize.width + buttonInset * 2.0
let buttonSize = CGSize(width: width, height: 45.0)
let titleOffset: CGFloat = 0.0
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - textSize.width) / 2.0) + titleOffset, y: floorToScreenPixels((buttonSize.height - textSize.height) / 2.0)), size: textSize))
let backgroundSize = CGSize(width: width - 11.0, height: 33.0)
transition.updateFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((width - backgroundSize.width) / 2.0), y: floorToScreenPixels((buttonSize.height - backgroundSize.height) / 2.0)), size: backgroundSize))
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
self.backgroundView.backgroundColor = UIColor(rgb: 0x007aff)
return buttonSize;
}
}
//Xcode 16
#if canImport(ContactProvider)
extension SolidRoundedButtonView: @retroactive TGPhotoSolidRoundedButtonView {

View file

@ -211,7 +211,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
public var openAvatarEditor: () -> Void = {}
private var completed = false
public var legacyCompletion: (_ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _ in }
public var legacyCompletion: (_ fromGallery: Bool, _ signals: [Any], _ silently: Bool, _ scheduleTime: Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void = { _, _, _, _, _, _, _ in }
public var requestAttachmentMenuExpansion: () -> Void = { }
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
@ -1294,7 +1294,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
}
fileprivate func send(asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, parameters: ChatSendMessageActionSheetController.SendParameters?, completion: @escaping () -> Void) {
fileprivate func send(fromGallery: Bool = false, asFile: Bool = false, silently: Bool, scheduleTime: Int32?, animated: Bool, parameters: ChatSendMessageActionSheetController.SendParameters?, completion: @escaping () -> Void) {
guard let controller = self.controller, !controller.completed else {
return
}
@ -1334,7 +1334,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
return
}
controller.completed = true
controller.legacyCompletion(signals, silently, scheduleTime, parameters, { [weak self] identifier in
controller.legacyCompletion(fromGallery, signals, silently, scheduleTime, parameters, { [weak self] identifier in
return !asFile ? self?.getItemSnapshot(identifier) : nil
}, { [weak self] in
completion()
@ -1834,6 +1834,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
paidMediaAllowed: Bool = false,
subject: Subject,
forCollage: Bool = false,
sendPaidMessageStars: Int64? = nil,
editingContext: TGMediaEditingContext? = nil,
selectionContext: TGMediaSelectionContext? = nil,
saveEditedPhotos: Bool = false,
@ -2114,7 +2115,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.controllerNode.send(silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
strongSelf.controllerNode.send(fromGallery: currentItem != nil, silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
}
}, schedule: { [weak self] parameters in
if let strongSelf = self {
@ -2129,6 +2130,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext())
self.interaction?.selectionState?.grouping = true
self.interaction?.editingState.sendPaidMessageStars = sendPaidMessageStars ?? 0
if case let .media(media) = self.subject {
for item in media {
selectionContext.setItem(item.asset, selected: true)

View file

@ -429,10 +429,6 @@ public final class FFMpegFileReader {
if let stream = self.stream, Int(packet.streamIndex) == stream.info.index {
let packetPts = packet.pts
/*if let focusedPart = self.focusedPart, packetPts >= focusedPart.endPts.value {
self.hasReadToEnd = true
}*/
let pts = CMTimeMake(value: packetPts, timescale: stream.info.timeScale)
let dts = CMTimeMake(value: packet.dts, timescale: stream.info.timeScale)
@ -442,7 +438,7 @@ public final class FFMpegFileReader {
if frameDuration != 0 {
duration = CMTimeMake(value: frameDuration * stream.info.timeBase, timescale: stream.info.timeScale)
} else {
duration = stream.info.fps
duration = CMTimeConvertScale(CMTimeMakeWithSeconds(1.0 / stream.info.fps.seconds, preferredTimescale: stream.info.timeScale), timescale: stream.info.timeScale, method: .quickTime)
}
let frame = MediaTrackDecodableFrame(type: .video, packet: packet, pts: pts, dts: dts, duration: duration)

View file

@ -93,7 +93,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
case chargeForMessagesInfo(PresentationTheme, String)
case messagePriceHeader(PresentationTheme, String)
case messagePrice(PresentationTheme, StarsAmount, String)
case messagePrice(PresentationTheme, Int64, Int64, String)
case messagePriceInfo(PresentationTheme, String)
case unrestrictBoostersSwitch(PresentationTheme, String, Bool)
@ -241,8 +241,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
} else {
return false
}
case let .messagePrice(lhsTheme, lhsValue, lhsPrice):
if case let .messagePrice(rhsTheme, rhsValue, rhsPrice) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsPrice == rhsPrice {
case let .messagePrice(lhsTheme, lhsValue, lhsMaxValue, lhsPrice):
if case let .messagePrice(rhsTheme, rhsValue, rhsMaxValue, rhsPrice) = rhs, lhsTheme === rhsTheme, lhsValue == rhsValue, lhsMaxValue == rhsMaxValue, lhsPrice == rhsPrice {
return true
} else {
return false
@ -424,8 +424,8 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(value), sectionId: self.section)
case let .messagePriceHeader(_, value):
return ItemListSectionHeaderItem(presentationData: presentationData, text: value, sectionId: self.section)
case let .messagePrice(_, value, price):
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, minValue: 10, maxValue: 9000, value: value.value, price: price, sectionId: self.section, updated: { value in
case let .messagePrice(_, value, maxValue, price):
return MessagePriceItem(theme: presentationData.theme, strings: presentationData.strings, isEnabled: true, minValue: 1, maxValue: maxValue, value: value, price: price, sectionId: self.section, updated: { value in
arguments.updateStarsAmount(StarsAmount(value: value, nanos: 0))
})
case let .messagePriceInfo(_, value):
@ -720,23 +720,22 @@ private func channelPermissionsControllerEntries(context: AccountContext, presen
entries.append(.conversionInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_BroadcastConvertInfo(presentationStringsFormattedNumber(participantsLimit, presentationData.dateTimeFormat.groupingSeparator)).string))
}
let chargeEnabled = state.modifiedStarsAmount != nil
entries.append(.chargeForMessages(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessages, chargeEnabled))
entries.append(.chargeForMessagesInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessagesInfo))
if chargeEnabled {
var price: String = ""
if let amount = state.modifiedStarsAmount {
var usdRate = 0.012
if let usdWithdrawRate = configuration.usdWithdrawRate {
usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
}
price = "\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
if cachedData.flags.contains(.paidMessagesAvailable) && channel.hasPermission(.banMembers) {
let sendPaidMessageStars = state.modifiedStarsAmount?.value ?? (cachedData.sendPaidMessageStars?.value ?? 0)
let chargeEnabled = sendPaidMessageStars > 0
entries.append(.chargeForMessages(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessages, chargeEnabled))
entries.append(.chargeForMessagesInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_ChargeForMessagesInfo))
if chargeEnabled {
var price: String = ""
let usdRate = Double(configuration.usdWithdrawRate) / 1000.0 / 100.0
price = "\(formatTonUsdValue(sendPaidMessageStars, divide: false, rate: usdRate, dateTimeFormat: presentationData.dateTimeFormat))"
entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice))
entries.append(.messagePrice(presentationData.theme, sendPaidMessageStars, configuration.paidMessageMaxAmount, price))
entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo("\(configuration.paidMessageCommissionPermille / 10)", price).string))
}
entries.append(.messagePriceHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePrice))
entries.append(.messagePrice(presentationData.theme, state.modifiedStarsAmount ?? StarsAmount(value: 4000, nanos: 0), price))
entries.append(.messagePriceInfo(presentationData.theme, presentationData.strings.GroupInfo_Permissions_MessagePriceInfo(price).string))
}
let canSendText = !effectiveRightsFlags.contains(.banSendText)
@ -876,6 +875,9 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
let updateUnrestrictBoostersDisposable = MetaDisposable()
actionsDisposable.add(updateUnrestrictBoostersDisposable)
let updateSendPaidMessageStarsDisposable = MetaDisposable()
actionsDisposable.add(updateSendPaidMessageStarsDisposable)
let peerView = Promise<PeerView>()
peerView.set(sourcePeerId.get()
|> mapToSignal(context.account.viewTracker.peerView))
@ -1252,6 +1254,17 @@ public func channelPermissionsController(context: AccountContext, updatedPresent
state.modifiedStarsAmount = value
return state
}
let _ = (peerView.get()
|> take(1)
|> deliverOnMainQueue).start(next: { view in
var effectiveValue = value
if value?.value == 0 {
effectiveValue = nil
}
updateSendPaidMessageStarsDisposable.set((context.engine.peers.updateChannelPaidMessagesStars(peerId: view.peerId, stars: effectiveValue)
|> deliverOnMainQueue).start())
})
}, toggleIsOptionExpanded: { flags in
updateState { state in
var state = state

View file

@ -451,29 +451,48 @@ private func chatMessageImageFileThumbnailDatas(account: Account, userLocation:
private func chatMessageVideoDatas(postbox: Postbox, userLocation: MediaResourceUserLocation, customUserContentType: MediaResourceUserContentType? = nil, fileReference: FileMediaReference, previewSourceFileReference: FileMediaReference?, thumbnailSize: Bool = false, onlyFullSize: Bool = false, useLargeThumbnail: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false, forceThumbnail: Bool = false) -> Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError> {
let fullSizeResource = fileReference.media.resource
var reducedSizeResource: MediaResource?
if let previewSourceFileReference, let videoThumbnail = previewSourceFileReference.media.videoThumbnails.first {
reducedSizeResource = videoThumbnail.resource
} else if let videoThumbnail = fileReference.media.videoThumbnails.first {
if let videoThumbnail = fileReference.media.videoThumbnails.first {
reducedSizeResource = videoThumbnail.resource
}
var previewSourceFullSizeResource: MediaResource?
if let previewSourceFileReference {
previewSourceFullSizeResource = previewSourceFileReference.media.resource
}
var thumbnailRepresentation: TelegramMediaImageRepresentation?
if let previewSourceFileReference {
thumbnailRepresentation = useLargeThumbnail ? largestImageRepresentation(previewSourceFileReference.media.previewRepresentations) : smallestImageRepresentation(previewSourceFileReference.media.previewRepresentations)
}
if thumbnailRepresentation == nil {
thumbnailRepresentation = useLargeThumbnail ? largestImageRepresentation(fileReference.media.previewRepresentations) : smallestImageRepresentation(fileReference.media.previewRepresentations)
}
let thumbnailResource = thumbnailRepresentation?.resource
let maybePreviewSourceFullSize: Signal<MediaResourceData, NoError>
if let previewSourceFullSizeResource {
maybePreviewSourceFullSize = postbox.mediaBox.cachedResourceRepresentation(previewSourceFullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
} else {
maybePreviewSourceFullSize = .single(MediaResourceData(path: "", offset: 0, size: 0, complete: false))
}
let maybeFullSize = postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: false, attemptSynchronously: synchronousLoad)
let fetchedFullSize = postbox.mediaBox.cachedResourceRepresentation(fullSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: true, attemptSynchronously: synchronousLoad)
var fetchedReducedSize: Signal<MediaResourceData, NoError> = .single(MediaResourceData(path: "", offset: 0, size: 0, complete: false))
if let reducedSizeResource = reducedSizeResource {
fetchedReducedSize = postbox.mediaBox.cachedResourceRepresentation(reducedSizeResource, representation: thumbnailSize ? CachedScaledVideoFirstFrameRepresentation(size: CGSize(width: 160.0, height: 160.0)) : CachedVideoFirstFrameRepresentation(), complete: false, fetch: true, attemptSynchronously: synchronousLoad)
}
let signal = maybeFullSize
let signal = combineLatest(
maybePreviewSourceFullSize,
maybeFullSize
)
|> map { maybePreviewSourceFullSize, maybeFullSize -> MediaResourceData in
if maybePreviewSourceFullSize.complete {
return maybePreviewSourceFullSize
} else {
return maybeFullSize
}
}
|> take(1)
|> mapToSignal { maybeData -> Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError> in
if maybeData.complete && !forceThumbnail {

View file

@ -1,6 +1,6 @@
import Foundation
public struct MediaId: Hashable, PostboxCoding, CustomStringConvertible, Codable {
public struct MediaId: Hashable, Comparable, PostboxCoding, CustomStringConvertible, Codable {
public typealias Namespace = Int32
public typealias Id = Int64
@ -39,6 +39,14 @@ public struct MediaId: Hashable, PostboxCoding, CustomStringConvertible, Codable
encoder.encodeInt64(self.id, forKey: "i")
}
public static func <(lhs: MediaId, rhs: MediaId) -> Bool {
if lhs.namespace != rhs.namespace {
return lhs.namespace < rhs.namespace
} else {
return lhs.id < rhs.id
}
}
public func encodeToBuffer(_ buffer: WriteBuffer) {
var namespace = self.namespace
var id = self.id

View file

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

View file

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

View file

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

View file

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

View file

@ -75,8 +75,9 @@ public final class SelectablePeerNode: ASDisplayNode {
private let avatarSelectionNode: ASImageNode
private let avatarNodeContainer: ASDisplayNode
private let avatarNode: AvatarNode
private var avatarBadgeBackground: UIImageView?
private var avatarBadgeOutline: UIImageView?
private var avatarBadge: UIImageView?
private var avatarBadgeLabel: ImmediateTextView?
private let onlineNode: PeerOnlineMarkerNode
private var checkNode: CheckNode?
private let textNode: ImmediateTextNode
@ -149,7 +150,7 @@ public final class SelectablePeerNode: ASDisplayNode {
}
}
public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
public func setup(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
self.setup(
accountPeerId: context.account.peerId,
postbox: context.account.postbox,
@ -165,6 +166,7 @@ public final class SelectablePeerNode: ASDisplayNode {
strings: strings,
peer: peer,
requiresPremiumForMessaging: requiresPremiumForMessaging,
requiresStars: requiresStars,
customTitle: customTitle,
iconId: iconId,
iconColor: iconColor,
@ -184,7 +186,7 @@ public final class SelectablePeerNode: ASDisplayNode {
self.avatarNode.playRepostAnimation()
}
public func setup(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
public func setup(accountPeerId: EnginePeer.Id, postbox: Postbox, network: Network, energyUsageSettings: EnergyUsageSettings, contentSettings: ContentSettings, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer, resolveInlineStickers: @escaping ([Int64]) -> Signal<[Int64: TelegramMediaFile], NoError>, theme: PresentationTheme, strings: PresentationStrings, peer: EngineRenderedPeer, requiresPremiumForMessaging: Bool, requiresStars: Int64? = nil, customTitle: String? = nil, iconId: Int64? = nil, iconColor: Int32? = nil, online: Bool = false, numberOfLines: Int = 2, synchronousLoad: Bool) {
let isFirstTime = self.peer == nil
self.peer = peer
guard let mainPeer = peer.chatMainPeer else {
@ -223,16 +225,68 @@ public final class SelectablePeerNode: ASDisplayNode {
self.textNode.attributedText = NSAttributedString(string: customTitle ?? text, font: textFont, textColor: self.currentSelected ? self.theme.selectedTextColor : defaultColor, paragraphAlignment: .center)
self.avatarNode.setPeer(accountPeerId: accountPeerId, postbox: postbox, network: network, contentSettings: contentSettings, theme: theme, peer: mainPeer, overrideImage: overrideImage, emptyColor: self.theme.avatarPlaceholderColor, clipStyle: isForum ? .roundedRect : .round, synchronousLoad: synchronousLoad)
if requiresPremiumForMessaging {
let avatarBadgeBackground: UIImageView
if let current = self.avatarBadgeBackground {
avatarBadgeBackground = current
if let requiresStars {
let avatarBadgeOutline: UIImageView
if let current = self.avatarBadgeOutline {
avatarBadgeOutline = current
} else {
avatarBadgeBackground = UIImageView()
avatarBadgeBackground.image = PresentationResourcesChatList.shareAvatarPremiumLockBadgeBackground(theme)
avatarBadgeBackground.tintColor = theme.chatList.itemBackgroundColor
self.avatarBadgeBackground = avatarBadgeBackground
self.avatarNode.view.addSubview(avatarBadgeBackground)
avatarBadgeOutline = UIImageView()
avatarBadgeOutline.contentMode = .scaleToFill
avatarBadgeOutline.image = PresentationResourcesChatList.shareAvatarStarsLockBadgeBackground(theme)
avatarBadgeOutline.tintColor = theme.actionSheet.opaqueItemBackgroundColor
self.avatarBadgeOutline = avatarBadgeOutline
self.avatarNodeContainer.view.addSubview(avatarBadgeOutline)
}
let avatarBadge: UIImageView
if let current = self.avatarBadge {
avatarBadge = current
} else {
avatarBadge = UIImageView()
avatarBadge.contentMode = .scaleToFill
avatarBadge.image = PresentationResourcesChatList.shareAvatarStarsLockBadgeInnerBackground(theme)
avatarBadge.tintColor = theme.actionSheet.controlAccentColor
self.avatarBadge = avatarBadge
self.avatarNodeContainer.view.addSubview(avatarBadge)
}
let avatarBadgeLabel: ImmediateTextView
if let current = self.avatarBadgeLabel {
avatarBadgeLabel = current
} else {
avatarBadgeLabel = ImmediateTextView()
self.avatarBadgeLabel = avatarBadgeLabel
self.avatarNodeContainer.view.addSubview(avatarBadgeLabel)
}
let badgeString = NSMutableAttributedString(string: "⭐️\(presentationStringsFormattedNumber(Int32(requiresStars), " "))", font: Font.with(size: 9.0, design: .round , weight: .bold), textColor: theme.list.itemCheckColors.foregroundColor)
if let range = badgeString.string.range(of: "⭐️") {
badgeString.addAttribute(.attachment, value: UIImage(bundleImageName: "Premium/SendStarsPeerBadgeStarIcon")!, range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: badgeString.string))
badgeString.addAttribute(.kern, value: -0.8, range: NSRange(badgeString.string.startIndex ..< badgeString.string.endIndex, in: badgeString.string))
}
avatarBadgeLabel.attributedText = badgeString
let avatarFrame = self.avatarNode.frame
let badgeSize = avatarBadgeLabel.updateLayout(avatarFrame.size)
var badgeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((avatarFrame.width - badgeSize.width) / 2.0) - (self.currentSelected ? 15.0 : 0.0), y: avatarFrame.height - 13.0), size: badgeSize)
let badgeBackgroundFrame = CGRect(origin: CGPoint(x: badgeFrame.minX - 2.0, y: badgeFrame.minY - 3.0 - UIScreenPixel), size: CGSize(width: badgeFrame.width + 4.0, height: 16.0))
let badgeOutlineFrame = CGRect(origin: CGPoint(x: badgeBackgroundFrame.minX - 2.0, y: badgeBackgroundFrame.minY - 2.0), size: CGSize(width: badgeBackgroundFrame.width + 4.0, height: 20.0))
badgeFrame = badgeFrame.offsetBy(dx: -2.0, dy: 0.0)
avatarBadge.frame = badgeBackgroundFrame
avatarBadgeOutline.frame = badgeOutlineFrame
avatarBadgeLabel.frame = badgeFrame
} else if requiresPremiumForMessaging {
let avatarBadgeOutline: UIImageView
if let current = self.avatarBadgeOutline {
avatarBadgeOutline = current
} else {
avatarBadgeOutline = UIImageView()
avatarBadgeOutline.image = PresentationResourcesChatList.shareAvatarPremiumLockBadgeBackground(theme)
avatarBadgeOutline.tintColor = theme.chatList.itemBackgroundColor
self.avatarBadgeOutline = avatarBadgeOutline
self.avatarNode.view.addSubview(avatarBadgeOutline)
}
let avatarBadge: UIImageView
@ -247,19 +301,23 @@ public final class SelectablePeerNode: ASDisplayNode {
let avatarFrame = self.avatarNode.frame
let badgeFrame = CGRect(origin: CGPoint(x: avatarFrame.width - 20.0, y: avatarFrame.height - 20.0), size: CGSize(width: 20.0, height: 20.0))
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -2.0 + UIScreenPixel, dy: -2.0 + UIScreenPixel)
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -2.0, dy: -2.0)
avatarBadgeBackground.frame = badgeBackgroundFrame
avatarBadgeOutline.frame = badgeBackgroundFrame
avatarBadge.frame = badgeFrame
} else {
if let avatarBadgeBackground = self.avatarBadgeBackground {
self.avatarBadgeBackground = nil
avatarBadgeBackground.removeFromSuperview()
if let avatarBadgeOutline = self.avatarBadgeOutline {
self.avatarBadgeOutline = nil
avatarBadgeOutline.removeFromSuperview()
}
if let avatarBadge = self.avatarBadge {
self.avatarBadge = nil
avatarBadge.removeFromSuperview()
}
if let avatarBadgeLabel = self.avatarBadgeLabel {
self.avatarBadgeLabel = nil
avatarBadgeLabel.removeFromSuperview()
}
}
let onlineLayout = self.onlineNode.asyncLayout()
@ -340,6 +398,19 @@ public final class SelectablePeerNode: ASDisplayNode {
context.fillEllipse(in: bounds.insetBy(dx: 2.0, dy: 2.0))
}
})
if let avatarBadgeLabel = self.avatarBadgeLabel, let avatarBadge = self.avatarBadge, let avatarBadgeOutline = self.avatarBadgeOutline {
avatarBadgeLabel.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 17.0, y: avatarBadgeLabel.center.y)
avatarBadge.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 15.0, y: avatarBadge.center.y)
avatarBadgeOutline.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 15.0, y: avatarBadgeOutline.center.y)
if animated {
avatarBadgeLabel.layer.animatePosition(from: CGPoint(x: 15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadge.layer.animatePosition(from: CGPoint(x: 15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadgeOutline.layer.animatePosition(from: CGPoint(x: 15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
if animated {
self.avatarNode.layer.animateScale(from: 1.0, to: 0.866666, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring)
self.avatarSelectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
@ -355,6 +426,18 @@ public final class SelectablePeerNode: ASDisplayNode {
} else {
self.avatarSelectionNode.image = nil
}
if let avatarBadgeLabel = self.avatarBadgeLabel, let avatarBadge = self.avatarBadge, let avatarBadgeOutline = self.avatarBadgeOutline {
avatarBadgeLabel.center = CGPoint(x: self.avatarNode.bounds.width / 2.0 - 2.0, y: avatarBadgeLabel.center.y)
avatarBadge.center = CGPoint(x: self.avatarNode.bounds.width / 2.0, y: avatarBadge.center.y)
avatarBadgeOutline.center = CGPoint(x: self.avatarNode.bounds.width / 2.0, y: avatarBadgeOutline.center.y)
if animated {
avatarBadgeLabel.layer.animatePosition(from: CGPoint(x: -15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadge.layer.animatePosition(from: CGPoint(x: -15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
avatarBadgeOutline.layer.animatePosition(from: CGPoint(x: -15.0, y: 0.0), to: .zero, duration: 0.2, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
}
if selected {
@ -365,8 +448,8 @@ public final class SelectablePeerNode: ASDisplayNode {
self.addSubnode(checkNode)
let avatarFrame = self.avatarNode.frame
let checkSize = CGSize(width: 24.0, height: 24.0)
checkNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX - 10.0, y: avatarFrame.maxY - 18.0), size: checkSize)
let checkSize = CGSize(width: 22.0, height: 22.0)
checkNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX - 14.0, y: avatarFrame.maxY - 15.0), size: checkSize)
checkNode.setSelected(true, animated: animated)
}
} else if let checkNode = self.checkNode {
@ -416,8 +499,8 @@ public final class SelectablePeerNode: ASDisplayNode {
self.onlineNode.frame = CGRect(origin: CGPoint(x: avatarContainerFrame.maxX - self.onlineNode.frame.width - 2.0, y: avatarContainerFrame.maxY - self.onlineNode.frame.height - 2.0), size: self.onlineNode.frame.size)
if let checkNode = self.checkNode {
let checkSize = CGSize(width: 24.0, height: 24.0)
checkNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX - 10.0, y: avatarFrame.maxY - 18.0), size: checkSize)
let checkSize = CGSize(width: 22.0, height: 22.0)
checkNode.frame = CGRect(origin: CGPoint(x: avatarFrame.maxX - 14.0, y: avatarFrame.maxY - 15.0), size: checkSize)
}
}
}

View file

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

View file

@ -437,9 +437,8 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
label = presentationData.strings.Settings_Privacy_Messages_ValueEveryone
case .requirePremium:
label = presentationData.strings.Settings_Privacy_Messages_ValueContactsAndPremium
case let .paidMessages(amount):
//TODO:localize
label = "\(amount.value) Stars"
case .paidMessages:
label = presentationData.strings.Settings_Privacy_Messages_ValuePaid
}
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.Settings_Privacy_Messages, titleIcon: hasPremium ? PresentationResourcesItemList.premiumIcon(theme) : nil, label: label, sectionId: self.section, style: .blocks, action: {
arguments.openMessagePrivacy()
@ -862,7 +861,11 @@ public func privacyAndSecurityController(
updateHasTwoStepAuth()
var setupEmailImpl: ((String?) -> Void)?
var reviewCallPrivacySuggestion = false
var reviewInvitePrivacySuggestion = false
var showPrivacySuggestionImpl: (() -> Void)?
let arguments = PrivacyAndSecurityControllerArguments(account: context.account, openBlockedUsers: {
pushControllerImpl?(blockedPeersController(context: context, blockedPeersContext: blockedPeersContext), true)
}, openLastSeenPrivacy: {
@ -907,6 +910,10 @@ public func privacyAndSecurityController(
return .complete()
}
currentInfoDisposable.set(applySetting.start())
Queue.mainQueue().after(0.3) {
showPrivacySuggestionImpl?()
}
}
}), true)
}
@ -944,6 +951,10 @@ public func privacyAndSecurityController(
return .complete()
}
currentInfoDisposable.set(applySetting.start())
Queue.mainQueue().after(0.3) {
showPrivacySuggestionImpl?()
}
}
}), true)
}
@ -1319,6 +1330,22 @@ public func privacyAndSecurityController(
return state
}
}))
if case .everybody = privacySettings.globalSettings.nonContactChatsPrivacy {
if case .everybody = settingValue {
} else {
if case .enableEveryone = privacySettings.voiceCalls {
reviewCallPrivacySuggestion = true
}
if case .enableEveryone = privacySettings.groupInvitations {
reviewInvitePrivacySuggestion = true
}
Queue.mainQueue().after(0.3) {
showPrivacySuggestionImpl?()
}
}
}
}), true)
})
}, openGiftsPrivacy: {
@ -1442,6 +1469,50 @@ public func privacyAndSecurityController(
}
}
showPrivacySuggestionImpl = {
//TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
if reviewCallPrivacySuggestion {
reviewCallPrivacySuggestion = false
let alertController = textAlertController(
context: context,
title: "Call Settings",
text: "You've restricted who can message you, but anyone can still call you. Would you like to review these settings?",
actions: [
TextAlertAction(type: .defaultAction, title: "Review", action: {
arguments.openVoiceCallPrivacy()
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
Queue.mainQueue().after(0.3) {
showPrivacySuggestionImpl?()
}
})
],
actionLayout: .vertical
)
presentControllerImpl?(alertController)
} else if reviewInvitePrivacySuggestion {
reviewInvitePrivacySuggestion = false
let alertController = textAlertController(
context: context,
title: "Invitation Settings",
text: "You've restricted who can message you, but anyone can still invite you to groups and channels. Would you like to review these settings?",
actions: [
TextAlertAction(type: .defaultAction, title: "Review", action: {
arguments.openGroupsPrivacy()
}),
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
Queue.mainQueue().after(0.3) {
showPrivacySuggestionImpl?()
}
})
],
actionLayout: .vertical
)
presentControllerImpl?(alertController)
}
}
setupEmailImpl = { emailPattern in
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var dismissEmailControllerImpl: (() -> Void)?

View file

@ -46,6 +46,7 @@ swift_library(
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/MessageInputPanelComponent",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
"//submodules/ChatPresentationInterfaceState",
"//submodules/CheckNode",
],

View file

@ -21,6 +21,7 @@ import AnimationCache
import MultiAnimationRenderer
import ObjectiveC
import UndoUI
import ChatMessagePaymentAlertController
private var ObjCKey_DeinitWatcher: Int?
@ -405,7 +406,7 @@ public final class ShareController: ViewController {
private let fromForeignApp: Bool
private let collectibleItemInfo: TelegramCollectibleItemInfo?
private let peers = Promise<([(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)], EnginePeer)>()
private let peers = Promise<([(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)], EnginePeer)>()
private let peersDisposable = MetaDisposable()
private let readyDisposable = MetaDisposable()
private let accountActiveDisposable = MetaDisposable()
@ -664,12 +665,21 @@ public final class ShareController: ViewController {
override public func loadDisplayNode() {
var fromPublicChannel = false
var messageCount: Int = 1
if case let .messages(messages) = self.subject, let message = messages.first, let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
fromPublicChannel = true
} else if case let .url(link) = self.subject, link.contains("t.me/nft/") {
fromPublicChannel = true
}
if case let .messages(messages) = self.subject {
messageCount = messages.count
} else if case let .image(images) = self.subject {
messageCount = images.count
} else if case let .fromExternal(count, _) = self.subject {
messageCount = count
}
var mediaParameters: ShareControllerSubject.MediaParameters?
if case let .media(_, parameters) = self.subject {
mediaParameters = parameters
@ -682,7 +692,7 @@ public final class ShareController: ViewController {
return
}
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, fromPublicChannel: fromPublicChannel, segmentedValues: self.segmentedValues, shareStory: self.shareStory, collectibleItemInfo: self.collectibleItemInfo)
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, fromForeignApp: self.fromForeignApp, forceTheme: self.forceTheme, fromPublicChannel: fromPublicChannel, segmentedValues: self.segmentedValues, shareStory: self.shareStory, collectibleItemInfo: self.collectibleItemInfo, messageCount: messageCount)
self.controllerNode.completed = self.completed
self.controllerNode.enqueued = self.enqueued
self.controllerNode.present = { [weak self] c in
@ -1240,21 +1250,31 @@ public final class ShareController: ViewController {
return self.currentContext.stateManager.postbox.combinedView(
keys: peerIds.map { peerId in
return PostboxViewKey.basicPeer(peerId)
} + peerIds.map { peerId in
return PostboxViewKey.cachedPeerData(peerId: peerId)
}
)
|> take(1)
|> map { views -> [EnginePeer.Id: EnginePeer?] in
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: StarsAmount]) in
var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars
}
} else if let channel = peer as? TelegramChannel {
requiresStars[peerId] = channel.sendPaidMessageStars
}
}
}
return result
return (result, requiresStars)
}
|> deliverOnMainQueue
|> castError(ShareControllerError.self)
|> mapToSignal { [weak self] peers -> Signal<ShareState, ShareControllerError> in
|> mapToSignal { [weak self] peers, requiresStars -> Signal<ShareState, ShareControllerError> in
guard let strongSelf = self else {
return .complete()
}
@ -1266,7 +1286,7 @@ public final class ShareController: ViewController {
subject = selectedValue.subject
}
func transformMessages(_ messages: [StandaloneSendEnqueueMessage], showNames: Bool, silently: Bool) -> [StandaloneSendEnqueueMessage] {
func transformMessages(_ messages: [StandaloneSendEnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: StarsAmount?) -> [StandaloneSendEnqueueMessage] {
return messages.map { message in
var message = message
if !showNames {
@ -1278,6 +1298,7 @@ public final class ShareController: ViewController {
if silently {
message.isSilent = true
}
message.sendPaidMessageStars = sendPaidMessageStars
return message
}
}
@ -1325,7 +1346,7 @@ public final class ShareController: ViewController {
replyToMessageId: replyToMessageId
))
}
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1386,7 +1407,7 @@ public final class ShareController: ViewController {
)),
replyToMessageId: replyToMessageId
))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1451,7 +1472,7 @@ public final class ShareController: ViewController {
)),
replyToMessageId: replyToMessageId
))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1511,7 +1532,7 @@ public final class ShareController: ViewController {
replyToMessageId: replyToMessageId
))
}
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1621,7 +1642,7 @@ public final class ShareController: ViewController {
),
replyToMessageId: replyToMessageId
))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1680,7 +1701,7 @@ public final class ShareController: ViewController {
content: .map(map: media),
replyToMessageId: replyToMessageId
))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1800,7 +1821,7 @@ public final class ShareController: ViewController {
replyToMessageId: replyToMessageId
))
}
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently)
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(standaloneSendEnqueueMessages(
accountPeerId: strongSelf.currentContext.accountPeerId,
postbox: strongSelf.currentContext.stateManager.postbox,
@ -1820,8 +1841,8 @@ public final class ShareController: ViewController {
messages: messagesToEnqueue
))
}
case let .fromExternal(f):
return f(peerIds, topicIds, text, strongSelf.currentContext, silently)
case let .fromExternal(_, f):
return f(peerIds, topicIds, requiresStars, text, strongSelf.currentContext, silently)
|> map { state -> ShareState in
switch state {
case let .preparing(long):
@ -1880,12 +1901,34 @@ public final class ShareController: ViewController {
guard let currentContext = self.currentContext as? ShareControllerAppAccountContext else {
return .single(.done([]))
}
return currentContext.context.engine.data.get(EngineDataMap(
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init(id:))
))
return currentContext.stateManager.postbox.combinedView(
keys: peerIds.map { peerId in
return PostboxViewKey.basicPeer(peerId)
} + peerIds.map { peerId in
return PostboxViewKey.cachedPeerData(peerId: peerId)
}
)
|> take(1)
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: StarsAmount]) in
var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: StarsAmount] = [:]
for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars
}
} else if let channel = peer as? TelegramChannel {
requiresStars[peerId] = channel.sendPaidMessageStars
}
}
}
return (result, requiresStars)
}
|> deliverOnMainQueue
|> castError(ShareControllerError.self)
|> mapToSignal { [weak self] peers -> Signal<ShareState, ShareControllerError> in
|> mapToSignal { [weak self] peers, requiresStars -> Signal<ShareState, ShareControllerError> in
guard let strongSelf = self, let currentContext = strongSelf.currentContext as? ShareControllerAppAccountContext else {
return .complete()
}
@ -1897,7 +1940,7 @@ public final class ShareController: ViewController {
subject = selectedValue.subject
}
func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool) -> [EnqueueMessage] {
func transformMessages(_ messages: [EnqueueMessage], showNames: Bool, silently: Bool, sendPaidMessageStars: StarsAmount?) -> [EnqueueMessage] {
return messages.map { message in
return message.withUpdatedAttributes({ attributes in
var attributes = attributes
@ -1907,6 +1950,9 @@ public final class ShareController: ViewController {
if silently {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
if let sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
return attributes
})
}
@ -1949,7 +1995,7 @@ public final class ShareController: ViewController {
} else {
messages.append(.message(text: url, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages))
}
case let .text(string):
@ -1983,7 +2029,7 @@ public final class ShareController: ViewController {
messages.append(.message(text: text, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
messages.append(.message(text: string, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages))
}
case let .quote(string, url):
@ -2020,7 +2066,7 @@ public final class ShareController: ViewController {
attributedText.append(NSAttributedString(string: "\n\n\(url)"))
let entities = generateChatInputTextEntities(attributedText)
messages.append(.message(text: attributedText.string, attributes: [TextEntitiesMessageAttribute(entities: entities)], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages))
}
case let .image(representations):
@ -2051,7 +2097,7 @@ public final class ShareController: ViewController {
var messages: [EnqueueMessage] = []
messages.append(.message(text: text, attributes: [], inlineStickers: [:], mediaReference: .standalone(media: TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.LocalImage, id: Int64.random(in: Int64.min ... Int64.max)), representations: representations.map({ $0.representation }), immediateThumbnailData: nil, reference: nil, partialReference: nil, flags: [])), threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages))
}
case let .media(mediaReference, mediaParameters):
@ -2145,7 +2191,7 @@ public final class ShareController: ViewController {
} else {
messages.append(.message(text: sendTextAsCaption ? text : "", attributes: attributes, inlineStickers: [:], mediaReference: mediaReference, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages))
}
case let .mapMedia(media):
@ -2179,7 +2225,7 @@ public final class ShareController: ViewController {
messages.append(.message(text: text, attributes: [], inlineStickers: [:], mediaReference: nil, threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
}
messages.append(.message(text: "", attributes: [], inlineStickers: [:], mediaReference: .standalone(media: media), threadId: threadId, replyToMessageId: replyToMessageId.flatMap { EngineMessageReplySubject(messageId: $0, quote: nil) }, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: []))
messages = transformMessages(messages, showNames: showNames, silently: silently)
messages = transformMessages(messages, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messages))
}
case let .messages(messages):
@ -2274,11 +2320,11 @@ public final class ShareController: ViewController {
correlationIds.append(correlationId)
messagesToEnqueue.append(.forward(source: message.id, threadId: threadId, grouping: .auto, attributes: [], correlationId: correlationId))
}
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently)
messagesToEnqueue = transformMessages(messagesToEnqueue, showNames: showNames, silently: silently, sendPaidMessageStars: requiresStars[peerId])
shareSignals.append(enqueueMessages(account: currentContext.context.account, peerId: peerId, messages: messagesToEnqueue))
}
case let .fromExternal(f):
return f(peerIds, topicIds, text, currentContext, silently)
case let .fromExternal(_, f):
return f(peerIds, topicIds, requiresStars, text, currentContext, silently)
|> map { state -> ShareState in
switch state {
case let .preparing(long):
@ -2458,7 +2504,7 @@ public final class ShareController: ViewController {
peer,
tailChatList |> take(1)
)
|> mapToSignal { maybeAccountPeer, view -> Signal<([(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)], EnginePeer), NoError> in
|> mapToSignal { maybeAccountPeer, view -> Signal<([(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)], EnginePeer), NoError> in
let accountPeer = maybeAccountPeer!
var peers: [EngineRenderedPeer] = []
@ -2468,7 +2514,7 @@ public final class ShareController: ViewController {
case let .MessageEntry(entryData):
if let peer = entryData.renderedPeer.peers[entryData.renderedPeer.peerId], peer.id != accountPeer.id, canSendMessagesToPeer(peer) {
peers.append(EngineRenderedPeer(entryData.renderedPeer))
if let user = peer as? TelegramUser, user.flags.contains(.requirePremium) {
if let user = peer as? TelegramUser, user.flags.contains(.requirePremium) || user.flags.contains(.requireStars) {
possiblePremiumRequiredPeers.insert(user.id)
}
}
@ -2487,7 +2533,7 @@ public final class ShareController: ViewController {
}
return account.stateManager.postbox.combinedView(keys: keys)
|> map { views -> ([EnginePeer.Id: EnginePeer.Presence?], [EnginePeer.Id: Bool]) in
|> map { views -> ([EnginePeer.Id: EnginePeer.Presence?], [EnginePeer.Id: Bool], [EnginePeer.Id: Int64]) in
var result: [EnginePeer.Id: EnginePeer.Presence?] = [:]
if let view = views.views[peerPresencesKey] as? PeerPresencesView {
result = view.presences.mapValues { value -> EnginePeer.Presence? in
@ -2495,19 +2541,21 @@ public final class ShareController: ViewController {
}
}
var requiresPremiumForMessaging: [EnginePeer.Id: Bool] = [:]
var requiresStars: [EnginePeer.Id: Int64] = [:]
for id in possiblePremiumRequiredPeers {
if let view = views.views[.cachedPeerData(peerId: id)] as? CachedPeerDataView, let data = view.cachedPeerData as? CachedUserData {
requiresPremiumForMessaging[id] = data.flags.contains(.premiumRequired)
requiresStars[id] = data.sendPaidMessageStars?.value
} else {
requiresPremiumForMessaging[id] = false
}
}
return (result, requiresPremiumForMessaging)
return (result, requiresPremiumForMessaging, requiresStars)
}
|> map { presenceMap, requiresPremiumForMessaging -> ([(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)], EnginePeer) in
var resultPeers: [(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)] = []
|> map { presenceMap, requiresPremiumForMessaging, requiresStars -> ([(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)], EnginePeer) in
var resultPeers: [(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)] = []
for peer in peers {
resultPeers.append((peer, presenceMap[peer.peerId].flatMap { $0 }, requiresPremiumForMessaging[peer.peerId] ?? false))
resultPeers.append((peer, presenceMap[peer.peerId].flatMap { $0 }, requiresPremiumForMessaging[peer.peerId] ?? false, requiresStars[peer.peerId]))
}
return (resultPeers, accountPeer)
}

View file

@ -15,6 +15,7 @@ import TelegramStringFormatting
import BundleIconComponent
import LottieComponent
import CheckNode
import ChatMessagePaymentAlertController
enum ShareState {
case preparing(Bool)
@ -327,6 +328,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
private let segmentedValues: [ShareControllerSegmentedValue]?
private let collectibleItemInfo: TelegramCollectibleItemInfo?
private let mediaParameters: ShareControllerSubject.MediaParameters?
private let messageCount: Int
var selectedSegmentedIndex: Int = 0
@ -387,7 +389,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
private let showNames = ValuePromise<Bool>(true)
init(controller: ShareController, environment: ShareControllerEnvironment, presentationData: PresentationData, presetText: String?, defaultAction: ShareControllerAction?, mediaParameters: ShareControllerSubject.MediaParameters?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, fromForeignApp: Bool, forceTheme: PresentationTheme?, fromPublicChannel: Bool, segmentedValues: [ShareControllerSegmentedValue]?, shareStory: (() -> Void)?, collectibleItemInfo: TelegramCollectibleItemInfo?) {
init(controller: ShareController, environment: ShareControllerEnvironment, presentationData: PresentationData, presetText: String?, defaultAction: ShareControllerAction?, mediaParameters: ShareControllerSubject.MediaParameters?, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, presentError: @escaping (String?, String) -> Void, externalShare: Bool, immediateExternalShare: Bool, immediatePeerId: PeerId?, fromForeignApp: Bool, forceTheme: PresentationTheme?, fromPublicChannel: Bool, segmentedValues: [ShareControllerSegmentedValue]?, shareStory: (() -> Void)?, collectibleItemInfo: TelegramCollectibleItemInfo?, messageCount: Int) {
self.controller = controller
self.environment = environment
self.presentationData = presentationData
@ -401,6 +403,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
self.segmentedValues = segmentedValues
self.collectibleItemInfo = collectibleItemInfo
self.mediaParameters = mediaParameters
self.messageCount = messageCount
self.presetText = presetText
@ -1260,7 +1263,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
})
}
}
func send(peerId: PeerId? = nil, showNames: Bool = true, silently: Bool = false) {
let peerIds: [PeerId]
if let peerId = peerId {
@ -1273,19 +1276,29 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
let _ = (context.stateManager.postbox.combinedView(
keys: peerIds.map { peerId in
return PostboxViewKey.basicPeer(peerId)
} + peerIds.map { peerId in
return PostboxViewKey.cachedPeerData(peerId: peerId)
}
)
|> take(1)
|> map { views -> [EnginePeer.Id: EnginePeer?] in
|> map { views -> ([EnginePeer.Id: EnginePeer?], [EnginePeer.Id: Int64]) in
var result: [EnginePeer.Id: EnginePeer?] = [:]
var requiresStars: [EnginePeer.Id: Int64] = [:]
for peerId in peerIds {
if let view = views.views[PostboxViewKey.basicPeer(peerId)] as? BasicPeerView, let peer = view.peer {
result[peerId] = EnginePeer(peer)
if peer is TelegramUser, let cachedPeerDataView = views.views[PostboxViewKey.cachedPeerData(peerId: peerId)] as? CachedPeerDataView {
if let cachedData = cachedPeerDataView.cachedPeerData as? CachedUserData {
requiresStars[peerId] = cachedData.sendPaidMessageStars?.value
}
} else if let channel = peer as? TelegramChannel {
requiresStars[peerId] = channel.sendPaidMessageStars?.value
}
}
}
return result
return (result, requiresStars)
}
|> deliverOnMainQueue).start(next: { [weak self] peers in
|> deliverOnMainQueue).start(next: { [weak self] peers, requiresStars in
guard let self else {
return
}
@ -1300,14 +1313,49 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
if !tryShare(self.inputFieldNode.text, mappedPeers) {
return
}
self.presentPaidMessageAlertIfNeeded(peers: mappedPeers, requiresStars: requiresStars, completion: { [weak self] in
self?.commitSend(peerId: peerId, showNames: showNames, silently: silently)
})
self.commitSend(peerId: peerId, showNames: showNames, silently: silently)
})
} else {
self.commitSend(peerId: peerId, showNames: showNames, silently: silently)
}
}
private func presentPaidMessageAlertIfNeeded(peers: [EnginePeer], requiresStars: [EnginePeer.Id: Int64], completion: @escaping () -> Void) {
var count: Int32 = Int32(self.messageCount)
if !self.inputFieldNode.text.isEmpty {
count += 1
}
var totalAmount: StarsAmount = .zero
for peer in peers {
if let stars = requiresStars[peer.id] {
totalAmount = totalAmount + StarsAmount(value: stars, nanos: 0)
}
}
if totalAmount.value > 0 {
let controller = chatMessagePaymentAlertController(
context: nil,
presentationData: self.presentationData,
updatedPresentationData: nil,
peers: peers,
count: count,
amount: totalAmount,
totalAmount: totalAmount,
hasCheck: false,
navigationController: nil,
completion: { _ in
completion()
}
)
self.present?(controller)
} else {
completion()
}
}
private func commitSend(peerId: PeerId?, showNames: Bool, silently: Bool) {
if !self.inputFieldNode.text.isEmpty {
for peer in self.controllerInteraction!.selectedPeers {
@ -1522,7 +1570,7 @@ final class ShareControllerNode: ViewControllerTracingNode, ASScrollViewDelegate
}
}
func updatePeers(context: ShareControllerAccountContext, switchableAccounts: [ShareControllerSwitchableAccount], peers: [(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)], accountPeer: EnginePeer, defaultAction: ShareControllerAction?) {
func updatePeers(context: ShareControllerAccountContext, switchableAccounts: [ShareControllerSwitchableAccount], peers: [(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)], accountPeer: EnginePeer, defaultAction: ShareControllerAction?) {
self.context = context
if let peersContentNode = self.peersContentNode, peersContentNode.accountPeer.id == accountPeer.id {

View file

@ -96,11 +96,11 @@ final class ShareControllerGridSectionNode: ASDisplayNode {
final class ShareControllerPeerGridItem: GridItem {
enum ShareItem: Equatable {
case peer(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, topicId: Int64?, threadData: MessageHistoryThreadData?, requiresPremiumForMessaging: Bool)
case peer(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, topicId: Int64?, threadData: MessageHistoryThreadData?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)
case story(isMessage: Bool)
var peerId: EnginePeer.Id? {
if case let .peer(peer, _, _, _, _) = self {
if case let .peer(peer, _, _, _, _, _) = self {
return peer.peerId
} else {
return nil
@ -162,7 +162,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
private var absoluteLocation: (CGRect, CGSize)?
var peerId: EnginePeer.Id? {
if let item = self.currentState?.item, case let .peer(peer, _, _, _, _) = item {
if let item = self.currentState?.item, case let .peer(peer, _, _, _, _, _) = item {
return peer.peerId
} else {
return nil
@ -177,7 +177,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
self.peerNode.toggleSelection = { [weak self] isDisabled in
if let strongSelf = self {
if let (_, _, _, _, maybeItem, search) = strongSelf.currentState, let item = maybeItem {
if case let .peer(peer, _, _, _, _) = item, let _ = peer.peers[peer.peerId] {
if case let .peer(peer, _, _, _, _, _) = item, let _ = peer.peers[peer.peerId] {
if isDisabled {
strongSelf.controllerInteraction?.disabledPeerSelected(peer)
} else {
@ -213,7 +213,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
var effectivePresence: EnginePeer.Presence?
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
self.peerNode.theme = itemTheme
if let item, case let .peer(renderedPeer, presence, _, threadData, requiresPremiumForMessaging) = item, let peer = renderedPeer.peer {
if let item, case let .peer(renderedPeer, presence, _, threadData, requiresPremiumForMessaging, requiresStars) = item, let peer = renderedPeer.peer {
effectivePresence = presence
var isOnline = false
var isSupport = false
@ -243,6 +243,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
strings: strings,
peer: renderedPeer,
requiresPremiumForMessaging: requiresPremiumForMessaging,
requiresStars: requiresStars,
customTitle: threadData?.info.title,
iconId: threadData?.info.icon,
iconColor: threadData?.info.iconColor ?? 0,
@ -302,7 +303,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode {
func updateSelection(animated: Bool) {
var selected = false
if let controllerInteraction = self.controllerInteraction, let (_, _, _, _, maybeItem, _) = self.currentState, let item = maybeItem {
if case let .peer(peer, _, _, _, _) = item {
if case let .peer(peer, _, _, _, _, _) = item {
selected = controllerInteraction.selectedPeerIds.contains(peer.peerId)
}
}

View file

@ -43,7 +43,7 @@ private struct SharePeerEntry: Comparable, Identifiable {
var stableId: Int64 {
switch self.item {
case let .peer(peer, _, _, _, _):
case let .peer(peer, _, _, _, _, _):
return peer.peerId.toInt64()
case .story:
return 0
@ -137,7 +137,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
private var validLayout: (CGSize, CGFloat)?
private var overrideGridOffsetTransition: ContainedViewLayoutTransition?
let peersValue = Promise<[(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)]>()
let peersValue = Promise<[(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)]>()
private var _tick: Int = 0 {
didSet {
@ -146,7 +146,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
}
private let tick = ValuePromise<Int>(0)
init(environment: ShareControllerEnvironment, context: ShareControllerAccountContext, switchableAccounts: [ShareControllerSwitchableAccount], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool)], accountPeer: EnginePeer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, debugAction: @escaping () -> Void, extendedInitialReveal: Bool, segmentedValues: [ShareControllerSegmentedValue]?, fromPublicChannel: Bool) {
init(environment: ShareControllerEnvironment, context: ShareControllerAccountContext, switchableAccounts: [ShareControllerSwitchableAccount], theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, peers: [(peer: EngineRenderedPeer, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?)], accountPeer: EnginePeer, controllerInteraction: ShareControllerInteraction, externalShare: Bool, switchToAnotherAccount: @escaping () -> Void, debugAction: @escaping () -> Void, extendedInitialReveal: Bool, segmentedValues: [ShareControllerSegmentedValue]?, fromPublicChannel: Bool) {
self.environment = environment
self.context = context
self.theme = theme
@ -176,22 +176,22 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode {
}
var existingPeerIds: Set<EnginePeer.Id> = Set()
entries.append(SharePeerEntry(index: index, item: .peer(peer: EngineRenderedPeer(peer: accountPeer), presence: nil, topicId: nil, threadData: nil, requiresPremiumForMessaging: false), theme: theme, strings: strings))
entries.append(SharePeerEntry(index: index, item: .peer(peer: EngineRenderedPeer(peer: accountPeer), presence: nil, topicId: nil, threadData: nil, requiresPremiumForMessaging: false, requiresStars: nil), theme: theme, strings: strings))
existingPeerIds.insert(accountPeer.id)
index += 1
for (peer, requiresPremiumForMessaging) in foundPeers.reversed() {
if !existingPeerIds.contains(peer.peerId) {
entries.append(SharePeerEntry(index: index, item: .peer(peer: peer, presence: nil, topicId: nil, threadData: nil, requiresPremiumForMessaging: requiresPremiumForMessaging), theme: theme, strings: strings))
entries.append(SharePeerEntry(index: index, item: .peer(peer: peer, presence: nil, topicId: nil, threadData: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: nil), theme: theme, strings: strings))
existingPeerIds.insert(peer.peerId)
index += 1
}
}
for (peer, presence, requiresPremiumForMessaging) in initialPeers {
for (peer, presence, requiresPremiumForMessaging, requiresStars) in initialPeers {
if !existingPeerIds.contains(peer.peerId) {
let thread = controllerInteraction?.selectedTopics[peer.peerId]
entries.append(SharePeerEntry(index: index, item: .peer(peer: peer, presence: presence, topicId: thread?.0, threadData: thread?.1, requiresPremiumForMessaging: requiresPremiumForMessaging), theme: theme, strings: strings))
entries.append(SharePeerEntry(index: index, item: .peer(peer: peer, presence: presence, topicId: thread?.0, threadData: thread?.1, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: requiresStars), theme: theme, strings: strings))
existingPeerIds.insert(peer.peerId)
index += 1
}

View file

@ -36,13 +36,13 @@ private enum ShareSearchRecentEntryStableId: Hashable {
private enum ShareSearchRecentEntry: Comparable, Identifiable {
case topPeers(PresentationTheme, PresentationStrings)
case peer(index: Int, theme: PresentationTheme, peer: EnginePeer, associatedPeer: EnginePeer?, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, strings: PresentationStrings)
case peer(index: Int, theme: PresentationTheme, peer: EnginePeer, associatedPeer: EnginePeer?, presence: EnginePeer.Presence?, requiresPremiumForMessaging: Bool, requiresStars: Int64?, strings: PresentationStrings)
var stableId: ShareSearchRecentEntryStableId {
switch self {
case .topPeers:
return .topPeers
case let .peer(_, _, peer, _, _, _, _):
case let .peer(_, _, peer, _, _, _, _, _):
return .peerId(peer.id)
}
}
@ -61,8 +61,8 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable {
} else {
return false
}
case let .peer(lhsIndex, lhsTheme, lhsPeer, lhsAssociatedPeer, lhsPresence, lhsRequiresPremiumForMessaging, lhsStrings):
if case let .peer(rhsIndex, rhsTheme, rhsPeer, rhsAssociatedPeer, rhsPresence, rhsRequiresPremiumForMessaging, rhsStrings) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsStrings === rhsStrings && lhsTheme === rhsTheme && lhsPresence == rhsPresence && lhsRequiresPremiumForMessaging == rhsRequiresPremiumForMessaging {
case let .peer(lhsIndex, lhsTheme, lhsPeer, lhsAssociatedPeer, lhsPresence, lhsRequiresPremiumForMessaging, lhsRequiresStars, lhsStrings):
if case let .peer(rhsIndex, rhsTheme, rhsPeer, rhsAssociatedPeer, rhsPresence, rhsRequiresPremiumForMessaging, rhsRequiresStars, rhsStrings) = rhs, lhsPeer == rhsPeer && lhsAssociatedPeer == rhsAssociatedPeer && lhsIndex == rhsIndex && lhsStrings === rhsStrings && lhsTheme === rhsTheme && lhsPresence == rhsPresence && lhsRequiresPremiumForMessaging == rhsRequiresPremiumForMessaging && lhsRequiresStars == rhsRequiresStars {
return true
} else {
return false
@ -74,11 +74,11 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable {
switch lhs {
case .topPeers:
return true
case let .peer(lhsIndex, _, _, _, _, _, _):
case let .peer(lhsIndex, _, _, _, _, _, _, _):
switch rhs {
case .topPeers:
return false
case let .peer(rhsIndex, _, _, _, _, _, _):
case let .peer(rhsIndex, _, _, _, _, _, _, _):
return lhsIndex <= rhsIndex
}
}
@ -88,13 +88,13 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable {
switch self {
case let .topPeers(theme, strings):
return ShareControllerRecentPeersGridItem(environment: environment, context: context, theme: theme, strings: strings, controllerInteraction: interfaceInteraction)
case let .peer(_, theme, peer, associatedPeer, presence, requiresPremiumForMessaging, strings):
case let .peer(_, theme, peer, associatedPeer, presence, requiresPremiumForMessaging, requiresStars, strings):
var peers: [EnginePeer.Id: EnginePeer] = [peer.id: peer]
if let associatedPeer = associatedPeer {
peers[associatedPeer.id] = associatedPeer
}
let peer = EngineRenderedPeer(peerId: peer.id, peers: peers, associatedMedia: [:])
return ShareControllerPeerGridItem(environment: environment, context: context, theme: theme, strings: strings, item: .peer(peer: peer, presence: presence, topicId: nil, threadData: nil, requiresPremiumForMessaging: requiresPremiumForMessaging), controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true)
return ShareControllerPeerGridItem(environment: environment, context: context, theme: theme, strings: strings, item: .peer(peer: peer, presence: presence, topicId: nil, threadData: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: requiresStars), controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true)
}
}
}
@ -104,6 +104,7 @@ private struct ShareSearchPeerEntry: Comparable, Identifiable {
let peer: EngineRenderedPeer?
let presence: EnginePeer.Presence?
let requiresPremiumForMessaging: Bool
let requiresStars: Int64?
let theme: PresentationTheme
let strings: PresentationStrings
let isGlobal: Bool
@ -129,6 +130,9 @@ private struct ShareSearchPeerEntry: Comparable, Identifiable {
if lhs.requiresPremiumForMessaging != rhs.requiresPremiumForMessaging {
return false
}
if lhs.requiresStars != rhs.requiresStars {
return false
}
if lhs.theme !== rhs.theme {
return false
}
@ -149,7 +153,7 @@ private struct ShareSearchPeerEntry: Comparable, Identifiable {
} else {
sectionTitle = nil
}
return ShareControllerPeerGridItem(environment: environment, context: context, theme: self.theme, strings: self.strings, item: self.peer.flatMap({ .peer(peer: $0, presence: self.presence, topicId: nil, threadData: nil, requiresPremiumForMessaging: self.requiresPremiumForMessaging) }), controllerInteraction: interfaceInteraction, sectionTitle: sectionTitle, search: true)
return ShareControllerPeerGridItem(environment: environment, context: context, theme: self.theme, strings: self.strings, item: self.peer.flatMap({ .peer(peer: $0, presence: self.presence, topicId: nil, threadData: nil, requiresPremiumForMessaging: self.requiresPremiumForMessaging, requiresStars: self.requiresStars) }), controllerInteraction: interfaceInteraction, sectionTitle: sectionTitle, search: true)
}
}
@ -361,7 +365,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
if strings.DialogList_SavedMessages.lowercased().hasPrefix(lowercasedQuery) || "saved messages".hasPrefix(lowercasedQuery) {
if !existingPeerIds.contains(accountPeer.id) {
existingPeerIds.insert(accountPeer.id)
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(accountPeer)), presence: nil, requiresPremiumForMessaging: false, theme: theme, strings: strings, isGlobal: false))
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(accountPeer)), presence: nil, requiresPremiumForMessaging: false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false))
index += 1
}
}
@ -370,7 +374,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
if let peer = renderedPeer.peers[renderedPeer.peerId], peer.id != accountPeer.id {
if !existingPeerIds.contains(renderedPeer.peerId) && canSendMessagesToPeer(peer) {
existingPeerIds.insert(renderedPeer.peerId)
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(renderedPeer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, theme: theme, strings: strings, isGlobal: false))
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(renderedPeer), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false))
index += 1
}
}
@ -380,7 +384,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
if foundRemotePeers.2 {
isPlaceholder = true
for _ in 0 ..< 4 {
entries.append(ShareSearchPeerEntry(index: index, peer: nil, presence: nil, requiresPremiumForMessaging: false, theme: theme, strings: strings, isGlobal: false))
entries.append(ShareSearchPeerEntry(index: index, peer: nil, presence: nil, requiresPremiumForMessaging: false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false))
index += 1
}
} else {
@ -388,7 +392,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
existingPeerIds.insert(peer.id)
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(foundPeer.peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, theme: theme, strings: strings, isGlobal: false))
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(foundPeer.peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: false))
index += 1
}
}
@ -397,7 +401,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
let peer = foundPeer.peer
if !existingPeerIds.contains(peer.id) && canSendMessagesToPeer(peer) {
existingPeerIds.insert(peer.id)
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, theme: theme, strings: strings, isGlobal: true))
entries.append(ShareSearchPeerEntry(index: index, peer: EngineRenderedPeer(peer: EnginePeer(peer)), presence: nil, requiresPremiumForMessaging: peerRequiresPremiumForMessaging[peer.id] ?? false, requiresStars: nil, theme: theme, strings: strings, isGlobal: true))
index += 1
}
}
@ -462,7 +466,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
var index = 0
for (peer, requiresPremiumForMessaging) in recentPeerList {
if let mainPeer = peer.peers[peer.peerId], canSendMessagesToPeer(mainPeer._asPeer()) {
recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer._asPeer().associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, strings: strings))
recentItemList.append(.peer(index: index, theme: theme, peer: mainPeer, associatedPeer: mainPeer._asPeer().associatedPeerId.flatMap { peer.peers[$0] }, presence: nil, requiresPremiumForMessaging: requiresPremiumForMessaging, requiresStars: nil, strings: strings))
index += 1
}
}
@ -570,7 +574,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode {
switch $0 {
case .topPeers:
return false
case let .peer(_, _, peer, _, _, _, _):
case let .peer(_, _, peer, _, _, _, _, _):
return peer.id == ensurePeerVisibleOnLayout
}
}) {

View file

@ -409,7 +409,7 @@ public func preparedShareItems(postbox: Postbox, network: Network, to peerId: Pe
})
}
public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, auxiliaryMethods: AccountAuxiliaryMethods, to peerIds: [PeerId], threadIds: [PeerId: Int64], items: [PreparedShareItemContent], silently: Bool, additionalText: String) -> Signal<Float, Void> {
public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Network, stateManager: AccountStateManager, auxiliaryMethods: AccountAuxiliaryMethods, to peerIds: [PeerId], threadIds: [PeerId: Int64], requireStars: [PeerId: StarsAmount], items: [PreparedShareItemContent], silently: Bool, additionalText: String) -> Signal<Float, Void> {
var messages: [StandaloneSendEnqueueMessage] = []
var groupingKey: Int64?
var mediaTypes: (photo: Int, video: Int, music: Int, other: Int) = (0, 0, 0, 0)
@ -498,6 +498,16 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
var peerSignals: Signal<Float, StandaloneSendMessagesError> = .single(0.0)
for peerId in peerIds {
var peerMessages = messages
if let amount = requireStars[peerId] {
var updatedMessages: [StandaloneSendEnqueueMessage] = []
for message in peerMessages {
var message = message
message.sendPaidMessageStars = amount
updatedMessages.append(message)
}
peerMessages = updatedMessages
}
peerSignals = peerSignals |> then(standaloneSendEnqueueMessages(
accountPeerId: accountPeerId,
postbox: postbox,
@ -506,7 +516,7 @@ public func sentShareItems(accountPeerId: PeerId, postbox: Postbox, network: Net
auxiliaryMethods: auxiliaryMethods,
peerId: peerId,
threadId: threadIds[peerId],
messages: messages
messages: peerMessages
)
|> mapToSignal { status -> Signal<Float, StandaloneSendMessagesError> in
switch status {

View file

@ -1182,7 +1182,7 @@ private enum StatsEntry: ItemListNodeEntry {
detailText = stringForMediumCompactDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
}
let label = tonAmountAttributedString(formatTonAmountText(transaction.amount, dateTimeFormat: presentationData.dateTimeFormat, showPlus: true), integralFont: font, fractionalFont: smallLabelFont, color: labelColor).mutableCopy() as! NSMutableAttributedString
let label = tonAmountAttributedString(formatTonAmountText(transaction.amount, dateTimeFormat: presentationData.dateTimeFormat, showPlus: true), integralFont: font, fractionalFont: smallLabelFont, color: labelColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
label.insert(NSAttributedString(string: " $ ", font: font, textColor: labelColor), at: 1)
if let range = label.string.range(of: "$"), let icon = generateTintedImage(image: UIImage(bundleImageName: "Ads/TonMedium"), color: labelColor) {

View file

@ -177,7 +177,7 @@ final class MonetizationBalanceItemNode: ListViewItemNode, ItemListItemNode {
var isStars = false
if let stats = item.stats as? RevenueStats {
let cryptoValue = formatTonAmountText(stats.balances.availableBalance, dateTimeFormat: item.presentationData.dateTimeFormat)
amountString = tonAmountAttributedString(cryptoValue, integralFont: integralFont, fractionalFont: fractionalFont, color: item.presentationData.theme.list.itemPrimaryTextColor)
amountString = tonAmountAttributedString(cryptoValue, integralFont: integralFont, fractionalFont: fractionalFont, color: item.presentationData.theme.list.itemPrimaryTextColor, decimalSeparator: item.presentationData.dateTimeFormat.decimalSeparator)
value = stats.balances.availableBalance == 0 ? "" : "\(formatTonUsdValue(stats.balances.availableBalance, rate: stats.usdRate, dateTimeFormat: item.presentationData.dateTimeFormat))"
} else if let stats = item.stats as? StarsRevenueStats {
amountString = NSAttributedString(string: presentationStringsFormattedNumber(stats.balances.availableBalance, item.presentationData.dateTimeFormat.groupingSeparator), font: integralFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)

View file

@ -201,7 +201,7 @@ private final class ValueItemNode: ASDisplayNode {
let valueString: NSAttributedString
if case .ton = mode {
valueString = tonAmountAttributedString(value, integralFont: valueFont, fractionalFont: smallValueFont, color: valueColor)
valueString = tonAmountAttributedString(value, integralFont: valueFont, fractionalFont: smallValueFont, color: valueColor, decimalSeparator: presentationData.dateTimeFormat.decimalSeparator)
} else {
valueString = NSAttributedString(string: value, font: valueFont, textColor: valueColor)
}

View file

@ -139,7 +139,7 @@ private final class SheetContent: CombinedComponent {
switch component.transaction {
case let .proceeds(amount, fromDate, toDate):
labelColor = theme.list.itemDisclosureActions.constructive.fillColor
amountString = tonAmountAttributedString(formatTonAmountText(amount, dateTimeFormat: dateTimeFormat, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor).mutableCopy() as! NSMutableAttributedString
amountString = tonAmountAttributedString(formatTonAmountText(amount, dateTimeFormat: dateTimeFormat, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor, decimalSeparator: dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
dateString = "\(stringForMediumCompactDate(timestamp: fromDate, strings: strings, dateTimeFormat: dateTimeFormat)) \(stringForMediumCompactDate(timestamp: toDate, strings: strings, dateTimeFormat: dateTimeFormat))"
titleString = strings.Monetization_TransactionInfo_Proceeds
buttonTitle = strings.Common_OK
@ -147,7 +147,7 @@ private final class SheetContent: CombinedComponent {
showPeer = true
case let .withdrawal(status, amount, date, provider, _, transactionUrl):
labelColor = theme.list.itemDestructiveColor
amountString = tonAmountAttributedString(formatTonAmountText(amount, dateTimeFormat: dateTimeFormat), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor).mutableCopy() as! NSMutableAttributedString
amountString = tonAmountAttributedString(formatTonAmountText(amount, dateTimeFormat: dateTimeFormat), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor, decimalSeparator: dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
dateString = stringForFullDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat)
switch status {
@ -166,7 +166,7 @@ private final class SheetContent: CombinedComponent {
case let .refund(amount, date, _):
labelColor = theme.list.itemDisclosureActions.constructive.fillColor
titleString = strings.Monetization_TransactionInfo_Refund
amountString = tonAmountAttributedString(formatTonAmountText(amount, dateTimeFormat: dateTimeFormat, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor).mutableCopy() as! NSMutableAttributedString
amountString = tonAmountAttributedString(formatTonAmountText(amount, dateTimeFormat: dateTimeFormat, showPlus: true), integralFont: integralFont, fractionalFont: fractionalFont, color: labelColor, decimalSeparator: dateTimeFormat.decimalSeparator).mutableCopy() as! NSMutableAttributedString
dateString = stringForFullDate(timestamp: date, strings: strings, dateTimeFormat: dateTimeFormat)
buttonTitle = strings.Common_OK
explorerUrl = nil

View file

@ -1390,7 +1390,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) }
dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
dict[-418915641] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) }
dict[-2069218660] = { return Api.payments.StarGiftWithdrawalUrl.parse_starGiftWithdrawalUrl($0) }
dict[-1877571094] = { return Api.payments.StarGifts.parse_starGifts($0) }

View file

@ -1,16 +1,21 @@
public extension Api.payments {
enum SavedStarGifts: TypeConstructorDescription {
case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
case savedStarGifts(flags: Int32, count: Int32, chatNotificationsEnabled: Api.Bool?, pinnedToTop: [Int64]?, gifts: [Api.SavedStarGift], nextOffset: String?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users):
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users):
if boxed {
buffer.appendInt32(-1779201615)
buffer.appendInt32(-418915641)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 1) != 0 {chatNotificationsEnabled!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(pinnedToTop!.count))
for item in pinnedToTop! {
serializeInt64(item, buffer: buffer, boxed: false)
}}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(gifts.count))
for item in gifts {
@ -33,8 +38,8 @@ public extension Api.payments {
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let gifts, let nextOffset, let chats, let users):
return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
case .savedStarGifts(let flags, let count, let chatNotificationsEnabled, let pinnedToTop, let gifts, let nextOffset, let chats, let users):
return ("savedStarGifts", [("flags", flags as Any), ("count", count as Any), ("chatNotificationsEnabled", chatNotificationsEnabled as Any), ("pinnedToTop", pinnedToTop as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
@ -47,29 +52,34 @@ public extension Api.payments {
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_3 = Api.parse(reader, signature: signature) as? Api.Bool
} }
var _4: [Api.SavedStarGift]?
var _4: [Int64]?
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
} }
var _5: [Api.SavedStarGift]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.SavedStarGift.self)
}
var _5: String?
if Int(_1!) & Int(1 << 0) != 0 {_5 = parseString(reader) }
var _6: [Api.Chat]?
var _6: String?
if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) }
var _7: [Api.Chat]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _7: [Api.User]?
var _8: [Api.User]?
if let _ = reader.readInt32() {
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
let _c6 = _6 != nil
let _c4 = (Int(_1!) & Int(1 << 2) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
let _c7 = _7 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, gifts: _4!, nextOffset: _5, chats: _6!, users: _7!)
let _c8 = _8 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
return Api.payments.SavedStarGifts.savedStarGifts(flags: _1!, count: _2!, chatNotificationsEnabled: _3, pinnedToTop: _4, gifts: _5!, nextOffset: _6, chats: _7!, users: _8!)
}
else {
return nil

View file

@ -9717,6 +9717,26 @@ public extension Api.functions.payments {
})
}
}
public extension Api.functions.payments {
static func toggleStarGiftsPinnedToTop(peer: Api.InputPeer, stargift: [Api.InputSavedStarGift]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
buffer.appendInt32(353626032)
peer.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stargift.count))
for item in stargift {
item.serialize(buffer, true)
}
return (FunctionDescription(name: "payments.toggleStarGiftsPinnedToTop", parameters: [("peer", String(describing: peer)), ("stargift", String(describing: stargift))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer)
var result: Api.Bool?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Bool
}
return result
})
}
}
public extension Api.functions.payments {
static func transferStarGift(stargift: Api.InputSavedStarGift, toId: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()

View file

@ -0,0 +1,16 @@
include "InstantPageBlock.fbs";
include "MediaId.fbs";
include "Media.fbs";
namespace TelegramCore;
table InstantPage {
blocks:[InstantPageBlock] (id: 0, required);
media:[Media] (id: 1, required);
isComplete:bool (id: 2);
rtl:bool (id: 3);
url:string (id: 4, required);
views:int32 (id: 5);
}
root_type InstantPage;

View file

@ -0,0 +1,235 @@
include "RichText.fbs";
include "MediaId.fbs";
include "TelegramChannel.fbs";
include "PixelDimensions.fbs";
include "RichText.fbs";
namespace TelegramCore;
union InstantPageBlock_Value {
InstantPageBlock_Unsupported,
InstantPageBlock_Title,
InstantPageBlock_Subtitle,
InstantPageBlock_AuthorDate,
InstantPageBlock_Header,
InstantPageBlock_Subheader,
InstantPageBlock_Paragraph,
InstantPageBlock_Preformatted,
InstantPageBlock_Footer,
InstantPageBlock_Divider,
InstantPageBlock_Anchor,
InstantPageBlock_List,
InstantPageBlock_BlockQuote,
InstantPageBlock_PullQuote,
InstantPageBlock_Image,
InstantPageBlock_Video,
InstantPageBlock_Audio,
InstantPageBlock_Cover,
InstantPageBlock_WebEmbed,
InstantPageBlock_PostEmbed,
InstantPageBlock_Collage,
InstantPageBlock_Slideshow,
InstantPageBlock_ChannelBanner,
InstantPageBlock_Kicker,
InstantPageBlock_Table,
InstantPageBlock_Details,
InstantPageBlock_RelatedArticles,
InstantPageBlock_Map
}
table InstantPageBlock {
value:InstantPageBlock_Value (id: 1, required);
}
table InstantPageBlock_Unsupported {}
table InstantPageBlock_Title {
text:RichText (id: 0, required);
}
table InstantPageBlock_Subtitle {
text:RichText (id: 0, required);
}
table InstantPageBlock_AuthorDate {
author:RichText (id: 0, required);
date:int32 (id: 1);
}
table InstantPageBlock_Header {
text:RichText (id: 0, required);
}
table InstantPageBlock_Subheader {
text:RichText (id: 0, required);
}
table InstantPageBlock_Paragraph {
text:RichText (id: 0, required);
}
table InstantPageBlock_Preformatted {
text:RichText (id: 0, required);
}
table InstantPageBlock_Footer {
text:RichText (id: 0, required);
}
table InstantPageBlock_Divider {}
table InstantPageBlock_Anchor {
name:string (id: 0, required);
}
table InstantPageBlock_List {
items:[InstantPageListItem] (id: 0, required);
ordered:bool (id: 1);
}
table InstantPageBlock_BlockQuote {
text:RichText (id: 0, required);
caption:RichText (id: 1, required);
}
table InstantPageBlock_PullQuote {
text:RichText (id: 0, required);
caption:RichText (id: 1, required);
}
table InstantPageBlock_Image {
id:MediaId (id: 0, required);
caption:InstantPageCaption (id: 1, required);
url:string (id: 2);
webpageId:MediaId (id: 3);
}
table InstantPageBlock_Video {
id:MediaId (id: 0, required);
caption:InstantPageCaption (id: 1, required);
autoplay:bool (id: 2);
loop:bool (id: 3);
}
table InstantPageBlock_Audio {
id:MediaId (id: 0, required);
caption:InstantPageCaption (id: 1, required);
}
table InstantPageBlock_Cover {
block:InstantPageBlock (id: 0, required);
}
table InstantPageBlock_WebEmbed {
url:string (id: 0);
html:string (id: 1);
dimensions:PixelDimensions (id: 2);
caption:InstantPageCaption (id: 3, required);
stretchToWidth:bool (id: 4);
allowScrolling:bool (id: 5);
coverId:MediaId (id: 6);
}
table InstantPageBlock_PostEmbed {
url:string (id: 0, required);
webpageId:MediaId (id: 1);
avatarId:MediaId (id: 2);
author:string (id: 3, required);
date:int32 (id: 4);
blocks:[InstantPageBlock] (id: 5, required);
caption:InstantPageCaption (id: 6, required);
}
table InstantPageBlock_Collage {
items:[InstantPageBlock] (id: 0, required);
caption:InstantPageCaption (id: 1, required);
}
table InstantPageBlock_Slideshow {
items:[InstantPageBlock] (id: 0, required);
caption:InstantPageCaption (id: 1, required);
}
table InstantPageBlock_ChannelBanner {
channel:TelegramChannel (id: 0);
}
table InstantPageBlock_Kicker {
text:RichText (id: 0, required);
}
table InstantPageBlock_Table {
title:RichText (id: 0, required);
rows:[InstantPageTableRow] (id: 1, required);
bordered:bool (id: 2);
striped:bool (id: 3);
}
table InstantPageBlock_Details {
title:RichText (id: 0, required);
blocks:[InstantPageBlock] (id: 1, required);
expanded:bool (id: 2);
}
table InstantPageBlock_RelatedArticles {
title:RichText (id: 0, required);
articles:[InstantPageRelatedArticle] (id: 1, required);
}
table InstantPageBlock_Map {
latitude:float64 (id: 0);
longitude:float64 (id: 1);
zoom:int32 (id: 2);
dimensions:PixelDimensions (id: 3, required);
caption:InstantPageCaption (id: 4, required);
}
table InstantPageCaption {
text:RichText (id: 0, required);
credit:RichText (id: 1, required);
}
union InstantPageListItem_Value {
InstantPageListItem_Text,
InstantPageListItem_Blocks,
InstantPageListItem_Unknown
}
table InstantPageListItem {
value:InstantPageListItem_Value (id: 1, required);
}
table InstantPageListItem_Text {
text:RichText (id: 0, required);
number:string (id: 1);
}
table InstantPageListItem_Blocks {
blocks:[InstantPageBlock] (id: 0, required);
number:string (id: 1);
}
table InstantPageListItem_Unknown {}
table InstantPageTableCell {
text:RichText (id: 0);
header:bool (id: 1);
alignment:int32 (id: 2);
verticalAlignment:int32 (id: 3);
colspan:int32 (id: 4);
rowspan:int32 (id: 5);
}
table InstantPageTableRow {
cells:[InstantPageTableCell] (id: 0, required);
}
table InstantPageRelatedArticle {
url:string (id: 0, required);
webpageId:MediaId (id: 1, required);
title:string (id: 2);
description:string (id: 3);
photoId:MediaId (id: 4);
author:string (id: 5);
date:int32 (id: 6);
}

View file

@ -0,0 +1,23 @@
include "MediaId.fbs";
include "TelegramMediaFile.fbs";
include "TelegramMediaImage.fbs";
namespace TelegramCore;
union Media_Value {
Media_TelegramMediaFile,
Media_TelegramMediaImage
}
table Media {
value:Media_Value (id: 1, required);
}
table Media_TelegramMediaFile {
file:TelegramMediaFile (id: 0, required);
}
table Media_TelegramMediaImage {
image:TelegramMediaImage (id: 0, required);
}
root_type Media;

View file

@ -0,0 +1,5 @@
namespace TelegramCore;
struct OptionalBool {
value:bool (id: 0);
}

View file

@ -0,0 +1,11 @@
namespace TelegramCore;
table RestrictionRule {
platform:string (id: 0, required);
reason:string (id: 1, required);
text:string (id: 2, required);
}
table PeerAccessRestrictionInfo {
rules:[RestrictionRule] (id: 0, required);
}

View file

@ -0,0 +1,31 @@
namespace TelegramCore;
table PeerEmojiStatusContentEmoji {
fileId:int64 (id: 0);
}
table PeerEmojiStatusContentStarGift {
id:int64 (id: 0);
fileId:int64 (id: 1);
title:string (id: 2, required);
slug:string (id: 3, required);
patternFileId:int64 (id: 4);
innerColor:int32 (id: 5);
outerColor:int32 (id: 6);
patternColor:int32 (id: 7);
textColor:int32 (id: 8);
}
union PeerEmojiStatusContent_Value {
PeerEmojiStatusContentEmoji,
PeerEmojiStatusContentStarGift
}
table PeerEmojiStatusContent {
value:PeerEmojiStatusContent_Value (id: 1, required);
}
table PeerEmojiStatus {
content:PeerEmojiStatusContent (id: 0, required);
expirationDate:int32 (id: 1);
}

View file

@ -0,0 +1,7 @@
namespace TelegramCore;
table PeerNameColor {
value:int32 (id: 0);
}
root_type PeerNameColor;

View file

@ -0,0 +1,95 @@
include "MediaId.fbs";
include "PixelDimensions.fbs";
namespace TelegramCore;
union RichText_Value {
RichText_Empty,
RichText_Plain,
RichText_Bold,
RichText_Italic,
RichText_Underline,
RichText_Strikethrough,
RichText_Fixed,
RichText_Url,
RichText_Email,
RichText_Concat,
RichText_Subscript,
RichText_Superscript,
RichText_Marked,
RichText_Phone,
RichText_Image,
RichText_Anchor
}
table RichText {
value:RichText_Value (id: 1, required);
}
table RichText_Empty {}
table RichText_Plain {
text:string (id: 0, required);
}
table RichText_Bold {
text:RichText (id: 0, required);
}
table RichText_Italic {
text:RichText (id: 0, required);
}
table RichText_Underline {
text:RichText (id: 0, required);
}
table RichText_Strikethrough {
text:RichText (id: 0, required);
}
table RichText_Fixed {
text:RichText (id: 0, required);
}
table RichText_Url {
text:RichText (id: 0, required);
url:string (id: 1, required);
webpageId:MediaId (id: 2);
}
table RichText_Email {
text:RichText (id: 0, required);
email:string (id: 1, required);
}
table RichText_Concat {
texts:[RichText] (id: 0, required);
}
table RichText_Subscript {
text:RichText (id: 0, required);
}
table RichText_Superscript {
text:RichText (id: 0, required);
}
table RichText_Marked {
text:RichText (id: 0, required);
}
table RichText_Phone {
text:RichText (id: 0, required);
phone:string (id: 1, required);
}
table RichText_Image {
id:MediaId (id: 0, required);
dimensions:PixelDimensions (id: 1, required);
}
table RichText_Anchor {
text:RichText (id: 0, required);
name:string (id: 1, required);
}

View file

@ -0,0 +1,6 @@
namespace TelegramCore;
table StarsAmount {
value:int64 (id: 0);
nanos:int32 (id: 1);
}

View file

@ -0,0 +1,60 @@
include "PeerId.fbs";
include "TelegramPeerAccessHash.fbs";
include "TelegramMediaImageRepresentation.fbs";
include "PeerAccessRestrictionInfo.fbs";
include "TelegramChatAdminRights.fbs";
include "TelegramChatBannedRights.fbs";
include "TelegramPeerUsername.fbs";
include "Optional.fbs";
include "PeerNameColor.fbs";
include "PeerEmojiStatus.fbs";
include "StarsAmount.fbs";
namespace TelegramCore;
table TelegramChannelInfo_Broadcast {
flags:int32 (id: 0);
}
table TelegramChannelInfo_Group {
flags:int32 (id: 0);
}
union TelegramChannelInfo_Value {
TelegramChannelInfo_Broadcast,
TelegramChannelInfo_Group
}
table TelegramChannelInfo {
value:TelegramChannelInfo_Value (id: 1, required);
}
table TelegramChannel {
id:PeerId (id: 0, required);
accessHash:TelegramPeerAccessHash (id: 1);
title:string (id: 2, required);
username:string (id: 3);
photo:[TelegramMediaImageRepresentation] (id: 4);
creationDate:int32 (id: 5);
version:int32 (id: 6);
participationStatus:int32 (id: 7);
info:TelegramChannelInfo (id: 8, required);
flags:int32 (id: 9);
restrictionInfo:PeerAccessRestrictionInfo (id: 10);
adminRights:TelegramChatAdminRights (id: 11);
bannedRights:TelegramChatBannedRights (id: 12);
defaultBannedRights:TelegramChatBannedRights (id: 13);
usernames:[TelegramPeerUsername] (id: 14);
storiesHidden:OptionalBool (id: 15);
nameColor:PeerNameColor (id: 16);
backgroundEmojiId:int64 (id: 17);
profileColor:PeerNameColor (id: 18);
profileBackgroundEmojiId:int64 (id: 19);
emojiStatus:PeerEmojiStatus (id: 20);
approximateBoostLevel:int32 (id: 21);
subscriptionUntilDate:int32 (id: 22);
verificationIconFileId:int64 (id: 23);
sendPaidMessageStars:StarsAmount (id: 24);
}
root_type TelegramChannel;

View file

@ -0,0 +1,7 @@
namespace TelegramCore;
table TelegramChatAdminRights {
rights:int32 (id: 0);
}
root_type TelegramChatAdminRights;

View file

@ -0,0 +1,7 @@
namespace TelegramCore;
table TelegramChatBannedRights {
flags:int32 (id: 0);
untilDate:int32 (id: 1);
}

View file

@ -0,0 +1,48 @@
include "PeerId.fbs";
include "TelegramMediaImageRepresentation.fbs";
include "TelegramChatAdminRights.fbs";
include "TelegramChatBannedRights.fbs";
namespace TelegramCore;
table TelegramGroupRole_Creator {
rank:string (id: 0);
}
table TelegramGroupRole_Admin {
rights:TelegramChatAdminRights (id: 0, required);
rank:string (id: 1);
}
table TelegramGroupRole_Member {}
union TelegramGroupRole_Value {
TelegramGroupRole_Creator,
TelegramGroupRole_Admin,
TelegramGroupRole_Member
}
table TelegramGroupRole {
value:TelegramGroupRole_Value (id: 1, required);
}
table TelegramGroupToChannelMigrationReference {
peerId:int64 (id: 0);
accessHash:int64 (id: 1);
}
table TelegramGroup {
id:PeerId (id: 0, required);
title:string (id: 1, required);
photo:[TelegramMediaImageRepresentation] (id: 2, required);
participantCount:int32 (id: 3);
role:TelegramGroupRole (id: 4, required);
membership:int32 (id: 5);
flags:int32 (id: 6);
defaultBannedRights:TelegramChatBannedRights (id: 7);
migrationReference:TelegramGroupToChannelMigrationReference (id: 8);
creationDate:int32 (id: 9);
version:int32 (id: 10);
}
root_type TelegramGroup;

View file

@ -0,0 +1,18 @@
namespace TelegramCore;
table TelegramPeerAccessHash_Personal {
accessHash:int64 (id: 0);
}
table TelegramPeerAccessHash_GenericPublic {
accessHash:int64 (id: 0);
}
union TelegramPeerAccessHash_Value {
TelegramPeerAccessHash_Personal,
TelegramPeerAccessHash_GenericPublic
}
table TelegramPeerAccessHash {
value:TelegramPeerAccessHash_Value (id: 1, required);
}

View file

@ -0,0 +1,8 @@
namespace TelegramCore;
table TelegramPeerUsername {
flags:int32 (id: 0);
username:string (id: 1, required);
}
root_type TelegramPeerUsername;

View file

@ -0,0 +1,40 @@
include "PeerId.fbs";
include "TelegramPeerAccessHash.fbs";
include "TelegramMediaImageRepresentation.fbs";
include "PeerAccessRestrictionInfo.fbs";
include "PeerEmojiStatus.fbs";
include "TelegramPeerUsername.fbs";
include "PeerNameColor.fbs";
include "Optional.fbs";
include "StarsAmount.fbs";
namespace TelegramCore;
table BotUserInfo {
flags:int32 (id: 0);
inlinePlaceholder:string (id: 1);
}
table TelegramUser {
id:PeerId (id: 0, required);
accessHash:TelegramPeerAccessHash (id: 1);
firstName:string (id: 2);
lastName:string (id: 3);
username:string (id: 4);
phone:string (id: 5);
photo:[TelegramMediaImageRepresentation] (id: 6, required);
botInfo:BotUserInfo (id: 7);
restrictionInfo:PeerAccessRestrictionInfo (id: 8);
flags:int32 (id: 9);
emojiStatus:PeerEmojiStatus (id: 10);
usernames:[TelegramPeerUsername] (id: 11);
storiesHidden:OptionalBool (id: 12);
nameColor:PeerNameColor (id: 13);
backgroundEmojiId:int64 (id: 14);
profileColor:PeerNameColor (id: 15);
profileBackgroundEmojiId:int64 (id: 16);
subscriberCount:int32 (id: 17);
verificationIconFileId:int64 (id: 18);
}
root_type TelegramUser;

View file

@ -190,6 +190,7 @@ private var declaredEncodables: Void = {
declareEncodable(OutgoingScheduleInfoMessageAttribute.self, f: { OutgoingScheduleInfoMessageAttribute(decoder: $0) })
declareEncodable(UpdateMessageReactionsAction.self, f: { UpdateMessageReactionsAction(decoder: $0) })
declareEncodable(SendStarsReactionsAction.self, f: { SendStarsReactionsAction(decoder: $0) })
declareEncodable(PostponeSendPaidMessageAction.self, f: { PostponeSendPaidMessageAction(decoder: $0) })
declareEncodable(RestrictedContentMessageAttribute.self, f: { RestrictedContentMessageAttribute(decoder: $0) })
declareEncodable(SendScheduledMessageImmediatelyAction.self, f: { SendScheduledMessageImmediatelyAction(decoder: $0) })
declareEncodable(EmbeddedMediaStickersMessageAttribute.self, f: { EmbeddedMediaStickersMessageAttribute(decoder: $0) })

View file

@ -904,7 +904,7 @@ extension StoreMessage {
}
if let paidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: StarsAmount(value: paidMessageStars, nanos: 0)))
attributes.append(PaidStarsMessageAttribute(stars: StarsAmount(value: paidMessageStars, nanos: 0), postponeSending: false))
}
var entitiesAttribute: TextEntitiesMessageAttribute?

View file

@ -1,7 +1,6 @@
import Foundation
import Postbox
public enum TelegramChannelPermission {
case sendText
case sendPhoto

View file

@ -129,6 +129,7 @@ public func standaloneSendEnqueueMessages(
struct MessageResult {
var result: PendingMessageUploadedContentResult
var media: [Media]
var attributes: [MessageAttribute]
}
let signals: [Signal<MessageResult, PendingMessageUploadError>] = messages.map { message in
@ -178,7 +179,10 @@ public func standaloneSendEnqueueMessages(
if message.isSilent {
attributes.append(NotificationInfoMessageAttribute(flags: .muted))
}
if let sendPaidMessageStars = message.sendPaidMessageStars {
attributes.append(PaidStarsMessageAttribute(stars: sendPaidMessageStars, postponeSending: false))
}
let content = messageContentToUpload(accountPeerId: accountPeerId, network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: { _, _, _, _ in
return .single(nil)
}, messageMediaPreuploadManager: MessageMediaPreuploadManager(), revalidationContext: MediaReferenceRevalidationContext(), forceReupload: false, isGrouped: false, passFetchProgress: true, forceNoBigParts: false, peerId: peerId, messageId: nil, attributes: attributes, text: text, media: media)
@ -191,7 +195,7 @@ public func standaloneSendEnqueueMessages(
}
return contentResult
|> map { contentResult in
return MessageResult(result: contentResult, media: media)
return MessageResult(result: contentResult, media: media, attributes: attributes)
}
}
@ -201,7 +205,7 @@ public func standaloneSendEnqueueMessages(
}
|> mapToSignal { contentResults -> Signal<StandaloneSendMessageStatus, StandaloneSendMessagesError> in
var progressSum: Float = 0.0
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media])] = []
var allResults: [(result: PendingMessageUploadedContentAndReuploadInfo, media: [Media], attributes: [MessageAttribute])] = []
var allDone = true
for result in contentResults {
switch result.result {
@ -209,13 +213,13 @@ public func standaloneSendEnqueueMessages(
allDone = false
progressSum += value.progress
case let .content(content):
allResults.append((content, result.media))
allResults.append((content, result.media, result.attributes))
}
}
if allDone {
var sendSignals: [Signal<Never, StandaloneSendMessagesError>] = []
for (content, media) in allResults {
for (content, media, attributes) in allResults {
var text: String = ""
switch content.content {
case let .text(textValue):
@ -235,7 +239,7 @@ public func standaloneSendEnqueueMessages(
peerId: peerId,
content: content,
text: text,
attributes: [],
attributes: attributes,
media: media,
threadId: threadId
))
@ -328,6 +332,7 @@ private func sendUploadedMessageContent(
var videoTimestamp: Int32?
var sendAsPeerId: PeerId?
var bubbleUpEmojiOrStickersets = false
var allowPaidStars: Int64?
var flags: Int32 = 0
@ -365,6 +370,8 @@ private func sendUploadedMessageContent(
} else if let attribute = attribute as? ForwardVideoTimestampAttribute {
flags |= Int32(1 << 20)
videoTimestamp = attribute.timestamp
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
}
}
@ -390,6 +397,11 @@ private func sendUploadedMessageContent(
flags |= (1 << 13)
}
if let _ = allowPaidStars {
flags |= 1 << 21
}
let dependencyTag: PendingMessageRequestDependencyTag? = nil//(messageId: messageId)
let sendMessageRequest: Signal<NetworkRequestResult<Api.Updates>, MTRpcError>
@ -415,7 +427,7 @@ private func sendUploadedMessageContent(
}
}
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil), info: .acknowledgement, tag: dependencyTag)
sendMessageRequest = network.requestWithAdditionalInfo(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), info: .acknowledgement, tag: dependencyTag)
case let .media(inputMedia, text):
if bubbleUpEmojiOrStickersets {
flags |= Int32(1 << 15)
@ -437,7 +449,7 @@ private func sendUploadedMessageContent(
}
}
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil), tag: dependencyTag)
sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars), tag: dependencyTag)
|> map(NetworkRequestResult.result)
case let .forward(sourceInfo):
var topMsgId: Int32?
@ -447,7 +459,7 @@ private func sendUploadedMessageContent(
}
if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) {
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: nil), tag: dependencyTag)
sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars), tag: dependencyTag)
|> map(NetworkRequestResult.result)
} else {
sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal"))
@ -473,7 +485,7 @@ private func sendUploadedMessageContent(
}
}
sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: nil))
sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyTo: replyTo, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, allowPaidStars: allowPaidStars))
|> map(NetworkRequestResult.result)
case .messageScreenshot:
let replyTo: Api.InputReplyTo
@ -585,6 +597,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
var replyToStoryId: StoryId?
var scheduleTime: Int32?
var sendAsPeerId: PeerId?
var allowPaidStars: Int64?
var flags: Int32 = 0
flags |= (1 << 7)
@ -609,6 +622,8 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
scheduleTime = attribute.scheduleTime
} else if let attribute = attribute as? SendAsMessageAttribute {
sendAsPeerId = attribute.peerId
} else if let attribute = attribute as? PaidStarsMessageAttribute {
allowPaidStars = attribute.stars.value
}
}
@ -622,6 +637,11 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
flags |= (1 << 13)
}
if let _ = allowPaidStars {
flags |= 1 << 21
}
let sendMessageRequest: Signal<Api.Updates, NoError>
switch content {
case let .text(text):
@ -641,7 +661,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil)
}
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil))
sendMessageRequest = account.network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyTo: replyTo, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars))
|> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete()
}
@ -662,7 +682,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M
replyTo = .inputReplyToMessage(flags: flags, replyToMsgId: threadId, topMsgId: threadId, replyToPeerId: nil, quoteText: nil, quoteEntities: nil, quoteOffset: nil)
}
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: nil))
sendMessageRequest = account.network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyTo: replyTo, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, effect: nil, allowPaidStars: allowPaidStars))
|> `catch` { _ -> Signal<Api.Updates, NoError> in
return .complete()
}

View file

@ -331,6 +331,16 @@ public final class AccountStateManager {
return self.forceSendPendingStarsReactionPipe.signal()
}
fileprivate let forceSendPendingPaidMessagePipe = ValuePipe<PeerId>()
public var forceSendPendingPaidMessage: Signal<PeerId, NoError> {
return self.forceSendPendingPaidMessagePipe.signal()
}
fileprivate let commitSendPendingPaidMessagePipe = ValuePipe<MessageId>()
public var commitSendPendingPaidMessage: Signal<MessageId, NoError> {
return self.commitSendPendingPaidMessagePipe.signal()
}
fileprivate let sentScheduledMessageIdsPipe = ValuePipe<Set<MessageId>>()
public var sentScheduledMessageIds: Signal<Set<MessageId>, NoError> {
return self.sentScheduledMessageIdsPipe.signal()
@ -1951,6 +1961,18 @@ public final class AccountStateManager {
}
}
var forceSendPendingPaidMessage: Signal<PeerId, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.forceSendPendingPaidMessage.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
var commitSendPendingPaidMessage: Signal<MessageId, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.commitSendPendingPaidMessage.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
}
}
public var sentScheduledMessageIds: Signal<Set<MessageId>, NoError> {
return self.impl.signalWith { impl, subscriber in
return impl.sentScheduledMessageIds.start(next: subscriber.putNext, error: subscriber.putError, completed: subscriber.putCompletion)
@ -1963,6 +1985,19 @@ public final class AccountStateManager {
}
}
func forceSendPendingPaidMessage(peerId: PeerId) {
self.impl.with { impl in
impl.forceSendPendingPaidMessagePipe.putNext(peerId)
}
}
func commitSendPendingPaidMessage(messageId: MessageId) {
self.impl.with { impl in
impl.commitSendPendingPaidMessagePipe.putNext(messageId)
}
}
var updateConfigRequested: (() -> Void)?
var isPremiumUpdated: (() -> Void)?

View file

@ -88,6 +88,9 @@ final class AccountTaskManager {
tasks.add(managedSynchronizeMarkAllUnseenReactionsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedApplyPendingMessageReactionsActions(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedApplyPendingMessageStarsReactionsActions(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedApplyPendingPaidMessageActions(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedSynchronizeEmojiKeywordsOperations(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedApplyPendingScheduledMessagesActions(postbox: self.stateManager.postbox, network: self.stateManager.network, stateManager: self.stateManager).start())
tasks.add(managedSynchronizeAvailableReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start())

View file

@ -160,7 +160,7 @@ public final class AvailableReactions: Equatable, Codable {
if let staticIconData = try container.decodeIfPresent(Data.self, forKey: .staticIconData) {
var byteBuffer = ByteBuffer(data: staticIconData)
self.staticIcon = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, staticIconData)
self.staticIcon = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, staticIconData)
} else {
let staticIconData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .staticIcon)
self.staticIcon = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: staticIconData.data))))
@ -168,7 +168,7 @@ public final class AvailableReactions: Equatable, Codable {
if let appearAnimationData = try container.decodeIfPresent(Data.self, forKey: .appearAnimationData) {
var byteBuffer = ByteBuffer(data: appearAnimationData)
self.appearAnimation = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, appearAnimationData)
self.appearAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, appearAnimationData)
} else {
let appearAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .appearAnimation)
self.appearAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: appearAnimationData.data))))
@ -176,7 +176,7 @@ public final class AvailableReactions: Equatable, Codable {
if let selectAnimationData = try container.decodeIfPresent(Data.self, forKey: .selectAnimationData) {
var byteBuffer = ByteBuffer(data: selectAnimationData)
self.selectAnimation = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, selectAnimationData)
self.selectAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, selectAnimationData)
} else {
let selectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .selectAnimation)
self.selectAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: selectAnimationData.data))))
@ -184,7 +184,7 @@ public final class AvailableReactions: Equatable, Codable {
if let activateAnimationData = try container.decodeIfPresent(Data.self, forKey: .activateAnimationData) {
var byteBuffer = ByteBuffer(data: activateAnimationData)
self.activateAnimation = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, activateAnimationData)
self.activateAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, activateAnimationData)
} else {
let activateAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .activateAnimation)
self.activateAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: activateAnimationData.data))))
@ -192,7 +192,7 @@ public final class AvailableReactions: Equatable, Codable {
if let effectAnimationData = try container.decodeIfPresent(Data.self, forKey: .effectAnimationData) {
var byteBuffer = ByteBuffer(data: effectAnimationData)
self.effectAnimation = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, effectAnimationData)
self.effectAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, effectAnimationData)
} else {
let effectAnimationData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: .effectAnimation)
self.effectAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: effectAnimationData.data))))
@ -200,7 +200,7 @@ public final class AvailableReactions: Equatable, Codable {
if let aroundAnimationData = try container.decodeIfPresent(Data.self, forKey: .aroundAnimationData) {
var byteBuffer = ByteBuffer(data: aroundAnimationData)
self.aroundAnimation = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, aroundAnimationData)
self.aroundAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, aroundAnimationData)
} else if let aroundAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .aroundAnimation) {
self.aroundAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: aroundAnimationData.data))))
} else {
@ -209,7 +209,7 @@ public final class AvailableReactions: Equatable, Codable {
if let centerAnimationData = try container.decodeIfPresent(Data.self, forKey: .centerAnimationData) {
var byteBuffer = ByteBuffer(data: centerAnimationData)
self.centerAnimation = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, centerAnimationData)
self.centerAnimation = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, centerAnimationData)
} else if let centerAnimationData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: .centerAnimation) {
self.centerAnimation = TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: centerAnimationData.data))))
} else {

View file

@ -89,4 +89,169 @@ func _internal_updateChannelPaidMessagesStars(account: Account, peerId: PeerId,
|> switchToLatest
}
public final class PostponeSendPaidMessageAction: PendingMessageActionData {
public let randomId: Int64
public init(randomId: Int64) {
self.randomId = randomId
}
public init(decoder: PostboxDecoder) {
self.randomId = decoder.decodeInt64ForKey("id", orElse: 0)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeInt64(self.randomId, forKey: "id")
}
public func isEqual(to: PendingMessageActionData) -> Bool {
if let other = to as? PostponeSendPaidMessageAction {
if self.randomId != other.randomId {
return false
}
return true
} else {
return false
}
}
}
private final class ManagedApplyPendingPaidMessageActionsHelper {
var operationDisposables: [MessageId: (PendingMessageActionData, Disposable)] = [:]
func update(entries: [PendingMessageActionsEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) {
var disposeOperations: [Disposable] = []
var beginOperations: [(PendingMessageActionsEntry, MetaDisposable)] = []
var hasRunningOperationForPeerId = Set<PeerId>()
var validIds = Set<MessageId>()
for entry in entries {
if let current = self.operationDisposables[entry.id], !current.0.isEqual(to: entry.action) {
self.operationDisposables.removeValue(forKey: entry.id)
disposeOperations.append(current.1)
}
if !hasRunningOperationForPeerId.contains(entry.id.peerId) {
hasRunningOperationForPeerId.insert(entry.id.peerId)
validIds.insert(entry.id)
let disposable = MetaDisposable()
beginOperations.append((entry, disposable))
self.operationDisposables[entry.id] = (entry.action, disposable)
}
}
var removeMergedIds: [MessageId] = []
for (id, actionAndDisposable) in self.operationDisposables {
if !validIds.contains(id) {
removeMergedIds.append(id)
disposeOperations.append(actionAndDisposable.1)
}
}
for id in removeMergedIds {
self.operationDisposables.removeValue(forKey: id)
}
return (disposeOperations, beginOperations)
}
func reset() -> [Disposable] {
let disposables = Array(self.operationDisposables.values.map(\.1))
self.operationDisposables.removeAll()
return disposables
}
}
private func withTakenStarsAction(postbox: Postbox, type: PendingMessageActionType, id: MessageId, _ f: @escaping (Transaction, PendingMessageActionsEntry?) -> Signal<Never, NoError>) -> Signal<Never, NoError> {
return postbox.transaction { transaction -> Signal<Never, NoError> in
var result: PendingMessageActionsEntry?
if let action = transaction.getPendingMessageAction(type: type, id: id) as? PostponeSendPaidMessageAction {
result = PendingMessageActionsEntry(id: id, action: action)
}
return f(transaction, result)
}
|> switchToLatest
}
private func sendPostponedPaidMessage(transaction: Transaction, postbox: Postbox, network: Network, stateManager: AccountStateManager, id: MessageId) -> Signal<Never, NoError> {
stateManager.commitSendPendingPaidMessage(messageId: id)
return postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .sendPostponedPaidMessage, id: id, action: nil)
}
|> ignoreValues
}
func managedApplyPendingPaidMessageActions(postbox: Postbox, network: Network, stateManager: AccountStateManager) -> Signal<Void, NoError> {
return Signal { _ in
let helper = Atomic<ManagedApplyPendingPaidMessageActionsHelper>(value: ManagedApplyPendingPaidMessageActionsHelper())
let actionsKey = PostboxViewKey.pendingMessageActions(type: .sendPostponedPaidMessage)
let disposable = postbox.combinedView(keys: [actionsKey]).start(next: { view in
var entries: [PendingMessageActionsEntry] = []
if let v = view.views[actionsKey] as? PendingMessageActionsView {
entries = v.entries
}
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PendingMessageActionsEntry, MetaDisposable)]) in
return helper.update(entries: entries)
}
for disposable in disposeOperations {
disposable.dispose()
}
for (entry, disposable) in beginOperations {
let signal = withTakenStarsAction(postbox: postbox, type: .sendPostponedPaidMessage, id: entry.id, { transaction, entry -> Signal<Never, NoError> in
if let entry = entry {
if let _ = entry.action as? PostponeSendPaidMessageAction {
let triggerSignal: Signal<Void, NoError> = stateManager.forceSendPendingPaidMessage
|> filter {
$0 == entry.id.peerId
}
|> map { _ -> Void in
return Void()
}
|> take(1)
|> timeout(5.0, queue: .mainQueue(), alternate: .single(Void()))
return triggerSignal
|> mapToSignal { _ -> Signal<Never, NoError> in
return sendPostponedPaidMessage(transaction: transaction, postbox: postbox, network: network, stateManager: stateManager, id: entry.id)
}
} else {
assertionFailure()
}
}
return .complete()
})
|> then(
postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .sendPostponedPaidMessage, id: entry.id, action: nil)
}
|> ignoreValues
)
disposable.set(signal.start())
}
})
return ActionDisposable {
let disposables = helper.with { helper -> [Disposable] in
return helper.reset()
}
for disposable in disposables {
disposable.dispose()
}
disposable.dispose()
}
}
}
func _internal_forceSendPostponedPaidMessage(account: Account, peerId: PeerId) -> Signal<Never, NoError> {
account.stateManager.forceSendPendingPaidMessage(peerId: peerId)
return .complete()
}

View file

@ -63,6 +63,8 @@ private final class PendingMessageContext {
var error: PendingMessageFailureReason?
var statusSubscribers = Bag<(PendingMessageStatus?, PendingMessageFailureReason?) -> Void>()
var forcedReuploadOnce: Bool = false
let postponeDisposable = MetaDisposable()
var postponeSending = false
}
public enum PendingMessageFailureReason {
@ -486,7 +488,10 @@ public final class PendingMessageManager {
for (messageContext, message, type, contentUploadSignal) in messagesToUpload {
if strongSelf.canBeginUploadingMessage(id: message.id, type: type) {
if let paidStarsAttribute = message.paidStarsAttribute, paidStarsAttribute.postponeSending {
strongSelf.beginWaitingForPostponedMessageCommit(messageContext: messageContext, id: message.id)
}
if strongSelf.canBeginUploadingMessage(id: message.id, type: type), !messageContext.postponeSending {
strongSelf.beginUploadingMessage(messageContext: messageContext, id: message.id, threadId: message.threadId, groupId: message.groupingKey, uploadSignal: contentUploadSignal)
} else {
messageContext.state = .waitingForUploadToStart(groupId: message.groupingKey, upload: contentUploadSignal)
@ -663,6 +668,33 @@ public final class PendingMessageManager {
messageContext.state = .collectingInfo(message: message)
}
private func beginWaitingForPostponedMessageCommit(messageContext: PendingMessageContext, id: MessageId) {
messageContext.postponeSending = true
let signal: Signal<Void, NoError> = self.postbox.transaction { transaction -> Void in
transaction.setPendingMessageAction(type: .sendPostponedPaidMessage, id: id, action: PostponeSendPaidMessageAction(randomId: Int64.random(in: Int64.min ... Int64.max)))
}
|> mapToSignal { _ in
return self.stateManager.commitSendPendingPaidMessage
|> filter {
$0 == id
}
|> take(1)
|> map { _ in
Void()
}
}
|> deliverOn(self.queue)
messageContext.postponeDisposable.set(signal.start(next: { [weak self] _ in
guard let self else {
return
}
messageContext.postponeSending = false
self.updateWaitingUploads(peerId: id.peerId)
}))
}
private func beginUploadingMessage(messageContext: PendingMessageContext, id: MessageId, threadId: Int64?, groupId: Int64?, uploadSignal: Signal<PendingMessageUploadedContentResult, PendingMessageUploadError>) {
messageContext.state = .uploading(groupId: groupId)
@ -740,7 +772,7 @@ public final class PendingMessageManager {
loop: for contextId in messageIdsForPeer {
let context = self.messageContexts[contextId]!
if case let .waitingForUploadToStart(groupId, uploadSignal) = context.state {
if self.canBeginUploadingMessage(id: contextId, type: context.contentType ?? .media) {
if self.canBeginUploadingMessage(id: contextId, type: context.contentType ?? .media), !context.postponeSending {
context.state = .uploading(groupId: groupId)
let status = PendingMessageStatus(isRunning: true, progress: PendingMessageStatus.Progress(progress: 0.0))
context.status = status

View file

@ -4,20 +4,24 @@ import TelegramApi
public final class PaidStarsMessageAttribute: Equatable, MessageAttribute {
public let stars: StarsAmount
public let postponeSending: Bool
public init(stars: StarsAmount) {
public init(stars: StarsAmount, postponeSending: Bool) {
self.stars = stars
self.postponeSending = postponeSending
}
required public init(decoder: PostboxDecoder) {
self.stars = decoder.decodeCodable(StarsAmount.self, forKey: "s") ?? StarsAmount(value: 0, nanos: 0)
self.postponeSending = decoder.decodeBoolForKey("ps", orElse: false)
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeCodable(self.stars, forKey: "s")
encoder.encodeBool(self.postponeSending, forKey: "ps")
}
public static func ==(lhs: PaidStarsMessageAttribute, rhs: PaidStarsMessageAttribute) -> Bool {
return lhs.stars == rhs.stars
return lhs.stars == rhs.stars && lhs.postponeSending == rhs.postponeSending
}
}

View file

@ -25,6 +25,7 @@ public struct CachedChannelFlags: OptionSet {
public static let paidMediaAllowed = CachedChannelFlags(rawValue: 1 << 11)
public static let canViewStarsRevenue = CachedChannelFlags(rawValue: 1 << 12)
public static let starGiftsAvailable = CachedChannelFlags(rawValue: 1 << 13)
public static let paidMessagesAvailable = CachedChannelFlags(rawValue: 1 << 14)
}
public struct CachedChannelParticipantsSummary: PostboxCoding, Equatable {

View file

@ -2,6 +2,8 @@ import Foundation
import Postbox
import TelegramApi
import SwiftSignalKit
import FlatBuffers
import FlatSerialization
public enum CachedPeerAutoremoveTimeout: Equatable, PostboxCoding {
public struct Value: Equatable, PostboxCoding {
@ -261,6 +263,16 @@ public enum PeerNameColor: Hashable {
return value
}
}
public init(flatBuffersObject: TelegramCore_PeerNameColor) throws {
self.init(rawValue: flatBuffersObject.value)
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let start = TelegramCore_PeerNameColor.startPeerNameColor(&builder)
TelegramCore_PeerNameColor.add(value: self.rawValue, &builder)
return TelegramCore_PeerNameColor.endPeerNameColor(&builder, start: start)
}
}
public struct PeerEmojiStatus: Equatable, Codable {
@ -320,6 +332,68 @@ public struct PeerEmojiStatus: Equatable, Codable {
try container.encode(textColor, forKey: .textColor)
}
}
public init(flatBuffersObject: TelegramCore_PeerEmojiStatusContent) throws {
switch flatBuffersObject.valueType {
case .peeremojistatuscontentemoji:
guard let emoji = flatBuffersObject.value(type: TelegramCore_PeerEmojiStatusContentEmoji.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .emoji(fileId: emoji.fileId)
case .peeremojistatuscontentstargift:
guard let starGift = flatBuffersObject.value(type: TelegramCore_PeerEmojiStatusContentStarGift.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .starGift(
id: starGift.id,
fileId: starGift.fileId,
title: starGift.title,
slug: starGift.slug,
patternFileId: starGift.patternFileId,
innerColor: starGift.innerColor,
outerColor: starGift.outerColor,
patternColor: starGift.patternColor,
textColor: starGift.textColor
)
case .none_:
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let valueType: TelegramCore_PeerEmojiStatusContent_Value
let valueOffset: Offset
switch self {
case let .emoji(fileId):
valueType = .peeremojistatuscontentemoji
let start = TelegramCore_PeerEmojiStatusContentEmoji.startPeerEmojiStatusContentEmoji(&builder)
TelegramCore_PeerEmojiStatusContentEmoji.add(fileId: fileId, &builder)
valueOffset = TelegramCore_PeerEmojiStatusContentEmoji.endPeerEmojiStatusContentEmoji(&builder, start: start)
case let .starGift(id, fileId, title, slug, patternFileId, innerColor, outerColor, patternColor, textColor):
valueType = .peeremojistatuscontentstargift
let titleOffset = builder.create(string: title)
let slugOffset = builder.create(string: slug)
let start = TelegramCore_PeerEmojiStatusContentStarGift.startPeerEmojiStatusContentStarGift(&builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(id: id, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(fileId: fileId, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(title: titleOffset, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(slug: slugOffset, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(patternFileId: patternFileId, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(innerColor: innerColor, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(outerColor: outerColor, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(patternColor: patternColor, &builder)
TelegramCore_PeerEmojiStatusContentStarGift.add(textColor: textColor, &builder)
valueOffset = TelegramCore_PeerEmojiStatusContentStarGift.endPeerEmojiStatusContentStarGift(&builder, start: start)
}
let start = TelegramCore_PeerEmojiStatusContent.startPeerEmojiStatusContent(&builder)
TelegramCore_PeerEmojiStatusContent.add(valueType: valueType, &builder)
TelegramCore_PeerEmojiStatusContent.add(value: valueOffset, &builder)
return TelegramCore_PeerEmojiStatusContent.endPeerEmojiStatusContent(&builder, start: start)
}
}
public var content: Content
public var expirationDate: Int32?
@ -348,6 +422,20 @@ public struct PeerEmojiStatus: Equatable, Codable {
try container.encode(self.content, forKey: .content)
try container.encodeIfPresent(self.expirationDate, forKey: .expirationDate)
}
public init(flatBuffersObject: TelegramCore_PeerEmojiStatus) throws {
self.content = try Content(flatBuffersObject: flatBuffersObject.content)
self.expirationDate = flatBuffersObject.expirationDate == Int32.min ? nil : flatBuffersObject.expirationDate
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let contentOffset = self.content.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_PeerEmojiStatus.startPeerEmojiStatus(&builder)
TelegramCore_PeerEmojiStatus.add(content: contentOffset, &builder)
TelegramCore_PeerEmojiStatus.add(expirationDate: self.expirationDate ?? Int32.min, &builder)
return TelegramCore_PeerEmojiStatus.endPeerEmojiStatus(&builder, start: start)
}
}
extension PeerEmojiStatus {

View file

@ -961,7 +961,7 @@ public final class WallpaperDataResource: TelegramMediaResource {
public func TelegramMediaResource_parse(flatBuffersData data: Data) throws -> TelegramMediaResource {
var byteBuffer = ByteBuffer(data: data)
let flatBuffersObject: TelegramCore_TelegramMediaResource = getRoot(byteBuffer: &byteBuffer)
let flatBuffersObject: TelegramCore_TelegramMediaResource = FlatBuffers_getRoot(byteBuffer: &byteBuffer)
return try TelegramMediaResource_parse(flatBuffersObject: flatBuffersObject)
}
@ -970,7 +970,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
switch flatBuffersObject.valueType {
case .telegrammediaresourceCloudfilemediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_CloudFileMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
return CloudFileMediaResource(
datacenterId: Int(value.datacenterId),
@ -982,7 +982,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
)
case .telegrammediaresourceClouddocumentsizemediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_CloudDocumentSizeMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
return CloudDocumentSizeMediaResource(
datacenterId: value.datacenterId,
@ -993,7 +993,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
)
case .telegrammediaresourceCloudphotosizemediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
return CloudPhotoSizeMediaResource(
datacenterId: value.datacenterId,
@ -1005,7 +1005,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
)
case .telegrammediaresourceCloudpeerphotosizemediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_CloudPeerPhotoSizeMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
let sizeSpec: CloudPeerPhotoSizeSpec
switch value.sizeSpec {
@ -1023,7 +1023,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
)
case .telegrammediaresourceCloudstickerpackthumbnailmediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_CloudStickerPackThumbnailMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
return CloudStickerPackThumbnailMediaResource(
datacenterId: value.datacenterId,
@ -1033,7 +1033,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
)
case .telegrammediaresourceClouddocumentmediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_CloudDocumentMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
return CloudDocumentMediaResource(
datacenterId: Int(value.datacenterId),
@ -1045,7 +1045,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
)
case .telegrammediaresourceLocalfilemediaresource:
guard let value = flatBuffersObject.value(type: TelegramCore_TelegramMediaResource_LocalFileMediaResource.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
return LocalFileMediaResource(
fileId: value.fileId,
@ -1053,7 +1053,7 @@ public func TelegramMediaResource_parse(flatBuffersObject: TelegramCore_Telegram
isSecretRelated: value.isSecretRelated
)
case .none_:
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}
@ -1094,9 +1094,10 @@ public func TelegramMediaResource_serialize(resource: TelegramMediaResource, fla
return TelegramCore_TelegramMediaResource.createTelegramMediaResource(&builder, valueType: .telegrammediaresourceClouddocumentsizemediaresource, valueOffset: offset)
case let resource as CloudPhotoSizeMediaResource:
let sizeSpecOffset = builder.create(string: resource.sizeSpec)
let start = TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.startTelegramMediaResource_CloudPhotoSizeMediaResource(&builder)
let fileReferenceOffset = resource.fileReference.flatMap { builder.createVector(bytes: $0) }
let start = TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.startTelegramMediaResource_CloudPhotoSizeMediaResource(&builder)
TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.add(datacenterId: Int32(resource.datacenterId), &builder)
TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.add(photoId: resource.photoId, &builder)
TelegramCore_TelegramMediaResource_CloudPhotoSizeMediaResource.add(accessHash: resource.accessHash, &builder)

View file

@ -1,4 +1,7 @@
import Foundation
import Postbox
import FlatBuffers
import FlatSerialization
private enum InstantPageBlockType: Int32 {
case unsupported = 0
@ -512,6 +515,395 @@ public indirect enum InstantPageBlock: PostboxCoding, Equatable {
}
}
}
public init(flatBuffersObject: TelegramCore_InstantPageBlock) throws {
switch flatBuffersObject.valueType {
case .instantpageblockUnsupported:
self = .unsupported
case .instantpageblockTitle:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Title.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .title(try RichText(flatBuffersObject: value.text))
case .instantpageblockSubtitle:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Subtitle.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .subtitle(try RichText(flatBuffersObject: value.text))
case .instantpageblockAuthordate:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_AuthorDate.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .authorDate(author: try RichText(flatBuffersObject: value.author), date: value.date)
case .instantpageblockHeader:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Header.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .header(try RichText(flatBuffersObject: value.text))
case .instantpageblockSubheader:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Subheader.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .subheader(try RichText(flatBuffersObject: value.text))
case .instantpageblockParagraph:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Paragraph.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .paragraph(try RichText(flatBuffersObject: value.text))
case .instantpageblockPreformatted:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Preformatted.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .preformatted(try RichText(flatBuffersObject: value.text))
case .instantpageblockFooter:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Footer.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .footer(try RichText(flatBuffersObject: value.text))
case .instantpageblockDivider:
self = .divider
case .instantpageblockAnchor:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Anchor.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .anchor(value.name)
case .instantpageblockList:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_List.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .list(items: try (0 ..< value.itemsCount).map { try InstantPageListItem(flatBuffersObject: value.items(at: $0)!) }, ordered: value.ordered)
case .instantpageblockBlockquote:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_BlockQuote.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .blockQuote(text: try RichText(flatBuffersObject: value.text), caption: try RichText(flatBuffersObject: value.caption))
case .instantpageblockPullquote:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_PullQuote.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .pullQuote(text: try RichText(flatBuffersObject: value.text), caption: try RichText(flatBuffersObject: value.caption))
case .instantpageblockImage:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Image.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .image(id: MediaId(value.id), caption: try InstantPageCaption(flatBuffersObject: value.caption), url: value.url, webpageId: value.webpageId.flatMap(MediaId.init))
case .instantpageblockVideo:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Video.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .video(id: MediaId(value.id), caption: try InstantPageCaption(flatBuffersObject: value.caption), autoplay: value.autoplay, loop: value.loop)
case .instantpageblockAudio:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Audio.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .audio(id: MediaId(value.id), caption: try InstantPageCaption(flatBuffersObject: value.caption))
case .instantpageblockCover:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Cover.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .cover(try InstantPageBlock(flatBuffersObject: value.block))
case .instantpageblockWebembed:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_WebEmbed.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .webEmbed(url: value.url, html: value.html, dimensions: value.dimensions.flatMap(PixelDimensions.init), caption: try InstantPageCaption(flatBuffersObject: value.caption), stretchToWidth: value.stretchToWidth, allowScrolling: value.allowScrolling, coverId: value.coverId.flatMap(MediaId.init))
case .instantpageblockPostembed:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_PostEmbed.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .postEmbed(url: value.url, webpageId: value.webpageId.flatMap(MediaId.init), avatarId: value.avatarId.flatMap(MediaId.init), author: value.author, date: value.date, blocks: try (0 ..< value.blocksCount).map { try InstantPageBlock(flatBuffersObject: value.blocks(at: $0)!) }, caption: try InstantPageCaption(flatBuffersObject: value.caption))
case .instantpageblockCollage:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Collage.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .collage(items: try (0 ..< value.itemsCount).map { try InstantPageBlock(flatBuffersObject: value.items(at: $0)!) }, caption: try InstantPageCaption(flatBuffersObject: value.caption))
case .instantpageblockSlideshow:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Slideshow.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .slideshow(items: try (0 ..< value.itemsCount).map { try InstantPageBlock(flatBuffersObject: value.items(at: $0)!) }, caption: try InstantPageCaption(flatBuffersObject: value.caption))
case .instantpageblockChannelbanner:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_ChannelBanner.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
let channel = try value.channel.flatMap { try TelegramChannel(flatBuffersObject: $0) }
self = .channelBanner(channel)
case .instantpageblockKicker:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Kicker.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .kicker(try RichText(flatBuffersObject: value.text))
case .instantpageblockTable:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Table.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .table(title: try RichText(flatBuffersObject: value.title), rows: try (0 ..< value.rowsCount).map { try InstantPageTableRow(flatBuffersObject: value.rows(at: $0)!) }, bordered: value.bordered, striped: value.striped)
case .instantpageblockDetails:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Details.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .details(title: try RichText(flatBuffersObject: value.title), blocks: try (0 ..< value.blocksCount).map { try InstantPageBlock(flatBuffersObject: value.blocks(at: $0)!) }, expanded: value.expanded)
case .instantpageblockRelatedarticles:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_RelatedArticles.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .relatedArticles(title: try RichText(flatBuffersObject: value.title), articles: try (0 ..< value.articlesCount).map { try InstantPageRelatedArticle(flatBuffersObject: value.articles(at: $0)!) })
case .instantpageblockMap:
guard let value = flatBuffersObject.value(type: TelegramCore_InstantPageBlock_Map.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .map(latitude: value.latitude, longitude: value.longitude, zoom: value.zoom, dimensions: PixelDimensions(value.dimensions), caption: try InstantPageCaption(flatBuffersObject: value.caption))
case .none_:
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let valueType: TelegramCore_InstantPageBlock_Value
let offset: Offset
switch self {
case .unsupported:
valueType = .instantpageblockUnsupported
let start = TelegramCore_InstantPageBlock_Unsupported.startInstantPageBlock_Unsupported(&builder)
offset = TelegramCore_InstantPageBlock_Unsupported.endInstantPageBlock_Unsupported(&builder, start: start)
case let .title(text):
valueType = .instantpageblockTitle
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Title.startInstantPageBlock_Title(&builder)
TelegramCore_InstantPageBlock_Title.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Title.endInstantPageBlock_Title(&builder, start: start)
case let .subtitle(text):
valueType = .instantpageblockSubtitle
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Subtitle.startInstantPageBlock_Subtitle(&builder)
TelegramCore_InstantPageBlock_Subtitle.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Subtitle.endInstantPageBlock_Subtitle(&builder, start: start)
case let .authorDate(author, date):
valueType = .instantpageblockAuthordate
let authorOffset = author.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_AuthorDate.startInstantPageBlock_AuthorDate(&builder)
TelegramCore_InstantPageBlock_AuthorDate.add(author: authorOffset, &builder)
TelegramCore_InstantPageBlock_AuthorDate.add(date: date, &builder)
offset = TelegramCore_InstantPageBlock_AuthorDate.endInstantPageBlock_AuthorDate(&builder, start: start)
case let .header(text):
valueType = .instantpageblockHeader
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Header.startInstantPageBlock_Header(&builder)
TelegramCore_InstantPageBlock_Header.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Header.endInstantPageBlock_Header(&builder, start: start)
case let .subheader(text):
valueType = .instantpageblockSubheader
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Subheader.startInstantPageBlock_Subheader(&builder)
TelegramCore_InstantPageBlock_Subheader.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Subheader.endInstantPageBlock_Subheader(&builder, start: start)
case let .paragraph(text):
valueType = .instantpageblockParagraph
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Paragraph.startInstantPageBlock_Paragraph(&builder)
TelegramCore_InstantPageBlock_Paragraph.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Paragraph.endInstantPageBlock_Paragraph(&builder, start: start)
case let .preformatted(text):
valueType = .instantpageblockPreformatted
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Preformatted.startInstantPageBlock_Preformatted(&builder)
TelegramCore_InstantPageBlock_Preformatted.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Preformatted.endInstantPageBlock_Preformatted(&builder, start: start)
case let .footer(text):
valueType = .instantpageblockFooter
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Footer.startInstantPageBlock_Footer(&builder)
TelegramCore_InstantPageBlock_Footer.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Footer.endInstantPageBlock_Footer(&builder, start: start)
case .divider:
valueType = .instantpageblockDivider
let start = TelegramCore_InstantPageBlock_Divider.startInstantPageBlock_Divider(&builder)
offset = TelegramCore_InstantPageBlock_Divider.endInstantPageBlock_Divider(&builder, start: start)
case let .anchor(name):
valueType = .instantpageblockAnchor
let nameOffset = builder.create(string: name)
let start = TelegramCore_InstantPageBlock_Anchor.startInstantPageBlock_Anchor(&builder)
TelegramCore_InstantPageBlock_Anchor.add(name: nameOffset, &builder)
offset = TelegramCore_InstantPageBlock_Anchor.endInstantPageBlock_Anchor(&builder, start: start)
case let .list(items, ordered):
valueType = .instantpageblockList
let itemsOffsets = items.map { $0.encodeToFlatBuffers(builder: &builder) }
let itemsOffset = builder.createVector(ofOffsets: itemsOffsets, len: itemsOffsets.count)
let start = TelegramCore_InstantPageBlock_List.startInstantPageBlock_List(&builder)
TelegramCore_InstantPageBlock_List.addVectorOf(items: itemsOffset, &builder)
TelegramCore_InstantPageBlock_List.add(ordered: ordered, &builder)
offset = TelegramCore_InstantPageBlock_List.endInstantPageBlock_List(&builder, start: start)
case let .blockQuote(text, caption):
valueType = .instantpageblockBlockquote
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_BlockQuote.startInstantPageBlock_BlockQuote(&builder)
TelegramCore_InstantPageBlock_BlockQuote.add(text: textOffset, &builder)
TelegramCore_InstantPageBlock_BlockQuote.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_BlockQuote.endInstantPageBlock_BlockQuote(&builder, start: start)
case let .pullQuote(text, caption):
valueType = .instantpageblockPullquote
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_PullQuote.startInstantPageBlock_PullQuote(&builder)
TelegramCore_InstantPageBlock_PullQuote.add(text: textOffset, &builder)
TelegramCore_InstantPageBlock_PullQuote.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_PullQuote.endInstantPageBlock_PullQuote(&builder, start: start)
case let .image(id, caption, url, webpageId):
valueType = .instantpageblockImage
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let urlOffset = url.flatMap { builder.create(string: $0) }
let start = TelegramCore_InstantPageBlock_Image.startInstantPageBlock_Image(&builder)
TelegramCore_InstantPageBlock_Image.add(id: id.asFlatBuffersObject(), &builder)
TelegramCore_InstantPageBlock_Image.add(caption: captionOffset, &builder)
if let urlOffset {
TelegramCore_InstantPageBlock_Image.add(url: urlOffset, &builder)
}
if let webpageId {
TelegramCore_InstantPageBlock_Image.add(webpageId: webpageId.asFlatBuffersObject(), &builder)
}
offset = TelegramCore_InstantPageBlock_Image.endInstantPageBlock_Image(&builder, start: start)
case let .video(id, caption, autoplay, loop):
valueType = .instantpageblockVideo
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Video.startInstantPageBlock_Video(&builder)
TelegramCore_InstantPageBlock_Video.add(id: id.asFlatBuffersObject(), &builder)
TelegramCore_InstantPageBlock_Video.add(caption: captionOffset, &builder)
TelegramCore_InstantPageBlock_Video.add(autoplay: autoplay, &builder)
TelegramCore_InstantPageBlock_Video.add(loop: loop, &builder)
offset = TelegramCore_InstantPageBlock_Video.endInstantPageBlock_Video(&builder, start: start)
case let .audio(id, caption):
valueType = .instantpageblockAudio
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Audio.startInstantPageBlock_Audio(&builder)
TelegramCore_InstantPageBlock_Audio.add(id: id.asFlatBuffersObject(), &builder)
TelegramCore_InstantPageBlock_Audio.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_Audio.endInstantPageBlock_Audio(&builder, start: start)
case let .cover(block):
valueType = .instantpageblockCover
let blockOffset = block.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Cover.startInstantPageBlock_Cover(&builder)
TelegramCore_InstantPageBlock_Cover.add(block: blockOffset, &builder)
offset = TelegramCore_InstantPageBlock_Cover.endInstantPageBlock_Cover(&builder, start: start)
case let .webEmbed(url, html, dimensions, caption, stretchToWidth, allowScrolling, coverId):
valueType = .instantpageblockWebembed
let urlOffset = url.flatMap { builder.create(string: $0) }
let htmlOffset = html.flatMap { builder.create(string: $0) }
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_WebEmbed.startInstantPageBlock_WebEmbed(&builder)
if let urlOffset {
TelegramCore_InstantPageBlock_WebEmbed.add(url: urlOffset, &builder)
}
if let htmlOffset {
TelegramCore_InstantPageBlock_WebEmbed.add(html: htmlOffset, &builder)
}
if let dimensions {
TelegramCore_InstantPageBlock_WebEmbed.add(dimensions: dimensions.asFlatBuffersObject(), &builder)
}
TelegramCore_InstantPageBlock_WebEmbed.add(caption: captionOffset, &builder)
TelegramCore_InstantPageBlock_WebEmbed.add(stretchToWidth: stretchToWidth, &builder)
TelegramCore_InstantPageBlock_WebEmbed.add(allowScrolling: allowScrolling, &builder)
if let coverId {
TelegramCore_InstantPageBlock_WebEmbed.add(coverId: coverId.asFlatBuffersObject(), &builder)
}
offset = TelegramCore_InstantPageBlock_WebEmbed.endInstantPageBlock_WebEmbed(&builder, start: start)
case let .postEmbed(url, webpageId, avatarId, author, date, blocks, caption):
valueType = .instantpageblockPostembed
let urlOffset = builder.create(string: url)
let authorOffset = builder.create(string: author)
let blocksOffsets = blocks.map { $0.encodeToFlatBuffers(builder: &builder) }
let blocksOffset = builder.createVector(ofOffsets: blocksOffsets, len: blocksOffsets.count)
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_PostEmbed.startInstantPageBlock_PostEmbed(&builder)
TelegramCore_InstantPageBlock_PostEmbed.add(url: urlOffset, &builder)
if let webpageId {
TelegramCore_InstantPageBlock_PostEmbed.add(webpageId: webpageId.asFlatBuffersObject(), &builder)
}
if let avatarId {
TelegramCore_InstantPageBlock_PostEmbed.add(avatarId: avatarId.asFlatBuffersObject(), &builder)
}
TelegramCore_InstantPageBlock_PostEmbed.add(author: authorOffset, &builder)
TelegramCore_InstantPageBlock_PostEmbed.add(date: date, &builder)
TelegramCore_InstantPageBlock_PostEmbed.addVectorOf(blocks: blocksOffset, &builder)
TelegramCore_InstantPageBlock_PostEmbed.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_PostEmbed.endInstantPageBlock_PostEmbed(&builder, start: start)
case let .collage(items, caption):
valueType = .instantpageblockCollage
let itemsOffsets = items.map { $0.encodeToFlatBuffers(builder: &builder) }
let itemsOffset = builder.createVector(ofOffsets: itemsOffsets, len: itemsOffsets.count)
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Collage.startInstantPageBlock_Collage(&builder)
TelegramCore_InstantPageBlock_Collage.addVectorOf(items: itemsOffset, &builder)
TelegramCore_InstantPageBlock_Collage.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_Collage.endInstantPageBlock_Collage(&builder, start: start)
case let .slideshow(items, caption):
valueType = .instantpageblockSlideshow
let itemsOffsets = items.map { $0.encodeToFlatBuffers(builder: &builder) }
let itemsOffset = builder.createVector(ofOffsets: itemsOffsets, len: itemsOffsets.count)
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Slideshow.startInstantPageBlock_Slideshow(&builder)
TelegramCore_InstantPageBlock_Slideshow.addVectorOf(items: itemsOffset, &builder)
TelegramCore_InstantPageBlock_Slideshow.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_Slideshow.endInstantPageBlock_Slideshow(&builder, start: start)
case let .channelBanner(channel):
valueType = .instantpageblockChannelbanner
let channelOffset = channel.flatMap { $0.encodeToFlatBuffers(builder: &builder) }
let start = TelegramCore_InstantPageBlock_ChannelBanner.startInstantPageBlock_ChannelBanner(&builder)
if let channelOffset {
TelegramCore_InstantPageBlock_ChannelBanner.add(channel: channelOffset, &builder)
}
offset = TelegramCore_InstantPageBlock_ChannelBanner.endInstantPageBlock_ChannelBanner(&builder, start: start)
case let .kicker(text):
valueType = .instantpageblockKicker
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Kicker.startInstantPageBlock_Kicker(&builder)
TelegramCore_InstantPageBlock_Kicker.add(text: textOffset, &builder)
offset = TelegramCore_InstantPageBlock_Kicker.endInstantPageBlock_Kicker(&builder, start: start)
case let .table(title, rows, bordered, striped):
valueType = .instantpageblockTable
let titleOffset = title.encodeToFlatBuffers(builder: &builder)
let rowsOffsets = rows.map { $0.encodeToFlatBuffers(builder: &builder) }
let rowsOffset = builder.createVector(ofOffsets: rowsOffsets, len: rowsOffsets.count)
let start = TelegramCore_InstantPageBlock_Table.startInstantPageBlock_Table(&builder)
TelegramCore_InstantPageBlock_Table.add(title: titleOffset, &builder)
TelegramCore_InstantPageBlock_Table.addVectorOf(rows: rowsOffset, &builder)
TelegramCore_InstantPageBlock_Table.add(bordered: bordered, &builder)
TelegramCore_InstantPageBlock_Table.add(striped: striped, &builder)
offset = TelegramCore_InstantPageBlock_Table.endInstantPageBlock_Table(&builder, start: start)
case let .details(title, blocks, expanded):
valueType = .instantpageblockDetails
let titleOffset = title.encodeToFlatBuffers(builder: &builder)
let blocksOffsets = blocks.map { $0.encodeToFlatBuffers(builder: &builder) }
let blocksOffset = builder.createVector(ofOffsets: blocksOffsets, len: blocksOffsets.count)
let start = TelegramCore_InstantPageBlock_Details.startInstantPageBlock_Details(&builder)
TelegramCore_InstantPageBlock_Details.add(title: titleOffset, &builder)
TelegramCore_InstantPageBlock_Details.addVectorOf(blocks: blocksOffset, &builder)
TelegramCore_InstantPageBlock_Details.add(expanded: expanded, &builder)
offset = TelegramCore_InstantPageBlock_Details.endInstantPageBlock_Details(&builder, start: start)
case let .relatedArticles(title, articles):
valueType = .instantpageblockRelatedarticles
let titleOffset = title.encodeToFlatBuffers(builder: &builder)
let articlesOffsets = articles.map { $0.encodeToFlatBuffers(builder: &builder) }
let articlesOffset = builder.createVector(ofOffsets: articlesOffsets, len: articlesOffsets.count)
let start = TelegramCore_InstantPageBlock_RelatedArticles.startInstantPageBlock_RelatedArticles(&builder)
TelegramCore_InstantPageBlock_RelatedArticles.add(title: titleOffset, &builder)
TelegramCore_InstantPageBlock_RelatedArticles.addVectorOf(articles: articlesOffset, &builder)
offset = TelegramCore_InstantPageBlock_RelatedArticles.endInstantPageBlock_RelatedArticles(&builder, start: start)
case let .map(latitude, longitude, zoom, dimensions, caption):
valueType = .instantpageblockMap
let captionOffset = caption.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageBlock_Map.startInstantPageBlock_Map(&builder)
TelegramCore_InstantPageBlock_Map.add(latitude: latitude, &builder)
TelegramCore_InstantPageBlock_Map.add(longitude: longitude, &builder)
TelegramCore_InstantPageBlock_Map.add(zoom: zoom, &builder)
TelegramCore_InstantPageBlock_Map.add(dimensions: dimensions.asFlatBuffersObject(), &builder)
TelegramCore_InstantPageBlock_Map.add(caption: captionOffset, &builder)
offset = TelegramCore_InstantPageBlock_Map.endInstantPageBlock_Map(&builder, start: start)
}
return TelegramCore_InstantPageBlock.createInstantPageBlock(&builder, valueType: valueType, valueOffset: offset)
}
}
public final class InstantPageCaption: PostboxCoding, Equatable {
@ -542,6 +934,21 @@ public final class InstantPageCaption: PostboxCoding, Equatable {
}
return true
}
public init(flatBuffersObject: TelegramCore_InstantPageCaption) throws {
self.text = try RichText(flatBuffersObject: flatBuffersObject.text)
self.credit = try RichText(flatBuffersObject: flatBuffersObject.credit)
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let textOffset = self.text.encodeToFlatBuffers(builder: &builder)
let creditOffset = self.credit.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_InstantPageCaption.startInstantPageCaption(&builder)
TelegramCore_InstantPageCaption.add(text: textOffset, &builder)
TelegramCore_InstantPageCaption.add(credit: creditOffset, &builder)
let offset = TelegramCore_InstantPageCaption.endInstantPageCaption(&builder, start: start)
return offset
}
}
private enum InstantPageListItemType: Int32 {
@ -611,6 +1018,69 @@ public indirect enum InstantPageListItem: PostboxCoding, Equatable {
}
}
}
public init(flatBuffersObject: TelegramCore_InstantPageListItem) throws {
switch flatBuffersObject.valueType {
case .instantpagelistitemText:
guard let textValue = flatBuffersObject.value(type: TelegramCore_InstantPageListItem_Text.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .text(try RichText(flatBuffersObject: textValue.text), textValue.number)
case .instantpagelistitemBlocks:
guard let blocksValue = flatBuffersObject.value(type: TelegramCore_InstantPageListItem_Blocks.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
let blocks = try (0 ..< blocksValue.blocksCount).map { i in
return try InstantPageBlock(flatBuffersObject: blocksValue.blocks(at: i)!)
}
self = .blocks(blocks, blocksValue.number)
case .instantpagelistitemUnknown:
self = .unknown
case .none_:
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let valueType: TelegramCore_InstantPageListItem_Value
let offset: Offset
switch self {
case let .text(text, number):
valueType = .instantpagelistitemText
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let numberOffset = number.map { builder.create(string: $0) } ?? Offset()
let start = TelegramCore_InstantPageListItem_Text.startInstantPageListItem_Text(&builder)
TelegramCore_InstantPageListItem_Text.add(text: textOffset, &builder)
if let _ = number {
TelegramCore_InstantPageListItem_Text.add(number: numberOffset, &builder)
}
offset = TelegramCore_InstantPageListItem_Text.endInstantPageListItem_Text(&builder, start: start)
case let .blocks(blocks, number):
valueType = .instantpagelistitemBlocks
let blocksOffsets = blocks.map { $0.encodeToFlatBuffers(builder: &builder) }
let blocksOffset = builder.createVector(ofOffsets: blocksOffsets, len: blocksOffsets.count)
let numberOffset = number.map { builder.create(string: $0) } ?? Offset()
let start = TelegramCore_InstantPageListItem_Blocks.startInstantPageListItem_Blocks(&builder)
TelegramCore_InstantPageListItem_Blocks.addVectorOf(blocks: blocksOffset, &builder)
if let _ = number {
TelegramCore_InstantPageListItem_Blocks.add(number: numberOffset, &builder)
}
offset = TelegramCore_InstantPageListItem_Blocks.endInstantPageListItem_Blocks(&builder, start: start)
case .unknown:
valueType = .instantpagelistitemUnknown
let start = TelegramCore_InstantPageListItem_Unknown.startInstantPageListItem_Unknown(&builder)
offset = TelegramCore_InstantPageListItem_Unknown.endInstantPageListItem_Unknown(&builder, start: start)
}
let start = TelegramCore_InstantPageListItem.startInstantPageListItem(&builder)
TelegramCore_InstantPageListItem.add(valueType: valueType, &builder)
TelegramCore_InstantPageListItem.add(value: offset, &builder)
return TelegramCore_InstantPageListItem.endInstantPageListItem(&builder, start: start)
}
}
public enum TableHorizontalAlignment: Int32 {
@ -685,6 +1155,30 @@ public final class InstantPageTableCell: PostboxCoding, Equatable {
}
return true
}
public init(flatBuffersObject: TelegramCore_InstantPageTableCell) throws {
self.text = try flatBuffersObject.text.map { try RichText(flatBuffersObject: $0) }
self.header = flatBuffersObject.header
self.alignment = TableHorizontalAlignment(rawValue: flatBuffersObject.alignment) ?? .left
self.verticalAlignment = TableVerticalAlignment(rawValue: flatBuffersObject.verticalAlignment) ?? .top
self.colspan = flatBuffersObject.colspan
self.rowspan = flatBuffersObject.rowspan
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let textOffset = text.map { $0.encodeToFlatBuffers(builder: &builder) } ?? Offset()
let start = TelegramCore_InstantPageTableCell.startInstantPageTableCell(&builder)
if let _ = text {
TelegramCore_InstantPageTableCell.add(text: textOffset, &builder)
}
TelegramCore_InstantPageTableCell.add(header: header, &builder)
TelegramCore_InstantPageTableCell.add(alignment: alignment.rawValue, &builder)
TelegramCore_InstantPageTableCell.add(verticalAlignment: verticalAlignment.rawValue, &builder)
TelegramCore_InstantPageTableCell.add(colspan: colspan, &builder)
TelegramCore_InstantPageTableCell.add(rowspan: rowspan, &builder)
return TelegramCore_InstantPageTableCell.endInstantPageTableCell(&builder, start: start)
}
}
public final class InstantPageTableRow: PostboxCoding, Equatable {
@ -705,6 +1199,21 @@ public final class InstantPageTableRow: PostboxCoding, Equatable {
public static func ==(lhs: InstantPageTableRow, rhs: InstantPageTableRow) -> Bool {
return lhs.cells == rhs.cells
}
public init(flatBuffersObject: TelegramCore_InstantPageTableRow) throws {
self.cells = try (0 ..< flatBuffersObject.cellsCount).map { i in
return try InstantPageTableCell(flatBuffersObject: flatBuffersObject.cells(at: i)!)
}
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let cellsOffsets = cells.map { $0.encodeToFlatBuffers(builder: &builder) }
let cellsOffset = builder.createVector(ofOffsets: cellsOffsets, len: cellsOffsets.count)
let start = TelegramCore_InstantPageTableRow.startInstantPageTableRow(&builder)
TelegramCore_InstantPageTableRow.addVectorOf(cells: cellsOffset, &builder)
return TelegramCore_InstantPageTableRow.endInstantPageTableRow(&builder, start: start)
}
}
public final class InstantPageRelatedArticle: PostboxCoding, Equatable {
@ -801,6 +1310,45 @@ public final class InstantPageRelatedArticle: PostboxCoding, Equatable {
}
return true
}
public init(flatBuffersObject: TelegramCore_InstantPageRelatedArticle) throws {
self.url = flatBuffersObject.url
self.webpageId = MediaId(flatBuffersObject.webpageId)
self.title = flatBuffersObject.title
self.description = flatBuffersObject.description
self.photoId = flatBuffersObject.photoId.flatMap(MediaId.init)
self.author = flatBuffersObject.author
self.date = flatBuffersObject.date == Int32.min ? nil : flatBuffersObject.date
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let urlOffset = builder.create(string: url)
let titleOffset = title.map { builder.create(string: $0) }
let descriptionOffset = description.map { builder.create(string: $0) }
let authorOffset = author.map { builder.create(string: $0) }
let start = TelegramCore_InstantPageRelatedArticle.startInstantPageRelatedArticle(&builder)
TelegramCore_InstantPageRelatedArticle.add(url: urlOffset, &builder)
TelegramCore_InstantPageRelatedArticle.add(webpageId: webpageId.asFlatBuffersObject(), &builder)
if let titleOffset {
TelegramCore_InstantPageRelatedArticle.add(title: titleOffset, &builder)
}
if let descriptionOffset {
TelegramCore_InstantPageRelatedArticle.add(description: descriptionOffset, &builder)
}
if let photoId {
TelegramCore_InstantPageRelatedArticle.add(photoId: photoId.asFlatBuffersObject(), &builder)
}
if let authorOffset {
TelegramCore_InstantPageRelatedArticle.add(author: authorOffset, &builder)
}
if let date {
TelegramCore_InstantPageRelatedArticle.add(date: date, &builder)
} else {
TelegramCore_InstantPageRelatedArticle.add(date: Int32.min, &builder)
}
return TelegramCore_InstantPageRelatedArticle.endInstantPageRelatedArticle(&builder, start: start)
}
}
private final class MediaDictionary: PostboxCoding {
@ -862,6 +1410,17 @@ public final class InstantPage: PostboxCoding, Equatable {
self.rtl = decoder.decodeInt32ForKey("r", orElse: 0) != 0
self.url = decoder.decodeStringForKey("url", orElse: "")
self.views = decoder.decodeOptionalInt32ForKey("v")
#if DEBUG
var builder = FlatBufferBuilder(initialSize: 1024)
let offset = self.encodeToFlatBuffers(builder: &builder)
builder.finish(offset: offset)
let serializedData = builder.data
var byteBuffer = ByteBuffer(data: serializedData)
let deserializedValue = FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_InstantPage
let parsedValue = try! InstantPage(flatBuffersObject: deserializedValue)
assert(self == parsedValue)
#endif
}
public func encode(_ encoder: PostboxEncoder) {
@ -908,4 +1467,119 @@ public final class InstantPage: PostboxCoding, Equatable {
}
return true
}
public init(flatBuffersObject: TelegramCore_InstantPage) throws {
self.blocks = try (0 ..< flatBuffersObject.blocksCount).map { i in
return try InstantPageBlock(flatBuffersObject: flatBuffersObject.blocks(at: i)!)
}
//TODO:release support other media types
var media: [MediaId: Media] = [:]
for i in 0 ..< flatBuffersObject.mediaCount {
let mediaItem = flatBuffersObject.media(at: i)!
switch mediaItem.valueType {
case .mediaTelegrammediafile:
guard let value = mediaItem.value(type: TelegramCore_Media_TelegramMediaFile.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
let parsedMedia = try TelegramMediaFile(flatBuffersObject: value.file)
media[parsedMedia.fileId] = parsedMedia
case .mediaTelegrammediaimage:
guard let value = mediaItem.value(type: TelegramCore_Media_TelegramMediaImage.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
let parsedMedia = try TelegramMediaImage(flatBuffersObject: value.image)
media[parsedMedia.imageId] = parsedMedia
case .none_:
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}
self.media = media
self.isComplete = flatBuffersObject.isComplete
self.rtl = flatBuffersObject.rtl
self.url = flatBuffersObject.url
self.views = flatBuffersObject.views == Int32.min ? nil : flatBuffersObject.views
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let blocksOffsets = self.blocks.map { block in
return block.encodeToFlatBuffers(builder: &builder)
}
let blocksOffset = builder.createVector(ofOffsets: blocksOffsets, len: blocksOffsets.count)
var mediaOffsets: [Offset] = []
for (_, media) in self.media.sorted(by: { $0.key < $1.key }) {
switch media {
case let file as TelegramMediaFile:
let fileOffset = file.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_Media_TelegramMediaFile.startMedia_TelegramMediaFile(&builder)
TelegramCore_Media_TelegramMediaFile.add(file: fileOffset, &builder)
let offset = TelegramCore_Media_TelegramMediaFile.endMedia_TelegramMediaFile(&builder, start: start)
mediaOffsets.append(TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediafile, valueOffset: offset))
case let image as TelegramMediaImage:
let imageOffset = image.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_Media_TelegramMediaImage.startMedia_TelegramMediaImage(&builder)
TelegramCore_Media_TelegramMediaImage.add(image: imageOffset, &builder)
let offset = TelegramCore_Media_TelegramMediaImage.endMedia_TelegramMediaImage(&builder, start: start)
mediaOffsets.append(TelegramCore_Media.createMedia(&builder, valueType: .mediaTelegrammediaimage, valueOffset: offset))
default:
assertionFailure()
}
}
let mediaOffset = builder.createVector(ofOffsets: mediaOffsets, len: mediaOffsets.count)
let urlOffset = builder.create(string: self.url)
let start = TelegramCore_InstantPage.startInstantPage(&builder)
TelegramCore_InstantPage.addVectorOf(blocks: blocksOffset, &builder)
TelegramCore_InstantPage.addVectorOf(media: mediaOffset, &builder)
TelegramCore_InstantPage.add(isComplete: self.isComplete, &builder)
TelegramCore_InstantPage.add(rtl: self.rtl, &builder)
TelegramCore_InstantPage.add(url: urlOffset, &builder)
TelegramCore_InstantPage.add(views: self.views ?? Int32.min, &builder)
return TelegramCore_InstantPage.endInstantPage(&builder, start: start)
}
}
public extension InstantPage {
struct Accessor: Equatable {
let _wrappedInstantPage: InstantPage?
let _wrapped: TelegramCore_InstantPage?
let _wrappedData: Data?
public init(_ wrapped: TelegramCore_InstantPage, _ _wrappedData: Data) {
self._wrapped = wrapped
self._wrappedData = _wrappedData
self._wrappedInstantPage = nil
}
public init(_ wrapped: InstantPage) {
self._wrapped = nil
self._wrappedData = nil
self._wrappedInstantPage = wrapped
}
public func _parse() -> InstantPage {
if let _wrappedInstantPage = self._wrappedInstantPage {
return _wrappedInstantPage
} else {
return try! InstantPage(flatBuffersObject: self._wrapped!)
}
}
public static func ==(lhs: InstantPage.Accessor, rhs: InstantPage.Accessor) -> Bool {
if let lhsWrappedInstantPage = lhs._wrappedInstantPage, let rhsWrappedInstantPage = rhs._wrappedInstantPage {
return lhsWrappedInstantPage === rhsWrappedInstantPage
} else if let lhsWrappedData = lhs._wrappedData, let rhsWrappedData = rhs._wrappedData {
return lhsWrappedData == rhsWrappedData
} else {
assertionFailure()
return lhs._parse() == rhs._parse()
}
}
}
}

View file

@ -79,7 +79,7 @@ public extension PartialMediaReference {
switch flatBuffersObject.valueType {
case .partialmediareferenceMessage:
guard let value = flatBuffersObject.value(type: TelegramCore_PartialMediaReference_Message.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
if let message = value.message {
self = .message(message: try MessageReference(flatBuffersObject: message))
@ -88,7 +88,7 @@ public extension PartialMediaReference {
}
case .partialmediareferenceWebpage:
guard let value = flatBuffersObject.value(type: TelegramCore_PartialMediaReference_WebPage.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
if let webPage = value.webPage {
self = .webPage(webPage: try WebpageReference(flatBuffersObject: webPage))
@ -97,7 +97,7 @@ public extension PartialMediaReference {
}
case .partialmediareferenceStickerpack:
guard let value = flatBuffersObject.value(type: TelegramCore_PartialMediaReference_StickerPack.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .stickerPack(stickerPack: try StickerPackReference(flatBuffersObject: value.stickerPack))
case .partialmediareferenceSavedgif:
@ -107,7 +107,7 @@ public extension PartialMediaReference {
case .partialmediareferenceRecentsticker:
self = .recentSticker
case .none_:
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}

View file

@ -191,6 +191,7 @@ public extension PendingMessageActionType {
static let sendScheduledMessageImmediately = PendingMessageActionType(rawValue: 2)
static let readReaction = PendingMessageActionType(rawValue: 3)
static let sendStarsReaction = PendingMessageActionType(rawValue: 4)
static let sendPostponedPaidMessage = PendingMessageActionType(rawValue: 5)
}
public let peerIdNamespacesWithInitialCloudMessageHoles = [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel]

View file

@ -1,4 +1,6 @@
import Foundation
import FlatBuffers
import FlatSerialization
public enum TelegramPeerAccessHash: Hashable {
case personal(Int64)
@ -12,4 +14,44 @@ public enum TelegramPeerAccessHash: Hashable {
return genericPublic
}
}
public init(flatBuffersObject: TelegramCore_TelegramPeerAccessHash) throws {
switch flatBuffersObject.valueType {
case .telegrampeeraccesshashPersonal:
guard let personal = flatBuffersObject.value(type: TelegramCore_TelegramPeerAccessHash_Personal.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .personal(personal.accessHash)
case .telegrampeeraccesshashGenericpublic:
guard let genericPublic = flatBuffersObject.value(type: TelegramCore_TelegramPeerAccessHash_GenericPublic.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .genericPublic(genericPublic.accessHash)
case .none_:
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let valueType: TelegramCore_TelegramPeerAccessHash_Value
let valueOffset: Offset
switch self {
case let .personal(accessHash):
valueType = .telegrampeeraccesshashPersonal
let start = TelegramCore_TelegramPeerAccessHash_Personal.startTelegramPeerAccessHash_Personal(&builder)
TelegramCore_TelegramPeerAccessHash_Personal.add(accessHash: accessHash, &builder)
valueOffset = TelegramCore_TelegramPeerAccessHash_Personal.endTelegramPeerAccessHash_Personal(&builder, start: start)
case let .genericPublic(accessHash):
valueType = .telegrampeeraccesshashGenericpublic
let start = TelegramCore_TelegramPeerAccessHash_GenericPublic.startTelegramPeerAccessHash_GenericPublic(&builder)
TelegramCore_TelegramPeerAccessHash_GenericPublic.add(accessHash: accessHash, &builder)
valueOffset = TelegramCore_TelegramPeerAccessHash_GenericPublic.endTelegramPeerAccessHash_GenericPublic(&builder, start: start)
}
let start = TelegramCore_TelegramPeerAccessHash.startTelegramPeerAccessHash(&builder)
TelegramCore_TelegramPeerAccessHash.add(valueType: valueType, &builder)
TelegramCore_TelegramPeerAccessHash.add(value: valueOffset, &builder)
return TelegramCore_TelegramPeerAccessHash.endTelegramPeerAccessHash(&builder, start: start)
}
}

View file

@ -1,4 +1,6 @@
import Postbox
import FlatBuffers
import FlatSerialization
public final class RestrictionRule: PostboxCoding, Equatable {
public let platform: String
@ -41,6 +43,24 @@ public final class RestrictionRule: PostboxCoding, Equatable {
}
return true
}
public init(flatBuffersObject: TelegramCore_RestrictionRule) throws {
self.platform = flatBuffersObject.platform
self.reason = flatBuffersObject.reason
self.text = flatBuffersObject.text
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let platformOffset = builder.create(string: self.platform)
let reasonOffset = builder.create(string: self.reason)
let textOffset = builder.create(string: self.text)
let start = TelegramCore_RestrictionRule.startRestrictionRule(&builder)
TelegramCore_RestrictionRule.add(platform: platformOffset, &builder)
TelegramCore_RestrictionRule.add(reason: reasonOffset, &builder)
TelegramCore_RestrictionRule.add(text: textOffset, &builder)
return TelegramCore_RestrictionRule.endRestrictionRule(&builder, start: start)
}
}
public final class PeerAccessRestrictionInfo: PostboxCoding, Equatable {
@ -65,4 +85,17 @@ public final class PeerAccessRestrictionInfo: PostboxCoding, Equatable {
public static func ==(lhs: PeerAccessRestrictionInfo, rhs: PeerAccessRestrictionInfo) -> Bool {
return lhs.rules == rhs.rules
}
public init(flatBuffersObject: TelegramCore_PeerAccessRestrictionInfo) throws {
self.rules = try (0 ..< flatBuffersObject.rulesCount).map { try RestrictionRule(flatBuffersObject: flatBuffersObject.rules(at: $0)!) }
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let rulesOffsets = self.rules.map { $0.encodeToFlatBuffers(builder: &builder) }
let rulesOffset = builder.createVector(ofOffsets: rulesOffsets, len: rulesOffsets.count)
let start = TelegramCore_PeerAccessRestrictionInfo.startPeerAccessRestrictionInfo(&builder)
TelegramCore_PeerAccessRestrictionInfo.addVectorOf(rules: rulesOffset, &builder)
return TelegramCore_PeerAccessRestrictionInfo.endPeerAccessRestrictionInfo(&builder, start: start)
}
}

View file

@ -80,21 +80,21 @@ public enum PeerReference: PostboxCoding, Hashable, Equatable {
switch flatBuffersObject.valueType {
case .peerreferenceUser:
guard let value = flatBuffersObject.value(type: TelegramCore_PeerReference_User.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .user(id: value.id, accessHash: value.accessHash)
case .peerreferenceGroup:
guard let value = flatBuffersObject.value(type: TelegramCore_PeerReference_Group.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .group(id: value.id)
case .peerreferenceChannel:
guard let value = flatBuffersObject.value(type: TelegramCore_PeerReference_Channel.self) else {
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .channel(id: value.id, accessHash: value.accessHash)
case .none_:
throw FlatBuffersError.missingRequiredField
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
}

View file

@ -42,7 +42,7 @@ public final class RecentMediaItem: Codable, Equatable {
if let serializedFileData = try container.decodeIfPresent(Data.self, forKey: "md") {
self.serializedFile = serializedFileData
var byteBuffer = ByteBuffer(data: serializedFileData)
self.media = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, serializedFileData)
self.media = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, serializedFileData)
} else {
let mediaData = try container.decode(AdaptedPostboxDecoder.RawObjectData.self, forKey: "m")
let media = TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: mediaData.data)))
@ -282,7 +282,7 @@ public final class RecentReactionItem: Codable, Equatable {
if let mediaData = try container.decodeIfPresent(Data.self, forKey: "md") {
var byteBuffer = ByteBuffer(data: mediaData)
let file = TelegramMediaFile.Accessor(getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, mediaData)
let file = TelegramMediaFile.Accessor(FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_TelegramMediaFile, mediaData)
self.content = .custom(file)
} else if let mediaData = try container.decodeIfPresent(AdaptedPostboxDecoder.RawObjectData.self, forKey: "m") {
self.content = .custom(TelegramMediaFile.Accessor(TelegramMediaFile(decoder: PostboxDecoder(buffer: MemoryBuffer(data: mediaData.data)))))

View file

@ -1,4 +1,6 @@
import Postbox
import FlatBuffers
import FlatSerialization
private enum RichTextTypes: Int32 {
case empty = 0
@ -292,3 +294,208 @@ public extension RichText {
}
}
}
extension RichText {
public init(flatBuffersObject: TelegramCore_RichText) throws {
switch flatBuffersObject.valueType {
case .richtextEmpty:
self = .empty
case .richtextPlain:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Plain.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .plain(value.text)
case .richtextBold:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Bold.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .bold(try RichText(flatBuffersObject: value.text))
case .richtextItalic:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Italic.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .italic(try RichText(flatBuffersObject: value.text))
case .richtextUnderline:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Underline.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .underline(try RichText(flatBuffersObject: value.text))
case .richtextStrikethrough:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Strikethrough.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .strikethrough(try RichText(flatBuffersObject: value.text))
case .richtextFixed:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Fixed.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .fixed(try RichText(flatBuffersObject: value.text))
case .richtextUrl:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Url.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .url(text: try RichText(flatBuffersObject: value.text), url: value.url, webpageId: value.webpageId.flatMap { MediaId($0) })
case .richtextEmail:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Email.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .email(text: try RichText(flatBuffersObject: value.text),
email: value.email)
case .richtextConcat:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Concat.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .concat(try (0..<value.textsCount).map { try RichText(flatBuffersObject: value.texts(at: $0)!) })
case .richtextSubscript:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Subscript.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .subscript(try RichText(flatBuffersObject: value.text))
case .richtextSuperscript:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Superscript.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .superscript(try RichText(flatBuffersObject: value.text))
case .richtextMarked:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Marked.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .marked(try RichText(flatBuffersObject: value.text))
case .richtextPhone:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Phone.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .phone(text: try RichText(flatBuffersObject: value.text),
phone: value.phone)
case .richtextImage:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Image.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .image(id: MediaId(value.id), dimensions: PixelDimensions(value.dimensions))
case .richtextAnchor:
guard let value = flatBuffersObject.value(type: TelegramCore_RichText_Anchor.self) else {
throw FlatBuffersError.missingRequiredField(file: #file, line: #line)
}
self = .anchor(text: try RichText(flatBuffersObject: value.text),
name: value.name)
case .none_:
self = .empty
}
}
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
let valueType: TelegramCore_RichText_Value
let offset: Offset
switch self {
case .empty:
valueType = .richtextEmpty
let start = TelegramCore_RichText_Empty.startRichText_Empty(&builder)
offset = TelegramCore_RichText_Empty.endRichText_Empty(&builder, start: start)
case let .plain(text):
valueType = .richtextPlain
let textOffset = builder.create(string: text)
let start = TelegramCore_RichText_Plain.startRichText_Plain(&builder)
TelegramCore_RichText_Plain.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Plain.endRichText_Plain(&builder, start: start)
case let .bold(text):
valueType = .richtextBold
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Bold.startRichText_Bold(&builder)
TelegramCore_RichText_Bold.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Bold.endRichText_Bold(&builder, start: start)
case let .italic(text):
valueType = .richtextItalic
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Italic.startRichText_Italic(&builder)
TelegramCore_RichText_Italic.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Italic.endRichText_Italic(&builder, start: start)
case let .underline(text):
valueType = .richtextUnderline
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Underline.startRichText_Underline(&builder)
TelegramCore_RichText_Underline.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Underline.endRichText_Underline(&builder, start: start)
case let .strikethrough(text):
valueType = .richtextStrikethrough
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Strikethrough.startRichText_Strikethrough(&builder)
TelegramCore_RichText_Strikethrough.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Strikethrough.endRichText_Strikethrough(&builder, start: start)
case let .fixed(text):
valueType = .richtextFixed
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Fixed.startRichText_Fixed(&builder)
TelegramCore_RichText_Fixed.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Fixed.endRichText_Fixed(&builder, start: start)
case let .url(text, url, webpageId):
valueType = .richtextUrl
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let urlOffset = builder.create(string: url)
let start = TelegramCore_RichText_Url.startRichText_Url(&builder)
TelegramCore_RichText_Url.add(text: textOffset, &builder)
TelegramCore_RichText_Url.add(url: urlOffset, &builder)
if let webpageId {
TelegramCore_RichText_Url.add(webpageId: webpageId.asFlatBuffersObject(), &builder)
}
offset = TelegramCore_RichText_Url.endRichText_Url(&builder, start: start)
case let .email(text, email):
valueType = .richtextEmail
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let emailOffset = builder.create(string: email)
let start = TelegramCore_RichText_Email.startRichText_Email(&builder)
TelegramCore_RichText_Email.add(text: textOffset, &builder)
TelegramCore_RichText_Email.add(email: emailOffset, &builder)
offset = TelegramCore_RichText_Email.endRichText_Email(&builder, start: start)
case let .concat(texts):
valueType = .richtextConcat
let textsOffsets = texts.map { $0.encodeToFlatBuffers(builder: &builder) }
let textsOffset = builder.createVector(ofOffsets: textsOffsets, len: textsOffsets.count)
let start = TelegramCore_RichText_Concat.startRichText_Concat(&builder)
TelegramCore_RichText_Concat.addVectorOf(texts: textsOffset, &builder)
offset = TelegramCore_RichText_Concat.endRichText_Concat(&builder, start: start)
case let .subscript(text):
valueType = .richtextSubscript
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Subscript.startRichText_Subscript(&builder)
TelegramCore_RichText_Subscript.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Subscript.endRichText_Subscript(&builder, start: start)
case let .superscript(text):
valueType = .richtextSuperscript
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Superscript.startRichText_Superscript(&builder)
TelegramCore_RichText_Superscript.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Superscript.endRichText_Superscript(&builder, start: start)
case let .marked(text):
valueType = .richtextMarked
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let start = TelegramCore_RichText_Marked.startRichText_Marked(&builder)
TelegramCore_RichText_Marked.add(text: textOffset, &builder)
offset = TelegramCore_RichText_Marked.endRichText_Marked(&builder, start: start)
case let .phone(text, phone):
valueType = .richtextPhone
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let phoneOffset = builder.create(string: phone)
let start = TelegramCore_RichText_Phone.startRichText_Phone(&builder)
TelegramCore_RichText_Phone.add(text: textOffset, &builder)
TelegramCore_RichText_Phone.add(phone: phoneOffset, &builder)
offset = TelegramCore_RichText_Phone.endRichText_Phone(&builder, start: start)
case let .image(id, dimensions):
valueType = .richtextImage
let start = TelegramCore_RichText_Image.startRichText_Image(&builder)
TelegramCore_RichText_Image.add(id: id.asFlatBuffersObject(), &builder)
TelegramCore_RichText_Image.add(dimensions: dimensions.asFlatBuffersObject(), &builder)
offset = TelegramCore_RichText_Image.endRichText_Image(&builder, start: start)
case let .anchor(text, name):
valueType = .richtextAnchor
let textOffset = text.encodeToFlatBuffers(builder: &builder)
let nameOffset = builder.create(string: name)
let start = TelegramCore_RichText_Anchor.startRichText_Anchor(&builder)
TelegramCore_RichText_Anchor.add(text: textOffset, &builder)
TelegramCore_RichText_Anchor.add(name: nameOffset, &builder)
offset = TelegramCore_RichText_Anchor.endRichText_Anchor(&builder, start: start)
}
return TelegramCore_RichText.createRichText(&builder, valueType: valueType, valueOffset: offset)
}
}

Some files were not shown because too many files have changed in this diff Show more