Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
# Conflicts: # .gitignore
This commit is contained in:
commit
84a17115fa
221 changed files with 7564 additions and 2298 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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
19
.vscode/launch.json
vendored
Normal 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
5
.vscode/settings.json
vendored
Normal 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
19
.vscode/tasks.json
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@
|
|||
|
||||
@property (nonatomic, readonly) bool inhibitEditing;
|
||||
|
||||
@property (nonatomic, assign) int64_t sendPaidMessageStars;
|
||||
|
||||
+ (instancetype)contextForCaptionsOnly;
|
||||
|
||||
- (SSignal *)imageSignalForItem:(NSObject<TGMediaEditableItem> *)item;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ swift_library(
|
|||
"//submodules/DrawingUI:DrawingUI",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
"//submodules/AnimatedCountLabelNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)?
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
23
submodules/TelegramCore/FlatSerialization/Models/Media.fbs
Normal file
23
submodules/TelegramCore/FlatSerialization/Models/Media.fbs
Normal 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;
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace TelegramCore;
|
||||
|
||||
struct OptionalBool {
|
||||
value:bool (id: 0);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace TelegramCore;
|
||||
|
||||
table PeerNameColor {
|
||||
value:int32 (id: 0);
|
||||
}
|
||||
|
||||
root_type PeerNameColor;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace TelegramCore;
|
||||
|
||||
table StarsAmount {
|
||||
value:int64 (id: 0);
|
||||
nanos:int32 (id: 1);
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace TelegramCore;
|
||||
|
||||
table TelegramChatAdminRights {
|
||||
rights:int32 (id: 0);
|
||||
}
|
||||
|
||||
root_type TelegramChatAdminRights;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace TelegramCore;
|
||||
|
||||
table TelegramChatBannedRights {
|
||||
flags:int32 (id: 0);
|
||||
untilDate:int32 (id: 1);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace TelegramCore;
|
||||
|
||||
table TelegramPeerUsername {
|
||||
flags:int32 (id: 0);
|
||||
username:string (id: 1, required);
|
||||
}
|
||||
|
||||
root_type TelegramPeerUsername;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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) })
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import Foundation
|
||||
import Postbox
|
||||
|
||||
|
||||
public enum TelegramChannelPermission {
|
||||
case sendText
|
||||
case sendPhoto
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)))))
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue