mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'gift-resale'
This commit is contained in:
commit
7a261aec95
89 changed files with 6964 additions and 999 deletions
|
|
@ -14188,3 +14188,12 @@ Sorry for the inconvenience.";
|
|||
"SendInviteLink.TextCallsRestrictedMultipleUsers_1" = "{user_list}, and **%d** more person do not accept calls.";
|
||||
"SendInviteLink.TextCallsRestrictedMultipleUsers_any" = "{user_list}, and **%d** more people do not accept calls.";
|
||||
"SendInviteLink.TextCallsRestrictedSendInviteLink" = "You can try to send an invite link instead.";
|
||||
|
||||
"Story.Privacy.ShareStories" = "Share Stories";
|
||||
"Story.Privacy.PostStoriesAsHeader" = "POST STORIES AS";
|
||||
"Story.Privacy.WhoCanViewStoriesHeader" = "WHO CAN VIEW THIS STORIES";
|
||||
"Story.Privacy.PostStories_1" = "Post %@ Story";
|
||||
"Story.Privacy.PostStories_any" = "Post %@ Stories";
|
||||
"Story.Privacy.KeepOnMyPageManyInfo" = "Keep these stories on your profile even after they expire in %@. Privacy settings will apply.";
|
||||
"Story.Privacy.KeepOnChannelPageManyInfo" = "Keep these stories on the channel profile even after they expire in %@.";
|
||||
"Story.Privacy.KeepOnGroupPageManyInfo" = "Keep these stories on the group page even after they expire in %@.";
|
||||
|
|
|
|||
|
|
@ -653,6 +653,7 @@ public enum ChatListSearchFilter: Equatable {
|
|||
case files
|
||||
case music
|
||||
case voice
|
||||
case instantVideo
|
||||
case peer(PeerId, Bool, String, String)
|
||||
case date(Int32?, Int32, String)
|
||||
case publicPosts
|
||||
|
|
@ -679,8 +680,10 @@ public enum ChatListSearchFilter: Equatable {
|
|||
return 8
|
||||
case .voice:
|
||||
return 9
|
||||
case .publicPosts:
|
||||
case .instantVideo:
|
||||
return 10
|
||||
case .publicPosts:
|
||||
return 11
|
||||
case let .peer(peerId, _, _, _):
|
||||
return peerId.id._internalGetInt64Value()
|
||||
case let .date(_, date, _):
|
||||
|
|
@ -806,7 +809,7 @@ public protocol MediaEditorScreenResult {
|
|||
public protocol TelegramRootControllerInterface: NavigationController {
|
||||
@discardableResult
|
||||
func openStoryCamera(customTarget: Stories.PendingTarget?, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator?
|
||||
func proceedWithStoryUpload(target: Stories.PendingTarget, result: MediaEditorScreenResult, existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
|
||||
func proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void)
|
||||
|
||||
func getContactsController() -> ViewController?
|
||||
func getChatsController() -> ViewController?
|
||||
|
|
@ -1131,6 +1134,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeStarsGiftController(context: AccountContext, birthdays: [EnginePeer.Id: TelegramBirthday]?, completion: @escaping (([EnginePeer.Id]) -> Void)) -> ViewController
|
||||
func makePremiumGiftController(context: AccountContext, source: PremiumGiftSource, completion: (([EnginePeer.Id]) -> Signal<Never, TransferStarGiftError>)?) -> ViewController
|
||||
func makeGiftOptionsController(context: AccountContext, peerId: EnginePeer.Id, premiumOptions: [CachedPremiumGiftOption], hasBirthday: Bool, completion: (() -> Void)?) -> ViewController
|
||||
func makeGiftStoreController(context: AccountContext, peerId: EnginePeer.Id, gift: StarGift.Gift) -> ViewController
|
||||
func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController
|
||||
func makePremiumBoostLevelsController(context: AccountContext, peerId: EnginePeer.Id, subject: BoostSubject, boostStatus: ChannelBoostStatus, myBoostStatus: MyBoostStatus, forceDark: Bool, openStats: (() -> Void)?) -> ViewController
|
||||
|
||||
|
|
@ -1148,7 +1152,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
|
||||
func makeAvatarMediaPickerScreen(context: AccountContext, getSourceRect: @escaping () -> CGRect?, canDelete: Bool, performDelete: @escaping () -> Void, completion: @escaping (Any?, UIView?, CGRect, UIImage?, Bool, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, dismissed: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any]) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
||||
func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController
|
||||
|
||||
func makeStickerPickerScreen(context: AccountContext, inputData: Promise<StickerPickerInput>, completion: @escaping (FileMediaReference) -> Void) -> ViewController
|
||||
|
||||
|
|
@ -1174,6 +1178,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsWithdrawalScreen(context: AccountContext, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController
|
||||
func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController
|
||||
func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController
|
||||
func makeStarsIntroScreen(context: AccountContext) -> ViewController
|
||||
|
|
@ -1442,7 +1447,10 @@ public struct StarsSubscriptionConfiguration {
|
|||
usdWithdrawRate: 1200,
|
||||
paidMessageMaxAmount: 10000,
|
||||
paidMessageCommissionPermille: 850,
|
||||
paidMessagesAvailable: false
|
||||
paidMessagesAvailable: false,
|
||||
starGiftResaleMinAmount: 125,
|
||||
starGiftResaleMaxAmount: 3500,
|
||||
starGiftCommissionPermille: 80
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1451,19 +1459,28 @@ public struct StarsSubscriptionConfiguration {
|
|||
public let paidMessageMaxAmount: Int64
|
||||
public let paidMessageCommissionPermille: Int32
|
||||
public let paidMessagesAvailable: Bool
|
||||
public let starGiftResaleMinAmount: Int64
|
||||
public let starGiftResaleMaxAmount: Int64
|
||||
public let starGiftCommissionPermille: Int32
|
||||
|
||||
fileprivate init(
|
||||
maxFee: Int64,
|
||||
usdWithdrawRate: Int64,
|
||||
paidMessageMaxAmount: Int64,
|
||||
paidMessageCommissionPermille: Int32,
|
||||
paidMessagesAvailable: Bool
|
||||
paidMessagesAvailable: Bool,
|
||||
starGiftResaleMinAmount: Int64,
|
||||
starGiftResaleMaxAmount: Int64,
|
||||
starGiftCommissionPermille: Int32
|
||||
) {
|
||||
self.maxFee = maxFee
|
||||
self.usdWithdrawRate = usdWithdrawRate
|
||||
self.paidMessageMaxAmount = paidMessageMaxAmount
|
||||
self.paidMessageCommissionPermille = paidMessageCommissionPermille
|
||||
self.paidMessagesAvailable = paidMessagesAvailable
|
||||
self.starGiftResaleMinAmount = starGiftResaleMinAmount
|
||||
self.starGiftResaleMaxAmount = starGiftResaleMaxAmount
|
||||
self.starGiftCommissionPermille = starGiftCommissionPermille
|
||||
}
|
||||
|
||||
public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration {
|
||||
|
|
@ -1473,13 +1490,19 @@ public struct StarsSubscriptionConfiguration {
|
|||
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
|
||||
let starGiftResaleMinAmount = (data["stars_stargift_resale_amount_min"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftResaleMinAmount
|
||||
let starGiftResaleMaxAmount = (data["stars_stargift_resale_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftResaleMaxAmount
|
||||
let starGiftCommissionPermille = (data["stars_stargift_resale_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftCommissionPermille
|
||||
|
||||
return StarsSubscriptionConfiguration(
|
||||
maxFee: maxFee,
|
||||
usdWithdrawRate: usdWithdrawRate,
|
||||
paidMessageMaxAmount: paidMessageMaxAmount,
|
||||
paidMessageCommissionPermille: paidMessageCommissionPermille,
|
||||
paidMessagesAvailable: paidMessagesAvailable
|
||||
paidMessagesAvailable: paidMessagesAvailable,
|
||||
starGiftResaleMinAmount: starGiftResaleMinAmount,
|
||||
starGiftResaleMaxAmount: starGiftResaleMaxAmount,
|
||||
starGiftCommissionPermille: starGiftCommissionPermille
|
||||
)
|
||||
} else {
|
||||
return .defaultValue
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ public struct AttachmentMainButtonState {
|
|||
public let progress: Progress
|
||||
public let isEnabled: Bool
|
||||
public let hasShimmer: Bool
|
||||
public let iconName: String?
|
||||
public let smallSpacing: Bool
|
||||
public let position: Position?
|
||||
|
||||
public init(
|
||||
|
|
@ -53,6 +55,8 @@ public struct AttachmentMainButtonState {
|
|||
progress: Progress,
|
||||
isEnabled: Bool,
|
||||
hasShimmer: Bool,
|
||||
iconName: String? = nil,
|
||||
smallSpacing: Bool = false,
|
||||
position: Position? = nil
|
||||
) {
|
||||
self.text = text
|
||||
|
|
@ -64,6 +68,8 @@ public struct AttachmentMainButtonState {
|
|||
self.progress = progress
|
||||
self.isEnabled = isEnabled
|
||||
self.hasShimmer = hasShimmer
|
||||
self.iconName = iconName
|
||||
self.smallSpacing = smallSpacing
|
||||
self.position = position
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -608,7 +608,7 @@ public struct ChatTextInputStateText: Codable, Equatable {
|
|||
return lhs.text == rhs.text && lhs.attributes == rhs.attributes
|
||||
}
|
||||
|
||||
public func attributedText() -> NSAttributedString {
|
||||
public func attributedText(files: [Int64: TelegramMediaFile] = [:]) -> NSAttributedString {
|
||||
let result = NSMutableAttributedString(string: self.text)
|
||||
for attribute in self.attributes {
|
||||
switch attribute.type {
|
||||
|
|
@ -623,7 +623,7 @@ public struct ChatTextInputStateText: Codable, Equatable {
|
|||
case let .textUrl(url):
|
||||
result.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
case let .customEmoji(_, fileId, enableAnimation):
|
||||
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil, enableAnimation: enableAnimation), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
result.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: files[fileId], enableAnimation: enableAnimation), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
case .strikethrough:
|
||||
result.addAttribute(ChatTextInputAttributes.strikethrough, value: true as NSNumber, range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
case .underline:
|
||||
|
|
|
|||
|
|
@ -476,6 +476,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||
private var size: CGSize?
|
||||
|
||||
private let backgroundAnimationNode: ASImageNode
|
||||
private var iconNode: ASImageNode?
|
||||
fileprivate let textNode: ImmediateTextNode
|
||||
private var badgeNode: BadgeNode?
|
||||
private let statusNode: SemanticStatusNode
|
||||
|
|
@ -781,6 +782,26 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||
badgeNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let iconName = state.iconName {
|
||||
let iconNode: ASImageNode
|
||||
if let current = self.iconNode {
|
||||
iconNode = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.displaysAsynchronously = false
|
||||
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: iconName), color: state.textColor)
|
||||
self.iconNode = iconNode
|
||||
self.addSubnode(iconNode)
|
||||
}
|
||||
if let iconSize = iconNode.image?.size {
|
||||
textFrame.origin.x += (iconSize.width + 6.0) / 2.0
|
||||
iconNode.frame = CGRect(origin: CGPoint(x: textFrame.minX - iconSize.width - 6.0, y: textFrame.minY + floorToScreenPixels((textFrame.height - iconSize.height) * 0.5)), size: iconSize)
|
||||
}
|
||||
} else if let iconNode = self.iconNode {
|
||||
self.iconNode = nil
|
||||
iconNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if self.textNode.frame.width.isZero {
|
||||
self.textNode.frame = textFrame
|
||||
} else {
|
||||
|
|
@ -795,7 +816,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
|
|||
self.transitionFromProgress()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if let shimmerView = self.shimmerView, let borderView = self.borderView, let borderMaskView = self.borderMaskView, let borderShimmerView = self.borderShimmerView {
|
||||
let buttonFrame = CGRect(origin: .zero, size: size)
|
||||
let buttonWidth = size.width
|
||||
|
|
@ -1786,7 +1807,9 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
} else {
|
||||
height = bounds.height + 8.0
|
||||
}
|
||||
if !isNarrowButton {
|
||||
if isTwoVerticalButtons && self.secondaryButtonState.smallSpacing {
|
||||
|
||||
} else if !isNarrowButton {
|
||||
height += 9.0
|
||||
}
|
||||
if isTwoVerticalButtons {
|
||||
|
|
@ -1876,7 +1899,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
|||
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize)
|
||||
case .bottom:
|
||||
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
|
||||
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + sideInset + buttonSize.height), size: buttonSize)
|
||||
let buttonSpacing = self.secondaryButtonState.smallSpacing ? 8.0 : sideInset
|
||||
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY + buttonSpacing + buttonSize.height), size: buttonSize)
|
||||
case .left:
|
||||
secondaryButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX, y: buttonOriginY), size: buttonSize)
|
||||
mainButtonFrame = CGRect(origin: CGPoint(x: buttonOriginX + buttonSize.width + sideInset, y: buttonOriginY), size: buttonSize)
|
||||
|
|
|
|||
|
|
@ -357,6 +357,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
key = .music
|
||||
case .voice:
|
||||
key = .voice
|
||||
case .instantVideo:
|
||||
key = .instantVideo
|
||||
case .publicPosts:
|
||||
key = .publicPosts
|
||||
case let .date(minDate, maxDate, title):
|
||||
|
|
@ -685,6 +687,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
filterKey = .music
|
||||
case .voice:
|
||||
filterKey = .voice
|
||||
case .instantVideo:
|
||||
filterKey = .instantVideo
|
||||
case .publicPosts:
|
||||
filterKey = .publicPosts
|
||||
}
|
||||
|
|
@ -725,6 +729,8 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||
key = .music
|
||||
case .voice:
|
||||
key = .voice
|
||||
case .instantVideo:
|
||||
key = .instantVideo
|
||||
case .downloads:
|
||||
key = .downloads
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -108,6 +108,9 @@ private final class ItemNode: ASDisplayNode {
|
|||
case .voice:
|
||||
title = presentationData.strings.ChatList_Search_FilterVoice
|
||||
icon = nil
|
||||
case .instantVideo:
|
||||
title = presentationData.strings.ChatList_Search_FilterVoice
|
||||
icon = nil
|
||||
case .publicPosts:
|
||||
title = presentationData.strings.ChatList_Search_FilterPublicPosts
|
||||
icon = nil
|
||||
|
|
|
|||
|
|
@ -1587,8 +1587,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
|
||||
private let recentListNode: ListView
|
||||
private let shimmerNode: ChatListSearchShimmerNode
|
||||
private let listNode: ListView
|
||||
private let mediaNode: ChatListSearchMediaNode
|
||||
private let listNode: ListView?
|
||||
private let mediaNode: ChatListSearchMediaNode?
|
||||
private var enqueuedRecentTransitions: [(ChatListSearchContainerRecentTransition, Bool)] = []
|
||||
private var enqueuedTransitions: [(ChatListSearchContainerTransition, Bool)] = []
|
||||
|
||||
|
|
@ -1714,6 +1714,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
tagMask = .music
|
||||
case .voice:
|
||||
tagMask = .voiceOrInstantVideo
|
||||
case .instantVideo:
|
||||
tagMask = .roundVideo
|
||||
}
|
||||
self.tagMask = tagMask
|
||||
|
||||
|
|
@ -1737,8 +1739,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
self.shimmerNode.allowsGroupOpacity = true
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
self.listNode?.verticalScrollIndicatorColor = self.presentationData.theme.list.scrollIndicatorColor
|
||||
self.listNode?.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).string
|
||||
}
|
||||
|
||||
|
|
@ -1746,13 +1748,17 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
var transitionNodeImpl: ((EngineMessage.Id, EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?)?
|
||||
var addToTransitionSurfaceImpl: ((UIView) -> Void)?
|
||||
|
||||
self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in
|
||||
openMediaMessageImpl?(EngineMessage(message), mode)
|
||||
}, messageContextAction: { message, node, rect, gesture in
|
||||
interaction.mediaMessageContextAction(EngineMessage(message), node, rect, gesture)
|
||||
}, toggleMessageSelection: { messageId, selected in
|
||||
interaction.toggleMessageSelection(messageId, selected)
|
||||
})
|
||||
if key == .media {
|
||||
self.mediaNode = ChatListSearchMediaNode(context: self.context, contentType: .photoOrVideo, openMessage: { message, mode in
|
||||
openMediaMessageImpl?(EngineMessage(message), mode)
|
||||
}, messageContextAction: { message, node, rect, gesture in
|
||||
interaction.mediaMessageContextAction(EngineMessage(message), node, rect, gesture)
|
||||
}, toggleMessageSelection: { messageId, selected in
|
||||
interaction.toggleMessageSelection(messageId, selected)
|
||||
})
|
||||
} else {
|
||||
self.mediaNode = nil
|
||||
}
|
||||
|
||||
self.mediaAccessoryPanelContainer = PassthroughContainerNode()
|
||||
self.mediaAccessoryPanelContainer.clipsToBounds = true
|
||||
|
|
@ -1822,8 +1828,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
self.addSubnode(recentEmptyNode)
|
||||
}
|
||||
|
||||
self.addSubnode(self.listNode)
|
||||
self.addSubnode(self.mediaNode)
|
||||
if let listNode = self.listNode {
|
||||
self.addSubnode(listNode)
|
||||
}
|
||||
if let mediaNode = self.mediaNode {
|
||||
self.addSubnode(mediaNode)
|
||||
}
|
||||
|
||||
self.addSubnode(self.emptyResultsAnimationNode)
|
||||
self.addSubnode(self.emptyResultsTitleNode)
|
||||
|
|
@ -1850,8 +1860,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.listNode.isHidden = true
|
||||
self.mediaNode.isHidden = true
|
||||
self.listNode?.isHidden = true
|
||||
self.mediaNode?.isHidden = true
|
||||
self.recentListNode.isHidden = peersFilter.contains(.excludeRecent)
|
||||
|
||||
let currentRemotePeers = Atomic<([FoundPeer], [FoundPeer], [AdPeer])?>(value: nil)
|
||||
|
|
@ -3227,16 +3237,16 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
|
||||
transitionNodeImpl = { [weak self] messageId, media in
|
||||
if let strongSelf = self {
|
||||
return strongSelf.mediaNode.transitionNodeForGallery(messageId: messageId, media: media._asMedia())
|
||||
if let self {
|
||||
return self.mediaNode?.transitionNodeForGallery(messageId: messageId, media: media._asMedia())
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
addToTransitionSurfaceImpl = { [weak self] view in
|
||||
if let strongSelf = self {
|
||||
strongSelf.mediaNode.addToTransitionSurface(view: view)
|
||||
if let self {
|
||||
self.mediaNode?.addToTransitionSurface(view: view)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3270,7 +3280,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
case .savedMessagesChats:
|
||||
break
|
||||
}
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
self?.listNode?.clearHighlightAnimated(true)
|
||||
}, disabledPeerSelected: { _, _, _ in
|
||||
}, togglePeerSelected: { _, _ in
|
||||
}, togglePeersSelection: { _, _ in
|
||||
|
|
@ -3280,12 +3290,12 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
if let strongSelf = self, let peer = message.peers[message.id.peerId] {
|
||||
interaction.openMessage(EnginePeer(peer), threadId, message.id, strongSelf.key == .chats)
|
||||
}
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
self?.listNode?.clearHighlightAnimated(true)
|
||||
}, groupSelected: { _ in
|
||||
}, addContact: { [weak self] phoneNumber in
|
||||
interaction.dismissInput()
|
||||
interaction.addContact(phoneNumber)
|
||||
self?.listNode.clearHighlightAnimated(true)
|
||||
self?.listNode?.clearHighlightAnimated(true)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in
|
||||
}, setItemPinned: { _, _ in
|
||||
}, setPeerMuted: { _, _ in
|
||||
|
|
@ -3328,7 +3338,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
interaction.dismissInput()
|
||||
interaction.openPeer(peer, peer, threadId, false)
|
||||
self.listNode.clearHighlightAnimated(true)
|
||||
self.listNode?.clearHighlightAnimated(true)
|
||||
})
|
||||
}, openStorageManagement: {
|
||||
}, openPasswordSetup: {
|
||||
|
|
@ -3397,7 +3407,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}, transitionNode: { messageId, media, _ in
|
||||
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||
if let strongSelf = self {
|
||||
strongSelf.listNode.forEachItemNode { itemNode in
|
||||
strongSelf.listNode?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ListMessageNode {
|
||||
if let result = itemNode.transitionNode(id: messageId, media: media, adjustRect: false) {
|
||||
transitionNode = result
|
||||
|
|
@ -3556,7 +3566,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
if isSearching && (entries?.isEmpty ?? true) {
|
||||
entries = nil
|
||||
}
|
||||
strongSelf.mediaNode.updateHistory(entries: entries, totalCount: 0, updateType: .Initial)
|
||||
strongSelf.mediaNode?.updateHistory(entries: entries, totalCount: 0, updateType: .Initial)
|
||||
} else if strongSelf.tagMask == .roundVideo {
|
||||
|
||||
}
|
||||
|
||||
var peers: [EnginePeer] = []
|
||||
|
|
@ -4349,7 +4361,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
if strongSelf.backgroundColor != nil {
|
||||
strongSelf.backgroundColor = presentationData.theme.chatList.backgroundColor
|
||||
}
|
||||
strongSelf.listNode.forEachItemHeaderNode({ itemHeaderNode in
|
||||
strongSelf.listNode?.forEachItemHeaderNode({ itemHeaderNode in
|
||||
if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode {
|
||||
itemHeaderNode.updateTheme(theme: presentationData.theme)
|
||||
}
|
||||
|
|
@ -4367,26 +4379,26 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
interaction.dismissInput()
|
||||
}
|
||||
|
||||
self.listNode.beganInteractiveDragging = { _ in
|
||||
self.listNode?.beganInteractiveDragging = { _ in
|
||||
interaction.dismissInput()
|
||||
}
|
||||
|
||||
self.mediaNode.beganInteractiveDragging = {
|
||||
self.mediaNode?.beganInteractiveDragging = {
|
||||
interaction.dismissInput()
|
||||
}
|
||||
|
||||
self.listNode.visibleBottomContentOffsetChanged = { offset in
|
||||
self.listNode?.visibleBottomContentOffsetChanged = { offset in
|
||||
guard case let .known(value) = offset, value < 160.0 else {
|
||||
return
|
||||
}
|
||||
loadMore()
|
||||
}
|
||||
|
||||
self.mediaNode.loadMore = {
|
||||
self.mediaNode?.loadMore = {
|
||||
loadMore()
|
||||
}
|
||||
|
||||
if [.file, .music, .voiceOrInstantVideo].contains(tagMask) || self.key == .downloads {
|
||||
if [.file, .music, .voiceOrInstantVideo, .voice, .roundVideo].contains(tagMask) || self.key == .downloads {
|
||||
let key = self.key
|
||||
self.mediaStatusDisposable = (context.sharedContext.mediaManager.globalMediaPlayerState
|
||||
|> mapToSignal { playlistStateAndType -> Signal<(Account, SharedMediaPlayerItemPlaybackState, MediaManagerPlayerType)?, NoError> in
|
||||
|
|
@ -4396,7 +4408,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
if let playlistId = state.playlistId as? PeerMessagesMediaPlaylistId, case .custom = playlistId {
|
||||
switch type {
|
||||
case .voice:
|
||||
if tagMask != .voiceOrInstantVideo {
|
||||
if ![.voiceOrInstantVideo, .voice, .roundVideo].contains(tagMask) {
|
||||
return .single(nil) |> delay(0.2, queue: .mainQueue())
|
||||
}
|
||||
case .music:
|
||||
|
|
@ -4524,8 +4536,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
|
||||
func scrollToTop() -> Bool {
|
||||
if !self.mediaNode.isHidden {
|
||||
return self.mediaNode.scrollToTop()
|
||||
if let mediaNode = self.mediaNode, !mediaNode.isHidden {
|
||||
return mediaNode.scrollToTop()
|
||||
} else if !self.recentListNode.isHidden {
|
||||
let offset = self.recentListNode.visibleContentOffset()
|
||||
switch offset {
|
||||
|
|
@ -4535,15 +4547,17 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
self.recentListNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
let offset = self.listNode.visibleContentOffset()
|
||||
} else if let listNode = self.listNode {
|
||||
let offset = listNode.visibleContentOffset()
|
||||
switch offset {
|
||||
case let .known(value) where value <= CGFloat.ulpOfOne:
|
||||
return false
|
||||
default:
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4852,11 +4866,11 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
emptyRecentAnimationNode.updateLayout(size: emptyRecentAnimationSize)
|
||||
}
|
||||
|
||||
self.listNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
self.listNode?.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.listNode?.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: size, insets: insets, duration: duration, curve: curve), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
|
||||
|
||||
self.mediaNode.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height))
|
||||
self.mediaNode.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition)
|
||||
self.mediaNode?.frame = CGRect(origin: CGPoint(x: 0.0, y: topInset), size: CGSize(width: size.width, height: size.height))
|
||||
self.mediaNode?.update(size: size, sideInset: sideInset, bottomInset: bottomInset, visibleHeight: visibleHeight, isScrollingLockedAtTop: false, expandProgress: 1.0, presentationData: self.presentationData, synchronous: true, transition: transition)
|
||||
|
||||
do {
|
||||
let padding: CGFloat = 16.0
|
||||
|
|
@ -4887,7 +4901,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
|
||||
func updateHiddenMedia() {
|
||||
self.listNode.forEachItemNode { itemNode in
|
||||
self.listNode?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ListMessageNode {
|
||||
itemNode.updateHiddenMedia()
|
||||
}
|
||||
|
|
@ -4899,7 +4913,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
|
||||
func transitionNodeForGallery(messageId: EngineMessage.Id, media: EngineMedia) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||
self.listNode.forEachItemNode { itemNode in
|
||||
self.listNode?.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ListMessageNode {
|
||||
if let result = itemNode.transitionNode(id: messageId, media: media._asMedia(), adjustRect: false) {
|
||||
transitionNode = result
|
||||
|
|
@ -4915,8 +4929,8 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
|
||||
func updateSelectedMessages(animated: Bool) {
|
||||
self.selectedMessages = self.interaction.getSelectedMessageIds()
|
||||
self.mediaNode.selectedMessageIds = self.selectedMessages
|
||||
self.mediaNode.updateSelectedMessages(animated: animated)
|
||||
self.mediaNode?.selectedMessageIds = self.selectedMessages
|
||||
self.mediaNode?.updateSelectedMessages(animated: animated)
|
||||
}
|
||||
|
||||
func removeAds() {
|
||||
|
|
@ -5006,16 +5020,16 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
options.insert(.PreferSynchronousResourceLoading)
|
||||
}
|
||||
|
||||
self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
self.listNode?.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
let searchOptions = strongSelf.searchOptionsValue
|
||||
strongSelf.listNode.isHidden = strongSelf.tagMask == .photoOrVideo && (strongSelf.searchQueryValue ?? "").isEmpty
|
||||
strongSelf.mediaNode.isHidden = !strongSelf.listNode.isHidden
|
||||
strongSelf.listNode?.isHidden = strongSelf.tagMask == .photoOrVideo && (strongSelf.searchQueryValue ?? "").isEmpty
|
||||
strongSelf.mediaNode?.isHidden = !(strongSelf.listNode?.isHidden ?? true)
|
||||
|
||||
let displayingResults = transition.displayingResults
|
||||
if !displayingResults {
|
||||
strongSelf.listNode.isHidden = true
|
||||
strongSelf.mediaNode.isHidden = true
|
||||
strongSelf.listNode?.isHidden = true
|
||||
strongSelf.mediaNode?.isHidden = true
|
||||
}
|
||||
|
||||
let emptyResults = displayingResults && transition.isEmpty
|
||||
|
|
@ -5103,7 +5117,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
|||
}
|
||||
} else {
|
||||
let adjustedLocation = self.convert(location, to: self.listNode)
|
||||
self.listNode.forEachItemNode { itemNode in
|
||||
self.listNode?.forEachItemNode { itemNode in
|
||||
if itemNode.frame.contains(adjustedLocation) {
|
||||
selectedItemNode = itemNode
|
||||
}
|
||||
|
|
@ -5506,7 +5520,7 @@ public final class ChatListSearchShimmerNode: ASDisplayNode {
|
|||
)
|
||||
|
||||
return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: context, chatLocation: .peer(id: peer1.id), interaction: ListMessageItemInteraction.default, message: message._asMessage(), selection: hasSelection ? .selectable(selected: false) : .none, displayHeader: false, customHeader: nil, hintIsLink: false, isGlobalSearchResult: true)
|
||||
case .voice:
|
||||
case .voice, .instantVideo:
|
||||
var media: [EngineMedia] = []
|
||||
media.append(.file(TelegramMediaFile(fileId: EngineMedia.Id(namespace: 0, id: 0), partialReference: nil, resource: LocalFileMediaResource(fileId: 0), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "audio/ogg", size: 0, attributes: [.Audio(isVoice: true, duration: 0, title: nil, performer: nil, waveform: Data())], alternativeRepresentations: [])))
|
||||
let message = EngineMessage(
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ public enum ChatListSearchPaneKey {
|
|||
case files
|
||||
case music
|
||||
case voice
|
||||
case instantVideo
|
||||
}
|
||||
|
||||
extension ChatListSearchPaneKey {
|
||||
|
|
@ -88,6 +89,8 @@ extension ChatListSearchPaneKey {
|
|||
return .music
|
||||
case .voice:
|
||||
return .voice
|
||||
case .instantVideo:
|
||||
return .instantVideo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ public final class ContextMenuActionItem {
|
|||
public let id: AnyHashable?
|
||||
public let text: String
|
||||
public let entities: [MessageTextEntity]
|
||||
public let entityFiles: [Int64: TelegramMediaFile]
|
||||
public let enableEntityAnimations: Bool
|
||||
public let textColor: ContextMenuActionItemTextColor
|
||||
public let textFont: ContextMenuActionItemFont
|
||||
|
|
@ -147,6 +148,7 @@ public final class ContextMenuActionItem {
|
|||
id: AnyHashable? = nil,
|
||||
text: String,
|
||||
entities: [MessageTextEntity] = [],
|
||||
entityFiles: [Int64: TelegramMediaFile] = [:],
|
||||
enableEntityAnimations: Bool = true,
|
||||
textColor: ContextMenuActionItemTextColor = .primary,
|
||||
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
|
||||
|
|
@ -168,6 +170,7 @@ public final class ContextMenuActionItem {
|
|||
id: id,
|
||||
text: text,
|
||||
entities: entities,
|
||||
entityFiles: entityFiles,
|
||||
enableEntityAnimations: enableEntityAnimations,
|
||||
textColor: textColor,
|
||||
textLayout: textLayout,
|
||||
|
|
@ -199,6 +202,7 @@ public final class ContextMenuActionItem {
|
|||
id: AnyHashable? = nil,
|
||||
text: String,
|
||||
entities: [MessageTextEntity] = [],
|
||||
entityFiles: [Int64: TelegramMediaFile] = [:],
|
||||
enableEntityAnimations: Bool = true,
|
||||
textColor: ContextMenuActionItemTextColor = .primary,
|
||||
textLayout: ContextMenuActionItemTextLayout = .twoLinesMax,
|
||||
|
|
@ -219,6 +223,7 @@ public final class ContextMenuActionItem {
|
|||
self.id = id
|
||||
self.text = text
|
||||
self.entities = entities
|
||||
self.entityFiles = entityFiles
|
||||
self.enableEntityAnimations = enableEntityAnimations
|
||||
self.textColor = textColor
|
||||
self.textFont = textFont
|
||||
|
|
@ -2140,6 +2145,8 @@ public protocol ContextReferenceContentSource: AnyObject {
|
|||
|
||||
var shouldBeDismissed: Signal<Bool, NoError> { get }
|
||||
|
||||
var forceDisplayBelowKeyboard: Bool { get }
|
||||
|
||||
func transitionInfo() -> ContextControllerReferenceViewInfo?
|
||||
}
|
||||
|
||||
|
|
@ -2148,6 +2155,10 @@ public extension ContextReferenceContentSource {
|
|||
return false
|
||||
}
|
||||
|
||||
var forceDisplayBelowKeyboard: Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var shouldBeDismissed: Signal<Bool, NoError> {
|
||||
return .single(false)
|
||||
}
|
||||
|
|
@ -2739,7 +2750,9 @@ public final class ContextController: ViewController, StandalonePresentableContr
|
|||
}
|
||||
|
||||
public func dismiss(result: ContextMenuActionResult, completion: (() -> Void)?) {
|
||||
if viewTreeContainsFirstResponder(view: self.view) {
|
||||
if let mainSource = self.configuration.sources.first(where: { $0.id == self.configuration.initialId }), case let .reference(source) = mainSource.source, source.forceDisplayBelowKeyboard {
|
||||
|
||||
} else if viewTreeContainsFirstResponder(view: self.view) {
|
||||
self.dismissOnInputClose = (result, completion)
|
||||
self.view.endEditing(true)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -273,7 +273,7 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
return super.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
func setItem(item: ContextMenuActionItem) {
|
||||
public func setItem(item: ContextMenuActionItem) {
|
||||
self.item = item
|
||||
self.accessibilityLabel = item.text
|
||||
}
|
||||
|
|
@ -361,14 +361,25 @@ public final class ContextControllerActionsListActionItemNode: HighlightTracking
|
|||
let inputStateText = ChatTextInputStateText(text: self.item.text, attributes: self.item.entities.compactMap { entity -> ChatTextInputStateTextAttribute? in
|
||||
if case let .CustomEmoji(_, fileId) = entity.type {
|
||||
return ChatTextInputStateTextAttribute(type: .customEmoji(stickerPack: nil, fileId: fileId, enableAnimation: true), range: entity.range)
|
||||
} else if case .Bold = entity.type {
|
||||
return ChatTextInputStateTextAttribute(type: .bold, range: entity.range)
|
||||
} else if case .Italic = entity.type {
|
||||
return ChatTextInputStateTextAttribute(type: .italic, range: entity.range)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
let result = NSMutableAttributedString(attributedString: inputStateText.attributedText())
|
||||
let result = NSMutableAttributedString(attributedString: inputStateText.attributedText(files: self.item.entityFiles))
|
||||
result.addAttributes([
|
||||
.font: titleFont,
|
||||
.foregroundColor: titleColor
|
||||
], range: NSRange(location: 0, length: result.length))
|
||||
for attribute in inputStateText.attributes {
|
||||
if case .bold = attribute.type {
|
||||
result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(presentationData.listsFontSize.baseDisplaySize), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
} else if case .italic = attribute.type {
|
||||
result.addAttribute(NSAttributedString.Key.font, value: Font.semibold(15.0), range: NSRange(location: attribute.range.lowerBound, length: attribute.range.count))
|
||||
}
|
||||
}
|
||||
attributedText = result
|
||||
} else {
|
||||
attributedText = parseMarkdownIntoAttributedString(
|
||||
|
|
|
|||
|
|
@ -500,6 +500,8 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
func wantsDisplayBelowKeyboard() -> Bool {
|
||||
if let reactionContextNode = self.reactionContextNode {
|
||||
return reactionContextNode.wantsDisplayBelowKeyboard()
|
||||
} else if case let .reference(source) = self.source {
|
||||
return source.forceDisplayBelowKeyboard
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -437,8 +437,8 @@ public extension CALayer {
|
|||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.size.height", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
|
||||
func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
|
||||
}
|
||||
|
||||
func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
|
|
|
|||
|
|
@ -566,6 +566,14 @@ public final class DrawingEntitiesView: UIView, TGPhotoDrawingEntitiesView {
|
|||
self.hasSelectionChanged(false)
|
||||
}
|
||||
|
||||
public func clearAll() {
|
||||
for case let view as DrawingEntityView in self.subviews {
|
||||
view.reset()
|
||||
view.selectionView?.removeFromSuperview()
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
private func clear(animated: Bool = false) {
|
||||
if animated {
|
||||
for case let view as DrawingEntityView in self.subviews {
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
private let bannedSendVideos: (Int32, Bool)?
|
||||
private let canBoostToUnrestrict: Bool
|
||||
fileprivate let paidMediaAllowed: Bool
|
||||
private let subject: Subject
|
||||
fileprivate let subject: Subject
|
||||
fileprivate let forCollage: Bool
|
||||
private let saveEditedPhotos: Bool
|
||||
|
||||
|
|
@ -1823,8 +1823,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
private var isDismissing = false
|
||||
|
||||
fileprivate let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||
fileprivate let secondaryButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||
|
||||
private let mainButtonAction: (() -> Void)?
|
||||
private let secondaryButtonAction: (() -> Void)?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
|
|
@ -1844,7 +1846,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
selectionContext: TGMediaSelectionContext? = nil,
|
||||
saveEditedPhotos: Bool = false,
|
||||
mainButtonState: AttachmentMainButtonState? = nil,
|
||||
mainButtonAction: (() -> Void)? = nil
|
||||
mainButtonAction: (() -> Void)? = nil,
|
||||
secondaryButtonAction: (() -> Void)? = nil
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
|
|
@ -1864,6 +1867,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
self.saveEditedPhotos = saveEditedPhotos
|
||||
self.mainButtonStatePromise.set(.single(mainButtonState))
|
||||
self.mainButtonAction = mainButtonAction
|
||||
self.secondaryButtonAction = secondaryButtonAction
|
||||
|
||||
let selectionContext = selectionContext ?? TGMediaSelectionContext()
|
||||
|
||||
|
|
@ -1997,7 +2001,14 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
} else if collection == nil {
|
||||
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
|
||||
|
||||
var hasSelect = false
|
||||
if forCollage {
|
||||
hasSelect = true
|
||||
} else if case .story = mode {
|
||||
hasSelect = true
|
||||
}
|
||||
|
||||
if hasSelect {
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(backButtonAppearanceWithTitle: self.presentationData.strings.Common_Select, target: self, action: #selector(self.selectPressed))
|
||||
} else {
|
||||
if [.createSticker].contains(mode) {
|
||||
|
|
@ -2337,6 +2348,9 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
let transition = ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
|
||||
var moreIsVisible = false
|
||||
if case let .assets(_, mode) = self.subject, [.story, .createSticker].contains(mode) {
|
||||
if count == 1 {
|
||||
self.requestAttachmentMenuExpansion()
|
||||
}
|
||||
moreIsVisible = true
|
||||
} else if case let .media(media) = self.subject {
|
||||
self.titleView.title = media.count == 1 ? self.presentationData.strings.Attachment_Pasteboard : self.presentationData.strings.Attachment_SelectedMedia(count)
|
||||
|
|
@ -2380,9 +2394,23 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
transition.updateAlpha(node: self.moreButtonNode.iconNode, alpha: moreIsVisible ? 1.0 : 0.0)
|
||||
transition.updateTransformScale(node: self.moreButtonNode.iconNode, scale: moreIsVisible ? 1.0 : 0.1)
|
||||
|
||||
//if self. {
|
||||
//self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Add", badge: "\(count)", font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: count > 0, progress: .none, isEnabled: true, hasShimmer: false)))
|
||||
//}
|
||||
if case .assets(_, .story) = self.subject, self.selectionCount > 0 {
|
||||
//TODO:localize
|
||||
var text = "Create 1 Story"
|
||||
if self.selectionCount > 1 {
|
||||
text = "Create \(self.selectionCount) Stories"
|
||||
}
|
||||
self.mainButtonStatePromise.set(.single(AttachmentMainButtonState(text: text, badge: nil, font: .bold, background: .color(self.presentationData.theme.actionSheet.controlAccentColor), textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, position: .top)))
|
||||
|
||||
if self.selectionCount > 1 && self.selectionCount <= 6 {
|
||||
self.secondaryButtonStatePromise.set(.single(AttachmentMainButtonState(text: "Combine into Collage", badge: nil, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true, hasShimmer: false, iconName: "Media Editor/Collage", smallSpacing: true, position: .bottom)))
|
||||
} else {
|
||||
self.secondaryButtonStatePromise.set(.single(nil))
|
||||
}
|
||||
} else {
|
||||
self.mainButtonStatePromise.set(.single(nil))
|
||||
self.secondaryButtonStatePromise.set(.single(nil))
|
||||
}
|
||||
}
|
||||
|
||||
private func updateThemeAndStrings() {
|
||||
|
|
@ -2412,6 +2440,10 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
|
|||
self.mainButtonAction?()
|
||||
}
|
||||
|
||||
func secondaryButtonPressed() {
|
||||
self.secondaryButtonAction?()
|
||||
}
|
||||
|
||||
func dismissAllTooltips() {
|
||||
self.undoOverlayController?.dismissWithCommitAction()
|
||||
}
|
||||
|
|
@ -2795,7 +2827,7 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||
private weak var controller: MediaPickerScreenImpl?
|
||||
|
||||
var selectionCount: Signal<Int, NoError> {
|
||||
if self.controller?.forCollage == true {
|
||||
if let controller = self.controller, case .assets(_, .story) = controller.subject {
|
||||
return .single(0)
|
||||
} else {
|
||||
return Signal { [weak self] subscriber in
|
||||
|
|
@ -2933,6 +2965,10 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||
return self.controller?.mainButtonStatePromise.get() ?? .single(nil)
|
||||
}
|
||||
|
||||
public var secondaryButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
||||
return self.controller?.secondaryButtonStatePromise.get() ?? .single(nil)
|
||||
}
|
||||
|
||||
init(controller: MediaPickerScreenImpl) {
|
||||
self.controller = controller
|
||||
}
|
||||
|
|
@ -2952,6 +2988,10 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
|
|||
func mainButtonAction() {
|
||||
self.controller?.mainButtonPressed()
|
||||
}
|
||||
|
||||
func secondaryButtonAction() {
|
||||
self.controller?.secondaryButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private final class MediaPickerContextReferenceContentSource: ContextReferenceContentSource {
|
||||
|
|
@ -3139,7 +3179,7 @@ public func storyMediaPickerController(
|
|||
selectionLimit: Int?,
|
||||
getSourceRect: @escaping () -> CGRect,
|
||||
completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void,
|
||||
multipleCompletion: @escaping ([Any]) -> Void,
|
||||
multipleCompletion: @escaping ([Any], Bool) -> Void,
|
||||
dismissed: @escaping () -> Void,
|
||||
groupsPresented: @escaping () -> Void
|
||||
) -> ViewController {
|
||||
|
|
@ -3158,9 +3198,18 @@ public func storyMediaPickerController(
|
|||
}
|
||||
}
|
||||
|
||||
let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
|
||||
return nil
|
||||
})
|
||||
let controller = AttachmentController(
|
||||
context: context,
|
||||
updatedPresentationData: updatedPresentationData,
|
||||
chatLocation: nil,
|
||||
buttons: [.standalone],
|
||||
initialButton: .standalone,
|
||||
fromMenu: false,
|
||||
hasTextInput: false,
|
||||
makeEntityInputView: {
|
||||
return nil
|
||||
}
|
||||
)
|
||||
controller.forceSourceRect = true
|
||||
controller.getSourceRect = getSourceRect
|
||||
controller.requestController = { _, present in
|
||||
|
|
@ -3184,7 +3233,18 @@ public func storyMediaPickerController(
|
|||
results.append(asset)
|
||||
}
|
||||
}
|
||||
multipleCompletion(results)
|
||||
multipleCompletion(results, false)
|
||||
}
|
||||
},
|
||||
secondaryButtonAction: { [weak selectionContext] in
|
||||
if let selectionContext, let selectedItems = selectionContext.selectedItems() {
|
||||
var results: [Any] = []
|
||||
for item in selectedItems {
|
||||
if let item = item as? TGMediaAsset, let asset = item.backingAsset {
|
||||
results.append(asset)
|
||||
}
|
||||
}
|
||||
multipleCompletion(results, true)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[-625298705] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftStars($0) }
|
||||
dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) }
|
||||
dict[-396206446] = { return Api.InputInvoice.parse_inputInvoiceStarGift($0) }
|
||||
dict[1674298252] = { return Api.InputInvoice.parse_inputInvoiceStarGiftResale($0) }
|
||||
dict[1247763417] = { return Api.InputInvoice.parse_inputInvoiceStarGiftTransfer($0) }
|
||||
dict[1300335965] = { return Api.InputInvoice.parse_inputInvoiceStarGiftUpgrade($0) }
|
||||
dict[1710230755] = { return Api.InputInvoice.parse_inputInvoiceStars($0) }
|
||||
|
|
@ -600,7 +601,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[1348510708] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) }
|
||||
dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
|
||||
dict[1192749220] = { return Api.MessageAction.parse_messageActionStarGift($0) }
|
||||
dict[-1394619519] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) }
|
||||
dict[1600878025] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) }
|
||||
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
|
||||
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
|
||||
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
|
||||
|
|
@ -932,12 +933,16 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[1301522832] = { return Api.SponsoredMessage.parse_sponsoredMessage($0) }
|
||||
dict[1124938064] = { return Api.SponsoredMessageReportOption.parse_sponsoredMessageReportOption($0) }
|
||||
dict[-963180333] = { return Api.SponsoredPeer.parse_sponsoredPeer($0) }
|
||||
dict[46953416] = { return Api.StarGift.parse_starGift($0) }
|
||||
dict[1549979985] = { return Api.StarGift.parse_starGiftUnique($0) }
|
||||
dict[-1809377438] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
|
||||
dict[-970274264] = { return Api.StarGift.parse_starGift($0) }
|
||||
dict[1678891913] = { return Api.StarGift.parse_starGiftUnique($0) }
|
||||
dict[-650279524] = { return Api.StarGiftAttribute.parse_starGiftAttributeBackdrop($0) }
|
||||
dict[970559507] = { return Api.StarGiftAttribute.parse_starGiftAttributeModel($0) }
|
||||
dict[-524291476] = { return Api.StarGiftAttribute.parse_starGiftAttributeOriginalDetails($0) }
|
||||
dict[330104601] = { return Api.StarGiftAttribute.parse_starGiftAttributePattern($0) }
|
||||
dict[783398488] = { return Api.StarGiftAttributeCounter.parse_starGiftAttributeCounter($0) }
|
||||
dict[520210263] = { return Api.StarGiftAttributeId.parse_starGiftAttributeIdBackdrop($0) }
|
||||
dict[1219145276] = { return Api.StarGiftAttributeId.parse_starGiftAttributeIdModel($0) }
|
||||
dict[1242965043] = { return Api.StarGiftAttributeId.parse_starGiftAttributeIdPattern($0) }
|
||||
dict[-586389774] = { return Api.StarRefProgram.parse_starRefProgram($0) }
|
||||
dict[-1145654109] = { return Api.StarsAmount.parse_starsAmount($0) }
|
||||
dict[1577421297] = { return Api.StarsGiftOption.parse_starsGiftOption($0) }
|
||||
|
|
@ -1405,6 +1410,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[-625215430] = { return Api.payments.PaymentReceipt.parse_paymentReceiptStars($0) }
|
||||
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
|
||||
dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) }
|
||||
dict[-1803939105] = { return Api.payments.ResaleStarGifts.parse_resaleStarGifts($0) }
|
||||
dict[-74456004] = { return Api.payments.SavedInfo.parse_savedInfo($0) }
|
||||
dict[-1779201615] = { return Api.payments.SavedStarGifts.parse_savedStarGifts($0) }
|
||||
dict[377215243] = { return Api.payments.StarGiftUpgradePreview.parse_starGiftUpgradePreview($0) }
|
||||
|
|
@ -2117,6 +2123,10 @@ public extension Api {
|
|||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StarGiftAttribute:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StarGiftAttributeCounter:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StarGiftAttributeId:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StarRefProgram:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.StarsAmount:
|
||||
|
|
@ -2505,6 +2515,8 @@ public extension Api {
|
|||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.PaymentResult:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.ResaleStarGifts:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.SavedInfo:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.SavedStarGifts:
|
||||
|
|
|
|||
|
|
@ -255,6 +255,7 @@ public extension Api {
|
|||
case inputInvoicePremiumGiftStars(flags: Int32, userId: Api.InputUser, months: Int32, message: Api.TextWithEntities?)
|
||||
case inputInvoiceSlug(slug: String)
|
||||
case inputInvoiceStarGift(flags: Int32, peer: Api.InputPeer, giftId: Int64, message: Api.TextWithEntities?)
|
||||
case inputInvoiceStarGiftResale(slug: String, toId: Api.InputPeer)
|
||||
case inputInvoiceStarGiftTransfer(stargift: Api.InputSavedStarGift, toId: Api.InputPeer)
|
||||
case inputInvoiceStarGiftUpgrade(flags: Int32, stargift: Api.InputSavedStarGift)
|
||||
case inputInvoiceStars(purpose: Api.InputStorePaymentPurpose)
|
||||
|
|
@ -312,6 +313,13 @@ public extension Api {
|
|||
serializeInt64(giftId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {message!.serialize(buffer, true)}
|
||||
break
|
||||
case .inputInvoiceStarGiftResale(let slug, let toId):
|
||||
if boxed {
|
||||
buffer.appendInt32(1674298252)
|
||||
}
|
||||
serializeString(slug, buffer: buffer, boxed: false)
|
||||
toId.serialize(buffer, true)
|
||||
break
|
||||
case .inputInvoiceStarGiftTransfer(let stargift, let toId):
|
||||
if boxed {
|
||||
buffer.appendInt32(1247763417)
|
||||
|
|
@ -351,6 +359,8 @@ public extension Api {
|
|||
return ("inputInvoiceSlug", [("slug", slug as Any)])
|
||||
case .inputInvoiceStarGift(let flags, let peer, let giftId, let message):
|
||||
return ("inputInvoiceStarGift", [("flags", flags as Any), ("peer", peer as Any), ("giftId", giftId as Any), ("message", message as Any)])
|
||||
case .inputInvoiceStarGiftResale(let slug, let toId):
|
||||
return ("inputInvoiceStarGiftResale", [("slug", slug as Any), ("toId", toId as Any)])
|
||||
case .inputInvoiceStarGiftTransfer(let stargift, let toId):
|
||||
return ("inputInvoiceStarGiftTransfer", [("stargift", stargift as Any), ("toId", toId as Any)])
|
||||
case .inputInvoiceStarGiftUpgrade(let flags, let stargift):
|
||||
|
|
@ -480,6 +490,22 @@ public extension Api {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputInvoiceStarGiftResale(_ reader: BufferReader) -> InputInvoice? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
var _2: Api.InputPeer?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.InputPeer
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.InputInvoice.inputInvoiceStarGiftResale(slug: _1!, toId: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputInvoiceStarGiftTransfer(_ reader: BufferReader) -> InputInvoice? {
|
||||
var _1: Api.InputSavedStarGift?
|
||||
if let signature = reader.readInt32() {
|
||||
|
|
|
|||
|
|
@ -381,7 +381,7 @@ public extension Api {
|
|||
case messageActionSetChatWallPaper(flags: Int32, wallpaper: Api.WallPaper)
|
||||
case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?)
|
||||
case messageActionStarGift(flags: Int32, gift: Api.StarGift, message: Api.TextWithEntities?, convertStars: Int64?, upgradeMsgId: Int32?, upgradeStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?)
|
||||
case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?)
|
||||
case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleStars: Int64?)
|
||||
case messageActionSuggestProfilePhoto(photo: Api.Photo)
|
||||
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
|
||||
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
|
||||
|
|
@ -767,9 +767,9 @@ public extension Api {
|
|||
if Int(flags) & Int(1 << 12) != 0 {peer!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 12) != 0 {serializeInt64(savedId!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId):
|
||||
case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleStars):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1394619519)
|
||||
buffer.appendInt32(1600878025)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
gift.serialize(buffer, true)
|
||||
|
|
@ -778,6 +778,7 @@ public extension Api {
|
|||
if Int(flags) & Int(1 << 6) != 0 {fromId!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 7) != 0 {peer!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 7) != 0 {serializeInt64(savedId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 8) != 0 {serializeInt64(resaleStars!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .messageActionSuggestProfilePhoto(let photo):
|
||||
if boxed {
|
||||
|
|
@ -912,8 +913,8 @@ public extension Api {
|
|||
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
|
||||
case .messageActionStarGift(let flags, let gift, let message, let convertStars, let upgradeMsgId, let upgradeStars, let fromId, let peer, let savedId):
|
||||
return ("messageActionStarGift", [("flags", flags as Any), ("gift", gift as Any), ("message", message as Any), ("convertStars", convertStars as Any), ("upgradeMsgId", upgradeMsgId as Any), ("upgradeStars", upgradeStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any)])
|
||||
case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId):
|
||||
return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any)])
|
||||
case .messageActionStarGiftUnique(let flags, let gift, let canExportAt, let transferStars, let fromId, let peer, let savedId, let resaleStars):
|
||||
return ("messageActionStarGiftUnique", [("flags", flags as Any), ("gift", gift as Any), ("canExportAt", canExportAt as Any), ("transferStars", transferStars as Any), ("fromId", fromId as Any), ("peer", peer as Any), ("savedId", savedId as Any), ("resaleStars", resaleStars as Any)])
|
||||
case .messageActionSuggestProfilePhoto(let photo):
|
||||
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
|
||||
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
|
||||
|
|
@ -1672,6 +1673,8 @@ public extension Api {
|
|||
} }
|
||||
var _7: Int64?
|
||||
if Int(_1!) & Int(1 << 7) != 0 {_7 = reader.readInt64() }
|
||||
var _8: Int64?
|
||||
if Int(_1!) & Int(1 << 8) != 0 {_8 = reader.readInt64() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil
|
||||
|
|
@ -1679,8 +1682,9 @@ public extension Api {
|
|||
let _c5 = (Int(_1!) & Int(1 << 6) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 7) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7)
|
||||
let _c8 = (Int(_1!) & Int(1 << 8) == 0) || _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.MessageAction.messageActionStarGiftUnique(flags: _1!, gift: _2!, canExportAt: _3, transferStars: _4, fromId: _5, peer: _6, savedId: _7, resaleStars: _8)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -628,14 +628,14 @@ public extension Api {
|
|||
}
|
||||
public extension Api {
|
||||
enum StarGift: TypeConstructorDescription {
|
||||
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?)
|
||||
case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?)
|
||||
case starGift(flags: Int32, id: Int64, sticker: Api.Document, stars: Int64, availabilityRemains: Int32?, availabilityTotal: Int32?, availabilityResale: Int64?, convertStars: Int64, firstSaleDate: Int32?, lastSaleDate: Int32?, upgradeStars: Int64?, resellMinStars: Int64?, title: String?)
|
||||
case starGiftUnique(flags: Int32, id: Int64, title: String, slug: String, num: Int32, ownerId: Api.Peer?, ownerName: String?, ownerAddress: String?, attributes: [Api.StarGiftAttribute], availabilityIssued: Int32, availabilityTotal: Int32, giftAddress: String?, resellStars: Int64?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars):
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title):
|
||||
if boxed {
|
||||
buffer.appendInt32(46953416)
|
||||
buffer.appendInt32(-970274264)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
|
|
@ -643,14 +643,17 @@ public extension Api {
|
|||
serializeInt64(stars, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityRemains!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(availabilityTotal!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(availabilityResale!, buffer: buffer, boxed: false)}
|
||||
serializeInt64(convertStars, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(firstSaleDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(lastSaleDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(upgradeStars!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellMinStars!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 5) != 0 {serializeString(title!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress):
|
||||
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars):
|
||||
if boxed {
|
||||
buffer.appendInt32(1549979985)
|
||||
buffer.appendInt32(1678891913)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(id, buffer: buffer, boxed: false)
|
||||
|
|
@ -668,16 +671,17 @@ public extension Api {
|
|||
serializeInt32(availabilityIssued, buffer: buffer, boxed: false)
|
||||
serializeInt32(availabilityTotal, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeString(giftAddress!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt64(resellStars!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars):
|
||||
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any)])
|
||||
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress):
|
||||
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any)])
|
||||
case .starGift(let flags, let id, let sticker, let stars, let availabilityRemains, let availabilityTotal, let availabilityResale, let convertStars, let firstSaleDate, let lastSaleDate, let upgradeStars, let resellMinStars, let title):
|
||||
return ("starGift", [("flags", flags as Any), ("id", id as Any), ("sticker", sticker as Any), ("stars", stars as Any), ("availabilityRemains", availabilityRemains as Any), ("availabilityTotal", availabilityTotal as Any), ("availabilityResale", availabilityResale as Any), ("convertStars", convertStars as Any), ("firstSaleDate", firstSaleDate as Any), ("lastSaleDate", lastSaleDate as Any), ("upgradeStars", upgradeStars as Any), ("resellMinStars", resellMinStars as Any), ("title", title as Any)])
|
||||
case .starGiftUnique(let flags, let id, let title, let slug, let num, let ownerId, let ownerName, let ownerAddress, let attributes, let availabilityIssued, let availabilityTotal, let giftAddress, let resellStars):
|
||||
return ("starGiftUnique", [("flags", flags as Any), ("id", id as Any), ("title", title as Any), ("slug", slug as Any), ("num", num as Any), ("ownerId", ownerId as Any), ("ownerName", ownerName as Any), ("ownerAddress", ownerAddress as Any), ("attributes", attributes as Any), ("availabilityIssued", availabilityIssued as Any), ("availabilityTotal", availabilityTotal as Any), ("giftAddress", giftAddress as Any), ("resellStars", resellStars as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -697,25 +701,34 @@ public extension Api {
|
|||
var _6: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() }
|
||||
var _7: Int64?
|
||||
_7 = reader.readInt64()
|
||||
var _8: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_8 = reader.readInt32() }
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_7 = reader.readInt64() }
|
||||
var _8: Int64?
|
||||
_8 = reader.readInt64()
|
||||
var _9: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_9 = reader.readInt32() }
|
||||
var _10: Int64?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_10 = reader.readInt64() }
|
||||
var _10: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_10 = reader.readInt32() }
|
||||
var _11: Int64?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() }
|
||||
var _12: Int64?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_12 = reader.readInt64() }
|
||||
var _13: String?
|
||||
if Int(_1!) & Int(1 << 5) != 0 {_13 = parseString(reader) }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 0) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 1) == 0) || _8 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 4) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = (Int(_1!) & Int(1 << 1) == 0) || _9 != nil
|
||||
let _c10 = (Int(_1!) & Int(1 << 3) == 0) || _10 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
|
||||
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, convertStars: _7!, firstSaleDate: _8, lastSaleDate: _9, upgradeStars: _10)
|
||||
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
|
||||
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _11 != nil
|
||||
let _c12 = (Int(_1!) & Int(1 << 4) == 0) || _12 != nil
|
||||
let _c13 = (Int(_1!) & Int(1 << 5) == 0) || _13 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
|
||||
return Api.StarGift.starGift(flags: _1!, id: _2!, sticker: _3!, stars: _4!, availabilityRemains: _5, availabilityTotal: _6, availabilityResale: _7, convertStars: _8!, firstSaleDate: _9, lastSaleDate: _10, upgradeStars: _11, resellMinStars: _12, title: _13)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -750,6 +763,8 @@ public extension Api {
|
|||
_11 = reader.readInt32()
|
||||
var _12: String?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_12 = parseString(reader) }
|
||||
var _13: Int64?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_13 = reader.readInt64() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
|
|
@ -762,8 +777,9 @@ public extension Api {
|
|||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != nil
|
||||
let _c12 = (Int(_1!) & Int(1 << 3) == 0) || _12 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 {
|
||||
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12)
|
||||
let _c13 = (Int(_1!) & Int(1 << 4) == 0) || _13 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 {
|
||||
return Api.StarGift.starGiftUnique(flags: _1!, id: _2!, title: _3!, slug: _4!, num: _5!, ownerId: _6, ownerName: _7, ownerAddress: _8, attributes: _9!, availabilityIssued: _10!, availabilityTotal: _11!, giftAddress: _12, resellStars: _13)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -774,18 +790,19 @@ public extension Api {
|
|||
}
|
||||
public extension Api {
|
||||
enum StarGiftAttribute: TypeConstructorDescription {
|
||||
case starGiftAttributeBackdrop(name: String, centerColor: Int32, edgeColor: Int32, patternColor: Int32, textColor: Int32, rarityPermille: Int32)
|
||||
case starGiftAttributeBackdrop(name: String, backdropId: Int32, centerColor: Int32, edgeColor: Int32, patternColor: Int32, textColor: Int32, rarityPermille: Int32)
|
||||
case starGiftAttributeModel(name: String, document: Api.Document, rarityPermille: Int32)
|
||||
case starGiftAttributeOriginalDetails(flags: Int32, senderId: Api.Peer?, recipientId: Api.Peer, date: Int32, message: Api.TextWithEntities?)
|
||||
case starGiftAttributePattern(name: String, document: Api.Document, rarityPermille: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starGiftAttributeBackdrop(let name, let centerColor, let edgeColor, let patternColor, let textColor, let rarityPermille):
|
||||
case .starGiftAttributeBackdrop(let name, let backdropId, let centerColor, let edgeColor, let patternColor, let textColor, let rarityPermille):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1809377438)
|
||||
buffer.appendInt32(-650279524)
|
||||
}
|
||||
serializeString(name, buffer: buffer, boxed: false)
|
||||
serializeInt32(backdropId, buffer: buffer, boxed: false)
|
||||
serializeInt32(centerColor, buffer: buffer, boxed: false)
|
||||
serializeInt32(edgeColor, buffer: buffer, boxed: false)
|
||||
serializeInt32(patternColor, buffer: buffer, boxed: false)
|
||||
|
|
@ -823,8 +840,8 @@ public extension Api {
|
|||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starGiftAttributeBackdrop(let name, let centerColor, let edgeColor, let patternColor, let textColor, let rarityPermille):
|
||||
return ("starGiftAttributeBackdrop", [("name", name as Any), ("centerColor", centerColor as Any), ("edgeColor", edgeColor as Any), ("patternColor", patternColor as Any), ("textColor", textColor as Any), ("rarityPermille", rarityPermille as Any)])
|
||||
case .starGiftAttributeBackdrop(let name, let backdropId, let centerColor, let edgeColor, let patternColor, let textColor, let rarityPermille):
|
||||
return ("starGiftAttributeBackdrop", [("name", name as Any), ("backdropId", backdropId as Any), ("centerColor", centerColor as Any), ("edgeColor", edgeColor as Any), ("patternColor", patternColor as Any), ("textColor", textColor as Any), ("rarityPermille", rarityPermille as Any)])
|
||||
case .starGiftAttributeModel(let name, let document, let rarityPermille):
|
||||
return ("starGiftAttributeModel", [("name", name as Any), ("document", document as Any), ("rarityPermille", rarityPermille as Any)])
|
||||
case .starGiftAttributeOriginalDetails(let flags, let senderId, let recipientId, let date, let message):
|
||||
|
|
@ -847,14 +864,17 @@ public extension Api {
|
|||
_5 = reader.readInt32()
|
||||
var _6: Int32?
|
||||
_6 = reader.readInt32()
|
||||
var _7: Int32?
|
||||
_7 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.StarGiftAttribute.starGiftAttributeBackdrop(name: _1!, centerColor: _2!, edgeColor: _3!, patternColor: _4!, textColor: _5!, rarityPermille: _6!)
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.StarGiftAttribute.starGiftAttributeBackdrop(name: _1!, backdropId: _2!, centerColor: _3!, edgeColor: _4!, patternColor: _5!, textColor: _6!, rarityPermille: _7!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -931,55 +951,39 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StarRefProgram: TypeConstructorDescription {
|
||||
case starRefProgram(flags: Int32, botId: Int64, commissionPermille: Int32, durationMonths: Int32?, endDate: Int32?, dailyRevenuePerUser: Api.StarsAmount?)
|
||||
enum StarGiftAttributeCounter: TypeConstructorDescription {
|
||||
case starGiftAttributeCounter(attribute: Api.StarGiftAttributeId, count: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starRefProgram(let flags, let botId, let commissionPermille, let durationMonths, let endDate, let dailyRevenuePerUser):
|
||||
case .starGiftAttributeCounter(let attribute, let count):
|
||||
if boxed {
|
||||
buffer.appendInt32(-586389774)
|
||||
buffer.appendInt32(783398488)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(botId, buffer: buffer, boxed: false)
|
||||
serializeInt32(commissionPermille, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(durationMonths!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(endDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {dailyRevenuePerUser!.serialize(buffer, true)}
|
||||
attribute.serialize(buffer, true)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starRefProgram(let flags, let botId, let commissionPermille, let durationMonths, let endDate, let dailyRevenuePerUser):
|
||||
return ("starRefProgram", [("flags", flags as Any), ("botId", botId as Any), ("commissionPermille", commissionPermille as Any), ("durationMonths", durationMonths as Any), ("endDate", endDate as Any), ("dailyRevenuePerUser", dailyRevenuePerUser as Any)])
|
||||
case .starGiftAttributeCounter(let attribute, let count):
|
||||
return ("starGiftAttributeCounter", [("attribute", attribute as Any), ("count", count as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starRefProgram(_ reader: BufferReader) -> StarRefProgram? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||
var _6: Api.StarsAmount?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.StarsAmount
|
||||
} }
|
||||
public static func parse_starGiftAttributeCounter(_ reader: BufferReader) -> StarGiftAttributeCounter? {
|
||||
var _1: Api.StarGiftAttributeId?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StarGiftAttributeId
|
||||
}
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.StarRefProgram.starRefProgram(flags: _1!, botId: _2!, commissionPermille: _3!, durationMonths: _4, endDate: _5, dailyRevenuePerUser: _6)
|
||||
if _c1 && _c2 {
|
||||
return Api.StarGiftAttributeCounter.starGiftAttributeCounter(attribute: _1!, count: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
@ -989,37 +993,73 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsAmount: TypeConstructorDescription {
|
||||
case starsAmount(amount: Int64, nanos: Int32)
|
||||
enum StarGiftAttributeId: TypeConstructorDescription {
|
||||
case starGiftAttributeIdBackdrop(backdropId: Int32)
|
||||
case starGiftAttributeIdModel(documentId: Int64)
|
||||
case starGiftAttributeIdPattern(documentId: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsAmount(let amount, let nanos):
|
||||
case .starGiftAttributeIdBackdrop(let backdropId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1145654109)
|
||||
buffer.appendInt32(520210263)
|
||||
}
|
||||
serializeInt64(amount, buffer: buffer, boxed: false)
|
||||
serializeInt32(nanos, buffer: buffer, boxed: false)
|
||||
serializeInt32(backdropId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .starGiftAttributeIdModel(let documentId):
|
||||
if boxed {
|
||||
buffer.appendInt32(1219145276)
|
||||
}
|
||||
serializeInt64(documentId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .starGiftAttributeIdPattern(let documentId):
|
||||
if boxed {
|
||||
buffer.appendInt32(1242965043)
|
||||
}
|
||||
serializeInt64(documentId, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsAmount(let amount, let nanos):
|
||||
return ("starsAmount", [("amount", amount as Any), ("nanos", nanos as Any)])
|
||||
case .starGiftAttributeIdBackdrop(let backdropId):
|
||||
return ("starGiftAttributeIdBackdrop", [("backdropId", backdropId as Any)])
|
||||
case .starGiftAttributeIdModel(let documentId):
|
||||
return ("starGiftAttributeIdModel", [("documentId", documentId as Any)])
|
||||
case .starGiftAttributeIdPattern(let documentId):
|
||||
return ("starGiftAttributeIdPattern", [("documentId", documentId as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
|
||||
public static func parse_starGiftAttributeIdBackdrop(_ reader: BufferReader) -> StarGiftAttributeId? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StarGiftAttributeId.starGiftAttributeIdBackdrop(backdropId: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_starGiftAttributeIdModel(_ reader: BufferReader) -> StarGiftAttributeId? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StarsAmount.starsAmount(amount: _1!, nanos: _2!)
|
||||
if _c1 {
|
||||
return Api.StarGiftAttributeId.starGiftAttributeIdModel(documentId: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_starGiftAttributeIdPattern(_ reader: BufferReader) -> StarGiftAttributeId? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StarGiftAttributeId.starGiftAttributeIdPattern(documentId: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,3 +1,101 @@
|
|||
public extension Api {
|
||||
enum StarRefProgram: TypeConstructorDescription {
|
||||
case starRefProgram(flags: Int32, botId: Int64, commissionPermille: Int32, durationMonths: Int32?, endDate: Int32?, dailyRevenuePerUser: Api.StarsAmount?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starRefProgram(let flags, let botId, let commissionPermille, let durationMonths, let endDate, let dailyRevenuePerUser):
|
||||
if boxed {
|
||||
buffer.appendInt32(-586389774)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt64(botId, buffer: buffer, boxed: false)
|
||||
serializeInt32(commissionPermille, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(durationMonths!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(endDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {dailyRevenuePerUser!.serialize(buffer, true)}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starRefProgram(let flags, let botId, let commissionPermille, let durationMonths, let endDate, let dailyRevenuePerUser):
|
||||
return ("starRefProgram", [("flags", flags as Any), ("botId", botId as Any), ("commissionPermille", commissionPermille as Any), ("durationMonths", durationMonths as Any), ("endDate", endDate as Any), ("dailyRevenuePerUser", dailyRevenuePerUser as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starRefProgram(_ reader: BufferReader) -> StarRefProgram? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int64?
|
||||
_2 = reader.readInt64()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||
var _6: Api.StarsAmount?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.StarsAmount
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.StarRefProgram.starRefProgram(flags: _1!, botId: _2!, commissionPermille: _3!, durationMonths: _4, endDate: _5, dailyRevenuePerUser: _6)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsAmount: TypeConstructorDescription {
|
||||
case starsAmount(amount: Int64, nanos: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsAmount(let amount, let nanos):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1145654109)
|
||||
}
|
||||
serializeInt64(amount, buffer: buffer, boxed: false)
|
||||
serializeInt32(nanos, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsAmount(let amount, let nanos):
|
||||
return ("starsAmount", [("amount", amount as Any), ("nanos", nanos as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StarsAmount.starsAmount(amount: _1!, nanos: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsGiftOption: TypeConstructorDescription {
|
||||
case starsGiftOption(flags: Int32, stars: Int64, storeProduct: String?, currency: String, amount: Int64)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,101 @@
|
|||
public extension Api.payments {
|
||||
enum ResaleStarGifts: TypeConstructorDescription {
|
||||
case resaleStarGifts(flags: Int32, count: Int32, gifts: [Api.StarGift], nextOffset: String?, attributes: [Api.StarGiftAttribute]?, attributesHash: Int64?, chats: [Api.Chat], counters: [Api.StarGiftAttributeCounter]?, users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .resaleStarGifts(let flags, let count, let gifts, let nextOffset, let attributes, let attributesHash, let chats, let counters, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1803939105)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(gifts.count))
|
||||
for item in gifts {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(nextOffset!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(attributes!.count))
|
||||
for item in attributes! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt64(attributesHash!, buffer: buffer, boxed: false)}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(chats.count))
|
||||
for item in chats {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
if Int(flags) & Int(1 << 2) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(counters!.count))
|
||||
for item in counters! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(users.count))
|
||||
for item in users {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .resaleStarGifts(let flags, let count, let gifts, let nextOffset, let attributes, let attributesHash, let chats, let counters, let users):
|
||||
return ("resaleStarGifts", [("flags", flags as Any), ("count", count as Any), ("gifts", gifts as Any), ("nextOffset", nextOffset as Any), ("attributes", attributes as Any), ("attributesHash", attributesHash as Any), ("chats", chats as Any), ("counters", counters as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_resaleStarGifts(_ reader: BufferReader) -> ResaleStarGifts? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: [Api.StarGift]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGift.self)
|
||||
}
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
|
||||
var _5: [Api.StarGiftAttribute]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttribute.self)
|
||||
} }
|
||||
var _6: Int64?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt64() }
|
||||
var _7: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _8: [Api.StarGiftAttributeCounter]?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StarGiftAttributeCounter.self)
|
||||
} }
|
||||
var _9: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = (Int(_1!) & Int(1 << 2) == 0) || _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.payments.ResaleStarGifts.resaleStarGifts(flags: _1!, count: _2!, gifts: _3!, nextOffset: _4, attributes: _5, attributesHash: _6, chats: _7!, counters: _8, users: _9!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum SavedInfo: TypeConstructorDescription {
|
||||
case savedInfo(flags: Int32, savedInfo: Api.PaymentRequestedInfo?)
|
||||
|
|
@ -1598,171 +1696,3 @@ public extension Api.stats {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.stats {
|
||||
enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions):
|
||||
if boxed {
|
||||
buffer.appendInt32(963421692)
|
||||
}
|
||||
period.serialize(buffer, true)
|
||||
followers.serialize(buffer, true)
|
||||
viewsPerPost.serialize(buffer, true)
|
||||
sharesPerPost.serialize(buffer, true)
|
||||
reactionsPerPost.serialize(buffer, true)
|
||||
viewsPerStory.serialize(buffer, true)
|
||||
sharesPerStory.serialize(buffer, true)
|
||||
reactionsPerStory.serialize(buffer, true)
|
||||
enabledNotifications.serialize(buffer, true)
|
||||
growthGraph.serialize(buffer, true)
|
||||
followersGraph.serialize(buffer, true)
|
||||
muteGraph.serialize(buffer, true)
|
||||
topHoursGraph.serialize(buffer, true)
|
||||
interactionsGraph.serialize(buffer, true)
|
||||
ivInteractionsGraph.serialize(buffer, true)
|
||||
viewsBySourceGraph.serialize(buffer, true)
|
||||
newFollowersBySourceGraph.serialize(buffer, true)
|
||||
languagesGraph.serialize(buffer, true)
|
||||
reactionsByEmotionGraph.serialize(buffer, true)
|
||||
storyInteractionsGraph.serialize(buffer, true)
|
||||
storyReactionsByEmotionGraph.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(recentPostsInteractions.count))
|
||||
for item in recentPostsInteractions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions):
|
||||
return ("broadcastStats", [("period", period as Any), ("followers", followers as Any), ("viewsPerPost", viewsPerPost as Any), ("sharesPerPost", sharesPerPost as Any), ("reactionsPerPost", reactionsPerPost as Any), ("viewsPerStory", viewsPerStory as Any), ("sharesPerStory", sharesPerStory as Any), ("reactionsPerStory", reactionsPerStory as Any), ("enabledNotifications", enabledNotifications as Any), ("growthGraph", growthGraph as Any), ("followersGraph", followersGraph as Any), ("muteGraph", muteGraph as Any), ("topHoursGraph", topHoursGraph as Any), ("interactionsGraph", interactionsGraph as Any), ("ivInteractionsGraph", ivInteractionsGraph as Any), ("viewsBySourceGraph", viewsBySourceGraph as Any), ("newFollowersBySourceGraph", newFollowersBySourceGraph as Any), ("languagesGraph", languagesGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any), ("storyInteractionsGraph", storyInteractionsGraph as Any), ("storyReactionsByEmotionGraph", storyReactionsByEmotionGraph as Any), ("recentPostsInteractions", recentPostsInteractions as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? {
|
||||
var _1: Api.StatsDateRangeDays?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays
|
||||
}
|
||||
var _2: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _3: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _4: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _5: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _6: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _7: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _8: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _9: Api.StatsPercentValue?
|
||||
if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue
|
||||
}
|
||||
var _10: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_10 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _11: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_11 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _12: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _13: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_13 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _14: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_14 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _15: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_15 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _16: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_16 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _17: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_17 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _18: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_18 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _19: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_19 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _20: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_20 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _21: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_21 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _22: [Api.PostInteractionCounters]?
|
||||
if let _ = reader.readInt32() {
|
||||
_22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PostInteractionCounters.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != nil
|
||||
let _c12 = _12 != nil
|
||||
let _c13 = _13 != nil
|
||||
let _c14 = _14 != nil
|
||||
let _c15 = _15 != nil
|
||||
let _c16 = _16 != nil
|
||||
let _c17 = _17 != nil
|
||||
let _c18 = _18 != nil
|
||||
let _c19 = _19 != nil
|
||||
let _c20 = _20 != nil
|
||||
let _c21 = _21 != nil
|
||||
let _c22 = _22 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 {
|
||||
return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,171 @@
|
|||
public extension Api.stats {
|
||||
enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: Api.StatsAbsValueAndPrev, reactionsPerPost: Api.StatsAbsValueAndPrev, viewsPerStory: Api.StatsAbsValueAndPrev, sharesPerStory: Api.StatsAbsValueAndPrev, reactionsPerStory: Api.StatsAbsValueAndPrev, enabledNotifications: Api.StatsPercentValue, growthGraph: Api.StatsGraph, followersGraph: Api.StatsGraph, muteGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, interactionsGraph: Api.StatsGraph, ivInteractionsGraph: Api.StatsGraph, viewsBySourceGraph: Api.StatsGraph, newFollowersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, reactionsByEmotionGraph: Api.StatsGraph, storyInteractionsGraph: Api.StatsGraph, storyReactionsByEmotionGraph: Api.StatsGraph, recentPostsInteractions: [Api.PostInteractionCounters])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions):
|
||||
if boxed {
|
||||
buffer.appendInt32(963421692)
|
||||
}
|
||||
period.serialize(buffer, true)
|
||||
followers.serialize(buffer, true)
|
||||
viewsPerPost.serialize(buffer, true)
|
||||
sharesPerPost.serialize(buffer, true)
|
||||
reactionsPerPost.serialize(buffer, true)
|
||||
viewsPerStory.serialize(buffer, true)
|
||||
sharesPerStory.serialize(buffer, true)
|
||||
reactionsPerStory.serialize(buffer, true)
|
||||
enabledNotifications.serialize(buffer, true)
|
||||
growthGraph.serialize(buffer, true)
|
||||
followersGraph.serialize(buffer, true)
|
||||
muteGraph.serialize(buffer, true)
|
||||
topHoursGraph.serialize(buffer, true)
|
||||
interactionsGraph.serialize(buffer, true)
|
||||
ivInteractionsGraph.serialize(buffer, true)
|
||||
viewsBySourceGraph.serialize(buffer, true)
|
||||
newFollowersBySourceGraph.serialize(buffer, true)
|
||||
languagesGraph.serialize(buffer, true)
|
||||
reactionsByEmotionGraph.serialize(buffer, true)
|
||||
storyInteractionsGraph.serialize(buffer, true)
|
||||
storyReactionsByEmotionGraph.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(recentPostsInteractions.count))
|
||||
for item in recentPostsInteractions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let reactionsPerPost, let viewsPerStory, let sharesPerStory, let reactionsPerStory, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let reactionsByEmotionGraph, let storyInteractionsGraph, let storyReactionsByEmotionGraph, let recentPostsInteractions):
|
||||
return ("broadcastStats", [("period", period as Any), ("followers", followers as Any), ("viewsPerPost", viewsPerPost as Any), ("sharesPerPost", sharesPerPost as Any), ("reactionsPerPost", reactionsPerPost as Any), ("viewsPerStory", viewsPerStory as Any), ("sharesPerStory", sharesPerStory as Any), ("reactionsPerStory", reactionsPerStory as Any), ("enabledNotifications", enabledNotifications as Any), ("growthGraph", growthGraph as Any), ("followersGraph", followersGraph as Any), ("muteGraph", muteGraph as Any), ("topHoursGraph", topHoursGraph as Any), ("interactionsGraph", interactionsGraph as Any), ("ivInteractionsGraph", ivInteractionsGraph as Any), ("viewsBySourceGraph", viewsBySourceGraph as Any), ("newFollowersBySourceGraph", newFollowersBySourceGraph as Any), ("languagesGraph", languagesGraph as Any), ("reactionsByEmotionGraph", reactionsByEmotionGraph as Any), ("storyInteractionsGraph", storyInteractionsGraph as Any), ("storyReactionsByEmotionGraph", storyReactionsByEmotionGraph as Any), ("recentPostsInteractions", recentPostsInteractions as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_broadcastStats(_ reader: BufferReader) -> BroadcastStats? {
|
||||
var _1: Api.StatsDateRangeDays?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StatsDateRangeDays
|
||||
}
|
||||
var _2: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _3: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _4: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _5: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _6: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _7: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _8: Api.StatsAbsValueAndPrev?
|
||||
if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.StatsAbsValueAndPrev
|
||||
}
|
||||
var _9: Api.StatsPercentValue?
|
||||
if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue
|
||||
}
|
||||
var _10: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_10 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _11: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_11 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _12: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_12 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _13: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_13 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _14: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_14 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _15: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_15 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _16: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_16 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _17: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_17 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _18: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_18 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _19: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_19 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _20: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_20 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _21: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_21 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _22: [Api.PostInteractionCounters]?
|
||||
if let _ = reader.readInt32() {
|
||||
_22 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PostInteractionCounters.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
let _c9 = _9 != nil
|
||||
let _c10 = _10 != nil
|
||||
let _c11 = _11 != nil
|
||||
let _c12 = _12 != nil
|
||||
let _c13 = _13 != nil
|
||||
let _c14 = _14 != nil
|
||||
let _c15 = _15 != nil
|
||||
let _c16 = _16 != nil
|
||||
let _c17 = _17 != nil
|
||||
let _c18 = _18 != nil
|
||||
let _c19 = _19 != nil
|
||||
let _c20 = _20 != nil
|
||||
let _c21 = _21 != nil
|
||||
let _c22 = _22 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 {
|
||||
return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, reactionsPerPost: _5!, viewsPerStory: _6!, sharesPerStory: _7!, reactionsPerStory: _8!, enabledNotifications: _9!, growthGraph: _10!, followersGraph: _11!, muteGraph: _12!, topHoursGraph: _13!, interactionsGraph: _14!, ivInteractionsGraph: _15!, viewsBySourceGraph: _16!, newFollowersBySourceGraph: _17!, languagesGraph: _18!, reactionsByEmotionGraph: _19!, storyInteractionsGraph: _20!, storyReactionsByEmotionGraph: _21!, recentPostsInteractions: _22!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.stats {
|
||||
enum MegagroupStats: TypeConstructorDescription {
|
||||
case megagroupStats(period: Api.StatsDateRangeDays, members: Api.StatsAbsValueAndPrev, messages: Api.StatsAbsValueAndPrev, viewers: Api.StatsAbsValueAndPrev, posters: Api.StatsAbsValueAndPrev, growthGraph: Api.StatsGraph, membersGraph: Api.StatsGraph, newMembersBySourceGraph: Api.StatsGraph, languagesGraph: Api.StatsGraph, messagesGraph: Api.StatsGraph, actionsGraph: Api.StatsGraph, topHoursGraph: Api.StatsGraph, weekdaysGraph: Api.StatsGraph, topPosters: [Api.StatsGroupTopPoster], topAdmins: [Api.StatsGroupTopAdmin], topInviters: [Api.StatsGroupTopInviter], users: [Api.User])
|
||||
|
|
@ -1444,59 +1612,3 @@ public extension Api.updates {
|
|||
|
||||
}
|
||||
}
|
||||
public extension Api.upload {
|
||||
enum CdnFile: TypeConstructorDescription {
|
||||
case cdnFile(bytes: Buffer)
|
||||
case cdnFileReuploadNeeded(requestToken: Buffer)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .cdnFile(let bytes):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1449145777)
|
||||
}
|
||||
serializeBytes(bytes, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .cdnFileReuploadNeeded(let requestToken):
|
||||
if boxed {
|
||||
buffer.appendInt32(-290921362)
|
||||
}
|
||||
serializeBytes(requestToken, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .cdnFile(let bytes):
|
||||
return ("cdnFile", [("bytes", bytes as Any)])
|
||||
case .cdnFileReuploadNeeded(let requestToken):
|
||||
return ("cdnFileReuploadNeeded", [("requestToken", requestToken as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? {
|
||||
var _1: Buffer?
|
||||
_1 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.upload.CdnFile.cdnFile(bytes: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? {
|
||||
var _1: Buffer?
|
||||
_1 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,59 @@
|
|||
public extension Api.upload {
|
||||
enum CdnFile: TypeConstructorDescription {
|
||||
case cdnFile(bytes: Buffer)
|
||||
case cdnFileReuploadNeeded(requestToken: Buffer)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .cdnFile(let bytes):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1449145777)
|
||||
}
|
||||
serializeBytes(bytes, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .cdnFileReuploadNeeded(let requestToken):
|
||||
if boxed {
|
||||
buffer.appendInt32(-290921362)
|
||||
}
|
||||
serializeBytes(requestToken, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .cdnFile(let bytes):
|
||||
return ("cdnFile", [("bytes", bytes as Any)])
|
||||
case .cdnFileReuploadNeeded(let requestToken):
|
||||
return ("cdnFileReuploadNeeded", [("requestToken", requestToken as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_cdnFile(_ reader: BufferReader) -> CdnFile? {
|
||||
var _1: Buffer?
|
||||
_1 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.upload.CdnFile.cdnFile(bytes: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_cdnFileReuploadNeeded(_ reader: BufferReader) -> CdnFile? {
|
||||
var _1: Buffer?
|
||||
_1 = parseBytes(reader)
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.upload.CdnFile.cdnFileReuploadNeeded(requestToken: _1!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.upload {
|
||||
enum File: TypeConstructorDescription {
|
||||
case file(type: Api.storage.FileType, mtime: Int32, bytes: Buffer)
|
||||
|
|
|
|||
|
|
@ -9332,6 +9332,30 @@ public extension Api.functions.payments {
|
|||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func getResaleStarGifts(flags: Int32, attributesHash: Int64?, giftId: Int64, attributes: [Api.StarGiftAttributeId]?, offset: String, limit: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.ResaleStarGifts>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(2053087798)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(attributesHash!, buffer: buffer, boxed: false)}
|
||||
serializeInt64(giftId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(attributes!.count))
|
||||
for item in attributes! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
serializeString(offset, buffer: buffer, boxed: false)
|
||||
serializeInt32(limit, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "payments.getResaleStarGifts", parameters: [("flags", String(describing: flags)), ("attributesHash", String(describing: attributesHash)), ("giftId", String(describing: giftId)), ("attributes", String(describing: attributes)), ("offset", String(describing: offset)), ("limit", String(describing: limit))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.ResaleStarGifts? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.payments.ResaleStarGifts?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.payments.ResaleStarGifts
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func getSavedInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.SavedInfo>) {
|
||||
let buffer = Buffer()
|
||||
|
|
@ -9766,6 +9790,22 @@ public extension Api.functions.payments {
|
|||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func updateStarGiftPrice(slug: String, resellStars: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-489360582)
|
||||
serializeString(slug, buffer: buffer, boxed: false)
|
||||
serializeInt64(resellStars, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "payments.updateStarGiftPrice", parameters: [("slug", String(describing: slug)), ("resellStars", String(describing: resellStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func upgradeStarGift(flags: Int32, stargift: Api.InputSavedStarGift) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
|
|
|
|||
|
|
@ -192,11 +192,11 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGift(gift: gift, convertStars: convertStars, text: text, entities: entities, nameHidden: (flags & (1 << 0)) != 0, savedToProfile: (flags & (1 << 2)) != 0, converted: (flags & (1 << 3)) != 0, upgraded: (flags & (1 << 5)) != 0, canUpgrade: (flags & (1 << 10)) != 0, upgradeStars: upgradeStars, isRefunded: (flags & (1 << 9)) != 0, upgradeMessageId: upgradeMessageId, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId))
|
||||
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId):
|
||||
case let .messageActionStarGiftUnique(flags, apiGift, canExportAt, transferStars, fromId, peer, savedId, resaleStars):
|
||||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId))
|
||||
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleStars: resaleStars))
|
||||
case let .messageActionPaidMessagesRefunded(count, stars):
|
||||
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
|
||||
case let .messageActionPaidMessagesPrice(stars):
|
||||
|
|
|
|||
|
|
@ -109,6 +109,10 @@ func messageFilterForTagMask(_ tagMask: MessageTags) -> Api.MessagesFilter? {
|
|||
return Api.MessagesFilter.inputMessagesFilterGif
|
||||
} else if tagMask == .pinned {
|
||||
return Api.MessagesFilter.inputMessagesFilterPinned
|
||||
} else if tagMask == .voice {
|
||||
return Api.MessagesFilter.inputMessagesFilterVoice
|
||||
} else if tagMask == .roundVideo {
|
||||
return Api.MessagesFilter.inputMessagesFilterRoundVideo
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,10 +350,11 @@ func managedUniqueStarGifts(accountPeerId: PeerId, postbox: Postbox, network: Ne
|
|||
attributes: [
|
||||
.model(name: "", file: file, rarity: 0),
|
||||
.pattern(name: "", file: patternFile, rarity: 0),
|
||||
.backdrop(name: "", innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor, rarity: 0)
|
||||
.backdrop(name: "", id: 0, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor, rarity: 0)
|
||||
],
|
||||
availability: StarGift.UniqueGift.Availability(issued: 0, total: 0),
|
||||
giftAddress: nil
|
||||
giftAddress: nil,
|
||||
resellStars: nil
|
||||
)
|
||||
if let entry = CodableEntry(RecentStarGiftItem(gift)) {
|
||||
items.append(OrderedItemListEntry(id: RecentStarGiftItemId(id).rawValue, contents: entry))
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
|
|||
|
||||
public class Serialization: NSObject, MTSerialization {
|
||||
public func currentLayer() -> UInt {
|
||||
return 202
|
||||
return 203
|
||||
}
|
||||
|
||||
public func parseMessage(_ data: Data!) -> Any! {
|
||||
|
|
|
|||
|
|
@ -170,8 +170,10 @@ public extension MessageTags {
|
|||
static let video = MessageTags(rawValue: 1 << 9)
|
||||
static let pinned = MessageTags(rawValue: 1 << 10)
|
||||
static let unseenReaction = MessageTags(rawValue: 1 << 11)
|
||||
static let voice = MessageTags(rawValue: 1 << 12)
|
||||
static let roundVideo = MessageTags(rawValue: 1 << 13)
|
||||
|
||||
static let all: MessageTags = [.photoOrVideo, .file, .music, .webPage, .voiceOrInstantVideo, .unseenPersonalMessage, .liveLocation, .gif, .photo, .video, .pinned, .unseenReaction]
|
||||
static let all: MessageTags = [.photoOrVideo, .file, .music, .webPage, .voiceOrInstantVideo, .unseenPersonalMessage, .liveLocation, .gif, .photo, .video, .pinned, .unseenReaction, .voice, .roundVideo]
|
||||
}
|
||||
|
||||
public extension GlobalMessageTags {
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleStars: Int64?)
|
||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64)
|
||||
case conferenceCall(ConferenceCall)
|
||||
|
|
@ -283,7 +283,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case 44:
|
||||
self = .starGift(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, convertStars: decoder.decodeOptionalInt64ForKey("convertStars"), text: decoder.decodeOptionalStringForKey("text"), entities: decoder.decodeOptionalObjectArrayWithDecoderForKey("entities"), nameHidden: decoder.decodeBoolForKey("nameHidden", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), converted: decoder.decodeBoolForKey("converted", orElse: false), upgraded: decoder.decodeBoolForKey("upgraded", orElse: false), canUpgrade: decoder.decodeBoolForKey("canUpgrade", orElse: false), upgradeStars: decoder.decodeOptionalInt64ForKey("upgradeStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), upgradeMessageId: decoder.decodeOptionalInt32ForKey("upgradeMessageId"), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
|
||||
case 45:
|
||||
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"))
|
||||
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleStars: decoder.decodeOptionalInt64ForKey("resaleStars"))
|
||||
case 46:
|
||||
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
|
||||
case 47:
|
||||
|
|
@ -633,7 +633,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "savedId")
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, peerId, senderId, savedId):
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, peerId, senderId, savedId, resaleStars):
|
||||
encoder.encodeInt32(45, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
encoder.encodeBool(isUpgrade, forKey: "isUpgrade")
|
||||
|
|
@ -665,6 +665,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "savedId")
|
||||
}
|
||||
if let resaleStars {
|
||||
encoder.encodeInt64(resaleStars, forKey: "resaleStars")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "resaleStars")
|
||||
}
|
||||
case let .paidMessagesRefunded(count, stars):
|
||||
encoder.encodeInt32(46, forKey: "_rawValue")
|
||||
encoder.encodeInt32(count, forKey: "count")
|
||||
|
|
@ -718,7 +723,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
peerIds.append(senderId)
|
||||
}
|
||||
return peerIds
|
||||
case let .starGiftUnique(_, _, _, _, _, _, _, peerId, senderId, _):
|
||||
case let .starGiftUnique(_, _, _, _, _, _, _, peerId, senderId, _, _):
|
||||
var peerIds: [PeerId] = []
|
||||
if let peerId {
|
||||
peerIds.append(peerId)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ public extension TelegramEngine {
|
|||
file = fileValue
|
||||
case let .pattern(_, patternFileValue, _):
|
||||
patternFile = patternFileValue
|
||||
case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||
innerColor = innerColorValue
|
||||
outerColor = outerColorValue
|
||||
patternColor = patternColorValue
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public enum BotPaymentInvoiceSource {
|
|||
case starGiftUpgrade(keepOriginalInfo: Bool, reference: StarGiftReference)
|
||||
case starGiftTransfer(reference: StarGiftReference, toPeerId: EnginePeer.Id)
|
||||
case premiumGift(peerId: EnginePeer.Id, option: CachedPremiumGiftOption, text: String?, entities: [MessageTextEntity]?)
|
||||
case starGiftResale(slug: String, toPeerId: EnginePeer.Id)
|
||||
}
|
||||
|
||||
public struct BotPaymentInvoiceFields: OptionSet {
|
||||
|
|
@ -400,6 +401,11 @@ func _internal_parseInputInvoice(transaction: Transaction, source: BotPaymentInv
|
|||
message = .textWithEntities(text: text, entities: entities.flatMap { apiEntitiesFromMessageTextEntities($0, associatedPeers: SimpleDictionary()) } ?? [])
|
||||
}
|
||||
return .inputInvoicePremiumGiftStars(flags: flags, userId: inputUser, months: option.months, message: message)
|
||||
case let .starGiftResale(slug, toPeerId):
|
||||
guard let peer = transaction.getPeer(toPeerId), let inputPeer = apiInputPeer(peer) else {
|
||||
return nil
|
||||
}
|
||||
return .inputInvoiceStarGiftResale(slug: slug, toId: inputPeer)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -733,7 +739,7 @@ func _internal_sendBotPaymentForm(account: Account, formId: Int64, source: BotPa
|
|||
receiptMessageId = id
|
||||
}
|
||||
}
|
||||
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift:
|
||||
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale:
|
||||
receiptMessageId = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case title
|
||||
case file
|
||||
case price
|
||||
case convertStars
|
||||
|
|
@ -59,24 +60,54 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
enum CodingKeys: String, CodingKey {
|
||||
case remains
|
||||
case total
|
||||
case resale
|
||||
case minResaleStars
|
||||
}
|
||||
|
||||
public let remains: Int32
|
||||
public let total: Int32
|
||||
public let resale: Int64
|
||||
public let minResaleStars: Int64?
|
||||
|
||||
public init(remains: Int32, total: Int32) {
|
||||
public init(remains: Int32, total: Int32, resale: Int64, minResaleStars: Int64?) {
|
||||
self.remains = remains
|
||||
self.total = total
|
||||
self.resale = resale
|
||||
self.minResaleStars = minResaleStars
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.remains = try container.decode(Int32.self, forKey: .remains)
|
||||
self.total = try container.decode(Int32.self, forKey: .total)
|
||||
self.resale = (try? container.decodeIfPresent(Int64.self, forKey: .resale)) ?? 0
|
||||
self.minResaleStars = try? container.decodeIfPresent(Int64.self, forKey: .minResaleStars)
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.remains = decoder.decodeInt32ForKey(CodingKeys.remains.rawValue, orElse: 0)
|
||||
self.total = decoder.decodeInt32ForKey(CodingKeys.total.rawValue, orElse: 0)
|
||||
self.resale = decoder.decodeInt64ForKey(CodingKeys.resale.rawValue, orElse: 0)
|
||||
self.minResaleStars = decoder.decodeInt64ForKey(CodingKeys.minResaleStars.rawValue, orElse: 0)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.remains, forKey: .remains)
|
||||
try container.encode(self.total, forKey: .total)
|
||||
try container.encode(self.resale, forKey: .resale)
|
||||
try container.encodeIfPresent(self.minResaleStars, forKey: .minResaleStars)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.remains, forKey: CodingKeys.remains.rawValue)
|
||||
encoder.encodeInt32(self.total, forKey: CodingKeys.total.rawValue)
|
||||
encoder.encodeInt64(self.resale, forKey: CodingKeys.resale.rawValue)
|
||||
if let minResaleStars = self.minResaleStars {
|
||||
encoder.encodeInt64(minResaleStars, forKey: CodingKeys.minResaleStars.rawValue)
|
||||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.minResaleStars.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,6 +141,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
}
|
||||
|
||||
public let id: Int64
|
||||
public let title: String?
|
||||
public let file: TelegramMediaFile
|
||||
public let price: Int64
|
||||
public let convertStars: Int64
|
||||
|
|
@ -118,8 +150,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public let flags: Flags
|
||||
public let upgradeStars: Int64?
|
||||
|
||||
public init(id: Int64, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?) {
|
||||
public init(id: Int64, title: String?, file: TelegramMediaFile, price: Int64, convertStars: Int64, availability: Availability?, soldOut: SoldOut?, flags: Flags, upgradeStars: Int64?) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.file = file
|
||||
self.price = price
|
||||
self.convertStars = convertStars
|
||||
|
|
@ -132,6 +165,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.id = try container.decode(Int64.self, forKey: .id)
|
||||
self.title = try container.decodeIfPresent(String.self, forKey: .title)
|
||||
|
||||
if let fileData = try container.decodeIfPresent(Data.self, forKey: .file), let file = PostboxDecoder(buffer: MemoryBuffer(data: fileData)).decodeRootObject() as? TelegramMediaFile {
|
||||
self.file = file
|
||||
|
|
@ -149,6 +183,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = decoder.decodeInt64ForKey(CodingKeys.id.rawValue, orElse: 0)
|
||||
self.title = decoder.decodeOptionalStringForKey(CodingKeys.title.rawValue)
|
||||
self.file = decoder.decodeObjectForKey(CodingKeys.file.rawValue) as! TelegramMediaFile
|
||||
self.price = decoder.decodeInt64ForKey(CodingKeys.price.rawValue, orElse: 0)
|
||||
self.convertStars = decoder.decodeInt64ForKey(CodingKeys.convertStars.rawValue, orElse: 0)
|
||||
|
|
@ -161,6 +196,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(self.id, forKey: .id)
|
||||
try container.encodeIfPresent(self.title, forKey: .title)
|
||||
|
||||
let encoder = PostboxEncoder()
|
||||
encoder.encodeRootObject(self.file)
|
||||
|
|
@ -177,6 +213,11 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.id, forKey: CodingKeys.id.rawValue)
|
||||
if let title = self.title {
|
||||
encoder.encodeString(title, forKey: CodingKeys.title.rawValue)
|
||||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.title.rawValue)
|
||||
}
|
||||
encoder.encodeObject(self.file, forKey: CodingKeys.file.rawValue)
|
||||
encoder.encodeInt64(self.price, forKey: CodingKeys.price.rawValue)
|
||||
encoder.encodeInt64(self.convertStars, forKey: CodingKeys.convertStars.rawValue)
|
||||
|
|
@ -211,6 +252,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case attributes
|
||||
case availability
|
||||
case giftAddress
|
||||
case resellStars
|
||||
}
|
||||
|
||||
public enum Attribute: Equatable, Codable, PostboxCoding {
|
||||
|
|
@ -218,6 +260,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case type
|
||||
case name
|
||||
case file
|
||||
case id
|
||||
case innerColor
|
||||
case outerColor
|
||||
case patternColor
|
||||
|
|
@ -239,7 +282,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
|
||||
case model(name: String, file: TelegramMediaFile, rarity: Int32)
|
||||
case pattern(name: String, file: TelegramMediaFile, rarity: Int32)
|
||||
case backdrop(name: String, innerColor: Int32, outerColor: Int32, patternColor: Int32, textColor: Int32, rarity: Int32)
|
||||
case backdrop(name: String, id: Int32, innerColor: Int32, outerColor: Int32, patternColor: Int32, textColor: Int32, rarity: Int32)
|
||||
case originalInfo(senderPeerId: EnginePeer.Id?, recipientPeerId: EnginePeer.Id, date: Int32, text: String?, entities: [MessageTextEntity]?)
|
||||
|
||||
public var attributeType: AttributeType {
|
||||
|
|
@ -275,6 +318,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case 2:
|
||||
self = .backdrop(
|
||||
name: try container.decode(String.self, forKey: .name),
|
||||
id: try container.decodeIfPresent(Int32.self, forKey: .id) ?? 0,
|
||||
innerColor: try container.decode(Int32.self, forKey: .innerColor),
|
||||
outerColor: try container.decode(Int32.self, forKey: .outerColor),
|
||||
patternColor: try container.decode(Int32.self, forKey: .patternColor),
|
||||
|
|
@ -313,6 +357,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case 2:
|
||||
self = .backdrop(
|
||||
name: decoder.decodeStringForKey(CodingKeys.name.rawValue, orElse: ""),
|
||||
id: decoder.decodeInt32ForKey(CodingKeys.id.rawValue, orElse: 0),
|
||||
innerColor: decoder.decodeInt32ForKey(CodingKeys.innerColor.rawValue, orElse: 0),
|
||||
outerColor: decoder.decodeInt32ForKey(CodingKeys.outerColor.rawValue, orElse: 0),
|
||||
patternColor: decoder.decodeInt32ForKey(CodingKeys.patternColor.rawValue, orElse: 0),
|
||||
|
|
@ -346,9 +391,10 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
try container.encode(name, forKey: .name)
|
||||
try container.encode(file, forKey: .file)
|
||||
try container.encode(rarity, forKey: .rarity)
|
||||
case let .backdrop(name, innerColor, outerColor, patternColor, textColor, rarity):
|
||||
case let .backdrop(name, id, innerColor, outerColor, patternColor, textColor, rarity):
|
||||
try container.encode(Int32(2), forKey: .type)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(id, forKey: .id)
|
||||
try container.encode(innerColor, forKey: .innerColor)
|
||||
try container.encode(outerColor, forKey: .outerColor)
|
||||
try container.encode(patternColor, forKey: .patternColor)
|
||||
|
|
@ -376,9 +422,10 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
encoder.encodeString(name, forKey: CodingKeys.name.rawValue)
|
||||
encoder.encodeObject(file, forKey: CodingKeys.file.rawValue)
|
||||
encoder.encodeInt32(rarity, forKey: CodingKeys.rarity.rawValue)
|
||||
case let .backdrop(name, innerColor, outerColor, patternColor, textColor, rarity):
|
||||
case let .backdrop(name, id, innerColor, outerColor, patternColor, textColor, rarity):
|
||||
encoder.encodeInt32(2, forKey: CodingKeys.type.rawValue)
|
||||
encoder.encodeString(name, forKey: CodingKeys.name.rawValue)
|
||||
encoder.encodeInt32(id, forKey: CodingKeys.id.rawValue)
|
||||
encoder.encodeInt32(innerColor, forKey: CodingKeys.innerColor.rawValue)
|
||||
encoder.encodeInt32(outerColor, forKey: CodingKeys.outerColor.rawValue)
|
||||
encoder.encodeInt32(patternColor, forKey: CodingKeys.patternColor.rawValue)
|
||||
|
|
@ -451,8 +498,9 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
public let attributes: [Attribute]
|
||||
public let availability: Availability
|
||||
public let giftAddress: String?
|
||||
public let resellStars: Int64?
|
||||
|
||||
public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?) {
|
||||
public init(id: Int64, title: String, number: Int32, slug: String, owner: Owner, attributes: [Attribute], availability: Availability, giftAddress: String?, resellStars: Int64?) {
|
||||
self.id = id
|
||||
self.title = title
|
||||
self.number = number
|
||||
|
|
@ -461,6 +509,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.attributes = attributes
|
||||
self.availability = availability
|
||||
self.giftAddress = giftAddress
|
||||
self.resellStars = resellStars
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
|
@ -481,6 +530,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.attributes = try container.decode([UniqueGift.Attribute].self, forKey: .attributes)
|
||||
self.availability = try container.decode(UniqueGift.Availability.self, forKey: .availability)
|
||||
self.giftAddress = try container.decodeIfPresent(String.self, forKey: .giftAddress)
|
||||
self.resellStars = try container.decodeIfPresent(Int64.self, forKey: .resellStars)
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
|
|
@ -500,6 +550,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
self.attributes = (try? decoder.decodeObjectArrayWithCustomDecoderForKey(CodingKeys.attributes.rawValue, decoder: { UniqueGift.Attribute(decoder: $0) })) ?? []
|
||||
self.availability = decoder.decodeObjectForKey(CodingKeys.availability.rawValue, decoder: { UniqueGift.Availability(decoder: $0) }) as! UniqueGift.Availability
|
||||
self.giftAddress = decoder.decodeOptionalStringForKey(CodingKeys.giftAddress.rawValue)
|
||||
self.resellStars = decoder.decodeOptionalInt64ForKey(CodingKeys.resellStars.rawValue)
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
|
|
@ -519,6 +570,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
try container.encode(self.attributes, forKey: .attributes)
|
||||
try container.encode(self.availability, forKey: .availability)
|
||||
try container.encodeIfPresent(self.giftAddress, forKey: .giftAddress)
|
||||
try container.encodeIfPresent(self.resellStars, forKey: .resellStars)
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
|
|
@ -541,6 +593,25 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.giftAddress.rawValue)
|
||||
}
|
||||
if let resellStars = self.resellStars {
|
||||
encoder.encodeInt64(resellStars, forKey: CodingKeys.resellStars.rawValue)
|
||||
} else {
|
||||
encoder.encodeNil(forKey: CodingKeys.resellStars.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func withResellStars(_ resellStars: Int64?) -> UniqueGift {
|
||||
return UniqueGift(
|
||||
id: self.id,
|
||||
title: self.title,
|
||||
number: self.number,
|
||||
slug: self.slug,
|
||||
owner: self.owner,
|
||||
attributes: self.attributes,
|
||||
availability: self.availability,
|
||||
giftAddress: self.giftAddress,
|
||||
resellStars: resellStars
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -608,7 +679,7 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
extension StarGift {
|
||||
init?(apiStarGift: Api.StarGift) {
|
||||
switch apiStarGift {
|
||||
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, convertStars, firstSale, lastSale, upgradeStars):
|
||||
case let .starGift(apiFlags, id, sticker, stars, availabilityRemains, availabilityTotal, availabilityResale, convertStars, firstSale, lastSale, upgradeStars, minResaleStars, title):
|
||||
var flags = StarGift.Gift.Flags()
|
||||
if (apiFlags & (1 << 2)) != 0 {
|
||||
flags.insert(.isBirthdayGift)
|
||||
|
|
@ -616,7 +687,12 @@ extension StarGift {
|
|||
|
||||
var availability: StarGift.Gift.Availability?
|
||||
if let availabilityRemains, let availabilityTotal {
|
||||
availability = StarGift.Gift.Availability(remains: availabilityRemains, total: availabilityTotal)
|
||||
availability = StarGift.Gift.Availability(
|
||||
remains: availabilityRemains,
|
||||
total: availabilityTotal,
|
||||
resale: availabilityResale ?? 0,
|
||||
minResaleStars: minResaleStars
|
||||
)
|
||||
}
|
||||
var soldOut: StarGift.Gift.SoldOut?
|
||||
if let firstSale, let lastSale {
|
||||
|
|
@ -625,8 +701,8 @@ extension StarGift {
|
|||
guard let file = telegramMediaFileFromApiDocument(sticker, altDocuments: nil) else {
|
||||
return nil
|
||||
}
|
||||
self = .generic(StarGift.Gift(id: id, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars))
|
||||
case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress):
|
||||
self = .generic(StarGift.Gift(id: id, title: title, file: file, price: stars, convertStars: convertStars, availability: availability, soldOut: soldOut, flags: flags, upgradeStars: upgradeStars))
|
||||
case let .starGiftUnique(_, id, title, slug, num, ownerPeerId, ownerName, ownerAddress, attributes, availabilityIssued, availabilityTotal, giftAddress, reselltars):
|
||||
let owner: StarGift.UniqueGift.Owner
|
||||
if let ownerAddress {
|
||||
owner = .address(ownerAddress)
|
||||
|
|
@ -637,7 +713,7 @@ extension StarGift {
|
|||
} else {
|
||||
return nil
|
||||
}
|
||||
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress))
|
||||
self = .unique(StarGift.UniqueGift(id: id, title: title, number: num, slug: slug, owner: owner, attributes: attributes.compactMap { UniqueGift.Attribute(apiAttribute: $0) }, availability: UniqueGift.Availability(issued: availabilityIssued, total: availabilityTotal), giftAddress: giftAddress, resellStars: reselltars))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -769,6 +845,34 @@ public enum TransferStarGiftError {
|
|||
case disallowedStarGift
|
||||
}
|
||||
|
||||
public enum BuyStarGiftError {
|
||||
case generic
|
||||
}
|
||||
|
||||
public enum UpgradeStarGiftError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_buyStarGift(account: Account, slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||
let source: BotPaymentInvoiceSource = .starGiftResale(slug: slug, toPeerId: peerId)
|
||||
return _internal_fetchBotPaymentForm(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, source: source, themeParams: nil)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { error -> Signal<BotPaymentForm?, BuyStarGiftError> in
|
||||
return .fail(.generic)
|
||||
}
|
||||
|> mapToSignal { paymentForm in
|
||||
if let paymentForm {
|
||||
return _internal_sendStarsPaymentForm(account: account, formId: paymentForm.id, source: source)
|
||||
|> mapError { _ -> BuyStarGiftError in
|
||||
return .generic
|
||||
}
|
||||
|> ignoreValues
|
||||
} else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_transferStarGift(account: Account, prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal<Never, TransferStarGiftError> {
|
||||
return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputSavedStarGift)? in
|
||||
guard let inputPeer = transaction.getPeer(peerId).flatMap(apiInputPeer), let starGift = reference.apiStarGiftReference(transaction: transaction) else {
|
||||
|
|
@ -821,10 +925,6 @@ func _internal_transferStarGift(account: Account, prepaid: Bool, reference: Star
|
|||
}
|
||||
}
|
||||
|
||||
public enum UpgradeStarGiftError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
|
||||
if let formId {
|
||||
let source: BotPaymentInvoiceSource = .starGiftUpgrade(keepOriginalInfo: keepOriginalInfo, reference: reference)
|
||||
|
|
@ -863,7 +963,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
|
|||
case let .updateNewMessage(message, _, _):
|
||||
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, peerId, _, savedId) = action.action, case let .Id(messageId) = message.id {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, peerId, _, savedId, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
@ -1090,7 +1190,7 @@ private final class ProfileGiftsContextImpl {
|
|||
if !filter.contains(.unique) {
|
||||
flags |= (1 << 4)
|
||||
}
|
||||
return network.request(Api.functions.payments.getSavedStarGifts(flags: flags, peer: inputPeer, offset: initialNextOffset ?? "", limit: 32))
|
||||
return network.request(Api.functions.payments.getSavedStarGifts(flags: flags, peer: inputPeer, offset: initialNextOffset ?? "", limit: 36))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.SavedStarGifts?, NoError> in
|
||||
return .single(nil)
|
||||
|
|
@ -1359,6 +1459,27 @@ private final class ProfileGiftsContextImpl {
|
|||
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
|
||||
}
|
||||
|
||||
func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||
if let count = self.count {
|
||||
self.count = max(0, count - 1)
|
||||
}
|
||||
self.gifts.removeAll(where: { gift in
|
||||
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
self.filteredGifts.removeAll(where: { gift in
|
||||
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
self.pushState()
|
||||
|
||||
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
|
||||
}
|
||||
|
||||
func upgradeStarGift(formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
guard let self else {
|
||||
|
|
@ -1388,6 +1509,41 @@ private final class ProfileGiftsContextImpl {
|
|||
}
|
||||
}
|
||||
|
||||
func updateStarGiftResellPrice(slug: String, price: Int64?) {
|
||||
self.actionDisposable.set(
|
||||
_internal_updateStarGiftResalePrice(account: self.account, slug: slug, price: price).startStrict()
|
||||
)
|
||||
|
||||
|
||||
if let index = self.gifts.firstIndex(where: { gift in
|
||||
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
if case let .unique(uniqueGift) = self.gifts[index].gift {
|
||||
let updatedUniqueGift = uniqueGift.withResellStars(price)
|
||||
let updatedGift = self.gifts[index].withGift(.unique(updatedUniqueGift))
|
||||
self.gifts[index] = updatedGift
|
||||
}
|
||||
}
|
||||
|
||||
if let index = self.filteredGifts.firstIndex(where: { gift in
|
||||
if case let .unique(uniqueGift) = gift.gift, uniqueGift.slug == slug {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}) {
|
||||
if case let .unique(uniqueGift) = self.filteredGifts[index].gift {
|
||||
let updatedUniqueGift = uniqueGift.withResellStars(price)
|
||||
let updatedGift = self.filteredGifts[index].withGift(.unique(updatedUniqueGift))
|
||||
self.filteredGifts[index] = updatedGift
|
||||
}
|
||||
}
|
||||
|
||||
self.pushState()
|
||||
}
|
||||
|
||||
func toggleStarGiftsNotifications(enabled: Bool) {
|
||||
self.actionDisposable.set(
|
||||
_internal_toggleStarGiftsNotifications(account: self.account, peerId: self.peerId, enabled: enabled).startStrict()
|
||||
|
|
@ -1583,6 +1739,25 @@ public final class ProfileGiftsContext {
|
|||
try container.encodeIfPresent(self.transferStars, forKey: .transferStars)
|
||||
}
|
||||
|
||||
public func withGift(_ gift: TelegramCore.StarGift) -> StarGift {
|
||||
return StarGift(
|
||||
gift: gift,
|
||||
reference: self.reference,
|
||||
fromPeer: self.fromPeer,
|
||||
date: self.date,
|
||||
text: self.text,
|
||||
entities: self.entities,
|
||||
nameHidden: self.nameHidden,
|
||||
savedToProfile: self.savedToProfile,
|
||||
pinnedToTop: self.pinnedToTop,
|
||||
convertStars: self.convertStars,
|
||||
canUpgrade: self.canUpgrade,
|
||||
canExportDate: self.canExportDate,
|
||||
upgradeStars: self.upgradeStars,
|
||||
transferStars: self.transferStars
|
||||
)
|
||||
}
|
||||
|
||||
public func withSavedToProfile(_ savedToProfile: Bool) -> StarGift {
|
||||
return StarGift(
|
||||
gift: self.gift,
|
||||
|
|
@ -1720,6 +1895,20 @@ public final class ProfileGiftsContext {
|
|||
}
|
||||
}
|
||||
|
||||
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.buyStarGift(slug: slug, peerId: peerId).start(error: { error in
|
||||
subscriber.putError(error)
|
||||
}, completed: {
|
||||
subscriber.putCompletion()
|
||||
}))
|
||||
}
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func transferStarGift(prepaid: Bool, reference: StarGiftReference, peerId: EnginePeer.Id) -> Signal<Never, TransferStarGiftError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
|
@ -1750,6 +1939,12 @@ public final class ProfileGiftsContext {
|
|||
}
|
||||
}
|
||||
|
||||
public func updateStarGiftResellPrice(slug: String, price: Int64?) {
|
||||
self.impl.with { impl in
|
||||
impl.updateStarGiftResellPrice(slug: slug, price: price)
|
||||
}
|
||||
}
|
||||
|
||||
public func toggleStarGiftsNotifications(enabled: Bool) {
|
||||
self.impl.with { impl in
|
||||
impl.toggleStarGiftsNotifications(enabled: enabled)
|
||||
|
|
@ -1841,8 +2036,8 @@ extension StarGift.UniqueGift.Attribute {
|
|||
return nil
|
||||
}
|
||||
self = .pattern(name: name, file: file, rarity: rarityPermille)
|
||||
case let .starGiftAttributeBackdrop(name, centerColor, edgeColor, patternColor, textColor, rarityPermille):
|
||||
self = .backdrop(name: name, innerColor: centerColor, outerColor: edgeColor, patternColor: patternColor, textColor: textColor, rarity: rarityPermille)
|
||||
case let .starGiftAttributeBackdrop(name, id, centerColor, edgeColor, patternColor, textColor, rarityPermille):
|
||||
self = .backdrop(name: name, id: id, innerColor: centerColor, outerColor: edgeColor, patternColor: patternColor, textColor: textColor, rarity: rarityPermille)
|
||||
case let .starGiftAttributeOriginalDetails(_, sender, recipient, date, message):
|
||||
var text: String?
|
||||
var entities: [MessageTextEntity]?
|
||||
|
|
@ -2070,6 +2265,21 @@ func _internal_toggleStarGiftsNotifications(account: Account, peerId: EnginePeer
|
|||
}
|
||||
}
|
||||
|
||||
func _internal_updateStarGiftResalePrice(account: Account, slug: String, price: Int64?) -> Signal<Never, NoError> {
|
||||
return account.network.request(Api.functions.payments.updateStarGiftPrice(slug: slug, resellStars: price ?? 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public extension StarGift.UniqueGift {
|
||||
var itemFile: TelegramMediaFile? {
|
||||
for attribute in self.attributes {
|
||||
|
|
@ -2080,3 +2290,289 @@ public extension StarGift.UniqueGift {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private final class ResaleGiftsContextImpl {
|
||||
private let queue: Queue
|
||||
private let account: Account
|
||||
private let giftId: Int64
|
||||
|
||||
private let disposable = MetaDisposable()
|
||||
|
||||
private var sorting: ResaleGiftsContext.Sorting = .value
|
||||
private var filterAttributes: [ResaleGiftsContext.Attribute] = []
|
||||
|
||||
private var gifts: [StarGift] = []
|
||||
private var attributes: [StarGift.UniqueGift.Attribute] = []
|
||||
private var attributeCount: [ResaleGiftsContext.Attribute: Int32] = [:]
|
||||
|
||||
private var count: Int32?
|
||||
private var dataState: ResaleGiftsContext.State.DataState = .ready(canLoadMore: true, nextOffset: nil)
|
||||
|
||||
var _state: ResaleGiftsContext.State?
|
||||
private let stateValue = Promise<ResaleGiftsContext.State>()
|
||||
var state: Signal<ResaleGiftsContext.State, NoError> {
|
||||
return self.stateValue.get()
|
||||
}
|
||||
|
||||
init(
|
||||
queue: Queue,
|
||||
account: Account,
|
||||
giftId: Int64
|
||||
) {
|
||||
self.queue = queue
|
||||
self.account = account
|
||||
self.giftId = giftId
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.disposable.dispose()
|
||||
}
|
||||
|
||||
func reload() {
|
||||
self.gifts = []
|
||||
self.dataState = .ready(canLoadMore: true, nextOffset: nil)
|
||||
self.loadMore(reload: true)
|
||||
}
|
||||
|
||||
func loadMore(reload: Bool = false) {
|
||||
let giftId = self.giftId
|
||||
let accountPeerId = self.account.peerId
|
||||
let network = self.account.network
|
||||
let postbox = self.account.postbox
|
||||
let sorting = self.sorting
|
||||
let filterAttributes = self.filterAttributes
|
||||
|
||||
let dataState = self.dataState
|
||||
|
||||
if case let .ready(true, initialNextOffset) = dataState {
|
||||
self.dataState = .loading
|
||||
if !reload {
|
||||
self.pushState()
|
||||
}
|
||||
|
||||
var flags: Int32 = 0
|
||||
switch sorting {
|
||||
case .date:
|
||||
break
|
||||
case .value:
|
||||
flags |= (1 << 1)
|
||||
case .number:
|
||||
flags |= (1 << 2)
|
||||
}
|
||||
|
||||
var apiAttributes: [Api.StarGiftAttributeId]?
|
||||
if !filterAttributes.isEmpty {
|
||||
flags |= (1 << 3)
|
||||
apiAttributes = filterAttributes.map {
|
||||
switch $0 {
|
||||
case let .model(id):
|
||||
return .starGiftAttributeIdModel(documentId: id)
|
||||
case let .pattern(id):
|
||||
return .starGiftAttributeIdPattern(documentId: id)
|
||||
case let .backdrop(id):
|
||||
return .starGiftAttributeIdBackdrop(backdropId: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var attributesHash: Int64?
|
||||
if "".isEmpty {
|
||||
flags |= (1 << 0)
|
||||
attributesHash = 0
|
||||
}
|
||||
|
||||
let signal = network.request(Api.functions.payments.getResaleStarGifts(flags: flags, attributesHash: attributesHash, giftId: giftId, attributes: apiAttributes, offset: initialNextOffset ?? "", limit: 36))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.ResaleStarGifts?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<([StarGift], [StarGift.UniqueGift.Attribute], [ResaleGiftsContext.Attribute: Int32], Int32, String?), NoError> in
|
||||
guard let result else {
|
||||
return .single(([], [], [:], 0, nil))
|
||||
}
|
||||
return postbox.transaction { transaction -> ([StarGift], [StarGift.UniqueGift.Attribute], [ResaleGiftsContext.Attribute: Int32], Int32, String?) in
|
||||
switch result {
|
||||
case let .resaleStarGifts(_, count, gifts, nextOffset, attributes, attributesHash, chats, counters, users):
|
||||
let _ = attributesHash
|
||||
|
||||
var resultAttributes: [StarGift.UniqueGift.Attribute] = []
|
||||
if let attributes {
|
||||
resultAttributes = attributes.compactMap { StarGift.UniqueGift.Attribute(apiAttribute: $0) }
|
||||
}
|
||||
|
||||
var attributeCount: [ResaleGiftsContext.Attribute: Int32] = [:]
|
||||
if let counters {
|
||||
for counter in counters {
|
||||
switch counter {
|
||||
case let .starGiftAttributeCounter(attribute, count):
|
||||
switch attribute {
|
||||
case let .starGiftAttributeIdModel(documentId):
|
||||
attributeCount[.model(documentId)] = count
|
||||
case let .starGiftAttributeIdPattern(documentId):
|
||||
attributeCount[.pattern(documentId)] = count
|
||||
case let .starGiftAttributeIdBackdrop(backdropId):
|
||||
attributeCount[.backdrop(backdropId)] = count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: accountPeerId, peers: parsedPeers)
|
||||
|
||||
var mappedGifts: [StarGift] = []
|
||||
for gift in gifts {
|
||||
if let mappedGift = StarGift(apiStarGift: gift), case let .unique(uniqueGift) = mappedGift, let resellStars = uniqueGift.resellStars, resellStars > 0 {
|
||||
mappedGifts.append(mappedGift)
|
||||
}
|
||||
}
|
||||
|
||||
return (mappedGifts, resultAttributes, attributeCount, count, nextOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.disposable.set((signal
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] (gifts, attributes, attributeCount, count, nextOffset) in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if initialNextOffset == nil || reload {
|
||||
self.gifts = gifts
|
||||
} else {
|
||||
self.gifts.append(contentsOf: gifts)
|
||||
}
|
||||
|
||||
let updatedCount = max(Int32(self.gifts.count), count)
|
||||
self.count = updatedCount
|
||||
self.attributes = attributes
|
||||
if !attributeCount.isEmpty {
|
||||
self.attributeCount = attributeCount
|
||||
}
|
||||
self.dataState = .ready(canLoadMore: count != 0 && updatedCount > self.gifts.count && nextOffset != nil, nextOffset: nextOffset)
|
||||
|
||||
self.pushState()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func updateFilterAttributes(_ filterAttributes: [ResaleGiftsContext.Attribute]) {
|
||||
guard self.filterAttributes != filterAttributes else {
|
||||
return
|
||||
}
|
||||
self.filterAttributes = filterAttributes
|
||||
self.dataState = .ready(canLoadMore: true, nextOffset: nil)
|
||||
self.pushState()
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
func updateSorting(_ sorting: ResaleGiftsContext.Sorting) {
|
||||
guard self.sorting != sorting else {
|
||||
return
|
||||
}
|
||||
self.sorting = sorting
|
||||
self.dataState = .ready(canLoadMore: true, nextOffset: nil)
|
||||
self.pushState()
|
||||
|
||||
self.loadMore()
|
||||
}
|
||||
|
||||
private func pushState() {
|
||||
let state = ResaleGiftsContext.State(
|
||||
sorting: self.sorting,
|
||||
filterAttributes: self.filterAttributes,
|
||||
gifts: self.gifts,
|
||||
attributes: self.attributes,
|
||||
attributeCount: self.attributeCount,
|
||||
count: self.count,
|
||||
dataState: self.dataState
|
||||
)
|
||||
self._state = state
|
||||
self.stateValue.set(.single(state))
|
||||
}
|
||||
}
|
||||
|
||||
public final class ResaleGiftsContext {
|
||||
public enum Sorting: Equatable {
|
||||
case date
|
||||
case value
|
||||
case number
|
||||
}
|
||||
|
||||
public enum Attribute: Equatable, Hashable {
|
||||
case model(Int64)
|
||||
case pattern(Int64)
|
||||
case backdrop(Int32)
|
||||
}
|
||||
|
||||
public struct State: Equatable {
|
||||
public enum DataState: Equatable {
|
||||
case loading
|
||||
case ready(canLoadMore: Bool, nextOffset: String?)
|
||||
}
|
||||
|
||||
public var sorting: Sorting
|
||||
public var filterAttributes: [Attribute]
|
||||
public var gifts: [StarGift]
|
||||
public var attributes: [StarGift.UniqueGift.Attribute]
|
||||
public var attributeCount: [Attribute: Int32]
|
||||
public var count: Int32?
|
||||
public var dataState: ResaleGiftsContext.State.DataState
|
||||
}
|
||||
|
||||
private let queue: Queue = .mainQueue()
|
||||
private let impl: QueueLocalObject<ResaleGiftsContextImpl>
|
||||
|
||||
public var state: Signal<ResaleGiftsContext.State, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
|
||||
self.impl.with { impl in
|
||||
disposable.set(impl.state.start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
}
|
||||
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
account: Account,
|
||||
giftId: Int64
|
||||
) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return ResaleGiftsContextImpl(queue: queue, account: account, giftId: giftId)
|
||||
})
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
self.impl.with { impl in
|
||||
impl.loadMore()
|
||||
}
|
||||
}
|
||||
|
||||
public func updateSorting(_ sorting: ResaleGiftsContext.Sorting) {
|
||||
self.impl.with { impl in
|
||||
impl.updateSorting(sorting)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateFilterAttributes(_ attributes: [ResaleGiftsContext.Attribute]) {
|
||||
self.impl.with { impl in
|
||||
impl.updateFilterAttributes(attributes)
|
||||
}
|
||||
}
|
||||
|
||||
public var currentState: ResaleGiftsContext.State? {
|
||||
var state: ResaleGiftsContext.State?
|
||||
self.impl.syncWith { impl in
|
||||
state = impl._state
|
||||
}
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1524,10 +1524,10 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
|
|||
receiptMessageId = id
|
||||
}
|
||||
}
|
||||
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift:
|
||||
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale:
|
||||
receiptMessageId = nil
|
||||
}
|
||||
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, peerId, _, savedId) = action.action, case let .Id(messageId) = message.id {
|
||||
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, peerId, _, savedId, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ public extension TelegramEngine {
|
|||
return _internal_transferStarGift(account: self.account, prepaid: prepaid, reference: reference, peerId: peerId)
|
||||
}
|
||||
|
||||
public func buyStarGift(slug: String, peerId: EnginePeer.Id) -> Signal<Never, BuyStarGiftError> {
|
||||
return _internal_buyStarGift(account: self.account, slug: slug, peerId: peerId)
|
||||
}
|
||||
|
||||
public func upgradeStarGift(formId: Int64?, reference: StarGiftReference, keepOriginalInfo: Bool) -> Signal<ProfileGiftsContext.State.StarGift, UpgradeStarGiftError> {
|
||||
return _internal_upgradeStarGift(account: self.account, formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||
}
|
||||
|
|
@ -148,6 +152,10 @@ public extension TelegramEngine {
|
|||
public func toggleStarGiftsNotifications(peerId: EnginePeer.Id, enabled: Bool) -> Signal<Never, NoError> {
|
||||
return _internal_toggleStarGiftsNotifications(account: self.account, peerId: peerId, enabled: enabled)
|
||||
}
|
||||
|
||||
public func updateStarGiftResalePrice(slug: String, price: Int64?) -> Signal<Never, NoError> {
|
||||
return _internal_updateStarGiftResalePrice(account: self.account, slug: slug, price: price)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -326,7 +326,7 @@ func _internal_updatePeerStarGiftStatus(account: Account, peerId: PeerId, starGi
|
|||
file = fileValue
|
||||
case let .pattern(_, patternFileValue, _):
|
||||
patternFile = patternFileValue
|
||||
case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||
innerColor = innerColorValue
|
||||
outerColor = outerColorValue
|
||||
patternColor = patternColorValue
|
||||
|
|
|
|||
|
|
@ -1165,7 +1165,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, peerId, senderId, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, peerId, senderId, _, _):
|
||||
if case let .unique(gift) = gift {
|
||||
if !forAdditionalServiceMessage && !"".isEmpty {
|
||||
attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor)
|
||||
|
|
|
|||
|
|
@ -463,6 +463,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/MiniAppListScreen",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsIntroScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftOptionsScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftStoreScreen",
|
||||
"//submodules/TelegramUI/Components/ContentReportScreen",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
|
|
@ -472,6 +473,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/CheckComponent",
|
||||
"//submodules/TelegramUI/Components/MarqueeComponent",
|
||||
"//third-party/recaptcha:RecaptchaEnterprise",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
|
|
|
|||
|
|
@ -1651,6 +1651,7 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
case videoCollage(VideoCollage)
|
||||
case asset(PHAsset)
|
||||
case draft(MediaEditorDraft)
|
||||
case assets([PHAsset])
|
||||
|
||||
func withPIPPosition(_ position: CameraScreenImpl.PIPPosition) -> Result {
|
||||
switch self {
|
||||
|
|
@ -2726,9 +2727,13 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
self.additionalPreviewView.isEnabled = false
|
||||
self.collageView?.isEnabled = false
|
||||
|
||||
#if targetEnvironment(simulator)
|
||||
|
||||
#else
|
||||
Queue.mainQueue().after(0.3) {
|
||||
self.previewBlurPromise.set(true)
|
||||
}
|
||||
#endif
|
||||
self.camera?.stopCapture()
|
||||
|
||||
self.cameraIsActive = false
|
||||
|
|
@ -3627,7 +3632,11 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
if self.cameraState.isCollageEnabled, let collage = self.node.collage {
|
||||
selectionLimit = collage.grid.count - collage.results.count
|
||||
} else {
|
||||
selectionLimit = 6
|
||||
if self.cameraState.isCollageEnabled {
|
||||
selectionLimit = 6
|
||||
} else {
|
||||
selectionLimit = 10
|
||||
}
|
||||
}
|
||||
controller = self.context.sharedContext.makeStoryMediaPickerScreen(
|
||||
context: self.context,
|
||||
|
|
@ -3698,44 +3707,52 @@ public class CameraScreenImpl: ViewController, CameraScreen {
|
|||
}
|
||||
}
|
||||
}
|
||||
}, multipleCompletion: { [weak self] results in
|
||||
}, multipleCompletion: { [weak self] results, collage in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if !self.cameraState.isCollageEnabled {
|
||||
var selectedGrid: Camera.CollageGrid = collageGrids.first!
|
||||
for grid in collageGrids {
|
||||
if grid.count == results.count {
|
||||
selectedGrid = grid
|
||||
break
|
||||
|
||||
if collage {
|
||||
if !self.cameraState.isCollageEnabled {
|
||||
var selectedGrid: Camera.CollageGrid = collageGrids.first!
|
||||
for grid in collageGrids {
|
||||
if grid.count == results.count {
|
||||
selectedGrid = grid
|
||||
break
|
||||
}
|
||||
}
|
||||
self.updateCameraState({
|
||||
$0.updatedIsCollageEnabled(true).updatedCollageProgress(0.0).updatedIsDualCameraEnabled(false).updatedCollageGrid(selectedGrid)
|
||||
}, transition: .spring(duration: 0.3))
|
||||
}
|
||||
self.updateCameraState({
|
||||
$0.updatedIsCollageEnabled(true).updatedCollageProgress(0.0).updatedIsDualCameraEnabled(false).updatedCollageGrid(selectedGrid)
|
||||
}, transition: .spring(duration: 0.3))
|
||||
}
|
||||
|
||||
if let assets = results as? [PHAsset] {
|
||||
var results: [Signal<CameraScreenImpl.Result, NoError>] = []
|
||||
for asset in assets {
|
||||
if asset.mediaType == .video && asset.duration > 1.0 {
|
||||
results.append(.single(.asset(asset)))
|
||||
} else {
|
||||
results.append(
|
||||
assetImage(asset: asset, targetSize: CGSize(width: 1080, height: 1080), exact: false, deliveryMode: .highQualityFormat)
|
||||
|> runOn(Queue.concurrentDefaultQueue())
|
||||
|> mapToSignal { image -> Signal<CameraScreenImpl.Result, NoError> in
|
||||
if let image {
|
||||
return .single(.image(Result.Image(image: image, additionalImage: nil, additionalImagePosition: .topLeft)))
|
||||
} else {
|
||||
return .complete()
|
||||
|
||||
if let assets = results as? [PHAsset] {
|
||||
var results: [Signal<CameraScreenImpl.Result, NoError>] = []
|
||||
for asset in assets {
|
||||
if asset.mediaType == .video && asset.duration > 1.0 {
|
||||
results.append(.single(.asset(asset)))
|
||||
} else {
|
||||
results.append(
|
||||
assetImage(asset: asset, targetSize: CGSize(width: 1080, height: 1080), exact: false, deliveryMode: .highQualityFormat)
|
||||
|> runOn(Queue.concurrentDefaultQueue())
|
||||
|> mapToSignal { image -> Signal<CameraScreenImpl.Result, NoError> in
|
||||
if let image {
|
||||
return .single(.image(Result.Image(image: image, additionalImage: nil, additionalImagePosition: .topLeft)))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
self.node.collage?.addResults(signals: results)
|
||||
}
|
||||
} else {
|
||||
if let assets = results as? [PHAsset] {
|
||||
self.completion(.single(.assets(assets)), nil, {
|
||||
|
||||
})
|
||||
}
|
||||
self.node.collage?.addResults(signals: results)
|
||||
}
|
||||
self.galleryController = nil
|
||||
|
||||
|
|
|
|||
|
|
@ -560,7 +560,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
buttonTitle = item.presentationData.strings.Notification_StarGift_View
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _):
|
||||
if case let .unique(uniqueGift) = gift {
|
||||
isStarGift = true
|
||||
|
||||
|
|
@ -594,7 +594,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
case let .model(name, file, _):
|
||||
modelValue = name
|
||||
animationFile = file
|
||||
case let .backdrop(name, innerColor, outerColor, patternColor, _, _):
|
||||
case let .backdrop(name, _, innerColor, outerColor, patternColor, _, _):
|
||||
uniqueBackgroundColor = UIColor(rgb: UInt32(bitPattern: outerColor))
|
||||
uniqueSecondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||
uniquePatternColor = UIColor(rgb: UInt32(bitPattern: patternColor))
|
||||
|
|
|
|||
|
|
@ -2189,7 +2189,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|
|||
context: context,
|
||||
theme: presentationData.theme.theme,
|
||||
strings: presentationData.strings,
|
||||
subject: .uniqueGift(gift: gift),
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
mode: .preview
|
||||
)
|
||||
),
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ public final class EntityKeyboardAnimationData: Equatable {
|
|||
for attribute in gift.attributes {
|
||||
if case let .model(_, fileValue, _) = attribute {
|
||||
file = fileValue
|
||||
} else if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute {
|
||||
} else if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
|
||||
color = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||
let _ = outerColor
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,6 +149,11 @@ public final class GiftCompositionComponent: Component {
|
|||
previewTimer.invalidate()
|
||||
self.previewTimer = nil
|
||||
}
|
||||
|
||||
if !self.fetchedFiles.contains(file.fileId.id) {
|
||||
self.disposables.add(freeMediaFileResourceInteractiveFetched(account: component.context.account, userLocation: .other, fileReference: .standalone(media: file), resource: file.resource).start())
|
||||
self.fetchedFiles.insert(file.fileId.id)
|
||||
}
|
||||
case let .unique(gift):
|
||||
for attribute in gift.attributes {
|
||||
switch attribute {
|
||||
|
|
@ -161,7 +166,7 @@ public final class GiftCompositionComponent: Component {
|
|||
case let .pattern(_, file, _):
|
||||
patternFile = file
|
||||
files[file.fileId.id] = file
|
||||
case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, _, _):
|
||||
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, _, _):
|
||||
backgroundColor = UIColor(rgb: UInt32(bitPattern: outerColorValue))
|
||||
secondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColorValue))
|
||||
patternColor = UIColor(rgb: UInt32(bitPattern: patternColorValue))
|
||||
|
|
@ -222,7 +227,7 @@ public final class GiftCompositionComponent: Component {
|
|||
files[file.fileId.id] = file
|
||||
}
|
||||
|
||||
if case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, _, _) = self.previewBackdrops[Int(self.previewBackdropIndex)] {
|
||||
if case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, _, _) = self.previewBackdrops[Int(self.previewBackdropIndex)] {
|
||||
backgroundColor = UIColor(rgb: UInt32(bitPattern: outerColorValue))
|
||||
secondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColorValue))
|
||||
patternColor = UIColor(rgb: UInt32(bitPattern: patternColorValue))
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ public final class GiftItemComponent: Component {
|
|||
public enum Subject: Equatable {
|
||||
case premium(months: Int32, price: String)
|
||||
case starGift(gift: StarGift.Gift, price: String)
|
||||
case uniqueGift(gift: StarGift.UniqueGift)
|
||||
case uniqueGift(gift: StarGift.UniqueGift, price: String?)
|
||||
}
|
||||
|
||||
public struct Ribbon: Equatable {
|
||||
|
|
@ -28,6 +28,7 @@ public final class GiftItemComponent: Component {
|
|||
case red
|
||||
case blue
|
||||
case purple
|
||||
case green
|
||||
case custom(Int32, Int32)
|
||||
|
||||
func colors(theme: PresentationTheme) -> [UIColor] {
|
||||
|
|
@ -61,6 +62,11 @@ public final class GiftItemComponent: Component {
|
|||
UIColor(rgb: 0x747bf6),
|
||||
UIColor(rgb: 0xe367d8)
|
||||
]
|
||||
case .green:
|
||||
return [
|
||||
UIColor(rgb: 0x4bb121),
|
||||
UIColor(rgb: 0x53d654)
|
||||
]
|
||||
case let .custom(topColor, _):
|
||||
return [
|
||||
UIColor(rgb: UInt32(bitPattern: topColor)).withMultiplied(hue: 0.97, saturation: 1.45, brightness: 0.89),
|
||||
|
|
@ -72,17 +78,25 @@ public final class GiftItemComponent: Component {
|
|||
|
||||
public enum Font {
|
||||
case generic
|
||||
case larger
|
||||
case monospaced
|
||||
}
|
||||
|
||||
public let text: String
|
||||
public let font: Font
|
||||
public let color: Color
|
||||
public let outline: UIColor?
|
||||
|
||||
public init(text: String, font: Font = .generic, color: Color) {
|
||||
public init(
|
||||
text: String,
|
||||
font: Font = .generic,
|
||||
color: Color,
|
||||
outline: UIColor? = nil
|
||||
) {
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.outline = outline
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +122,7 @@ public final class GiftItemComponent: Component {
|
|||
let subtitle: String?
|
||||
let label: String?
|
||||
let ribbon: Ribbon?
|
||||
let resellPrice: Int64?
|
||||
let isLoading: Bool
|
||||
let isHidden: Bool
|
||||
let isSoldOut: Bool
|
||||
|
|
@ -128,6 +143,7 @@ public final class GiftItemComponent: Component {
|
|||
subtitle: String? = nil,
|
||||
label: String? = nil,
|
||||
ribbon: Ribbon? = nil,
|
||||
resellPrice: Int64? = nil,
|
||||
isLoading: Bool = false,
|
||||
isHidden: Bool = false,
|
||||
isSoldOut: Bool = false,
|
||||
|
|
@ -147,6 +163,7 @@ public final class GiftItemComponent: Component {
|
|||
self.subtitle = subtitle
|
||||
self.label = label
|
||||
self.ribbon = ribbon
|
||||
self.resellPrice = resellPrice
|
||||
self.isLoading = isLoading
|
||||
self.isHidden = isHidden
|
||||
self.isSoldOut = isSoldOut
|
||||
|
|
@ -186,6 +203,9 @@ public final class GiftItemComponent: Component {
|
|||
if lhs.ribbon != rhs.ribbon {
|
||||
return false
|
||||
}
|
||||
if lhs.resellPrice != rhs.resellPrice {
|
||||
return false
|
||||
}
|
||||
if lhs.isLoading != rhs.isLoading {
|
||||
return false
|
||||
}
|
||||
|
|
@ -229,6 +249,8 @@ public final class GiftItemComponent: Component {
|
|||
private let subtitle = ComponentView<Empty>()
|
||||
private let button = ComponentView<Empty>()
|
||||
private let label = ComponentView<Empty>()
|
||||
|
||||
private let ribbonOutline = UIImageView()
|
||||
private let ribbon = UIImageView()
|
||||
private let ribbonText = ComponentView<Empty>()
|
||||
|
||||
|
|
@ -244,6 +266,9 @@ public final class GiftItemComponent: Component {
|
|||
private var hiddenIcon: UIImageView?
|
||||
private var pinnedIcon: UIImageView?
|
||||
|
||||
private var resellBackground: BlurredBackgroundView?
|
||||
private let reselLabel = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
|
|
@ -383,7 +408,7 @@ public final class GiftItemComponent: Component {
|
|||
file: gift.file
|
||||
)
|
||||
animationOffset = 16.0
|
||||
case let .uniqueGift(gift):
|
||||
case let .uniqueGift(gift, _):
|
||||
animationOffset = 16.0
|
||||
for attribute in gift.attributes {
|
||||
switch attribute {
|
||||
|
|
@ -396,7 +421,7 @@ public final class GiftItemComponent: Component {
|
|||
case let .pattern(_, file, _):
|
||||
patternFile = file
|
||||
files[file.fileId.id] = file
|
||||
case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, _, _):
|
||||
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, _, _):
|
||||
backgroundColor = UIColor(rgb: UInt32(bitPattern: outerColorValue))
|
||||
secondBackgroundColor = UIColor(rgb: UInt32(bitPattern: innerColorValue))
|
||||
patternColor = UIColor(rgb: UInt32(bitPattern: patternColorValue))
|
||||
|
|
@ -530,6 +555,7 @@ public final class GiftItemComponent: Component {
|
|||
|
||||
let buttonColor: UIColor
|
||||
var starsColor: UIColor?
|
||||
var tinted = false
|
||||
let price: String
|
||||
switch component.subject {
|
||||
case let .premium(_, priceValue), let .starGift(_, priceValue):
|
||||
|
|
@ -542,9 +568,10 @@ public final class GiftItemComponent: Component {
|
|||
buttonColor = component.theme.list.itemAccentColor
|
||||
}
|
||||
price = priceValue
|
||||
case .uniqueGift:
|
||||
case let .uniqueGift(_, priceValue):
|
||||
buttonColor = UIColor.white
|
||||
price = component.strings.Gift_Options_Gift_Transfer
|
||||
price = priceValue ?? component.strings.Gift_Options_Gift_Transfer
|
||||
tinted = true
|
||||
}
|
||||
|
||||
let buttonSize = self.button.update(
|
||||
|
|
@ -554,6 +581,7 @@ public final class GiftItemComponent: Component {
|
|||
context: component.context,
|
||||
text: price,
|
||||
color: buttonColor,
|
||||
tinted: tinted,
|
||||
starsColor: starsColor
|
||||
)
|
||||
),
|
||||
|
|
@ -623,6 +651,8 @@ public final class GiftItemComponent: Component {
|
|||
switch ribbon.font {
|
||||
case .generic:
|
||||
ribbonFont = Font.semibold(ribbonFontSize)
|
||||
case .larger:
|
||||
ribbonFont = Font.semibold(10.0)
|
||||
case .monospaced:
|
||||
ribbonFont = Font.with(size: 10.0, design: .monospace, weight: .semibold)
|
||||
}
|
||||
|
|
@ -645,6 +675,18 @@ public final class GiftItemComponent: Component {
|
|||
}
|
||||
ribbonTextView.bounds = CGRect(origin: .zero, size: ribbonTextSize)
|
||||
|
||||
if let _ = component.ribbon?.outline {
|
||||
if self.ribbonOutline.image == nil || themeUpdated || previousComponent?.ribbon?.outline != component.ribbon?.outline {
|
||||
self.ribbonOutline.image = ribbonOutlineImage
|
||||
self.ribbonOutline.tintColor = component.ribbon?.outline
|
||||
if self.ribbonOutline.superview == nil {
|
||||
self.insertSubview(self.ribbonOutline, belowSubview: self.ribbon)
|
||||
}
|
||||
}
|
||||
} else if self.ribbonOutline.superview != nil {
|
||||
self.ribbonOutline.removeFromSuperview()
|
||||
}
|
||||
|
||||
if self.ribbon.image == nil || themeUpdated || previousComponent?.ribbon?.color != component.ribbon?.color {
|
||||
var direction: GradientImageDirection = .mirroredDiagonal
|
||||
if case .custom = ribbon.color {
|
||||
|
|
@ -661,11 +703,16 @@ public final class GiftItemComponent: Component {
|
|||
if let ribbonImage = self.ribbon.image {
|
||||
self.ribbon.frame = CGRect(origin: CGPoint(x: size.width - ribbonImage.size.width + ribbonOffset.x, y: ribbonOffset.y), size: ribbonImage.size)
|
||||
}
|
||||
if let ribbonOutlineImage = self.ribbonOutline.image {
|
||||
self.ribbonOutline.frame = ribbonOutlineImage.size.centered(around: self.ribbon.center.offsetBy(dx: 0.0, dy: 2.0))
|
||||
}
|
||||
|
||||
ribbonTextView.transform = CGAffineTransform(rotationAngle: .pi / 4.0)
|
||||
ribbonTextView.center = CGPoint(x: size.width - 22.0 + ribbonOffset.x, y: 22.0 + ribbonOffset.y)
|
||||
}
|
||||
} else {
|
||||
if self.ribbonText.view?.superview != nil {
|
||||
self.ribbonOutline.removeFromSuperview()
|
||||
self.ribbon.removeFromSuperview()
|
||||
self.ribbonText.view?.removeFromSuperview()
|
||||
}
|
||||
|
|
@ -808,6 +855,72 @@ public final class GiftItemComponent: Component {
|
|||
hiddenIcon.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
|
||||
if let resellPrice = component.resellPrice {
|
||||
let labelColor = UIColor.white
|
||||
let attributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.semibold(11.0), textColor: labelColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(11.0), textColor: labelColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(11.0), textColor: labelColor),
|
||||
linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
}
|
||||
)
|
||||
let labelText = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("#\(resellPrice)", attributes: attributes))
|
||||
if let range = labelText.string.range(of: "#") {
|
||||
labelText.addAttribute(NSAttributedString.Key.font, value: Font.semibold(10.0), range: NSRange(range, in: labelText.string))
|
||||
labelText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: NSRange(range, in: labelText.string))
|
||||
}
|
||||
|
||||
let resellSize = self.reselLabel.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
text: .plain(labelText),
|
||||
horizontalAlignment: .center
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let resellFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - resellSize.width) / 2.0), y: size.height - 20.0), size: resellSize)
|
||||
|
||||
let resellBackground: BlurredBackgroundView
|
||||
var resellBackgroundTransition = transition
|
||||
if let currentBackground = self.resellBackground {
|
||||
resellBackground = currentBackground
|
||||
} else {
|
||||
resellBackgroundTransition = .immediate
|
||||
|
||||
resellBackground = BlurredBackgroundView(color: UIColor(rgb: 0x000000, alpha: 0.3), enableBlur: true) //UIVisualEffectView(effect: blurEffect)
|
||||
resellBackground.clipsToBounds = true
|
||||
self.resellBackground = resellBackground
|
||||
|
||||
self.addSubview(resellBackground)
|
||||
}
|
||||
let resellBackgroundFrame = resellFrame.insetBy(dx: -6.0, dy: -4.0)
|
||||
resellBackgroundTransition.setFrame(view: resellBackground, frame: resellBackgroundFrame)
|
||||
resellBackground.update(size: resellBackgroundFrame.size, cornerRadius: resellBackgroundFrame.size.height / 2.0, transition: resellBackgroundTransition.containedViewLayoutTransition)
|
||||
|
||||
if let resellLabelView = self.reselLabel.view {
|
||||
if resellLabelView.superview == nil {
|
||||
self.addSubview(resellLabelView)
|
||||
}
|
||||
transition.setFrame(view: resellLabelView, frame: resellFrame)
|
||||
}
|
||||
} else {
|
||||
self.reselLabel.view?.removeFromSuperview()
|
||||
if let resellBackground = self.resellBackground {
|
||||
self.resellBackground = nil
|
||||
resellBackground.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
if case .grid = component.mode {
|
||||
let lineWidth: CGFloat = 2.0
|
||||
let selectionFrame = backgroundFrame.insetBy(dx: 3.0, dy: 3.0)
|
||||
|
|
@ -873,17 +986,20 @@ private final class ButtonContentComponent: Component {
|
|||
let context: AccountContext
|
||||
let text: String
|
||||
let color: UIColor
|
||||
let tinted: Bool
|
||||
let starsColor: UIColor?
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
text: String,
|
||||
color: UIColor,
|
||||
tinted: Bool = false,
|
||||
starsColor: UIColor? = nil
|
||||
) {
|
||||
self.context = context
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.tinted = tinted
|
||||
self.starsColor = starsColor
|
||||
}
|
||||
|
||||
|
|
@ -897,6 +1013,9 @@ private final class ButtonContentComponent: Component {
|
|||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.tinted != rhs.tinted {
|
||||
return false
|
||||
}
|
||||
if lhs.starsColor != rhs.starsColor {
|
||||
return false
|
||||
}
|
||||
|
|
@ -930,7 +1049,7 @@ private final class ButtonContentComponent: Component {
|
|||
let attributedText = NSMutableAttributedString(string: component.text, font: Font.semibold(11.0), textColor: component.color)
|
||||
let range = (attributedText.string as NSString).range(of: "⭐️")
|
||||
if range.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: component.tinted)), range: range)
|
||||
attributedText.addAttribute(.font, value: Font.semibold(15.0), range: range)
|
||||
attributedText.addAttribute(.baselineOffset, value: 2.0, range: NSRange(location: range.upperBound, length: attributedText.length - range.upperBound))
|
||||
}
|
||||
|
|
@ -1057,3 +1176,11 @@ private final class StarsButtonEffectLayer: SimpleLayer {
|
|||
self.emitterLayer.emitterPosition = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
}
|
||||
}
|
||||
|
||||
private var ribbonOutlineImage: UIImage? = {
|
||||
if let image = UIImage(bundleImageName: "Premium/GiftRibbon") {
|
||||
return generateScaledImage(image: image, size: CGSize(width: image.size.width + 8.0, height: image.size.height + 8.0), opaque: false)?.withRenderingMode(.alwaysTemplate)
|
||||
} else {
|
||||
return UIImage()
|
||||
}
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
case all
|
||||
case limited
|
||||
case inStock
|
||||
case resale
|
||||
case stars(Int64)
|
||||
case transfer
|
||||
|
||||
|
|
@ -92,6 +93,8 @@ final class GiftOptionsScreenComponent: Component {
|
|||
self = .inStock
|
||||
case -3:
|
||||
self = .transfer
|
||||
case -4:
|
||||
self = .resale
|
||||
default:
|
||||
self = .stars(rawValue)
|
||||
}
|
||||
|
|
@ -107,6 +110,8 @@ final class GiftOptionsScreenComponent: Component {
|
|||
return -2
|
||||
case .transfer:
|
||||
return -3
|
||||
case .resale:
|
||||
return -4
|
||||
case let .stars(stars):
|
||||
return stars
|
||||
}
|
||||
|
|
@ -136,6 +141,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
private var starsItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private let tabSelector = ComponentView<Empty>()
|
||||
private var starsFilter: StarsFilter = .all
|
||||
private var switchingFilter = false
|
||||
|
||||
private var _effectiveStarGifts: ([StarGift], StarsFilter)?
|
||||
private var effectiveStarGifts: [StarGift]? {
|
||||
|
|
@ -191,6 +197,12 @@ final class GiftOptionsScreenComponent: Component {
|
|||
return true
|
||||
}
|
||||
}
|
||||
case .resale:
|
||||
if case let .generic(gift) = $0 {
|
||||
if let availability = gift.availability, availability.resale > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case .transfer:
|
||||
break
|
||||
}
|
||||
|
|
@ -216,6 +228,7 @@ final class GiftOptionsScreenComponent: Component {
|
|||
private(set) weak var state: State?
|
||||
private var environment: EnvironmentType?
|
||||
|
||||
private var tabSelectorOrigin: CGFloat = 0.0
|
||||
private var starsItemsOrigin: CGFloat = 0.0
|
||||
|
||||
private var dismissed = false
|
||||
|
|
@ -355,12 +368,28 @@ final class GiftOptionsScreenComponent: Component {
|
|||
|
||||
var ribbon: GiftItemComponent.Ribbon?
|
||||
var isSoldOut = false
|
||||
if case let .generic(gift) = gift {
|
||||
if let _ = gift.soldOut {
|
||||
switch gift {
|
||||
case let .generic(gift):
|
||||
if let availability = gift.availability, availability.resale > 0 {
|
||||
//TODO:localize
|
||||
//TODO:unmock
|
||||
ribbon = GiftItemComponent.Ribbon(
|
||||
text: environment.strings.Gift_Options_Gift_SoldOut,
|
||||
color: .red
|
||||
text: "resale",
|
||||
color: .green
|
||||
)
|
||||
} else if let _ = gift.soldOut {
|
||||
if let availability = gift.availability, availability.resale > 0 {
|
||||
//TODO:localize
|
||||
ribbon = GiftItemComponent.Ribbon(
|
||||
text: "resale",
|
||||
color: .green
|
||||
)
|
||||
} else {
|
||||
ribbon = GiftItemComponent.Ribbon(
|
||||
text: environment.strings.Gift_Options_Gift_SoldOut,
|
||||
color: .red
|
||||
)
|
||||
}
|
||||
isSoldOut = true
|
||||
} else if let _ = gift.availability {
|
||||
ribbon = GiftItemComponent.Ribbon(
|
||||
|
|
@ -368,14 +397,31 @@ final class GiftOptionsScreenComponent: Component {
|
|||
color: .blue
|
||||
)
|
||||
}
|
||||
case let .unique(gift):
|
||||
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
|
||||
for attribute in gift.attributes {
|
||||
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
|
||||
ribbonColor = .custom(outerColor, innerColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
ribbon = GiftItemComponent.Ribbon(
|
||||
text: "#\(gift.number)",
|
||||
font: .monospaced,
|
||||
color: ribbonColor
|
||||
)
|
||||
}
|
||||
|
||||
let subject: GiftItemComponent.Subject
|
||||
switch gift {
|
||||
case let .generic(gift):
|
||||
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
||||
if let availability = gift.availability, let minResaleStars = availability.minResaleStars {
|
||||
subject = .starGift(gift: gift, price: "⭐️ \(minResaleStars)+")
|
||||
} else {
|
||||
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
||||
}
|
||||
case let .unique(gift):
|
||||
subject = .uniqueGift(gift: gift)
|
||||
subject = .uniqueGift(gift: gift, price: nil)
|
||||
}
|
||||
|
||||
let _ = visibleItem.update(
|
||||
|
|
@ -404,13 +450,27 @@ final class GiftOptionsScreenComponent: Component {
|
|||
mainController = controller
|
||||
}
|
||||
if case let .generic(gift) = gift {
|
||||
if gift.availability?.remains == 0 {
|
||||
let giftController = GiftViewScreen(
|
||||
context: component.context,
|
||||
subject: .soldOutGift(gift)
|
||||
)
|
||||
mainController.push(giftController)
|
||||
} else {
|
||||
var forceStore = !"".isEmpty
|
||||
#if DEBUG
|
||||
forceStore = true
|
||||
#endif
|
||||
|
||||
if let availability = gift.availability, availability.remains == 0 || (availability.resale > 0 && forceStore) {
|
||||
if availability.resale > 0 {
|
||||
let storeController = component.context.sharedContext.makeGiftStoreController(
|
||||
context: component.context,
|
||||
peerId: component.peerId,
|
||||
gift: gift
|
||||
)
|
||||
mainController.push(storeController)
|
||||
} else {
|
||||
let giftController = GiftViewScreen(
|
||||
context: component.context,
|
||||
subject: .soldOutGift(gift)
|
||||
)
|
||||
mainController.push(giftController)
|
||||
}
|
||||
} else {
|
||||
var forceUnique = false
|
||||
if let disallowedGifts = self.state?.disallowedGifts, disallowedGifts.contains(.limited) && !disallowedGifts.contains(.unique) {
|
||||
forceUnique = true
|
||||
|
|
@ -475,6 +535,53 @@ final class GiftOptionsScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var topPanelHeight = environment.navigationHeight
|
||||
let tabSelectorThreshold = self.tabSelectorOrigin - 8.0
|
||||
if contentOffset > tabSelectorThreshold - environment.navigationHeight {
|
||||
topPanelHeight += 39.0
|
||||
}
|
||||
|
||||
if let tabSelectorView = self.tabSelector.view {
|
||||
let tabSelectorSize = tabSelectorView.bounds.size
|
||||
transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableWidth - tabSelectorSize.width) / 2.0), y: max(56.0, self.tabSelectorOrigin - contentOffset)), size: tabSelectorSize))
|
||||
}
|
||||
|
||||
var panelTransition = transition
|
||||
if self.topPanel.view?.superview != nil && !self.switchingFilter {
|
||||
panelTransition = .spring(duration: 0.3)
|
||||
}
|
||||
let topPanelSize = self.topPanel.update(
|
||||
transition: panelTransition,
|
||||
component: AnyComponent(BlurredBackgroundComponent(
|
||||
color: environment.theme.rootController.navigationBar.blurredBackgroundColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: topPanelHeight)
|
||||
)
|
||||
|
||||
let topSeparatorSize = self.topSeparator.update(
|
||||
transition: panelTransition,
|
||||
component: AnyComponent(Rectangle(
|
||||
color: environment.theme.rootController.navigationBar.separatorColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableWidth, height: UIScreenPixel)
|
||||
)
|
||||
let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableWidth, height: topPanelSize.height))
|
||||
let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height))
|
||||
if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view {
|
||||
if topPanelView.superview == nil {
|
||||
if let headerView = self.header.view {
|
||||
self.insertSubview(topSeparatorView, aboveSubview: headerView)
|
||||
self.insertSubview(topPanelView, aboveSubview: headerView)
|
||||
}
|
||||
}
|
||||
panelTransition.setFrame(view: topPanelView, frame: topPanelFrame)
|
||||
panelTransition.setFrame(view: topSeparatorView, frame: topSeparatorFrame)
|
||||
}
|
||||
|
||||
let bottomContentOffset = max(0.0, self.scrollView.contentSize.height - self.scrollView.contentOffset.y - self.scrollView.frame.height)
|
||||
if interactive, bottomContentOffset < 320.0, case .transfer = self.starsFilter {
|
||||
self.state?.starGiftsContext.loadMore()
|
||||
|
|
@ -755,34 +862,34 @@ final class GiftOptionsScreenComponent: Component {
|
|||
}
|
||||
transition.setBounds(view: headerView, bounds: CGRect(origin: .zero, size: headerSize))
|
||||
}
|
||||
|
||||
let topPanelSize = self.topPanel.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(BlurredBackgroundComponent(
|
||||
color: theme.rootController.navigationBar.blurredBackgroundColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: environment.navigationHeight)
|
||||
)
|
||||
|
||||
let topSeparatorSize = self.topSeparator.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Rectangle(
|
||||
color: theme.rootController.navigationBar.separatorColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: UIScreenPixel)
|
||||
)
|
||||
let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: topPanelSize.height))
|
||||
let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height))
|
||||
if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view {
|
||||
if topPanelView.superview == nil {
|
||||
self.addSubview(topPanelView)
|
||||
self.addSubview(topSeparatorView)
|
||||
}
|
||||
transition.setFrame(view: topPanelView, frame: topPanelFrame)
|
||||
transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame)
|
||||
}
|
||||
|
||||
// let topPanelSize = self.topPanel.update(
|
||||
// transition: transition,
|
||||
// component: AnyComponent(BlurredBackgroundComponent(
|
||||
// color: theme.rootController.navigationBar.blurredBackgroundColor
|
||||
// )),
|
||||
// environment: {},
|
||||
// containerSize: CGSize(width: availableSize.width, height: environment.navigationHeight)
|
||||
// )
|
||||
//
|
||||
// let topSeparatorSize = self.topSeparator.update(
|
||||
// transition: transition,
|
||||
// component: AnyComponent(Rectangle(
|
||||
// color: theme.rootController.navigationBar.separatorColor
|
||||
// )),
|
||||
// environment: {},
|
||||
// containerSize: CGSize(width: availableSize.width, height: UIScreenPixel)
|
||||
// )
|
||||
// let topPanelFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: topPanelSize.height))
|
||||
// let topSeparatorFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelSize.height), size: CGSize(width: topSeparatorSize.width, height: topSeparatorSize.height))
|
||||
// if let topPanelView = self.topPanel.view, let topSeparatorView = self.topSeparator.view {
|
||||
// if topPanelView.superview == nil {
|
||||
// self.addSubview(topPanelView)
|
||||
// self.addSubview(topSeparatorView)
|
||||
// }
|
||||
// transition.setFrame(view: topPanelView, frame: topPanelFrame)
|
||||
// transition.setFrame(view: topSeparatorView, frame: topSeparatorFrame)
|
||||
// }
|
||||
|
||||
let cancelButtonSize = self.cancelButton.update(
|
||||
transition: transition,
|
||||
|
|
@ -1186,13 +1293,17 @@ final class GiftOptionsScreenComponent: Component {
|
|||
}
|
||||
|
||||
var hasLimited = false
|
||||
var hasResale = false
|
||||
var starsAmountsSet = Set<Int64>()
|
||||
if let starGifts = self.state?.starGifts {
|
||||
for gift in starGifts {
|
||||
if case let .generic(gift) = gift {
|
||||
starsAmountsSet.insert(gift.price)
|
||||
if gift.availability != nil {
|
||||
if let availability = gift.availability {
|
||||
hasLimited = true
|
||||
if availability.resale > 0 {
|
||||
hasResale = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1210,6 +1321,14 @@ final class GiftOptionsScreenComponent: Component {
|
|||
title: strings.Gift_Options_Gift_Filter_InStock
|
||||
))
|
||||
|
||||
if hasResale {
|
||||
//TODO:localize
|
||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||
id: AnyHashable(StarsFilter.resale.rawValue),
|
||||
title: "Resale"
|
||||
))
|
||||
}
|
||||
|
||||
let starsAmounts = Array(starsAmountsSet).sorted()
|
||||
for amount in starsAmounts {
|
||||
tabSelectorItems.append(TabSelectorComponent.Item(
|
||||
|
|
@ -1235,17 +1354,26 @@ final class GiftOptionsScreenComponent: Component {
|
|||
}
|
||||
let starsFilter = StarsFilter(rawValue: idValue)
|
||||
if self.starsFilter != starsFilter {
|
||||
if self.scrollView.contentOffset.y > self.tabSelectorOrigin - 56.0 {
|
||||
self.scrollView.setContentOffset(CGPoint(x: 0.0, y: self.tabSelectorOrigin - 56.0), animated: true)
|
||||
}
|
||||
|
||||
self.switchingFilter = true
|
||||
self.starsFilter = starsFilter
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
Queue.mainQueue().after(0.1, {
|
||||
self.switchingFilter = false
|
||||
})
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 10.0 * 2.0, height: 50.0)
|
||||
)
|
||||
self.tabSelectorOrigin = contentHeight
|
||||
if let tabSelectorView = self.tabSelector.view {
|
||||
if tabSelectorView.superview == nil {
|
||||
self.scrollView.addSubview(tabSelectorView)
|
||||
self.addSubview(tabSelectorView)
|
||||
}
|
||||
transition.setFrame(view: tabSelectorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabSelectorSize.width) / 2.0), y: contentHeight), size: tabSelectorSize))
|
||||
}
|
||||
|
|
|
|||
52
submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD
Normal file
52
submodules/TelegramUI/Components/Gifts/GiftStoreScreen/BUILD
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "GiftStoreScreen",
|
||||
module_name = "GiftStoreScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SheetComponent",
|
||||
"//submodules/UndoUI",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramUI/Components/ScrollComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftItemComponent",
|
||||
"//submodules/ConfettiEffect",
|
||||
"//submodules/InAppPurchaseManager",
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftSetupScreen",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftViewScreen",
|
||||
"//submodules/TelegramUI/Components/LottieComponent",
|
||||
"//submodules/TelegramUI/Components/TextFieldComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import PlainButtonComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import BundleIconComponent
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
|
||||
public final class FilterSelectorComponent: Component {
|
||||
public struct Colors: Equatable {
|
||||
public var foreground: UIColor
|
||||
public var background: UIColor
|
||||
|
||||
public init(
|
||||
foreground: UIColor,
|
||||
background: UIColor
|
||||
) {
|
||||
self.foreground = foreground
|
||||
self.background = background
|
||||
}
|
||||
}
|
||||
|
||||
public struct Item: Equatable {
|
||||
public var id: AnyHashable
|
||||
public var iconName: String?
|
||||
public var title: String
|
||||
public var action: (UIView) -> Void
|
||||
|
||||
public init(
|
||||
id: AnyHashable,
|
||||
iconName: String? = nil,
|
||||
title: String,
|
||||
action: @escaping (UIView) -> Void
|
||||
) {
|
||||
self.id = id
|
||||
self.iconName = iconName
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
return lhs.id == rhs.id && lhs.iconName == rhs.iconName && lhs.title == rhs.title
|
||||
}
|
||||
}
|
||||
|
||||
public let context: AccountContext?
|
||||
public let colors: Colors
|
||||
public let items: [Item]
|
||||
|
||||
public init(
|
||||
context: AccountContext? = nil,
|
||||
colors: Colors,
|
||||
items: [Item]
|
||||
) {
|
||||
self.context = context
|
||||
self.colors = colors
|
||||
self.items = items
|
||||
}
|
||||
|
||||
public static func ==(lhs: FilterSelectorComponent, rhs: FilterSelectorComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.colors != rhs.colors {
|
||||
return false
|
||||
}
|
||||
if lhs.items != rhs.items {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private final class VisibleItem {
|
||||
let title = ComponentView<Empty>()
|
||||
|
||||
init() {
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: UIScrollView {
|
||||
private var component: FilterSelectorComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private var visibleItems: [AnyHashable: VisibleItem] = [:]
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.showsVerticalScrollIndicator = false
|
||||
self.showsHorizontalScrollIndicator = false
|
||||
self.scrollsToTop = false
|
||||
self.delaysContentTouches = false
|
||||
self.canCancelContentTouches = true
|
||||
self.contentInsetAdjustmentBehavior = .never
|
||||
self.alwaysBounceVertical = false
|
||||
self.clipsToBounds = false
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override public func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func update(component: FilterSelectorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let baseHeight: CGFloat = 28.0
|
||||
|
||||
var spacing: CGFloat = 6.0
|
||||
|
||||
let itemFont = Font.semibold(14.0)
|
||||
let allowScroll = true
|
||||
|
||||
var innerContentWidth: CGFloat = 0.0
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
var index = 0
|
||||
var itemViews: [AnyHashable: (VisibleItem, CGSize, ComponentTransition)] = [:]
|
||||
|
||||
for item in component.items {
|
||||
var itemTransition = transition
|
||||
let itemView: VisibleItem
|
||||
if let current = self.visibleItems[item.id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = VisibleItem()
|
||||
self.visibleItems[item.id] = itemView
|
||||
itemTransition = itemTransition.withAnimation(.none)
|
||||
}
|
||||
|
||||
let itemId = item.id
|
||||
validIds.append(itemId)
|
||||
|
||||
let itemSize = itemView.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(ItemComponent(
|
||||
context: component.context,
|
||||
iconName: item.iconName,
|
||||
text: item.title,
|
||||
font: itemFont,
|
||||
color: component.colors.foreground,
|
||||
backgroundColor: component.colors.background
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
minSize: nil,
|
||||
action: { [weak itemView] in
|
||||
if let view = itemView?.title.view {
|
||||
item.action(view)
|
||||
}
|
||||
},
|
||||
animateScale: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 100.0)
|
||||
)
|
||||
innerContentWidth += itemSize.width
|
||||
itemViews[item.id] = (itemView, itemSize, itemTransition)
|
||||
index += 1
|
||||
}
|
||||
|
||||
let estimatedContentWidth = 2.0 * spacing + innerContentWidth + (CGFloat(component.items.count - 1) * spacing)
|
||||
if estimatedContentWidth > availableSize.width && !allowScroll {
|
||||
spacing = (availableSize.width - innerContentWidth) / CGFloat(component.items.count + 1)
|
||||
}
|
||||
|
||||
var contentWidth: CGFloat = spacing
|
||||
for item in component.items {
|
||||
guard let (itemView, itemSize, itemTransition) = itemViews[item.id] else {
|
||||
continue
|
||||
}
|
||||
if contentWidth > spacing {
|
||||
contentWidth += spacing
|
||||
}
|
||||
let itemFrame = CGRect(origin: CGPoint(x: contentWidth, y: floor((baseHeight - itemSize.height) * 0.5)), size: itemSize)
|
||||
contentWidth = itemFrame.maxX
|
||||
|
||||
if let itemTitleView = itemView.title.view {
|
||||
if itemTitleView.superview == nil {
|
||||
itemTitleView.layer.anchorPoint = CGPoint()
|
||||
self.addSubview(itemTitleView)
|
||||
}
|
||||
itemTransition.setPosition(view: itemTitleView, position: itemFrame.origin)
|
||||
itemTransition.setBounds(view: itemTitleView, bounds: CGRect(origin: CGPoint(), size: itemFrame.size))
|
||||
}
|
||||
}
|
||||
contentWidth += spacing
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.visibleItems {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
itemView.title.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.visibleItems.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
self.contentSize = CGSize(width: contentWidth, height: baseHeight)
|
||||
self.disablesInteractiveTransitionGestureRecognizer = contentWidth > availableSize.width
|
||||
|
||||
return CGSize(width: min(contentWidth, availableSize.width), height: baseHeight)
|
||||
}
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
extension CGRect {
|
||||
func interpolate(with other: CGRect, fraction: CGFloat) -> CGRect {
|
||||
return CGRect(
|
||||
x: self.origin.x * (1.0 - fraction) + (other.origin.x) * fraction,
|
||||
y: self.origin.y * (1.0 - fraction) + (other.origin.y) * fraction,
|
||||
width: self.size.width * (1.0 - fraction) + (other.size.width) * fraction,
|
||||
height: self.size.height * (1.0 - fraction) + (other.size.height) * fraction
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ItemComponent: CombinedComponent {
|
||||
let context: AccountContext?
|
||||
let iconName: String?
|
||||
let text: String
|
||||
let font: UIFont
|
||||
let color: UIColor
|
||||
let backgroundColor: UIColor
|
||||
|
||||
init(
|
||||
context: AccountContext?,
|
||||
iconName: String?,
|
||||
text: String,
|
||||
font: UIFont,
|
||||
color: UIColor,
|
||||
backgroundColor: UIColor
|
||||
) {
|
||||
self.context = context
|
||||
self.iconName = iconName
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.color = color
|
||||
self.backgroundColor = backgroundColor
|
||||
}
|
||||
|
||||
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.font != rhs.font {
|
||||
return false
|
||||
}
|
||||
if lhs.color != rhs.color {
|
||||
return false
|
||||
}
|
||||
if lhs.backgroundColor != rhs.backgroundColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let title = Child(MultilineTextWithEntitiesComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
|
||||
let attributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.color)
|
||||
let range = (attributedTitle.string as NSString).range(of: "⭐️")
|
||||
if range.location != NSNotFound {
|
||||
attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range)
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context?.animationCache,
|
||||
animationRenderer: component.context?.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
text: .plain(attributedTitle)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: component.iconName ?? "Item List/ExpandableSelectorArrows",
|
||||
tintColor: component.color,
|
||||
maxSize: component.iconName != nil ? CGSize(width: 22.0, height: 22.0) : nil
|
||||
),
|
||||
availableSize: CGSize(width: 100, height: 100),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let padding: CGFloat = 12.0
|
||||
var leftPadding = padding
|
||||
if let _ = component.iconName {
|
||||
leftPadding -= 4.0
|
||||
}
|
||||
let spacing: CGFloat = 4.0
|
||||
let totalWidth = title.size.width + icon.size.width + spacing
|
||||
let size = CGSize(width: totalWidth + leftPadding + padding, height: 28.0)
|
||||
let background = background.update(
|
||||
component: RoundedRectangle(
|
||||
color: component.backgroundColor,
|
||||
cornerRadius: 14.0
|
||||
),
|
||||
availableSize: size,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(background
|
||||
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
if let _ = component.iconName {
|
||||
context.add(title
|
||||
.position(CGPoint(x: size.width - padding - title.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: leftPadding + icon.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
} else {
|
||||
context.add(title
|
||||
.position(CGPoint(x: padding + title.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: size.width - padding - icon.size.width / 2.0, y: size.height / 2.0))
|
||||
)
|
||||
}
|
||||
return size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,509 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import ContextUI
|
||||
|
||||
final class GiftAttributeListContextItem: ContextMenuCustomItem {
|
||||
let context: AccountContext
|
||||
let attributes: [StarGift.UniqueGift.Attribute]
|
||||
let selectedAttributes: [ResaleGiftsContext.Attribute]
|
||||
let attributeCount: [ResaleGiftsContext.Attribute: Int32]
|
||||
let searchQuery: Signal<String, NoError>
|
||||
let attributeSelected: (ResaleGiftsContext.Attribute, Bool) -> Void
|
||||
let selectAll: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
attributes: [StarGift.UniqueGift.Attribute],
|
||||
selectedAttributes: [ResaleGiftsContext.Attribute],
|
||||
attributeCount: [ResaleGiftsContext.Attribute: Int32],
|
||||
searchQuery: Signal<String, NoError>,
|
||||
attributeSelected: @escaping (ResaleGiftsContext.Attribute, Bool) -> Void,
|
||||
selectAll: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.attributes = attributes
|
||||
self.selectedAttributes = selectedAttributes
|
||||
self.attributeCount = attributeCount
|
||||
self.searchQuery = searchQuery
|
||||
self.attributeSelected = attributeSelected
|
||||
self.selectAll = selectAll
|
||||
}
|
||||
|
||||
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return GiftAttributeListContextItemNode(
|
||||
presentationData: presentationData,
|
||||
item: self,
|
||||
getController: getController,
|
||||
actionSelected: actionSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func actionForAttribute(attribute: StarGift.UniqueGift.Attribute, presentationData: PresentationData, selectedAttributes: Set<ResaleGiftsContext.Attribute>, searchQuery: String, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?) -> ContextMenuActionItem? {
|
||||
let searchComponents = searchQuery.lowercased().components(separatedBy: .whitespaces).filter { !$0.isEmpty }
|
||||
|
||||
switch attribute {
|
||||
case let .model(name, file, _), let .pattern(name, file, _):
|
||||
let attributeId: ResaleGiftsContext.Attribute
|
||||
if case .model = attribute {
|
||||
attributeId = .model(file.fileId.id)
|
||||
} else {
|
||||
attributeId = .pattern(file.fileId.id)
|
||||
}
|
||||
let isSelected = selectedAttributes.isEmpty || selectedAttributes.contains(attributeId)
|
||||
|
||||
var entities: [MessageTextEntity] = []
|
||||
var entityFiles: [Int64: TelegramMediaFile] = [:]
|
||||
entities = [
|
||||
MessageTextEntity(
|
||||
range: 0..<1,
|
||||
type: .CustomEmoji(stickerPack: nil, fileId: file.fileId.id)
|
||||
)
|
||||
]
|
||||
entityFiles[file.fileId.id] = file
|
||||
|
||||
var title = "# \(name)"
|
||||
var count = ""
|
||||
if let counter = item.attributeCount[.model(file.fileId.id)] {
|
||||
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
entities.append(
|
||||
MessageTextEntity(
|
||||
range: title.count ..< title.count + count.count,
|
||||
type: .Italic
|
||||
)
|
||||
)
|
||||
title += count
|
||||
}
|
||||
|
||||
let words = title.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
|
||||
var wordStartIndices: [String.Index] = []
|
||||
var currentIndex = title.startIndex
|
||||
|
||||
for word in words {
|
||||
while currentIndex < title.endIndex {
|
||||
let range = title.range(of: word, range: currentIndex..<title.endIndex)
|
||||
if let range = range {
|
||||
wordStartIndices.append(range.lowerBound)
|
||||
currentIndex = range.upperBound
|
||||
break
|
||||
}
|
||||
currentIndex = title.index(after: currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
for (wordIndex, word) in words.enumerated() {
|
||||
let lowercaseWord = word.lowercased()
|
||||
for component in searchComponents {
|
||||
if lowercaseWord.hasPrefix(component) {
|
||||
let startIndex = wordStartIndices[wordIndex]
|
||||
let prefixRange = startIndex..<title.index(startIndex, offsetBy: min(component.count, word.count))
|
||||
|
||||
entities.append(
|
||||
MessageTextEntity(
|
||||
range: title.distance(from: title.startIndex, to: prefixRange.lowerBound)..<title.distance(from: title.startIndex, to: prefixRange.upperBound),
|
||||
type: .Bold
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ContextMenuActionItem(text: title, entities: entities, entityFiles: entityFiles, enableEntityAnimations: false, parseMarkdown: true, icon: { theme in
|
||||
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, action: { _, f in
|
||||
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
|
||||
item.attributeSelected(attributeId, false)
|
||||
}, longPressAction: { _, f in
|
||||
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
|
||||
item.attributeSelected(attributeId, true)
|
||||
})
|
||||
case let .backdrop(name, id, innerColor, outerColor, _, _, _):
|
||||
let attributeId: ResaleGiftsContext.Attribute = .backdrop(id)
|
||||
let isSelected = selectedAttributes.isEmpty || selectedAttributes.contains(attributeId)
|
||||
|
||||
var entities: [MessageTextEntity] = []
|
||||
var title = " \(name)"
|
||||
var count = ""
|
||||
if let counter = item.attributeCount[attributeId] {
|
||||
count = " \(presentationStringsFormattedNumber(counter, presentationData.dateTimeFormat.groupingSeparator))"
|
||||
entities.append(
|
||||
MessageTextEntity(range: title.count ..< title.count + count.count, type: .Italic)
|
||||
)
|
||||
title += count
|
||||
}
|
||||
|
||||
let words = title.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty }
|
||||
var wordStartIndices: [String.Index] = []
|
||||
var currentIndex = title.startIndex
|
||||
|
||||
for word in words {
|
||||
while currentIndex < title.endIndex {
|
||||
let range = title.range(of: word, range: currentIndex..<title.endIndex)
|
||||
if let range = range {
|
||||
wordStartIndices.append(range.lowerBound)
|
||||
currentIndex = range.upperBound
|
||||
break
|
||||
}
|
||||
currentIndex = title.index(after: currentIndex)
|
||||
}
|
||||
}
|
||||
|
||||
for (wordIndex, word) in words.enumerated() {
|
||||
let lowercaseWord = word.lowercased()
|
||||
for component in searchComponents {
|
||||
if lowercaseWord.hasPrefix(component) {
|
||||
let startIndex = wordStartIndices[wordIndex]
|
||||
let prefixRange = startIndex..<title.index(startIndex, offsetBy: min(component.count, word.count))
|
||||
|
||||
entities.append(
|
||||
MessageTextEntity(
|
||||
range: title.distance(from: title.startIndex, to: prefixRange.lowerBound)..<title.distance(from: title.startIndex, to: prefixRange.upperBound),
|
||||
type: .Bold
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ContextMenuActionItem(text: title, entities: entities, icon: { theme in
|
||||
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
|
||||
}, additionalLeftIcon: { _ in
|
||||
return generateGradientFilledCircleImage(diameter: 24.0, colors: [UIColor(rgb: UInt32(bitPattern: innerColor)).cgColor, UIColor(rgb: UInt32(bitPattern: outerColor)).cgColor])
|
||||
}, action: { _, f in
|
||||
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
|
||||
item.attributeSelected(attributeId, false)
|
||||
}, longPressAction: { _, f in
|
||||
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
|
||||
item.attributeSelected(attributeId, true)
|
||||
})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private final class GiftAttributeListContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, ASScrollViewDelegate {
|
||||
private let item: GiftAttributeListContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let scrollNode: ASScrollNode
|
||||
private let actionNodes: [ContextControllerActionsListActionItemNode]
|
||||
private let separatorNodes: [ASDisplayNode]
|
||||
|
||||
private var searchDisposable: Disposable?
|
||||
private var searchQuery = ""
|
||||
|
||||
init(presentationData: PresentationData, item: GiftAttributeListContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
|
||||
var actionNodes: [ContextControllerActionsListActionItemNode] = []
|
||||
var separatorNodes: [ASDisplayNode] = []
|
||||
|
||||
let selectedAttributes = Set(item.selectedAttributes)
|
||||
|
||||
//TODO:localize
|
||||
let selectAllAction = ContextMenuActionItem(text: "Select All", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Select"), color: theme.contextMenu.primaryColor)
|
||||
}, iconPosition: .left, action: { _, f in
|
||||
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
|
||||
|
||||
item.selectAll()
|
||||
})
|
||||
|
||||
let selectAllActionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: selectAllAction)
|
||||
actionNodes.append(selectAllActionNode)
|
||||
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
separatorNodes.append(separatorNode)
|
||||
|
||||
for attribute in item.attributes {
|
||||
guard let action = actionForAttribute(attribute: attribute, presentationData: presentationData, selectedAttributes: selectedAttributes, searchQuery: self.searchQuery, item: item, getController: getController) else {
|
||||
continue
|
||||
}
|
||||
let actionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: action)
|
||||
actionNodes.append(actionNode)
|
||||
if actionNodes.count != item.attributes.count {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.backgroundColor = presentationData.theme.contextMenu.itemSeparatorColor
|
||||
separatorNodes.append(separatorNode)
|
||||
}
|
||||
}
|
||||
|
||||
let nopAction: ((ContextControllerProtocol?, @escaping (ContextMenuActionResult) -> Void) -> Void)? = nil
|
||||
let emptyResultsAction = ContextMenuActionItem(text: "No Results", textFont: .small, icon: { _ in return nil }, action: nopAction)
|
||||
let emptyResultsActionNode = ContextControllerActionsListActionItemNode(context: item.context, getController: getController, requestDismiss: actionSelected, requestUpdateAction: { _, _ in }, item: emptyResultsAction)
|
||||
actionNodes.append(emptyResultsActionNode)
|
||||
|
||||
self.actionNodes = actionNodes
|
||||
self.separatorNodes = separatorNodes
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.scrollNode)
|
||||
for separatorNode in self.separatorNodes {
|
||||
self.scrollNode.addSubnode(separatorNode)
|
||||
}
|
||||
for actionNode in self.actionNodes {
|
||||
self.scrollNode.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
self.searchDisposable = (item.searchQuery
|
||||
|> deliverOnMainQueue).start(next: { [weak self] searchQuery in
|
||||
guard let self, self.searchQuery != searchQuery else {
|
||||
return
|
||||
}
|
||||
self.searchQuery = searchQuery.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
var i = 1
|
||||
for attribute in item.attributes {
|
||||
guard let action = actionForAttribute(attribute: attribute, presentationData: presentationData, selectedAttributes: selectedAttributes, searchQuery: self.searchQuery, item: item, getController: getController) else {
|
||||
continue
|
||||
}
|
||||
self.actionNodes[i].setItem(item: action)
|
||||
i += 1
|
||||
}
|
||||
self.getController()?.requestLayout(transition: .immediate)
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.searchDisposable?.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.scrollNode.view.delegate = self.wrappedScrollViewDelegate
|
||||
self.scrollNode.view.alwaysBounceVertical = false
|
||||
self.scrollNode.view.showsHorizontalScrollIndicator = false
|
||||
self.scrollNode.view.scrollIndicatorInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 5.0, right: 0.0)
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let minActionsWidth: CGFloat = 250.0
|
||||
let maxActionsWidth: CGFloat = 300.0
|
||||
let constrainedWidth = min(constrainedWidth, maxActionsWidth)
|
||||
var maxWidth: CGFloat = 0.0
|
||||
var contentHeight: CGFloat = 0.0
|
||||
var heightsAndCompletions: [(Int, CGFloat, (CGSize, ContainedViewLayoutTransition) -> Void)] = []
|
||||
|
||||
|
||||
let effectiveAttributes: [StarGift.UniqueGift.Attribute]
|
||||
if self.searchQuery.isEmpty {
|
||||
effectiveAttributes = self.item.attributes
|
||||
} else {
|
||||
effectiveAttributes = filteredAttributes(attributes: self.item.attributes, query: self.searchQuery)
|
||||
}
|
||||
let visibleAttributes = Set(effectiveAttributes.map { attribute -> AnyHashable in
|
||||
switch attribute {
|
||||
case let .model(_, file, _):
|
||||
return file.fileId.id
|
||||
case let .pattern(_, file, _):
|
||||
return file.fileId.id
|
||||
case let .backdrop(_, id, _, _, _, _, _):
|
||||
return id
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
})
|
||||
|
||||
for i in 0 ..< self.actionNodes.count {
|
||||
let itemNode = self.actionNodes[i]
|
||||
if !self.searchQuery.isEmpty && i == 0 {
|
||||
itemNode.isHidden = true
|
||||
continue
|
||||
}
|
||||
|
||||
if i > 0 && i < self.actionNodes.count - 1 {
|
||||
let attribute = self.item.attributes[i - 1]
|
||||
let attributeId: AnyHashable
|
||||
switch attribute {
|
||||
case let .model(_, file, _):
|
||||
attributeId = AnyHashable(file.fileId.id)
|
||||
case let .pattern(_, file, _):
|
||||
attributeId = AnyHashable(file.fileId.id)
|
||||
case let .backdrop(_, id, _, _, _, _, _):
|
||||
attributeId = AnyHashable(id)
|
||||
default:
|
||||
fatalError()
|
||||
}
|
||||
if !visibleAttributes.contains(attributeId) {
|
||||
itemNode.isHidden = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if i == self.actionNodes.count - 1 {
|
||||
if !visibleAttributes.isEmpty {
|
||||
itemNode.isHidden = true
|
||||
continue
|
||||
} else {
|
||||
}
|
||||
}
|
||||
itemNode.isHidden = false
|
||||
|
||||
let (minSize, complete) = itemNode.update(presentationData: self.presentationData, constrainedSize: CGSize(width: constrainedWidth, height: constrainedHeight))
|
||||
maxWidth = max(maxWidth, minSize.width)
|
||||
heightsAndCompletions.append((i, minSize.height, complete))
|
||||
contentHeight += minSize.height
|
||||
}
|
||||
|
||||
maxWidth = max(maxWidth, minActionsWidth)
|
||||
|
||||
let maxHeight: CGFloat = min(360.0, constrainedHeight - 108.0)
|
||||
|
||||
return (CGSize(width: maxWidth, height: min(maxHeight, contentHeight)), { size, transition in
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
for (i, itemHeight, itemCompletion) in heightsAndCompletions {
|
||||
let itemNode = self.actionNodes[i]
|
||||
|
||||
let itemSize = CGSize(width: maxWidth, height: itemHeight)
|
||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: itemSize))
|
||||
itemCompletion(itemSize, transition)
|
||||
verticalOffset += itemHeight
|
||||
|
||||
if i < self.actionNodes.count - 2 {
|
||||
let separatorNode = self.separatorNodes[i]
|
||||
separatorNode.frame = CGRect(x: 0, y: verticalOffset, width: size.width, height: UIScreenPixel)
|
||||
}
|
||||
}
|
||||
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.scrollNode.view.contentSize = CGSize(width: size.width, height: contentHeight)
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
|
||||
}
|
||||
|
||||
var isActionEnabled: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return self.isActionEnabled
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
self.setIsHighlighted(isHighlighted)
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
// for actionNode in self.actionNodes {
|
||||
// let frame = actionNode.convert(actionNode.bounds, to: self)
|
||||
// if frame.contains(point) {
|
||||
// return actionNode
|
||||
// }
|
||||
// }
|
||||
return self
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateIsHighlighted(isHighlighted: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func stringTokens(_ string: String) -> [ValueBoxKey] {
|
||||
let nsString = string.folding(options: .diacriticInsensitive, locale: .current).lowercased() as NSString
|
||||
|
||||
let flag = UInt(kCFStringTokenizerUnitWord)
|
||||
let tokenizer = CFStringTokenizerCreate(kCFAllocatorDefault, nsString, CFRangeMake(0, nsString.length), flag, CFLocaleCopyCurrent())
|
||||
var tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
|
||||
var tokens: [ValueBoxKey] = []
|
||||
|
||||
var addedTokens = Set<ValueBoxKey>()
|
||||
while tokenType != [] {
|
||||
let currentTokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer)
|
||||
|
||||
if currentTokenRange.location >= 0 && currentTokenRange.length != 0 {
|
||||
let token = ValueBoxKey(length: currentTokenRange.length * 2)
|
||||
nsString.getCharacters(token.memory.assumingMemoryBound(to: unichar.self), range: NSMakeRange(currentTokenRange.location, currentTokenRange.length))
|
||||
if !addedTokens.contains(token) {
|
||||
tokens.append(token)
|
||||
addedTokens.insert(token)
|
||||
}
|
||||
}
|
||||
tokenType = CFStringTokenizerAdvanceToNextToken(tokenizer)
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
private func matchStringTokens(_ tokens: [ValueBoxKey], with other: [ValueBoxKey]) -> Bool {
|
||||
if other.isEmpty {
|
||||
return false
|
||||
} else if other.count == 1 {
|
||||
let otherToken = other[0]
|
||||
for token in tokens {
|
||||
if otherToken.isPrefix(to: token) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for otherToken in other {
|
||||
var found = false
|
||||
for token in tokens {
|
||||
if otherToken.isPrefix(to: token) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func filteredAttributes(attributes: [StarGift.UniqueGift.Attribute], query: String) -> [StarGift.UniqueGift.Attribute] {
|
||||
let queryTokens = stringTokens(query.lowercased())
|
||||
|
||||
var result: [StarGift.UniqueGift.Attribute] = []
|
||||
for attribute in attributes {
|
||||
let string: String
|
||||
switch attribute {
|
||||
case let .model(name, _, _):
|
||||
string = name
|
||||
case let .pattern(name, _, _):
|
||||
string = name
|
||||
case let .backdrop(name, _, _, _, _, _, _):
|
||||
string = name
|
||||
default:
|
||||
continue
|
||||
}
|
||||
let tokens = stringTokens(string)
|
||||
if matchStringTokens(tokens, with: queryTokens) {
|
||||
result.append(attribute)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,186 @@
|
|||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
|
||||
private final class SearchShimmerEffectNode: ASDisplayNode {
|
||||
private var currentBackgroundColor: UIColor?
|
||||
private var currentForegroundColor: UIColor?
|
||||
private let imageNodeContainer: ASDisplayNode
|
||||
private let imageNode: ASImageNode
|
||||
|
||||
private var absoluteLocation: (CGRect, CGSize)?
|
||||
private var isCurrentlyInHierarchy = false
|
||||
private var shouldBeAnimating = false
|
||||
|
||||
override init() {
|
||||
self.imageNodeContainer = ASDisplayNode()
|
||||
self.imageNodeContainer.isLayerBacked = true
|
||||
|
||||
self.imageNode = ASImageNode()
|
||||
self.imageNode.isLayerBacked = true
|
||||
self.imageNode.displaysAsynchronously = false
|
||||
self.imageNode.displayWithoutProcessing = true
|
||||
self.imageNode.contentMode = .scaleToFill
|
||||
|
||||
super.init()
|
||||
|
||||
self.isLayerBacked = true
|
||||
self.clipsToBounds = true
|
||||
|
||||
self.imageNodeContainer.addSubnode(self.imageNode)
|
||||
self.addSubnode(self.imageNodeContainer)
|
||||
}
|
||||
|
||||
override func didEnterHierarchy() {
|
||||
super.didEnterHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = true
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
override func didExitHierarchy() {
|
||||
super.didExitHierarchy()
|
||||
|
||||
self.isCurrentlyInHierarchy = false
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
func update(backgroundColor: UIColor, foregroundColor: UIColor) {
|
||||
if let currentBackgroundColor = self.currentBackgroundColor, currentBackgroundColor.argb == backgroundColor.argb, let currentForegroundColor = self.currentForegroundColor, currentForegroundColor.argb == foregroundColor.argb {
|
||||
return
|
||||
}
|
||||
self.currentBackgroundColor = backgroundColor
|
||||
self.currentForegroundColor = foregroundColor
|
||||
|
||||
self.imageNode.image = generateImage(CGSize(width: 4.0, height: 320.0), opaque: true, scale: 1.0, rotatedContext: { size, context in
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.clip(to: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let transparentColor = foregroundColor.withAlphaComponent(0.0).cgColor
|
||||
let peakColor = foregroundColor.cgColor
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.5, 1.0]
|
||||
let colors: [CGColor] = [transparentColor, peakColor, transparentColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
})
|
||||
}
|
||||
|
||||
func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
if let absoluteLocation = self.absoluteLocation, absoluteLocation.0 == rect && absoluteLocation.1 == containerSize {
|
||||
return
|
||||
}
|
||||
let sizeUpdated = self.absoluteLocation?.1 != containerSize
|
||||
let frameUpdated = self.absoluteLocation?.0 != rect
|
||||
self.absoluteLocation = (rect, containerSize)
|
||||
|
||||
if sizeUpdated {
|
||||
if self.shouldBeAnimating {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
self.addImageAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
if frameUpdated {
|
||||
self.imageNodeContainer.frame = CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: containerSize)
|
||||
}
|
||||
|
||||
self.updateAnimation()
|
||||
}
|
||||
|
||||
private func updateAnimation() {
|
||||
let shouldBeAnimating = self.isCurrentlyInHierarchy && self.absoluteLocation != nil
|
||||
if shouldBeAnimating != self.shouldBeAnimating {
|
||||
self.shouldBeAnimating = shouldBeAnimating
|
||||
if shouldBeAnimating {
|
||||
self.addImageAnimation()
|
||||
} else {
|
||||
self.imageNode.layer.removeAnimation(forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addImageAnimation() {
|
||||
guard let containerSize = self.absoluteLocation?.1 else {
|
||||
return
|
||||
}
|
||||
let gradientHeight: CGFloat = 250.0
|
||||
self.imageNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -gradientHeight), size: CGSize(width: containerSize.width, height: gradientHeight))
|
||||
let animation = self.imageNode.layer.makeAnimation(from: 0.0 as NSNumber, to: (containerSize.height + gradientHeight) as NSNumber, keyPath: "position.y", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: 1.3 * 1.0, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: true)
|
||||
animation.repeatCount = Float.infinity
|
||||
animation.beginTime = 1.0
|
||||
self.imageNode.layer.add(animation, forKey: "shimmer")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class LoadingShimmerNode: ASDisplayNode {
|
||||
private let backgroundColorNode: ASDisplayNode
|
||||
private let effectNode: SearchShimmerEffectNode
|
||||
private let maskNode: ASImageNode
|
||||
private var currentParams: (size: CGSize, theme: PresentationTheme)?
|
||||
|
||||
override init() {
|
||||
self.backgroundColorNode = ASDisplayNode()
|
||||
self.effectNode = SearchShimmerEffectNode()
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.allowsGroupOpacity = true
|
||||
self.isUserInteractionEnabled = false
|
||||
|
||||
self.addSubnode(self.backgroundColorNode)
|
||||
self.addSubnode(self.effectNode)
|
||||
self.addSubnode(self.maskNode)
|
||||
}
|
||||
|
||||
func update(size: CGSize, theme: PresentationTheme, transition: ContainedViewLayoutTransition) {
|
||||
let color = theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
|
||||
|
||||
if self.currentParams?.size != size || self.currentParams?.theme !== theme {
|
||||
self.currentParams = (size, theme)
|
||||
|
||||
self.backgroundColorNode.backgroundColor = color
|
||||
|
||||
self.maskNode.image = generateImage(size, rotatedContext: { size, context in
|
||||
context.setFillColor(theme.list.blocksBackgroundColor.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
var currentY: CGFloat = 0.0
|
||||
var rowIndex: Int = 0
|
||||
|
||||
let sideInset: CGFloat = 16.0// + environment.safeInsets.left
|
||||
let optionSpacing: CGFloat = 10.0
|
||||
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
||||
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
||||
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
|
||||
while currentY < size.height {
|
||||
for i in 0 ..< 3 {
|
||||
let itemOrigin = CGPoint(x: sideInset + CGFloat(i) * (itemSize.width + optionSpacing), y: 2.0 + CGFloat(rowIndex) * (itemSize.height + optionSpacing))
|
||||
context.addPath(CGPath(roundedRect: CGRect(origin: itemOrigin, size: itemSize), cornerWidth: 10.0, cornerHeight: 10.0, transform: nil))
|
||||
}
|
||||
currentY += itemSize.height
|
||||
rowIndex += 1
|
||||
}
|
||||
context.fillPath()
|
||||
})
|
||||
|
||||
self.effectNode.update(backgroundColor: color, foregroundColor: theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4))
|
||||
self.effectNode.updateAbsoluteRect(CGRect(origin: CGPoint(), size: size), within: size)
|
||||
}
|
||||
transition.updateFrame(node: self.backgroundColorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||
transition.updateFrame(node: self.maskNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||
transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import ContextUI
|
||||
import TextFieldComponent
|
||||
import MultilineTextComponent
|
||||
import BundleIconComponent
|
||||
|
||||
final class SearchContextItem: ContextMenuCustomItem {
|
||||
let context: AccountContext
|
||||
let placeholder: String
|
||||
let value: String
|
||||
let valueChanged: (String) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
placeholder: String,
|
||||
value: String,
|
||||
valueChanged: @escaping (String) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.placeholder = placeholder
|
||||
self.value = value
|
||||
self.valueChanged = valueChanged
|
||||
}
|
||||
|
||||
func node(presentationData: PresentationData, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode {
|
||||
return SearchContextItemNode(
|
||||
presentationData: presentationData,
|
||||
item: self,
|
||||
getController: getController,
|
||||
actionSelected: actionSelected
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private final class SearchContextItemNode: ASDisplayNode, ContextMenuCustomNode, ContextActionNodeProtocol, ASScrollViewDelegate {
|
||||
private let item: SearchContextItem
|
||||
private let presentationData: PresentationData
|
||||
private let getController: () -> ContextControllerProtocol?
|
||||
private let actionSelected: (ContextMenuActionResult) -> Void
|
||||
|
||||
private let state = EmptyComponentState()
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let inputField = ComponentView<Empty>()
|
||||
private let inputFieldExternalState = TextFieldComponent.ExternalState()
|
||||
private let inputPlaceholderView = ComponentView<Empty>()
|
||||
private let inputClear = ComponentView<Empty>()
|
||||
private var inputText = ""
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
init(presentationData: PresentationData, item: SearchContextItem, getController: @escaping () -> ContextControllerProtocol?, actionSelected: @escaping (ContextMenuActionResult) -> Void) {
|
||||
self.item = item
|
||||
self.presentationData = presentationData
|
||||
self.getController = getController
|
||||
self.actionSelected = actionSelected
|
||||
|
||||
super.init()
|
||||
|
||||
self.state._updated = { [weak self] transition, _ in
|
||||
guard let self, let size = self.validLayout else {
|
||||
return
|
||||
}
|
||||
self.internalUpdateLayout(size: size, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func internalUpdateLayout(size: CGSize, transition: ComponentTransition) {
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Chat/Context Menu/Search", tintColor: self.presentationData.theme.contextMenu.primaryColor)),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: 17.0, y: floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.view.addSubview(iconView)
|
||||
}
|
||||
transition.setFrame(view: iconView, frame: iconFrame)
|
||||
}
|
||||
|
||||
let inputInset: CGFloat = 42.0
|
||||
|
||||
self.inputField.parentState = self.state
|
||||
let inputFieldSize = self.inputField.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(TextFieldComponent(
|
||||
context: self.item.context,
|
||||
theme: self.presentationData.theme,
|
||||
strings: self.presentationData.strings,
|
||||
externalState: self.inputFieldExternalState,
|
||||
fontSize: self.presentationData.listsFontSize.baseDisplaySize,
|
||||
textColor: self.presentationData.theme.contextMenu.primaryColor,
|
||||
accentColor: self.presentationData.theme.contextMenu.primaryColor,
|
||||
insets: UIEdgeInsets(top: 8.0, left: 2.0, bottom: 8.0, right: 2.0),
|
||||
hideKeyboard: false,
|
||||
customInputView: nil,
|
||||
resetText: nil,
|
||||
isOneLineWhenUnfocused: false,
|
||||
emptyLineHandling: .notAllowed,
|
||||
formatMenuAvailability: .none,
|
||||
returnKeyType: .search,
|
||||
lockedFormatAction: {
|
||||
},
|
||||
present: { _ in
|
||||
},
|
||||
paste: { _ in
|
||||
},
|
||||
returnKeyAction: nil,
|
||||
backspaceKeyAction: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: size.width - inputInset - 40.0, height: size.height)
|
||||
)
|
||||
let inputFieldFrame = CGRect(origin: CGPoint(x: inputInset, y: floorToScreenPixels((size.height - inputFieldSize.height) / 2.0)), size: inputFieldSize)
|
||||
if let inputFieldView = self.inputField.view as? TextFieldComponent.View {
|
||||
if inputFieldView.superview == nil {
|
||||
self.view.addSubview(inputFieldView)
|
||||
}
|
||||
transition.setFrame(view: inputFieldView, frame: inputFieldFrame)
|
||||
}
|
||||
|
||||
if self.inputText != self.inputFieldExternalState.text.string {
|
||||
self.inputText = self.inputFieldExternalState.text.string
|
||||
self.item.valueChanged(self.inputText)
|
||||
}
|
||||
|
||||
let inputPlaceholderSize = self.inputPlaceholderView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(
|
||||
string: self.item.placeholder,
|
||||
font: Font.regular(self.presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: self.presentationData.theme.contextMenu.secondaryColor
|
||||
)))
|
||||
),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let inputPlaceholderFrame = CGRect(origin: CGPoint(x: inputInset + 10.0, y: floorToScreenPixels(inputFieldFrame.midY - inputPlaceholderSize.height / 2.0)), size: inputPlaceholderSize)
|
||||
if let inputPlaceholderView = self.inputPlaceholderView.view {
|
||||
if inputPlaceholderView.superview == nil {
|
||||
inputPlaceholderView.isUserInteractionEnabled = false
|
||||
self.view.addSubview(inputPlaceholderView)
|
||||
}
|
||||
inputPlaceholderView.frame = inputPlaceholderFrame
|
||||
inputPlaceholderView.isHidden = self.inputFieldExternalState.hasText
|
||||
}
|
||||
|
||||
let inputClearSize = self.inputClear.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
Button(
|
||||
content: AnyComponent(
|
||||
BundleIconComponent(name: "Components/Search Bar/Clear", tintColor: self.presentationData.theme.contextMenu.secondaryColor, maxSize: CGSize(width: 24.0, height: 24.0))
|
||||
),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if let inputFieldView = self.inputField.view as? TextFieldComponent.View {
|
||||
inputFieldView.updateText(NSAttributedString(), selectionRange: 0..<0)
|
||||
}
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 30.0, height: 30.0)
|
||||
)
|
||||
let inputClearFrame = CGRect(origin: CGPoint(x: size.width - inputClearSize.width - 16.0, y: floorToScreenPixels(inputFieldFrame.midY - inputClearSize.height / 2.0)), size: inputClearSize)
|
||||
if let inputClearView = self.inputClear.view {
|
||||
if inputClearView.superview == nil {
|
||||
self.view.addSubview(inputClearView)
|
||||
}
|
||||
inputClearView.frame = inputClearFrame
|
||||
inputClearView.isHidden = !self.inputFieldExternalState.hasText
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(constrainedWidth: CGFloat, constrainedHeight: CGFloat) -> (CGSize, (CGSize, ContainedViewLayoutTransition) -> Void) {
|
||||
let maxWidth: CGFloat = 220.0
|
||||
let height: CGFloat = 42.0
|
||||
|
||||
return (CGSize(width: maxWidth, height: height), { size, transition in
|
||||
self.validLayout = size
|
||||
self.internalUpdateLayout(size: size, transition: ComponentTransition(transition))
|
||||
})
|
||||
}
|
||||
|
||||
func updateTheme(presentationData: PresentationData) {
|
||||
|
||||
}
|
||||
|
||||
var isActionEnabled: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func performAction() {
|
||||
}
|
||||
|
||||
func setIsHighlighted(_ value: Bool) {
|
||||
}
|
||||
|
||||
func canBeHighlighted() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func updateIsHighlighted(isHighlighted: Bool) {
|
||||
}
|
||||
|
||||
func actionNode(at point: CGPoint) -> ContextActionNodeProtocol {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,95 @@ import MoreButtonNode
|
|||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
|
||||
final class PriceButtonComponent: Component {
|
||||
let price: Int64
|
||||
|
||||
init(
|
||||
price: Int64
|
||||
) {
|
||||
self.price = price
|
||||
}
|
||||
|
||||
static func ==(lhs: PriceButtonComponent, rhs: PriceButtonComponent) -> Bool {
|
||||
return lhs.price == rhs.price
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private let icon = UIImageView()
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private var component: PriceButtonComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundView.clipsToBounds = true
|
||||
self.addSubview(self.backgroundView)
|
||||
|
||||
self.icon.image = UIImage(bundleImageName: "Premium/Stars/ButtonStar")?.withRenderingMode(.alwaysTemplate)
|
||||
self.backgroundView.addSubview(self.icon)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: PriceButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
var backgroundSize = CGSize(width: 42.0, height: 30.0)
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "\(component.price)",
|
||||
font: Font.semibold(11.0),
|
||||
textColor: UIColor(rgb: 0xffffff)
|
||||
))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: 32.0, y: floorToScreenPixels((backgroundSize.height - textSize.height) / 2.0)), size: textSize)
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.backgroundView.addSubview(textView)
|
||||
}
|
||||
transition.setFrame(view: textView, frame: textFrame)
|
||||
}
|
||||
backgroundSize.width += textSize.width
|
||||
|
||||
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
|
||||
|
||||
let backgroundColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
|
||||
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
|
||||
|
||||
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
|
||||
if let iconSize = self.icon.image?.size {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floorToScreenPixels((backgroundSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
transition.setFrame(view: self.icon, frame: iconFrame)
|
||||
}
|
||||
self.icon.tintColor = UIColor(rgb: 0xffffff)
|
||||
|
||||
return backgroundSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ButtonsComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let isOverlay: Bool
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ private final class GiftTransferAlertContentNode: AlertContentNode {
|
|||
theme: self.presentationTheme,
|
||||
strings: self.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift),
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ private final class SheetContent: CombinedComponent {
|
|||
|
||||
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
|
||||
for attribute in displayGift.attributes {
|
||||
if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute {
|
||||
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
|
||||
ribbonColor = .custom(outerColor, innerColor)
|
||||
break
|
||||
}
|
||||
|
|
@ -175,7 +175,7 @@ private final class SheetContent: CombinedComponent {
|
|||
context: component.context,
|
||||
theme: theme,
|
||||
strings: strings,
|
||||
subject: .uniqueGift(gift: displayGift),
|
||||
subject: .uniqueGift(gift: displayGift, price: nil),
|
||||
ribbon: GiftItemComponent.Ribbon(text: "#\(displayGift.number)", font: .monospaced, color: ribbonColor),
|
||||
mode: .grid
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -148,7 +148,7 @@ private final class GiftWithdrawAlertContentNode: AlertContentNode {
|
|||
theme: self.presentationTheme,
|
||||
strings: self.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: self.gift),
|
||||
subject: .uniqueGift(gift: self.gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
|
|
|
|||
19
submodules/TelegramUI/Components/MarqueeComponent/BUILD
Normal file
19
submodules/TelegramUI/Components/MarqueeComponent/BUILD
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "MarqueeComponent",
|
||||
module_name = "MarqueeComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
private let animationDuration: TimeInterval = 12.0
|
||||
private let animationDelay: TimeInterval = 2.0
|
||||
private let spacing: CGFloat = 20.0
|
||||
|
||||
public final class MarqueeComponent: Component {
|
||||
public static let innerPadding: CGFloat = 16.0
|
||||
|
||||
private final class MeasureState: Equatable {
|
||||
let attributedText: NSAttributedString
|
||||
let availableSize: CGSize
|
||||
let size: CGSize
|
||||
|
||||
init(attributedText: NSAttributedString, availableSize: CGSize, size: CGSize) {
|
||||
self.attributedText = attributedText
|
||||
self.availableSize = availableSize
|
||||
self.size = size
|
||||
}
|
||||
|
||||
static func ==(lhs: MeasureState, rhs: MeasureState) -> Bool {
|
||||
if !lhs.attributedText.isEqual(rhs.attributedText) {
|
||||
return false
|
||||
}
|
||||
if lhs.availableSize != rhs.availableSize {
|
||||
return false
|
||||
}
|
||||
if lhs.size != rhs.size {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public final class View: UIView {
|
||||
private var measureState: MeasureState?
|
||||
private let containerLayer = SimpleLayer()
|
||||
private let textLayer = SimpleLayer()
|
||||
private let duplicateTextLayer = SimpleLayer()
|
||||
private let gradientMaskLayer = SimpleGradientLayer()
|
||||
private var isAnimating = false
|
||||
private var isOverflowing = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.containerLayer.masksToBounds = true
|
||||
self.layer.addSublayer(self.containerLayer)
|
||||
|
||||
self.containerLayer.addSublayer(self.textLayer)
|
||||
self.containerLayer.addSublayer(self.duplicateTextLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(component: MarqueeComponent, availableSize: CGSize) -> CGSize {
|
||||
let attributedText = component.attributedText
|
||||
if let measureState = self.measureState {
|
||||
if measureState.attributedText.isEqual(to: attributedText) && measureState.availableSize == availableSize {
|
||||
return measureState.size
|
||||
}
|
||||
}
|
||||
|
||||
var boundingRect = attributedText.boundingRect(with: CGSize(width: 10000, height: availableSize.height), options: .usesLineFragmentOrigin, context: nil)
|
||||
boundingRect.size.width = ceil(boundingRect.size.width)
|
||||
boundingRect.size.height = ceil(boundingRect.size.height)
|
||||
|
||||
let measureState = MeasureState(attributedText: attributedText, availableSize: availableSize, size: boundingRect.size)
|
||||
self.measureState = measureState
|
||||
|
||||
self.containerLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: measureState.size.width + innerPadding * 2.0, height: measureState.size.height))
|
||||
|
||||
let isOverflowing = boundingRect.width > availableSize.width
|
||||
|
||||
let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: measureState.size))
|
||||
let image = renderer.image { context in
|
||||
UIGraphicsPushContext(context.cgContext)
|
||||
measureState.attributedText.draw(at: CGPoint())
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
||||
if isOverflowing {
|
||||
self.setupMarqueeTextLayers(textImage: image.cgImage!, textWidth: boundingRect.width, containerWidth: availableSize.width)
|
||||
self.setupGradientMask(size: CGSize(width: availableSize.width, height: boundingRect.height))
|
||||
self.startAnimation()
|
||||
} else {
|
||||
self.stopAnimation()
|
||||
self.textLayer.frame = CGRect(origin: CGPoint(x: innerPadding, y: 0.0), size: boundingRect.size)
|
||||
self.textLayer.contents = image.cgImage
|
||||
self.duplicateTextLayer.frame = .zero
|
||||
self.duplicateTextLayer.contents = nil
|
||||
self.layer.mask = nil
|
||||
}
|
||||
|
||||
return CGSize(width: min(measureState.size.width + innerPadding * 2.0, availableSize.width), height: measureState.size.height)
|
||||
}
|
||||
|
||||
private func setupMarqueeTextLayers(textImage: CGImage, textWidth: CGFloat, containerWidth: CGFloat) {
|
||||
self.textLayer.frame = CGRect(x: innerPadding, y: 0, width: textWidth, height: self.containerLayer.bounds.height)
|
||||
self.textLayer.contents = textImage
|
||||
|
||||
self.duplicateTextLayer.frame = CGRect(x: innerPadding + textWidth + spacing, y: 0, width: textWidth, height: self.containerLayer.bounds.height)
|
||||
self.duplicateTextLayer.contents = textImage
|
||||
|
||||
self.containerLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: textWidth * 2.0 + spacing, height: self.containerLayer.bounds.height))
|
||||
}
|
||||
|
||||
private func setupGradientMask(size: CGSize) {
|
||||
self.gradientMaskLayer.frame = CGRect(origin: .zero, size: size)
|
||||
self.gradientMaskLayer.colors = [
|
||||
UIColor.clear.cgColor,
|
||||
UIColor.clear.cgColor,
|
||||
UIColor.black.cgColor,
|
||||
UIColor.black.cgColor,
|
||||
UIColor.clear.cgColor,
|
||||
UIColor.clear.cgColor
|
||||
]
|
||||
self.gradientMaskLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
|
||||
self.gradientMaskLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
|
||||
|
||||
let edgePercentage = innerPadding / size.width
|
||||
self.gradientMaskLayer.locations = [
|
||||
0.0,
|
||||
NSNumber(value: edgePercentage * 0.4),
|
||||
NSNumber(value: edgePercentage),
|
||||
NSNumber(value: 1.0 - edgePercentage),
|
||||
NSNumber(value: 1.0 - edgePercentage * 0.4),
|
||||
1.0
|
||||
]
|
||||
|
||||
self.layer.mask = self.gradientMaskLayer
|
||||
}
|
||||
|
||||
private func startAnimation() {
|
||||
guard !self.isAnimating else {
|
||||
return
|
||||
}
|
||||
self.isAnimating = true
|
||||
|
||||
self.containerLayer.removeAllAnimations()
|
||||
|
||||
self.containerLayer.animateBoundsOriginXAdditive(from: 0.0, to: self.textLayer.frame.width + spacing, duration: animationDuration, delay: animationDelay, timingFunction: CAMediaTimingFunctionName.linear.rawValue, completion: { _ in
|
||||
self.isAnimating = false
|
||||
self.startAnimation()
|
||||
})
|
||||
}
|
||||
|
||||
private func stopAnimation() {
|
||||
self.containerLayer.removeAllAnimations()
|
||||
self.isAnimating = false
|
||||
}
|
||||
}
|
||||
|
||||
public let attributedText: NSAttributedString
|
||||
|
||||
public init(attributedText: NSAttributedString) {
|
||||
self.attributedText = attributedText
|
||||
}
|
||||
|
||||
public static func ==(lhs: MarqueeComponent, rhs: MarqueeComponent) -> Bool {
|
||||
if lhs.attributedText != rhs.attributedText {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize)
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,8 @@ swift_library(
|
|||
"//submodules/UrlEscaping",
|
||||
"//submodules/DeviceLocationManager",
|
||||
"//submodules/TelegramUI/Components/SaveProgressScreen",
|
||||
"//submodules/TelegramUI/Components/MediaAssetsContext",
|
||||
"//submodules/CheckNode",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ public extension MediaEditorScreenImpl {
|
|||
} else {
|
||||
existingMedia = storyItem.media
|
||||
}
|
||||
rootController.proceedWithStoryUpload(target: target, result: result as! MediaEditorScreenResult, existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit)
|
||||
rootController.proceedWithStoryUpload(target: target, results: [result as! MediaEditorScreenResult], existingMedia: existingMedia, forwardInfo: forwardInfo, externalState: externalState, commit: commit)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -326,6 +326,9 @@ final class MediaEditorScreenComponent: Component {
|
|||
|
||||
private let switchCameraButton = ComponentView<Empty>()
|
||||
|
||||
private let selectionButton = ComponentView<Empty>()
|
||||
private let selectionPanel = ComponentView<Empty>()
|
||||
|
||||
private let textCancelButton = ComponentView<Empty>()
|
||||
private let textDoneButton = ComponentView<Empty>()
|
||||
private let textSize = ComponentView<Empty>()
|
||||
|
|
@ -335,6 +338,8 @@ final class MediaEditorScreenComponent: Component {
|
|||
private var isEditingCaption = false
|
||||
private var currentInputMode: MessageInputPanelComponent.InputMode = .text
|
||||
|
||||
private var isSelectionPanelOpen = false
|
||||
|
||||
private var didInitializeInputMediaNodeDataPromise = false
|
||||
private var inputMediaNodeData: ChatEntityKeyboardInputNode.InputData?
|
||||
private var inputMediaNodeDataPromise = Promise<ChatEntityKeyboardInputNode.InputData>()
|
||||
|
|
@ -737,6 +742,13 @@ final class MediaEditorScreenComponent: Component {
|
|||
return inputText
|
||||
}
|
||||
|
||||
func setInputText(_ text: NSAttributedString) {
|
||||
guard let inputPanelView = self.inputPanel.view as? MessageInputPanelComponent.View else {
|
||||
return
|
||||
}
|
||||
inputPanelView.setSendMessageInput(value: .text(text), updateState: true)
|
||||
}
|
||||
|
||||
private func updateCoverPosition() {
|
||||
guard let controller = self.environment?.controller() as? MediaEditorScreenImpl, let mediaEditor = controller.node.mediaEditor else {
|
||||
return
|
||||
|
|
@ -1988,6 +2000,120 @@ final class MediaEditorScreenComponent: Component {
|
|||
transition.setScale(view: switchCameraButtonView, scale: isRecordingAdditionalVideo ? 1.0 : 0.01)
|
||||
transition.setAlpha(view: switchCameraButtonView, alpha: isRecordingAdditionalVideo ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
if controller.node.items.count > 1 {
|
||||
let selectionButtonSize = self.selectionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
SelectionPanelButtonContentComponent(
|
||||
count: Int32(controller.node.items.count(where: { $0.isEnabled })),
|
||||
isSelected: self.isSelectionPanelOpen,
|
||||
tag: nil
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
if let self {
|
||||
self.isSelectionPanelOpen = !self.isSelectionPanelOpen
|
||||
self.state?.updated()
|
||||
}
|
||||
},
|
||||
animateAlpha: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 33.0, height: 33.0)
|
||||
)
|
||||
let selectionButtonFrame = CGRect(
|
||||
origin: CGPoint(x: availableSize.width - selectionButtonSize.width - 12.0, y: inputPanelFrame.minY - selectionButtonSize.height - 3.0),
|
||||
size: selectionButtonSize
|
||||
)
|
||||
if let selectionButtonView = self.selectionButton.view as? PlainButtonComponent.View {
|
||||
if selectionButtonView.superview == nil {
|
||||
self.addSubview(selectionButtonView)
|
||||
}
|
||||
transition.setPosition(view: selectionButtonView, position: selectionButtonFrame.center)
|
||||
transition.setBounds(view: selectionButtonView, bounds: CGRect(origin: .zero, size: selectionButtonFrame.size))
|
||||
transition.setScale(view: selectionButtonView, scale: displayTopButtons ? 1.0 : 0.01)
|
||||
transition.setAlpha(view: selectionButtonView, alpha: displayTopButtons && !component.isDismissing && !component.isInteractingWithEntities ? 1.0 : 0.0)
|
||||
|
||||
if self.isSelectionPanelOpen {
|
||||
let selectionPanelFrame = CGRect(
|
||||
origin: CGPoint(x: 12.0, y: inputPanelFrame.minY - selectionButtonSize.height - 3.0 - 130.0),
|
||||
size: CGSize(width: availableSize.width - 24.0, height: 120.0)
|
||||
)
|
||||
|
||||
var selectedItemId = ""
|
||||
if case let .asset(asset) = controller.node.subject {
|
||||
selectedItemId = asset.localIdentifier
|
||||
}
|
||||
|
||||
let _ = self.selectionPanel.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
SelectionPanelComponent(
|
||||
previewContainerView: controller.node.previewContentContainerView,
|
||||
frame: selectionPanelFrame,
|
||||
items: controller.node.items,
|
||||
selectedItemId: selectedItemId,
|
||||
itemTapped: { [weak self, weak controller] id in
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
self.isSelectionPanelOpen = false
|
||||
self.state?.updated()
|
||||
|
||||
if let id {
|
||||
controller.node.switchToItem(id)
|
||||
}
|
||||
},
|
||||
itemSelectionToggled: { [weak self, weak controller] id in
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
if let itemIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == id }) {
|
||||
controller.node.items[itemIndex].isEnabled = !controller.node.items[itemIndex].isEnabled
|
||||
}
|
||||
self.state?.updated(transition: .spring(duration: 0.3))
|
||||
},
|
||||
itemReordered: { [weak self, weak controller] fromId, toId in
|
||||
guard let self, let controller else {
|
||||
return
|
||||
}
|
||||
guard let fromIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == fromId }), let toIndex = controller.node.items.firstIndex(where: { $0.asset.localIdentifier == toId }), toIndex < controller.node.items.count else {
|
||||
return
|
||||
}
|
||||
let fromItem = controller.node.items[fromIndex]
|
||||
let toItem = controller.node.items[toIndex]
|
||||
controller.node.items[fromIndex] = toItem
|
||||
controller.node.items[toIndex] = fromItem
|
||||
self.state?.updated(transition: .spring(duration: 0.3))
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
||||
if selectionPanelView.superview == nil {
|
||||
self.insertSubview(selectionPanelView, belowSubview: selectionButtonView)
|
||||
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||
selectionPanelView.animateIn(from: buttonView)
|
||||
}
|
||||
}
|
||||
selectionPanelView.frame = CGRect(origin: .zero, size: availableSize)
|
||||
}
|
||||
} else if let selectionPanelView = self.selectionPanel.view as? SelectionPanelComponent.View {
|
||||
if let buttonView = selectionButtonView.contentView as? SelectionPanelButtonContentComponent.View {
|
||||
selectionPanelView.animateOut(to: buttonView, completion: { [weak selectionPanelView] in
|
||||
selectionPanelView?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
selectionPanelView.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inputPanelSize = CGSize(width: 0.0, height: 12.0)
|
||||
}
|
||||
|
|
@ -2756,6 +2882,38 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
}
|
||||
|
||||
struct EditingItem: Equatable {
|
||||
let asset: PHAsset
|
||||
var values: MediaEditorValues?
|
||||
var caption = NSAttributedString()
|
||||
var thumbnail: UIImage?
|
||||
var isEnabled = true
|
||||
var version: Int = 0
|
||||
|
||||
init(asset: PHAsset) {
|
||||
self.asset = asset
|
||||
}
|
||||
|
||||
public static func ==(lhs: EditingItem, rhs: EditingItem) -> Bool {
|
||||
if lhs.asset.localIdentifier != rhs.asset.localIdentifier {
|
||||
return false
|
||||
}
|
||||
if lhs.values != rhs.values {
|
||||
return false
|
||||
}
|
||||
if lhs.caption != rhs.caption {
|
||||
return false
|
||||
}
|
||||
if lhs.thumbnail != rhs.thumbnail {
|
||||
return false
|
||||
}
|
||||
if lhs.version != rhs.version {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class Node: ViewControllerTracingNode, ASGestureRecognizerDelegate, UIScrollViewDelegate {
|
||||
private weak var controller: MediaEditorScreenImpl?
|
||||
private let context: AccountContext
|
||||
|
|
@ -2764,6 +2922,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|
||||
var subject: MediaEditorScreenImpl.Subject?
|
||||
var actualSubject: MediaEditorScreenImpl.Subject?
|
||||
var items: [EditingItem] = []
|
||||
|
||||
private var subjectDisposable: Disposable?
|
||||
private var appInForegroundDisposable: Disposable?
|
||||
|
|
@ -2852,6 +3011,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|
||||
private let readyValue = Promise<Bool>()
|
||||
|
||||
var componentHostView: MediaEditorScreenComponent.View? {
|
||||
return self.componentHost.view as? MediaEditorScreenComponent.View
|
||||
}
|
||||
|
||||
init(controller: MediaEditorScreenImpl) {
|
||||
self.controller = controller
|
||||
self.context = controller.context
|
||||
|
|
@ -3023,7 +3186,46 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|> deliverOnMainQueue
|
||||
).start(next: { [weak self] subject in
|
||||
if let self, let subject {
|
||||
self.setup(with: subject)
|
||||
self.actualSubject = subject
|
||||
|
||||
var effectiveSubject = subject
|
||||
switch subject {
|
||||
case let .assets(assets):
|
||||
effectiveSubject = .asset(assets.first!)
|
||||
self.items = assets.map { EditingItem(asset: $0) }
|
||||
case let .draft(draft, _):
|
||||
for entity in draft.values.entities {
|
||||
if case let .sticker(sticker) = entity {
|
||||
switch sticker.content {
|
||||
case let .message(ids, _, _, _, _):
|
||||
effectiveSubject = .message(ids)
|
||||
case let .gift(gift, _):
|
||||
effectiveSubject = .gift(gift)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
var privacy: MediaEditorResultPrivacy?
|
||||
var values: MediaEditorValues?
|
||||
var isDraft = false
|
||||
if case let .draft(draft, _) = subject {
|
||||
privacy = draft.privacy
|
||||
values = draft.values
|
||||
isDraft = true
|
||||
}
|
||||
|
||||
self.setup(
|
||||
subject: effectiveSubject,
|
||||
privacy: privacy,
|
||||
values: values,
|
||||
caption: nil,
|
||||
isDraft: isDraft
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -3144,35 +3346,24 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.stickerCutoutStatusDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func setup(with subject: MediaEditorScreenImpl.Subject) {
|
||||
func setup(
|
||||
subject: MediaEditorScreenImpl.Subject,
|
||||
privacy: MediaEditorResultPrivacy? = nil,
|
||||
values: MediaEditorValues?,
|
||||
caption: NSAttributedString?,
|
||||
isDraft: Bool = false
|
||||
) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
}
|
||||
|
||||
self.actualSubject = subject
|
||||
|
||||
var effectiveSubject = subject
|
||||
if case let .draft(draft, _ ) = subject {
|
||||
for entity in draft.values.entities {
|
||||
if case let .sticker(sticker) = entity {
|
||||
switch sticker.content {
|
||||
case let .message(ids, _, _, _, _):
|
||||
effectiveSubject = .message(ids)
|
||||
case let .gift(gift, _):
|
||||
effectiveSubject = .gift(gift)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.subject = effectiveSubject
|
||||
self.subject = subject
|
||||
|
||||
Queue.mainQueue().justDispatch {
|
||||
controller.setupAudioSessionIfNeeded()
|
||||
}
|
||||
|
||||
if case let .draft(draft, _) = subject, let privacy = draft.privacy {
|
||||
|
||||
if let privacy {
|
||||
controller.state.privacy = privacy
|
||||
}
|
||||
|
||||
|
|
@ -3190,7 +3381,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
controller.isSavingAvailable = isSavingAvailable
|
||||
controller.requestLayout(transition: .immediate)
|
||||
|
||||
let mediaDimensions = effectiveSubject.dimensions
|
||||
let mediaDimensions = subject.dimensions
|
||||
let maxSide: CGFloat = 1920.0 / UIScreen.main.scale
|
||||
let fittedSize = mediaDimensions.cgSize.fitted(CGSize(width: maxSide, height: maxSide))
|
||||
let mediaEntity = DrawingMediaEntity(size: fittedSize)
|
||||
|
|
@ -3229,27 +3420,28 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
|
||||
let initialValues: MediaEditorValues?
|
||||
if case let .draft(draft, _) = subject {
|
||||
initialValues = draft.values
|
||||
if let values {
|
||||
initialValues = values
|
||||
|
||||
for entity in draft.values.entities {
|
||||
for entity in values.entities {
|
||||
self.entitiesView.add(entity.entity.duplicate(copy: true), announce: false)
|
||||
}
|
||||
|
||||
if let drawingData = initialValues?.drawing?.pngData() {
|
||||
if let drawingData = values.drawing?.pngData() {
|
||||
self.drawingView.setup(withDrawing: drawingData)
|
||||
}
|
||||
} else {
|
||||
initialValues = nil
|
||||
}
|
||||
|
||||
var mediaEditorMode: MediaEditor.Mode = .default
|
||||
if case .stickerEditor = controller.mode {
|
||||
let mediaEditorMode: MediaEditor.Mode
|
||||
switch controller.mode {
|
||||
case .stickerEditor:
|
||||
mediaEditorMode = .sticker
|
||||
} else if case .avatarEditor = controller.mode {
|
||||
mediaEditorMode = .avatar
|
||||
} else if case .coverEditor = controller.mode {
|
||||
case .avatarEditor, .coverEditor:
|
||||
mediaEditorMode = .avatar
|
||||
default:
|
||||
mediaEditorMode = .default
|
||||
}
|
||||
|
||||
if let mediaEntityView = self.entitiesView.add(mediaEntity, announce: false) as? DrawingMediaEntityView {
|
||||
|
|
@ -3275,7 +3467,13 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
}
|
||||
|
||||
let mediaEditor = MediaEditor(context: self.context, mode: mediaEditorMode, subject: effectiveSubject.editorSubject, values: initialValues, hasHistogram: true)
|
||||
let mediaEditor = MediaEditor(
|
||||
context: self.context,
|
||||
mode: mediaEditorMode,
|
||||
subject: subject.editorSubject,
|
||||
values: initialValues,
|
||||
hasHistogram: true
|
||||
)
|
||||
if case .avatarEditor = controller.mode {
|
||||
mediaEditor.setVideoIsMuted(true)
|
||||
} else if case let .coverEditor(dimensions) = controller.mode {
|
||||
|
|
@ -3288,7 +3486,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
mediaEditor.seek(initialVideoPosition, andPlay: true)
|
||||
}
|
||||
}
|
||||
if self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered {
|
||||
if !isDraft, self.context.sharedContext.currentPresentationData.with({$0}).autoNightModeTriggered {
|
||||
switch subject {
|
||||
case .message, .gift:
|
||||
mediaEditor.setNightTheme(true)
|
||||
|
|
@ -3308,46 +3506,48 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
controller.requestLayout(transition: .animated(duration: 0.25, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
self.stickerCutoutStatusDisposable = (mediaEditor.cutoutStatus
|
||||
|> deliverOnMainQueue).start(next: { [weak self] cutoutStatus in
|
||||
guard let self else {
|
||||
return
|
||||
if case .stickerEditor = controller.mode {
|
||||
self.stickerCutoutStatusDisposable = (mediaEditor.cutoutStatus
|
||||
|> deliverOnMainQueue).start(next: { [weak self] cutoutStatus in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.stickerCutoutStatus = cutoutStatus
|
||||
self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25))
|
||||
})
|
||||
mediaEditor.maskUpdated = { [weak self] mask, apply in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if self.stickerMaskDrawingView == nil {
|
||||
self.setupMaskDrawingView(size: mask.size)
|
||||
}
|
||||
if apply, let maskData = mask.pngData() {
|
||||
self.stickerMaskDrawingView?.setup(withDrawing: maskData, storeAsClear: true)
|
||||
}
|
||||
}
|
||||
self.stickerCutoutStatus = cutoutStatus
|
||||
self.requestLayout(forceUpdate: true, transition: .easeInOut(duration: 0.25))
|
||||
})
|
||||
mediaEditor.maskUpdated = { [weak self] mask, apply in
|
||||
guard let self else {
|
||||
return
|
||||
mediaEditor.classificationUpdated = { [weak self] classes in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
|
||||
}
|
||||
if self.stickerMaskDrawingView == nil {
|
||||
self.setupMaskDrawingView(size: mask.size)
|
||||
}
|
||||
if apply, let maskData = mask.pngData() {
|
||||
self.stickerMaskDrawingView?.setup(withDrawing: maskData, storeAsClear: true)
|
||||
}
|
||||
}
|
||||
mediaEditor.classificationUpdated = { [weak self] classes in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.controller?.stickerRecommendedEmoji = emojiForClasses(classes.map { $0.0 })
|
||||
}
|
||||
mediaEditor.attachPreviewView(self.previewView, andPlay: !(self.controller?.isEditingStoryCover ?? false))
|
||||
|
||||
if case .empty = effectiveSubject {
|
||||
if case .empty = subject {
|
||||
self.stickerMaskDrawingView?.emptyColor = .black
|
||||
self.stickerMaskDrawingView?.clearWithEmptyColor()
|
||||
}
|
||||
|
||||
switch effectiveSubject {
|
||||
switch subject {
|
||||
case .message, .gift:
|
||||
break
|
||||
default:
|
||||
self.readyValue.set(.single(true))
|
||||
}
|
||||
|
||||
switch effectiveSubject {
|
||||
switch subject {
|
||||
case let .image(_, _, additionalImage, position):
|
||||
if let additionalImage {
|
||||
let image = generateImage(CGSize(width: additionalImage.size.width, height: additionalImage.size.width), contextGenerator: { size, context in
|
||||
|
|
@ -3392,7 +3592,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
case .message, .gift:
|
||||
var isGift = false
|
||||
let messages: Signal<[Message], NoError>
|
||||
if case let .message(messageIds) = effectiveSubject {
|
||||
if case let .message(messageIds) = subject {
|
||||
messages = self.context.engine.data.get(
|
||||
EngineDataMap(messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init(id:)))
|
||||
)
|
||||
|
|
@ -3405,9 +3605,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
return messages
|
||||
}
|
||||
} else if case let .gift(gift) = effectiveSubject {
|
||||
} else if case let .gift(gift) = subject {
|
||||
isGift = true
|
||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, peerId: nil, senderId: nil, savedId: nil))]
|
||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, peerId: nil, senderId: nil, savedId: nil, resaleStars: nil))]
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
messages = .single([message])
|
||||
} else {
|
||||
|
|
@ -3429,7 +3629,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
|
||||
let wallpaperColors: Signal<(UIColor?, UIColor?), NoError>
|
||||
if let subject = self.subject, case .gift = subject {
|
||||
if case .gift = subject {
|
||||
wallpaperColors = self.mediaEditorPromise.get()
|
||||
|> mapToSignal { mediaEditor in
|
||||
if let mediaEditor {
|
||||
|
|
@ -3459,7 +3659,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
let renderer = DrawingMessageRenderer(context: self.context, messages: messages, parentView: self.view, isGift: isGift, wallpaperDayColor: wallpaperColors.0, wallpaperNightColor: wallpaperColors.1)
|
||||
renderer.render(completion: { result in
|
||||
if case .draft = subject, let existingEntityView = self.entitiesView.getView(where: { entityView in
|
||||
if isDraft, let existingEntityView = self.entitiesView.getView(where: { entityView in
|
||||
if let stickerEntityView = entityView as? DrawingStickerEntityView {
|
||||
if case .message = (stickerEntityView.entity as! DrawingStickerEntity).content {
|
||||
return true
|
||||
|
|
@ -3469,13 +3669,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
return false
|
||||
}) as? DrawingStickerEntityView {
|
||||
#if DEBUG
|
||||
if let data = result.dayImage.pngData() {
|
||||
let path = NSTemporaryDirectory() + "\(Int(Date().timeIntervalSince1970)).png"
|
||||
try? data.write(to: URL(fileURLWithPath: path))
|
||||
}
|
||||
#endif
|
||||
|
||||
existingEntityView.isNightTheme = isNightTheme
|
||||
let messageEntity = existingEntityView.entity as! DrawingStickerEntity
|
||||
messageEntity.renderImage = result.dayImage
|
||||
|
|
@ -3485,7 +3678,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
} else {
|
||||
var content: DrawingStickerEntity.Content
|
||||
var position: CGPoint
|
||||
switch effectiveSubject {
|
||||
switch subject {
|
||||
case let .message(messageIds):
|
||||
content = .message(messageIds, result.size, messageFile, result.mediaFrame?.rect, result.mediaFrame?.cornerRadius)
|
||||
position = CGPoint(x: storyDimensions.width / 2.0 - 54.0, y: storyDimensions.height / 2.0)
|
||||
|
|
@ -3540,7 +3733,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.backgroundDimView.isHidden = false
|
||||
})
|
||||
}
|
||||
} else if CACurrentMediaTime() - self.initializationTimestamp > 0.2, case .image = subject {
|
||||
} else if CACurrentMediaTime() - self.initializationTimestamp > 0.2, case .image = subject {
|
||||
self.previewContainerView.alpha = 1.0
|
||||
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
|
|
@ -3569,7 +3762,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
})
|
||||
}
|
||||
|
||||
if effectiveSubject.isPhoto {
|
||||
if subject.isPhoto {
|
||||
self.previewContainerView.layer.allowsGroupOpacity = true
|
||||
self.previewContainerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { _ in
|
||||
self.previewContainerView.layer.allowsGroupOpacity = false
|
||||
|
|
@ -3582,6 +3775,12 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let caption {
|
||||
mediaEditor.onFirstDisplay = { [weak self] in
|
||||
self?.componentHostView?.setInputText(caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mediaEditor.onPlaybackAction = { [weak self] action in
|
||||
|
|
@ -3776,7 +3975,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
return nil
|
||||
}
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let imageSize = CGSize(width: 1080, height: 1920)
|
||||
let imageSize = storyDimensions
|
||||
if let context = DrawingContext(size: imageSize, scale: 1.0, opaque: true, colorSpace: colorSpace) {
|
||||
context.withFlippedContext { context in
|
||||
if let image = mediaEditor.resultImage?.cgImage {
|
||||
|
|
@ -4175,9 +4374,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||
completion()
|
||||
case .camera:
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateIn(from: .camera, completion: completion)
|
||||
}
|
||||
self.componentHostView?.animateIn(from: .camera, completion: completion)
|
||||
|
||||
if let subject = self.subject, case let .video(_, mainTransitionImage, _, _, additionalTransitionImage, _, _, positionChangeTimestamps, pipPosition) = subject, let mainTransitionImage {
|
||||
var transitionImage = mainTransitionImage
|
||||
|
|
@ -4188,7 +4385,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
backgroundImage = additionalTransitionImage
|
||||
foregroundImage = mainTransitionImage
|
||||
}
|
||||
if let combinedTransitionImage = generateImage(CGSize(width: 1080, height: 1920), scale: 1.0, rotatedContext: { size, context in
|
||||
if let combinedTransitionImage = generateImage(storyDimensions, scale: 1.0, rotatedContext: { size, context in
|
||||
UIGraphicsPushContext(context)
|
||||
backgroundImage.draw(in: CGRect(origin: CGPoint(x: (size.width - backgroundImage.size.width) / 2.0, y: (size.height - backgroundImage.size.height) / 2.0), size: backgroundImage.size))
|
||||
|
||||
|
|
@ -4214,9 +4411,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.setupTransitionImage(sourceImage)
|
||||
}
|
||||
if let sourceView = transitionIn.sourceView {
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateIn(from: .gallery)
|
||||
}
|
||||
self.componentHostView?.animateIn(from: .gallery)
|
||||
|
||||
let sourceLocalFrame = sourceView.convert(transitionIn.sourceRect, to: self.view)
|
||||
let sourceScale = sourceLocalFrame.width / self.previewContainerView.frame.width
|
||||
|
|
@ -4234,7 +4429,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.backgroundDimView.isHidden = false
|
||||
self.backgroundDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.35)
|
||||
|
||||
if let componentView = self.componentHost.view {
|
||||
if let componentView = self.componentHostView {
|
||||
componentView.layer.animatePosition(from: sourceLocalFrame.center, to: componentView.center, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
componentView.layer.animateScale(from: sourceScale, to: 1.0, duration: duration, timingFunction: timingFunction)
|
||||
componentView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
|
|
@ -4254,8 +4449,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
if animateIn, let layout = self.validLayout {
|
||||
self.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: .zero, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
completion()
|
||||
} else if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateIn(from: .camera, completion: completion)
|
||||
} else {
|
||||
self.componentHostView?.animateIn(from: .camera, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4307,9 +4502,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
destinationTransitionView = destinationTransitionOutView
|
||||
destinationTransitionRect = galleryTransitionIn.sourceRect
|
||||
}
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateOut(to: .gallery)
|
||||
}
|
||||
self.componentHostView?.animateOut(to: .gallery)
|
||||
}
|
||||
let destinationLocalFrame = destinationView.convert(transitionOut.destinationRect, to: self.view)
|
||||
let destinationScale = destinationLocalFrame.width / self.previewContainerView.frame.width
|
||||
|
|
@ -4407,7 +4600,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
removeOnCompletion: false
|
||||
)
|
||||
|
||||
if let componentView = self.componentHost.view {
|
||||
if let componentView = self.componentHostView {
|
||||
componentView.clipsToBounds = true
|
||||
componentView.layer.animatePosition(from: componentView.center, to: destinationLocalFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
componentView.layer.animateScale(from: 1.0, to: destinationScale, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
|
|
@ -4425,18 +4618,14 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
}
|
||||
} else if let transitionIn = controller.transitionIn, case .camera = transitionIn {
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateOut(to: .camera)
|
||||
}
|
||||
self.componentHostView?.animateOut(to: .camera)
|
||||
let transition = ComponentTransition(animation: .curve(duration: 0.25, curve: .easeInOut))
|
||||
transition.setAlpha(view: self.previewContainerView, alpha: 0.0, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
} else {
|
||||
if controller.isEmbeddedEditor {
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateOut(to: .gallery)
|
||||
}
|
||||
self.componentHostView?.animateOut(to: .gallery)
|
||||
|
||||
self.layer.allowsGroupOpacity = true
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.4, removeOnCompletion: false, completion: { _ in
|
||||
|
|
@ -4456,9 +4645,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.isDisplayingTool = tool
|
||||
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateOutToTool(inPlace: inPlace, transition: transition)
|
||||
}
|
||||
self.componentHostView?.animateOutToTool(inPlace: inPlace, transition: transition)
|
||||
self.requestUpdate(transition: transition)
|
||||
}
|
||||
|
||||
|
|
@ -4466,9 +4653,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self.isDisplayingTool = nil
|
||||
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
if let view = self.componentHost.view as? MediaEditorScreenComponent.View {
|
||||
view.animateInFromTool(inPlace: inPlace, transition: transition)
|
||||
}
|
||||
self.componentHostView?.animateInFromTool(inPlace: inPlace, transition: transition)
|
||||
self.requestUpdate(transition: transition)
|
||||
}
|
||||
|
||||
|
|
@ -4682,12 +4867,10 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
|
||||
var location: CLLocationCoordinate2D?
|
||||
if let subject = self.actualSubject {
|
||||
if case let .asset(asset) = subject {
|
||||
location = asset.location?.coordinate
|
||||
} else if case let .draft(draft, _) = subject {
|
||||
location = draft.location
|
||||
}
|
||||
if case let .draft(draft, _) = self.actualSubject {
|
||||
location = draft.location
|
||||
} else if case let .asset(asset) = self.subject {
|
||||
location = asset.location?.coordinate
|
||||
}
|
||||
let locationController = storyLocationPickerController(
|
||||
context: self.context,
|
||||
|
|
@ -5156,7 +5339,6 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|
||||
func addWeather(_ weather: StickerPickerScreen.Weather.LoadedWeather?) {
|
||||
guard let weather else {
|
||||
|
||||
return
|
||||
}
|
||||
let maxWeatherCount = 1
|
||||
|
|
@ -5183,6 +5365,58 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
)
|
||||
}
|
||||
|
||||
func getCaption() -> NSAttributedString {
|
||||
return self.componentHostView?.getInputText() ?? NSAttributedString()
|
||||
}
|
||||
|
||||
func switchToItem(_ identifier: String) {
|
||||
guard let controller = self.controller, let mediaEditor = self.mediaEditor, let itemIndex = self.items.firstIndex(where: { $0.asset.localIdentifier == identifier }), case let .asset(asset) = self.subject, let currentItemIndex = self.items.firstIndex(where: { $0.asset.localIdentifier == asset.localIdentifier }) else {
|
||||
return
|
||||
}
|
||||
|
||||
let entities = self.entitiesView.entities.filter { !($0 is DrawingMediaEntity) }
|
||||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.entitiesView)
|
||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||
|
||||
var updatedCurrentItem = self.items[currentItemIndex]
|
||||
updatedCurrentItem.caption = self.getCaption()
|
||||
|
||||
if mediaEditor.values.hasChanges && updatedCurrentItem.values != mediaEditor.values {
|
||||
updatedCurrentItem.values = mediaEditor.values
|
||||
updatedCurrentItem.version += 1
|
||||
|
||||
if let resultImage = mediaEditor.resultImage {
|
||||
mediaEditor.seek(0.0, andPlay: false)
|
||||
makeEditorImageComposition(
|
||||
context: self.ciContext,
|
||||
postbox: self.context.account.postbox,
|
||||
inputImage: resultImage,
|
||||
dimensions: storyDimensions,
|
||||
values: mediaEditor.values,
|
||||
time: .zero,
|
||||
textScale: 2.0,
|
||||
completion: { [weak self] resultImage in
|
||||
updatedCurrentItem.version += 1
|
||||
updatedCurrentItem.thumbnail = resultImage
|
||||
self?.items[currentItemIndex] = updatedCurrentItem
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
updatedCurrentItem.version += 1
|
||||
self.items[currentItemIndex] = updatedCurrentItem
|
||||
}
|
||||
|
||||
self.entitiesView.clearAll()
|
||||
|
||||
let targetItem = self.items[itemIndex]
|
||||
controller.node.setup(
|
||||
subject: .asset(targetItem.asset),
|
||||
values: targetItem.values,
|
||||
caption: targetItem.caption
|
||||
)
|
||||
}
|
||||
|
||||
func requestCompletion(playHaptic: Bool = true) {
|
||||
guard let controller = self.controller else {
|
||||
return
|
||||
|
|
@ -5284,7 +5518,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
let result = super.hitTest(point, with: event)
|
||||
if result == self.componentHost.view {
|
||||
if result == self.componentHostView {
|
||||
let point = self.view.convert(point, to: self.previewContainerView)
|
||||
if let previewResult = self.previewContainerView.hitTest(point, with: event) {
|
||||
return previewResult
|
||||
|
|
@ -6142,6 +6376,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
case message([MessageId])
|
||||
case gift(StarGift.UniqueGift)
|
||||
case sticker(TelegramMediaFile, [String])
|
||||
case assets([PHAsset])
|
||||
|
||||
var dimensions: PixelDimensions {
|
||||
switch self {
|
||||
|
|
@ -6153,8 +6388,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
return PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight))
|
||||
case let .draft(draft, _):
|
||||
return draft.dimensions
|
||||
case .message, .gift, .sticker, .videoCollage:
|
||||
return PixelDimensions(width: 1080, height: 1920)
|
||||
case .message, .gift, .sticker, .videoCollage, .assets:
|
||||
return PixelDimensions(storyDimensions)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6181,6 +6416,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
return .gift(gift)
|
||||
case let .sticker(sticker, _):
|
||||
return .sticker(sticker)
|
||||
case let .assets(assets):
|
||||
return .asset(assets.first!)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6208,6 +6445,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
return false
|
||||
case .sticker:
|
||||
return false
|
||||
case .assets:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6516,7 +6755,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|
||||
let privacy = privacy ?? self.state.privacy
|
||||
|
||||
let text = self.getCaption().string
|
||||
let text = self.node.getCaption().string
|
||||
let mentions = generateTextEntities(text, enabledTypes: [.mention], currentEntities: []).map { (text as NSString).substring(with: NSRange(location: $0.range.lowerBound + 1, length: $0.range.upperBound - $0.range.lowerBound - 1)) }
|
||||
|
||||
let coverImage: UIImage?
|
||||
|
|
@ -6528,7 +6767,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: self.context,
|
||||
subject: .stories(editing: false),
|
||||
subject: .stories(editing: false, count: Int32(self.node.items.count(where: { $0.isEnabled }))),
|
||||
editing: false,
|
||||
initialPeerIds: Set(privacy.privacy.additionallyIncludePeers),
|
||||
closeFriends: self.closeFriends.get(),
|
||||
|
|
@ -7066,13 +7305,9 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
self?.dismissed()
|
||||
})
|
||||
}
|
||||
|
||||
func getCaption() -> NSAttributedString {
|
||||
return (self.node.componentHost.view as? MediaEditorScreenComponent.View)?.getInputText() ?? NSAttributedString()
|
||||
}
|
||||
|
||||
|
||||
fileprivate func checkCaptionLimit() -> Bool {
|
||||
let caption = self.getCaption()
|
||||
let caption = self.node.getCaption()
|
||||
if caption.length > self.context.userLimits.maxStoryCaptionLength {
|
||||
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
|
|
@ -7121,7 +7356,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||
|
||||
var caption = self.getCaption()
|
||||
var caption = self.node.getCaption()
|
||||
caption = convertMarkdownToAttributes(caption)
|
||||
|
||||
var hasEntityChanges = false
|
||||
|
|
@ -7170,7 +7405,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
|
||||
if self.isEmbeddedEditor && !(hasAnyChanges || hasEntityChanges) {
|
||||
self.saveDraft(id: randomId, edit: true)
|
||||
self.saveDraft(id: randomId, isEdit: true)
|
||||
|
||||
self.completion(MediaEditorScreenImpl.Result(media: nil, mediaAreas: [], caption: caption, coverTimestamp: mediaEditor.values.coverImageTimestamp, options: self.state.privacy, stickers: stickers, randomId: randomId), { [weak self] finished in
|
||||
self?.node.animateOut(finished: true, saveDraft: false, completion: { [weak self] in
|
||||
|
|
@ -7459,7 +7694,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
duration = 5.0
|
||||
case .sticker:
|
||||
let image = generateImage(CGSize(width: 1080, height: 1920), contextGenerator: { size, context in
|
||||
let image = generateImage(storyDimensions, contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
}, opaque: false, scale: 1.0)
|
||||
let tempImagePath = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max)).png"
|
||||
|
|
@ -7470,6 +7705,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
duration = 3.0
|
||||
|
||||
firstFrame = .single((image, nil))
|
||||
case .assets:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let _ = combineLatest(queue: Queue.mainQueue(), firstFrame, videoResult)
|
||||
|
|
@ -8360,6 +8597,8 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
case let .sticker(file, _):
|
||||
exportSubject = .single(.sticker(file: file))
|
||||
case .assets:
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let _ = (exportSubject
|
||||
|
|
|
|||
|
|
@ -11,7 +11,16 @@ import DrawingUI
|
|||
|
||||
extension MediaEditorScreenImpl {
|
||||
func isEligibleForDraft() -> Bool {
|
||||
if self.isEditingStory {
|
||||
guard !self.isEditingStory else {
|
||||
return false
|
||||
}
|
||||
if case .avatarEditor = self.mode {
|
||||
return false
|
||||
}
|
||||
if case .coverEditor = self.mode {
|
||||
return false
|
||||
}
|
||||
if case .assets = self.node.actualSubject {
|
||||
return false
|
||||
}
|
||||
guard let mediaEditor = self.node.mediaEditor else {
|
||||
|
|
@ -21,13 +30,6 @@ extension MediaEditorScreenImpl {
|
|||
let codableEntities = DrawingEntitiesView.encodeEntities(entities, entitiesView: self.node.entitiesView)
|
||||
mediaEditor.setDrawingAndEntities(data: nil, image: mediaEditor.values.drawing, entities: codableEntities)
|
||||
|
||||
if case .avatarEditor = self.mode {
|
||||
return false
|
||||
}
|
||||
if case .coverEditor = self.mode {
|
||||
return false
|
||||
}
|
||||
|
||||
let filteredEntities = self.node.entitiesView.entities.filter { entity in
|
||||
if entity is DrawingMediaEntity {
|
||||
return false
|
||||
|
|
@ -44,34 +46,42 @@ extension MediaEditorScreenImpl {
|
|||
|
||||
let values = mediaEditor.values
|
||||
let filteredValues = values.withUpdatedEntities([])
|
||||
let caption = self.node.getCaption()
|
||||
|
||||
let caption = self.getCaption()
|
||||
if let subject = self.node.subject {
|
||||
if case .asset = subject, !values.hasChanges && caption.string.isEmpty {
|
||||
return false
|
||||
} else if case .message = subject, !filteredValues.hasChanges && filteredEntities.isEmpty && caption.string.isEmpty {
|
||||
return false
|
||||
} else if case .gift = subject, !filteredValues.hasChanges && filteredEntities.isEmpty && caption.string.isEmpty {
|
||||
return false
|
||||
} else if case .empty = subject, !self.node.hasAnyChanges && !self.node.drawingView.internalState.canUndo {
|
||||
return false
|
||||
} else if case .videoCollage = subject {
|
||||
switch subject {
|
||||
case .asset:
|
||||
if !values.hasChanges && caption.string.isEmpty {
|
||||
return false
|
||||
}
|
||||
case .message, .gift:
|
||||
if !filteredValues.hasChanges && filteredEntities.isEmpty && caption.string.isEmpty {
|
||||
return false
|
||||
}
|
||||
case .empty:
|
||||
if !self.node.hasAnyChanges && !self.node.drawingView.internalState.canUndo {
|
||||
return false
|
||||
}
|
||||
case .videoCollage:
|
||||
return false
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func saveDraft(id: Int64?, edit: Bool = false) {
|
||||
func saveDraft(id: Int64?, isEdit: Bool = false, completion: ((MediaEditorDraft) -> Void)? = nil) {
|
||||
guard case .storyEditor = self.mode, let subject = self.node.subject, let actualSubject = self.node.actualSubject, let mediaEditor = self.node.mediaEditor else {
|
||||
return
|
||||
}
|
||||
|
||||
try? FileManager.default.createDirectory(atPath: draftPath(engine: self.context.engine), withIntermediateDirectories: true)
|
||||
|
||||
let values = mediaEditor.values
|
||||
let privacy = self.state.privacy
|
||||
let forwardSource = self.forwardSource
|
||||
let caption = self.getCaption()
|
||||
let caption = self.node.getCaption()
|
||||
let duration = mediaEditor.duration ?? 0.0
|
||||
|
||||
let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
|
|
@ -99,10 +109,19 @@ extension MediaEditorScreenImpl {
|
|||
}
|
||||
|
||||
if let resultImage = mediaEditor.resultImage {
|
||||
if !edit {
|
||||
if !isEdit {
|
||||
mediaEditor.seek(0.0, andPlay: false)
|
||||
}
|
||||
makeEditorImageComposition(context: self.node.ciContext, postbox: self.context.account.postbox, inputImage: resultImage, dimensions: storyDimensions, values: values, time: .zero, textScale: 2.0, completion: { resultImage in
|
||||
|
||||
makeEditorImageComposition(
|
||||
context: self.node.ciContext,
|
||||
postbox: self.context.account.postbox,
|
||||
inputImage: resultImage,
|
||||
dimensions: storyDimensions,
|
||||
values: values,
|
||||
time: .zero,
|
||||
textScale: 2.0,
|
||||
completion: { resultImage in
|
||||
guard let resultImage else {
|
||||
return
|
||||
}
|
||||
|
|
@ -148,54 +167,64 @@ extension MediaEditorScreenImpl {
|
|||
}
|
||||
|
||||
let context = self.context
|
||||
func innerSaveDraft(media: MediaInput) {
|
||||
func innerSaveDraft(media: MediaInput, save: Bool = true) -> MediaEditorDraft? {
|
||||
let fittedSize = resultImage.size.aspectFitted(CGSize(width: 128.0, height: 128.0))
|
||||
if let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) {
|
||||
let path = "\(Int64.random(in: .min ... .max)).\(media.fileExtension)"
|
||||
let draft = MediaEditorDraft(
|
||||
path: path,
|
||||
isVideo: media.isVideo,
|
||||
thumbnail: thumbnailImage,
|
||||
dimensions: media.dimensions,
|
||||
duration: media.duration,
|
||||
values: values,
|
||||
caption: caption,
|
||||
privacy: privacy,
|
||||
forwardInfo: forwardSource.flatMap { StoryId(peerId: $0.0.id, id: $0.1.id) },
|
||||
timestamp: timestamp,
|
||||
location: location,
|
||||
expiresOn: expiresOn
|
||||
)
|
||||
switch media {
|
||||
case let .image(image, _):
|
||||
if let data = image.jpegData(compressionQuality: 0.87) {
|
||||
try? data.write(to: URL(fileURLWithPath: draft.fullPath(engine: context.engine)))
|
||||
}
|
||||
case let .video(path, _, _):
|
||||
try? FileManager.default.copyItem(atPath: path, toPath: draft.fullPath(engine: context.engine))
|
||||
guard let thumbnailImage = generateScaledImage(image: resultImage, size: fittedSize) else {
|
||||
return nil
|
||||
}
|
||||
let path = "\(Int64.random(in: .min ... .max)).\(media.fileExtension)"
|
||||
let draft = MediaEditorDraft(
|
||||
path: path,
|
||||
isVideo: media.isVideo,
|
||||
thumbnail: thumbnailImage,
|
||||
dimensions: media.dimensions,
|
||||
duration: media.duration,
|
||||
values: values,
|
||||
caption: caption,
|
||||
privacy: privacy,
|
||||
forwardInfo: forwardSource.flatMap { StoryId(peerId: $0.0.id, id: $0.1.id) },
|
||||
timestamp: timestamp,
|
||||
location: location,
|
||||
expiresOn: expiresOn
|
||||
)
|
||||
switch media {
|
||||
case let .image(image, _):
|
||||
if let data = image.jpegData(compressionQuality: 0.87) {
|
||||
try? data.write(to: URL(fileURLWithPath: draft.fullPath(engine: context.engine)))
|
||||
}
|
||||
case let .video(path, _, _):
|
||||
try? FileManager.default.copyItem(atPath: path, toPath: draft.fullPath(engine: context.engine))
|
||||
}
|
||||
if save {
|
||||
if let id {
|
||||
saveStorySource(engine: context.engine, item: draft, peerId: context.account.peerId, id: id)
|
||||
} else {
|
||||
addStoryDraft(engine: context.engine, item: draft)
|
||||
}
|
||||
}
|
||||
return draft
|
||||
}
|
||||
|
||||
switch subject {
|
||||
case .empty:
|
||||
break
|
||||
case let .image(image, dimensions, _, _):
|
||||
innerSaveDraft(media: .image(image: image, dimensions: dimensions))
|
||||
if let draft = innerSaveDraft(media: .image(image: image, dimensions: dimensions)) {
|
||||
completion?(draft)
|
||||
}
|
||||
case let .video(path, _, _, _, _, dimensions, _, _, _):
|
||||
innerSaveDraft(media: .video(path: path, dimensions: dimensions, duration: duration))
|
||||
if let draft = innerSaveDraft(media: .video(path: path, dimensions: dimensions, duration: duration)) {
|
||||
completion?(draft)
|
||||
}
|
||||
case let .videoCollage(items):
|
||||
let _ = items
|
||||
case let .asset(asset):
|
||||
if asset.mediaType == .video {
|
||||
PHImageManager.default().requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in
|
||||
if let urlAsset = avAsset as? AVURLAsset {
|
||||
innerSaveDraft(media: .video(path: urlAsset.url.relativePath, dimensions: PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)), duration: duration))
|
||||
if let draft = innerSaveDraft(media: .video(path: urlAsset.url.relativePath, dimensions: PixelDimensions(width: Int32(asset.pixelWidth), height: Int32(asset.pixelHeight)), duration: duration)) {
|
||||
completion?(draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -203,22 +232,32 @@ extension MediaEditorScreenImpl {
|
|||
options.deliveryMode = .highQualityFormat
|
||||
PHImageManager.default().requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .default, options: options) { image, _ in
|
||||
if let image {
|
||||
innerSaveDraft(media: .image(image: image, dimensions: PixelDimensions(image.size)))
|
||||
if let draft = innerSaveDraft(media: .image(image: image, dimensions: PixelDimensions(image.size))) {
|
||||
completion?(draft)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .draft(draft, _):
|
||||
if draft.isVideo {
|
||||
innerSaveDraft(media: .video(path: draft.fullPath(engine: context.engine), dimensions: draft.dimensions, duration: draft.duration ?? 0.0))
|
||||
if let draft = innerSaveDraft(media: .video(path: draft.fullPath(engine: context.engine), dimensions: draft.dimensions, duration: draft.duration ?? 0.0)) {
|
||||
completion?(draft)
|
||||
}
|
||||
} else if let image = UIImage(contentsOfFile: draft.fullPath(engine: context.engine)) {
|
||||
innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions))
|
||||
if let draft = innerSaveDraft(media: .image(image: image, dimensions: draft.dimensions)) {
|
||||
completion?(draft)
|
||||
}
|
||||
}
|
||||
case .message, .gift:
|
||||
if let pixel = generateSingleColorImage(size: CGSize(width: 1, height: 1), color: .black) {
|
||||
innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920)))
|
||||
if let draft = innerSaveDraft(media: .image(image: pixel, dimensions: PixelDimensions(width: 1080, height: 1920))) {
|
||||
completion?(draft)
|
||||
}
|
||||
}
|
||||
case .sticker:
|
||||
break
|
||||
case .assets:
|
||||
break
|
||||
}
|
||||
|
||||
if case let .draft(draft, _) = actualSubject {
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
|
||||
final class SelectionPanelButtonContentComponent: Component {
|
||||
let count: Int32
|
||||
let isSelected: Bool
|
||||
let tag: AnyObject?
|
||||
|
||||
init(
|
||||
count: Int32,
|
||||
isSelected: Bool,
|
||||
tag: AnyObject?
|
||||
) {
|
||||
self.count = count
|
||||
self.isSelected = isSelected
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
static func ==(lhs: SelectionPanelButtonContentComponent, rhs: SelectionPanelButtonContentComponent) -> Bool {
|
||||
return lhs.count == rhs.count && lhs.isSelected == rhs.isSelected
|
||||
}
|
||||
|
||||
final class View: UIView, ComponentTaggedView {
|
||||
private var component: SelectionPanelButtonContentComponent?
|
||||
public func matches(tag: Any) -> Bool {
|
||||
if let component = self.component, let componentTag = component.tag {
|
||||
let tag = tag as AnyObject
|
||||
if componentTag === tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
let backgroundView: BlurredBackgroundView
|
||||
private let outline = SimpleLayer()
|
||||
private let icon = SimpleLayer()
|
||||
private let label = ComponentView<Empty>()
|
||||
|
||||
init() {
|
||||
self.backgroundView = BlurredBackgroundView(color: UIColor(white: 0.2, alpha: 0.45), enableBlur: true)
|
||||
self.icon.opacity = 0.0
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.layer.addSublayer(self.icon)
|
||||
self.layer.addSublayer(self.outline)
|
||||
|
||||
self.outline.contents = generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
let lineWidth: CGFloat = 2.0 - UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
context.strokeEllipse(in: bounds.insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0))
|
||||
})?.cgImage
|
||||
|
||||
self.icon.contents = generateImage(CGSize(width: 33.0, height: 33.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: .zero, size: size)
|
||||
context.clear(bounds)
|
||||
let lineWidth: CGFloat = 2.0 - UIScreenPixel
|
||||
context.setLineWidth(lineWidth)
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 11.0, y: 11.0))
|
||||
context.addLine(to: CGPoint(x: size.width - 11.0, y: size.height - 11.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: size.width - 11.0, y: 11.0))
|
||||
context.addLine(to: CGPoint(x: 11.0, y: size.height - 11.0))
|
||||
context.strokePath()
|
||||
})?.cgImage
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
func update(component: SelectionPanelButtonContentComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
|
||||
let size = CGSize(width: 33.0, height: 33.0)
|
||||
let backgroundFrame = CGRect(origin: .zero, size: size)
|
||||
|
||||
self.backgroundView.frame = backgroundFrame
|
||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: backgroundFrame.width / 2.0, transition: .immediate)
|
||||
|
||||
self.icon.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
self.icon.bounds = CGRect(origin: .zero, size: size)
|
||||
|
||||
self.outline.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
self.outline.bounds = CGRect(origin: .zero, size: size)
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
Text(
|
||||
text: "\(component.count)",
|
||||
font: Font.with(size: 18.0, design: .round, weight: .semibold),
|
||||
color: .white
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: size
|
||||
)
|
||||
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
|
||||
if let labelView = self.label.view {
|
||||
if labelView.superview == nil {
|
||||
self.addSubview(labelView)
|
||||
}
|
||||
labelView.center = labelFrame.center
|
||||
labelView.bounds = CGRect(origin: .zero, size: labelFrame.size)
|
||||
}
|
||||
|
||||
if (previousComponent?.isSelected ?? false) != component.isSelected {
|
||||
let changeTransition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
changeTransition.setAlpha(layer: self.icon, alpha: component.isSelected ? 1.0 : 0.0)
|
||||
changeTransition.setTransform(layer: self.icon, transform: !component.isSelected ? CATransform3DMakeRotation(.pi / 4.0, 0.0, 0.0, 1.0) : CATransform3DIdentity)
|
||||
if let labelView = self.label.view {
|
||||
changeTransition.setAlpha(view: labelView, alpha: component.isSelected ? 0.0 : 1.0)
|
||||
changeTransition.setTransform(view: labelView, transform: component.isSelected ? CATransform3DMakeRotation(-.pi / 4.0, 0.0, 0.0, 1.0) : CATransform3DIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,670 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import MediaEditor
|
||||
import MediaAssetsContext
|
||||
import CheckNode
|
||||
import TelegramPresentationData
|
||||
|
||||
final class SelectionPanelComponent: Component {
|
||||
let previewContainerView: PortalSourceView
|
||||
let frame: CGRect
|
||||
let items: [MediaEditorScreenImpl.EditingItem]
|
||||
let selectedItemId: String
|
||||
let itemTapped: (String?) -> Void
|
||||
let itemSelectionToggled: (String) -> Void
|
||||
let itemReordered: (String, String) -> Void
|
||||
|
||||
init(
|
||||
previewContainerView: PortalSourceView,
|
||||
frame: CGRect,
|
||||
items: [MediaEditorScreenImpl.EditingItem],
|
||||
selectedItemId: String,
|
||||
itemTapped: @escaping (String?) -> Void,
|
||||
itemSelectionToggled: @escaping (String) -> Void,
|
||||
itemReordered: @escaping (String, String) -> Void
|
||||
) {
|
||||
self.previewContainerView = previewContainerView
|
||||
self.frame = frame
|
||||
self.items = items
|
||||
self.selectedItemId = selectedItemId
|
||||
self.itemTapped = itemTapped
|
||||
self.itemSelectionToggled = itemSelectionToggled
|
||||
self.itemReordered = itemReordered
|
||||
}
|
||||
|
||||
static func ==(lhs: SelectionPanelComponent, rhs: SelectionPanelComponent) -> Bool {
|
||||
return lhs.frame == rhs.frame && lhs.items == rhs.items && lhs.selectedItemId == rhs.selectedItemId
|
||||
}
|
||||
|
||||
final class View: UIView, UIGestureRecognizerDelegate {
|
||||
final class ItemView: UIView {
|
||||
private let backgroundNode: ASImageNode
|
||||
private let imageNode: ImageNode
|
||||
private let checkNode: InteractiveCheckNode
|
||||
private var selectionLayer: SimpleShapeLayer?
|
||||
|
||||
var toggleSelection: () -> Void = {}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.imageNode = ImageNode()
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
|
||||
self.checkNode = InteractiveCheckNode(theme: CheckNodeTheme(theme: defaultDarkColorPresentationTheme, style: .overlay))
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipsToBounds = true
|
||||
self.layer.cornerRadius = 6.0
|
||||
|
||||
self.addSubview(self.backgroundNode.view)
|
||||
self.addSubview(self.imageNode.view)
|
||||
self.addSubview(self.checkNode.view)
|
||||
|
||||
self.checkNode.valueChanged = { [weak self] value in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.toggleSelection()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
fileprivate var item: MediaEditorScreenImpl.EditingItem?
|
||||
func update(item: MediaEditorScreenImpl.EditingItem, number: Int, isSelected: Bool, isEnabled: Bool, size: CGSize, portalView: PortalView?, transition: ComponentTransition) {
|
||||
let previousItem = self.item
|
||||
self.item = item
|
||||
|
||||
if previousItem?.asset.localIdentifier != item.asset.localIdentifier || previousItem?.version != item.version {
|
||||
let imageSignal: Signal<UIImage?, NoError>
|
||||
if let thumbnail = item.thumbnail {
|
||||
imageSignal = .single(thumbnail)
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
} else {
|
||||
imageSignal = assetImage(asset: item.asset, targetSize:CGSize(width: 128.0 * UIScreenScale, height: 128.0 * UIScreenScale), exact: false, synchronous: true)
|
||||
self.imageNode.contentUpdated = { [weak self] image in
|
||||
if let self {
|
||||
if self.backgroundNode.image == nil {
|
||||
if let image, image.size.width > image.size.height {
|
||||
self.imageNode.contentMode = .scaleAspectFit
|
||||
Queue.concurrentDefaultQueue().async {
|
||||
let colors = mediaEditorGetGradientColors(from: image)
|
||||
let gradientImage = mediaEditorGenerateGradientImage(size: CGSize(width: 3.0, height: 128.0), colors: colors.array)
|
||||
Queue.mainQueue().async {
|
||||
self.backgroundNode.image = gradientImage
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.imageNode.contentMode = .scaleAspectFill
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.imageNode.setSignal(imageSignal)
|
||||
}
|
||||
|
||||
let backgroundSize = CGSize(width: size.width, height: floorToScreenPixels(size.width / 9.0 * 16.0))
|
||||
self.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - backgroundSize.height) / 2.0)), size: backgroundSize)
|
||||
|
||||
self.imageNode.frame = CGRect(origin: .zero, size: size)
|
||||
|
||||
//self.checkNode.content = .counter(number)
|
||||
self.checkNode.setSelected(isEnabled, animated: previousItem != nil)
|
||||
|
||||
let checkSize = CGSize(width: 29.0, height: 29.0)
|
||||
self.checkNode.frame = CGRect(origin: CGPoint(x: size.width - checkSize.width - 4.0, y: 4.0), size: checkSize)
|
||||
|
||||
if isSelected, let portalView {
|
||||
portalView.view.frame = CGRect(origin: .zero, size: size)
|
||||
self.insertSubview(portalView.view, aboveSubview: self.imageNode.view)
|
||||
}
|
||||
|
||||
let lineWidth: CGFloat = 2.0 - UIScreenPixel
|
||||
let selectionFrame = CGRect(origin: .zero, size: size)
|
||||
if isSelected {
|
||||
let selectionLayer: SimpleShapeLayer
|
||||
if let current = self.selectionLayer {
|
||||
selectionLayer = current
|
||||
} else {
|
||||
selectionLayer = SimpleShapeLayer()
|
||||
self.selectionLayer = selectionLayer
|
||||
self.layer.addSublayer(selectionLayer)
|
||||
|
||||
selectionLayer.fillColor = UIColor.clear.cgColor
|
||||
selectionLayer.strokeColor = UIColor.white.cgColor
|
||||
selectionLayer.lineWidth = lineWidth
|
||||
selectionLayer.frame = selectionFrame
|
||||
selectionLayer.path = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: lineWidth / 2.0, dy: lineWidth / 2.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
|
||||
// if !transition.animation.isImmediate {
|
||||
// let initialPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
// selectionLayer.animate(from: initialPath, to: selectionLayer.path as AnyObject, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2)
|
||||
// selectionLayer.animateShapeLineWidth(from: 0.0, to: lineWidth, duration: 0.2)
|
||||
// }
|
||||
}
|
||||
|
||||
} else if let selectionLayer = self.selectionLayer {
|
||||
self.selectionLayer = nil
|
||||
selectionLayer.removeFromSuperlayer()
|
||||
|
||||
// let targetPath = CGPath(roundedRect: CGRect(origin: .zero, size: selectionFrame.size).insetBy(dx: 0.0, dy: 0.0), cornerWidth: 6.0, cornerHeight: 6.0, transform: nil)
|
||||
// selectionLayer.animate(from: selectionLayer.path, to: targetPath, keyPath: "path", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2, removeOnCompletion: false)
|
||||
// selectionLayer.animateShapeLineWidth(from: selectionLayer.lineWidth, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
// selectionLayer.removeFromSuperlayer()
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let backgroundView: BlurredBackgroundView
|
||||
private let backgroundMaskView: UIView
|
||||
private let backgroundMaskPanelView: UIView
|
||||
|
||||
private let scrollView: UIScrollView
|
||||
private var itemViews: [AnyHashable: ItemView] = [:]
|
||||
private var portalView: PortalView?
|
||||
|
||||
private var reorderRecognizer: ReorderGestureRecognizer?
|
||||
private var reorderingItem: (id: AnyHashable, initialPosition: CGPoint, position: CGPoint, snapshotView: UIView)?
|
||||
|
||||
private var tapRecognizer: UITapGestureRecognizer?
|
||||
|
||||
private var component: SelectionPanelComponent?
|
||||
private var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundView = BlurredBackgroundView(color: UIColor(white: 0.2, alpha: 0.45), enableBlur: true)
|
||||
self.backgroundMaskView = UIView(frame: .zero)
|
||||
|
||||
self.backgroundMaskPanelView = UIView(frame: .zero)
|
||||
self.backgroundMaskPanelView.backgroundColor = UIColor.white
|
||||
self.backgroundMaskPanelView.clipsToBounds = true
|
||||
self.backgroundMaskPanelView.layer.cornerRadius = 10.0
|
||||
|
||||
self.scrollView = UIScrollView(frame: .zero)
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.layer.cornerRadius = 10.0
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundView.mask = self.backgroundMaskView
|
||||
|
||||
let reorderRecognizer = ReorderGestureRecognizer(
|
||||
shouldBegin: { [weak self] point in
|
||||
guard let self, let item = self.item(at: point) else {
|
||||
return (allowed: false, requiresLongPress: false, item: nil)
|
||||
}
|
||||
|
||||
return (allowed: true, requiresLongPress: true, item: item)
|
||||
},
|
||||
willBegin: { point in
|
||||
},
|
||||
began: { [weak self] item in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.setReorderingItem(item: item)
|
||||
},
|
||||
ended: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.setReorderingItem(item: nil)
|
||||
},
|
||||
moved: { [weak self] distance in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.moveReorderingItem(distance: distance)
|
||||
},
|
||||
isActiveUpdated: { _ in
|
||||
}
|
||||
)
|
||||
reorderRecognizer.delegate = self
|
||||
self.reorderRecognizer = reorderRecognizer
|
||||
self.addGestureRecognizer(reorderRecognizer)
|
||||
|
||||
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap))
|
||||
self.tapRecognizer = tapRecognizer
|
||||
self.addGestureRecognizer(tapRecognizer)
|
||||
|
||||
self.addSubview(self.backgroundView)
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.backgroundMaskView.addSubview(self.backgroundMaskPanelView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.reorderRecognizer?.isEnabled = false
|
||||
self.reorderRecognizer?.isEnabled = true
|
||||
|
||||
let location = gestureRecognizer.location(in: self)
|
||||
if let itemView = self.item(at: location), let item = itemView.item, item.asset.localIdentifier != component.selectedItemId {
|
||||
component.itemTapped(item.asset.localIdentifier)
|
||||
} else {
|
||||
component.itemTapped(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
if otherGestureRecognizer is UITapGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
if otherGestureRecognizer is UIPanGestureRecognizer {
|
||||
if gestureRecognizer === self.reorderRecognizer, ![.began, .changed].contains(gestureRecognizer.state) {
|
||||
gestureRecognizer.isEnabled = false
|
||||
gestureRecognizer.isEnabled = true
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func item(at point: CGPoint) -> ItemView? {
|
||||
let point = self.convert(point, to: self.scrollView)
|
||||
for (_, itemView) in self.itemViews {
|
||||
if itemView.frame.contains(point) {
|
||||
return itemView
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setReorderingItem(item: ItemView?) {
|
||||
self.tapRecognizer?.isEnabled = false
|
||||
self.tapRecognizer?.isEnabled = true
|
||||
|
||||
var mappedItem: (AnyHashable, ItemView)?
|
||||
if let item {
|
||||
for (id, visibleItem) in self.itemViews {
|
||||
if visibleItem === item {
|
||||
mappedItem = (id, visibleItem)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.reorderingItem?.id != mappedItem?.0 {
|
||||
let transition: ComponentTransition = .spring(duration: 0.4)
|
||||
if let (id, itemView) = mappedItem, let snapshotView = itemView.snapshotView(afterScreenUpdates: false) {
|
||||
itemView.isHidden = true
|
||||
|
||||
let position = self.scrollView.convert(itemView.center, to: self)
|
||||
snapshotView.center = position
|
||||
transition.setScale(view: snapshotView, scale: 0.9)
|
||||
self.addSubview(snapshotView)
|
||||
|
||||
self.reorderingItem = (id, position, position, snapshotView)
|
||||
} else {
|
||||
if let (id, _, _, snapshotView) = self.reorderingItem {
|
||||
if let itemView = self.itemViews[id] {
|
||||
if let innerSnapshotView = snapshotView.snapshotView(afterScreenUpdates: false) {
|
||||
innerSnapshotView.center = self.convert(snapshotView.center, to: self.scrollView)
|
||||
innerSnapshotView.transform = CGAffineTransformMakeScale(0.9, 0.9)
|
||||
self.scrollView.addSubview(innerSnapshotView)
|
||||
|
||||
transition.setPosition(view: innerSnapshotView, position: itemView.center, completion: { [weak innerSnapshotView] _ in
|
||||
innerSnapshotView?.removeFromSuperview()
|
||||
itemView.isHidden = false
|
||||
})
|
||||
transition.setScale(view: innerSnapshotView, scale: 1.0)
|
||||
}
|
||||
|
||||
transition.setPosition(view: snapshotView, position: self.scrollView.convert(itemView.center, to: self), completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
transition.setScale(view: snapshotView, scale: 1.0)
|
||||
transition.setAlpha(view: snapshotView, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
self.reorderingItem = nil
|
||||
}
|
||||
self.state?.updated(transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func moveReorderingItem(distance: CGPoint) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
if let (id, initialPosition, _, snapshotView) = self.reorderingItem {
|
||||
let targetPosition = CGPoint(x: initialPosition.x + distance.x, y: initialPosition.y + distance.y)
|
||||
self.reorderingItem = (id, initialPosition, targetPosition, snapshotView)
|
||||
snapshotView.center = targetPosition
|
||||
|
||||
let mappedPosition = self.convert(targetPosition, to: self.scrollView)
|
||||
|
||||
if let visibleReorderingItem = self.itemViews[id], let fromId = self.itemViews[id]?.item?.asset.localIdentifier {
|
||||
for (_, visibleItem) in self.itemViews {
|
||||
if visibleItem === visibleReorderingItem {
|
||||
continue
|
||||
}
|
||||
if visibleItem.frame.contains(mappedPosition), let toId = visibleItem.item?.asset.localIdentifier {
|
||||
component.itemReordered(fromId, toId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn(from buttonView: SelectionPanelButtonContentComponent.View) {
|
||||
|
||||
}
|
||||
|
||||
func animateOut(to buttonView: SelectionPanelButtonContentComponent.View, completion: @escaping () -> Void) {
|
||||
completion()
|
||||
}
|
||||
|
||||
func update(component: SelectionPanelComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if self.portalView == nil {
|
||||
if let portalView = PortalView(matchPosition: false) {
|
||||
portalView.view.layer.rasterizationScale = UIScreenScale
|
||||
|
||||
let scale = 95.0 / component.previewContainerView.frame.width
|
||||
portalView.view.transform = CGAffineTransformMakeScale(scale, scale)
|
||||
|
||||
component.previewContainerView.addPortal(view: portalView)
|
||||
self.portalView = portalView
|
||||
}
|
||||
}
|
||||
|
||||
var validIds = Set<AnyHashable>()
|
||||
|
||||
let itemSize = CGSize(width: 95.0, height: 112.0)
|
||||
let spacing: CGFloat = 4.0
|
||||
|
||||
var itemFrame: CGRect = CGRect(origin: CGPoint(x: spacing, y: spacing), size: itemSize)
|
||||
|
||||
var index = 1
|
||||
for item in component.items {
|
||||
let id = item.asset.localIdentifier
|
||||
validIds.insert(id)
|
||||
|
||||
var itemTransition = transition
|
||||
let itemView: ItemView
|
||||
if let current = self.itemViews[id] {
|
||||
itemView = current
|
||||
} else {
|
||||
itemView = ItemView(frame: itemFrame)
|
||||
self.scrollView.addSubview(itemView)
|
||||
self.itemViews[id] = itemView
|
||||
|
||||
itemTransition = .immediate
|
||||
}
|
||||
itemView.toggleSelection = { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.itemSelectionToggled(id)
|
||||
}
|
||||
itemView.update(item: item, number: index, isSelected: item.asset.localIdentifier == component.selectedItemId, isEnabled: item.isEnabled, size: itemFrame.size, portalView: self.portalView, transition: itemTransition)
|
||||
|
||||
itemTransition.setBounds(view: itemView, bounds: CGRect(origin: .zero, size: itemFrame.size))
|
||||
itemTransition.setPosition(view: itemView, position: itemFrame.center)
|
||||
|
||||
itemFrame.origin.x += itemSize.width + spacing
|
||||
index += 1
|
||||
}
|
||||
|
||||
var removeIds: [AnyHashable] = []
|
||||
for (id, itemView) in self.itemViews {
|
||||
if !validIds.contains(id) {
|
||||
removeIds.append(id)
|
||||
transition.setAlpha(view: itemView, alpha: 0.0, completion: { [weak itemView] _ in
|
||||
itemView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
for id in removeIds {
|
||||
self.itemViews.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
let contentSize = CGSize(width: itemFrame.minX, height: itemSize.height + spacing * 2.0)
|
||||
if self.scrollView.contentSize != contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
|
||||
let backgroundSize = CGSize(width: min(availableSize.width - 24.0, contentSize.width), height: contentSize.height)
|
||||
self.backgroundView.frame = CGRect(origin: .zero, size: availableSize)
|
||||
self.backgroundView.update(size: availableSize, transition: .immediate)
|
||||
|
||||
let contentFrame = CGRect(origin: CGPoint(x: availableSize.width - 12.0 - backgroundSize.width, y: component.frame.minY), size: backgroundSize)
|
||||
self.backgroundMaskPanelView.frame = contentFrame
|
||||
self.scrollView.frame = contentFrame
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ReorderGestureRecognizer: UIGestureRecognizer {
|
||||
private let shouldBegin: (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, item: SelectionPanelComponent.View.ItemView?)
|
||||
private let willBegin: (CGPoint) -> Void
|
||||
private let began: (SelectionPanelComponent.View.ItemView) -> Void
|
||||
private let ended: () -> Void
|
||||
private let moved: (CGPoint) -> Void
|
||||
private let isActiveUpdated: (Bool) -> Void
|
||||
|
||||
private var initialLocation: CGPoint?
|
||||
private var longTapTimer: SwiftSignalKit.Timer?
|
||||
private var longPressTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var itemView: SelectionPanelComponent.View.ItemView?
|
||||
|
||||
public init(shouldBegin: @escaping (CGPoint) -> (allowed: Bool, requiresLongPress: Bool, item: SelectionPanelComponent.View.ItemView?), willBegin: @escaping (CGPoint) -> Void, began: @escaping (SelectionPanelComponent.View.ItemView) -> Void, ended: @escaping () -> Void, moved: @escaping (CGPoint) -> Void, isActiveUpdated: @escaping (Bool) -> Void) {
|
||||
self.shouldBegin = shouldBegin
|
||||
self.willBegin = willBegin
|
||||
self.began = began
|
||||
self.ended = ended
|
||||
self.moved = moved
|
||||
self.isActiveUpdated = isActiveUpdated
|
||||
|
||||
super.init(target: nil, action: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.longTapTimer?.invalidate()
|
||||
self.longPressTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func startLongTapTimer() {
|
||||
self.longTapTimer?.invalidate()
|
||||
let longTapTimer = SwiftSignalKit.Timer(timeout: 0.25, repeat: false, completion: { [weak self] in
|
||||
self?.longTapTimerFired()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.longTapTimer = longTapTimer
|
||||
longTapTimer.start()
|
||||
}
|
||||
|
||||
private func stopLongTapTimer() {
|
||||
self.itemView = nil
|
||||
self.longTapTimer?.invalidate()
|
||||
self.longTapTimer = nil
|
||||
}
|
||||
|
||||
private func startLongPressTimer() {
|
||||
self.longPressTimer?.invalidate()
|
||||
let longPressTimer = SwiftSignalKit.Timer(timeout: 0.6, repeat: false, completion: { [weak self] in
|
||||
self?.longPressTimerFired()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.longPressTimer = longPressTimer
|
||||
longPressTimer.start()
|
||||
}
|
||||
|
||||
private func stopLongPressTimer() {
|
||||
self.itemView = nil
|
||||
self.longPressTimer?.invalidate()
|
||||
self.longPressTimer = nil
|
||||
}
|
||||
|
||||
override public func reset() {
|
||||
super.reset()
|
||||
|
||||
self.itemView = nil
|
||||
self.stopLongTapTimer()
|
||||
self.stopLongPressTimer()
|
||||
self.initialLocation = nil
|
||||
|
||||
self.isActiveUpdated(false)
|
||||
}
|
||||
|
||||
private func longTapTimerFired() {
|
||||
guard let location = self.initialLocation else {
|
||||
return
|
||||
}
|
||||
|
||||
self.longTapTimer?.invalidate()
|
||||
self.longTapTimer = nil
|
||||
|
||||
self.willBegin(location)
|
||||
}
|
||||
|
||||
private func longPressTimerFired() {
|
||||
guard let _ = self.initialLocation else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isActiveUpdated(true)
|
||||
self.state = .began
|
||||
self.longPressTimer?.invalidate()
|
||||
self.longPressTimer = nil
|
||||
self.longTapTimer?.invalidate()
|
||||
self.longTapTimer = nil
|
||||
if let itemView = self.itemView {
|
||||
self.began(itemView)
|
||||
}
|
||||
self.isActiveUpdated(true)
|
||||
}
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if self.numberOfTouches > 1 {
|
||||
self.isActiveUpdated(false)
|
||||
self.state = .failed
|
||||
self.ended()
|
||||
return
|
||||
}
|
||||
|
||||
if self.state == .possible {
|
||||
if let location = touches.first?.location(in: self.view) {
|
||||
let (allowed, requiresLongPress, itemView) = self.shouldBegin(location)
|
||||
if allowed {
|
||||
self.isActiveUpdated(true)
|
||||
|
||||
self.itemView = itemView
|
||||
self.initialLocation = location
|
||||
if requiresLongPress {
|
||||
self.startLongTapTimer()
|
||||
self.startLongPressTimer()
|
||||
} else {
|
||||
self.state = .began
|
||||
if let itemView = self.itemView {
|
||||
self.began(itemView)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.isActiveUpdated(false)
|
||||
self.state = .failed
|
||||
}
|
||||
} else {
|
||||
self.isActiveUpdated(false)
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
self.initialLocation = nil
|
||||
|
||||
self.stopLongTapTimer()
|
||||
if self.longPressTimer != nil {
|
||||
self.stopLongPressTimer()
|
||||
self.isActiveUpdated(false)
|
||||
self.state = .failed
|
||||
}
|
||||
if self.state == .began || self.state == .changed {
|
||||
self.isActiveUpdated(false)
|
||||
self.ended()
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.initialLocation = nil
|
||||
|
||||
self.stopLongTapTimer()
|
||||
if self.longPressTimer != nil {
|
||||
self.isActiveUpdated(false)
|
||||
self.stopLongPressTimer()
|
||||
self.state = .failed
|
||||
}
|
||||
if self.state == .began || self.state == .changed {
|
||||
self.isActiveUpdated(false)
|
||||
self.ended()
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
|
||||
self.state = .changed
|
||||
let offset = CGPoint(x: location.x - initialLocation.x, y: location.y - initialLocation.y)
|
||||
self.moved(offset)
|
||||
} else if let touch = touches.first, let initialTapLocation = self.initialLocation, self.longPressTimer != nil {
|
||||
let touchLocation = touch.location(in: self.view)
|
||||
let dX = touchLocation.x - initialTapLocation.x
|
||||
let dY = touchLocation.y - initialTapLocation.y
|
||||
|
||||
if dX * dX + dY * dY > 3.0 * 3.0 {
|
||||
self.stopLongTapTimer()
|
||||
self.stopLongPressTimer()
|
||||
self.initialLocation = nil
|
||||
self.isActiveUpdated(false)
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -497,7 +497,7 @@ private class GiftIconLayer: SimpleLayer {
|
|||
for attribute in gift.attributes {
|
||||
if case let .model(_, fileValue, _) = attribute {
|
||||
file = fileValue
|
||||
} else if case let .backdrop(_, innerColor, _, _, _, _) = attribute {
|
||||
} else if case let .backdrop(_, _, innerColor, _, _, _, _) = attribute {
|
||||
color = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||
}
|
||||
}
|
||||
|
|
@ -563,7 +563,7 @@ private class GiftIconLayer: SimpleLayer {
|
|||
for attribute in gift.attributes {
|
||||
if case let .model(_, fileValue, _) = attribute {
|
||||
file = fileValue
|
||||
} else if case let .backdrop(_, innerColor, _, _, _, _) = attribute {
|
||||
} else if case let .backdrop(_, _, innerColor, _, _, _, _) = attribute {
|
||||
color = UIColor(rgb: UInt32(bitPattern: innerColor))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4867,6 +4867,12 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
}
|
||||
return profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||
},
|
||||
buyGift: { [weak profileGifts] slug, peerId in
|
||||
guard let profileGifts else {
|
||||
return .never()
|
||||
}
|
||||
return profileGifts.buyStarGift(slug: slug, peerId: peerId)
|
||||
},
|
||||
shareStory: { [weak self] uniqueGift in
|
||||
guard let self, let controller = self.controller else {
|
||||
return
|
||||
|
|
@ -10012,7 +10018,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
viewControllers = viewControllers.filter { !($0 is AttachmentController)}
|
||||
rootController.setViewControllers(viewControllers, animated: false)
|
||||
|
||||
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
rootController.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
}
|
||||
},
|
||||
cancelled: {}
|
||||
|
|
@ -10047,7 +10053,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
|
||||
self.openBotPreviewEditor(target: .botPreview(id: self.peerId, language: pane.currentBotPreviewLanguage?.id), source: result, transitionIn: (transitionView, transitionRect, transitionImage))
|
||||
},
|
||||
multipleCompletion: { _ in },
|
||||
multipleCompletion: { _, _ in },
|
||||
dismissed: {},
|
||||
groupsPresented: {}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -477,42 +477,52 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
itemTransition = .immediate
|
||||
}
|
||||
|
||||
let ribbonText: String?
|
||||
var ribbonText: String?
|
||||
var ribbonColor: GiftItemComponent.Ribbon.Color = .blue
|
||||
var ribbonFont: GiftItemComponent.Ribbon.Font = .generic
|
||||
var ribbonOutline: UIColor?
|
||||
|
||||
let peer: GiftItemComponent.Peer?
|
||||
let subject: GiftItemComponent.Subject
|
||||
var resellPrice: Int64?
|
||||
|
||||
switch product.gift {
|
||||
case let .generic(gift):
|
||||
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
||||
peer = product.fromPeer.flatMap { .peer($0) } ?? .anonymous
|
||||
|
||||
if let availability = gift.availability {
|
||||
ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(availability.total), decimalSeparator: params.presentationData.dateTimeFormat.decimalSeparator)).string
|
||||
} else {
|
||||
ribbonText = nil
|
||||
}
|
||||
case let .unique(gift):
|
||||
if product.pinnedToTop {
|
||||
ribbonFont = .monospaced
|
||||
ribbonText = "#\(gift.number)"
|
||||
subject = .uniqueGift(gift: gift, price: nil)
|
||||
peer = nil
|
||||
resellPrice = gift.resellStars
|
||||
|
||||
if let _ = resellPrice {
|
||||
//TODO:localize
|
||||
ribbonText = "sale"
|
||||
ribbonFont = .larger
|
||||
ribbonColor = .green
|
||||
ribbonOutline = params.presentationData.theme.list.blocksBackgroundColor
|
||||
} else {
|
||||
ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(gift.availability.issued), decimalSeparator: params.presentationData.dateTimeFormat.decimalSeparator)).string
|
||||
}
|
||||
for attribute in gift.attributes {
|
||||
if case let .backdrop(_, innerColor, outerColor, _, _, _) = attribute {
|
||||
ribbonColor = .custom(outerColor, innerColor)
|
||||
break
|
||||
if product.pinnedToTop {
|
||||
ribbonFont = .monospaced
|
||||
ribbonText = "#\(gift.number)"
|
||||
} else {
|
||||
ribbonText = params.presentationData.strings.PeerInfo_Gifts_OneOf(compactNumericCountString(Int(gift.availability.issued), decimalSeparator: params.presentationData.dateTimeFormat.decimalSeparator)).string
|
||||
}
|
||||
for attribute in gift.attributes {
|
||||
if case let .backdrop(_, _, innerColor, outerColor, _, _, _) = attribute {
|
||||
ribbonColor = .custom(outerColor, innerColor)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let peer: GiftItemComponent.Peer?
|
||||
let subject: GiftItemComponent.Subject
|
||||
switch product.gift {
|
||||
case let .generic(gift):
|
||||
subject = .starGift(gift: gift, price: "⭐️ \(gift.price)")
|
||||
peer = product.fromPeer.flatMap { .peer($0) } ?? .anonymous
|
||||
case let .unique(gift):
|
||||
subject = .uniqueGift(gift: gift)
|
||||
peer = nil
|
||||
}
|
||||
|
||||
|
||||
let _ = visibleItem.update(
|
||||
transition: itemTransition,
|
||||
component: AnyComponent(
|
||||
|
|
@ -522,7 +532,8 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
strings: params.presentationData.strings,
|
||||
peer: peer,
|
||||
subject: subject,
|
||||
ribbon: ribbonText.flatMap { GiftItemComponent.Ribbon(text: $0, font: ribbonFont, color: ribbonColor) },
|
||||
ribbon: ribbonText.flatMap { GiftItemComponent.Ribbon(text: $0, font: ribbonFont, color: ribbonColor, outline: ribbonOutline) },
|
||||
resellPrice: resellPrice,
|
||||
isHidden: !product.savedToProfile,
|
||||
isPinned: product.pinnedToTop,
|
||||
isEditing: self.isReordering,
|
||||
|
|
@ -589,6 +600,18 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
|||
}
|
||||
return self.profileGifts.upgradeStarGift(formId: formId, reference: reference, keepOriginalInfo: keepOriginalInfo)
|
||||
},
|
||||
buyGift: { [weak self] slug, peerId in
|
||||
guard let self else {
|
||||
return .never()
|
||||
}
|
||||
return self.profileGifts.buyStarGift(slug: slug, peerId: peerId)
|
||||
},
|
||||
updateResellStars: { [weak self] price in
|
||||
guard let self, case let .unique(uniqueGift) = product.gift else {
|
||||
return
|
||||
}
|
||||
self.profileGifts.updateStarGiftResellPrice(slug: uniqueGift.slug, price: price)
|
||||
},
|
||||
togglePinnedToTop: { [weak self] pinnedToTop in
|
||||
guard let self else {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ final class GiftListItemComponent: Component {
|
|||
theme: component.theme,
|
||||
strings: component.context.sharedContext.currentPresentationData.with { $0 }.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift),
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
ribbon: nil,
|
||||
isHidden: false,
|
||||
isSelected: gift.id == component.selectedId,
|
||||
|
|
|
|||
|
|
@ -456,10 +456,11 @@ final class UserAppearanceScreenComponent: Component {
|
|||
attributes: [
|
||||
.model(name: "", file: file, rarity: 0),
|
||||
.pattern(name: "", file: patternFile, rarity: 0),
|
||||
.backdrop(name: "", innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor, rarity: 0)
|
||||
.backdrop(name: "", id: 0, innerColor: innerColor, outerColor: outerColor, patternColor: patternColor, textColor: textColor, rarity: 0)
|
||||
],
|
||||
availability: StarGift.UniqueGift.Availability(issued: 0, total: 0),
|
||||
giftAddress: nil
|
||||
giftAddress: nil,
|
||||
resellStars: nil
|
||||
)
|
||||
signal = component.context.engine.accountData.setStarGiftStatus(starGift: gift, expirationDate: emojiStatus.expirationDate)
|
||||
} else {
|
||||
|
|
@ -1090,7 +1091,7 @@ final class UserAppearanceScreenComponent: Component {
|
|||
case let .pattern(_, file, _):
|
||||
patternFileId = file.fileId.id
|
||||
self.cachedIconFiles[file.fileId.id] = file
|
||||
case let .backdrop(_, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||
case let .backdrop(_, _, innerColorValue, outerColorValue, patternColorValue, textColorValue, _):
|
||||
innerColor = innerColorValue
|
||||
outerColor = outerColorValue
|
||||
patternColor = patternColorValue
|
||||
|
|
|
|||
|
|
@ -991,10 +991,22 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
}
|
||||
|
||||
let sectionTitle: String
|
||||
if section.id == 0, case .stories = component.stateContext.subject {
|
||||
sectionTitle = component.coverItem == nil ? environment.strings.Story_Privacy_PostStoryAsHeader : ""
|
||||
if section.id == 0, case let .stories(_, count) = component.stateContext.subject {
|
||||
if component.coverItem == nil {
|
||||
if count > 1 {
|
||||
sectionTitle = environment.strings.Story_Privacy_PostStoriesAsHeader
|
||||
} else {
|
||||
sectionTitle = environment.strings.Story_Privacy_PostStoryAsHeader
|
||||
}
|
||||
} else {
|
||||
sectionTitle = ""
|
||||
}
|
||||
} else if section.id == 2 {
|
||||
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
|
||||
if case let .stories(_, count) = component.stateContext.subject, count > 1 {
|
||||
sectionTitle = environment.strings.Story_Privacy_WhoCanViewStoriesHeader
|
||||
} else {
|
||||
sectionTitle = environment.strings.Story_Privacy_WhoCanViewHeader
|
||||
}
|
||||
} else if section.id == 1 {
|
||||
if case let .members(isGroup, _, _) = component.stateContext.subject {
|
||||
sectionTitle = isGroup ? environment.strings.BoostGift_Members_SectionTitle : environment.strings.BoostGift_Subscribers_SectionTitle
|
||||
|
|
@ -1637,12 +1649,21 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
}
|
||||
|
||||
let footerValue = environment.strings.Story_Privacy_KeepOnMyPageHours(Int32(component.timeout / 3600))
|
||||
var footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string
|
||||
|
||||
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
|
||||
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string
|
||||
var footerText: String
|
||||
if case let .stories(_, count) = component.stateContext.subject, count > 1 {
|
||||
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
|
||||
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageManyInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageManyInfo(footerValue).string
|
||||
} else {
|
||||
footerText = environment.strings.Story_Privacy_KeepOnMyPageManyInfo(footerValue).string
|
||||
}
|
||||
} else {
|
||||
if let sendAsPeerId = self.sendAsPeerId, sendAsPeerId.isGroupOrChannel == true {
|
||||
footerText = isSendAsGroup ? environment.strings.Story_Privacy_KeepOnGroupPageInfo(footerValue).string : environment.strings.Story_Privacy_KeepOnChannelPageInfo(footerValue).string
|
||||
} else {
|
||||
footerText = environment.strings.Story_Privacy_KeepOnMyPageInfo(footerValue).string
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let footerSize = sectionFooter.update(
|
||||
transition: sectionFooterTransition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
|
|
@ -2371,7 +2392,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
)
|
||||
|
||||
var footersTotalHeight: CGFloat = 0.0
|
||||
if case let .stories(editing) = component.stateContext.subject {
|
||||
if case let .stories(editing, _) = component.stateContext.subject {
|
||||
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.freeTextColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: environment.theme.list.freeTextColor)
|
||||
let link = MarkdownAttributeSet(font: Font.regular(13.0), textColor: environment.theme.list.itemAccentColor)
|
||||
|
|
@ -2451,7 +2472,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
itemHeight: peerItemSize.height,
|
||||
itemCount: peers.count
|
||||
))
|
||||
} else if case let .stories(editing) = component.stateContext.subject {
|
||||
} else if case let .stories(editing, _) = component.stateContext.subject {
|
||||
if !editing && hasChannels {
|
||||
sections.append(ItemLayout.Section(
|
||||
id: 0,
|
||||
|
|
@ -2533,12 +2554,17 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
switch component.stateContext.subject {
|
||||
case .peers:
|
||||
title = environment.strings.Story_Privacy_PostStoryAs
|
||||
case let .stories(editing):
|
||||
case let .stories(editing, count):
|
||||
if editing {
|
||||
title = environment.strings.Story_Privacy_EditStory
|
||||
} else {
|
||||
title = environment.strings.Story_Privacy_ShareStory
|
||||
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
||||
if count > 1 {
|
||||
title = environment.strings.Story_Privacy_ShareStories
|
||||
actionButtonTitle = environment.strings.Story_Privacy_PostStories(count)
|
||||
} else {
|
||||
title = environment.strings.Story_Privacy_ShareStory
|
||||
actionButtonTitle = environment.strings.Story_Privacy_PostStory
|
||||
}
|
||||
}
|
||||
case let .chats(grayList):
|
||||
if grayList {
|
||||
|
|
@ -2627,7 +2653,7 @@ final class ShareWithPeersScreenComponent: Component {
|
|||
inset = 1000.0
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
inset = 1000.0
|
||||
} else if case let .stories(editing) = component.stateContext.subject {
|
||||
} else if case let .stories(editing, _) = component.stateContext.subject {
|
||||
if editing {
|
||||
inset = 351.0
|
||||
inset += 10.0 + environment.safeInsets.bottom + 50.0 + footersTotalHeight
|
||||
|
|
@ -3026,7 +3052,7 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
|||
var categoryItems: [ShareWithPeersScreenComponent.CategoryItem] = []
|
||||
var optionItems: [ShareWithPeersScreenComponent.OptionItem] = []
|
||||
var coverItem: ShareWithPeersScreenComponent.CoverItem?
|
||||
if case let .stories(editing) = stateContext.subject {
|
||||
if case let .stories(editing, _) = stateContext.subject {
|
||||
var everyoneSubtitle = presentationData.strings.Story_Privacy_ExcludePeople
|
||||
if (stateContext.stateValue?.savedSelectedPeers[.everyone]?.count ?? 0) > 0 {
|
||||
var peerNamesArray: [String] = []
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public extension ShareWithPeersScreen {
|
|||
final class StateContext {
|
||||
public enum Subject: Equatable {
|
||||
case peers(peers: [EnginePeer], peerId: EnginePeer.Id?)
|
||||
case stories(editing: Bool)
|
||||
case stories(editing: Bool, count: Int32)
|
||||
case chats(blocked: Bool)
|
||||
case contacts(base: EngineStoryPrivacy.Base)
|
||||
case contactsSearch(query: String, onlyContacts: Bool)
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ public final class StarsAvatarComponent: Component {
|
|||
theme: component.theme,
|
||||
strings: component.context.sharedContext.currentPresentationData.with { $0 }.strings,
|
||||
peer: nil,
|
||||
subject: .uniqueGift(gift: gift),
|
||||
subject: .uniqueGift(gift: gift, price: nil),
|
||||
mode: .thumbnail
|
||||
)
|
||||
),
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ private final class SheetContent: CombinedComponent {
|
|||
let minAmount: StarsAmount?
|
||||
let maxAmount: StarsAmount?
|
||||
|
||||
let configuration = StarsWithdrawConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
let withdrawConfiguration = StarsWithdrawConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 })
|
||||
|
||||
switch component.mode {
|
||||
case let .withdraw(status):
|
||||
|
|
@ -113,7 +114,7 @@ private final class SheetContent: CombinedComponent {
|
|||
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
|
||||
amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder
|
||||
|
||||
minAmount = configuration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
maxAmount = status.balances.availableBalance
|
||||
amountLabel = nil
|
||||
case .accountWithdraw:
|
||||
|
|
@ -121,7 +122,7 @@ private final class SheetContent: CombinedComponent {
|
|||
amountTitle = environment.strings.Stars_Withdraw_AmountTitle
|
||||
amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder
|
||||
|
||||
minAmount = configuration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
minAmount = withdrawConfiguration.minWithdrawAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
maxAmount = state.balance
|
||||
amountLabel = nil
|
||||
case .paidMedia:
|
||||
|
|
@ -130,9 +131,9 @@ private final class SheetContent: CombinedComponent {
|
|||
amountPlaceholder = environment.strings.Stars_PaidContent_AmountPlaceholder
|
||||
|
||||
minAmount = StarsAmount(value: 1, nanos: 0)
|
||||
maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
|
||||
if let usdWithdrawRate = configuration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
|
||||
if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let amount = state.amount, amount > StarsAmount.zero {
|
||||
let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0
|
||||
amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))"
|
||||
} else {
|
||||
|
|
@ -144,7 +145,16 @@ private final class SheetContent: CombinedComponent {
|
|||
amountPlaceholder = environment.strings.Stars_SendStars_AmountPlaceholder
|
||||
|
||||
minAmount = StarsAmount(value: 1, nanos: 0)
|
||||
maxAmount = configuration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
amountLabel = nil
|
||||
case let .starGiftResell(update):
|
||||
//TODO:localize
|
||||
titleString = update ? "Edit Price" : "Sell Gift"
|
||||
amountTitle = "PRICE IN STARS"
|
||||
amountPlaceholder = "Enter Price"
|
||||
|
||||
minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0)
|
||||
maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0)
|
||||
amountLabel = nil
|
||||
}
|
||||
|
||||
|
|
@ -214,10 +224,16 @@ private final class SheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
let amountFont = Font.regular(13.0)
|
||||
let boldAmountFont = Font.semibold(13.0)
|
||||
let amountTextColor = theme.list.freeTextColor
|
||||
let amountMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), bold: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor), link: MarkdownAttributeSet(font: amountFont, textColor: theme.list.itemAccentColor), linkAttribute: { contents in
|
||||
let amountMarkdownAttributes = MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: amountFont, textColor: amountTextColor),
|
||||
bold: MarkdownAttributeSet(font: boldAmountFont, textColor: amountTextColor),
|
||||
link: MarkdownAttributeSet(font: amountFont, textColor: theme.list.itemAccentColor),
|
||||
linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
}
|
||||
)
|
||||
if state.cachedChevronImage == nil || state.cachedChevronImage?.1 !== environment.theme {
|
||||
state.cachedChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: environment.theme.list.itemAccentColor)!, environment.theme)
|
||||
}
|
||||
|
|
@ -252,6 +268,18 @@ private final class SheetContent: CombinedComponent {
|
|||
text: .plain(amountInfoString),
|
||||
maximumNumberOfLines: 0
|
||||
))
|
||||
case .starGiftResell:
|
||||
//TODO:localize
|
||||
let amountInfoString: NSAttributedString
|
||||
if let value = state.amount?.value, value > 0 {
|
||||
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **\(Int32(floor(Float(value) * 0.8))) Stars**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
} else {
|
||||
amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("You will receive **80%**.", attributes: amountMarkdownAttributes, textAlignment: .natural))
|
||||
}
|
||||
amountFooter = AnyComponent(MultilineTextComponent(
|
||||
text: .plain(amountInfoString),
|
||||
maximumNumberOfLines: 0
|
||||
))
|
||||
default:
|
||||
amountFooter = nil
|
||||
}
|
||||
|
|
@ -305,8 +333,15 @@ private final class SheetContent: CombinedComponent {
|
|||
let buttonString: String
|
||||
if case .paidMedia = component.mode {
|
||||
buttonString = environment.strings.Stars_PaidContent_Create
|
||||
} else if case .starGiftResell = component.mode {
|
||||
//TODO:localize
|
||||
if let amount = state.amount, amount.value > 0 {
|
||||
buttonString = "Sell for # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
||||
} else {
|
||||
buttonString = "Sell"
|
||||
}
|
||||
} else if let amount = state.amount {
|
||||
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
||||
buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))"
|
||||
} else {
|
||||
buttonString = environment.strings.Stars_Withdraw_Withdraw
|
||||
}
|
||||
|
|
@ -318,8 +353,9 @@ private final class SheetContent: CombinedComponent {
|
|||
let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
|
||||
if let range = buttonAttributedString.string.range(of: "#"), let starImage = state.cachedStarImage?.0 {
|
||||
buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.foregroundColor, value: theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string))
|
||||
buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string))
|
||||
}
|
||||
|
||||
let button = button.update(
|
||||
|
|
@ -394,6 +430,8 @@ private final class SheetContent: CombinedComponent {
|
|||
amount = initialValue.flatMap { StarsAmount(value: $0, nanos: 0) }
|
||||
case .reaction:
|
||||
amount = nil
|
||||
case .starGiftResell:
|
||||
amount = nil
|
||||
}
|
||||
|
||||
self.amount = amount
|
||||
|
|
@ -514,9 +552,11 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||
case accountWithdraw
|
||||
case paidMedia(Int64?)
|
||||
case reaction(Int64?)
|
||||
case starGiftResell(Bool)
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let mode: StarsWithdrawScreen.Mode
|
||||
fileprivate let completion: (Int64) -> Void
|
||||
|
||||
public init(
|
||||
|
|
@ -525,6 +565,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||
completion: @escaping (Int64) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.mode = mode
|
||||
self.completion = completion
|
||||
|
||||
super.init(
|
||||
|
|
@ -558,12 +599,17 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer {
|
|||
|
||||
func presentMinAmountTooltip(_ minAmount: Int64) {
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
var text = presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string
|
||||
if case .starGiftResell = self.mode {
|
||||
text = "You cannot sell gift for less than \(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount)))."
|
||||
}
|
||||
|
||||
let resultController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .image(
|
||||
image: UIImage(bundleImageName: "Premium/Stars/StarLarge")!,
|
||||
title: nil,
|
||||
text: presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum(presentationData.strings.Stars_Withdraw_Withdraw_ErrorMinimum_Stars(Int32(minAmount))).string,
|
||||
text: text,
|
||||
round: false,
|
||||
undoText: nil
|
||||
),
|
||||
|
|
|
|||
|
|
@ -5068,7 +5068,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: context,
|
||||
subject: .stories(editing: true),
|
||||
subject: .stories(editing: true, count: 1),
|
||||
editing: true,
|
||||
initialSelectedPeers: selectedPeers,
|
||||
closeFriends: component.closeFriends.get(),
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "combine.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/combine.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Media Editor/Collage.imageset/combine.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Peer Info/SortNumber.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Peer Info/SortNumber.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "hash.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/SortNumber.imageset/hash.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Peer Info/SortNumber.imageset/hash.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Sell.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Sell.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "sale.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Sell.imageset/sale.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Sell.imageset/sale.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unlist.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unlist.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "unsale.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unlist.imageset/unsale.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Collectible/Unlist.imageset/unsale.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -1124,7 +1124,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||
let sendGiftTitle: String
|
||||
var isIncoming = message.effectivelyIncoming(context.account.peerId)
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _) = action.action {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _) = action.action {
|
||||
if isUpgrade && message.author?.id == context.account.peerId {
|
||||
isIncoming = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import Foundation
|
|||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
|
@ -17,6 +18,7 @@ import TelegramBaseController
|
|||
import ContextUI
|
||||
import SliderContextItem
|
||||
import UndoUI
|
||||
import MarqueeComponent
|
||||
|
||||
private func normalizeValue(_ value: CGFloat) -> CGFloat {
|
||||
return round(value * 10.0) / 10.0
|
||||
|
|
@ -147,6 +149,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
private let albumArtNode: TransformImageNode
|
||||
private var largeAlbumArtNode: TransformImageNode?
|
||||
private let titleNode: TextNode
|
||||
private let title: ComponentView<Empty>
|
||||
private let descriptionNode: TextNode
|
||||
private let shareNode: HighlightableButtonNode
|
||||
private let artistButton: HighlightTrackingButtonNode
|
||||
|
|
@ -236,6 +239,8 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.displaysAsynchronously = false
|
||||
|
||||
self.title = ComponentView<Empty>()
|
||||
|
||||
self.descriptionNode = TextNode()
|
||||
self.descriptionNode.isUserInteractionEnabled = false
|
||||
self.descriptionNode.displaysAsynchronously = false
|
||||
|
|
@ -295,7 +300,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
self.addSubnode(self.collapseNode)
|
||||
|
||||
self.addSubnode(self.albumArtNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
//self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.descriptionNode)
|
||||
self.addSubnode(self.artistButton)
|
||||
self.addSubnode(self.shareNode)
|
||||
|
|
@ -725,8 +730,25 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
self.artistButton.isUserInteractionEnabled = hasArtist
|
||||
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MarqueeComponent(attributedText: titleString ?? NSAttributedString())
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset + MarqueeComponent.innerPadding, height: CGFloat.greatestFiniteMagnitude)
|
||||
)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.view.addSubview(titleView)
|
||||
}
|
||||
transition.updateFrame(view: titleView, frame: CGRect(origin: CGPoint(x: self.isExpanded ? floor((width - titleSize.width) / 2.0) : (leftInset + sideInset + infoLabelsLeftInset) - MarqueeComponent.innerPadding, y: infoVerticalOrigin + 1.0), size: titleSize))
|
||||
}
|
||||
|
||||
let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode)
|
||||
let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: descriptionString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: width - sideInset * 2.0 - leftInset - rightInset - infoLabelsLeftInset - infoLabelsRightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
|
|
|
|||
|
|
@ -81,8 +81,10 @@ import AccountFreezeInfoScreen
|
|||
import JoinSubjectScreen
|
||||
import OldChannelsController
|
||||
import InviteLinksUI
|
||||
import GiftStoreScreen
|
||||
import SendInviteLinkScreen
|
||||
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
let tokens = Bag<Void>()
|
||||
|
|
@ -2985,11 +2987,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
))
|
||||
controller.navigationPresentation = .modal
|
||||
|
||||
let _ = combineLatest(
|
||||
let _ = (combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
controller.result,
|
||||
options.get()
|
||||
).startStandalone(next: { [weak controller] result, options in
|
||||
options.get())
|
||||
|> take(1)).startStandalone(next: { [weak controller] result, options in
|
||||
if let (peers, _, _, _, _, _) = result, let contactPeer = peers.first, case let .peer(peer, _, _) = contactPeer, let starsContext = context.starsContext {
|
||||
if case .starGiftTransfer = source {
|
||||
presentTransferAlertImpl?(EnginePeer(peer))
|
||||
|
|
@ -3268,6 +3270,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return controller
|
||||
}
|
||||
|
||||
public func makeGiftStoreController(context: AccountContext, peerId: EnginePeer.Id, gift: StarGift.Gift) -> ViewController {
|
||||
guard let starsContext = context.starsContext else {
|
||||
fatalError()
|
||||
}
|
||||
let controller = GiftStoreScreen(context: context, starsContext: starsContext, peerId: peerId, gift: gift)
|
||||
return controller
|
||||
}
|
||||
|
||||
public func makePremiumPrivacyControllerController(context: AccountContext, subject: PremiumPrivacySubject, peerId: EnginePeer.Id) -> ViewController {
|
||||
let mappedSubject: PremiumPrivacyScreen.Subject
|
||||
let introSource: PremiumIntroSource
|
||||
|
|
@ -3562,7 +3572,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return mediaPickerController(context: context, hasSearch: hasSearch, completion: completion)
|
||||
}
|
||||
|
||||
public func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any]) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController {
|
||||
public func makeStoryMediaPickerScreen(context: AccountContext, isDark: Bool, forCollage: Bool, selectionLimit: Int?, getSourceRect: @escaping () -> CGRect, completion: @escaping (Any, UIView, CGRect, UIImage?, @escaping (Bool?) -> (UIView, CGRect)?, @escaping () -> Void) -> Void, multipleCompletion: @escaping ([Any], Bool) -> Void, dismissed: @escaping () -> Void, groupsPresented: @escaping () -> Void) -> ViewController {
|
||||
return storyMediaPickerController(context: context, isDark: isDark, forCollage: forCollage, selectionLimit: selectionLimit, getSourceRect: getSourceRect, completion: completion, multipleCompletion: multipleCompletion, dismissed: dismissed, groupsPresented: groupsPresented)
|
||||
}
|
||||
|
||||
|
|
@ -3657,6 +3667,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return StarsWithdrawScreen(context: context, mode: .accountWithdraw, completion: completion)
|
||||
}
|
||||
|
||||
public func makeStarGiftResellScreen(context: AccountContext, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController {
|
||||
return StarsWithdrawScreen(context: context, mode: .starGiftResell(update), completion: completion)
|
||||
}
|
||||
|
||||
public func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController {
|
||||
return StarsTransactionScreen(context: context, subject: .gift(message))
|
||||
}
|
||||
|
|
@ -3674,7 +3688,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
}
|
||||
|
||||
public func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, dismissed: (() -> Void)?) -> ViewController {
|
||||
let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift), shareStory: shareStory)
|
||||
let controller = GiftViewScreen(context: context, subject: .uniqueGift(gift, nil), shareStory: shareStory)
|
||||
controller.disposed = {
|
||||
dismissed?()
|
||||
}
|
||||
|
|
@ -3723,7 +3737,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
externalState.storyTarget = target
|
||||
|
||||
if let rootController = context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
rootController.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
}
|
||||
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: targetPeerId))
|
||||
|
|
|
|||
|
|
@ -391,6 +391,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
return .asset(asset)
|
||||
case let .draft(draft):
|
||||
return .draft(draft, nil)
|
||||
case let .assets(assets):
|
||||
return .assets(assets)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -451,7 +453,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
|
||||
if let customTarget, case .botPreview = customTarget {
|
||||
externalState.storyTarget = customTarget
|
||||
self.proceedWithStoryUpload(target: customTarget, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
self.proceedWithStoryUpload(target: customTarget, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
|
||||
dismissCameraImpl?()
|
||||
return
|
||||
|
|
@ -484,7 +486,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
externalState.isPeerArchived = channel.storiesHidden ?? false
|
||||
}
|
||||
|
||||
self.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
self.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
|
||||
dismissCameraImpl?()
|
||||
})
|
||||
|
|
@ -548,8 +550,8 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
})
|
||||
}
|
||||
|
||||
public func proceedWithStoryUpload(target: Stories.PendingTarget, result: MediaEditorScreenResult, existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void) {
|
||||
guard let result = result as? MediaEditorScreenImpl.Result else {
|
||||
public func proceedWithStoryUpload(target: Stories.PendingTarget, results: [MediaEditorScreenResult], existingMedia: EngineMedia?, forwardInfo: Stories.PendingForwardInfo?, externalState: MediaEditorTransitionOutExternalState, commit: @escaping (@escaping () -> Void) -> Void) {
|
||||
guard let results = results as? [MediaEditorScreenImpl.Result] else {
|
||||
return
|
||||
}
|
||||
let context = self.context
|
||||
|
|
@ -657,83 +659,85 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
|
|||
}
|
||||
|
||||
if let _ = self.chatListController as? ChatListControllerImpl {
|
||||
var media: EngineStoryInputMedia?
|
||||
|
||||
if let mediaResult = result.media {
|
||||
switch mediaResult {
|
||||
case let .image(image, dimensions):
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
||||
defer {
|
||||
TempBox.shared.dispose(tempFile)
|
||||
}
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
||||
media = .image(dimensions: dimensions, data: imageData, stickers: result.stickers)
|
||||
}
|
||||
case let .video(content, firstFrameImage, values, duration, dimensions):
|
||||
let adjustments: VideoMediaResourceAdjustments
|
||||
if let valuesData = try? JSONEncoder().encode(values) {
|
||||
let data = MemoryBuffer(data: valuesData)
|
||||
let digest = MemoryBuffer(data: data.md5Digest())
|
||||
adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
|
||||
|
||||
let resource: TelegramMediaResource
|
||||
switch content {
|
||||
case let .imageFile(path):
|
||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
||||
case let .videoFile(path):
|
||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
||||
case let .asset(localIdentifier):
|
||||
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
|
||||
}
|
||||
for result in results {
|
||||
var media: EngineStoryInputMedia?
|
||||
|
||||
if let mediaResult = result.media {
|
||||
switch mediaResult {
|
||||
case let .image(image, dimensions):
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
||||
defer {
|
||||
TempBox.shared.dispose(tempFile)
|
||||
}
|
||||
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
|
||||
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
|
||||
let file = TempBox.shared.tempFile(fileName: "image.jpg")
|
||||
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
|
||||
return file
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
|
||||
media = .image(dimensions: dimensions, data: imageData, stickers: result.stickers)
|
||||
}
|
||||
|
||||
var coverTime: Double?
|
||||
if let coverImageTimestamp = values.coverImageTimestamp {
|
||||
if let trimRange = values.videoTrimRange {
|
||||
coverTime = min(duration, coverImageTimestamp - trimRange.lowerBound)
|
||||
} else {
|
||||
coverTime = min(duration, coverImageTimestamp)
|
||||
case let .video(content, firstFrameImage, values, duration, dimensions):
|
||||
let adjustments: VideoMediaResourceAdjustments
|
||||
if let valuesData = try? JSONEncoder().encode(values) {
|
||||
let data = MemoryBuffer(data: valuesData)
|
||||
let digest = MemoryBuffer(data: data.md5Digest())
|
||||
adjustments = VideoMediaResourceAdjustments(data: data, digest: digest, isStory: true)
|
||||
|
||||
let resource: TelegramMediaResource
|
||||
switch content {
|
||||
case let .imageFile(path):
|
||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
||||
case let .videoFile(path):
|
||||
resource = LocalFileVideoMediaResource(randomId: Int64.random(in: .min ... .max), path: path, adjustments: adjustments)
|
||||
case let .asset(localIdentifier):
|
||||
resource = VideoLibraryMediaResource(localIdentifier: localIdentifier, conversion: .compress(adjustments))
|
||||
}
|
||||
let tempFile = TempBox.shared.tempFile(fileName: "file")
|
||||
defer {
|
||||
TempBox.shared.dispose(tempFile)
|
||||
}
|
||||
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
|
||||
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
|
||||
let file = TempBox.shared.tempFile(fileName: "image.jpg")
|
||||
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
|
||||
return file
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var coverTime: Double?
|
||||
if let coverImageTimestamp = values.coverImageTimestamp {
|
||||
if let trimRange = values.videoTrimRange {
|
||||
coverTime = min(duration, coverImageTimestamp - trimRange.lowerBound)
|
||||
} else {
|
||||
coverTime = min(duration, coverImageTimestamp)
|
||||
}
|
||||
}
|
||||
|
||||
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: coverTime)
|
||||
}
|
||||
|
||||
media = .video(dimensions: dimensions, duration: duration, resource: resource, firstFrameFile: firstFrameFile, stickers: result.stickers, coverTime: coverTime)
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
} else if let existingMedia {
|
||||
media = .existing(media: existingMedia._asMedia())
|
||||
}
|
||||
|
||||
if let media {
|
||||
let _ = (context.engine.messages.uploadStory(
|
||||
target: target,
|
||||
media: media,
|
||||
mediaAreas: result.mediaAreas,
|
||||
text: result.caption.string,
|
||||
entities: generateChatInputTextEntities(result.caption),
|
||||
pin: result.options.pin,
|
||||
privacy: result.options.privacy,
|
||||
isForwardingDisabled: result.options.isForwardingDisabled,
|
||||
period: result.options.timeout,
|
||||
randomId: result.randomId,
|
||||
forwardInfo: forwardInfo
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { stableId in
|
||||
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: result.randomId, to: Int64(stableId))
|
||||
})
|
||||
}
|
||||
} else if let existingMedia {
|
||||
media = .existing(media: existingMedia._asMedia())
|
||||
}
|
||||
|
||||
if let media {
|
||||
let _ = (context.engine.messages.uploadStory(
|
||||
target: target,
|
||||
media: media,
|
||||
mediaAreas: result.mediaAreas,
|
||||
text: result.caption.string,
|
||||
entities: generateChatInputTextEntities(result.caption),
|
||||
pin: result.options.pin,
|
||||
privacy: result.options.privacy,
|
||||
isForwardingDisabled: result.options.isForwardingDisabled,
|
||||
period: result.options.timeout,
|
||||
randomId: result.randomId,
|
||||
forwardInfo: forwardInfo
|
||||
)
|
||||
|> deliverOnMainQueue).startStandalone(next: { stableId in
|
||||
moveStorySource(engine: context.engine, peerId: context.account.peerId, from: result.randomId, to: Int64(stableId))
|
||||
})
|
||||
}
|
||||
completionImpl()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1483,7 +1483,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
|||
externalState.storyTarget = target
|
||||
|
||||
if let rootController = self.context.sharedContext.mainWindow?.viewController as? TelegramRootControllerInterface {
|
||||
rootController.proceedWithStoryUpload(target: target, result: result, existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
rootController.proceedWithStoryUpload(target: target, results: [result], existingMedia: nil, forwardInfo: nil, externalState: externalState, commit: commit)
|
||||
}
|
||||
})
|
||||
if let navigationController = self.controller?.getNavigationController() {
|
||||
|
|
|
|||
2
third-party/td/td
vendored
2
third-party/td/td
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit f1b7500310baa496c0b779e4273a3aff0f14f42f
|
||||
Subproject commit 04adfc87deea4c804def118e88c89a08c388b32b
|
||||
Loading…
Add table
Add a link
Reference in a new issue