mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Giveaway improvements
This commit is contained in:
parent
d1f2b29370
commit
713336a13f
Telegram/Telegram-iOS/en.lproj
submodules
AccountContext/Sources
AttachmentUI
ChatListUI/Sources/Node
ComponentFlow/Source/Components
Components/LottieAnimationComponent/Sources
InAppPurchaseManager/Sources
ItemListUI/Sources
LegacyComponents
PublicHeaders/LegacyComponents
TGMediaAssetsController.hTGMediaPickerGalleryInterfaceView.hTGMediaPickerGalleryModel.hTGPhotoToolbarView.h
Sources
LegacyMediaPickerUI
LegacyUI
MediaPickerUI/Sources
PremiumUI
BUILD
Sources
StatisticsUI/Sources
TelegramApi/Sources
Api0.swiftApi10.swiftApi13.swiftApi14.swiftApi17.swiftApi28.swiftApi29.swiftApi30.swiftApi31.swiftApi5.swiftApi8.swift
TelegramCore/Sources
ApiUtils
SyncCore
TelegramEngine
TelegramPresentationData/Sources/Resources
TelegramStringFormatting/Sources
TelegramUI
BUILD
Components
ButtonComponent
CameraButtonComponent/Sources
CameraScreen
MetalResources
Sources
ChatEntityKeyboardInputNode/Sources
LegacyMessageInputPanel/Sources
PremiumGiftAttachmentScreen
ShareWithPeersScreen/Sources
Stories/StoryContainerScreen
Images.xcassets/Premium
Resources/Animations
Sources
WebSearchUI/Sources
WebUI/Sources
@ -10095,3 +10095,8 @@ Sorry for the inconvenience.";
|
||||
|
||||
"PremiumGift.LabelRecipients_1" = "1 recipient";
|
||||
"PremiumGift.LabelRecipients_any" = "%d recipients";
|
||||
|
||||
"Message.Giveaway" = "Giveaway";
|
||||
|
||||
"GiftLink.LinkSharedToChat" = "Gift link forwarded to **%@**";
|
||||
"GiftLink.LinkSharedToSavedMessages" = "Gift link forwarded to **Saved Messages**";
|
||||
|
@ -910,6 +910,8 @@ public protocol SharedAccountContext: AnyObject {
|
||||
|
||||
func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController
|
||||
|
||||
func makeChannelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, boosts: Bool, boostStatus: ChannelBoostStatus?, statsDatacenterId: Int32) -> ViewController
|
||||
|
||||
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||
|
||||
func navigateToCurrentCall()
|
||||
|
@ -0,0 +1,50 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public struct AttachmentMainButtonState {
|
||||
public enum Background {
|
||||
case color(UIColor)
|
||||
case premium
|
||||
}
|
||||
|
||||
public enum Progress: Equatable {
|
||||
case none
|
||||
case side
|
||||
case center
|
||||
}
|
||||
|
||||
public enum Font: Equatable {
|
||||
case regular
|
||||
case bold
|
||||
}
|
||||
|
||||
public let text: String?
|
||||
public let font: Font
|
||||
public let background: Background
|
||||
public let textColor: UIColor
|
||||
public let isVisible: Bool
|
||||
public let progress: Progress
|
||||
public let isEnabled: Bool
|
||||
|
||||
public init(
|
||||
text: String?,
|
||||
font: Font,
|
||||
background: Background,
|
||||
textColor: UIColor,
|
||||
isVisible: Bool,
|
||||
progress: Progress,
|
||||
isEnabled: Bool
|
||||
) {
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.background = background
|
||||
self.textColor = textColor
|
||||
self.isVisible = isVisible
|
||||
self.progress = progress
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
public static var initial: AttachmentMainButtonState {
|
||||
return AttachmentMainButtonState(text: nil, font: .bold, background: .color(.clear), textColor: .clear, isVisible: false, progress: .none, isEnabled: false)
|
||||
}
|
||||
}
|
@ -37,6 +37,8 @@ swift_library(
|
||||
"//submodules/Components/MultilineTextComponent:MultilineTextComponent",
|
||||
"//submodules/ShimmerEffect:ShimmerEffect",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/TelegramUI/Components/LegacyMessageInputPanel",
|
||||
"//submodules/TelegramUI/Components/LegacyMessageInputPanelInputView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -11,7 +11,8 @@ import AccountContext
|
||||
import TelegramStringFormatting
|
||||
import UIKitRuntimeUtils
|
||||
import MediaResources
|
||||
import AttachmentTextInputPanelNode
|
||||
import LegacyMessageInputPanel
|
||||
import LegacyMessageInputPanelInputView
|
||||
|
||||
public enum AttachmentButtonType: Equatable {
|
||||
case gallery
|
||||
@ -184,7 +185,7 @@ public class AttachmentController: ViewController {
|
||||
private let initialButton: AttachmentButtonType
|
||||
private let fromMenu: Bool
|
||||
private let hasTextInput: Bool
|
||||
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
|
||||
private let makeEntityInputView: () -> LegacyMessageInputPanelInputView?
|
||||
public var animateAppearance: Bool = false
|
||||
|
||||
public var willDismiss: () -> Void = {}
|
||||
@ -209,7 +210,7 @@ public class AttachmentController: ViewController {
|
||||
private let dim: ASDisplayNode
|
||||
private let shadowNode: ASImageNode
|
||||
fileprivate let container: AttachmentContainer
|
||||
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
|
||||
private let makeEntityInputView: () -> LegacyMessageInputPanelInputView?
|
||||
let panel: AttachmentPanel
|
||||
|
||||
private var currentType: AttachmentButtonType?
|
||||
@ -279,7 +280,7 @@ public class AttachmentController: ViewController {
|
||||
|
||||
private let wrapperNode: ASDisplayNode
|
||||
|
||||
init(controller: AttachmentController, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
|
||||
init(controller: AttachmentController, makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView?) {
|
||||
self.controller = controller
|
||||
self.makeEntityInputView = makeEntityInputView
|
||||
|
||||
@ -910,7 +911,7 @@ public class AttachmentController: ViewController {
|
||||
|
||||
public var getSourceRect: (() -> CGRect?)?
|
||||
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation?, isScheduledMessages: Bool = false, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil}) {
|
||||
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, chatLocation: ChatLocation?, isScheduledMessages: Bool = false, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView? = { return nil}) {
|
||||
self.context = context
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.chatLocation = chatLocation
|
||||
|
@ -19,6 +19,8 @@ import MediaResources
|
||||
import MultilineTextComponent
|
||||
import ShimmerEffect
|
||||
import TextFormat
|
||||
import LegacyMessageInputPanel
|
||||
import LegacyMessageInputPanelInputView
|
||||
|
||||
private let buttonSize = CGSize(width: 88.0, height: 49.0)
|
||||
private let smallButtonWidth: CGFloat = 69.0
|
||||
@ -375,54 +377,6 @@ private final class LoadingProgressNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public struct AttachmentMainButtonState {
|
||||
public enum Background {
|
||||
case color(UIColor)
|
||||
case premium
|
||||
}
|
||||
|
||||
public enum Progress: Equatable {
|
||||
case none
|
||||
case side
|
||||
case center
|
||||
}
|
||||
|
||||
public enum Font: Equatable {
|
||||
case regular
|
||||
case bold
|
||||
}
|
||||
|
||||
public let text: String?
|
||||
public let font: Font
|
||||
public let background: Background
|
||||
public let textColor: UIColor
|
||||
public let isVisible: Bool
|
||||
public let progress: Progress
|
||||
public let isEnabled: Bool
|
||||
|
||||
public init(
|
||||
text: String?,
|
||||
font: Font,
|
||||
background: Background,
|
||||
textColor: UIColor,
|
||||
isVisible: Bool,
|
||||
progress: Progress,
|
||||
isEnabled: Bool
|
||||
) {
|
||||
self.text = text
|
||||
self.font = font
|
||||
self.background = background
|
||||
self.textColor = textColor
|
||||
self.isVisible = isVisible
|
||||
self.progress = progress
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
|
||||
static var initial: AttachmentMainButtonState {
|
||||
return AttachmentMainButtonState(text: nil, font: .bold, background: .color(.clear), textColor: .clear, isVisible: false, progress: .none, isEnabled: false)
|
||||
}
|
||||
}
|
||||
|
||||
private final class MainButtonNode: HighlightTrackingButtonNode {
|
||||
private var state: AttachmentMainButtonState
|
||||
private var size: CGSize?
|
||||
@ -734,7 +688,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState
|
||||
private var interfaceInteraction: ChatPanelInterfaceInteraction?
|
||||
|
||||
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
|
||||
private let makeEntityInputView: () -> LegacyMessageInputPanelInputView?
|
||||
|
||||
private let containerNode: ASDisplayNode
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
@ -742,7 +696,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let separatorNode: ASDisplayNode
|
||||
private var buttonViews: [Int: ComponentHostView<Empty>] = [:]
|
||||
|
||||
private var textInputPanelNode: AttachmentTextInputPanelNode?
|
||||
private var textInputPanelNode: LegacyMessageInputPanelNode?
|
||||
private var progressNode: LoadingProgressNode?
|
||||
private var mainButtonNode: MainButtonNode
|
||||
|
||||
@ -776,7 +730,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var mainButtonPressed: () -> Void = { }
|
||||
|
||||
init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
|
||||
init(context: AccountContext, chatLocation: ChatLocation?, isScheduledMessages: Bool, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> LegacyMessageInputPanelInputView?) {
|
||||
self.context = context
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.isScheduledMessages = isScheduledMessages
|
||||
@ -937,7 +891,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
})
|
||||
}
|
||||
if let textInputPanelNode = strongSelf.textInputPanelNode {
|
||||
textInputPanelNode.ensureFocused()
|
||||
textInputPanelNode.activateInput()
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(animated: true, { state in
|
||||
return state.updatedInputMode({ _ in return inputMode }).updatedInterfaceState({
|
||||
@ -950,52 +904,53 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
}, reportPeerIrrelevantGeoLocation: {
|
||||
}, displaySlowmodeTooltip: { _, _ in
|
||||
}, displaySendMessageOptions: { [weak self] node, gesture in
|
||||
guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else {
|
||||
return
|
||||
}
|
||||
textInputPanelNode.loadTextInputNodeIfNeeded()
|
||||
guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation?.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
var hasEntityKeyboard = false
|
||||
if case .media = strongSelf.presentationInterfaceState.inputMode {
|
||||
hasEntityKeyboard = true
|
||||
}
|
||||
let _ = (strongSelf.context.account.viewTracker.peerView(peerId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in
|
||||
guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
|
||||
return
|
||||
}
|
||||
var sendWhenOnlineAvailable = false
|
||||
if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status {
|
||||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if currentTime > until {
|
||||
sendWhenOnlineAvailable = true
|
||||
}
|
||||
}
|
||||
if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
|
||||
sendWhenOnlineAvailable = false
|
||||
}
|
||||
|
||||
let controller = ChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, completion: {
|
||||
}, sendMessage: { [weak textInputPanelNode] mode in
|
||||
switch mode {
|
||||
case .generic:
|
||||
textInputPanelNode?.sendMessage(.generic)
|
||||
case .silently:
|
||||
textInputPanelNode?.sendMessage(.silent)
|
||||
case .whenOnline:
|
||||
textInputPanelNode?.sendMessage(.whenOnline)
|
||||
}
|
||||
}, schedule: { [weak textInputPanelNode] in
|
||||
textInputPanelNode?.sendMessage(.schedule)
|
||||
})
|
||||
controller.emojiViewProvider = textInputPanelNode.emojiViewProvider
|
||||
strongSelf.presentInGlobalOverlay(controller)
|
||||
})
|
||||
}, displaySendMessageOptions: { node, gesture in
|
||||
let _ = node
|
||||
let _ = gesture
|
||||
// guard let strongSelf = self, let textInputPanelNode = strongSelf.textInputPanelNode else {
|
||||
// return
|
||||
// }
|
||||
// guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation?.peerId else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// var hasEntityKeyboard = false
|
||||
// if case .media = strongSelf.presentationInterfaceState.inputMode {
|
||||
// hasEntityKeyboard = true
|
||||
// }
|
||||
// let _ = (strongSelf.context.account.viewTracker.peerView(peerId)
|
||||
// |> take(1)
|
||||
// |> deliverOnMainQueue).startStandalone(next: { [weak self] peerView in
|
||||
// guard let strongSelf = self, let peer = peerViewMainPeer(peerView) else {
|
||||
// return
|
||||
// }
|
||||
// var sendWhenOnlineAvailable = false
|
||||
// if let presence = peerView.peerPresences[peer.id] as? TelegramUserPresence, case let .present(until) = presence.status {
|
||||
// let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
// if currentTime > until {
|
||||
// sendWhenOnlineAvailable = true
|
||||
// }
|
||||
// }
|
||||
// if peer.id.namespace == Namespaces.Peer.CloudUser && peer.id.id._internalGetInt64Value() == 777000 {
|
||||
// sendWhenOnlineAvailable = false
|
||||
// }
|
||||
//
|
||||
// let controller = ChatSendMessageActionSheetController(context: strongSelf.context, peerId: strongSelf.presentationInterfaceState.chatLocation.peerId, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds, hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, sourceSendButton: node, textInputNode: textInputNode, attachment: true, canSendWhenOnline: sendWhenOnlineAvailable, completion: {
|
||||
// }, sendMessage: { [weak textInputPanelNode] mode in
|
||||
// switch mode {
|
||||
// case .generic:
|
||||
// textInputPanelNode?.sendMessage(.generic)
|
||||
// case .silently:
|
||||
// textInputPanelNode?.sendMessage(.silent)
|
||||
// case .whenOnline:
|
||||
// textInputPanelNode?.sendMessage(.whenOnline)
|
||||
// }
|
||||
// }, schedule: { [weak textInputPanelNode] in
|
||||
// textInputPanelNode?.sendMessage(.schedule)
|
||||
// })
|
||||
// controller.emojiViewProvider = textInputPanelNode.emojiViewProvider
|
||||
// strongSelf.presentInGlobalOverlay(controller)
|
||||
// })
|
||||
}, openScheduledMessages: {
|
||||
}, openPeersNearby: {
|
||||
}, displaySearchResultsTooltip: { _, _ in
|
||||
@ -1075,10 +1030,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func updateCaption(_ caption: NSAttributedString) {
|
||||
if !caption.string.isEmpty {
|
||||
self.loadTextNodeIfNeeded()
|
||||
}
|
||||
self.updateChatPresentationInterfaceState(animated: false, { $0.updatedInterfaceState { $0.withUpdatedComposeInputState(ChatTextInputState(inputText: caption))} })
|
||||
self.textInputPanelNode?.setCaption(caption)
|
||||
}
|
||||
|
||||
private func updateChatPresentationInterfaceState(animated: Bool = true, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
|
||||
@ -1086,16 +1038,16 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
private func updateChatPresentationInterfaceState(transition: ContainedViewLayoutTransition, _ f: (ChatPresentationInterfaceState) -> ChatPresentationInterfaceState, completion externalCompletion: @escaping (ContainedViewLayoutTransition) -> Void = { _ in }) {
|
||||
let presentationInterfaceState = f(self.presentationInterfaceState)
|
||||
let updateInputTextState = self.presentationInterfaceState.interfaceState.effectiveInputState != presentationInterfaceState.interfaceState.effectiveInputState
|
||||
|
||||
self.presentationInterfaceState = presentationInterfaceState
|
||||
|
||||
if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
|
||||
textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated)
|
||||
|
||||
self.textUpdated(presentationInterfaceState.interfaceState.effectiveInputState.inputText)
|
||||
}
|
||||
// let presentationInterfaceState = f(self.presentationInterfaceState)
|
||||
// let updateInputTextState = self.presentationInterfaceState.interfaceState.effectiveInputState != presentationInterfaceState.interfaceState.effectiveInputState
|
||||
//
|
||||
// self.presentationInterfaceState = presentationInterfaceState
|
||||
//
|
||||
// if let textInputPanelNode = self.textInputPanelNode, updateInputTextState {
|
||||
// textInputPanelNode.updateInputTextState(presentationInterfaceState.interfaceState.effectiveInputState, animated: transition.isAnimated)
|
||||
//
|
||||
// self.textUpdated(presentationInterfaceState.interfaceState.effectiveInputState.inputText)
|
||||
// }
|
||||
}
|
||||
|
||||
func updateSelectedIndex(_ index: Int) {
|
||||
@ -1250,32 +1202,52 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
private func loadTextNodeIfNeeded() {
|
||||
if let _ = self.textInputPanelNode {
|
||||
} else {
|
||||
let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(c)
|
||||
}
|
||||
}, makeEntityInputView: self.makeEntityInputView)
|
||||
textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
||||
textInputPanelNode.sendMessage = { [weak self] mode in
|
||||
if let strongSelf = self {
|
||||
strongSelf.sendMessagePressed(mode)
|
||||
}
|
||||
}
|
||||
textInputPanelNode.focusUpdated = { [weak self] focus in
|
||||
if let strongSelf = self, focus {
|
||||
strongSelf.beganTextEditing()
|
||||
}
|
||||
}
|
||||
textInputPanelNode.updateHeight = { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.requestLayout()
|
||||
}
|
||||
}
|
||||
|
||||
let textInputPanelNode = LegacyMessageInputPanelNode(
|
||||
context: self.context,
|
||||
chatLocation: self.presentationInterfaceState.chatLocation,
|
||||
isScheduledMessages: self.isScheduledMessages,
|
||||
present: { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(c)
|
||||
}
|
||||
},
|
||||
presentInGlobalOverlay: { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
strongSelf.present(c)
|
||||
}
|
||||
},
|
||||
makeEntityInputView: self.makeEntityInputView
|
||||
)
|
||||
self.addSubnode(textInputPanelNode)
|
||||
self.textInputPanelNode = textInputPanelNode
|
||||
|
||||
textInputPanelNode.alpha = self.isSelecting ? 1.0 : 0.0
|
||||
textInputPanelNode.isUserInteractionEnabled = self.isSelecting
|
||||
// let textInputPanelNode = AttachmentTextInputPanelNode(context: self.context, presentationInterfaceState: self.presentationInterfaceState, isAttachment: true, isScheduledMessages: self.isScheduledMessages, presentController: { [weak self] c in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.present(c)
|
||||
// }
|
||||
// }, makeEntityInputView: self.makeEntityInputView)
|
||||
// textInputPanelNode.interfaceInteraction = self.interfaceInteraction
|
||||
// textInputPanelNode.sendMessage = { [weak self] mode in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.sendMessagePressed(mode)
|
||||
// }
|
||||
// }
|
||||
// textInputPanelNode.focusUpdated = { [weak self] focus in
|
||||
// if let strongSelf = self, focus {
|
||||
// strongSelf.beganTextEditing()
|
||||
// }
|
||||
// }
|
||||
// textInputPanelNode.updateHeight = { [weak self] _ in
|
||||
// if let strongSelf = self {
|
||||
// strongSelf.requestLayout()
|
||||
// }
|
||||
// }
|
||||
// self.addSubnode(textInputPanelNode)
|
||||
// self.textInputPanelNode = textInputPanelNode
|
||||
//
|
||||
// textInputPanelNode.alpha = self.isSelecting ? 1.0 : 0.0
|
||||
// textInputPanelNode.isUserInteractionEnabled = self.isSelecting
|
||||
}
|
||||
}
|
||||
|
||||
@ -1448,7 +1420,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
if isSelecting {
|
||||
self.loadTextNodeIfNeeded()
|
||||
} else {
|
||||
self.textInputPanelNode?.ensureUnfocused()
|
||||
let _ = self.textInputPanelNode?.dismissInput()
|
||||
}
|
||||
var textPanelHeight: CGFloat = 0.0
|
||||
if let textInputPanelNode = self.textInputPanelNode {
|
||||
@ -1458,7 +1430,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
|
||||
if textInputPanelNode.frame.width.isZero {
|
||||
panelTransition = .immediate
|
||||
}
|
||||
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, interfaceState: self.presentationInterfaceState, metrics: layout.metrics, isMediaInputExpanded: false)
|
||||
let panelHeight = textInputPanelNode.updateLayout(width: layout.size.width, leftInset: insets.left + layout.safeInsets.left, rightInset: insets.right + layout.safeInsets.right, bottomInset: 0.0, keyboardHeight: layout.inputHeight ?? 0.0, additionalSideInsets: UIEdgeInsets(), maxHeight: layout.size.height / 2.0, isSecondary: false, transition: panelTransition, metrics: layout.metrics, isMediaInputExpanded: false)
|
||||
let panelFrame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: panelHeight)
|
||||
if textInputPanelNode.frame.width.isZero {
|
||||
textInputPanelNode.frame = panelFrame
|
||||
|
@ -305,6 +305,8 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
} else {
|
||||
messageText = strings.Notification_Story
|
||||
}
|
||||
case _ as TelegramMediaGiveaway:
|
||||
messageText = strings.Message_Giveaway
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ public final class List<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
|
||||
private let items: [AnyComponentWithIdentity<ChildEnvironment>]
|
||||
private let direction: Direction
|
||||
private let centerAlignment: Bool
|
||||
private let appear: Transition.Appear
|
||||
|
||||
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], direction: Direction = .vertical, appear: Transition.Appear = .default()) {
|
||||
public init(_ items: [AnyComponentWithIdentity<ChildEnvironment>], direction: Direction = .vertical, centerAlignment: Bool = false, appear: Transition.Appear = .default()) {
|
||||
self.items = items
|
||||
self.direction = direction
|
||||
self.centerAlignment = centerAlignment
|
||||
self.appear = appear
|
||||
}
|
||||
|
||||
@ -26,6 +28,9 @@ public final class List<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
if lhs.direction != rhs.direction {
|
||||
return false
|
||||
}
|
||||
if lhs.centerAlignment != rhs.centerAlignment {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -42,6 +47,10 @@ public final class List<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
transition: context.transition
|
||||
)
|
||||
}
|
||||
|
||||
let maxWidth: CGFloat = updatedChildren.reduce(CGFloat(0.0)) { partialResult, child in
|
||||
return max(partialResult, child.size.width)
|
||||
}
|
||||
|
||||
var nextOrigin: CGFloat = 0.0
|
||||
for child in updatedChildren {
|
||||
@ -51,7 +60,13 @@ public final class List<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
position = CGPoint(x: nextOrigin + child.size.width / 2.0, y: child.size.height / 2.0)
|
||||
nextOrigin += child.size.width
|
||||
case .vertical:
|
||||
position = CGPoint(x: child.size.width / 2.0, y: nextOrigin + child.size.height / 2.0)
|
||||
let originX: CGFloat
|
||||
if context.component.centerAlignment {
|
||||
originX = maxWidth / 2.0
|
||||
} else {
|
||||
originX = child.size.width / 2.0
|
||||
}
|
||||
position = CGPoint(x: originX, y: nextOrigin + child.size.height / 2.0)
|
||||
nextOrigin += child.size.height
|
||||
}
|
||||
context.add(child
|
||||
@ -63,8 +78,14 @@ public final class List<ChildEnvironment: Equatable>: CombinedComponent {
|
||||
switch context.component.direction {
|
||||
case .horizontal:
|
||||
return CGSize(width: min(context.availableSize.width, nextOrigin), height: context.availableSize.height)
|
||||
case.vertical:
|
||||
return CGSize(width: context.availableSize.width, height: min(context.availableSize.height, nextOrigin))
|
||||
case .vertical:
|
||||
let width: CGFloat
|
||||
if context.component.centerAlignment {
|
||||
width = maxWidth
|
||||
} else {
|
||||
width = context.availableSize.width
|
||||
}
|
||||
return CGSize(width: width, height: min(context.availableSize.height, nextOrigin))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -224,6 +224,9 @@ public final class LottieAnimationComponent: Component {
|
||||
|
||||
if updateColors, let animationView = self.animationView {
|
||||
if let value = component.colors["__allcolors__"] {
|
||||
for keypath in animationView.allKeypaths(predicate: { $0.keys.last == "Colors" }) {
|
||||
animationView.setValueProvider(GradientValueProvider([value.lottieColorValue, value.lottieColorValue]), keypath: AnimationKeypath(keypath: keypath))
|
||||
}
|
||||
for keypath in animationView.allKeypaths(predicate: { $0.keys.last == "Color" }) {
|
||||
animationView.setValueProvider(ColorValueProvider(value.lottieColorValue), keypath: AnimationKeypath(keypath: keypath))
|
||||
}
|
||||
|
@ -18,7 +18,15 @@ private let productIdentifiers = [
|
||||
|
||||
"org.telegram.telegramPremium.threeMonths.code_x1",
|
||||
"org.telegram.telegramPremium.sixMonths.code_x1",
|
||||
"org.telegram.telegramPremium.twelveMonths.code_x1"
|
||||
"org.telegram.telegramPremium.twelveMonths.code_x1",
|
||||
|
||||
"org.telegram.telegramPremium.threeMonths.code_x5",
|
||||
"org.telegram.telegramPremium.sixMonths.code_x5",
|
||||
"org.telegram.telegramPremium.twelveMonths.code_x5",
|
||||
|
||||
"org.telegram.telegramPremium.threeMonths.code_x10",
|
||||
"org.telegram.telegramPremium.sixMonths.code_x10",
|
||||
"org.telegram.telegramPremium.twelveMonths.code_x10"
|
||||
]
|
||||
|
||||
private extension NSDecimalNumber {
|
||||
@ -253,7 +261,7 @@ public final class InAppPurchaseManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
public func buyProduct(_ product: Product, purpose: AppStoreTransactionPurpose) -> Signal<PurchaseState, PurchaseError> {
|
||||
public func buyProduct(_ product: Product, quantity: Int32 = 1, purpose: AppStoreTransactionPurpose) -> Signal<PurchaseState, PurchaseError> {
|
||||
if !self.canMakePayments {
|
||||
return .fail(.cantMakePayments)
|
||||
}
|
||||
@ -266,7 +274,7 @@ public final class InAppPurchaseManager: NSObject {
|
||||
|
||||
let payment = SKMutablePayment(product: product.skProduct)
|
||||
payment.applicationUsername = accountPeerId
|
||||
payment.quantity = purpose.quantity
|
||||
payment.quantity = Int(quantity)
|
||||
SKPaymentQueue.default().add(payment)
|
||||
|
||||
let productIdentifier = payment.productIdentifier
|
||||
@ -564,6 +572,8 @@ private final class PendingInAppPurchaseState: Codable {
|
||||
case peer
|
||||
case peers
|
||||
case boostPeer
|
||||
case additionalPeerIds
|
||||
case onlyNewSubscribers
|
||||
case randomId
|
||||
case untilDate
|
||||
}
|
||||
@ -582,7 +592,7 @@ private final class PendingInAppPurchaseState: Codable {
|
||||
case restore
|
||||
case gift(peerId: EnginePeer.Id)
|
||||
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?)
|
||||
case giveaway(boostPeer: EnginePeer.Id, randomId: Int64, untilDate: Int32)
|
||||
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32)
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
@ -607,6 +617,8 @@ private final class PendingInAppPurchaseState: Codable {
|
||||
case .giveaway:
|
||||
self = .giveaway(
|
||||
boostPeer: EnginePeer.Id(try container.decode(Int64.self, forKey: .boostPeer)),
|
||||
additionalPeerIds: try container.decode([Int64].self, forKey: .randomId).map { EnginePeer.Id($0) },
|
||||
onlyNewSubscribers: try container.decode(Bool.self, forKey: .onlyNewSubscribers),
|
||||
randomId: try container.decode(Int64.self, forKey: .randomId),
|
||||
untilDate: try container.decode(Int32.self, forKey: .untilDate)
|
||||
)
|
||||
@ -632,9 +644,11 @@ private final class PendingInAppPurchaseState: Codable {
|
||||
try container.encode(PurposeType.giftCode.rawValue, forKey: .type)
|
||||
try container.encode(peerIds.map { $0.toInt64() }, forKey: .peers)
|
||||
try container.encodeIfPresent(boostPeer?.toInt64(), forKey: .boostPeer)
|
||||
case let .giveaway(boostPeer, randomId, untilDate):
|
||||
case let .giveaway(boostPeer, additionalPeerIds, onlyNewSubscribers, randomId, untilDate):
|
||||
try container.encode(PurposeType.giveaway.rawValue, forKey: .type)
|
||||
try container.encode(boostPeer.toInt64(), forKey: .boostPeer)
|
||||
try container.encode(additionalPeerIds.map { $0.toInt64() }, forKey: .additionalPeerIds)
|
||||
try container.encode(onlyNewSubscribers, forKey: .onlyNewSubscribers)
|
||||
try container.encode(randomId, forKey: .randomId)
|
||||
try container.encode(untilDate, forKey: .untilDate)
|
||||
}
|
||||
@ -652,8 +666,8 @@ private final class PendingInAppPurchaseState: Codable {
|
||||
self = .gift(peerId: peerId)
|
||||
case let .giftCode(peerIds, boostPeer, _, _):
|
||||
self = .giftCode(peerIds: peerIds, boostPeer: boostPeer)
|
||||
case let .giveaway(boostPeer, randomId, untilDate, _, _):
|
||||
self = .giveaway(boostPeer: boostPeer, randomId: randomId, untilDate: untilDate)
|
||||
case let .giveaway(boostPeer, additionalPeerIds, onlyNewSubscribers, randomId, untilDate, _, _):
|
||||
self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -670,19 +684,8 @@ private final class PendingInAppPurchaseState: Codable {
|
||||
return .gift(peerId: peerId, currency: currency, amount: amount)
|
||||
case let .giftCode(peerIds, boostPeer):
|
||||
return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount)
|
||||
case let .giveaway(boostPeer, randomId, untilDate):
|
||||
return .giveaway(boostPeer: boostPeer, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
|
||||
}
|
||||
}
|
||||
|
||||
var quantity: Int {
|
||||
switch self {
|
||||
case .subscription, .upgrade, .restore, .gift:
|
||||
return 1
|
||||
case let .giftCode(peerIds, _):
|
||||
return peerIds.count
|
||||
case .giveaway:
|
||||
return 1
|
||||
case let .giveaway(boostPeer, additionalPeerIds, onlyNewSubscribers, randomId, untilDate):
|
||||
return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,3 +17,19 @@ open class ItemListControllerFooterItemNode: ASDisplayNode {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol ItemListControllerHeaderItem {
|
||||
func isEqual(to: ItemListControllerHeaderItem) -> Bool
|
||||
func node(current: ItemListControllerHeaderItemNode?) -> ItemListControllerHeaderItemNode
|
||||
}
|
||||
|
||||
open class ItemListControllerHeaderItemNode: ASDisplayNode {
|
||||
open func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
open func updateContentOffset(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -127,6 +127,7 @@ private struct ItemListNodeTransition {
|
||||
let emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
let searchItem: ItemListControllerSearch?
|
||||
let toolbarItem: ItemListToolbarItem?
|
||||
let headerItem: ItemListControllerHeaderItem?
|
||||
let footerItem: ItemListControllerFooterItem?
|
||||
let focusItemTag: ItemListItemTag?
|
||||
let ensureVisibleItemTag: ItemListItemTag?
|
||||
@ -146,6 +147,7 @@ public final class ItemListNodeState {
|
||||
let emptyStateItem: ItemListControllerEmptyStateItem?
|
||||
let searchItem: ItemListControllerSearch?
|
||||
let toolbarItem: ItemListToolbarItem?
|
||||
let headerItem: ItemListControllerHeaderItem?
|
||||
let footerItem: ItemListControllerFooterItem?
|
||||
let animateChanges: Bool
|
||||
let crossfadeState: Bool
|
||||
@ -154,13 +156,14 @@ public final class ItemListNodeState {
|
||||
let ensureVisibleItemTag: ItemListItemTag?
|
||||
let initialScrollToItem: ListViewScrollToItem?
|
||||
|
||||
public init<T: ItemListNodeEntry>(presentationData: ItemListPresentationData, entries: [T], style: ItemListStyle, focusItemTag: ItemListItemTag? = nil, ensureVisibleItemTag: ItemListItemTag? = nil, emptyStateItem: ItemListControllerEmptyStateItem? = nil, searchItem: ItemListControllerSearch? = nil, toolbarItem: ItemListToolbarItem? = nil, footerItem: ItemListControllerFooterItem? = nil, initialScrollToItem: ListViewScrollToItem? = nil, crossfadeState: Bool = false, animateChanges: Bool = true, scrollEnabled: Bool = true) {
|
||||
public init<T: ItemListNodeEntry>(presentationData: ItemListPresentationData, entries: [T], style: ItemListStyle, focusItemTag: ItemListItemTag? = nil, ensureVisibleItemTag: ItemListItemTag? = nil, emptyStateItem: ItemListControllerEmptyStateItem? = nil, searchItem: ItemListControllerSearch? = nil, toolbarItem: ItemListToolbarItem? = nil, headerItem: ItemListControllerHeaderItem? = nil, footerItem: ItemListControllerFooterItem? = nil, initialScrollToItem: ListViewScrollToItem? = nil, crossfadeState: Bool = false, animateChanges: Bool = true, scrollEnabled: Bool = true) {
|
||||
self.presentationData = presentationData
|
||||
self.entries = entries.map { $0 }
|
||||
self.style = style
|
||||
self.emptyStateItem = emptyStateItem
|
||||
self.searchItem = searchItem
|
||||
self.toolbarItem = toolbarItem
|
||||
self.headerItem = headerItem
|
||||
self.footerItem = footerItem
|
||||
self.crossfadeState = crossfadeState
|
||||
self.animateChanges = animateChanges
|
||||
@ -250,6 +253,9 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
private var searchNode: ItemListControllerSearchNode?
|
||||
|
||||
private var toolbarItem: ItemListToolbarItem?
|
||||
|
||||
private var headerItem: ItemListControllerHeaderItem?
|
||||
private var headerItemNode: ItemListControllerHeaderItemNode?
|
||||
|
||||
private var footerItem: ItemListControllerFooterItem?
|
||||
private var footerItemNode: ItemListControllerFooterItemNode?
|
||||
@ -375,9 +381,19 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
} else {
|
||||
transition = .immediate
|
||||
}
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(value, transition: transition)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
|
||||
}
|
||||
case .unknown, .none:
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
if let headerItemNode = strongSelf.headerItemNode {
|
||||
headerItemNode.updateContentOffset(0.0, transition: .immediate)
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(0.0, transition: .immediate)
|
||||
} else {
|
||||
strongSelf.navigationBar.updateBackgroundAlpha(1.0, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.previousContentOffset = offset
|
||||
@ -425,7 +441,7 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
scrollToItem = state.initialScrollToItem
|
||||
}
|
||||
|
||||
return ItemListNodeTransition(theme: presentationData.theme, strings: presentationData.strings, entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, searchItem: state.searchItem, toolbarItem: state.toolbarItem, footerItem: state.footerItem, focusItemTag: state.focusItemTag, ensureVisibleItemTag: state.ensureVisibleItemTag, scrollToItem: scrollToItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && state.animateChanges, crossfade: state.crossfadeState, mergedEntries: state.entries, scrollEnabled: state.scrollEnabled)
|
||||
return ItemListNodeTransition(theme: presentationData.theme, strings: presentationData.strings, entries: transition, updateStyle: updatedStyle, emptyStateItem: state.emptyStateItem, searchItem: state.searchItem, toolbarItem: state.toolbarItem, headerItem: state.headerItem, footerItem: state.footerItem, focusItemTag: state.focusItemTag, ensureVisibleItemTag: state.ensureVisibleItemTag, scrollToItem: scrollToItem, firstTime: previous == nil, animated: previous != nil && state.animateChanges, animateAlpha: previous != nil && state.animateChanges, crossfade: state.crossfadeState, mergedEntries: state.entries, scrollEnabled: state.scrollEnabled)
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { [weak self] transition in
|
||||
if let strongSelf = self {
|
||||
@ -563,6 +579,12 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if let headerItemNode = self.headerItemNode {
|
||||
let headerHeight = headerItemNode.updateLayout(layout: layout, transition: transition)
|
||||
headerItemNode.frame = CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: 56.0))
|
||||
insets.top += headerHeight
|
||||
}
|
||||
|
||||
if let footerItemNode = self.footerItemNode {
|
||||
let footerHeight = footerItemNode.updateLayout(layout: layout, transition: transition)
|
||||
insets.bottom += footerHeight
|
||||
@ -855,6 +877,43 @@ open class ItemListControllerNode: ASDisplayNode {
|
||||
self.emptyStateNode = nil
|
||||
}
|
||||
}
|
||||
var updateHeaderItem = false
|
||||
if let headerItem = self.headerItem, let updatedHeaderItem = transition.headerItem {
|
||||
updateHeaderItem = !headerItem.isEqual(to: updatedHeaderItem)
|
||||
} else if (self.headerItem != nil) != (transition.headerItem != nil) {
|
||||
updateHeaderItem = true
|
||||
}
|
||||
if updateHeaderItem {
|
||||
self.headerItem = transition.headerItem
|
||||
if let headerItem = transition.headerItem {
|
||||
let updatedNode = headerItem.node(current: self.headerItemNode)
|
||||
if let headerItemNode = self.headerItemNode, updatedNode !== headerItemNode {
|
||||
headerItemNode.removeFromSupernode()
|
||||
}
|
||||
if self.headerItemNode !== updatedNode {
|
||||
self.headerItemNode = updatedNode
|
||||
|
||||
let headerHeight: CGFloat
|
||||
if let validLayout = self.validLayout {
|
||||
headerHeight = updatedNode.updateLayout(layout: validLayout.0, transition: .immediate)
|
||||
} else {
|
||||
headerHeight = 100.0
|
||||
}
|
||||
let _ = headerHeight
|
||||
self.addSubnode(updatedNode)
|
||||
}
|
||||
} else if let headerItemNode = self.headerItemNode {
|
||||
let headerHeight: CGFloat
|
||||
if let validLayout = self.validLayout {
|
||||
headerHeight = headerItemNode.updateLayout(layout: validLayout.0, transition: .immediate)
|
||||
} else {
|
||||
headerHeight = 100.0
|
||||
}
|
||||
let _ = headerHeight
|
||||
headerItemNode.removeFromSupernode()
|
||||
self.headerItemNode = nil
|
||||
}
|
||||
}
|
||||
var updateFooterItem = false
|
||||
if let footerItem = self.footerItem, let updatedFooterItem = transition.footerItem {
|
||||
updateFooterItem = !footerItem.isEqual(to: updatedFooterItem)
|
||||
|
@ -39,10 +39,11 @@ typedef enum
|
||||
@property (nonatomic, readonly) UIColor *badgeTextColor;
|
||||
@property (nonatomic, readonly) UIImage *sendIconImage;
|
||||
@property (nonatomic, readonly) UIImage *doneIconImage;
|
||||
@property (nonatomic, readonly) UIImage *scheduleIconImage;
|
||||
|
||||
@property (nonatomic, readonly) UIColor *maybeAccentColor;
|
||||
|
||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor;
|
||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage scheduleIconImage:(UIImage *)scheduleIconImage maybeAccentColor:(UIColor *)maybeAccentColor;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -37,7 +37,7 @@
|
||||
|
||||
@property (nonatomic, readonly) UIView *timerButton;
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasSelectionPanel:(bool)hasSelectionPanel hasCameraButton:(bool)hasCameraButton recipientName:(NSString *)recipientName;
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasSelectionPanel:(bool)hasSelectionPanel hasCameraButton:(bool)hasCameraButton recipientName:(NSString *)recipientName isScheduledMessages:(bool)isScheduledMessages;
|
||||
|
||||
- (void)setSelectedItemsModel:(TGMediaPickerGallerySelectedItemsModel *)selectedItemsModel;
|
||||
- (void)setEditorTabPressed:(void (^)(TGPhotoEditorTab tab))editorTabPressed;
|
||||
|
@ -46,7 +46,7 @@
|
||||
@property (nonatomic, readonly) TGMediaSelectionContext *selectionContext;
|
||||
@property (nonatomic, strong) id<TGPhotoPaintStickersContext> stickersContext;
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName;
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName isScheduledMessages:(bool)isScheduledMessages;
|
||||
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab;
|
||||
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots fromRect:(CGRect)fromRect;
|
||||
|
@ -30,7 +30,8 @@ typedef enum
|
||||
{
|
||||
TGPhotoEditorDoneButtonSend,
|
||||
TGPhotoEditorDoneButtonCheck,
|
||||
TGPhotoEditorDoneButtonDone
|
||||
TGPhotoEditorDoneButtonDone,
|
||||
TGPhotoEditorDoneButtonSchedule
|
||||
} TGPhotoEditorDoneButton;
|
||||
|
||||
@interface TGPhotoToolbarView : UIView
|
||||
|
@ -654,7 +654,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
|
||||
if (self.cameraMode == PGCameraModeVideo || self.cameraMode == PGCameraModeSquareVideo || self.cameraMode == PGCameraModeSquareSwing)
|
||||
return self.captureSession.videoDevice.torchActive;
|
||||
|
||||
return self.captureSession.videoDevice.flashActive;
|
||||
return self.captureSession.imageOutput.isFlashScene;
|
||||
}
|
||||
|
||||
- (bool)flashAvailable
|
||||
|
@ -1536,7 +1536,7 @@ static CGPoint TGCameraControllerClampPointToScreenSize(__unused id self, __unus
|
||||
}];
|
||||
|
||||
bool hasCamera = !self.inhibitMultipleCapture && (((_intent == TGCameraControllerGenericIntent || _intent == TGCameraControllerGenericPhotoOnlyIntent || _intent == TGCameraControllerGenericVideoOnlyIntent) && !_shortcut) || (_intent == TGCameraControllerPassportMultipleIntent));
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName];
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:galleryItems focusItem:focusItem selectionContext:_items.count > 1 ? selectionContext : nil editingContext:editingContext hasCaptions:self.allowCaptions allowCaptionEntities:self.allowCaptionEntities hasTimer:self.hasTimer onlyCrop:_intent == TGCameraControllerPassportIntent || _intent == TGCameraControllerPassportIdIntent || _intent == TGCameraControllerPassportMultipleIntent inhibitDocumentCaptions:self.inhibitDocumentCaptions hasSelectionPanel:true hasCamera:hasCamera recipientName:self.recipientName isScheduledMessages:false];
|
||||
model.inhibitMute = self.inhibitMute;
|
||||
model.controller = galleryController;
|
||||
model.stickersContext = self.stickersContext;
|
||||
|
@ -1894,7 +1894,7 @@
|
||||
|
||||
@implementation TGMediaAssetsPallete
|
||||
|
||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage maybeAccentColor:(UIColor *)maybeAccentColor
|
||||
+ (instancetype)palleteWithDark:(bool)dark backgroundColor:(UIColor *)backgroundColor selectionColor:(UIColor *)selectionColor separatorColor:(UIColor *)separatorColor textColor:(UIColor *)textColor secondaryTextColor:(UIColor *)secondaryTextColor accentColor:(UIColor *)accentColor destructiveColor:(UIColor *)destructiveColor barBackgroundColor:(UIColor *)barBackgroundColor barSeparatorColor:(UIColor *)barSeparatorColor navigationTitleColor:(UIColor *)navigationTitleColor badge:(UIImage *)badge badgeTextColor:(UIColor *)badgeTextColor sendIconImage:(UIImage *)sendIconImage doneIconImage:(UIImage *)doneIconImage scheduleIconImage:(UIImage *)scheduleIconImage maybeAccentColor:(UIColor *)maybeAccentColor
|
||||
{
|
||||
TGMediaAssetsPallete *pallete = [[TGMediaAssetsPallete alloc] init];
|
||||
pallete->_isDark = dark;
|
||||
@ -1912,6 +1912,7 @@
|
||||
pallete->_badgeTextColor = badgeTextColor;
|
||||
pallete->_sendIconImage = sendIconImage;
|
||||
pallete->_doneIconImage = doneIconImage;
|
||||
pallete->_scheduleIconImage = scheduleIconImage;
|
||||
pallete->_maybeAccentColor = maybeAccentColor;
|
||||
return pallete;
|
||||
}
|
||||
|
@ -125,7 +125,7 @@
|
||||
|
||||
@synthesize safeAreaInset = _safeAreaInset;
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasSelectionPanel:(bool)hasSelectionPanel hasCameraButton:(bool)hasCameraButton recipientName:(NSString *)recipientName
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext hasSelectionPanel:(bool)hasSelectionPanel hasCameraButton:(bool)hasCameraButton recipientName:(NSString *)recipientName isScheduledMessages:(bool)isScheduledMessages
|
||||
{
|
||||
self = [super initWithFrame:CGRectZero];
|
||||
if (self != nil)
|
||||
@ -392,13 +392,15 @@
|
||||
_captionMixin.stickersContext = stickersContext;
|
||||
[_captionMixin createInputPanelIfNeeded];
|
||||
|
||||
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
|
||||
TGPhotoEditorDoneButton doneButton = isScheduledMessages ? TGPhotoEditorDoneButtonSchedule : TGPhotoEditorDoneButtonSend;
|
||||
|
||||
_portraitToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false];
|
||||
_portraitToolbarView.cancelPressed = toolbarCancelPressed;
|
||||
_portraitToolbarView.donePressed = toolbarDonePressed;
|
||||
_portraitToolbarView.doneLongPressed = toolbarDoneLongPressed;
|
||||
[_wrapperView addSubview:_portraitToolbarView];
|
||||
|
||||
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:TGPhotoEditorDoneButtonSend solidBackground:false];
|
||||
_landscapeToolbarView = [[TGPhotoToolbarView alloc] initWithContext:_context backButton:TGPhotoEditorBackButtonBack doneButton:doneButton solidBackground:false];
|
||||
_landscapeToolbarView.cancelPressed = toolbarCancelPressed;
|
||||
_landscapeToolbarView.donePressed = toolbarDonePressed;
|
||||
_landscapeToolbarView.doneLongPressed = toolbarDoneLongPressed;
|
||||
|
@ -39,6 +39,7 @@
|
||||
bool _inhibitDocumentCaptions;
|
||||
NSString *_recipientName;
|
||||
bool _hasCamera;
|
||||
bool _isScheduledMessages;
|
||||
}
|
||||
|
||||
@property (nonatomic, weak) TGPhotoEditorController *editorController;
|
||||
@ -47,7 +48,7 @@
|
||||
|
||||
@implementation TGMediaPickerGalleryModel
|
||||
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName
|
||||
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName isScheduledMessages:(bool)isScheduledMessages
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
@ -68,6 +69,7 @@
|
||||
_inhibitDocumentCaptions = inhibitDocumentCaptions;
|
||||
_recipientName = recipientName;
|
||||
_hasCamera = hasCamera;
|
||||
_isScheduledMessages = isScheduledMessages;
|
||||
|
||||
__weak TGMediaPickerGalleryModel *weakSelf = self;
|
||||
if (selectionContext != nil)
|
||||
@ -177,7 +179,7 @@
|
||||
if (_interfaceView == nil)
|
||||
{
|
||||
__weak TGMediaPickerGalleryModel *weakSelf = self;
|
||||
_interfaceView = [[TGMediaPickerGalleryInterfaceView alloc] initWithContext:_context focusItem:_initialFocusItem selectionContext:_selectionContext editingContext:_editingContext stickersContext:_stickersContext hasSelectionPanel:_hasSelectionPanel hasCameraButton:_hasCamera recipientName:_recipientName];
|
||||
_interfaceView = [[TGMediaPickerGalleryInterfaceView alloc] initWithContext:_context focusItem:_initialFocusItem selectionContext:_selectionContext editingContext:_editingContext stickersContext:_stickersContext hasSelectionPanel:_hasSelectionPanel hasCameraButton:_hasCamera recipientName:_recipientName isScheduledMessages:_isScheduledMessages];
|
||||
_interfaceView.hasCaptions = _hasCaptions;
|
||||
_interfaceView.allowCaptionEntities = _allowCaptionEntities;
|
||||
_interfaceView.hasTimer = _hasTimer;
|
||||
|
@ -84,7 +84,7 @@
|
||||
|
||||
NSArray *galleryItems = [self prepareGalleryItemsForFetchResult:fetchResult selectionContext:selectionContext editingContext:editingContext stickersContext:stickersContext asFile:asFile enumerationBlock:enumerationBlock];
|
||||
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:[_windowManager context] items:galleryItems focusItem:focusItem selectionContext:selectionContext editingContext:editingContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions hasSelectionPanel:true hasCamera:false recipientName:recipientName];
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:[_windowManager context] items:galleryItems focusItem:focusItem selectionContext:selectionContext editingContext:editingContext hasCaptions:hasCaptions allowCaptionEntities:allowCaptionEntities hasTimer:hasTimer onlyCrop:onlyCrop inhibitDocumentCaptions:inhibitDocumentCaptions hasSelectionPanel:true hasCamera:false recipientName:recipientName isScheduledMessages:false];
|
||||
_galleryModel = model;
|
||||
model.stickersContext = stickersContext;
|
||||
model.inhibitMute = inhibitMute;
|
||||
|
@ -112,6 +112,9 @@
|
||||
doneImage = pallete != nil ? pallete.doneIconImage : TGTintedImage([UIImage imageNamed:@"Editor/Commit"], [UIColor whiteColor]);
|
||||
break;
|
||||
}
|
||||
case TGPhotoEditorDoneButtonSchedule:
|
||||
doneImage = pallete != nil ? pallete.scheduleIconImage : TGComponentsImageNamed(@"PhotoPickerSendIcon");
|
||||
break;
|
||||
default:
|
||||
{
|
||||
doneImage = pallete != nil ? pallete.sendIconImage : TGComponentsImageNamed(@"PhotoPickerSendIcon");
|
||||
|
@ -178,7 +178,7 @@
|
||||
galleryItem.editingContext = editingContext;
|
||||
galleryItem.stickersContext = stickersContext;
|
||||
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName];
|
||||
TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName isScheduledMessages:false];
|
||||
model.controller = galleryController;
|
||||
model.stickersContext = stickersContext;
|
||||
|
||||
|
@ -27,7 +27,6 @@ swift_library(
|
||||
"//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode",
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/AttachmentUI:AttachmentUI",
|
||||
"//submodules/DrawingUI:DrawingUI",
|
||||
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
|
||||
"//submodules/TelegramUI/Components/MediaEditor",
|
||||
|
@ -14,7 +14,6 @@ import MimeTypes
|
||||
import LocalMediaResources
|
||||
import LegacyUI
|
||||
import TextFormat
|
||||
import AttachmentUI
|
||||
|
||||
public func guessMimeTypeByFileExtension(_ ext: String) -> String {
|
||||
return TGMimeTypeMap.mimeType(forExtension: ext) ?? "application/binary"
|
||||
@ -60,64 +59,6 @@ public func configureLegacyAssetPicker(_ controller: TGMediaAssetsController, co
|
||||
}
|
||||
}
|
||||
|
||||
public class LegacyAssetPickerContext: AttachmentMediaPickerContext {
|
||||
private weak var controller: TGMediaAssetsController?
|
||||
|
||||
public var selectionCount: Signal<Int, NoError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = self?.controller?.selectionContext.selectionChangedSignal().start(next: { [weak self] value in
|
||||
subscriber.putNext(Int(self?.controller?.selectionContext.count() ?? 0))
|
||||
}, error: { _ in }, completed: { })
|
||||
return ActionDisposable {
|
||||
disposable?.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var caption: Signal<NSAttributedString?, NoError> {
|
||||
return Signal { [weak self] subscriber in
|
||||
let disposable = self?.controller?.editingContext.forcedCaption().start(next: { caption in
|
||||
if let caption = caption as? NSAttributedString {
|
||||
subscriber.putNext(caption)
|
||||
} else {
|
||||
subscriber.putNext(nil)
|
||||
}
|
||||
}, error: { _ in }, completed: { })
|
||||
return ActionDisposable {
|
||||
disposable?.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var loadingProgress: Signal<CGFloat?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
public var mainButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
public init(controller: TGMediaAssetsController) {
|
||||
self.controller = controller
|
||||
}
|
||||
|
||||
public func setCaption(_ caption: NSAttributedString) {
|
||||
self.controller?.editingContext.setForcedCaption(caption, skipUpdate: true)
|
||||
}
|
||||
|
||||
public func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
||||
self.controller?.send(mode == .silently, whenOnline: mode == .whenOnline)
|
||||
}
|
||||
|
||||
public func schedule() {
|
||||
self.controller?.schedule(false)
|
||||
}
|
||||
|
||||
public func mainButtonAction() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public func legacyAssetPicker(context: AccountContext, presentationData: PresentationData, editingMedia: Bool, fileMode: Bool, peer: Peer?, threadTitle: String?, saveEditedPhotos: Bool, allowGrouping: Bool, selectionLimit: Int) -> Signal<(LegacyComponentsContext) -> TGMediaAssetsController, Void> {
|
||||
let isSecretChat = (peer?.id.namespace._internalGetInt32Value() ?? 0) == Namespaces.Peer.SecretChat._internalGetInt32Value()
|
||||
|
||||
|
@ -20,7 +20,6 @@ swift_library(
|
||||
"//submodules/DeviceAccess:DeviceAccess",
|
||||
"//submodules/LegacyComponents:LegacyComponents",
|
||||
"//submodules/StickerResources:StickerResources",
|
||||
"//submodules/AttachmentUI:AttachmentUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -4,7 +4,6 @@ import Display
|
||||
import SwiftSignalKit
|
||||
import LegacyComponents
|
||||
import TelegramPresentationData
|
||||
import AttachmentUI
|
||||
|
||||
public enum LegacyControllerPresentation {
|
||||
case custom
|
||||
@ -387,7 +386,7 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
|
||||
let navigationBar = presentationTheme.rootController.navigationBar
|
||||
let tabBar = presentationTheme.rootController.tabBar
|
||||
|
||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, destructiveColor: theme.itemDestructiveColor, barBackgroundColor: navigationBar.opaqueBackgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.opaqueBackgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, destructiveColor: theme.itemDestructiveColor, barBackgroundColor: navigationBar.opaqueBackgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.opaqueBackgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), scheduleIconImage: PresentationResourcesChat.chatInputPanelScheduleButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||
}
|
||||
|
||||
public func checkButtonPallete() -> TGCheckButtonPallete! {
|
||||
@ -403,7 +402,7 @@ public final class LegacyControllerContext: NSObject, LegacyComponentsContext {
|
||||
}
|
||||
}
|
||||
|
||||
open class LegacyController: ViewController, PresentableController, AttachmentContainable {
|
||||
open class LegacyController: ViewController, PresentableController {
|
||||
public private(set) var legacyController: UIViewController!
|
||||
private let presentation: LegacyControllerPresentation
|
||||
|
||||
@ -439,18 +438,7 @@ open class LegacyController: ViewController, PresentableController, AttachmentCo
|
||||
}
|
||||
|
||||
public var disposables = DisposableSet()
|
||||
|
||||
open var requestAttachmentMenuExpansion: () -> Void = {}
|
||||
open var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||
open var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
||||
open var cancelPanGesture: () -> Void = { }
|
||||
open var isContainerPanning: () -> Bool = { return false }
|
||||
open var isContainerExpanded: () -> Bool = { return false }
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
self.sizeClass.set(SSignal.single(UIUserInterfaceSizeClass.compact.rawValue as NSNumber))
|
||||
self.presentation = presentation
|
||||
|
@ -290,7 +290,7 @@ private final class LegacyComponentsGlobalsProviderImpl: NSObject, LegacyCompone
|
||||
let navigationBar = presentationTheme.rootController.navigationBar
|
||||
let tabBar = presentationTheme.rootController.tabBar
|
||||
|
||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, destructiveColor: theme.itemDestructiveColor, barBackgroundColor: navigationBar.opaqueBackgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.opaqueBackgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||
return TGMediaAssetsPallete(dark: presentationTheme.overallDarkAppearance, backgroundColor: theme.plainBackgroundColor, selectionColor: theme.itemHighlightedBackgroundColor, separatorColor: theme.itemPlainSeparatorColor, textColor: theme.itemPrimaryTextColor, secondaryTextColor: theme.controlSecondaryColor, accentColor: theme.itemAccentColor, destructiveColor: theme.itemDestructiveColor, barBackgroundColor: navigationBar.opaqueBackgroundColor, barSeparatorColor: tabBar.separatorColor, navigationTitleColor: navigationBar.primaryTextColor, badge: generateStretchableFilledCircleImage(diameter: 22.0, color: navigationBar.accentTextColor), badgeTextColor: navigationBar.opaqueBackgroundColor, sendIconImage: PresentationResourcesChat.chatInputPanelSendButtonImage(presentationTheme), doneIconImage: PresentationResourcesChat.chatInputPanelApplyButtonImage(presentationTheme), scheduleIconImage: PresentationResourcesChat.chatInputPanelScheduleButtonImage(presentationTheme), maybeAccentColor: navigationBar.accentTextColor)
|
||||
}
|
||||
|
||||
func checkButtonPallete() -> TGCheckButtonPallete! {
|
||||
|
@ -135,7 +135,7 @@ func presentLegacyMediaPickerGallery(context: AccountContext, peer: EnginePeer?,
|
||||
}
|
||||
}
|
||||
|
||||
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: true, allowCaptionEntities: true, hasTimer: hasTimer, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: true, hasCamera: false, recipientName: recipientName)!
|
||||
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: true, allowCaptionEntities: true, hasTimer: hasTimer, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: true, hasCamera: false, recipientName: recipientName, isScheduledMessages: isScheduledMessages)!
|
||||
model.stickersContext = paintStickersContext
|
||||
controller.model = model
|
||||
model.controller = controller
|
||||
|
@ -99,12 +99,12 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
"//submodules/AttachmentUI:AttachmentUI",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/ItemListPeerItem:ItemListPeerItem",
|
||||
"//submodules/ItemListPeerActionItem:ItemListPeerActionItem",
|
||||
"//submodules/TelegramUI/Components/ItemListDatePickerItem:ItemListDatePickerItem",
|
||||
"//submodules/TelegramUI/Components/ShareWithPeersScreen",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -18,6 +18,7 @@ import ItemListDatePickerItem
|
||||
import ItemListPeerActionItem
|
||||
import ShareWithPeersScreen
|
||||
import InAppPurchaseManager
|
||||
import UndoUI
|
||||
|
||||
private final class CreateGiveawayControllerArguments {
|
||||
let context: AccountContext
|
||||
@ -25,13 +26,15 @@ private final class CreateGiveawayControllerArguments {
|
||||
let dismissInput: () -> Void
|
||||
let openPeersSelection: () -> Void
|
||||
let openChannelsSelection: () -> Void
|
||||
let openPremiumIntro: () -> Void
|
||||
|
||||
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void) {
|
||||
init(context: AccountContext, updateState: @escaping ((CreateGiveawayControllerState) -> CreateGiveawayControllerState) -> Void, dismissInput: @escaping () -> Void, openPeersSelection: @escaping () -> Void, openChannelsSelection: @escaping () -> Void, openPremiumIntro: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.updateState = updateState
|
||||
self.dismissInput = dismissInput
|
||||
self.openPeersSelection = openPeersSelection
|
||||
self.openChannelsSelection = openChannelsSelection
|
||||
self.openPremiumIntro = openPremiumIntro
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,12 +66,15 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case createGiveaway(PresentationTheme, String, String, Bool)
|
||||
case awardUsers(PresentationTheme, String, String, Bool)
|
||||
|
||||
case subscriptionsHeader(PresentationTheme, String)
|
||||
case prepaidHeader(PresentationTheme, String)
|
||||
case prepaid(PresentationTheme, String, String, Int32, Int32)
|
||||
|
||||
case subscriptionsHeader(PresentationTheme, String, String)
|
||||
case subscriptions(PresentationTheme, Int32)
|
||||
case subscriptionsInfo(PresentationTheme, String)
|
||||
|
||||
case channelsHeader(PresentationTheme, String)
|
||||
case channel(Int32, PresentationTheme, EnginePeer, Int32)
|
||||
case channel(Int32, PresentationTheme, EnginePeer, Int32?)
|
||||
case channelAdd(PresentationTheme, String)
|
||||
case channelsInfo(PresentationTheme, String)
|
||||
|
||||
@ -83,14 +89,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case timeInfo(PresentationTheme, String)
|
||||
|
||||
case durationHeader(PresentationTheme, String)
|
||||
case duration(Int32, PresentationTheme, String, String, String, String, String?, Bool)
|
||||
case duration(Int32, PresentationTheme, Int32, String, String, String, String?, Bool)
|
||||
case durationInfo(PresentationTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .header:
|
||||
return CreateGiveawaySection.header.rawValue
|
||||
case .createGiveaway, .awardUsers:
|
||||
case .createGiveaway, .awardUsers, .prepaidHeader, .prepaid:
|
||||
return CreateGiveawaySection.mode.rawValue
|
||||
case .subscriptionsHeader, .subscriptions, .subscriptionsInfo:
|
||||
return CreateGiveawaySection.subscriptions.rawValue
|
||||
@ -113,16 +119,20 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
return 0
|
||||
case .awardUsers:
|
||||
return 1
|
||||
case .subscriptionsHeader:
|
||||
case .prepaidHeader:
|
||||
return 2
|
||||
case .subscriptions:
|
||||
case .prepaid:
|
||||
return 3
|
||||
case .subscriptionsInfo:
|
||||
case .subscriptionsHeader:
|
||||
return 4
|
||||
case .channelsHeader:
|
||||
case .subscriptions:
|
||||
return 5
|
||||
case .subscriptionsInfo:
|
||||
return 6
|
||||
case .channelsHeader:
|
||||
return 7
|
||||
case let .channel(index, _, _, _):
|
||||
return 6 + index
|
||||
return 8 + index
|
||||
case .channelAdd:
|
||||
return 100
|
||||
case .channelsInfo:
|
||||
@ -172,8 +182,20 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionsHeader(lhsTheme, lhsText):
|
||||
if case let .subscriptionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
case let .prepaidHeader(lhsTheme, lhsText):
|
||||
if case let .prepaidHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .prepaid(lhsTheme, lhsText, lhsSubtext, lhsBoosts, lhsMonths):
|
||||
if case let .prepaid(rhsTheme, rhsText, rhsSubtext, rhsBoosts, rhsMonths) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsSubtext == rhsSubtext, lhsBoosts == rhsBoosts, lhsMonths == rhsMonths {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .subscriptionsHeader(lhsTheme, lhsText, lhsAdditionalText):
|
||||
if case let .subscriptionsHeader(rhsTheme, rhsText, rhsAdditionalText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsAdditionalText == rhsAdditionalText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -269,8 +291,8 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .duration(lhsIndex, lhsTheme, lhsProductId, lhsTitle, lhsSubtitle, lhsLabel, lhsBadge, lhsIsSelected):
|
||||
if case let .duration(rhsIndex, rhsTheme, rhsProductId, rhsTitle, rhsSubtitle, rhsLabel, rhsBadge, rhsIsSelected) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsProductId == rhsProductId, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel, lhsBadge == rhsBadge, lhsIsSelected == rhsIsSelected {
|
||||
case let .duration(lhsIndex, lhsTheme, lhsMonths, lhsTitle, lhsSubtitle, lhsLabel, lhsBadge, lhsIsSelected):
|
||||
if case let .duration(rhsIndex, rhsTheme, rhsMonths, rhsTitle, rhsSubtitle, rhsLabel, rhsBadge, rhsIsSelected) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsMonths == rhsMonths, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsLabel == rhsLabel, lhsBadge == rhsBadge, lhsIsSelected == rhsIsSelected {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
@ -292,9 +314,9 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
let arguments = arguments as! CreateGiveawayControllerArguments
|
||||
switch self {
|
||||
case let .header(_, title, text):
|
||||
return CreateGiveawayHeaderItem(theme: presentationData.theme, title: title, text: text, sectionId: self.section)
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(title + text), sectionId: self.section)
|
||||
case let .createGiveaway(_, title, subtitle, isSelected):
|
||||
return GiftModeItem(presentationData: presentationData, context: arguments.context, iconName: "Premium/Giveaway", title: title, subtitle: subtitle, label: nil, badge: nil, isSelected: isSelected, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: .blue, name: "Premium/Giveaway"), title: title, subtitle: subtitle, isSelected: isSelected, sectionId: self.section, action: {
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.mode = .giveaway
|
||||
@ -302,7 +324,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
}
|
||||
})
|
||||
case let .awardUsers(_, title, subtitle, isSelected):
|
||||
return GiftModeItem(presentationData: presentationData, context: arguments.context, iconName: "Media Editor/Privacy/SelectedUsers", title: title, subtitle: subtitle, subtitleActive: true, label: nil, badge: nil, isSelected: isSelected, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: .violet, name: "Media Editor/Privacy/SelectedUsers"), title: title, subtitle: subtitle, subtitleActive: true, isSelected: isSelected, sectionId: self.section, action: {
|
||||
var openSelection = false
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
@ -316,11 +338,26 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
arguments.openPeersSelection()
|
||||
}
|
||||
})
|
||||
case let .subscriptionsHeader(_, text):
|
||||
case let .prepaidHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .prepaid(_, title, subtitle, boosts, months):
|
||||
let _ = boosts
|
||||
let color: GiftOptionItem.Icon.Color
|
||||
switch months {
|
||||
case 3:
|
||||
color = .green
|
||||
case 6:
|
||||
color = .blue
|
||||
case 12:
|
||||
color = .red
|
||||
default:
|
||||
color = .blue
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(boosts), sectionId: self.section, action: nil)
|
||||
case let .subscriptionsHeader(_, text, additionalText):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, accessoryText: ItemListSectionHeaderAccessoryText(value: additionalText, color: .generic), sectionId: self.section)
|
||||
case let .subscriptions(_, value):
|
||||
let text = "\(value) Subscriptions / Boosts"
|
||||
return SubscriptionsCountItem(theme: presentationData.theme, strings: presentationData.strings, text: text, value: value, range: 1 ..< 11, sectionId: self.section, updated: { value in
|
||||
return SubscriptionsCountItem(theme: presentationData.theme, strings: presentationData.strings, value: value, sectionId: self.section, updated: { value in
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.subscriptions = value
|
||||
@ -332,11 +369,11 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case let .channelsHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .channel(_, _, peer, boosts):
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text("this channel will receive \(boosts) boosts", .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text("this channel will receive \($0) boosts", .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
|
||||
// arguments.openPeer(peer)
|
||||
}, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in })
|
||||
case let .channelAdd(theme, text):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .compactPeerList, color: .accent, editing: false, action: {
|
||||
arguments.openChannelsSelection()
|
||||
})
|
||||
case let .channelsInfo(_, text):
|
||||
@ -344,7 +381,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
case let .usersHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .usersAll(_, title, isSelected):
|
||||
return GiftModeItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: nil, label: nil, badge: nil, isSelected: isSelected, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: nil, isSelected: isSelected, sectionId: self.section, action: {
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.onlyNewEligible = false
|
||||
@ -352,7 +389,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
}
|
||||
})
|
||||
case let .usersNew(_, title, isSelected):
|
||||
return GiftModeItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: nil, label: nil, badge: nil, isSelected: isSelected, sectionId: self.section, action: {
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: nil, isSelected: isSelected, sectionId: self.section, action: {
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.onlyNewEligible = true
|
||||
@ -390,16 +427,18 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
|
||||
case let .durationHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .duration(_, _, productId, title, subtitle, label, badge, isSelected):
|
||||
return GiftModeItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: subtitle, label: label, badge: badge, isSelected: isSelected, sectionId: self.section, action: {
|
||||
case let .duration(_, _, months, title, subtitle, label, badge, isSelected):
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, title: title, subtitle: subtitle, subtitleFont: .small, label: .generic(label), badge: badge, isSelected: isSelected, sectionId: self.section, action: {
|
||||
arguments.updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.selectedProductId = productId
|
||||
updatedState.selectedMonths = months
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
case let .durationInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
|
||||
arguments.openPremiumIntro()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -425,43 +464,50 @@ private struct PremiumGiftProduct: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private func createGiveawayControllerEntries(state: CreateGiveawayControllerState, presentationData: PresentationData, peers: [EnginePeer.Id: EnginePeer], products: [PremiumGiftProduct]) -> [CreateGiveawayEntry] {
|
||||
private func createGiveawayControllerEntries(peerId: EnginePeer.Id, subject: CreateGiveawaySubject, state: CreateGiveawayControllerState, presentationData: PresentationData, peers: [EnginePeer.Id: EnginePeer], products: [PremiumGiftProduct], defaultPrice: (Int64, NSDecimalNumber)) -> [CreateGiveawayEntry] {
|
||||
var entries: [CreateGiveawayEntry] = []
|
||||
|
||||
entries.append(.header(presentationData.theme, "Boosts via Gifts", "Get more boosts for your channel by gifting\nPremium to your subscribers."))
|
||||
|
||||
entries.append(.createGiveaway(presentationData.theme, "Create Giveaway", "winners are chosen randomly", state.mode == .giveaway))
|
||||
|
||||
let recipientsText: String
|
||||
if !state.peers.isEmpty {
|
||||
var peerNamesArray: [String] = []
|
||||
let peersCount = state.peers.count
|
||||
for peerId in state.peers.prefix(2) {
|
||||
if let peer = peers[peerId] {
|
||||
peerNamesArray.append(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
|
||||
|
||||
switch subject {
|
||||
case .generic:
|
||||
entries.append(.createGiveaway(presentationData.theme, "Create Giveaway", "winners are chosen randomly", state.mode == .giveaway))
|
||||
|
||||
let recipientsText: String
|
||||
if !state.peers.isEmpty {
|
||||
var peerNamesArray: [String] = []
|
||||
let peersCount = state.peers.count
|
||||
for peerId in state.peers.prefix(2) {
|
||||
if let peer = peers[peerId] {
|
||||
peerNamesArray.append(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder))
|
||||
}
|
||||
}
|
||||
let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", "))
|
||||
if !peerNames.isEmpty {
|
||||
recipientsText = peerNames
|
||||
} else {
|
||||
recipientsText = presentationData.strings.PremiumGift_LabelRecipients(Int32(peersCount))
|
||||
}
|
||||
}
|
||||
let peerNames = String(peerNamesArray.map { $0 }.joined(separator: ", "))
|
||||
if !peerNames.isEmpty {
|
||||
recipientsText = peerNames
|
||||
} else {
|
||||
recipientsText = presentationData.strings.PremiumGift_LabelRecipients(Int32(peersCount))
|
||||
recipientsText = "select recipients"
|
||||
}
|
||||
} else {
|
||||
recipientsText = "select recipients"
|
||||
entries.append(.awardUsers(presentationData.theme, "Award Specific Users", recipientsText, state.mode == .gift))
|
||||
case let .prepaid(months, count):
|
||||
entries.append(.prepaidHeader(presentationData.theme, "PREPAID GIVEAWAY"))
|
||||
entries.append(.prepaid(presentationData.theme, "\(count) Telegram Premium", "\(months)-month subscriptions", count, months))
|
||||
}
|
||||
entries.append(.awardUsers(presentationData.theme, "Award Specific Users", recipientsText, state.mode == .gift))
|
||||
|
||||
if case .giveaway = state.mode {
|
||||
entries.append(.subscriptionsHeader(presentationData.theme, "QUANTITY OF PRIZES / BOOSTS".uppercased()))
|
||||
entries.append(.subscriptions(presentationData.theme, state.subscriptions))
|
||||
entries.append(.subscriptionsInfo(presentationData.theme, "Choose how many Premium subscriptions to give away and boosts to receive."))
|
||||
if case .generic = subject {
|
||||
entries.append(.subscriptionsHeader(presentationData.theme, "QUANTITY OF PRIZES".uppercased(), "\(state.subscriptions) BOOSTS"))
|
||||
entries.append(.subscriptions(presentationData.theme, state.subscriptions))
|
||||
entries.append(.subscriptionsInfo(presentationData.theme, "Choose how many Premium subscriptions to give away and boosts to receive."))
|
||||
}
|
||||
|
||||
entries.append(.channelsHeader(presentationData.theme, "CHANNELS INCLUDED IN THE GIVEAWAY".uppercased()))
|
||||
var index: Int32 = 0
|
||||
for peerId in state.channels {
|
||||
if let peer = peers[peerId] {
|
||||
entries.append(.channel(index, presentationData.theme, peer, state.subscriptions))
|
||||
let channels = [peerId] + state.channels
|
||||
for channelId in channels {
|
||||
if let channel = peers[channelId] {
|
||||
entries.append(.channel(index, presentationData.theme, channel, channel.id == peerId ? state.subscriptions : nil))
|
||||
}
|
||||
index += 1
|
||||
}
|
||||
@ -480,62 +526,54 @@ private func createGiveawayControllerEntries(state: CreateGiveawayControllerStat
|
||||
}
|
||||
entries.append(.timeInfo(presentationData.theme, "Choose when \(state.subscriptions) subscribers of your channel will be randomly selected to receive Telegram Premium."))
|
||||
}
|
||||
|
||||
entries.append(.durationHeader(presentationData.theme, "DURATION OF PREMIUM SUBSCRIPTIONS".uppercased()))
|
||||
|
||||
let recipientCount: Int
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
recipientCount = Int(state.subscriptions)
|
||||
case .gift:
|
||||
recipientCount = state.peers.count
|
||||
}
|
||||
|
||||
let shortestOptionPrice: (Int64, NSDecimalNumber)
|
||||
if let product = products.last {
|
||||
shortestOptionPrice = (Int64(Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(product.months)), product.storeProduct.priceValue.dividing(by: NSDecimalNumber(value: product.months)))
|
||||
} else {
|
||||
shortestOptionPrice = (1, NSDecimalNumber(decimal: 1))
|
||||
}
|
||||
|
||||
var i: Int32 = 0
|
||||
for product in products {
|
||||
let giftTitle: String
|
||||
if product.months == 12 {
|
||||
giftTitle = presentationData.strings.Premium_Gift_Years(1)
|
||||
} else {
|
||||
giftTitle = presentationData.strings.Premium_Gift_Months(product.months)
|
||||
if case .generic = subject {
|
||||
entries.append(.durationHeader(presentationData.theme, "DURATION OF PREMIUM SUBSCRIPTIONS".uppercased()))
|
||||
|
||||
let recipientCount: Int
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
recipientCount = Int(state.subscriptions)
|
||||
case .gift:
|
||||
recipientCount = state.peers.count
|
||||
}
|
||||
|
||||
let discountValue = Int((1.0 - Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(product.months) / Float(shortestOptionPrice.0)) * 100.0)
|
||||
let discount: String?
|
||||
if discountValue > 0 {
|
||||
discount = "-\(discountValue)%"
|
||||
} else {
|
||||
discount = nil
|
||||
var i: Int32 = 0
|
||||
var existingMonths = Set<Int32>()
|
||||
for product in products {
|
||||
if existingMonths.contains(product.months) {
|
||||
continue
|
||||
}
|
||||
existingMonths.insert(product.months)
|
||||
let giftTitle: String
|
||||
if product.months == 12 {
|
||||
giftTitle = presentationData.strings.Premium_Gift_Years(1)
|
||||
} else {
|
||||
giftTitle = presentationData.strings.Premium_Gift_Months(product.months)
|
||||
}
|
||||
|
||||
let discountValue = Int((1.0 - Float(product.storeProduct.priceCurrencyAndAmount.amount) / Float(product.months) / Float(defaultPrice.0)) * 100.0)
|
||||
let discount: String?
|
||||
if discountValue > 0 {
|
||||
discount = "-\(discountValue)%"
|
||||
} else {
|
||||
discount = nil
|
||||
}
|
||||
|
||||
let subtitle = "\(product.storeProduct.price) x \(recipientCount)"
|
||||
let label = product.storeProduct.multipliedPrice(count: recipientCount)
|
||||
|
||||
let selectedMonths = state.selectedMonths ?? 12
|
||||
let isSelected = product.months == selectedMonths
|
||||
|
||||
entries.append(.duration(i, presentationData.theme, product.months, giftTitle, subtitle, label, discount, isSelected))
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
let subtitle = "\(product.storeProduct.price) x \(recipientCount)"
|
||||
let label = product.storeProduct.multipliedPrice(count: recipientCount)
|
||||
|
||||
var isSelected = false
|
||||
if let selectedProductId = state.selectedProductId {
|
||||
isSelected = product.id == selectedProductId
|
||||
} else if i == 0 {
|
||||
isSelected = true
|
||||
}
|
||||
|
||||
entries.append(.duration(i, presentationData.theme, product.id, giftTitle, subtitle, label, discount, isSelected))
|
||||
|
||||
i += 1
|
||||
entries.append(.durationInfo(presentationData.theme, "You can review the list of features and terms of use for Telegram Premium [here]()."))
|
||||
}
|
||||
|
||||
// entries.append(.duration(0, presentationData.theme, "3 Months", "$13.99 x \(state.subscriptions)", "$41.99", nil, true))
|
||||
// entries.append(.duration(1, presentationData.theme, "6 Months", "$15.99 x \(state.subscriptions)", "$47.99", nil, false))
|
||||
// entries.append(.duration(2, presentationData.theme, "1 Year", "$29.99 x \(state.subscriptions)", "$89.99", nil, false))
|
||||
|
||||
entries.append(.durationInfo(presentationData.theme, "You can review the list of features and terms of use for Telegram Premium [here]()."))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -549,18 +587,30 @@ private struct CreateGiveawayControllerState: Equatable {
|
||||
var subscriptions: Int32
|
||||
var channels: [EnginePeer.Id]
|
||||
var peers: [EnginePeer.Id]
|
||||
var selectedProductId: String?
|
||||
var selectedMonths: Int32?
|
||||
var onlyNewEligible: Bool
|
||||
var time: Int32
|
||||
var pickingTimeLimit = false
|
||||
var updating = false
|
||||
}
|
||||
|
||||
public func createGiveawayController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, completion: (() -> Void)? = nil) -> ViewController {
|
||||
public enum CreateGiveawaySubject {
|
||||
case generic
|
||||
case prepaid(months: Int32, count: Int32)
|
||||
}
|
||||
|
||||
public func createGiveawayController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, peerId: EnginePeer.Id, subject: CreateGiveawaySubject, completion: (() -> Void)? = nil) -> ViewController {
|
||||
let actionsDisposable = DisposableSet()
|
||||
|
||||
let initialSubscriptions: Int32
|
||||
if case let .prepaid(_, count) = subject {
|
||||
initialSubscriptions = count
|
||||
} else {
|
||||
initialSubscriptions = 5
|
||||
}
|
||||
|
||||
let expiryTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + 86400 * 5
|
||||
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: 5, channels: [peerId], peers: [], onlyNewEligible: false, time: expiryTime)
|
||||
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, channels: [], peers: [], onlyNewEligible: false, time: expiryTime)
|
||||
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
@ -573,6 +623,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
var buyActionImpl: (() -> Void)?
|
||||
var openPeersSelectionImpl: (() -> Void)?
|
||||
var openChannelsSelectionImpl: (() -> Void)?
|
||||
var openPremiumIntroImpl: (() -> Void)?
|
||||
var presentControllerImpl: ((ViewController) -> Void)?
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
@ -586,11 +637,13 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
openPeersSelectionImpl?()
|
||||
}, openChannelsSelection: {
|
||||
openChannelsSelectionImpl?()
|
||||
}, openPremiumIntro: {
|
||||
openPremiumIntroImpl?()
|
||||
})
|
||||
|
||||
let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData
|
||||
|
||||
let products = combineLatest(
|
||||
let productsAndDefaultPrice: Signal<([PremiumGiftProduct], (Int64, NSDecimalNumber)), NoError> = combineLatest(
|
||||
.single([]) |> then(context.engine.payments.premiumGiftCodeOptions(peerId: peerId)),
|
||||
context.inAppPurchaseManager?.availableProducts ?? .single([])
|
||||
)
|
||||
@ -601,7 +654,13 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
gifts.append(PremiumGiftProduct(giftOption: option, storeProduct: product))
|
||||
}
|
||||
}
|
||||
return gifts
|
||||
let defaultPrice: (Int64, NSDecimalNumber)
|
||||
if let defaultProduct = products.first(where: { $0.id == "org.telegram.telegramPremium.monthly" }) {
|
||||
defaultPrice = (defaultProduct.priceCurrencyAndAmount.amount, defaultProduct.priceValue)
|
||||
} else {
|
||||
defaultPrice = (1, NSDecimalNumber(value: 1))
|
||||
}
|
||||
return (gifts, defaultPrice)
|
||||
}
|
||||
|
||||
let previousState = Atomic<CreateGiveawayControllerState?>(value: nil)
|
||||
@ -610,7 +669,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
statePromise.get()
|
||||
|> mapToSignal { state in
|
||||
return context.engine.data.get(EngineDataMap(
|
||||
Set(state.channels + state.peers).map {
|
||||
Set([peerId] + state.channels + state.peers).map {
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: $0)
|
||||
}
|
||||
))
|
||||
@ -618,24 +677,35 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
return (state, peers)
|
||||
}
|
||||
},
|
||||
products
|
||||
productsAndDefaultPrice
|
||||
)
|
||||
|> deliverOnMainQueue
|
||||
|> map { presentationData, stateAndPeersMap, products -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
|> map { presentationData, stateAndPeersMap, productsAndDefaultPrice -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var presentationData = presentationData
|
||||
|
||||
let (products, defaultPrice) = productsAndDefaultPrice
|
||||
|
||||
let updatedTheme = presentationData.theme.withModalBlocksBackground()
|
||||
presentationData = presentationData.withUpdated(theme: updatedTheme)
|
||||
|
||||
let (state, peersMap) = stateAndPeersMap
|
||||
|
||||
let footerItem = CreateGiveawayFooterItem(theme: presentationData.theme, title: state.mode == .gift ? "Gift Premium" : "Start Giveaway", action: {
|
||||
buyActionImpl?()
|
||||
})
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Common_Cancel), style: .regular, enabled: true, action: {
|
||||
let headerItem = CreateGiveawayHeaderItem(theme: presentationData.theme, title: "Boosts via Gifts", text: "Get more boosts for your channel by gifting\nPremium to your subscribers.", cancel: {
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let badgeCount: Int32
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
badgeCount = state.subscriptions
|
||||
case .gift:
|
||||
badgeCount = Int32(state.peers.count)
|
||||
}
|
||||
let footerItem = CreateGiveawayFooterItem(theme: presentationData.theme, title: state.mode == .gift ? "Gift Premium" : "Start Giveaway", badgeCount: badgeCount, isLoading: state.updating, action: {
|
||||
buyActionImpl?()
|
||||
})
|
||||
let leftNavigationButton = ItemListNavigationButton(content: .none, style: .regular, enabled: false, action: {})
|
||||
|
||||
let _ = productsValue.swap(products)
|
||||
|
||||
let previousState = previousState.swap(state)
|
||||
@ -652,7 +722,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(""), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGiveawayControllerEntries(state: state, presentationData: presentationData, peers: peers, products: products), style: .blocks, emptyStateItem: nil, footerItem: footerItem, crossfadeState: false, animateChanges: animateChanges)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: createGiveawayControllerEntries(peerId: peerId, subject: subject, state: state, presentationData: presentationData, peers: peers, products: products, defaultPrice: defaultPrice), style: .blocks, emptyStateItem: nil, headerItem: headerItem, footerItem: footerItem, crossfadeState: false, animateChanges: animateChanges)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
@ -678,17 +748,30 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
||||
buyActionImpl = {
|
||||
buyActionImpl = { [weak controller] in
|
||||
let state = stateValue.with { $0 }
|
||||
guard let products = productsValue.with({ $0 }) else {
|
||||
guard let products = productsValue.with({ $0 }), !products.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
let selectedProduct: PremiumGiftProduct
|
||||
if let selectedProductId = state.selectedProductId, let product = products.first(where: { $0.id == selectedProductId }) {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var selectedProduct: PremiumGiftProduct?
|
||||
let selectedMonths = state.selectedMonths ?? 12
|
||||
if let product = products.first(where: { $0.months == selectedMonths && $0.giftOption.users == state.subscriptions }) {
|
||||
selectedProduct = product
|
||||
} else {
|
||||
selectedProduct = products.first!
|
||||
}
|
||||
|
||||
guard let selectedProduct else {
|
||||
let alertController = textAlertController(context: context, title: "Reduce Quantity", text: "You can't acquire \(state.subscriptions) \(selectedMonths)-month subscriptions in the app. Do you want to reduce quantity to 25?", actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: "Reduce", action: {
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.subscriptions = 25
|
||||
return updatedState
|
||||
}
|
||||
})], parseMarkdown: true)
|
||||
presentControllerImpl?(alertController)
|
||||
return
|
||||
}
|
||||
|
||||
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
|
||||
@ -696,20 +779,64 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
let purpose: AppStoreTransactionPurpose
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
purpose = .giveaway(boostPeer: peerId, randomId: 1000, untilDate: state.time, currency: currency, amount: amount)
|
||||
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId}, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount)
|
||||
case .gift:
|
||||
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = true
|
||||
return updatedState
|
||||
}
|
||||
|
||||
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|
||||
|> deliverOnMainQueue).startStandalone(next: { available in
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
|
||||
if available, let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, purpose: purpose)
|
||||
|> deliverOnMainQueue).startStandalone(next: { status in
|
||||
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, quantity: selectedProduct.giftOption.storeQuantity, purpose: purpose)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
||||
if case .purchased = status {
|
||||
dismissImpl?()
|
||||
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
||||
var controllers = navigationController.viewControllers
|
||||
var count = 0
|
||||
for c in controllers.reversed() {
|
||||
if c is PeerInfoScreen {
|
||||
if case .giveaway = state.mode {
|
||||
count += 1
|
||||
}
|
||||
break
|
||||
} else {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
controllers.removeLast(count)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
switch state.mode {
|
||||
case .giveaway:
|
||||
title = "Giveaway Created"
|
||||
text = "Check your channel's [Statistics]() to see how this giveaway boosted your channel."
|
||||
case .gift:
|
||||
title = "Premium Subscriptions Gifted"
|
||||
text = "Check your channel's [Statistics]() to see how gifts boosted your channel."
|
||||
}
|
||||
|
||||
let tooltipController = UndoOverlayController(presentationData: presentationData, content: .premiumPaywall(title: title, text: text, customUndoText: nil, timeout: nil, linkAction: { [weak navigationController] _ in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.StatsDatacenterId(id: peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak navigationController] statsDatacenterId in
|
||||
guard let statsDatacenterId else {
|
||||
return
|
||||
}
|
||||
let statsController = context.sharedContext.makeChannelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, boosts: true, boostStatus: nil, statsDatacenterId: statsDatacenterId)
|
||||
navigationController?.pushViewController(statsController)
|
||||
})
|
||||
}), elevatedLayout: false, action: { _ in
|
||||
return true
|
||||
})
|
||||
(controllers.last as? ViewController)?.present(tooltipController, in: .current)
|
||||
}
|
||||
}
|
||||
}, error: { error in
|
||||
var errorText: String?
|
||||
@ -732,10 +859,22 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
presentControllerImpl?(alertController)
|
||||
}
|
||||
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = false
|
||||
return updatedState
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let alertController = textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Premium_Purchase_ErrorUnknown, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])
|
||||
presentControllerImpl?(alertController)
|
||||
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.updating = false
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -745,7 +884,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: context,
|
||||
subject: .members(peerId: peerId),
|
||||
subject: .members(peerId: peerId, searchQuery: nil),
|
||||
initialPeerIds: Set(state.peers)
|
||||
)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in
|
||||
@ -757,6 +896,9 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.peers = privacy.additionallyIncludePeers
|
||||
if updatedState.peers.isEmpty {
|
||||
updatedState.mode = .giveaway
|
||||
}
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
@ -766,17 +908,32 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
||||
}
|
||||
|
||||
openChannelsSelectionImpl = {
|
||||
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, requestPeerType: [ReplyMarkupButtonRequestPeerType.channel(ReplyMarkupButtonRequestPeerType.Channel(isCreator: false, hasUsername: nil, userAdminRights: TelegramChatAdminRights(rights: [.canChangeInfo]), botAdminRights: nil))]))
|
||||
controller.peerSelected = { [weak controller] peer, _ in
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
var channels = state.channels
|
||||
channels.append(peer.id)
|
||||
updatedState.channels = channels
|
||||
return updatedState
|
||||
}
|
||||
controller?.dismiss()
|
||||
}
|
||||
let state = stateValue.with { $0 }
|
||||
|
||||
let stateContext = ShareWithPeersScreen.StateContext(
|
||||
context: context,
|
||||
subject: .channels(exclude: Set([peerId])),
|
||||
initialPeerIds: Set(state.channels.filter { $0 != peerId })
|
||||
)
|
||||
let _ = (stateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).startStandalone(next: { _ in
|
||||
let controller = ShareWithPeersScreen(
|
||||
context: context,
|
||||
initialPrivacy: EngineStoryPrivacy(base: .nobody, additionallyIncludePeers: state.peers),
|
||||
stateContext: stateContext,
|
||||
completion: { _, privacy ,_, _, _, _ in
|
||||
updateState { state in
|
||||
var updatedState = state
|
||||
updatedState.channels = privacy.additionallyIncludePeers
|
||||
return updatedState
|
||||
}
|
||||
}
|
||||
)
|
||||
pushControllerImpl?(controller)
|
||||
})
|
||||
}
|
||||
|
||||
openPremiumIntroImpl = {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .settings, forceDark: false, dismissed: nil)
|
||||
pushControllerImpl?(controller)
|
||||
}
|
||||
|
||||
|
@ -2,25 +2,30 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import TelegramPresentationData
|
||||
import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import SolidRoundedButtonNode
|
||||
import ButtonComponent
|
||||
|
||||
final class CreateGiveawayFooterItem: ItemListControllerFooterItem {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let badgeCount: Int32
|
||||
let isLoading: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(theme: PresentationTheme, title: String, action: @escaping () -> Void) {
|
||||
init(theme: PresentationTheme, title: String, badgeCount: Int32, isLoading: Bool, action: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.badgeCount = badgeCount
|
||||
self.isLoading = isLoading
|
||||
self.action = action
|
||||
}
|
||||
|
||||
func isEqual(to: ItemListControllerFooterItem) -> Bool {
|
||||
if let item = to as? CreateGiveawayFooterItem {
|
||||
return self.theme === item.theme && self.title == item.title
|
||||
return self.theme === item.theme && self.title == item.title && self.badgeCount == item.badgeCount && self.isLoading == item.isLoading
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
@ -39,10 +44,11 @@ final class CreateGiveawayFooterItem: ItemListControllerFooterItem {
|
||||
final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let buttonNode: SolidRoundedButtonNode
|
||||
|
||||
private let button = ComponentView<Empty>()
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var currentIsLoading = false
|
||||
var item: CreateGiveawayFooterItem {
|
||||
didSet {
|
||||
self.updateItem()
|
||||
@ -58,26 +64,32 @@ final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
|
||||
self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.tabBar.backgroundColor)
|
||||
self.separatorNode = ASDisplayNode()
|
||||
|
||||
self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: .black, foregroundColor: .white), height: 50.0, cornerRadius: 11.0)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
|
||||
self.updateItem()
|
||||
}
|
||||
|
||||
|
||||
private func updateItem() {
|
||||
self.backgroundNode.updateColor(color: self.item.theme.rootController.tabBar.backgroundColor, transition: .immediate)
|
||||
self.separatorNode.backgroundColor = self.item.theme.rootController.tabBar.separatorColor
|
||||
self.buttonNode.updateTheme(SolidRoundedButtonTheme(backgroundColor: self.item.theme.list.itemCheckColors.fillColor, foregroundColor: self.item.theme.list.itemCheckColors.foregroundColor))
|
||||
self.buttonNode.title = self.item.title
|
||||
|
||||
self.buttonNode.pressed = { [weak self] in
|
||||
self?.item.action()
|
||||
}
|
||||
|
||||
// if self.item.isLoading != self.currentIsLoading {
|
||||
// self.currentIsLoading = self.item.isLoading
|
||||
//
|
||||
// if self.currentIsLoading {
|
||||
// self.buttonNode.transitionToProgress()
|
||||
// } else {
|
||||
// self.buttonNode.transitionFromProgress()
|
||||
// }
|
||||
// }
|
||||
|
||||
// self.buttonNode.pressed = { [weak self] in
|
||||
// self?.item.action()
|
||||
// }
|
||||
}
|
||||
|
||||
override func updateBackgroundAlpha(_ alpha: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
@ -86,16 +98,16 @@ final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
|
||||
}
|
||||
|
||||
override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let hadLayout = self.validLayout != nil
|
||||
self.validLayout = layout
|
||||
|
||||
let buttonInset: CGFloat = 16.0
|
||||
let buttonWidth = layout.size.width - layout.safeInsets.left - layout.safeInsets.right - buttonInset * 2.0
|
||||
let buttonHeight = self.buttonNode.updateLayout(width: buttonWidth, transition: transition)
|
||||
let inset: CGFloat = 9.0
|
||||
|
||||
let insets = layout.insets(options: [.input])
|
||||
|
||||
var panelHeight: CGFloat = buttonHeight + inset * 2.0
|
||||
var panelHeight: CGFloat = 50.0 + inset * 2.0
|
||||
let totalPanelHeight: CGFloat
|
||||
if let inputHeight = layout.inputHeight, inputHeight > 0.0 {
|
||||
totalPanelHeight = panelHeight + insets.bottom
|
||||
@ -105,7 +117,53 @@ final class CreateGiveawayFooterItemNode: ItemListControllerFooterItemNode {
|
||||
}
|
||||
|
||||
let panelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - totalPanelHeight), size: CGSize(width: layout.size.width, height: panelHeight))
|
||||
transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: panelFrame.minY + inset), size: CGSize(width: buttonWidth, height: buttonHeight)))
|
||||
|
||||
var buttonTransition: Transition = .easeInOut(duration: 0.2)
|
||||
if !hadLayout {
|
||||
buttonTransition = .immediate
|
||||
}
|
||||
let buttonSize = self.button.update(
|
||||
transition: buttonTransition,
|
||||
component: AnyComponent(
|
||||
ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: self.item.theme.list.itemCheckColors.fillColor,
|
||||
foreground: self.item.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: self.item.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: self.item.title,
|
||||
badge: Int(self.item.badgeCount),
|
||||
textColor: self.item.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: self.item.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: self.item.theme.list.itemCheckColors.fillColor,
|
||||
badgeStyle: .roundedRectangle,
|
||||
badgeIconName: "Premium/BoostButtonIcon",
|
||||
combinedAlignment: true
|
||||
))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: self.item.isLoading,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.item.action()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: buttonWidth, height: 50.0)
|
||||
)
|
||||
if let view = self.button.view {
|
||||
if view.superview == nil {
|
||||
self.view.addSubview(view)
|
||||
}
|
||||
transition.updateFrame(view: view, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + buttonInset, y: panelFrame.minY + inset), size: buttonSize))
|
||||
}
|
||||
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: panelFrame)
|
||||
self.backgroundNode.update(size: panelFrame.size, transition: transition)
|
||||
|
@ -9,52 +9,33 @@ import PresentationDataUtils
|
||||
import Markdown
|
||||
import ComponentFlow
|
||||
|
||||
final class CreateGiveawayHeaderItem: ListViewItem, ItemListItem {
|
||||
final class CreateGiveawayHeaderItem: ItemListControllerHeaderItem {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let text: String
|
||||
let sectionId: ItemListSectionId
|
||||
let cancel: () -> Void
|
||||
|
||||
init(theme: PresentationTheme, title: String, text: String, sectionId: ItemListSectionId) {
|
||||
init(theme: PresentationTheme, title: String, text: String, cancel: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.sectionId = sectionId
|
||||
self.cancel = cancel
|
||||
}
|
||||
|
||||
func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = CreateGiveawayHeaderItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
node.insets = layout.insets
|
||||
|
||||
Queue.mainQueue().async {
|
||||
completion(node, {
|
||||
return (nil, { _ in apply() })
|
||||
})
|
||||
}
|
||||
func isEqual(to: ItemListControllerHeaderItem) -> Bool {
|
||||
if let item = to as? CreateGiveawayHeaderItem {
|
||||
return self.theme === item.theme && self.title == item.title && self.text == item.text
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
guard let nodeValue = node() as? CreateGiveawayHeaderItemNode else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
async {
|
||||
let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
Queue.mainQueue().async {
|
||||
completion(layout, { _ in
|
||||
apply()
|
||||
})
|
||||
}
|
||||
}
|
||||
func node(current: ItemListControllerHeaderItemNode?) -> ItemListControllerHeaderItemNode {
|
||||
if let current = current as? CreateGiveawayHeaderItemNode {
|
||||
current.item = self
|
||||
return current
|
||||
} else {
|
||||
return CreateGiveawayHeaderItemNode(item: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -62,31 +43,63 @@ final class CreateGiveawayHeaderItem: ListViewItem, ItemListItem {
|
||||
private let titleFont = Font.semibold(20.0)
|
||||
private let textFont = Font.regular(15.0)
|
||||
|
||||
class CreateGiveawayHeaderItemNode: ListViewItemNode {
|
||||
private let titleNode: TextNode
|
||||
private let textNode: TextNode
|
||||
class CreateGiveawayHeaderItemNode: ItemListControllerHeaderItemNode {
|
||||
private let backgroundNode: NavigationBackgroundNode
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let titleNode: ImmediateTextNode
|
||||
private let textNode: ImmediateTextNode
|
||||
|
||||
private let cancelNode: HighlightableButtonNode
|
||||
|
||||
private var hostView: ComponentHostView<Empty>?
|
||||
|
||||
private var params: (AnyComponent<Empty>, CGSize, ListViewItemNodeLayout)?
|
||||
private var component: AnyComponent<Empty>?
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var item: CreateGiveawayHeaderItem?
|
||||
fileprivate var item: CreateGiveawayHeaderItem {
|
||||
didSet {
|
||||
self.updateItem()
|
||||
if let layout = self.validLayout {
|
||||
let _ = self.updateLayout(layout: layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
self.titleNode = TextNode()
|
||||
init(item: CreateGiveawayHeaderItem) {
|
||||
self.item = item
|
||||
|
||||
self.backgroundNode = NavigationBackgroundNode(color: item.theme.rootController.navigationBar.blurredBackgroundColor)
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
|
||||
self.titleNode = ImmediateTextNode()
|
||||
self.titleNode.isUserInteractionEnabled = false
|
||||
self.titleNode.contentMode = .left
|
||||
self.titleNode.contentsScale = UIScreen.main.scale
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode = ImmediateTextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.contentMode = .left
|
||||
self.textNode.contentsScale = UIScreen.main.scale
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
self.cancelNode = HighlightableButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.cancelNode)
|
||||
|
||||
self.cancelNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.updateItem()
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
self.item.cancel()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -94,76 +107,121 @@ class CreateGiveawayHeaderItemNode: ListViewItemNode {
|
||||
|
||||
let hostView = ComponentHostView<Empty>()
|
||||
self.hostView = hostView
|
||||
self.view.addSubview(hostView)
|
||||
self.view.insertSubview(hostView, at: 0)
|
||||
|
||||
if let (component, containerSize, layout) = self.params {
|
||||
if let layout = self.validLayout, let component = self.component {
|
||||
let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0)
|
||||
|
||||
let size = hostView.update(
|
||||
transition: .immediate,
|
||||
component: component,
|
||||
environment: {},
|
||||
containerSize: containerSize
|
||||
)
|
||||
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -121.0), size: size)
|
||||
hostView.bounds = CGRect(origin: .zero, size: size)
|
||||
}
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: CreateGiveawayHeaderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
func updateItem() {
|
||||
self.backgroundNode.updateColor(color: self.item.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.separatorNode.backgroundColor = self.item.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
return { item, params, neighbors in
|
||||
let topInset: CGFloat = 2.0
|
||||
let leftInset: CGFloat = 24.0 + params.leftInset
|
||||
let attributedTitle = NSAttributedString(string: self.item.title, font: titleFont, textColor: self.item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
|
||||
let attributedText = NSAttributedString(string: self.item.text, font: textFont, textColor: self.item.theme.list.freeTextColor, paragraphAlignment: .center)
|
||||
|
||||
let attributedTitle = NSAttributedString(string: item.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
|
||||
let attributedText = NSAttributedString(string: item.text, font: textFont, textColor: item.theme.list.freeTextColor, paragraphAlignment: .center)
|
||||
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: attributedTitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: topInset + titleLayout.size.height + textLayout.size.height + 80.0)
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
|
||||
return (layout, { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
let component = AnyComponent(PremiumStarComponent(isIntro: true, isVisible: true, hasIdleAnimations: true))
|
||||
let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0)
|
||||
|
||||
if let hostView = strongSelf.hostView {
|
||||
let size = hostView.update(
|
||||
transition: .immediate,
|
||||
component: component,
|
||||
environment: {},
|
||||
containerSize: containerSize
|
||||
)
|
||||
hostView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: -121.0), size: size)
|
||||
}
|
||||
|
||||
var origin: CGFloat = 78.0
|
||||
let _ = titleApply()
|
||||
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleLayout.size.width) / 2.0), y: origin), size: titleLayout.size)
|
||||
|
||||
origin += titleLayout.size.height + 10.0
|
||||
|
||||
let _ = textApply()
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - textLayout.size.width) / 2.0), y: origin), size: textLayout.size)
|
||||
|
||||
strongSelf.params = (component, containerSize, layout)
|
||||
}
|
||||
})
|
||||
self.titleNode.attributedText = attributedTitle
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.cancelNode.setAttributedTitle(NSAttributedString(string: "Cancel", font: Font.regular(17.0), textColor: self.item.theme.rootController.navigationBar.accentTextColor), for: .normal)
|
||||
}
|
||||
|
||||
override func updateContentOffset(_ contentOffset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
guard let layout = self.validLayout else {
|
||||
return
|
||||
}
|
||||
let navigationHeight: CGFloat = 56.0
|
||||
let statusBarHeight = layout.statusBarHeight ?? 0.0
|
||||
|
||||
let topInset : CGFloat = 0.0
|
||||
let titleOffsetDelta = (topInset + 160.0) - (statusBarHeight + (navigationHeight - statusBarHeight) / 2.0)
|
||||
let topContentOffset = contentOffset + max(0.0, min(1.0, contentOffset / titleOffsetDelta)) * 10.0
|
||||
|
||||
let titleOffset = topContentOffset
|
||||
let fraction = max(0.0, min(1.0, titleOffset / titleOffsetDelta))
|
||||
let titleScale = 1.0 - fraction * 0.18
|
||||
|
||||
let topPanelAlpha = min(20.0, max(0.0, contentOffset - 95.0)) / 20.0
|
||||
transition.updateAlpha(node: self.backgroundNode, alpha: topPanelAlpha)
|
||||
transition.updateAlpha(node: self.separatorNode, alpha: topPanelAlpha)
|
||||
|
||||
let starPosition = CGPoint(
|
||||
x: layout.size.width / 2.0,
|
||||
y: -contentOffset + 80.0
|
||||
)
|
||||
if let view = self.hostView {
|
||||
transition.updatePosition(layer: view.layer, position: starPosition)
|
||||
}
|
||||
|
||||
let titlePosition = CGPoint(
|
||||
x: layout.size.width / 2.0,
|
||||
y: max(topInset + 170.0 - titleOffset, statusBarHeight + (navigationHeight - statusBarHeight) / 2.0)
|
||||
)
|
||||
|
||||
transition.updatePosition(node: self.titleNode, position: titlePosition)
|
||||
transition.updateTransformScale(node: self.titleNode, scale: titleScale)
|
||||
|
||||
let textPosition = CGPoint(
|
||||
x: layout.size.width / 2.0,
|
||||
y: -contentOffset + 212.0
|
||||
)
|
||||
transition.updatePosition(node: self.textNode, position: textPosition)
|
||||
}
|
||||
|
||||
override func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) {
|
||||
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4)
|
||||
override func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let leftInset: CGFloat = 24.0
|
||||
|
||||
let navigationBarHeight: CGFloat = 56.0
|
||||
let constrainedSize = CGSize(width: layout.size.width - leftInset * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||
let titleSize = self.titleNode.updateLayout(constrainedSize)
|
||||
let textSize = self.textNode.updateLayout(constrainedSize)
|
||||
|
||||
let cancelSize = self.cancelNode.measure(constrainedSize)
|
||||
transition.updateFrame(node: self.cancelNode, frame: CGRect(origin: CGPoint(x: 16.0 + layout.safeInsets.left, y: floorToScreenPixels((navigationBarHeight - cancelSize.height) / 2.0)), size: cancelSize))
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: CGSize(width: layout.size.width, height: navigationBarHeight)))
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
|
||||
|
||||
self.backgroundNode.update(size: CGSize(width: layout.size.width, height: navigationBarHeight), transition: transition)
|
||||
|
||||
let component = AnyComponent(PremiumStarComponent(isIntro: true, isVisible: true, hasIdleAnimations: true))
|
||||
let containerSize = CGSize(width: min(414.0, layout.size.width), height: 220.0)
|
||||
|
||||
if let hostView = self.hostView {
|
||||
let size = hostView.update(
|
||||
transition: .immediate,
|
||||
component: component,
|
||||
environment: {},
|
||||
containerSize: containerSize
|
||||
)
|
||||
hostView.bounds = CGRect(origin: .zero, size: size)
|
||||
}
|
||||
|
||||
self.titleNode.bounds = CGRect(origin: .zero, size: titleSize)
|
||||
self.textNode.bounds = CGRect(origin: .zero, size: textSize)
|
||||
|
||||
let contentHeight = titleSize.height + textSize.height + 128.0
|
||||
|
||||
self.component = component
|
||||
self.validLayout = layout
|
||||
|
||||
return contentHeight
|
||||
}
|
||||
|
||||
override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if let hostView = self.hostView, hostView.frame.contains(point) {
|
||||
return true
|
||||
} else {
|
||||
return super.point(inside: point, with: event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
247
submodules/PremiumUI/Sources/GiftModeItem.swift → submodules/PremiumUI/Sources/GiftOptionItem.swift
247
submodules/PremiumUI/Sources/GiftModeItem.swift → submodules/PremiumUI/Sources/GiftOptionItem.swift
@ -11,25 +11,72 @@ import PresentationDataUtils
|
||||
import AccountContext
|
||||
import AvatarNode
|
||||
|
||||
public final class GiftModeItem: ListViewItem, ItemListItem {
|
||||
public final class GiftOptionItem: ListViewItem, ItemListItem {
|
||||
public struct Icon: Equatable {
|
||||
public enum Color {
|
||||
case blue
|
||||
case green
|
||||
case red
|
||||
case violet
|
||||
}
|
||||
public let color: Color
|
||||
public let name: String
|
||||
|
||||
public init(
|
||||
color: Color,
|
||||
name: String
|
||||
) {
|
||||
self.color = color
|
||||
self.name = name
|
||||
}
|
||||
}
|
||||
|
||||
public enum Font {
|
||||
case regular
|
||||
case bold
|
||||
}
|
||||
|
||||
public enum SubtitleFont {
|
||||
case regular
|
||||
case small
|
||||
}
|
||||
|
||||
public enum Label {
|
||||
case generic(String)
|
||||
case boosts(Int32)
|
||||
|
||||
var string: String {
|
||||
switch self {
|
||||
case let .generic(value):
|
||||
return value
|
||||
case let .boosts(value):
|
||||
return "\(value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData: ItemListPresentationData
|
||||
let context: AccountContext
|
||||
let iconName: String?
|
||||
let icon: Icon?
|
||||
let title: String
|
||||
let titleFont: Font
|
||||
let subtitle: String?
|
||||
let subtitleFont: SubtitleFont
|
||||
let subtitleActive: Bool
|
||||
let label: String?
|
||||
let label: Label?
|
||||
let badge: String?
|
||||
let isSelected: Bool
|
||||
let isSelected: Bool?
|
||||
public let sectionId: ItemListSectionId
|
||||
let action: (() -> Void)?
|
||||
|
||||
public init(presentationData: ItemListPresentationData, context: AccountContext, iconName: String? = nil, title: String, subtitle: String?, subtitleActive: Bool = false, label: String?, badge: String?, isSelected: Bool, sectionId: ItemListSectionId, action: (() -> Void)?) {
|
||||
public init(presentationData: ItemListPresentationData, context: AccountContext, icon: Icon? = nil, title: String, titleFont: Font = .regular, subtitle: String?, subtitleFont: SubtitleFont = .regular, subtitleActive: Bool = false, label: Label? = nil, badge: String? = nil, isSelected: Bool? = nil, sectionId: ItemListSectionId, action: (() -> Void)?) {
|
||||
self.presentationData = presentationData
|
||||
self.iconName = iconName
|
||||
self.icon = icon
|
||||
self.context = context
|
||||
self.title = title
|
||||
self.titleFont = titleFont
|
||||
self.subtitle = subtitle
|
||||
self.subtitleFont = subtitleFont
|
||||
self.subtitleActive = subtitleActive
|
||||
self.label = label
|
||||
self.badge = badge
|
||||
@ -40,7 +87,7 @@ public final class GiftModeItem: ListViewItem, ItemListItem {
|
||||
|
||||
public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal<Void, NoError>?, (ListViewItemApply) -> Void)) -> Void) {
|
||||
async {
|
||||
let node = GiftModeItemNode()
|
||||
let node = GiftOptionItemNode()
|
||||
let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
|
||||
|
||||
node.contentSize = layout.contentSize
|
||||
@ -56,7 +103,7 @@ public final class GiftModeItem: ListViewItem, ItemListItem {
|
||||
|
||||
public func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
if let nodeValue = node() as? GiftModeItemNode {
|
||||
if let nodeValue = node() as? GiftOptionItemNode {
|
||||
let makeLayout = nodeValue.asyncLayout()
|
||||
|
||||
var animated = true
|
||||
@ -76,7 +123,9 @@ public final class GiftModeItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
public var selectable: Bool = true
|
||||
public var selectable: Bool {
|
||||
return self.action != nil
|
||||
}
|
||||
|
||||
public func selected(listView: ListView){
|
||||
listView.clearHighlightAnimated(true)
|
||||
@ -84,7 +133,7 @@ public final class GiftModeItem: ListViewItem, ItemListItem {
|
||||
}
|
||||
}
|
||||
|
||||
class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
class GiftOptionItemNode: ItemListRevealOptionsItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
@ -96,14 +145,18 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
return self.containerNode
|
||||
}
|
||||
|
||||
fileprivate var avatarNode: ASImageNode?
|
||||
fileprivate var iconNode: ASImageNode?
|
||||
private let titleNode: TextNode
|
||||
private let statusNode: TextNode
|
||||
private var statusArrowNode: ASImageNode?
|
||||
|
||||
private var labelBackgroundNode: ASImageNode?
|
||||
private let labelNode: TextNode
|
||||
private var labelIconNode: ASImageNode?
|
||||
private let badgeTextNode: TextNode
|
||||
private var badgeBackgroundNode: ASImageNode?
|
||||
|
||||
private var layoutParams: (GiftModeItem, ListViewItemLayoutParams, ItemListNeighbors)?
|
||||
private var layoutParams: (GiftOptionItem, ListViewItemLayoutParams, ItemListNeighbors)?
|
||||
|
||||
private var selectableControlNode: ItemListSelectableControlNode?
|
||||
|
||||
@ -172,17 +225,31 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
item.action?()
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: GiftModeItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
func asyncLayout() -> (_ item: GiftOptionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
let makeStatusLayout = TextNode.asyncLayout(self.statusNode)
|
||||
let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
|
||||
let makeBadgeLayout = TextNode.asyncLayout(self.badgeTextNode)
|
||||
let selectableControlLayout = ItemListSelectableControlNode.asyncLayout(self.selectableControlNode)
|
||||
|
||||
let currentItem = self.layoutParams?.0
|
||||
|
||||
return { item, params, neighbors in
|
||||
let titleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0))
|
||||
let statusFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0))
|
||||
let titleFont: UIFont
|
||||
switch item.titleFont {
|
||||
case .regular:
|
||||
titleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0))
|
||||
case .bold:
|
||||
titleFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0))
|
||||
}
|
||||
|
||||
let statusFont: UIFont
|
||||
switch item.subtitleFont {
|
||||
case .regular:
|
||||
statusFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||
case .small:
|
||||
statusFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 13.0 / 17.0))
|
||||
}
|
||||
|
||||
var updatedTheme: PresentationTheme?
|
||||
if currentItem?.presentationData.theme !== item.presentationData.theme {
|
||||
@ -193,16 +260,28 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
|
||||
let titleAttributedString = NSAttributedString(string: item.title, font: titleFont, textColor: item.presentationData.theme.list.itemPrimaryTextColor)
|
||||
let statusAttributedString = NSAttributedString(string: item.subtitle ?? "", font: statusFont, textColor: item.subtitleActive ? item.presentationData.theme.list.itemAccentColor : item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
let labelAttributedString = NSAttributedString(string: item.label ?? "", font: titleFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor)
|
||||
let badgeAttributedString = NSAttributedString(string: item.badge ?? "", font: Font.with(size: 14.0, design: .round, weight: .semibold), textColor: item.presentationData.theme.list.itemCheckColors.foregroundColor)
|
||||
|
||||
let leftInset: CGFloat = 17.0 + params.leftInset
|
||||
|
||||
var avatarInset: CGFloat = 0.0
|
||||
if let _ = item.iconName {
|
||||
avatarInset += 40.0
|
||||
let labelColor: UIColor
|
||||
let labelFont: UIFont
|
||||
if let label = item.label, case .boosts = label {
|
||||
labelColor = item.presentationData.theme.list.itemAccentColor
|
||||
labelFont = Font.semibold(floor(item.presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
|
||||
} else {
|
||||
labelColor = item.presentationData.theme.list.itemSecondaryTextColor
|
||||
labelFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 17.0 / 17.0))
|
||||
}
|
||||
|
||||
let verticalInset: CGFloat = 11.0
|
||||
let labelAttributedString = NSAttributedString(string: item.label?.string ?? "", font: labelFont, textColor: labelColor)
|
||||
|
||||
let leftInset: CGFloat = 14.0 + params.leftInset
|
||||
|
||||
var avatarInset: CGFloat = 0.0
|
||||
if let _ = item.icon {
|
||||
avatarInset += 48.0
|
||||
}
|
||||
|
||||
let verticalInset: CGFloat = 10.0
|
||||
let titleSpacing: CGFloat = 2.0
|
||||
|
||||
let insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
@ -211,9 +290,11 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
var selectableControlSizeAndApply: (CGFloat, (CGSize, Bool) -> ItemListSelectableControlNode)?
|
||||
var editingOffset: CGFloat = 0.0
|
||||
|
||||
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, item.isSelected, false)
|
||||
selectableControlSizeAndApply = sizeAndApply
|
||||
editingOffset = sizeAndApply.0
|
||||
if let isSelected = item.isSelected {
|
||||
let sizeAndApply = selectableControlLayout(item.presentationData.theme.list.itemCheckColors.strokeColor, item.presentationData.theme.list.itemCheckColors.fillColor, item.presentationData.theme.list.itemCheckColors.foregroundColor, isSelected, false)
|
||||
selectableControlSizeAndApply = sizeAndApply
|
||||
editingOffset = sizeAndApply.0
|
||||
}
|
||||
|
||||
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: .greatestFiniteMagnitude)))
|
||||
|
||||
@ -222,6 +303,8 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrainedWidth, height: .greatestFiniteMagnitude)))
|
||||
let (statusLayout, statusApply) = makeStatusLayout(TextNodeLayoutArguments(attributedString: statusAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (badgeLayout, badgeApply) = makeBadgeLayout(TextNodeLayoutArguments(attributedString: badgeAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: textConstrainedWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let contentSize = CGSize(width: params.width, height: verticalInset * 2.0 + titleLayout.size.height + titleSpacing + statusLayout.size.height)
|
||||
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
|
||||
let layoutSize = layout.size
|
||||
@ -249,30 +332,35 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
}
|
||||
|
||||
let iconSize = CGSize(width: 40.0, height: 40.0)
|
||||
if let iconName = item.iconName {
|
||||
if let icon = item.icon {
|
||||
let iconNode: ASImageNode
|
||||
|
||||
if let current = strongSelf.avatarNode {
|
||||
if let current = strongSelf.iconNode {
|
||||
iconNode = current
|
||||
} else {
|
||||
iconNode = ASImageNode()
|
||||
iconNode.displaysAsynchronously = false
|
||||
strongSelf.addSubnode(iconNode)
|
||||
|
||||
strongSelf.avatarNode = iconNode
|
||||
strongSelf.iconNode = iconNode
|
||||
}
|
||||
|
||||
let colors: [UIColor]
|
||||
if iconName.contains("away") {
|
||||
colors = [UIColor(rgb: 0x4faaff), UIColor(rgb: 0x017aff)]
|
||||
} else {
|
||||
colors = [UIColor(rgb: 0xc36eff), UIColor(rgb: 0x8c61fa)]
|
||||
switch icon.color {
|
||||
case .blue:
|
||||
colors = [UIColor(rgb: 0x2a9ef1), UIColor(rgb: 0x71d4fc)]
|
||||
case .green:
|
||||
colors = [UIColor(rgb: 0x54cb68), UIColor(rgb: 0xa0de7e)]
|
||||
case .red:
|
||||
colors = [UIColor(rgb: 0xff516a), UIColor(rgb: 0xff885e)]
|
||||
case .violet:
|
||||
colors = [UIColor(rgb: 0xd569ec), UIColor(rgb: 0xe0a2f3)]
|
||||
}
|
||||
if iconNode.image == nil {
|
||||
iconNode.image = generateAvatarImage(size: iconSize, icon: generateTintedImage(image: UIImage(bundleImageName: iconName), color: .white), iconScale: 1.0, cornerRadius: 20.0, color: .blue, customColors: colors.reversed())
|
||||
iconNode.image = generateAvatarImage(size: iconSize, icon: generateTintedImage(image: UIImage(bundleImageName: icon.name), color: .white), iconScale: 1.0, cornerRadius: 20.0, color: .blue, customColors: colors)
|
||||
}
|
||||
|
||||
let iconFrame = CGRect(origin: CGPoint(x: leftInset + 38.0, y: floorToScreenPixels((layout.contentSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: leftInset - 3.0 + editingOffset, y: floorToScreenPixels((layout.contentSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
iconNode.frame = iconFrame
|
||||
}
|
||||
|
||||
@ -304,6 +392,7 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
let _ = titleApply()
|
||||
let _ = statusApply()
|
||||
let _ = labelApply()
|
||||
let _ = badgeApply()
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
@ -332,7 +421,7 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
let bottomStripeOffset: CGFloat
|
||||
switch neighbors.bottom {
|
||||
case .sameSection(false):
|
||||
bottomStripeInset = leftInset + editingOffset
|
||||
bottomStripeInset = leftInset + editingOffset + avatarInset
|
||||
bottomStripeOffset = -separatorHeight
|
||||
strongSelf.bottomStripeNode.isHidden = false
|
||||
default:
|
||||
@ -350,9 +439,91 @@ class GiftModeItemNode: ItemListRevealOptionsItemNode {
|
||||
transition.updateFrame(node: strongSelf.topStripeNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight)))
|
||||
transition.updateFrame(node: strongSelf.bottomStripeNode, frame: CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight)))
|
||||
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: verticalInset), size: titleLayout.size))
|
||||
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: layoutSize.width - rightInset - labelLayout.size.width - 18.0, y: floorToScreenPixels((layout.contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size))
|
||||
let titleVerticalOriginY: CGFloat
|
||||
if statusLayout.size.height > 0.0 {
|
||||
titleVerticalOriginY = verticalInset
|
||||
} else {
|
||||
titleVerticalOriginY = floorToScreenPixels((contentSize.height - titleLayout.size.height) / 2.0)
|
||||
}
|
||||
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset, y: titleVerticalOriginY), size: titleLayout.size))
|
||||
|
||||
var badgeOffset: CGFloat = 0.0
|
||||
if badgeLayout.size.width > 0.0 {
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset + 2.0, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: badgeLayout.size)
|
||||
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -2.0, dy: -2.0)
|
||||
|
||||
let badgeBackgroundNode: ASImageNode
|
||||
if let current = strongSelf.badgeBackgroundNode {
|
||||
badgeBackgroundNode = current
|
||||
} else {
|
||||
badgeBackgroundNode = ASImageNode()
|
||||
badgeBackgroundNode.displaysAsynchronously = false
|
||||
badgeBackgroundNode.image = generateStretchableFilledCircleImage(radius: 5.0, color: item.presentationData.theme.list.itemCheckColors.fillColor)
|
||||
strongSelf.badgeBackgroundNode = badgeBackgroundNode
|
||||
|
||||
strongSelf.containerNode.addSubnode(badgeBackgroundNode)
|
||||
strongSelf.containerNode.addSubnode(strongSelf.badgeTextNode)
|
||||
}
|
||||
|
||||
transition.updateFrame(node: badgeBackgroundNode, frame: badgeBackgroundFrame)
|
||||
transition.updateFrame(node: strongSelf.badgeTextNode, frame: badgeFrame)
|
||||
|
||||
badgeOffset = badgeLayout.size.width + 10.0
|
||||
}
|
||||
|
||||
transition.updateFrame(node: strongSelf.statusNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset + badgeOffset, y: strongSelf.titleNode.frame.maxY + titleSpacing), size: statusLayout.size))
|
||||
|
||||
if let label = item.label, case .boosts = label {
|
||||
let backgroundNode: ASImageNode
|
||||
let iconNode: ASImageNode
|
||||
if let currentBackground = strongSelf.labelBackgroundNode, let currentIcon = strongSelf.labelIconNode {
|
||||
backgroundNode = currentBackground
|
||||
iconNode = currentIcon
|
||||
} else {
|
||||
backgroundNode = ASImageNode()
|
||||
backgroundNode.displaysAsynchronously = false
|
||||
backgroundNode.image = generateStretchableFilledCircleImage(radius: 13.0, color: item.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.1))
|
||||
strongSelf.containerNode.insertSubnode(backgroundNode, at: 1)
|
||||
|
||||
iconNode = ASImageNode()
|
||||
iconNode.displaysAsynchronously = false
|
||||
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Premium/BoostChannel"), color: item.presentationData.theme.list.itemAccentColor)
|
||||
strongSelf.containerNode.addSubnode(iconNode)
|
||||
|
||||
strongSelf.labelBackgroundNode = backgroundNode
|
||||
strongSelf.labelIconNode = backgroundNode
|
||||
}
|
||||
|
||||
if let icon = iconNode.image {
|
||||
let labelFrame = CGRect(origin: CGPoint(x: layoutSize.width - rightInset - labelLayout.size.width - 21.0, y: floorToScreenPixels((layout.contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: labelFrame.minX - icon.size.width - 2.0, y: labelFrame.minY - 1.0), size: icon.size)
|
||||
let totalFrame = CGRect(x: iconFrame.minX - 7.0, y: labelFrame.minY - 4.0, width: iconFrame.width + labelFrame.width + 18.0, height: 26.0)
|
||||
transition.updateFrame(node: backgroundNode, frame: totalFrame)
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: labelFrame)
|
||||
transition.updateFrame(node: iconNode, frame: iconFrame)
|
||||
}
|
||||
} else {
|
||||
transition.updateFrame(node: strongSelf.labelNode, frame: CGRect(origin: CGPoint(x: layoutSize.width - rightInset - labelLayout.size.width - 18.0, y: floorToScreenPixels((layout.contentSize.height - labelLayout.size.height) / 2.0)), size: labelLayout.size))
|
||||
}
|
||||
|
||||
if item.subtitleActive {
|
||||
let statusArrowNode: ASImageNode
|
||||
if let current = strongSelf.statusArrowNode {
|
||||
statusArrowNode = current
|
||||
} else {
|
||||
statusArrowNode = ASImageNode()
|
||||
statusArrowNode.displaysAsynchronously = false
|
||||
statusArrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Contact List/SubtitleArrow"), color: item.presentationData.theme.list.itemAccentColor)
|
||||
strongSelf.statusArrowNode = statusArrowNode
|
||||
strongSelf.containerNode.addSubnode(statusArrowNode)
|
||||
}
|
||||
if let arrowSize = statusArrowNode.image?.size {
|
||||
transition.updateFrame(node: statusArrowNode, frame: CGRect(origin: CGPoint(x: leftInset + editingOffset + avatarInset + statusLayout.size.width + 4.0, y: strongSelf.titleNode.frame.maxY + titleSpacing + 4.0), size: arrowSize))
|
||||
}
|
||||
} else if let statusArrowNode = strongSelf.statusArrowNode {
|
||||
strongSelf.statusArrowNode = nil
|
||||
statusArrowNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: params.width, height: strongSelf.backgroundNode.frame.height + UIScreenPixel + UIScreenPixel))
|
||||
|
@ -29,19 +29,28 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
let action: () -> Void
|
||||
let cancel: () -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let openMessage: (EngineMessage.Id) -> Void
|
||||
let copyLink: (String) -> Void
|
||||
let shareLink: (String) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
giftCode: PremiumGiftCodeInfo,
|
||||
action: @escaping () -> Void,
|
||||
cancel: @escaping () -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
copyLink: @escaping (String) -> Void,
|
||||
shareLink: @escaping (String) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.giftCode = giftCode
|
||||
self.action = action
|
||||
self.cancel = cancel
|
||||
self.openPeer = openPeer
|
||||
self.openMessage = openMessage
|
||||
self.copyLink = copyLink
|
||||
self.shareLink = shareLink
|
||||
}
|
||||
|
||||
static func ==(lhs: PremiumGiftCodeSheetContent, rhs: PremiumGiftCodeSheetContent) -> Bool {
|
||||
@ -210,7 +219,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
LinkButtonContentComponent(theme: environment.theme, text: link)
|
||||
),
|
||||
action: {
|
||||
UIPasteboard.general.string = link
|
||||
component.copyLink(link)
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 50.0),
|
||||
@ -231,6 +240,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
|
||||
action: {
|
||||
if let peer = fromPeer {
|
||||
component.cancel()
|
||||
component.openPeer(peer)
|
||||
}
|
||||
}
|
||||
@ -247,6 +257,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
|
||||
action: {
|
||||
if let peer = toPeer {
|
||||
component.cancel()
|
||||
component.openPeer(peer)
|
||||
}
|
||||
}
|
||||
@ -268,12 +279,21 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
)
|
||||
))
|
||||
|
||||
let giftReason = giftCode.isGiveaway ? "Giveaway" : "Gift"
|
||||
let giftReason = giftCode.isGiveaway ? "Giveaway" : "You were selected by the channel"
|
||||
tableItems.append(.init(
|
||||
id: "reason",
|
||||
title: "Reason",
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(text: .plain(NSAttributedString(string: giftReason, font: tableFont, textColor: tableLinkColor)))
|
||||
Button(
|
||||
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: giftReason, font: tableFont, textColor: giftCode.messageId != nil ? tableLinkColor : tableTextColor)))),
|
||||
isEnabled: true,
|
||||
action: {
|
||||
component.cancel()
|
||||
if let messageId = giftCode.messageId {
|
||||
component.openMessage(messageId)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
))
|
||||
tableItems.append(.init(
|
||||
@ -298,7 +318,18 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
|
||||
text: .markdown(text: additionalText, attributes: markdownAttributes),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.1
|
||||
lineSpacing: 0.1,
|
||||
highlightColor: linkColor.withAlphaComponent(0.2),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { attributes, _ in
|
||||
component.shareLink("https://t.me/giftcode/\(giftCode.slug)")
|
||||
}
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - textSideInset * 2.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
@ -383,19 +414,28 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
let action: () -> Void
|
||||
let cancel: () -> Void
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
|
||||
let openMessage: (EngineMessage.Id) -> Void
|
||||
let copyLink: (String) -> Void
|
||||
let shareLink: (String) -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
giftCode: PremiumGiftCodeInfo,
|
||||
action: @escaping () -> Void,
|
||||
cancel: @escaping () -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void
|
||||
openPeer: @escaping (EnginePeer) -> Void,
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void,
|
||||
copyLink: @escaping (String) -> Void,
|
||||
shareLink: @escaping (String) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.giftCode = giftCode
|
||||
self.action = action
|
||||
self.cancel = cancel
|
||||
self.openPeer = openPeer
|
||||
self.openMessage = openMessage
|
||||
self.copyLink = copyLink
|
||||
self.shareLink = shareLink
|
||||
}
|
||||
|
||||
static func ==(lhs: PremiumGiftCodeSheetComponent, rhs: PremiumGiftCodeSheetComponent) -> Bool {
|
||||
@ -429,7 +469,10 @@ private final class PremiumGiftCodeSheetComponent: CombinedComponent {
|
||||
}
|
||||
})
|
||||
},
|
||||
openPeer: context.component.openPeer
|
||||
openPeer: context.component.openPeer,
|
||||
openMessage: context.component.openMessage,
|
||||
copyLink: context.component.copyLink,
|
||||
shareLink: context.component.shareLink
|
||||
)),
|
||||
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
|
||||
animateOut: animateOut
|
||||
@ -481,13 +524,28 @@ public class PremiumGiftCodeScreen: ViewControllerComponentContainer {
|
||||
forceDark: Bool = false,
|
||||
cancel: @escaping () -> Void = {},
|
||||
action: @escaping () -> Void,
|
||||
openPeer: @escaping (EnginePeer) -> Void = { _ in }
|
||||
openPeer: @escaping (EnginePeer) -> Void = { _ in },
|
||||
openMessage: @escaping (EngineMessage.Id) -> Void = { _ in },
|
||||
shareLink: @escaping (String) -> Void = { _ in }
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: PremiumGiftCodeSheetComponent(context: context, giftCode: giftCode, action: action, cancel: cancel, openPeer: openPeer), navigationBarAppearance: .none, statusBarStyle: .ignore, theme: forceDark ? .dark : .default)
|
||||
var copyLinkImpl: ((String) -> Void)?
|
||||
super.init(context: context, component: PremiumGiftCodeSheetComponent(context: context, giftCode: giftCode, action: action, cancel: cancel, openPeer: openPeer, openMessage: openMessage, copyLink: { link in
|
||||
copyLinkImpl?(link)
|
||||
}, shareLink: shareLink), navigationBarAppearance: .none, statusBarStyle: .ignore, theme: forceDark ? .dark : .default)
|
||||
|
||||
self.navigationPresentation = .flatModal
|
||||
|
||||
copyLinkImpl = { [weak self] link in
|
||||
UIPasteboard.general.string = link
|
||||
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.Conversation_LinkCopied), elevatedLayout: false, position: .top, action: { _ in return true }), in: .window(.root))
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
|
@ -19,7 +19,6 @@ import InAppPurchaseManager
|
||||
import ConfettiEffect
|
||||
import TextFormat
|
||||
import UniversalMediaPlayer
|
||||
import AttachmentUI
|
||||
|
||||
public enum PremiumGiftSource: Equatable {
|
||||
case profile
|
||||
@ -674,6 +673,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
||||
let _ = (self.context.engine.payments.canPurchasePremium(purpose: purpose)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] available in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
if available {
|
||||
strongSelf.paymentDisposable.set((inAppPurchaseManager.buyProduct(product.storeProduct, purpose: purpose)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
@ -693,7 +693,6 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
||||
strongSelf.updateInProgress(false)
|
||||
strongSelf.updated(transition: .immediate)
|
||||
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
var errorText: String?
|
||||
switch error {
|
||||
case .generic:
|
||||
@ -992,7 +991,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public final class PremiumGiftScreen: ViewControllerComponentContainer, AttachmentContainable {
|
||||
open class PremiumGiftScreen: ViewControllerComponentContainer {
|
||||
fileprivate let context: AccountContext
|
||||
|
||||
private var didSetReady = false
|
||||
@ -1005,7 +1004,9 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer, Attachme
|
||||
public weak var containerView: UIView?
|
||||
public var animationColor: UIColor?
|
||||
|
||||
fileprivate let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
||||
|
||||
public let mainButtonStatePromise = Promise<AttachmentMainButtonState?>(nil)
|
||||
private let mainButtonActionSlot = ActionSlot<Void>()
|
||||
|
||||
public init(context: AccountContext, peerId: PeerId, options: [CachedPremiumGiftOption], source: PremiumGiftSource, pushController: @escaping (ViewController) -> Void, completion: @escaping () -> Void) {
|
||||
@ -1092,55 +1093,7 @@ public final class PremiumGiftScreen: ViewControllerComponentContainer, Attachme
|
||||
}
|
||||
}
|
||||
|
||||
@objc fileprivate func mainButtonPressed() {
|
||||
@objc public func mainButtonPressed() {
|
||||
self.mainButtonActionSlot.invoke(Void())
|
||||
}
|
||||
|
||||
public var requestAttachmentMenuExpansion: () -> Void = {}
|
||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||
public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
|
||||
public var cancelPanGesture: () -> Void = { }
|
||||
public var isContainerPanning: () -> Bool = { return false }
|
||||
public var isContainerExpanded: () -> Bool = { return false }
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
return PremiumGiftContext(controller: self)
|
||||
}
|
||||
}
|
||||
|
||||
private final class PremiumGiftContext: AttachmentMediaPickerContext {
|
||||
private weak var controller: PremiumGiftScreen?
|
||||
|
||||
var selectionCount: Signal<Int, NoError> {
|
||||
return .single(0)
|
||||
}
|
||||
|
||||
var caption: Signal<NSAttributedString?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
public var loadingProgress: Signal<CGFloat?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
public var mainButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
||||
return self.controller?.mainButtonStatePromise.get() ?? .single(nil)
|
||||
}
|
||||
|
||||
init(controller: PremiumGiftScreen) {
|
||||
self.controller = controller
|
||||
}
|
||||
|
||||
func setCaption(_ caption: NSAttributedString) {
|
||||
}
|
||||
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
}
|
||||
|
||||
func mainButtonAction() {
|
||||
self.controller?.mainButtonPressed()
|
||||
}
|
||||
}
|
||||
|
@ -12,18 +12,14 @@ import PresentationDataUtils
|
||||
final class SubscriptionsCountItem: ListViewItem, ItemListItem {
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let text: String
|
||||
let value: Int32
|
||||
let range: Range<Int32>?
|
||||
let sectionId: ItemListSectionId
|
||||
let updated: (Int32) -> Void
|
||||
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, text: String, value: Int32, range: Range<Int32>?, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
|
||||
init(theme: PresentationTheme, strings: PresentationStrings, value: Int32, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.text = text
|
||||
self.value = value
|
||||
self.range = range
|
||||
self.sectionId = sectionId
|
||||
self.updated = updated
|
||||
}
|
||||
@ -68,9 +64,13 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
||||
private let minTextNode: TextNode
|
||||
private let maxTextNode: TextNode
|
||||
private let textNode: TextNode
|
||||
private let label1TextNode: TextNode
|
||||
private let label3TextNode: TextNode
|
||||
private let label5TextNode: TextNode
|
||||
private let label7TextNode: TextNode
|
||||
private let label10TextNode: TextNode
|
||||
private let label25TextNode: TextNode
|
||||
private let label50TextNode: TextNode
|
||||
private var sliderView: TGPhotoEditorSliderView?
|
||||
|
||||
private var item: SubscriptionsCountItem?
|
||||
@ -88,23 +88,43 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.label1TextNode = TextNode()
|
||||
self.label1TextNode.isUserInteractionEnabled = false
|
||||
self.label1TextNode.displaysAsynchronously = false
|
||||
|
||||
self.minTextNode = TextNode()
|
||||
self.minTextNode.isUserInteractionEnabled = false
|
||||
self.minTextNode.displaysAsynchronously = false
|
||||
self.label3TextNode = TextNode()
|
||||
self.label3TextNode.isUserInteractionEnabled = false
|
||||
self.label3TextNode.displaysAsynchronously = false
|
||||
|
||||
self.maxTextNode = TextNode()
|
||||
self.maxTextNode.isUserInteractionEnabled = false
|
||||
self.maxTextNode.displaysAsynchronously = false
|
||||
self.label5TextNode = TextNode()
|
||||
self.label5TextNode.isUserInteractionEnabled = false
|
||||
self.label5TextNode.displaysAsynchronously = false
|
||||
|
||||
self.label7TextNode = TextNode()
|
||||
self.label7TextNode.isUserInteractionEnabled = false
|
||||
self.label7TextNode.displaysAsynchronously = false
|
||||
|
||||
self.label10TextNode = TextNode()
|
||||
self.label10TextNode.isUserInteractionEnabled = false
|
||||
self.label10TextNode.displaysAsynchronously = false
|
||||
|
||||
self.label25TextNode = TextNode()
|
||||
self.label25TextNode.isUserInteractionEnabled = false
|
||||
self.label25TextNode.displaysAsynchronously = false
|
||||
|
||||
self.label50TextNode = TextNode()
|
||||
self.label50TextNode.isUserInteractionEnabled = false
|
||||
self.label50TextNode.displaysAsynchronously = false
|
||||
|
||||
super.init(layerBacked: false, dynamicBounce: false)
|
||||
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.minTextNode)
|
||||
self.addSubnode(self.maxTextNode)
|
||||
self.addSubnode(self.label1TextNode)
|
||||
self.addSubnode(self.label3TextNode)
|
||||
self.addSubnode(self.label5TextNode)
|
||||
self.addSubnode(self.label7TextNode)
|
||||
self.addSubnode(self.label10TextNode)
|
||||
self.addSubnode(self.label25TextNode)
|
||||
self.addSubnode(self.label50TextNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
@ -114,13 +134,34 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
sliderView.enablePanHandling = true
|
||||
sliderView.trackCornerRadius = 2.0
|
||||
sliderView.lineSize = 4.0
|
||||
sliderView.dotSize = 5.0
|
||||
sliderView.dotSize = 8.0
|
||||
sliderView.minimumValue = 0.0
|
||||
sliderView.maximumValue = 10.0
|
||||
sliderView.maximumValue = 6.0
|
||||
sliderView.startValue = 0.0
|
||||
sliderView.positionsCount = 7
|
||||
sliderView.useLinesForPositions = true
|
||||
sliderView.disablesInteractiveTransitionGestureRecognizer = true
|
||||
if let item = self.item, let params = self.layoutParams {
|
||||
sliderView.value = CGFloat(item.value)
|
||||
var mappedValue: Int32 = 0
|
||||
switch Int(item.value) {
|
||||
case 1:
|
||||
mappedValue = 0
|
||||
case 3:
|
||||
mappedValue = 1
|
||||
case 5:
|
||||
mappedValue = 2
|
||||
case 7:
|
||||
mappedValue = 3
|
||||
case 10:
|
||||
mappedValue = 4
|
||||
case 25:
|
||||
mappedValue = 5
|
||||
case 50:
|
||||
mappedValue = 6
|
||||
default:
|
||||
mappedValue = 0
|
||||
}
|
||||
sliderView.value = CGFloat(mappedValue)
|
||||
sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
sliderView.backColor = item.theme.list.itemSwitchColors.frameColor
|
||||
sliderView.startColor = item.theme.list.itemSwitchColors.frameColor
|
||||
@ -137,9 +178,13 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
|
||||
func asyncLayout() -> (_ item: SubscriptionsCountItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
let makeTextLayout = TextNode.asyncLayout(self.textNode)
|
||||
let makeMinTextLayout = TextNode.asyncLayout(self.minTextNode)
|
||||
let makeMaxTextLayout = TextNode.asyncLayout(self.maxTextNode)
|
||||
let makeLabel1TextLayout = TextNode.asyncLayout(self.label1TextNode)
|
||||
let makeLabel3TextLayout = TextNode.asyncLayout(self.label3TextNode)
|
||||
let makeLabel5TextLayout = TextNode.asyncLayout(self.label5TextNode)
|
||||
let makeLabel7TextLayout = TextNode.asyncLayout(self.label7TextNode)
|
||||
let makeLabel10TextLayout = TextNode.asyncLayout(self.label10TextNode)
|
||||
let makeLabel25TextLayout = TextNode.asyncLayout(self.label25TextNode)
|
||||
let makeLabel50TextLayout = TextNode.asyncLayout(self.label50TextNode)
|
||||
|
||||
return { item, params, neighbors in
|
||||
var themeUpdated = false
|
||||
@ -151,13 +196,19 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
let insets: UIEdgeInsets
|
||||
let separatorHeight = UIScreenPixel
|
||||
|
||||
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.text, font: Font.regular(17.0), textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (label1TextLayout, label1TextApply) = makeLabel1TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "1", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let range = item.range ?? (1 ..< 11)
|
||||
let (label3TextLayout, label3TextApply) = makeLabel3TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "3", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (minTextLayout, minTextApply) = makeMinTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(range.lowerBound)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (label5TextLayout, label5TextApply) = makeLabel5TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "5", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (maxTextLayout, maxTextApply) = makeMaxTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "\(range.upperBound - 1)", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
let (label7TextLayout, label7TextApply) = makeLabel7TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "7", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (label10TextLayout, label10TextApply) = makeLabel10TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "10", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (label25TextLayout, label25TextApply) = makeLabel25TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "25", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (label50TextLayout, label50TextApply) = makeLabel50TextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "50", font: Font.regular(13.0), textColor: item.theme.list.itemSecondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width, height: CGFloat.greatestFiniteMagnitude), alignment: .center, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
contentSize = CGSize(width: params.width, height: 88.0)
|
||||
insets = itemListNeighborsGroupedInsets(neighbors, params)
|
||||
@ -218,14 +269,31 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
||||
let _ = textApply()
|
||||
strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.size.width) / 2.0), y: 12.0), size: textLayout.size)
|
||||
let _ = label1TextApply()
|
||||
let _ = label3TextApply()
|
||||
let _ = label5TextApply()
|
||||
let _ = label7TextApply()
|
||||
let _ = label10TextApply()
|
||||
let _ = label25TextApply()
|
||||
let _ = label50TextApply()
|
||||
|
||||
let _ = minTextApply()
|
||||
strongSelf.minTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 16.0, y: 16.0), size: minTextLayout.size)
|
||||
let textNodes: [(TextNode, CGSize)] = [
|
||||
(strongSelf.label1TextNode, label1TextLayout.size),
|
||||
(strongSelf.label3TextNode, label3TextLayout.size),
|
||||
(strongSelf.label5TextNode, label5TextLayout.size),
|
||||
(strongSelf.label7TextNode, label7TextLayout.size),
|
||||
(strongSelf.label10TextNode, label10TextLayout.size),
|
||||
(strongSelf.label25TextNode, label25TextLayout.size),
|
||||
(strongSelf.label50TextNode, label50TextLayout.size)
|
||||
]
|
||||
|
||||
let _ = maxTextApply()
|
||||
strongSelf.maxTextNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - 16.0 - maxTextLayout.size.width, y: 16.0), size: maxTextLayout.size)
|
||||
let delta = (params.width - params.leftInset - params.rightInset - 20.0 * 2.0) / CGFloat(textNodes.count - 1)
|
||||
for i in 0 ..< textNodes.count {
|
||||
let (textNode, textSize) = textNodes[i]
|
||||
|
||||
let position = params.leftInset + 20.0 + delta * CGFloat(i)
|
||||
textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(position - textSize.width / 2.0), y: 15.0), size: textSize)
|
||||
}
|
||||
|
||||
if let sliderView = strongSelf.sliderView {
|
||||
if themeUpdated {
|
||||
@ -237,6 +305,29 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
|
||||
sliderView.frame = CGRect(origin: CGPoint(x: params.leftInset + 15.0, y: 37.0), size: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0))
|
||||
sliderView.hitTestEdgeInsets = UIEdgeInsets(top: -sliderView.frame.minX, left: 0.0, bottom: 0.0, right: -sliderView.frame.minX)
|
||||
|
||||
var mappedValue: Int32 = 0
|
||||
switch Int(item.value) {
|
||||
case 1:
|
||||
mappedValue = 0
|
||||
case 3:
|
||||
mappedValue = 1
|
||||
case 5:
|
||||
mappedValue = 2
|
||||
case 7:
|
||||
mappedValue = 3
|
||||
case 10:
|
||||
mappedValue = 4
|
||||
case 25:
|
||||
mappedValue = 5
|
||||
case 50:
|
||||
mappedValue = 6
|
||||
default:
|
||||
mappedValue = 0
|
||||
}
|
||||
if Int32(sliderView.value) != mappedValue {
|
||||
sliderView.value = CGFloat(mappedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -255,6 +346,27 @@ private final class SubscriptionsCountItemNode: ListViewItemNode {
|
||||
guard let sliderView = self.sliderView else {
|
||||
return
|
||||
}
|
||||
self.item?.updated(Int32(sliderView.value))
|
||||
|
||||
var mappedValue: Int32 = 1
|
||||
switch Int(sliderView.value) {
|
||||
case 0:
|
||||
mappedValue = 1
|
||||
case 1:
|
||||
mappedValue = 3
|
||||
case 2:
|
||||
mappedValue = 5
|
||||
case 3:
|
||||
mappedValue = 7
|
||||
case 4:
|
||||
mappedValue = 10
|
||||
case 5:
|
||||
mappedValue = 25
|
||||
case 6:
|
||||
mappedValue = 50
|
||||
default:
|
||||
mappedValue = 1
|
||||
}
|
||||
|
||||
self.item?.updated(Int32(mappedValue))
|
||||
}
|
||||
}
|
||||
|
@ -34,8 +34,9 @@ private final class ChannelStatsControllerArguments {
|
||||
let openPeer: (EnginePeer) -> Void
|
||||
let expandBoosters: () -> Void
|
||||
let openGifts: () -> Void
|
||||
let createPrepaidGiveaway: (Int32, Int32) -> Void
|
||||
|
||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void) {
|
||||
init(context: AccountContext, loadDetailedGraph: @escaping (StatsGraph, Int64) -> Signal<StatsGraph?, NoError>, openMessage: @escaping (MessageId) -> Void, contextAction: @escaping (MessageId, ASDisplayNode, ContextGesture?) -> Void, copyBoostLink: @escaping (String) -> Void, shareBoostLink: @escaping (String) -> Void, openPeer: @escaping (EnginePeer) -> Void, expandBoosters: @escaping () -> Void, openGifts: @escaping () -> Void, createPrepaidGiveaway: @escaping (Int32, Int32) -> Void) {
|
||||
self.context = context
|
||||
self.loadDetailedGraph = loadDetailedGraph
|
||||
self.openMessageStats = openMessage
|
||||
@ -45,6 +46,7 @@ private final class ChannelStatsControllerArguments {
|
||||
self.openPeer = openPeer
|
||||
self.expandBoosters = expandBoosters
|
||||
self.openGifts = openGifts
|
||||
self.createPrepaidGiveaway = createPrepaidGiveaway
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,6 +64,7 @@ private enum StatsSection: Int32 {
|
||||
case instantPageInteractions
|
||||
case boostLevel
|
||||
case boostOverview
|
||||
case boostPrepaid
|
||||
case boosters
|
||||
case boostLink
|
||||
case gifts
|
||||
@ -106,6 +109,10 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case boostOverviewTitle(PresentationTheme, String)
|
||||
case boostOverview(PresentationTheme, ChannelBoostStatus)
|
||||
|
||||
case boostPrepaidTitle(PresentationTheme, String)
|
||||
case boostPrepaid(Int32, PresentationTheme, String, String, Int32, Int32)
|
||||
case boostPrepaidInfo(PresentationTheme, String)
|
||||
|
||||
case boostersTitle(PresentationTheme, String)
|
||||
case boostersPlaceholder(PresentationTheme, String)
|
||||
case booster(Int32, PresentationTheme, PresentationDateTimeFormat, EnginePeer, Int32)
|
||||
@ -147,6 +154,8 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return StatsSection.boostLevel.rawValue
|
||||
case .boostOverviewTitle, .boostOverview:
|
||||
return StatsSection.boostOverview.rawValue
|
||||
case .boostPrepaidTitle, .boostPrepaid, .boostPrepaidInfo:
|
||||
return StatsSection.boostPrepaid.rawValue
|
||||
case .boostersTitle, .boostersPlaceholder, .booster, .boostersExpand, .boostersInfo:
|
||||
return StatsSection.boosters.rawValue
|
||||
case .boostLinkTitle, .boostLink, .boostLinkInfo:
|
||||
@ -208,12 +217,18 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
return 2001
|
||||
case .boostOverview:
|
||||
return 2002
|
||||
case .boostersTitle:
|
||||
case .boostPrepaidTitle:
|
||||
return 2003
|
||||
case let .boostPrepaid(index, _, _, _, _, _):
|
||||
return 2004 + index
|
||||
case .boostPrepaidInfo:
|
||||
return 2100
|
||||
case .boostersTitle:
|
||||
return 2101
|
||||
case .boostersPlaceholder:
|
||||
return 2004
|
||||
return 2102
|
||||
case let .booster(index, _, _, _, _):
|
||||
return 2005 + index
|
||||
return 2103 + index
|
||||
case .boostersExpand:
|
||||
return 10000
|
||||
case .boostersInfo:
|
||||
@ -383,6 +398,24 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostPrepaidTitle(lhsTheme, lhsText):
|
||||
if case let .boostPrepaidTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostPrepaid(lhsIndex, lhsTheme, lhsTitle, lhsSubtitle, lhsMonths, lhsCount):
|
||||
if case let .boostPrepaid(rhsIndex, rhsTheme, rhsTitle, rhsSubtitle, rhsMonths, rhsCount) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsSubtitle == rhsSubtitle, lhsMonths == rhsMonths, lhsCount == rhsCount {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostPrepaidInfo(lhsTheme, lhsText):
|
||||
if case let .boostPrepaidInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .boostersTitle(lhsTheme, lhsText):
|
||||
if case let .boostersTitle(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
|
||||
return true
|
||||
@ -466,12 +499,14 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
let .postsTitle(_, text),
|
||||
let .instantPageInteractionsTitle(_, text),
|
||||
let .boostOverviewTitle(_, text),
|
||||
let .boostPrepaidTitle(_, text),
|
||||
let .boostersTitle(_, text),
|
||||
let .boostLinkTitle(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .boostersInfo(_, text),
|
||||
case let .boostPrepaidInfo(_, text),
|
||||
let .boostersInfo(_, text),
|
||||
let .boostLinkInfo(_, text),
|
||||
let .giftsInfo(_, text):
|
||||
let .giftsInfo(_, text):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
|
||||
case let .overview(_, stats):
|
||||
return StatsOverviewItem(presentationData: presentationData, stats: stats, sectionId: self.section, style: .blocks)
|
||||
@ -523,9 +558,24 @@ private enum StatsEntry: ItemListNodeEntry {
|
||||
case let .boostersPlaceholder(_, text):
|
||||
return ItemListPlaceholderItem(theme: presentationData.theme, text: text, sectionId: self.section, style: .blocks)
|
||||
case let .gifts(theme, title):
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addBoostsIcon(theme), title: title, sectionId: self.section, editing: false, action: {
|
||||
arguments.openGifts()
|
||||
})
|
||||
case let .boostPrepaid(_, _, title, subtitle, months, count):
|
||||
let color: GiftOptionItem.Icon.Color
|
||||
switch months {
|
||||
case 3:
|
||||
color = .green
|
||||
case 6:
|
||||
color = .blue
|
||||
case 12:
|
||||
color = .red
|
||||
default:
|
||||
color = .blue
|
||||
}
|
||||
return GiftOptionItem(presentationData: presentationData, context: arguments.context, icon: GiftOptionItem.Icon(color: color, name: "Premium/Giveaway"), title: title, titleFont: .bold, subtitle: subtitle, label: .boosts(count), sectionId: self.section, action: {
|
||||
arguments.createPrepaidGiveaway(months, count)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -650,6 +700,12 @@ private func channelStatsControllerEntries(state: ChannelStatsControllerState, p
|
||||
entries.append(.boostOverviewTitle(presentationData.theme, presentationData.strings.Stats_Boosts_OverviewHeader))
|
||||
entries.append(.boostOverview(presentationData.theme, boostData))
|
||||
|
||||
//TODO:localize
|
||||
entries.append(.boostPrepaidTitle(presentationData.theme, "PREPAID GIVEAWAYS"))
|
||||
entries.append(.boostPrepaid(0, presentationData.theme, "70 Telegram Premium", "3-month subscriptions", 3, 70))
|
||||
entries.append(.boostPrepaid(1, presentationData.theme, "200 Telegram Premium", "6-month subscriptions", 6, 200))
|
||||
entries.append(.boostPrepaidInfo(presentationData.theme, "Select a giveaway you already paid for to set it up."))
|
||||
|
||||
let boostersTitle: String
|
||||
let boostersPlaceholder: String?
|
||||
let boostersFooter: String?
|
||||
@ -812,7 +868,11 @@ public func channelStatsController(context: AccountContext, updatedPresentationD
|
||||
updateState { $0.withUpdatedBoostersExpanded(true) }
|
||||
},
|
||||
openGifts: {
|
||||
let controller = createGiveawayController(context: context, peerId: peerId)
|
||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .generic)
|
||||
pushImpl?(controller)
|
||||
},
|
||||
createPrepaidGiveaway: { months, count in
|
||||
let controller = createGiveawayController(context: context, peerId: peerId, subject: .prepaid(months: months, count: count))
|
||||
pushImpl?(controller)
|
||||
})
|
||||
|
||||
|
@ -211,7 +211,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1815593308] = { return Api.DocumentAttribute.parse_documentAttributeImageSize($0) }
|
||||
dict[1662637586] = { return Api.DocumentAttribute.parse_documentAttributeSticker($0) }
|
||||
dict[-745541182] = { return Api.DocumentAttribute.parse_documentAttributeVideo($0) }
|
||||
dict[-40996577] = { return Api.DraftMessage.parse_draftMessage($0) }
|
||||
dict[-1783606645] = { return Api.DraftMessage.parse_draftMessage($0) }
|
||||
dict[453805082] = { return Api.DraftMessage.parse_draftMessageEmpty($0) }
|
||||
dict[-1764723459] = { return Api.EmailVerification.parse_emailVerificationApple($0) }
|
||||
dict[-1842457175] = { return Api.EmailVerification.parse_emailVerificationCode($0) }
|
||||
@ -325,6 +325,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) }
|
||||
dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) }
|
||||
dict[-977967015] = { return Api.InputInvoice.parse_inputInvoiceMessage($0) }
|
||||
dict[-1734841331] = { return Api.InputInvoice.parse_inputInvoicePremiumGiftCode($0) }
|
||||
dict[-1020867857] = { return Api.InputInvoice.parse_inputInvoiceSlug($0) }
|
||||
dict[-122978821] = { return Api.InputMedia.parse_inputMediaContact($0) }
|
||||
dict[-428884101] = { return Api.InputMedia.parse_inputMediaDice($0) }
|
||||
@ -385,7 +386,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-380694650] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowChatParticipants($0) }
|
||||
dict[195371015] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowContacts($0) }
|
||||
dict[-1877932953] = { return Api.InputPrivacyRule.parse_inputPrivacyValueDisallowUsers($0) }
|
||||
dict[-1672247580] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) }
|
||||
dict[121554949] = { return Api.InputReplyTo.parse_inputReplyToMessage($0) }
|
||||
dict[363917955] = { return Api.InputReplyTo.parse_inputReplyToStory($0) }
|
||||
dict[1399317950] = { return Api.InputSecureFile.parse_inputSecureFile($0) }
|
||||
dict[859091184] = { return Api.InputSecureFile.parse_inputSecureFileUploaded($0) }
|
||||
@ -406,7 +407,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1251549527] = { return Api.InputStickeredMedia.parse_inputStickeredMediaPhoto($0) }
|
||||
dict[1634697192] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentGiftPremium($0) }
|
||||
dict[-1551868097] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiftCode($0) }
|
||||
dict[-566640558] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
|
||||
dict[-381016791] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumGiveaway($0) }
|
||||
dict[-1502273946] = { return Api.InputStorePaymentPurpose.parse_inputStorePaymentPremiumSubscription($0) }
|
||||
dict[1012306921] = { return Api.InputTheme.parse_inputTheme($0) }
|
||||
dict[-175567375] = { return Api.InputTheme.parse_inputThemeSlug($0) }
|
||||
@ -534,7 +535,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) }
|
||||
dict[1457575028] = { return Api.MessageMedia.parse_messageMediaGeo($0) }
|
||||
dict[-1186937242] = { return Api.MessageMedia.parse_messageMediaGeoLive($0) }
|
||||
dict[1202724576] = { return Api.MessageMedia.parse_messageMediaGiveaway($0) }
|
||||
dict[1116825468] = { return Api.MessageMedia.parse_messageMediaGiveaway($0) }
|
||||
dict[-156940077] = { return Api.MessageMedia.parse_messageMediaInvoice($0) }
|
||||
dict[1766936791] = { return Api.MessageMedia.parse_messageMediaPhoto($0) }
|
||||
dict[1272375192] = { return Api.MessageMedia.parse_messageMediaPoll($0) }
|
||||
@ -549,7 +550,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[182649427] = { return Api.MessageRange.parse_messageRange($0) }
|
||||
dict[1328256121] = { return Api.MessageReactions.parse_messageReactions($0) }
|
||||
dict[-2083123262] = { return Api.MessageReplies.parse_messageReplies($0) }
|
||||
dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) }
|
||||
dict[1029445267] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) }
|
||||
dict[-1667711039] = { return Api.MessageReplyHeader.parse_messageReplyStoryHeader($0) }
|
||||
dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) }
|
||||
dict[975236280] = { return Api.MessagesFilter.parse_inputMessagesFilterChatPhotos($0) }
|
||||
@ -659,7 +660,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[2061444128] = { return Api.PollResults.parse_pollResults($0) }
|
||||
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
|
||||
dict[512535275] = { return Api.PostAddress.parse_postAddress($0) }
|
||||
dict[-713473172] = { return Api.PremiumGiftCodeOption.parse_premiumGiftCodeOption($0) }
|
||||
dict[629052971] = { return Api.PremiumGiftCodeOption.parse_premiumGiftCodeOption($0) }
|
||||
dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) }
|
||||
dict[1596792306] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) }
|
||||
dict[-1534675103] = { return Api.PrivacyKey.parse_privacyKeyAbout($0) }
|
||||
@ -1149,8 +1150,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[1218005070] = { return Api.messages.VotesList.parse_votesList($0) }
|
||||
dict[-44166467] = { return Api.messages.WebPage.parse_webPage($0) }
|
||||
dict[1042605427] = { return Api.payments.BankCardData.parse_bankCardData($0) }
|
||||
dict[-9426548] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) }
|
||||
dict[-1222446760] = { return Api.payments.CheckedGiftCode.parse_checkedGiftCode($0) }
|
||||
dict[-1362048039] = { return Api.payments.ExportedInvoice.parse_exportedInvoice($0) }
|
||||
dict[2054937690] = { return Api.payments.GiveawayInfo.parse_giveawayInfo($0) }
|
||||
dict[952312868] = { return Api.payments.GiveawayInfo.parse_giveawayInfoResults($0) }
|
||||
dict[-1610250415] = { return Api.payments.PaymentForm.parse_paymentForm($0) }
|
||||
dict[1891958275] = { return Api.payments.PaymentReceipt.parse_paymentReceipt($0) }
|
||||
dict[1314881805] = { return Api.payments.PaymentResult.parse_paymentResult($0) }
|
||||
@ -2034,6 +2037,8 @@ public extension Api {
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.ExportedInvoice:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.GiveawayInfo:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.PaymentForm:
|
||||
_1.serialize(buffer, boxed)
|
||||
case let _1 as Api.payments.PaymentReceipt:
|
||||
|
@ -1,17 +1,24 @@
|
||||
public extension Api {
|
||||
indirect enum InputReplyTo: TypeConstructorDescription {
|
||||
case inputReplyToMessage(flags: Int32, replyToMsgId: Int32, topMsgId: Int32?)
|
||||
case inputReplyToMessage(flags: Int32, replyToMsgId: Int32, topMsgId: Int32?, replyToPeerId: Api.InputPeer?, quoteText: String?, quoteEntities: [Api.MessageEntity]?)
|
||||
case inputReplyToStory(userId: Api.InputUser, storyId: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId):
|
||||
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId, let replyToPeerId, let quoteText, let quoteEntities):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1672247580)
|
||||
buffer.appendInt32(121554949)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(replyToMsgId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {replyToPeerId!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(quoteEntities!.count))
|
||||
for item in quoteEntities! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
break
|
||||
case .inputReplyToStory(let userId, let storyId):
|
||||
if boxed {
|
||||
@ -25,8 +32,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId):
|
||||
return ("inputReplyToMessage", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("topMsgId", topMsgId as Any)])
|
||||
case .inputReplyToMessage(let flags, let replyToMsgId, let topMsgId, let replyToPeerId, let quoteText, let quoteEntities):
|
||||
return ("inputReplyToMessage", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("topMsgId", topMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any)])
|
||||
case .inputReplyToStory(let userId, let storyId):
|
||||
return ("inputReplyToStory", [("userId", userId as Any), ("storyId", storyId as Any)])
|
||||
}
|
||||
@ -39,11 +46,24 @@ public extension Api {
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
|
||||
var _4: Api.InputPeer?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.InputPeer
|
||||
} }
|
||||
var _5: String?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_5 = parseString(reader) }
|
||||
var _6: [Api.MessageEntity]?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {if let _ = reader.readInt32() {
|
||||
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3)
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 2) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 3) == 0) || _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.InputReplyTo.inputReplyToMessage(flags: _1!, replyToMsgId: _2!, topMsgId: _3, replyToPeerId: _4, quoteText: _5, quoteEntities: _6)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -580,7 +600,7 @@ public extension Api {
|
||||
indirect enum InputStorePaymentPurpose: TypeConstructorDescription {
|
||||
case inputStorePaymentGiftPremium(userId: Api.InputUser, currency: String, amount: Int64)
|
||||
case inputStorePaymentPremiumGiftCode(flags: Int32, users: [Api.InputUser], boostPeer: Api.InputPeer?, currency: String, amount: Int64)
|
||||
case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
||||
case inputStorePaymentPremiumGiveaway(flags: Int32, boostPeer: Api.InputPeer, additionalPeers: [Api.InputPeer]?, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
||||
case inputStorePaymentPremiumSubscription(flags: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
@ -607,12 +627,17 @@ public extension Api {
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
serializeInt64(amount, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let randomId, let untilDate, let currency, let amount):
|
||||
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let randomId, let untilDate, let currency, let amount):
|
||||
if boxed {
|
||||
buffer.appendInt32(-566640558)
|
||||
buffer.appendInt32(-381016791)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
boostPeer.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 1) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(additionalPeers!.count))
|
||||
for item in additionalPeers! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
serializeInt64(randomId, buffer: buffer, boxed: false)
|
||||
serializeInt32(untilDate, buffer: buffer, boxed: false)
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
@ -633,8 +658,8 @@ public extension Api {
|
||||
return ("inputStorePaymentGiftPremium", [("userId", userId as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
case .inputStorePaymentPremiumGiftCode(let flags, let users, let boostPeer, let currency, let amount):
|
||||
return ("inputStorePaymentPremiumGiftCode", [("flags", flags as Any), ("users", users as Any), ("boostPeer", boostPeer as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let randomId, let untilDate, let currency, let amount):
|
||||
return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
case .inputStorePaymentPremiumGiveaway(let flags, let boostPeer, let additionalPeers, let randomId, let untilDate, let currency, let amount):
|
||||
return ("inputStorePaymentPremiumGiveaway", [("flags", flags as Any), ("boostPeer", boostPeer as Any), ("additionalPeers", additionalPeers as Any), ("randomId", randomId as Any), ("untilDate", untilDate as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
case .inputStorePaymentPremiumSubscription(let flags):
|
||||
return ("inputStorePaymentPremiumSubscription", [("flags", flags as Any)])
|
||||
}
|
||||
@ -693,22 +718,27 @@ public extension Api {
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.InputPeer
|
||||
}
|
||||
var _3: Int64?
|
||||
_3 = reader.readInt64()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: String?
|
||||
_5 = parseString(reader)
|
||||
var _6: Int64?
|
||||
_6 = reader.readInt64()
|
||||
var _3: [Api.InputPeer]?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.InputPeer.self)
|
||||
} }
|
||||
var _4: Int64?
|
||||
_4 = reader.readInt64()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: String?
|
||||
_6 = parseString(reader)
|
||||
var _7: Int64?
|
||||
_7 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 1) == 0) || _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
|
||||
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, randomId: _3!, untilDate: _4!, currency: _5!, amount: _6!)
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.InputStorePaymentPurpose.inputStorePaymentPremiumGiveaway(flags: _1!, boostPeer: _2!, additionalPeers: _3, randomId: _4!, untilDate: _5!, currency: _6!, amount: _7!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -741,7 +741,7 @@ public extension Api {
|
||||
case messageMediaGame(game: Api.Game)
|
||||
case messageMediaGeo(geo: Api.GeoPoint)
|
||||
case messageMediaGeoLive(flags: Int32, geo: Api.GeoPoint, heading: Int32?, period: Int32, proximityNotificationRadius: Int32?)
|
||||
case messageMediaGiveaway(channels: [Int64], quantity: Int32, months: Int32, untilDate: Int32)
|
||||
case messageMediaGiveaway(flags: Int32, channels: [Int64], quantity: Int32, months: Int32, untilDate: Int32)
|
||||
case messageMediaInvoice(flags: Int32, title: String, description: String, photo: Api.WebDocument?, receiptMsgId: Int32?, currency: String, totalAmount: Int64, startParam: String, extendedMedia: Api.MessageExtendedMedia?)
|
||||
case messageMediaPhoto(flags: Int32, photo: Api.Photo?, ttlSeconds: Int32?)
|
||||
case messageMediaPoll(poll: Api.Poll, results: Api.PollResults)
|
||||
@ -806,10 +806,11 @@ public extension Api {
|
||||
serializeInt32(period, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .messageMediaGiveaway(let channels, let quantity, let months, let untilDate):
|
||||
case .messageMediaGiveaway(let flags, let channels, let quantity, let months, let untilDate):
|
||||
if boxed {
|
||||
buffer.appendInt32(1202724576)
|
||||
buffer.appendInt32(1116825468)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(channels.count))
|
||||
for item in channels {
|
||||
@ -899,8 +900,8 @@ public extension Api {
|
||||
return ("messageMediaGeo", [("geo", geo as Any)])
|
||||
case .messageMediaGeoLive(let flags, let geo, let heading, let period, let proximityNotificationRadius):
|
||||
return ("messageMediaGeoLive", [("flags", flags as Any), ("geo", geo as Any), ("heading", heading as Any), ("period", period as Any), ("proximityNotificationRadius", proximityNotificationRadius as Any)])
|
||||
case .messageMediaGiveaway(let channels, let quantity, let months, let untilDate):
|
||||
return ("messageMediaGiveaway", [("channels", channels as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)])
|
||||
case .messageMediaGiveaway(let flags, let channels, let quantity, let months, let untilDate):
|
||||
return ("messageMediaGiveaway", [("flags", flags as Any), ("channels", channels as Any), ("quantity", quantity as Any), ("months", months as Any), ("untilDate", untilDate as Any)])
|
||||
case .messageMediaInvoice(let flags, let title, let description, let photo, let receiptMsgId, let currency, let totalAmount, let startParam, let extendedMedia):
|
||||
return ("messageMediaInvoice", [("flags", flags as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("receiptMsgId", receiptMsgId as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("startParam", startParam as Any), ("extendedMedia", extendedMedia as Any)])
|
||||
case .messageMediaPhoto(let flags, let photo, let ttlSeconds):
|
||||
@ -1034,22 +1035,25 @@ public extension Api {
|
||||
}
|
||||
}
|
||||
public static func parse_messageMediaGiveaway(_ reader: BufferReader) -> MessageMedia? {
|
||||
var _1: [Int64]?
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: [Int64]?
|
||||
if let _ = reader.readInt32() {
|
||||
_1 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
|
||||
_2 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
|
||||
}
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.MessageMedia.messageMediaGiveaway(channels: _1!, quantity: _2!, months: _3!, untilDate: _4!)
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.MessageMedia.messageMediaGiveaway(flags: _1!, channels: _2!, quantity: _3!, months: _4!, untilDate: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -322,19 +322,26 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum MessageReplyHeader: TypeConstructorDescription {
|
||||
case messageReplyHeader(flags: Int32, replyToMsgId: Int32, replyToPeerId: Api.Peer?, replyToTopId: Int32?)
|
||||
case messageReplyHeader(flags: Int32, replyToMsgId: Int32?, replyToPeerId: Api.Peer?, replyHeader: Api.MessageFwdHeader?, replyToTopId: Int32?, quoteText: String?, quoteEntities: [Api.MessageEntity]?)
|
||||
case messageReplyStoryHeader(userId: Int64, storyId: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId):
|
||||
case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyHeader, let replyToTopId, let quoteText, let quoteEntities):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1495959709)
|
||||
buffer.appendInt32(1029445267)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(replyToMsgId, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 4) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 0) != 0 {replyToPeerId!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 5) != 0 {replyHeader!.serialize(buffer, true)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(replyToTopId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 6) != 0 {serializeString(quoteText!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 7) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(quoteEntities!.count))
|
||||
for item in quoteEntities! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
break
|
||||
case .messageReplyStoryHeader(let userId, let storyId):
|
||||
if boxed {
|
||||
@ -348,8 +355,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyToTopId):
|
||||
return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyToTopId", replyToTopId as Any)])
|
||||
case .messageReplyHeader(let flags, let replyToMsgId, let replyToPeerId, let replyHeader, let replyToTopId, let quoteText, let quoteEntities):
|
||||
return ("messageReplyHeader", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("replyToPeerId", replyToPeerId as Any), ("replyHeader", replyHeader as Any), ("replyToTopId", replyToTopId as Any), ("quoteText", quoteText as Any), ("quoteEntities", quoteEntities as Any)])
|
||||
case .messageReplyStoryHeader(let userId, let storyId):
|
||||
return ("messageReplyStoryHeader", [("userId", userId as Any), ("storyId", storyId as Any)])
|
||||
}
|
||||
@ -359,19 +366,32 @@ public extension Api {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
if Int(_1!) & Int(1 << 4) != 0 {_2 = reader.readInt32() }
|
||||
var _3: Api.Peer?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
|
||||
_3 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
} }
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
|
||||
var _4: Api.MessageFwdHeader?
|
||||
if Int(_1!) & Int(1 << 5) != 0 {if let signature = reader.readInt32() {
|
||||
_4 = Api.parse(reader, signature: signature) as? Api.MessageFwdHeader
|
||||
} }
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||
var _6: String?
|
||||
if Int(_1!) & Int(1 << 6) != 0 {_6 = parseString(reader) }
|
||||
var _7: [Api.MessageEntity]?
|
||||
if Int(_1!) & Int(1 << 7) != 0 {if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageEntity.self)
|
||||
} }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2!, replyToPeerId: _3, replyToTopId: _4)
|
||||
let _c4 = (Int(_1!) & Int(1 << 5) == 0) || _4 != nil
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 6) == 0) || _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 7) == 0) || _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.MessageReplyHeader.messageReplyHeader(flags: _1!, replyToMsgId: _2, replyToPeerId: _3, replyHeader: _4, replyToTopId: _5, quoteText: _6, quoteEntities: _7)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -752,26 +752,29 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum PremiumGiftCodeOption: TypeConstructorDescription {
|
||||
case premiumGiftCodeOption(flags: Int32, users: Int32, months: Int32, storeProduct: String?)
|
||||
case premiumGiftCodeOption(flags: Int32, users: Int32, months: Int32, storeProduct: String?, storeQuantity: Int32?, currency: String, amount: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct):
|
||||
case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct, let storeQuantity, let currency, let amount):
|
||||
if boxed {
|
||||
buffer.appendInt32(-713473172)
|
||||
buffer.appendInt32(629052971)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(users, buffer: buffer, boxed: false)
|
||||
serializeInt32(months, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(storeQuantity!, buffer: buffer, boxed: false)}
|
||||
serializeString(currency, buffer: buffer, boxed: false)
|
||||
serializeInt64(amount, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct):
|
||||
return ("premiumGiftCodeOption", [("flags", flags as Any), ("users", users as Any), ("months", months as Any), ("storeProduct", storeProduct as Any)])
|
||||
case .premiumGiftCodeOption(let flags, let users, let months, let storeProduct, let storeQuantity, let currency, let amount):
|
||||
return ("premiumGiftCodeOption", [("flags", flags as Any), ("users", users as Any), ("months", months as Any), ("storeProduct", storeProduct as Any), ("storeQuantity", storeQuantity as Any), ("currency", currency as Any), ("amount", amount as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -784,12 +787,21 @@ public extension Api {
|
||||
_3 = reader.readInt32()
|
||||
var _4: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = parseString(reader) }
|
||||
var _5: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_5 = reader.readInt32() }
|
||||
var _6: String?
|
||||
_6 = parseString(reader)
|
||||
var _7: Int64?
|
||||
_7 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.PremiumGiftCodeOption.premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4)
|
||||
let _c5 = (Int(_1!) & Int(1 << 1) == 0) || _5 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 {
|
||||
return Api.PremiumGiftCodeOption.premiumGiftCodeOption(flags: _1!, users: _2!, months: _3!, storeProduct: _4, storeQuantity: _5, currency: _6!, amount: _7!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -538,16 +538,17 @@ public extension Api.payments {
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum CheckedGiftCode: TypeConstructorDescription {
|
||||
case checkedGiftCode(flags: Int32, fromId: Api.Peer, toId: Int64?, date: Int32, months: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User])
|
||||
case checkedGiftCode(flags: Int32, fromId: Api.Peer, giveawayMsgId: Int32?, toId: Int64?, date: Int32, months: Int32, usedDate: Int32?, chats: [Api.Chat], users: [Api.User])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .checkedGiftCode(let flags, let fromId, let toId, let date, let months, let usedDate, let chats, let users):
|
||||
case .checkedGiftCode(let flags, let fromId, let giveawayMsgId, let toId, let date, let months, let usedDate, let chats, let users):
|
||||
if boxed {
|
||||
buffer.appendInt32(-9426548)
|
||||
buffer.appendInt32(-1222446760)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
fromId.serialize(buffer, true)
|
||||
if Int(flags) & Int(1 << 3) != 0 {serializeInt32(giveawayMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(toId!, buffer: buffer, boxed: false)}
|
||||
serializeInt32(date, buffer: buffer, boxed: false)
|
||||
serializeInt32(months, buffer: buffer, boxed: false)
|
||||
@ -568,8 +569,8 @@ public extension Api.payments {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .checkedGiftCode(let flags, let fromId, let toId, let date, let months, let usedDate, let chats, let users):
|
||||
return ("checkedGiftCode", [("flags", flags as Any), ("fromId", fromId as Any), ("toId", toId as Any), ("date", date as Any), ("months", months as Any), ("usedDate", usedDate as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
case .checkedGiftCode(let flags, let fromId, let giveawayMsgId, let toId, let date, let months, let usedDate, let chats, let users):
|
||||
return ("checkedGiftCode", [("flags", flags as Any), ("fromId", fromId as Any), ("giveawayMsgId", giveawayMsgId as Any), ("toId", toId as Any), ("date", date as Any), ("months", months as Any), ("usedDate", usedDate as Any), ("chats", chats as Any), ("users", users as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -580,32 +581,35 @@ public extension Api.payments {
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Peer
|
||||
}
|
||||
var _3: Int64?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt64() }
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
if Int(_1!) & Int(1 << 3) != 0 {_3 = reader.readInt32() }
|
||||
var _4: Int64?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt64() }
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
var _6: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_6 = reader.readInt32() }
|
||||
var _7: [Api.Chat]?
|
||||
_6 = reader.readInt32()
|
||||
var _7: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_7 = reader.readInt32() }
|
||||
var _8: [Api.Chat]?
|
||||
if let _ = reader.readInt32() {
|
||||
_7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
|
||||
}
|
||||
var _8: [Api.User]?
|
||||
var _9: [Api.User]?
|
||||
if let _ = reader.readInt32() {
|
||||
_8 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
_9 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 3) == 0) || _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
let _c6 = (Int(_1!) & Int(1 << 1) == 0) || _6 != nil
|
||||
let _c7 = _7 != nil
|
||||
let _c6 = _6 != nil
|
||||
let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil
|
||||
let _c8 = _8 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 {
|
||||
return Api.payments.CheckedGiftCode.checkedGiftCode(flags: _1!, fromId: _2!, toId: _3, date: _4!, months: _5!, usedDate: _6, chats: _7!, users: _8!)
|
||||
let _c9 = _9 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 {
|
||||
return Api.payments.CheckedGiftCode.checkedGiftCode(flags: _1!, fromId: _2!, giveawayMsgId: _3, toId: _4, date: _5!, months: _6!, usedDate: _7, chats: _8!, users: _9!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
@ -650,6 +654,86 @@ public extension Api.payments {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum GiveawayInfo: TypeConstructorDescription {
|
||||
case giveawayInfo(flags: Int32, joinedTooEarlyDate: Int32?, adminDisallowedChatId: Int64?)
|
||||
case giveawayInfoResults(flags: Int32, giftCodeSlug: String?, finishDate: Int32, winnersCount: Int32, activatedCount: Int32)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .giveawayInfo(let flags, let joinedTooEarlyDate, let adminDisallowedChatId):
|
||||
if boxed {
|
||||
buffer.appendInt32(2054937690)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(joinedTooEarlyDate!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt64(adminDisallowedChatId!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .giveawayInfoResults(let flags, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount):
|
||||
if boxed {
|
||||
buffer.appendInt32(952312868)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(giftCodeSlug!, buffer: buffer, boxed: false)}
|
||||
serializeInt32(finishDate, buffer: buffer, boxed: false)
|
||||
serializeInt32(winnersCount, buffer: buffer, boxed: false)
|
||||
serializeInt32(activatedCount, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .giveawayInfo(let flags, let joinedTooEarlyDate, let adminDisallowedChatId):
|
||||
return ("giveawayInfo", [("flags", flags as Any), ("joinedTooEarlyDate", joinedTooEarlyDate as Any), ("adminDisallowedChatId", adminDisallowedChatId as Any)])
|
||||
case .giveawayInfoResults(let flags, let giftCodeSlug, let finishDate, let winnersCount, let activatedCount):
|
||||
return ("giveawayInfoResults", [("flags", flags as Any), ("giftCodeSlug", giftCodeSlug as Any), ("finishDate", finishDate as Any), ("winnersCount", winnersCount as Any), ("activatedCount", activatedCount as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_giveawayInfo(_ reader: BufferReader) -> GiveawayInfo? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_2 = reader.readInt32() }
|
||||
var _3: Int64?
|
||||
if Int(_1!) & Int(1 << 2) != 0 {_3 = reader.readInt64() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 1) == 0) || _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.payments.GiveawayInfo.giveawayInfo(flags: _1!, joinedTooEarlyDate: _2, adminDisallowedChatId: _3)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_giveawayInfoResults(_ reader: BufferReader) -> GiveawayInfo? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: String?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_2 = parseString(reader) }
|
||||
var _3: Int32?
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
_4 = reader.readInt32()
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.payments.GiveawayInfo.giveawayInfoResults(flags: _1!, giftCodeSlug: _2, finishDate: _3!, winnersCount: _4!, activatedCount: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.payments {
|
||||
enum PaymentForm: TypeConstructorDescription {
|
||||
case paymentForm(flags: Int32, formId: Int64, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, providerId: Int64, url: String, nativeProvider: String?, nativeParams: Api.DataJSON?, additionalMethods: [Api.PaymentFormMethod]?, savedInfo: Api.PaymentRequestedInfo?, savedCredentials: [Api.PaymentSavedCredentials]?, users: [Api.User])
|
||||
@ -1538,129 +1622,3 @@ public extension Api.photos {
|
||||
|
||||
}
|
||||
}
|
||||
public extension Api.stats {
|
||||
enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: 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, recentMessageInteractions: [Api.MessageInteractionCounters])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let recentMessageInteractions):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1107852396)
|
||||
}
|
||||
period.serialize(buffer, true)
|
||||
followers.serialize(buffer, true)
|
||||
viewsPerPost.serialize(buffer, true)
|
||||
sharesPerPost.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)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(recentMessageInteractions.count))
|
||||
for item in recentMessageInteractions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let recentMessageInteractions):
|
||||
return ("broadcastStats", [("period", period as Any), ("followers", followers as Any), ("viewsPerPost", viewsPerPost as Any), ("sharesPerPost", sharesPerPost 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), ("recentMessageInteractions", recentMessageInteractions 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.StatsPercentValue?
|
||||
if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue
|
||||
}
|
||||
var _6: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _7: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _8: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _9: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
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.MessageInteractionCounters]?
|
||||
if let _ = reader.readInt32() {
|
||||
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageInteractionCounters.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
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 {
|
||||
return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, enabledNotifications: _5!, growthGraph: _6!, followersGraph: _7!, muteGraph: _8!, topHoursGraph: _9!, interactionsGraph: _10!, ivInteractionsGraph: _11!, viewsBySourceGraph: _12!, newFollowersBySourceGraph: _13!, languagesGraph: _14!, recentMessageInteractions: _15!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,129 @@
|
||||
public extension Api.stats {
|
||||
enum BroadcastStats: TypeConstructorDescription {
|
||||
case broadcastStats(period: Api.StatsDateRangeDays, followers: Api.StatsAbsValueAndPrev, viewsPerPost: Api.StatsAbsValueAndPrev, sharesPerPost: 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, recentMessageInteractions: [Api.MessageInteractionCounters])
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let recentMessageInteractions):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1107852396)
|
||||
}
|
||||
period.serialize(buffer, true)
|
||||
followers.serialize(buffer, true)
|
||||
viewsPerPost.serialize(buffer, true)
|
||||
sharesPerPost.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)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(recentMessageInteractions.count))
|
||||
for item in recentMessageInteractions {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .broadcastStats(let period, let followers, let viewsPerPost, let sharesPerPost, let enabledNotifications, let growthGraph, let followersGraph, let muteGraph, let topHoursGraph, let interactionsGraph, let ivInteractionsGraph, let viewsBySourceGraph, let newFollowersBySourceGraph, let languagesGraph, let recentMessageInteractions):
|
||||
return ("broadcastStats", [("period", period as Any), ("followers", followers as Any), ("viewsPerPost", viewsPerPost as Any), ("sharesPerPost", sharesPerPost 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), ("recentMessageInteractions", recentMessageInteractions 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.StatsPercentValue?
|
||||
if let signature = reader.readInt32() {
|
||||
_5 = Api.parse(reader, signature: signature) as? Api.StatsPercentValue
|
||||
}
|
||||
var _6: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_6 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _7: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_7 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _8: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_8 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
var _9: Api.StatsGraph?
|
||||
if let signature = reader.readInt32() {
|
||||
_9 = Api.parse(reader, signature: signature) as? Api.StatsGraph
|
||||
}
|
||||
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.MessageInteractionCounters]?
|
||||
if let _ = reader.readInt32() {
|
||||
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.MessageInteractionCounters.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
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 {
|
||||
return Api.stats.BroadcastStats.broadcastStats(period: _1!, followers: _2!, viewsPerPost: _3!, sharesPerPost: _4!, enabledNotifications: _5!, growthGraph: _6!, followersGraph: _7!, muteGraph: _8!, topHoursGraph: _9!, interactionsGraph: _10!, ivInteractionsGraph: _11!, viewsBySourceGraph: _12!, newFollowersBySourceGraph: _13!, languagesGraph: _14!, recentMessageInteractions: _15!)
|
||||
}
|
||||
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])
|
||||
@ -1326,59 +1452,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)
|
||||
|
@ -6447,12 +6447,11 @@ public extension Api.functions.messages {
|
||||
}
|
||||
}
|
||||
public extension Api.functions.messages {
|
||||
static func saveDraft(flags: Int32, replyToMsgId: Int32?, topMsgId: Int32?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
static func saveDraft(flags: Int32, replyTo: Api.InputReplyTo?, peer: Api.InputPeer, message: String, entities: [Api.MessageEntity]?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1271718337)
|
||||
buffer.appendInt32(1688404588)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(topMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 0) != 0 {replyTo!.serialize(buffer, true)}
|
||||
peer.serialize(buffer, true)
|
||||
serializeString(message, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
@ -6460,7 +6459,7 @@ public extension Api.functions.messages {
|
||||
for item in entities! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", String(describing: flags)), ("replyToMsgId", String(describing: replyToMsgId)), ("topMsgId", String(describing: topMsgId)), ("peer", String(describing: peer)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
return (FunctionDescription(name: "messages.saveDraft", parameters: [("flags", String(describing: flags)), ("replyTo", String(describing: replyTo)), ("peer", String(describing: peer)), ("message", String(describing: message)), ("entities", String(describing: entities))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Bool?
|
||||
if let signature = reader.readInt32() {
|
||||
@ -7562,6 +7561,22 @@ public extension Api.functions.payments {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func getGiveawayInfo(peer: Api.InputPeer, msgId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.GiveawayInfo>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-198994907)
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||
return (FunctionDescription(name: "payments.getGiveawayInfo", parameters: [("peer", String(describing: peer)), ("msgId", String(describing: msgId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.payments.GiveawayInfo? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.payments.GiveawayInfo?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.payments.GiveawayInfo
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.payments {
|
||||
static func getPaymentForm(flags: Int32, invoice: Api.InputInvoice, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.payments.PaymentForm>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -1,16 +1,16 @@
|
||||
public extension Api {
|
||||
enum DraftMessage: TypeConstructorDescription {
|
||||
case draftMessage(flags: Int32, replyToMsgId: Int32?, message: String, entities: [Api.MessageEntity]?, date: Int32)
|
||||
case draftMessage(flags: Int32, replyTo: Api.MessageReplyHeader?, message: String, entities: [Api.MessageEntity]?, date: Int32)
|
||||
case draftMessageEmpty(flags: Int32, date: Int32?)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .draftMessage(let flags, let replyToMsgId, let message, let entities, let date):
|
||||
case .draftMessage(let flags, let replyTo, let message, let entities, let date):
|
||||
if boxed {
|
||||
buffer.appendInt32(-40996577)
|
||||
buffer.appendInt32(-1783606645)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 4) != 0 {replyTo!.serialize(buffer, true)}
|
||||
serializeString(message, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 3) != 0 {buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(entities!.count))
|
||||
@ -31,8 +31,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .draftMessage(let flags, let replyToMsgId, let message, let entities, let date):
|
||||
return ("draftMessage", [("flags", flags as Any), ("replyToMsgId", replyToMsgId as Any), ("message", message as Any), ("entities", entities as Any), ("date", date as Any)])
|
||||
case .draftMessage(let flags, let replyTo, let message, let entities, let date):
|
||||
return ("draftMessage", [("flags", flags as Any), ("replyTo", replyTo as Any), ("message", message as Any), ("entities", entities as Any), ("date", date as Any)])
|
||||
case .draftMessageEmpty(let flags, let date):
|
||||
return ("draftMessageEmpty", [("flags", flags as Any), ("date", date as Any)])
|
||||
}
|
||||
@ -41,8 +41,10 @@ public extension Api {
|
||||
public static func parse_draftMessage(_ reader: BufferReader) -> DraftMessage? {
|
||||
var _1: Int32?
|
||||
_1 = reader.readInt32()
|
||||
var _2: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_2 = reader.readInt32() }
|
||||
var _2: Api.MessageReplyHeader?
|
||||
if Int(_1!) & Int(1 << 4) != 0 {if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.MessageReplyHeader
|
||||
} }
|
||||
var _3: String?
|
||||
_3 = parseString(reader)
|
||||
var _4: [Api.MessageEntity]?
|
||||
@ -52,12 +54,12 @@ public extension Api {
|
||||
var _5: Int32?
|
||||
_5 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 0) == 0) || _2 != nil
|
||||
let _c2 = (Int(_1!) & Int(1 << 4) == 0) || _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 3) == 0) || _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.DraftMessage.draftMessage(flags: _1!, replyToMsgId: _2, message: _3!, entities: _4, date: _5!)
|
||||
return Api.DraftMessage.draftMessage(flags: _1!, replyTo: _2, message: _3!, entities: _4, date: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -209,6 +209,7 @@ public extension Api {
|
||||
public extension Api {
|
||||
indirect enum InputInvoice: TypeConstructorDescription {
|
||||
case inputInvoiceMessage(peer: Api.InputPeer, msgId: Int32)
|
||||
case inputInvoicePremiumGiftCode(purpose: Api.InputStorePaymentPurpose, option: Api.PremiumGiftCodeOption)
|
||||
case inputInvoiceSlug(slug: String)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
@ -220,6 +221,13 @@ public extension Api {
|
||||
peer.serialize(buffer, true)
|
||||
serializeInt32(msgId, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .inputInvoicePremiumGiftCode(let purpose, let option):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1734841331)
|
||||
}
|
||||
purpose.serialize(buffer, true)
|
||||
option.serialize(buffer, true)
|
||||
break
|
||||
case .inputInvoiceSlug(let slug):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1020867857)
|
||||
@ -233,6 +241,8 @@ public extension Api {
|
||||
switch self {
|
||||
case .inputInvoiceMessage(let peer, let msgId):
|
||||
return ("inputInvoiceMessage", [("peer", peer as Any), ("msgId", msgId as Any)])
|
||||
case .inputInvoicePremiumGiftCode(let purpose, let option):
|
||||
return ("inputInvoicePremiumGiftCode", [("purpose", purpose as Any), ("option", option as Any)])
|
||||
case .inputInvoiceSlug(let slug):
|
||||
return ("inputInvoiceSlug", [("slug", slug as Any)])
|
||||
}
|
||||
@ -254,6 +264,24 @@ public extension Api {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputInvoicePremiumGiftCode(_ reader: BufferReader) -> InputInvoice? {
|
||||
var _1: Api.InputStorePaymentPurpose?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.InputStorePaymentPurpose
|
||||
}
|
||||
var _2: Api.PremiumGiftCodeOption?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.PremiumGiftCodeOption
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.InputInvoice.inputInvoicePremiumGiftCode(purpose: _1!, option: _2!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_inputInvoiceSlug(_ reader: BufferReader) -> InputInvoice? {
|
||||
var _1: String?
|
||||
_1 = parseString(reader)
|
||||
|
@ -387,8 +387,12 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
||||
case let .messageMediaStory(flags, peerId, id, _):
|
||||
let isMention = (flags & (1 << 1)) != 0
|
||||
return (TelegramMediaStory(storyId: StoryId(peerId: peerId.peerId, id: id), isMention: isMention), nil, nil, nil)
|
||||
case let .messageMediaGiveaway(channels, quantity, months, untilDate):
|
||||
return (TelegramMediaGiveaway(channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, quantity: quantity, months: months, untilDate: untilDate), nil, nil, nil)
|
||||
case let .messageMediaGiveaway(apiFlags, channels, quantity, months, untilDate):
|
||||
var flags: TelegramMediaGiveaway.Flags = []
|
||||
if (apiFlags & (1 << 0)) != 0 {
|
||||
flags.insert(.onlyNewSubscribers)
|
||||
}
|
||||
return (TelegramMediaGiveaway(flags: flags, channelPeerIds: channels.map { PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value($0)) }, quantity: quantity, months: months, untilDate: untilDate), nil, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -582,7 +586,7 @@ extension StoreMessage {
|
||||
threadId = makeMessageThreadId(threadIdValue)
|
||||
}
|
||||
}
|
||||
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: replyToMsgId), threadMessageId: threadMessageId))
|
||||
attributes.append(ReplyMessageAttribute(messageId: MessageId(peerId: replyPeerId, namespace: Namespaces.Message.Cloud, id: 0), threadMessageId: threadMessageId))
|
||||
case let .messageReplyStoryHeader(userId, storyId):
|
||||
attributes.append(ReplyStoryAttribute(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: storyId)))
|
||||
}
|
||||
|
@ -1,6 +1,16 @@
|
||||
import Postbox
|
||||
|
||||
public final class TelegramMediaGiveaway: Media, Equatable {
|
||||
public struct Flags: OptionSet {
|
||||
public var rawValue: Int32
|
||||
|
||||
public init(rawValue: Int32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let onlyNewSubscribers = Flags(rawValue: 1 << 0)
|
||||
}
|
||||
|
||||
public var id: MediaId? {
|
||||
return nil
|
||||
}
|
||||
@ -8,12 +18,14 @@ public final class TelegramMediaGiveaway: Media, Equatable {
|
||||
return self.channelPeerIds
|
||||
}
|
||||
|
||||
public let flags: Flags
|
||||
public let channelPeerIds: [PeerId]
|
||||
public let quantity: Int32
|
||||
public let months: Int32
|
||||
public let untilDate: Int32
|
||||
|
||||
public init(channelPeerIds: [PeerId], quantity: Int32, months: Int32, untilDate: Int32) {
|
||||
public init(flags: Flags, channelPeerIds: [PeerId], quantity: Int32, months: Int32, untilDate: Int32) {
|
||||
self.flags = flags
|
||||
self.channelPeerIds = channelPeerIds
|
||||
self.quantity = quantity
|
||||
self.months = months
|
||||
@ -21,6 +33,7 @@ public final class TelegramMediaGiveaway: Media, Equatable {
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.flags = Flags(rawValue: decoder.decodeInt32ForKey("flg", orElse: 0))
|
||||
self.channelPeerIds = decoder.decodeInt64ArrayForKey("cns").map { PeerId($0) }
|
||||
self.quantity = decoder.decodeInt32ForKey("qty", orElse: 0)
|
||||
self.months = decoder.decodeInt32ForKey("mts", orElse: 0)
|
||||
@ -28,6 +41,7 @@ public final class TelegramMediaGiveaway: Media, Equatable {
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.flags.rawValue, forKey: "flg")
|
||||
encoder.encodeInt64Array(self.channelPeerIds.map { $0.toInt64() }, forKey: "cns")
|
||||
encoder.encodeInt32(self.quantity, forKey: "qty")
|
||||
encoder.encodeInt32(self.months, forKey: "mts")
|
||||
@ -42,6 +56,9 @@ public final class TelegramMediaGiveaway: Media, Equatable {
|
||||
guard let other = other as? TelegramMediaGiveaway else {
|
||||
return false
|
||||
}
|
||||
if self.flags != other.flags {
|
||||
return false
|
||||
}
|
||||
if self.channelPeerIds != other.channelPeerIds {
|
||||
return false
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ public enum EngineMedia: Equatable {
|
||||
case webFile(TelegramMediaWebFile)
|
||||
case webpage(TelegramMediaWebpage)
|
||||
case story(TelegramMediaStory)
|
||||
case giveaway(TelegramMediaGiveaway)
|
||||
}
|
||||
|
||||
public extension EngineMedia {
|
||||
@ -50,6 +51,8 @@ public extension EngineMedia {
|
||||
return webpage.id
|
||||
case let .story(story):
|
||||
return story.id
|
||||
case let .giveaway(giveaway):
|
||||
return giveaway.id
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,6 +88,8 @@ public extension EngineMedia {
|
||||
self = .webpage(webpage)
|
||||
case let story as TelegramMediaStory:
|
||||
self = .story(story)
|
||||
case let giveaway as TelegramMediaGiveaway:
|
||||
self = .giveaway(giveaway)
|
||||
default:
|
||||
preconditionFailure()
|
||||
}
|
||||
@ -120,6 +125,8 @@ public extension EngineMedia {
|
||||
return webpage
|
||||
case let .story(story):
|
||||
return story
|
||||
case let .giveaway(giveaway):
|
||||
return giveaway
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ public enum AppStoreTransactionPurpose {
|
||||
case restore
|
||||
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
|
||||
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64)
|
||||
case giveaway(boostPeer: EnginePeer.Id, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
||||
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
||||
}
|
||||
|
||||
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
|
||||
@ -59,14 +59,27 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
|
||||
|
||||
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount)
|
||||
}
|
||||
case let .giveaway(boostPeerId, randomId, untilDate, currency, amount):
|
||||
return account.postbox.loadedPeerWithId(boostPeerId)
|
||||
|> mapToSignal { peer in
|
||||
guard let apiBoostPeer = apiInputPeer(peer) else {
|
||||
case let .giveaway(boostPeerId, additionalPeerIds, onlyNewSubscribers, randomId, untilDate, currency, amount):
|
||||
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
|
||||
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
|
||||
return .complete()
|
||||
}
|
||||
return .single(.inputStorePaymentPremiumGiveaway(flags: 0, boostPeer: apiBoostPeer, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
|
||||
var flags: Int32 = 0
|
||||
if onlyNewSubscribers {
|
||||
flags |= (1 << 0)
|
||||
}
|
||||
var additionalPeers: [Api.InputPeer] = []
|
||||
if !additionalPeerIds.isEmpty {
|
||||
flags |= (1 << 1)
|
||||
for peerId in additionalPeerIds {
|
||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
additionalPeers.append(inputPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
|
||||
}
|
||||
|> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import TelegramApi
|
||||
public struct PremiumGiftCodeInfo: Equatable {
|
||||
public let slug: String
|
||||
public let fromPeerId: EnginePeer.Id
|
||||
public let messageId: EngineMessage.Id?
|
||||
public let toPeerId: EnginePeer.Id?
|
||||
public let date: Int32
|
||||
public let months: Int32
|
||||
@ -18,16 +19,19 @@ public struct PremiumGiftCodeOption: Codable, Equatable {
|
||||
case users
|
||||
case months
|
||||
case storeProductId
|
||||
case storeQuantity
|
||||
}
|
||||
|
||||
public let users: Int32
|
||||
public let months: Int32
|
||||
public let storeProductId: String?
|
||||
public let storeQuantity: Int32
|
||||
|
||||
public init(users: Int32, months: Int32, storeProductId: String?) {
|
||||
public init(users: Int32, months: Int32, storeProductId: String?, storeQuantity: Int32) {
|
||||
self.users = users
|
||||
self.months = months
|
||||
self.storeProductId = storeProductId
|
||||
self.storeQuantity = storeQuantity
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
@ -35,6 +39,7 @@ public struct PremiumGiftCodeOption: Codable, Equatable {
|
||||
self.users = try container.decode(Int32.self, forKey: .users)
|
||||
self.months = try container.decode(Int32.self, forKey: .months)
|
||||
self.storeProductId = try container.decodeIfPresent(String.self, forKey: .storeProductId)
|
||||
self.storeQuantity = try container.decodeIfPresent(Int32.self, forKey: .storeQuantity) ?? 1
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@ -42,6 +47,74 @@ public struct PremiumGiftCodeOption: Codable, Equatable {
|
||||
try container.encode(self.users, forKey: .users)
|
||||
try container.encode(self.months, forKey: .months)
|
||||
try container.encodeIfPresent(self.storeProductId, forKey: .storeProductId)
|
||||
try container.encode(self.storeQuantity, forKey: .storeQuantity)
|
||||
}
|
||||
}
|
||||
|
||||
public enum PremiumGiveawayInfo: Equatable {
|
||||
public enum OngoingStatus: Equatable {
|
||||
public enum DisallowReason: Equatable {
|
||||
case joinedTooEarly(Int32)
|
||||
case channelAdmin(EnginePeer.Id)
|
||||
}
|
||||
|
||||
case notQualified
|
||||
case notAllowed(DisallowReason)
|
||||
case participating
|
||||
case almostOver
|
||||
}
|
||||
|
||||
public enum ResultStatus: Equatable {
|
||||
case notWon
|
||||
case won(slug: String)
|
||||
case refunded
|
||||
}
|
||||
|
||||
case ongoing(status: OngoingStatus)
|
||||
case finished(status: ResultStatus, finishDate: Int32, winnersCount: Int32, activatedCount: Int32)
|
||||
}
|
||||
|
||||
func _internal_getPremiumGiveawayInfo(account: Account, peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal<PremiumGiveawayInfo?, NoError> {
|
||||
return account.postbox.loadedPeerWithId(peerId)
|
||||
|> mapToSignal { peer in
|
||||
guard let inputPeer = apiInputPeer(peer) else {
|
||||
return .complete()
|
||||
}
|
||||
return account.network.request(Api.functions.payments.getGiveawayInfo(peer: inputPeer, msgId: messageId.id))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.payments.GiveawayInfo?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> map { result -> PremiumGiveawayInfo? in
|
||||
if let result {
|
||||
switch result {
|
||||
case let .giveawayInfo(flags, joinedTooEarlyDate, adminDisallowedChatId):
|
||||
if (flags & (1 << 3)) != 0 {
|
||||
return .ongoing(status: .almostOver)
|
||||
} else if (flags & (1 << 0)) != 0 {
|
||||
return .ongoing(status: .participating)
|
||||
} else if let joinedTooEarlyDate = joinedTooEarlyDate {
|
||||
return .ongoing(status: .notAllowed(.joinedTooEarly(joinedTooEarlyDate)))
|
||||
} else if let adminDisallowedChatId = adminDisallowedChatId {
|
||||
return .ongoing(status: .notAllowed(.channelAdmin(EnginePeer.Id(namespace: Namespaces.Peer.CloudChannel, id: EnginePeer.Id.Id._internalFromInt64Value(adminDisallowedChatId)))))
|
||||
} else {
|
||||
return .ongoing(status: .notQualified)
|
||||
}
|
||||
case let .giveawayInfoResults(flags, giftCodeSlug, finishDate, winnersCount, activatedCount):
|
||||
let status: PremiumGiveawayInfo.ResultStatus
|
||||
if let giftCodeSlug = giftCodeSlug {
|
||||
status = .won(slug: giftCodeSlug)
|
||||
} else if (flags & (1 << 1)) != 0 {
|
||||
status = .refunded
|
||||
} else {
|
||||
status = .notWon
|
||||
}
|
||||
return .finished(status: status, finishDate: finishDate, winnersCount: winnersCount, activatedCount: activatedCount)
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +149,7 @@ func _internal_checkPremiumGiftCode(account: Account, slug: String) -> Signal<Pr
|
||||
|> mapToSignal { result -> Signal<PremiumGiftCodeInfo?, NoError> in
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .checkedGiftCode(_, _, _, _, _, _, chats, users):
|
||||
case let .checkedGiftCode(_, _, _, _, _, _, _, chats, users):
|
||||
return account.postbox.transaction { transaction in
|
||||
let parsedPeers = AccumulatedPeers(transaction: transaction, chats: chats, users: users)
|
||||
updatePeers(transaction: transaction, accountPeerId: account.peerId, peers: parsedPeers)
|
||||
@ -107,8 +180,8 @@ func _internal_applyPremiumGiftCode(account: Account, slug: String) -> Signal<Ne
|
||||
extension PremiumGiftCodeOption {
|
||||
init(apiGiftCodeOption: Api.PremiumGiftCodeOption) {
|
||||
switch apiGiftCodeOption {
|
||||
case let .premiumGiftCodeOption(_, users, months, storeProduct):
|
||||
self.init(users: users, months: months, storeProductId: storeProduct)
|
||||
case let .premiumGiftCodeOption(_, users, months, storeProduct, storeQuantity, _, _):
|
||||
self.init(users: users, months: months, storeProductId: storeProduct, storeQuantity: storeQuantity ?? 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,9 +189,10 @@ extension PremiumGiftCodeOption {
|
||||
extension PremiumGiftCodeInfo {
|
||||
init(apiCheckedGiftCode: Api.payments.CheckedGiftCode, slug: String) {
|
||||
switch apiCheckedGiftCode {
|
||||
case let .checkedGiftCode(flags, fromId, toId, date, months, usedDate, _, _):
|
||||
case let .checkedGiftCode(flags, fromId, giveawayMsgId, toId, date, months, usedDate, _, _):
|
||||
self.slug = slug
|
||||
self.fromPeerId = fromId.peerId
|
||||
self.messageId = giveawayMsgId.flatMap { EngineMessage.Id(peerId: fromId.peerId, namespace: Namespaces.Message.Cloud, id: $0) }
|
||||
self.toPeerId = toId.flatMap { EnginePeer.Id(namespace: Namespaces.Peer.CloudUser, id: EnginePeer.Id.Id._internalFromInt64Value($0)) }
|
||||
self.date = date
|
||||
self.months = months
|
||||
|
@ -57,5 +57,9 @@ public extension TelegramEngine {
|
||||
public func premiumGiftCodeOptions(peerId: EnginePeer.Id) -> Signal<[PremiumGiftCodeOption], NoError> {
|
||||
return _internal_premiumGiftCodeOptions(account: self.account, peerId: peerId)
|
||||
}
|
||||
|
||||
public func premiumGiveawayInfo(peerId: EnginePeer.Id, messageId: EngineMessage.Id) -> Signal<PremiumGiveawayInfo?, NoError> {
|
||||
return _internal_getPremiumGiveawayInfo(account: self.account, peerId: peerId, messageId: messageId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ public func _internal_recentlySearchedPeers(postbox: Postbox) -> Signal<[Recentl
|
||||
keys.append(contentsOf: peerIds.map({ .peer(peerId: $0, components: .all) }))
|
||||
|
||||
return postbox.combinedView(keys: keys)
|
||||
|> map { view -> [RecentlySearchedPeer] in
|
||||
|> mapToSignal { view -> Signal<[RecentlySearchedPeer], NoError> in
|
||||
var result: [RecentlySearchedPeer] = []
|
||||
var unreadCounts: [PeerId: Int32] = [:]
|
||||
if let unreadCountsView = view.views[unreadCountsKey] as? UnreadMessageCountsView {
|
||||
@ -62,6 +62,7 @@ public func _internal_recentlySearchedPeers(postbox: Postbox) -> Signal<[Recentl
|
||||
}
|
||||
}
|
||||
|
||||
var migratedPeerIds: [EnginePeer.Id: EnginePeer.Id] = [:]
|
||||
for peerId in peerIds {
|
||||
if let peerView = view.views[.peer(peerId: peerId, components: .all)] as? PeerView {
|
||||
var presence: TelegramUserPresence?
|
||||
@ -79,6 +80,10 @@ public func _internal_recentlySearchedPeers(postbox: Postbox) -> Signal<[Recentl
|
||||
unreadCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
if let group = peer as? TelegramGroup, let migrationReference = group.migrationReference {
|
||||
migratedPeerIds = [group.id: migrationReference.peerId]
|
||||
}
|
||||
}
|
||||
|
||||
var subpeerSummary: RecentlySearchedPeerSubpeerSummary?
|
||||
@ -91,7 +96,20 @@ public func _internal_recentlySearchedPeers(postbox: Postbox) -> Signal<[Recentl
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
if !migratedPeerIds.isEmpty {
|
||||
return postbox.transaction { transaction -> Signal<[RecentlySearchedPeer], NoError> in
|
||||
for (previousPeerId, updatedPeerId) in migratedPeerIds {
|
||||
transaction.removeOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, itemId: RecentPeerItemId(previousPeerId).rawValue)
|
||||
if let entry = CodableEntry(RecentPeerItem(rating: 0.0)) {
|
||||
transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.RecentlySearchedPeerIds, item: OrderedItemListEntry(id: RecentPeerItemId(updatedPeerId).rawValue, contents: entry), removeTailIfCountExceeds: 20)
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
|> switchToLatest
|
||||
} else {
|
||||
return .single(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,6 +70,7 @@ public enum PresentationResourceKey: Int32 {
|
||||
case itemListImageIcon
|
||||
case itemListCloudIcon
|
||||
case itemListTopicArrowIcon
|
||||
case itemListAddBoostsIcon
|
||||
|
||||
case itemListVoiceCallIcon
|
||||
case itemListVideoCallIcon
|
||||
|
@ -276,6 +276,12 @@ public struct PresentationResourcesItemList {
|
||||
})
|
||||
}
|
||||
|
||||
public static func addBoostsIcon(_ theme: PresentationTheme) -> UIImage? {
|
||||
return theme.image(PresentationResourceKey.itemListAddBoostsIcon.rawValue, { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Premium/AddBoosts"), color: theme.list.itemAccentColor)
|
||||
})
|
||||
}
|
||||
|
||||
public static func cornersImage(_ theme: PresentationTheme, top: Bool, bottom: Bool) -> UIImage? {
|
||||
if !top && !bottom {
|
||||
return nil
|
||||
|
@ -899,7 +899,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
}
|
||||
case .giftCode:
|
||||
attributedString = NSAttributedString(string: "Gift code", font: titleFont, textColor: primaryTextColor)
|
||||
attributedString = NSAttributedString(string: "Gift Link", font: titleFont, textColor: primaryTextColor)
|
||||
case .unknown:
|
||||
attributedString = nil
|
||||
}
|
||||
|
@ -340,6 +340,8 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Chat/AccessoryPanelNode",
|
||||
"//submodules/TelegramUI/Components/Chat/ForwardAccessoryPanelNode",
|
||||
"//submodules/TelegramUI/Components/LegacyMessageInputPanel",
|
||||
"//submodules/StatisticsUI",
|
||||
"//submodules/TelegramUI/Components/PremiumGiftAttachmentScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
@ -14,6 +14,7 @@ swift_library(
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramUI/Components/AnimatedTextComponent",
|
||||
"//submodules/ActivityIndicator",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -4,16 +4,20 @@ import Display
|
||||
import ComponentFlow
|
||||
import AnimatedTextComponent
|
||||
import ActivityIndicator
|
||||
import BundleIconComponent
|
||||
|
||||
public final class ButtonBadgeComponent: Component {
|
||||
let fillColor: UIColor
|
||||
let style: ButtonTextContentComponent.BadgeStyle
|
||||
let content: AnyComponent<Empty>
|
||||
|
||||
public init(
|
||||
fillColor: UIColor,
|
||||
style: ButtonTextContentComponent.BadgeStyle,
|
||||
content: AnyComponent<Empty>
|
||||
) {
|
||||
self.fillColor = fillColor
|
||||
self.style = style
|
||||
self.content = content
|
||||
}
|
||||
|
||||
@ -21,6 +25,9 @@ public final class ButtonBadgeComponent: Component {
|
||||
if lhs.fillColor != rhs.fillColor {
|
||||
return false
|
||||
}
|
||||
if lhs.style != rhs.style {
|
||||
return false
|
||||
}
|
||||
if lhs.content != rhs.content {
|
||||
return false
|
||||
}
|
||||
@ -46,7 +53,13 @@ public final class ButtonBadgeComponent: Component {
|
||||
}
|
||||
|
||||
public func update(component: ButtonBadgeComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
let height: CGFloat = 20.0
|
||||
let height: CGFloat
|
||||
switch component.style {
|
||||
case .round:
|
||||
height = 20.0
|
||||
case .roundedRectangle:
|
||||
height = 18.0
|
||||
}
|
||||
let contentInset: CGFloat = 10.0
|
||||
|
||||
let themeUpdated = self.component?.fillColor != component.fillColor
|
||||
@ -71,7 +84,12 @@ public final class ButtonBadgeComponent: Component {
|
||||
}
|
||||
|
||||
if themeUpdated || backgroundFrame.height != self.backgroundView.image?.size.height {
|
||||
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: backgroundFrame.height, color: component.fillColor)
|
||||
switch component.style {
|
||||
case .round:
|
||||
self.backgroundView.image = generateStretchableFilledCircleImage(diameter: backgroundFrame.height, color: component.fillColor)
|
||||
case .roundedRectangle:
|
||||
self.backgroundView.image = generateFilledRoundedRectImage(size: CGSize(width: height, height: height), cornerRadius: 4.0, color: component.fillColor)?.stretchableImage(withLeftCapWidth: Int(height / 2.0), topCapHeight: Int(height / 2.0))
|
||||
}
|
||||
}
|
||||
|
||||
return backgroundFrame.size
|
||||
@ -88,11 +106,18 @@ public final class ButtonBadgeComponent: Component {
|
||||
}
|
||||
|
||||
public final class ButtonTextContentComponent: Component {
|
||||
public enum BadgeStyle {
|
||||
case round
|
||||
case roundedRectangle
|
||||
}
|
||||
|
||||
public let text: String
|
||||
public let badge: Int
|
||||
public let textColor: UIColor
|
||||
public let badgeBackground: UIColor
|
||||
public let badgeForeground: UIColor
|
||||
public let badgeStyle: BadgeStyle
|
||||
public let badgeIconName: String?
|
||||
public let combinedAlignment: Bool
|
||||
|
||||
public init(
|
||||
@ -101,6 +126,8 @@ public final class ButtonTextContentComponent: Component {
|
||||
textColor: UIColor,
|
||||
badgeBackground: UIColor,
|
||||
badgeForeground: UIColor,
|
||||
badgeStyle: BadgeStyle = .round,
|
||||
badgeIconName: String? = nil,
|
||||
combinedAlignment: Bool = false
|
||||
) {
|
||||
self.text = text
|
||||
@ -108,6 +135,8 @@ public final class ButtonTextContentComponent: Component {
|
||||
self.textColor = textColor
|
||||
self.badgeBackground = badgeBackground
|
||||
self.badgeForeground = badgeForeground
|
||||
self.badgeStyle = badgeStyle
|
||||
self.badgeIconName = badgeIconName
|
||||
self.combinedAlignment = combinedAlignment
|
||||
}
|
||||
|
||||
@ -127,6 +156,12 @@ public final class ButtonTextContentComponent: Component {
|
||||
if lhs.badgeForeground != rhs.badgeForeground {
|
||||
return false
|
||||
}
|
||||
if lhs.badgeStyle != rhs.badgeStyle {
|
||||
return false
|
||||
}
|
||||
if lhs.badgeIconName != rhs.badgeIconName {
|
||||
return false
|
||||
}
|
||||
if lhs.combinedAlignment != rhs.combinedAlignment {
|
||||
return false
|
||||
}
|
||||
@ -158,7 +193,10 @@ public final class ButtonTextContentComponent: Component {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
let badgeSpacing: CGFloat = 6.0
|
||||
var badgeSpacing: CGFloat = 6.0
|
||||
if component.badgeIconName != nil {
|
||||
badgeSpacing += 4.0
|
||||
}
|
||||
|
||||
let contentSize = self.content.update(
|
||||
transition: .immediate,
|
||||
@ -182,17 +220,34 @@ public final class ButtonTextContentComponent: Component {
|
||||
badge = ComponentView()
|
||||
self.badge = badge
|
||||
}
|
||||
|
||||
var badgeContent: [AnyComponentWithIdentity<Empty>] = []
|
||||
if let badgeIconName = component.badgeIconName {
|
||||
badgeContent.append(AnyComponentWithIdentity(
|
||||
id: "icon",
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: badgeIconName,
|
||||
tintColor: component.badgeForeground
|
||||
)))
|
||||
)
|
||||
}
|
||||
badgeContent.append(AnyComponentWithIdentity(
|
||||
id: "text",
|
||||
component: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: .monospacedNumbers),
|
||||
color: component.badgeForeground,
|
||||
items: [
|
||||
AnimatedTextComponent.Item(id: AnyHashable(0), content: .number(component.badge, minDigits: 0))
|
||||
]
|
||||
)))
|
||||
)
|
||||
|
||||
badgeSize = badge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(ButtonBadgeComponent(
|
||||
fillColor: component.badgeBackground,
|
||||
content: AnyComponent(AnimatedTextComponent(
|
||||
font: Font.semibold(15.0),
|
||||
color: component.badgeForeground,
|
||||
items: [
|
||||
AnimatedTextComponent.Item(id: AnyHashable(0), content: .number(component.badge, minDigits: 0))
|
||||
]
|
||||
))
|
||||
style: component.badgeStyle,
|
||||
content: AnyComponent(HStack(badgeContent, spacing: 2.0))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
|
@ -8,19 +8,22 @@ public final class CameraButton: Component {
|
||||
let tag: AnyObject?
|
||||
let isEnabled: Bool
|
||||
let action: () -> Void
|
||||
let longTapAction: (() -> Void)?
|
||||
|
||||
public init(
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
minSize: CGSize? = nil,
|
||||
tag: AnyObject? = nil,
|
||||
isEnabled: Bool = true,
|
||||
action: @escaping () -> Void
|
||||
action: @escaping () -> Void,
|
||||
longTapAction: (() -> Void)? = nil
|
||||
) {
|
||||
self.content = content
|
||||
self.minSize = minSize
|
||||
self.tag = tag
|
||||
self.isEnabled = isEnabled
|
||||
self.action = action
|
||||
self.longTapAction = longTapAction
|
||||
}
|
||||
|
||||
public func tagged(_ tag: AnyObject) -> CameraButton {
|
||||
@ -29,7 +32,8 @@ public final class CameraButton: Component {
|
||||
minSize: self.minSize,
|
||||
tag: tag,
|
||||
isEnabled: self.isEnabled,
|
||||
action: self.action
|
||||
action: self.action,
|
||||
longTapAction: self.longTapAction
|
||||
)
|
||||
}
|
||||
|
||||
@ -73,6 +77,8 @@ public final class CameraButton: Component {
|
||||
}
|
||||
transition.setScale(view: self, scale: scale)
|
||||
}
|
||||
|
||||
private var longTapGestureRecognizer: UILongPressGestureRecognizer?
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
self.contentView = ComponentHostView<Empty>()
|
||||
@ -86,6 +92,10 @@ public final class CameraButton: Component {
|
||||
self.addSubview(self.contentView)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
|
||||
let longTapGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(self.handleLongPress))
|
||||
self.longTapGestureRecognizer = longTapGestureRecognizer
|
||||
self.addGestureRecognizer(longTapGestureRecognizer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -102,6 +112,10 @@ public final class CameraButton: Component {
|
||||
return false
|
||||
}
|
||||
|
||||
@objc private func handleLongPress() {
|
||||
self.component?.longTapAction?()
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
self.component?.action()
|
||||
}
|
||||
@ -159,6 +173,7 @@ public final class CameraButton: Component {
|
||||
|
||||
self.updateScale(transition: transition)
|
||||
self.isEnabled = component.isEnabled
|
||||
self.longTapGestureRecognizer?.isEnabled = component.longTapAction != nil
|
||||
|
||||
self.contentView.frame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize)
|
||||
|
||||
|
@ -67,7 +67,8 @@ fragment half4 cameraBlobFragment(RasterizerData in[[stage_in]],
|
||||
|
||||
float t = AARadius / resolution.y;
|
||||
|
||||
float cAlpha = 1.0 - primaryParameters.y;
|
||||
float cAlpha = min(1.0, 1.0 - primaryParameters.y);
|
||||
float minColor = min(1.0, 1.0 + primaryParameters.y);
|
||||
float bound = primaryParameters.x + 0.05;
|
||||
if (abs(offset) > bound) {
|
||||
cAlpha = mix(0.0, 1.0, min(1.0, (abs(offset) - bound) * 2.4));
|
||||
@ -75,5 +76,5 @@ fragment half4 cameraBlobFragment(RasterizerData in[[stage_in]],
|
||||
|
||||
float c = smoothstep(t, -t, map(uv, primaryParameters, primaryOffset, secondaryParameters, secondaryOffset));
|
||||
|
||||
return half4(c, max(cAlpha, 0.231), max(cAlpha, 0.188), c);
|
||||
return half4(min(minColor, c), min(minColor, max(cAlpha, 0.231)), min(minColor, max(cAlpha, 0.188)), c);
|
||||
}
|
||||
|
@ -36,36 +36,47 @@ private struct CameraState: Equatable {
|
||||
case holding
|
||||
case handsFree
|
||||
}
|
||||
enum FlashTint {
|
||||
case white
|
||||
case yellow
|
||||
case blue
|
||||
}
|
||||
|
||||
let mode: CameraMode
|
||||
let position: Camera.Position
|
||||
let flashMode: Camera.FlashMode
|
||||
let flashModeDidChange: Bool
|
||||
let flashTint: FlashTint
|
||||
let recording: Recording
|
||||
let duration: Double
|
||||
let isDualCameraEnabled: Bool
|
||||
|
||||
func updatedMode(_ mode: CameraMode) -> CameraState {
|
||||
return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
return CameraState(mode: mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
}
|
||||
|
||||
func updatedPosition(_ position: Camera.Position) -> CameraState {
|
||||
return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
return CameraState(mode: self.mode, position: position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
}
|
||||
|
||||
func updatedFlashMode(_ flashMode: Camera.FlashMode) -> CameraState {
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: flashMode, flashModeDidChange: self.flashMode != flashMode, flashTint: self.flashTint, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
}
|
||||
|
||||
func updatedFlashTint(_ flashTint: FlashTint) -> CameraState {
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: flashTint, recording: self.recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
}
|
||||
|
||||
func updatedRecording(_ recording: Recording) -> CameraState {
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, recording: recording, duration: self.duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
}
|
||||
|
||||
func updatedDuration(_ duration: Double) -> CameraState {
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, recording: self.recording, duration: duration, isDualCameraEnabled: self.isDualCameraEnabled)
|
||||
}
|
||||
|
||||
func updatedIsDualCameraEnabled(_ isDualCameraEnabled: Bool) -> CameraState {
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, recording: self.recording, duration: self.duration, isDualCameraEnabled: isDualCameraEnabled)
|
||||
return CameraState(mode: self.mode, position: self.position, flashMode: self.flashMode, flashModeDidChange: self.flashModeDidChange, flashTint: self.flashTint, recording: self.recording, duration: self.duration, isDualCameraEnabled: isDualCameraEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,6 +173,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
enum ImageKey: Hashable {
|
||||
case cancel
|
||||
case flip
|
||||
case flashImage
|
||||
}
|
||||
private var cachedImages: [ImageKey: UIImage] = [:]
|
||||
func image(_ key: ImageKey) -> UIImage {
|
||||
@ -171,9 +183,21 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
var image: UIImage
|
||||
switch key {
|
||||
case .cancel:
|
||||
image = UIImage(bundleImageName: "Camera/CloseIcon")!
|
||||
image = UIImage(bundleImageName: "Camera/CloseIcon")!.withRenderingMode(.alwaysTemplate)
|
||||
case .flip:
|
||||
image = UIImage(bundleImageName: "Camera/FlipIcon")!
|
||||
image = UIImage(bundleImageName: "Camera/FlipIcon")!.withRenderingMode(.alwaysTemplate)
|
||||
case .flashImage:
|
||||
image = generateImage(CGSize(width: 393.0, height: 852.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
|
||||
var locations: [CGFloat] = [0.0, 0.2, 0.6, 1.0]
|
||||
let colors: [CGColor] = [UIColor(rgb: 0xffffff, alpha: 0.25).cgColor, UIColor(rgb: 0xffffff, alpha: 0.25).cgColor, UIColor(rgb: 0xffffff, alpha: 1.0).cgColor, UIColor(rgb: 0xffffff, alpha: 1.0).cgColor]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
|
||||
let center = CGPoint(x: size.width / 2.0, y: size.height / 2.0 - 10.0)
|
||||
context.drawRadialGradient(gradient, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: size.width, options: .drawsAfterEndLocation)
|
||||
})!.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
cachedImages[key] = image
|
||||
return image
|
||||
@ -589,7 +613,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
|
||||
static var body: Body {
|
||||
let placeholder = Child(PlaceholderComponent.self)
|
||||
let frontFlash = Child(Rectangle.self)
|
||||
let frontFlash = Child(Image.self)
|
||||
let cancelButton = Child(CameraButton.self)
|
||||
let captureControls = Child(CaptureControlsComponent.self)
|
||||
let zoomControl = Child(ZoomComponent.self)
|
||||
@ -689,19 +713,29 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
// )
|
||||
}
|
||||
|
||||
if case .none = component.cameraState.recording {
|
||||
|
||||
} else if case .front = component.cameraState.position {
|
||||
var controlsTintColor: UIColor = .white
|
||||
if case .front = component.cameraState.position, case .on = component.cameraState.flashMode {
|
||||
let flashTintColor: UIColor
|
||||
switch component.cameraState.flashTint {
|
||||
case .white:
|
||||
flashTintColor = .white
|
||||
case .yellow:
|
||||
flashTintColor = UIColor(rgb: 0xffed8c)
|
||||
case .blue:
|
||||
flashTintColor = UIColor(rgb: 0x8cdfff)
|
||||
}
|
||||
let frontFlash = frontFlash.update(
|
||||
component: Rectangle(color: UIColor(white: 1.0, alpha: 0.6)),
|
||||
availableSize: CGSize(width: availableSize.width, height: previewHeight),
|
||||
component: Image(image: state.image(.flashImage), tintColor: flashTintColor),
|
||||
availableSize: availableSize,
|
||||
transition: .easeInOut(duration: 0.2)
|
||||
)
|
||||
context.add(frontFlash
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: environment.safeInsets.top + previewHeight / 2.0))
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
.appear(.default(alpha: true))
|
||||
.disappear(.default(alpha: true))
|
||||
)
|
||||
|
||||
controlsTintColor = .black
|
||||
}
|
||||
|
||||
let shutterState: ShutterButtonState
|
||||
@ -737,6 +771,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
isTablet: isTablet,
|
||||
hasAppeared: component.hasAppeared && hasAllRequiredAccess,
|
||||
hasAccess: hasAllRequiredAccess,
|
||||
tintColor: controlsTintColor,
|
||||
shutterState: shutterState,
|
||||
lastGalleryAsset: state.lastGalleryAsset,
|
||||
tag: captureControlsTag,
|
||||
@ -823,6 +858,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
component: AnyComponent(
|
||||
Image(
|
||||
image: state.image(.cancel),
|
||||
tintColor: controlsTintColor,
|
||||
size: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
)
|
||||
@ -867,7 +903,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
range: nil,
|
||||
waitForCompletion: false
|
||||
),
|
||||
colors: [:],
|
||||
colors: ["__allcolors__": controlsTintColor],
|
||||
size: CGSize(width: 40.0, height: 40.0)
|
||||
)
|
||||
)
|
||||
@ -878,7 +914,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Camera/FlashOffIcon",
|
||||
tintColor: nil
|
||||
tintColor: controlsTintColor
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -909,7 +945,10 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
content: AnyComponentWithIdentity(
|
||||
id: "dual",
|
||||
component: AnyComponent(
|
||||
DualIconComponent(isSelected: component.cameraState.isDualCameraEnabled)
|
||||
DualIconComponent(
|
||||
isSelected: component.cameraState.isDualCameraEnabled,
|
||||
tintColor: controlsTintColor
|
||||
)
|
||||
)
|
||||
),
|
||||
action: { [weak state] in
|
||||
@ -938,7 +977,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
component: AnyComponent(
|
||||
FlipButtonContentComponent(
|
||||
action: animateFlipAction,
|
||||
maskFrame: .zero
|
||||
maskFrame: .zero,
|
||||
tintColor: controlsTintColor
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -971,7 +1011,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
let durationString = String(format: "%02d:%02d", (duration / 60) % 60, duration % 60)
|
||||
let timeLabel = timeLabel.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: durationString, font: Font.with(size: 21.0, design: .camera), textColor: .white)),
|
||||
text: .plain(NSAttributedString(string: durationString, font: Font.with(size: 21.0, design: .camera), textColor: controlsTintColor)),
|
||||
horizontalAlignment: .center,
|
||||
textShadowColor: UIColor(rgb: 0x000000, alpha: 0.2)
|
||||
),
|
||||
@ -1021,7 +1061,10 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
}
|
||||
if let hintText {
|
||||
let hintLabel = hintLabel.update(
|
||||
component: HintLabelComponent(text: hintText),
|
||||
component: HintLabelComponent(
|
||||
text: hintText,
|
||||
tintColor: controlsTintColor
|
||||
),
|
||||
availableSize: availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
@ -1045,6 +1088,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
||||
component: ModeComponent(
|
||||
isTablet: isTablet,
|
||||
strings: environment.strings,
|
||||
tintColor: controlsTintColor,
|
||||
availableModes: [.photo, .video],
|
||||
currentMode: component.cameraState.mode,
|
||||
updatedMode: { [weak state] mode in
|
||||
@ -1358,6 +1402,7 @@ public class CameraScreen: ViewController {
|
||||
position: cameraFrontPosition ? .front : .back,
|
||||
flashMode: .off,
|
||||
flashModeDidChange: false,
|
||||
flashTint: .white,
|
||||
recording: .none,
|
||||
duration: 0.0,
|
||||
isDualCameraEnabled: isDualCameraEnabled
|
||||
@ -2820,17 +2865,23 @@ private final class DualIconComponent: Component {
|
||||
typealias EnvironmentType = Empty
|
||||
|
||||
let isSelected: Bool
|
||||
let tintColor: UIColor
|
||||
|
||||
init(
|
||||
isSelected: Bool
|
||||
isSelected: Bool,
|
||||
tintColor: UIColor
|
||||
) {
|
||||
self.isSelected = isSelected
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
|
||||
static func ==(lhs: DualIconComponent, rhs: DualIconComponent) -> Bool {
|
||||
if lhs.isSelected != rhs.isSelected {
|
||||
return false
|
||||
}
|
||||
if lhs.tintColor != rhs.tintColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -2849,7 +2900,7 @@ private final class DualIconComponent: Component {
|
||||
if let image = UIImage(bundleImageName: "Camera/DualIcon"), let cgImage = image.cgImage {
|
||||
context.draw(cgImage, in: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) / 2.0), y: floorToScreenPixels((size.height - image.size.height) / 2.0) - 1.0), size: image.size))
|
||||
}
|
||||
})
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
let selectedImage = generateImage(CGSize(width: 36.0, height: 36.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: .zero, size: size))
|
||||
@ -2861,7 +2912,7 @@ private final class DualIconComponent: Component {
|
||||
context.clip(to: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - image.size.width) / 2.0), y: floorToScreenPixels((size.height - image.size.height) / 2.0) - 1.0), size: image.size), mask: cgImage)
|
||||
context.fill(CGRect(origin: .zero, size: size))
|
||||
}
|
||||
})
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
self.iconView.image = image
|
||||
self.iconView.highlightedImage = selectedImage
|
||||
@ -2886,6 +2937,8 @@ private final class DualIconComponent: Component {
|
||||
self.iconView.frame = CGRect(origin: .zero, size: size)
|
||||
self.iconView.isHighlighted = component.isSelected
|
||||
|
||||
self.iconView.tintColor = component.tintColor
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ private extension SimpleShapeLayer {
|
||||
private final class ShutterButtonContentComponent: Component {
|
||||
let isTablet: Bool
|
||||
let hasAppeared: Bool
|
||||
let tintColor: UIColor
|
||||
let shutterState: ShutterButtonState
|
||||
let blobState: ShutterBlobView.BlobState
|
||||
let highlightedAction: ActionSlot<Bool>
|
||||
@ -40,6 +41,7 @@ private final class ShutterButtonContentComponent: Component {
|
||||
init(
|
||||
isTablet: Bool,
|
||||
hasAppeared: Bool,
|
||||
tintColor: UIColor,
|
||||
shutterState: ShutterButtonState,
|
||||
blobState: ShutterBlobView.BlobState,
|
||||
highlightedAction: ActionSlot<Bool>,
|
||||
@ -48,6 +50,7 @@ private final class ShutterButtonContentComponent: Component {
|
||||
) {
|
||||
self.isTablet = isTablet
|
||||
self.hasAppeared = hasAppeared
|
||||
self.tintColor = tintColor
|
||||
self.shutterState = shutterState
|
||||
self.blobState = blobState
|
||||
self.highlightedAction = highlightedAction
|
||||
@ -62,6 +65,9 @@ private final class ShutterButtonContentComponent: Component {
|
||||
if lhs.hasAppeared != rhs.hasAppeared {
|
||||
return false
|
||||
}
|
||||
if lhs.tintColor != rhs.tintColor {
|
||||
return false
|
||||
}
|
||||
if lhs.shutterState != rhs.shutterState {
|
||||
return false
|
||||
}
|
||||
@ -156,7 +162,7 @@ private final class ShutterButtonContentComponent: Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let innerColor: UIColor
|
||||
let innerSize: CGSize
|
||||
let ringSize: CGSize
|
||||
@ -164,7 +170,7 @@ private final class ShutterButtonContentComponent: Component {
|
||||
var recordingProgress: Float?
|
||||
switch component.shutterState {
|
||||
case .generic, .disabled:
|
||||
innerColor = .white
|
||||
innerColor = component.tintColor
|
||||
innerSize = CGSize(width: 60.0, height: 60.0)
|
||||
ringSize = CGSize(width: 68.0, height: 68.0)
|
||||
case .video:
|
||||
@ -188,7 +194,7 @@ private final class ShutterButtonContentComponent: Component {
|
||||
}
|
||||
|
||||
self.ringLayer.fillColor = UIColor.clear.cgColor
|
||||
self.ringLayer.strokeColor = UIColor.white.cgColor
|
||||
self.ringLayer.strokeColor = component.tintColor.cgColor
|
||||
self.ringLayer.lineWidth = ringWidth
|
||||
let ringPath = CGPath(
|
||||
ellipseIn: CGRect(
|
||||
@ -204,7 +210,7 @@ private final class ShutterButtonContentComponent: Component {
|
||||
self.ringLayer.position = CGPoint(x: maximumShutterSize.width / 2.0, y: maximumShutterSize.height / 2.0)
|
||||
|
||||
if let blobView = self.blobView {
|
||||
blobView.updateState(component.blobState, transition: transition)
|
||||
blobView.updateState(component.blobState, tintColor: innerColor, transition: transition)
|
||||
if component.isTablet {
|
||||
blobView.bounds = CGRect(origin: .zero, size: CGSize(width: maximumShutterSize.width, height: 440.0))
|
||||
} else {
|
||||
@ -247,14 +253,16 @@ private final class ShutterButtonContentComponent: Component {
|
||||
final class FlipButtonContentComponent: Component {
|
||||
private let action: ActionSlot<Void>
|
||||
private let maskFrame: CGRect
|
||||
private let tintColor: UIColor
|
||||
|
||||
init(action: ActionSlot<Void>, maskFrame: CGRect) {
|
||||
init(action: ActionSlot<Void>, maskFrame: CGRect, tintColor: UIColor) {
|
||||
self.action = action
|
||||
self.maskFrame = maskFrame
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
|
||||
static func ==(lhs: FlipButtonContentComponent, rhs: FlipButtonContentComponent) -> Bool {
|
||||
return lhs.maskFrame == rhs.maskFrame
|
||||
return lhs.maskFrame == rhs.maskFrame && lhs.tintColor == rhs.tintColor
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
@ -280,7 +288,7 @@ final class FlipButtonContentComponent: Component {
|
||||
self.maskLayer.masksToBounds = true
|
||||
self.maskLayer.cornerRadius = 16.0
|
||||
|
||||
self.icon.contents = UIImage(bundleImageName: "Camera/FlipIcon")?.cgImage
|
||||
self.icon.contents = UIImage(bundleImageName: "Camera/FlipIcon")?.withRenderingMode(.alwaysTemplate).cgImage
|
||||
self.darkIcon.contents = UIImage(bundleImageName: "Camera/FlipIcon")?.cgImage
|
||||
self.darkIcon.layerTintColor = UIColor.black.cgColor
|
||||
}
|
||||
@ -326,6 +334,8 @@ final class FlipButtonContentComponent: Component {
|
||||
|
||||
let size = CGSize(width: 48.0, height: 48.0)
|
||||
|
||||
self.icon.layerTintColor = component.tintColor.cgColor
|
||||
|
||||
self.icon.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
self.icon.bounds = CGRect(origin: .zero, size: size)
|
||||
|
||||
@ -350,13 +360,15 @@ final class FlipButtonContentComponent: Component {
|
||||
|
||||
final class LockContentComponent: Component {
|
||||
private let maskFrame: CGRect
|
||||
private let tintColor: UIColor
|
||||
|
||||
init(maskFrame: CGRect) {
|
||||
init(maskFrame: CGRect, tintColor: UIColor) {
|
||||
self.maskFrame = maskFrame
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
|
||||
static func ==(lhs: LockContentComponent, rhs: LockContentComponent) -> Bool {
|
||||
return lhs.maskFrame == rhs.maskFrame
|
||||
return lhs.maskFrame == rhs.maskFrame && lhs.tintColor == rhs.tintColor
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
@ -383,7 +395,7 @@ final class LockContentComponent: Component {
|
||||
self.maskLayer.masksToBounds = true
|
||||
self.maskLayer.cornerRadius = 24.0
|
||||
|
||||
self.icon.contents = UIImage(bundleImageName: "Camera/LockIcon")?.cgImage
|
||||
self.icon.contents = UIImage(bundleImageName: "Camera/LockIcon")?.withRenderingMode(.alwaysTemplate).cgImage
|
||||
self.darkIcon.contents = UIImage(bundleImageName: "Camera/LockedIcon")?.cgImage
|
||||
self.darkIcon.layerTintColor = UIColor.black.cgColor
|
||||
}
|
||||
@ -397,6 +409,8 @@ final class LockContentComponent: Component {
|
||||
|
||||
let size = CGSize(width: 30.0, height: 30.0)
|
||||
|
||||
self.icon.layerTintColor = component.tintColor.cgColor
|
||||
|
||||
self.icon.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
self.icon.bounds = CGRect(origin: .zero, size: size)
|
||||
|
||||
@ -447,6 +461,7 @@ final class CaptureControlsComponent: Component {
|
||||
let isTablet: Bool
|
||||
let hasAppeared: Bool
|
||||
let hasAccess: Bool
|
||||
let tintColor: UIColor
|
||||
let shutterState: ShutterButtonState
|
||||
let lastGalleryAsset: PHAsset?
|
||||
let tag: AnyObject?
|
||||
@ -465,6 +480,7 @@ final class CaptureControlsComponent: Component {
|
||||
isTablet: Bool,
|
||||
hasAppeared: Bool,
|
||||
hasAccess: Bool,
|
||||
tintColor: UIColor,
|
||||
shutterState: ShutterButtonState,
|
||||
lastGalleryAsset: PHAsset?,
|
||||
tag: AnyObject?,
|
||||
@ -482,6 +498,7 @@ final class CaptureControlsComponent: Component {
|
||||
self.isTablet = isTablet
|
||||
self.hasAppeared = hasAppeared
|
||||
self.hasAccess = hasAccess
|
||||
self.tintColor = tintColor
|
||||
self.shutterState = shutterState
|
||||
self.lastGalleryAsset = lastGalleryAsset
|
||||
self.tag = tag
|
||||
@ -507,6 +524,9 @@ final class CaptureControlsComponent: Component {
|
||||
if lhs.hasAccess != rhs.hasAccess {
|
||||
return false
|
||||
}
|
||||
if lhs.tintColor != rhs.tintColor {
|
||||
return false
|
||||
}
|
||||
if lhs.shutterState != rhs.shutterState {
|
||||
return false
|
||||
}
|
||||
@ -578,8 +598,7 @@ final class CaptureControlsComponent: Component {
|
||||
|
||||
private let shutterHightlightedAction = ActionSlot<Bool>()
|
||||
|
||||
private let lockImage = UIImage(bundleImageName: "Camera/LockIcon")
|
||||
private let zoomImage = UIImage(bundleImageName: "Camera/ZoomIcon")
|
||||
private let zoomImage = UIImage(bundleImageName: "Camera/ZoomIcon")?.withRenderingMode(.alwaysTemplate)
|
||||
|
||||
private var didFlip = false
|
||||
|
||||
@ -946,8 +965,10 @@ final class CaptureControlsComponent: Component {
|
||||
transition.setBounds(view: galleryButtonView, bounds: CGRect(origin: .zero, size: galleryButtonFrame.size))
|
||||
transition.setPosition(view: galleryButtonView, position: galleryButtonFrame.center)
|
||||
|
||||
let normalAlpha = component.tintColor.rgb == 0xffffff ? 1.0 : 0.6
|
||||
|
||||
transition.setScale(view: galleryButtonView, scale: isRecording || isTransitioning ? 0.1 : 1.0)
|
||||
transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning ? 0.0 : 1.0)
|
||||
transition.setAlpha(view: galleryButtonView, alpha: isRecording || isTransitioning ? 0.0 : normalAlpha)
|
||||
}
|
||||
|
||||
if !component.isTablet && component.hasAccess {
|
||||
@ -963,7 +984,8 @@ final class CaptureControlsComponent: Component {
|
||||
component: AnyComponent(
|
||||
FlipButtonContentComponent(
|
||||
action: component.flipAnimationAction,
|
||||
maskFrame: flipButtonMaskFrame
|
||||
maskFrame: flipButtonMaskFrame,
|
||||
tintColor: component.tintColor
|
||||
)
|
||||
)
|
||||
),
|
||||
@ -1011,6 +1033,7 @@ final class CaptureControlsComponent: Component {
|
||||
ShutterButtonContentComponent(
|
||||
isTablet: component.isTablet,
|
||||
hasAppeared: component.hasAppeared,
|
||||
tintColor: component.tintColor,
|
||||
shutterState: component.shutterState,
|
||||
blobState: blobState,
|
||||
highlightedAction: self.shutterHightlightedAction,
|
||||
@ -1072,6 +1095,7 @@ final class CaptureControlsComponent: Component {
|
||||
component: AnyComponent(
|
||||
Image(
|
||||
image: self.zoomImage,
|
||||
tintColor: component.tintColor,
|
||||
size: hintIconSize
|
||||
)
|
||||
),
|
||||
@ -1117,7 +1141,8 @@ final class CaptureControlsComponent: Component {
|
||||
id: "lock",
|
||||
component: AnyComponent(
|
||||
LockContentComponent(
|
||||
maskFrame: lockMaskFrame
|
||||
maskFrame: lockMaskFrame,
|
||||
tintColor: component.tintColor
|
||||
)
|
||||
)
|
||||
),
|
||||
|
@ -21,6 +21,7 @@ private let buttonSize = CGSize(width: 55.0, height: 44.0)
|
||||
final class ModeComponent: Component {
|
||||
let isTablet: Bool
|
||||
let strings: PresentationStrings
|
||||
let tintColor: UIColor
|
||||
let availableModes: [CameraMode]
|
||||
let currentMode: CameraMode
|
||||
let updatedMode: (CameraMode) -> Void
|
||||
@ -29,6 +30,7 @@ final class ModeComponent: Component {
|
||||
init(
|
||||
isTablet: Bool,
|
||||
strings: PresentationStrings,
|
||||
tintColor: UIColor,
|
||||
availableModes: [CameraMode],
|
||||
currentMode: CameraMode,
|
||||
updatedMode: @escaping (CameraMode) -> Void,
|
||||
@ -36,6 +38,7 @@ final class ModeComponent: Component {
|
||||
) {
|
||||
self.isTablet = isTablet
|
||||
self.strings = strings
|
||||
self.tintColor = tintColor
|
||||
self.availableModes = availableModes
|
||||
self.currentMode = currentMode
|
||||
self.updatedMode = updatedMode
|
||||
@ -49,6 +52,9 @@ final class ModeComponent: Component {
|
||||
if lhs.strings !== rhs.strings {
|
||||
return false
|
||||
}
|
||||
if lhs.tintColor != rhs.tintColor {
|
||||
return false
|
||||
}
|
||||
if lhs.availableModes != rhs.availableModes {
|
||||
return false
|
||||
}
|
||||
@ -82,8 +88,17 @@ final class ModeComponent: Component {
|
||||
self.pressed()
|
||||
}
|
||||
|
||||
func update(value: String, selected: Bool) {
|
||||
self.setAttributedTitle(NSAttributedString(string: value.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: selected ? UIColor(rgb: 0xf8d74a) : .white, paragraphAlignment: .center), for: .normal)
|
||||
func update(value: String, selected: Bool, tintColor: UIColor) {
|
||||
let accentColor: UIColor
|
||||
let normalColor: UIColor
|
||||
if tintColor.rgb == 0xffffff {
|
||||
accentColor = UIColor(rgb: 0xf8d74a)
|
||||
normalColor = .white
|
||||
} else {
|
||||
accentColor = tintColor
|
||||
normalColor = tintColor.withAlphaComponent(0.5)
|
||||
}
|
||||
self.setAttributedTitle(NSAttributedString(string: value.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: selected ? accentColor : normalColor, paragraphAlignment: .center), for: .normal)
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +167,7 @@ final class ModeComponent: Component {
|
||||
updatedMode(mode)
|
||||
}
|
||||
|
||||
itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode)
|
||||
itemView.update(value: mode.title(strings: component.strings), selected: mode == component.currentMode, tintColor: component.tintColor)
|
||||
itemView.bounds = CGRect(origin: .zero, size: itemFrame.size)
|
||||
|
||||
if isTablet {
|
||||
@ -199,17 +214,23 @@ final class ModeComponent: Component {
|
||||
|
||||
final class HintLabelComponent: Component {
|
||||
let text: String
|
||||
let tintColor: UIColor
|
||||
|
||||
init(
|
||||
text: String
|
||||
text: String,
|
||||
tintColor: UIColor
|
||||
) {
|
||||
self.text = text
|
||||
self.tintColor = tintColor
|
||||
}
|
||||
|
||||
static func ==(lhs: HintLabelComponent, rhs: HintLabelComponent) -> Bool {
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.tintColor != rhs.tintColor {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -247,7 +268,7 @@ final class HintLabelComponent: Component {
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.text.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: .white)),
|
||||
text: .plain(NSAttributedString(string: component.text.uppercased(), font: Font.with(size: 14.0, design: .camera, weight: .semibold), textColor: component.tintColor)),
|
||||
horizontalAlignment: .center
|
||||
)
|
||||
),
|
||||
|
@ -64,7 +64,6 @@ private final class AnimatableProperty<T: Interpolatable> {
|
||||
}
|
||||
|
||||
func tick(timestamp: Double) -> Bool {
|
||||
|
||||
guard let animation = self.animation, case let .curve(duration, curve) = animation.animation else {
|
||||
return false
|
||||
}
|
||||
@ -163,10 +162,14 @@ final class ShutterBlobView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
var primaryRedness: CGFloat {
|
||||
func primaryRedness(tintColor: UIColor) -> CGFloat {
|
||||
switch self {
|
||||
case .generic:
|
||||
return 0.0
|
||||
if tintColor.rgb == 0x000000 {
|
||||
return -1.0
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
default:
|
||||
return 1.0
|
||||
}
|
||||
@ -290,14 +293,14 @@ final class ShutterBlobView: UIView {
|
||||
self.displayLink?.invalidate()
|
||||
}
|
||||
|
||||
func updateState(_ state: BlobState, transition: Transition = .immediate) {
|
||||
func updateState(_ state: BlobState, tintColor: UIColor, transition: Transition = .immediate) {
|
||||
guard self.state != state else {
|
||||
return
|
||||
}
|
||||
self.state = state
|
||||
|
||||
|
||||
self.primarySize.update(value: state.primarySize, transition: transition)
|
||||
self.primaryRedness.update(value: state.primaryRedness, transition: transition)
|
||||
self.primaryRedness.update(value: state.primaryRedness(tintColor: tintColor), transition: transition)
|
||||
self.primaryCornerRadius.update(value: state.primaryCornerRadius, transition: transition)
|
||||
self.secondarySize.update(value: state.secondarySize, transition: transition)
|
||||
self.secondaryRedness.update(value: state.secondaryRedness, transition: transition)
|
||||
@ -453,7 +456,6 @@ final class ShutterBlobView: UIView {
|
||||
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 6, instanceCount: 1)
|
||||
renderEncoder.endEncoding()
|
||||
|
||||
|
||||
var storedDrawable: MetalImageLayer.Drawable? = drawable
|
||||
commandBuffer.addCompletedHandler { _ in
|
||||
DispatchQueue.main.async {
|
||||
|
@ -20,7 +20,6 @@ import AudioToolbox
|
||||
import UndoUI
|
||||
import ContextUI
|
||||
import GalleryUI
|
||||
import AttachmentTextInputPanelNode
|
||||
import TelegramPresentationData
|
||||
import TelegramNotices
|
||||
import StickerPeekUI
|
||||
@ -2081,7 +2080,7 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
||||
}
|
||||
}
|
||||
|
||||
public final class EntityInputView: UIInputView, LegacyMessageInputPanelInputView, AttachmentTextInputPanelInputView, UIInputViewAudioFeedback {
|
||||
public final class EntityInputView: UIInputView, LegacyMessageInputPanelInputView, UIInputViewAudioFeedback {
|
||||
private let context: AccountContext
|
||||
|
||||
public var insertText: ((NSAttributedString) -> Void)?
|
||||
|
@ -106,6 +106,12 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
|
||||
self.currentIsVideo = isVideo
|
||||
}
|
||||
|
||||
public func activateInput() {
|
||||
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||
view.activateInput()
|
||||
}
|
||||
}
|
||||
|
||||
public func dismissInput() -> Bool {
|
||||
if let view = self.inputPanel.view as? MessageInputPanelComponent.View {
|
||||
if view.canDeactivateInput() {
|
||||
|
@ -0,0 +1,25 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PremiumGiftAttachmentScreen",
|
||||
module_name = "PremiumGiftAttachmentScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/AttachmentUI",
|
||||
"//submodules/PremiumUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
58
submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift
Normal file
58
submodules/TelegramUI/Components/PremiumGiftAttachmentScreen/Sources/PremiumGiftAttachmentScreen.swift
Normal file
@ -0,0 +1,58 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import PremiumUI
|
||||
import AttachmentUI
|
||||
|
||||
public class PremiumGiftAttachmentScreen: PremiumGiftScreen, AttachmentContainable {
|
||||
public var requestAttachmentMenuExpansion: () -> Void = {}
|
||||
public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
|
||||
public var cancelPanGesture: () -> Void = { }
|
||||
public var isContainerPanning: () -> Bool = { return false }
|
||||
public var isContainerExpanded: () -> Bool = { return false }
|
||||
|
||||
public var mediaPickerContext: AttachmentMediaPickerContext? {
|
||||
return PremiumGiftContext(controller: self)
|
||||
}
|
||||
}
|
||||
|
||||
private final class PremiumGiftContext: AttachmentMediaPickerContext {
|
||||
private weak var controller: PremiumGiftScreen?
|
||||
|
||||
var selectionCount: Signal<Int, NoError> {
|
||||
return .single(0)
|
||||
}
|
||||
|
||||
var caption: Signal<NSAttributedString?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
public var loadingProgress: Signal<CGFloat?, NoError> {
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
public var mainButtonState: Signal<AttachmentMainButtonState?, NoError> {
|
||||
return self.controller?.mainButtonStatePromise.get() ?? .single(nil)
|
||||
}
|
||||
|
||||
init(controller: PremiumGiftScreen) {
|
||||
self.controller = controller
|
||||
}
|
||||
|
||||
func setCaption(_ caption: NSAttributedString) {
|
||||
}
|
||||
|
||||
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
|
||||
}
|
||||
|
||||
func schedule() {
|
||||
}
|
||||
|
||||
func mainButtonAction() {
|
||||
self.controller?.mainButtonPressed()
|
||||
}
|
||||
}
|
@ -520,7 +520,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
if translation.y > 100.0 || velocity.y > 10.0 {
|
||||
controller.requestDismiss()
|
||||
if case .members = component.stateContext.subject {
|
||||
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
} else {
|
||||
Queue.mainQueue().justDispatch {
|
||||
controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .spring))
|
||||
@ -826,6 +826,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
if case .members = component.stateContext.subject {
|
||||
return
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
return
|
||||
}
|
||||
|
||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||
@ -962,6 +964,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
} else if section.id == 1 {
|
||||
if case .members = component.stateContext.subject {
|
||||
sectionTitle = "SUBSCRIBERS"
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
sectionTitle = "CHANNELS"
|
||||
} else {
|
||||
sectionTitle = environment.strings.Story_Privacy_ContactsHeader
|
||||
}
|
||||
@ -1395,9 +1399,13 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
let subtitle: String?
|
||||
if case let .legacyGroup(group) = peer {
|
||||
subtitle = environment.strings.Conversation_StatusMembers(Int32(group.participantCount))
|
||||
} else if case .channel = peer {
|
||||
} else if case let .channel(channel) = peer {
|
||||
if let count = stateValue.participants[peer.id] {
|
||||
subtitle = environment.strings.Conversation_StatusMembers(Int32(count))
|
||||
if case .broadcast = channel.info {
|
||||
subtitle = environment.strings.Conversation_StatusSubscribers(Int32(count))
|
||||
} else {
|
||||
subtitle = environment.strings.Conversation_StatusMembers(Int32(count))
|
||||
}
|
||||
} else {
|
||||
subtitle = nil
|
||||
}
|
||||
@ -1430,27 +1438,60 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
selectionState: .editing(isSelected: isSelected, isTinted: false),
|
||||
hasNext: true,
|
||||
action: { [weak self] peer in
|
||||
guard let self else {
|
||||
guard let self, let environment = self.environment, let controller = environment.controller() as? ShareWithPeersScreen else {
|
||||
return
|
||||
}
|
||||
if peer.id.isGroupOrChannel {
|
||||
self.toggleGroupPeer(peer)
|
||||
} else {
|
||||
let update = {
|
||||
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||
self.state?.updated(transition: transition)
|
||||
|
||||
if self.searchStateContext != nil {
|
||||
if let navigationTextFieldView = self.navigationTextField.view as? TokenListTextField.View {
|
||||
navigationTextFieldView.clearText()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let togglePeer = {
|
||||
if let index = self.selectedPeers.firstIndex(of: peer.id) {
|
||||
self.selectedPeers.remove(at: index)
|
||||
self.updateSelectedGroupPeers()
|
||||
} else {
|
||||
self.selectedPeers.append(peer.id)
|
||||
}
|
||||
update()
|
||||
}
|
||||
|
||||
let transition = Transition(animation: .curve(duration: 0.35, curve: .spring))
|
||||
self.state?.updated(transition: transition)
|
||||
|
||||
if self.searchStateContext != nil {
|
||||
if let navigationTextFieldView = self.navigationTextField.view as? TokenListTextField.View {
|
||||
navigationTextFieldView.clearText()
|
||||
if peer.id.isGroupOrChannel {
|
||||
if case .channels = component.stateContext.subject, self.selectedPeers.count >= 10 {
|
||||
return
|
||||
}
|
||||
if case .channels = component.stateContext.subject {
|
||||
if case let .channel(channel) = peer, channel.addressName == nil, !self.selectedPeers.contains(peer.id) {
|
||||
let alertController = textAlertController(
|
||||
context: component.context,
|
||||
forceTheme: environment.theme,
|
||||
title: "Channel is Private",
|
||||
text: "Are you sure you want to add a private channel? Users won't be able to join it without an invite link.",
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: environment.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: "Add", action: {
|
||||
togglePeer()
|
||||
})
|
||||
]
|
||||
)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
} else {
|
||||
togglePeer()
|
||||
}
|
||||
} else {
|
||||
self.toggleGroupPeer(peer)
|
||||
update()
|
||||
}
|
||||
} else {
|
||||
if case .members = component.stateContext.subject, self.selectedPeers.count >= 10 {
|
||||
return
|
||||
}
|
||||
togglePeer()
|
||||
}
|
||||
}
|
||||
)),
|
||||
@ -1644,7 +1685,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
}
|
||||
|
||||
let fadeTransition = Transition.easeInOut(duration: 0.25)
|
||||
if let searchStateContext = self.searchStateContext, case let .search(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
|
||||
if let searchStateContext = self.searchStateContext, case let .contactsSearch(query, _) = searchStateContext.subject, let value = searchStateContext.stateValue, value.peers.isEmpty {
|
||||
let sideInset: CGFloat = 44.0
|
||||
let emptyAnimationHeight = 148.0
|
||||
let topInset: CGFloat = topOffset + itemLayout.containerInset + 40.0
|
||||
@ -1762,7 +1803,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
if let controller = self.environment?.controller() as? ShareWithPeersScreen {
|
||||
if case .members = component.stateContext.subject {
|
||||
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
} else {
|
||||
controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
@ -1828,6 +1869,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
self.currentHasChannels = hasChannels
|
||||
} else if case .members = component.stateContext.subject {
|
||||
self.dismissPanGesture?.isEnabled = false
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
self.dismissPanGesture?.isEnabled = false
|
||||
}
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
@ -1975,6 +2018,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
switch component.stateContext.subject {
|
||||
case .members:
|
||||
placeholder = "Search Subscribers"
|
||||
case .channels:
|
||||
placeholder = "Search Channels"
|
||||
case .chats:
|
||||
placeholder = environment.strings.Story_Privacy_SearchChats
|
||||
default:
|
||||
@ -2015,10 +2060,10 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
if component.initialPrivacy.base == .closeFriends || component.initialPrivacy.base == .contacts {
|
||||
onlyContacts = true
|
||||
}
|
||||
if let searchStateContext = self.searchStateContext, searchStateContext.subject == .search(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts) {
|
||||
if let searchStateContext = self.searchStateContext, searchStateContext.subject == .contactsSearch(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts) {
|
||||
} else {
|
||||
self.searchStateDisposable?.dispose()
|
||||
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: .search(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts), editing: false)
|
||||
let searchStateContext = ShareWithPeersScreen.StateContext(context: component.context, subject: .contactsSearch(query: self.navigationTextFieldState.text, onlyContacts: onlyContacts), editing: false)
|
||||
var applyState = false
|
||||
self.searchStateDisposable = (searchStateContext.ready |> filter { $0 } |> take(1) |> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||
guard let self else {
|
||||
@ -2043,6 +2088,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
if case .members = component.stateContext.subject {
|
||||
self.dimView .isHidden = true
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
self.dimView .isHidden = true
|
||||
} else {
|
||||
self.dimView .isHidden = false
|
||||
}
|
||||
@ -2065,7 +2112,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
containerSize: CGSize(width: itemsContainerWidth, height: 1000.0)
|
||||
)
|
||||
var isContactsSearch = false
|
||||
if let searchStateContext = self.searchStateContext, case .search(_, true) = searchStateContext.subject {
|
||||
if let searchStateContext = self.searchStateContext, case .contactsSearch(_, true) = searchStateContext.subject {
|
||||
isContactsSearch = true
|
||||
}
|
||||
let peerItemSize = self.peerTemplateItem.update(
|
||||
@ -2218,6 +2265,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
var containerInset: CGFloat = environment.statusBarHeight
|
||||
if case .members = component.stateContext.subject {
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
} else {
|
||||
containerInset += 10.0
|
||||
}
|
||||
@ -2274,6 +2322,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
var actionButtonTitle = environment.strings.Story_Privacy_SaveList
|
||||
let title: String
|
||||
var subtitle: String?
|
||||
switch component.stateContext.subject {
|
||||
case .peers:
|
||||
title = environment.strings.Story_Privacy_PostStoryAs
|
||||
@ -2301,15 +2350,40 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
case .everyone:
|
||||
title = environment.strings.Story_Privacy_ExcludedPeople
|
||||
}
|
||||
case .search:
|
||||
case .contactsSearch:
|
||||
title = ""
|
||||
case .members:
|
||||
title = "Gift Premium"
|
||||
actionButtonTitle = "Save Recipients"
|
||||
subtitle = "select up to 10 subscribers"
|
||||
case .channels:
|
||||
title = "Add Channels"
|
||||
actionButtonTitle = "Save Channels"
|
||||
subtitle = "select up to 10 channels"
|
||||
}
|
||||
|
||||
let titleComponent: AnyComponent<Empty>
|
||||
if let subtitle {
|
||||
titleComponent = AnyComponent(
|
||||
List([
|
||||
AnyComponentWithIdentity(
|
||||
id: "title",
|
||||
component: AnyComponent(Text(text: title, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.primaryTextColor))
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "subtitle",
|
||||
component: AnyComponent(Text(text: subtitle, font: Font.regular(13.0), color: environment.theme.rootController.navigationBar.secondaryTextColor))
|
||||
)
|
||||
],
|
||||
centerAlignment: true)
|
||||
)
|
||||
} else {
|
||||
titleComponent = AnyComponent(Text(text: title, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.primaryTextColor))
|
||||
}
|
||||
|
||||
let navigationTitleSize = self.navigationTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Text(text: title, font: Font.semibold(17.0), color: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||
component: titleComponent,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerWidth - navigationButtonsWidth, height: navigationHeight)
|
||||
)
|
||||
@ -2344,6 +2418,8 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
var inset: CGFloat
|
||||
if case .members = component.stateContext.subject {
|
||||
inset = 1000.0
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
inset = 1000.0
|
||||
} else if case let .stories(editing) = component.stateContext.subject {
|
||||
if editing {
|
||||
inset = 351.0
|
||||
@ -2644,6 +2720,7 @@ final class ShareWithPeersScreenComponent: Component {
|
||||
|
||||
var scrollClippingInset: CGFloat = 0.0
|
||||
if case .members = component.stateContext.subject {
|
||||
} else if case .channels = component.stateContext.subject {
|
||||
} else {
|
||||
scrollClippingInset = 10.0
|
||||
}
|
||||
@ -2865,6 +2942,8 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
var theme: ViewControllerComponentContainer.Theme = .dark
|
||||
if case .members = stateContext.subject {
|
||||
theme = .default
|
||||
} else if case .channels = stateContext.subject {
|
||||
theme = .default
|
||||
}
|
||||
super.init(context: context, component: ShareWithPeersScreenComponent(
|
||||
context: context,
|
||||
@ -2887,6 +2966,9 @@ public class ShareWithPeersScreen: ViewControllerComponentContainer {
|
||||
if case .members = stateContext.subject {
|
||||
self.navigationPresentation = .modal
|
||||
self.isCustomModal = false
|
||||
} else if case .channels = stateContext.subject {
|
||||
self.navigationPresentation = .modal
|
||||
self.isCustomModal = false
|
||||
} else {
|
||||
self.navigationPresentation = .flatModal
|
||||
}
|
||||
|
@ -46,8 +46,9 @@ public extension ShareWithPeersScreen {
|
||||
case stories(editing: Bool)
|
||||
case chats(blocked: Bool)
|
||||
case contacts(base: EngineStoryPrivacy.Base)
|
||||
case search(query: String, onlyContacts: Bool)
|
||||
case members(peerId: EnginePeer.Id)
|
||||
case contactsSearch(query: String, onlyContacts: Bool)
|
||||
case members(peerId: EnginePeer.Id, searchQuery: String?)
|
||||
case channels(exclude: Set<EnginePeer.Id>)
|
||||
}
|
||||
|
||||
var stateValue: State?
|
||||
@ -431,7 +432,7 @@ public extension ShareWithPeersScreen {
|
||||
|
||||
self.readySubject.set(true)
|
||||
})
|
||||
case let .search(query, onlyContacts):
|
||||
case let .contactsSearch(query, onlyContacts):
|
||||
let signal: Signal<([EngineRenderedPeer], [EnginePeer.Id: Optional<EnginePeer.Presence>], [EnginePeer.Id: Optional<Int>]), NoError>
|
||||
if onlyContacts {
|
||||
signal = combineLatest(
|
||||
@ -508,18 +509,16 @@ public extension ShareWithPeersScreen {
|
||||
|
||||
self.readySubject.set(true)
|
||||
})
|
||||
case let .members(peerId):
|
||||
case let .members(peerId, searchQuery):
|
||||
let membersState = Promise<ChannelMemberListState>()
|
||||
let contactsState = Promise<ChannelMemberListState>()
|
||||
|
||||
|
||||
|
||||
|
||||
let disposableAndLoadMoreControl: (Disposable, PeerChannelMemberCategoryControl?)
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, updated: { state in
|
||||
disposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: searchQuery, updated: { state in
|
||||
membersState.set(.single(state))
|
||||
})
|
||||
|
||||
let contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: nil, updated: { state in
|
||||
let contactsDisposableAndLoadMoreControl = context.peerChannelMemberCategoriesContextsManager.contacts(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId, searchQuery: searchQuery, updated: { state in
|
||||
contactsState.set(.single(state))
|
||||
})
|
||||
|
||||
@ -585,6 +584,81 @@ public extension ShareWithPeersScreen {
|
||||
self.stateDisposable = combinedDisposable
|
||||
|
||||
self.listControl = disposableAndLoadMoreControl.1
|
||||
case let .channels(excludePeerIds):
|
||||
self.stateDisposable = (combineLatest(
|
||||
context.engine.messages.chatList(group: .root, count: 500) |> take(1),
|
||||
context.engine.data.get(EngineDataMap(Array(self.initialPeerIds).map(TelegramEngine.EngineData.Item.Peer.Peer.init)))
|
||||
)
|
||||
|> mapToSignal { chatList, initialPeers -> Signal<(EngineChatList, [EnginePeer.Id: Optional<EnginePeer>], [EnginePeer.Id: Optional<Int>]), NoError> in
|
||||
return context.engine.data.subscribe(
|
||||
EngineDataMap(chatList.items.map(\.renderedPeer.peerId).map(TelegramEngine.EngineData.Item.Peer.ParticipantCount.init))
|
||||
)
|
||||
|> map { participantCountMap -> (EngineChatList, [EnginePeer.Id: Optional<EnginePeer>], [EnginePeer.Id: Optional<Int>]) in
|
||||
return (chatList, initialPeers, participantCountMap)
|
||||
}
|
||||
}
|
||||
|> deliverOnMainQueue).start(next: { [weak self] chatList, initialPeers, participantCounts in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var participants: [EnginePeer.Id: Int] = [:]
|
||||
for (key, value) in participantCounts {
|
||||
if let value {
|
||||
participants[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
var existingIds = Set<EnginePeer.Id>()
|
||||
var selectedPeers: [EnginePeer] = []
|
||||
|
||||
for item in chatList.items.reversed() {
|
||||
if let peer = item.renderedPeer.peer {
|
||||
if self.initialPeerIds.contains(peer.id) {
|
||||
selectedPeers.append(peer)
|
||||
existingIds.insert(peer.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for peerId in self.initialPeerIds {
|
||||
if !existingIds.contains(peerId), let maybePeer = initialPeers[peerId], let peer = maybePeer {
|
||||
selectedPeers.append(peer)
|
||||
existingIds.insert(peerId)
|
||||
}
|
||||
}
|
||||
|
||||
var peers: [EnginePeer] = []
|
||||
peers = chatList.items.filter { peer in
|
||||
if let peer = peer.renderedPeer.peer {
|
||||
if excludePeerIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
if self.initialPeerIds.contains(peer.id) {
|
||||
return false
|
||||
}
|
||||
if case let .channel(channel) = peer, case .broadcast = channel.info {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}.reversed().compactMap { $0.renderedPeer.peer }
|
||||
for peer in peers {
|
||||
existingIds.insert(peer.id)
|
||||
}
|
||||
peers.insert(contentsOf: selectedPeers, at: 0)
|
||||
|
||||
let state = State(
|
||||
peers: peers,
|
||||
participants: participants
|
||||
)
|
||||
self.stateValue = state
|
||||
self.stateSubject.set(.single(state))
|
||||
|
||||
self.readySubject.set(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/TabSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/OptionButtonComponent",
|
||||
"//submodules/TelegramUI/Components/EmojiTextAttachmentView",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/AnimatedCountLabelNode",
|
||||
"//submodules/StickerResources",
|
||||
],
|
||||
|
@ -395,6 +395,9 @@ private final class StoryContainerScreenComponent: Component {
|
||||
|
||||
private var didDisplayReactionTooltip: Bool = false
|
||||
|
||||
private let interactionGuide = ComponentView<Empty>()
|
||||
private var isDisplayingInteractionGuide: Bool = true
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
||||
@ -890,6 +893,10 @@ private final class StoryContainerScreenComponent: Component {
|
||||
self.didAnimateIn = true
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
|
||||
if let view = self.interactionGuide.view as? StoryInteractionGuideComponent.View {
|
||||
view.animateIn(transitionIn: transitionIn)
|
||||
}
|
||||
} else {
|
||||
self.didAnimateIn = true
|
||||
self.state?.updated(transition: .immediate)
|
||||
@ -929,7 +936,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
self.isAnimatingOut = true
|
||||
|
||||
@ -1290,6 +1297,9 @@ private final class StoryContainerScreenComponent: Component {
|
||||
if self.pendingNavigationToItemId != nil {
|
||||
isProgressPaused = true
|
||||
}
|
||||
if self.isDisplayingInteractionGuide {
|
||||
isProgressPaused = true
|
||||
}
|
||||
|
||||
var contentDerivedBottomInset: CGFloat = environment.safeInsets.bottom
|
||||
|
||||
@ -1698,6 +1708,35 @@ private final class StoryContainerScreenComponent: Component {
|
||||
controller.presentationContext.containerLayoutUpdated(subLayout, transition: transition.containedViewLayoutTransition)
|
||||
}
|
||||
|
||||
if self.isDisplayingInteractionGuide {
|
||||
let _ = self.interactionGuide.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
StoryInteractionGuideComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
strings: environment.strings,
|
||||
action: { [weak self] in
|
||||
self?.isDisplayingInteractionGuide = false
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
if let view = self.interactionGuide.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.layer.zPosition = 1000.0
|
||||
view.frame = CGRect(origin: .zero, size: availableSize)
|
||||
}
|
||||
} else if let view = self.interactionGuide.view as? StoryInteractionGuideComponent.View, view.superview != nil {
|
||||
view.animateOut(completion: {
|
||||
view.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
333
submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryInteractionGuideComponent.swift
Normal file
333
submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryInteractionGuideComponent.swift
Normal file
@ -0,0 +1,333 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import LottieComponent
|
||||
|
||||
final class StoryInteractionGuideComponent: Component {
|
||||
let context: AccountContext
|
||||
let theme: PresentationTheme
|
||||
let strings: PresentationStrings
|
||||
let action: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
strings: PresentationStrings,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.strings = strings
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: StoryInteractionGuideComponent, rhs: StoryInteractionGuideComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: StoryInteractionGuideComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private let effectView: UIVisualEffectView
|
||||
private let containerView = UIView()
|
||||
private let titleLabel = ComponentView<Empty>()
|
||||
private let descriptionLabel = ComponentView<Empty>()
|
||||
private let guideItems = ComponentView<Empty>()
|
||||
private let proceedButton = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.effectView = UIVisualEffectView(effect: nil)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.effectView)
|
||||
self.addSubview(self.containerView)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleTap)))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func handleTap() {
|
||||
if let component = self.component {
|
||||
component.action()
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn(transitionIn: StoryContainerScreen.TransitionIn) {
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
self.effectView.effect = UIBlurEffect(style: .dark)
|
||||
}
|
||||
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.containerView.layer.animateScale(from: 0.85, to: 1.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.containerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.containerView.layer.animateScale(from: 1.0, to: 1.1, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
func update(component: StoryInteractionGuideComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let sideInset: CGFloat = 48.0
|
||||
|
||||
let titleSize = self.titleLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Watching Stories", font: Font.semibold(20.0), textColor: .white, paragraphAlignment: .center)))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - titleSize.width) / 2.0), y: 146.0), size: titleSize)
|
||||
if let view = self.titleLabel.view {
|
||||
if view.superview == nil {
|
||||
self.containerView.addSubview(view)
|
||||
}
|
||||
view.frame = titleFrame
|
||||
}
|
||||
|
||||
let textSize = self.descriptionLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(text: .plain(NSAttributedString(string: "You can use these gestures to control playback.", font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.6), paragraphAlignment: .center)), maximumNumberOfLines: 0, lineSpacing: 0.2)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - textSize.width) / 2.0), y: titleFrame.maxY + 7.0), size: textSize)
|
||||
if let view = self.descriptionLabel.view {
|
||||
if view.superview == nil {
|
||||
self.containerView.addSubview(view)
|
||||
}
|
||||
view.frame = textFrame
|
||||
}
|
||||
|
||||
let items: [AnyComponentWithIdentity<Empty>] = [
|
||||
AnyComponentWithIdentity(
|
||||
id: "forward",
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Go forward",
|
||||
text: "Tap the screen",
|
||||
animationName: "story_forward"
|
||||
)
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "pause",
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Pause and Seek",
|
||||
text: "Hold and move sideways",
|
||||
animationName: "story_pause"
|
||||
)
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "back",
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Go back",
|
||||
text: "Tap the left edge",
|
||||
animationName: "story_back"
|
||||
)
|
||||
)
|
||||
),
|
||||
AnyComponentWithIdentity(
|
||||
id: "move",
|
||||
component: AnyComponent(
|
||||
GuideItemComponent(
|
||||
context: component.context,
|
||||
title: "Move between stories",
|
||||
text: "Swipe left or right",
|
||||
animationName: "story_move"
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
let itemsSize = self.guideItems.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(List(items)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let itemsFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - itemsSize.width) / 2.0), y: textFrame.maxY + 40.0), size: itemsSize)
|
||||
if let view = self.guideItems.view {
|
||||
if view.superview == nil {
|
||||
self.containerView.addSubview(view)
|
||||
}
|
||||
view.frame = itemsFrame
|
||||
}
|
||||
|
||||
let buttonSize = self.proceedButton.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: "Tap to keep watching", font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center)))),
|
||||
action: { [weak self] in
|
||||
self?.handleTap()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - buttonSize.width) / 2.0), y: itemsFrame.maxY + 57.0), size: buttonSize)
|
||||
if let view = self.proceedButton.view {
|
||||
if view.superview == nil {
|
||||
self.containerView.addSubview(view)
|
||||
}
|
||||
view.frame = buttonFrame
|
||||
}
|
||||
|
||||
let bounds = CGRect(origin: .zero, size: availableSize)
|
||||
self.effectView.frame = bounds
|
||||
self.containerView.frame = bounds
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private final class GuideItemComponent: Component {
|
||||
let context: AccountContext
|
||||
let title: String
|
||||
let text: String
|
||||
let animationName: String
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
title: String,
|
||||
text: String,
|
||||
animationName: String
|
||||
) {
|
||||
self.context = context
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.animationName = animationName
|
||||
}
|
||||
|
||||
static func ==(lhs: GuideItemComponent, rhs: GuideItemComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.animationName != rhs.animationName {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private var component: GuideItemComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
private let animation = ComponentView<Empty>()
|
||||
private let titleLabel = ComponentView<Empty>()
|
||||
private let descriptionLabel = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: GuideItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
self.component = component
|
||||
|
||||
let sideInset: CGFloat = 48.0
|
||||
|
||||
let size = CGSize(width: availableSize.width, height: 90.0)
|
||||
let originX = availableSize.width / 2.0 - 110.0
|
||||
|
||||
let animationSize = self.animation.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: component.animationName),
|
||||
color: .white,
|
||||
placeholderColor: nil,
|
||||
startingPosition: .begin,
|
||||
size: CGSize(width: 60.0, height: 60.0),
|
||||
renderingScale: UIScreen.main.scale,
|
||||
loop: true
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let animationFrame = CGRect(origin: CGPoint(x: originX - 11.0, y: 15.0), size: animationSize)
|
||||
if let view = self.animation.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = animationFrame
|
||||
}
|
||||
|
||||
let titleSize = self.titleLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: component.title, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .natural)))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: originX + 60.0, y: 25.0), size: titleSize)
|
||||
if let view = self.titleLabel.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = titleFrame
|
||||
}
|
||||
|
||||
let textSize = self.descriptionLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(text: .plain(NSAttributedString(string: component.text, font: Font.regular(15.0), textColor: UIColor(rgb: 0xffffff, alpha: 0.6), paragraphAlignment: .natural)), maximumNumberOfLines: 0)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: availableSize.height)
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: originX + 60.0, y: titleFrame.maxY + 2.0), size: textSize)
|
||||
if let view = self.descriptionLabel.view {
|
||||
if view.superview == nil {
|
||||
self.addSubview(view)
|
||||
}
|
||||
view.frame = textFrame
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -1918,7 +1918,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
present(controller, mediaPickerContext)
|
||||
}
|
||||
|
||||
private func presentOldMediaPicker(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, fileMode: Bool, editingMedia: Bool, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) {
|
||||
private func presentOldMediaPicker(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, fileMode: Bool, editingMedia: Bool, push: @escaping (ViewController) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) {
|
||||
guard let component = view.component else {
|
||||
return
|
||||
}
|
||||
@ -2047,14 +2047,14 @@ final class StoryItemSetContainerSendMessage {
|
||||
}
|
||||
}
|
||||
view.endEditing(true)
|
||||
present(legacyController, LegacyAssetPickerContext(controller: controller))
|
||||
push(legacyController)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private func presentFileGallery(view: StoryItemSetContainerComponent.View, peer: EnginePeer, replyMessageId: EngineMessage.Id?, replyToStoryId: StoryId?, editingMessage: Bool = false) {
|
||||
self.presentOldMediaPicker(view: view, peer: peer, replyMessageId: replyMessageId, replyToStoryId: replyToStoryId, fileMode: true, editingMedia: editingMessage, present: { [weak view] c, _ in
|
||||
self.presentOldMediaPicker(view: view, peer: peer, replyMessageId: replyMessageId, replyToStoryId: replyToStoryId, fileMode: true, editingMedia: editingMessage, push: { [weak view] c in
|
||||
view?.component?.controller()?.push(c)
|
||||
}, completion: { [weak self, weak view] signals, silentPosting, scheduleTime in
|
||||
guard let self, let view else {
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Premium/AddBoosts.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/AddBoosts.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "addboosts_30.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
185
submodules/TelegramUI/Images.xcassets/Premium/AddBoosts.imageset/addboosts_30.pdf
vendored
Normal file
185
submodules/TelegramUI/Images.xcassets/Premium/AddBoosts.imageset/addboosts_30.pdf
vendored
Normal file
@ -0,0 +1,185 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 11.000000 0.696762 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
9.816118 24.143660 m
|
||||
9.159256 24.247375 l
|
||||
9.816118 24.143660 l
|
||||
h
|
||||
9.408499 17.298222 m
|
||||
10.472980 24.039946 l
|
||||
9.159256 24.247375 l
|
||||
8.094773 17.505651 l
|
||||
9.408499 17.298222 l
|
||||
h
|
||||
7.454219 25.239820 m
|
||||
-0.372424 13.800879 l
|
||||
0.725235 13.049850 l
|
||||
8.551878 24.488791 l
|
||||
7.454219 25.239820 l
|
||||
h
|
||||
1.001714 11.195682 m
|
||||
4.259832 11.195682 l
|
||||
4.259832 12.525682 l
|
||||
1.001714 12.525682 l
|
||||
1.001714 11.195682 l
|
||||
h
|
||||
4.590733 10.808434 m
|
||||
3.526251 4.066710 l
|
||||
4.839975 3.859280 l
|
||||
5.904458 10.601005 l
|
||||
4.590733 10.808434 l
|
||||
h
|
||||
6.545013 2.866837 m
|
||||
14.371655 14.305777 l
|
||||
13.273996 15.056806 l
|
||||
5.447354 3.617867 l
|
||||
6.545013 2.866837 l
|
||||
h
|
||||
12.997517 16.910973 m
|
||||
9.739399 16.910973 l
|
||||
9.739399 15.580973 l
|
||||
12.997517 15.580973 l
|
||||
12.997517 16.910973 l
|
||||
h
|
||||
14.371655 14.305777 m
|
||||
15.127728 15.410808 14.336444 16.910973 12.997517 16.910973 c
|
||||
12.997517 15.580973 l
|
||||
13.266914 15.580973 13.426117 15.279137 13.273996 15.056806 c
|
||||
14.371655 14.305777 l
|
||||
h
|
||||
3.526251 4.066710 m
|
||||
3.249377 2.313175 5.542558 1.401711 6.545013 2.866837 c
|
||||
5.447354 3.617867 l
|
||||
5.245656 3.323076 4.784269 3.506472 4.839975 3.859280 c
|
||||
3.526251 4.066710 l
|
||||
h
|
||||
4.259832 11.195682 m
|
||||
4.465684 11.195682 4.622838 11.011766 4.590733 10.808434 c
|
||||
5.904458 10.601005 l
|
||||
6.064025 11.611597 5.282945 12.525682 4.259832 12.525682 c
|
||||
4.259832 11.195682 l
|
||||
h
|
||||
-0.372424 13.800879 m
|
||||
-1.128497 12.695848 -0.337213 11.195682 1.001714 11.195682 c
|
||||
1.001714 12.525682 l
|
||||
0.732317 12.525682 0.573114 12.827518 0.725235 13.049850 c
|
||||
-0.372424 13.800879 l
|
||||
h
|
||||
10.472980 24.039946 m
|
||||
10.749854 25.793478 8.456675 26.704947 7.454219 25.239820 c
|
||||
8.551878 24.488791 l
|
||||
8.753575 24.783579 9.214963 24.600183 9.159256 24.247375 c
|
||||
10.472980 24.039946 l
|
||||
h
|
||||
8.094773 17.505651 m
|
||||
7.935206 16.495058 8.716287 15.580973 9.739399 15.580973 c
|
||||
9.739399 16.910973 l
|
||||
9.533547 16.910973 9.376393 17.094889 9.408499 17.298222 c
|
||||
8.094773 17.505651 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
0.000000 1.000000 -1.000000 0.000000 4.330002 8.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
0.665000 1.330002 m
|
||||
0.665000 1.697271 0.367269 1.995002 0.000000 1.995002 c
|
||||
-0.367269 1.995002 -0.665000 1.697271 -0.665000 1.330002 c
|
||||
0.665000 1.330002 l
|
||||
h
|
||||
-0.665000 -6.669998 m
|
||||
-0.665000 -7.037268 -0.367269 -7.334998 0.000000 -7.334998 c
|
||||
0.367269 -7.334998 0.665000 -7.037268 0.665000 -6.669998 c
|
||||
-0.665000 -6.669998 l
|
||||
h
|
||||
-0.665000 1.330002 m
|
||||
-0.665000 -6.669998 l
|
||||
0.665000 -6.669998 l
|
||||
0.665000 1.330002 l
|
||||
-0.665000 1.330002 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 6.500000 2.669998 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
1.165000 9.330002 m
|
||||
1.165000 9.697271 0.867269 9.995002 0.500000 9.995002 c
|
||||
0.132731 9.995002 -0.165000 9.697271 -0.165000 9.330002 c
|
||||
1.165000 9.330002 l
|
||||
h
|
||||
-0.165000 1.330002 m
|
||||
-0.165000 0.962732 0.132731 0.665002 0.500000 0.665002 c
|
||||
0.867269 0.665002 1.165000 0.962732 1.165000 1.330002 c
|
||||
-0.165000 1.330002 l
|
||||
h
|
||||
-0.165000 9.330002 m
|
||||
-0.165000 1.330002 l
|
||||
1.165000 1.330002 l
|
||||
1.165000 9.330002 l
|
||||
-0.165000 9.330002 l
|
||||
h
|
||||
f
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2982
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000003072 00000 n
|
||||
0000003095 00000 n
|
||||
0000003268 00000 n
|
||||
0000003342 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
3401
|
||||
%%EOF
|
12
submodules/TelegramUI/Images.xcassets/Premium/BoostButtonIcon.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/BoostButtonIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "Union.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
79
submodules/TelegramUI/Images.xcassets/Premium/BoostButtonIcon.imageset/Union.pdf
vendored
Normal file
79
submodules/TelegramUI/Images.xcassets/Premium/BoostButtonIcon.imageset/Union.pdf
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 0.000000 -0.388103 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
4.868845 7.330381 m
|
||||
4.598474 7.330381 4.392062 7.571941 4.434230 7.839005 c
|
||||
5.022154 11.562523 l
|
||||
5.095322 12.025919 4.489317 12.266791 4.224403 11.879610 c
|
||||
0.077619 5.818924 l
|
||||
-0.122184 5.526905 0.086922 5.130463 0.440753 5.130463 c
|
||||
2.264232 5.130463 l
|
||||
2.534604 5.130463 2.741015 4.888903 2.698848 4.621839 c
|
||||
2.110924 0.898320 l
|
||||
2.037756 0.434924 2.643760 0.194052 2.908674 0.581233 c
|
||||
7.055459 6.641919 l
|
||||
7.255262 6.933939 7.046156 7.330381 6.692324 7.330381 c
|
||||
4.868845 7.330381 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
610
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 7.133078 11.684636 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000000700 00000 n
|
||||
0000000722 00000 n
|
||||
0000000894 00000 n
|
||||
0000000968 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1027
|
||||
%%EOF
|
BIN
submodules/TelegramUI/Resources/Animations/story_back.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/story_back.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/story_forward.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/story_forward.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/story_move.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/story_move.tgs
Normal file
Binary file not shown.
BIN
submodules/TelegramUI/Resources/Animations/story_pause.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/story_pause.tgs
Normal file
Binary file not shown.
@ -76,6 +76,7 @@ import ChatSendMessageActionUI
|
||||
import ChatTextLinkEditUI
|
||||
import WebUI
|
||||
import PremiumUI
|
||||
import PremiumGiftAttachmentScreen
|
||||
import ImageTransparency
|
||||
import StickerPackPreviewUI
|
||||
import TextNodeWithEntities
|
||||
@ -747,6 +748,204 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .full:
|
||||
break
|
||||
}
|
||||
} else if let giveaway = media as? TelegramMediaGiveaway {
|
||||
//TODO:localize
|
||||
var peerName = ""
|
||||
if let peerId = giveaway.channelPeerIds.first, let peer = message.peers[peerId] {
|
||||
peerName = EnginePeer(peer).compactDisplayTitle
|
||||
}
|
||||
|
||||
var signal = strongSelf.context.engine.payments.premiumGiveawayInfo(peerId: message.id.peerId, messageId: message.id)
|
||||
let disposable: MetaDisposable
|
||||
if let current = strongSelf.bankCardDisposable {
|
||||
disposable = current
|
||||
} else {
|
||||
disposable = MetaDisposable()
|
||||
strongSelf.bankCardDisposable = disposable
|
||||
}
|
||||
|
||||
var cancelImpl: (() -> Void)?
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||
cancelImpl?()
|
||||
}))
|
||||
strongSelf.present(controller, in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
return ActionDisposable { [weak controller] in
|
||||
Queue.mainQueue().async() {
|
||||
controller?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|> runOn(Queue.mainQueue())
|
||||
|> delay(0.15, queue: Queue.mainQueue())
|
||||
let progressDisposable = progressSignal.startStrict()
|
||||
|
||||
signal = signal
|
||||
|> afterDisposed {
|
||||
Queue.mainQueue().async {
|
||||
progressDisposable.dispose()
|
||||
}
|
||||
}
|
||||
cancelImpl = {
|
||||
disposable.set(nil)
|
||||
}
|
||||
disposable.set((signal
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] info in
|
||||
if let strongSelf = self, let info = info {
|
||||
let date = stringForDate(timestamp: giveaway.untilDate, strings: strongSelf.presentationData.strings)
|
||||
let startDate = stringForDate(timestamp: message.timestamp, strings: strongSelf.presentationData.strings)
|
||||
|
||||
let title: String
|
||||
let text: String
|
||||
var warning: String?
|
||||
|
||||
var dismissImpl: (() -> Void)?
|
||||
|
||||
var actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
|
||||
switch info {
|
||||
case let .ongoing(status):
|
||||
title = "About This Giveaway"
|
||||
|
||||
|
||||
let intro: String
|
||||
if case .almostOver = status {
|
||||
intro = "The giveaway was sponsored by the admins of **\(peerName)**, who acquired **\(giveaway.quantity) Telegram Premium** subscriptions for **\(giveaway.months)** months for its followers."
|
||||
} else {
|
||||
intro = "The giveaway is sponsored by the admins of **\(peerName)**, who acquired **\(giveaway.quantity) Telegram Premium** subscriptions for **\(giveaway.months)** months for its followers."
|
||||
}
|
||||
|
||||
let ending: String
|
||||
if case .almostOver = status {
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random users that joined **\(peerName)** and other listed channels after **\(startDate)**."
|
||||
} else {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random users that joined **\(peerName)** after **\(startDate)**."
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random subscribers of **\(peerName)** and other listed channels."
|
||||
} else {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random subscribers of **\(peerName)**."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(date)**, Telegram will automatically select **\(giveaway.quantity)** random users that joined **\(peerName)** and **\(giveaway.channelPeerIds.count - 1)** other listed channels after **\(startDate)**."
|
||||
} else {
|
||||
ending = "On **\(date)**, Telegram will automatically select **\(giveaway.quantity)** random users that joined **\(peerName)** after **\(startDate)**."
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(date)**, Telegram will automatically select **\(giveaway.quantity)** random subscribers of **\(peerName)** and **\(giveaway.channelPeerIds.count - 1)** other listed channels."
|
||||
} else {
|
||||
ending = "On **\(date)**, Telegram will automatically select **\(giveaway.quantity)** random subscribers of **\(peerName)**."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var participation: String
|
||||
switch status {
|
||||
case .notQualified:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = "To take part in this giveaway please join the channel **\(peerName)** (**\(giveaway.channelPeerIds.count - 1)** other listed channels) before **\(date)**."
|
||||
} else {
|
||||
participation = "To take part in this giveaway please join the channel **\(peerName)** before **\(date)**."
|
||||
}
|
||||
case let .notAllowed(reason):
|
||||
switch reason {
|
||||
case let .joinedTooEarly(joinedOn):
|
||||
let joinDate = stringForDate(timestamp: joinedOn, strings: strongSelf.presentationData.strings)
|
||||
participation = "You are not eligible to participate in this giveaway, because you joined this channel on **\(joinDate)**, which is before the contest started."
|
||||
case let .channelAdmin(adminId):
|
||||
let _ = adminId
|
||||
participation = "You are not eligible to participate in this giveaway, because you are an admin of participating channel (**\(peerName)**)."
|
||||
}
|
||||
case .participating:
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participation = "You are participating in this giveaway, because you have joined the channel **\(peerName)** (**\(giveaway.channelPeerIds.count - 1)** other listed channels)."
|
||||
} else {
|
||||
participation = "You are participating in this giveaway, because you have joined the channel **\(peerName)**."
|
||||
}
|
||||
case .almostOver:
|
||||
participation = "The giveaway is over, preparing results."
|
||||
}
|
||||
|
||||
if !participation.isEmpty {
|
||||
participation = "\n\n\(participation)"
|
||||
}
|
||||
|
||||
text = "\(intro)\n\n\(ending)\(participation)"
|
||||
case let .finished(status, finishDate, _, activatedCount):
|
||||
let date = stringForDate(timestamp: finishDate, strings: strongSelf.presentationData.strings)
|
||||
title = "Giveaway Ended"
|
||||
|
||||
let intro = "The giveaway was sponsored by the admins of **\(peerName)**, who acquired **\(giveaway.quantity) Telegram Premium** subscriptions for **\(giveaway.months)** months for its followers."
|
||||
|
||||
var ending: String
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random users that joined **\(peerName)** and other listed channels after **\(startDate)**."
|
||||
} else {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random users that joined **\(peerName)** after **\(startDate)**."
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random subscribers of **\(peerName)** and other listed channels."
|
||||
} else {
|
||||
ending = "On **\(date)**, Telegram automatically selected **\(giveaway.quantity)** random subscribers of **\(peerName)**."
|
||||
}
|
||||
}
|
||||
|
||||
if activatedCount > 0 {
|
||||
ending += " \(activatedCount) of the winners already used their gift links."
|
||||
}
|
||||
|
||||
var result: String
|
||||
switch status {
|
||||
case .refunded:
|
||||
result = ""
|
||||
warning = "The channel cancelled the prizes by reversing the payment for them."
|
||||
actions = [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_Close, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
case .notWon:
|
||||
result = "\n\nYou didn't win a prize in this giveaway."
|
||||
case let .won(slug):
|
||||
result = "\n\nYou won a prize in this giveaway. 🏆"
|
||||
let _ = slug
|
||||
actions = [TextAlertAction(type: .defaultAction, title: "View My Prize", action: {
|
||||
dismissImpl?()
|
||||
}), TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {
|
||||
dismissImpl?()
|
||||
})]
|
||||
}
|
||||
|
||||
text = "\(intro)\n\n\(ending)\(result)"
|
||||
}
|
||||
|
||||
let alertController = giveawayInfoAlertController(
|
||||
context: strongSelf.context,
|
||||
updatedPresentationData: strongSelf.updatedPresentationData,
|
||||
title: title,
|
||||
text: text,
|
||||
warning: warning,
|
||||
actions: actions
|
||||
)
|
||||
strongSelf.present(alertController, in: .window(.root))
|
||||
|
||||
dismissImpl = { [weak alertController] in
|
||||
alertController?.dismissAnimated()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
return true
|
||||
} else if let action = media as? TelegramMediaAction {
|
||||
if !displayVoiceMessageDiscardAlert() {
|
||||
return false
|
||||
@ -4021,9 +4220,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
})
|
||||
} else {
|
||||
strongSelf.presentOldMediaPicker(fileMode: false, editingMedia: true, present: { [weak self] c, _ in
|
||||
self?.effectiveNavigationController?.pushViewController(c)
|
||||
}, completion: { signals, _, _ in
|
||||
strongSelf.presentOldMediaPicker(fileMode: false, editingMedia: true, completion: { signals, _, _ in
|
||||
self?.interfaceInteraction?.setupEditMessage(messageId, { _ in })
|
||||
self?.editMessageMediaWithLegacySignals(signals)
|
||||
})
|
||||
@ -13806,7 +14003,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
case .gift:
|
||||
let premiumGiftOptions = strongSelf.presentationInterfaceState.premiumGiftOptions
|
||||
if !premiumGiftOptions.isEmpty {
|
||||
let controller = PremiumGiftScreen(context: context, peerId: peer.id, options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in
|
||||
let controller = PremiumGiftAttachmentScreen(context: context, peerId: peer.id, options: premiumGiftOptions, source: .attachMenu, pushController: { [weak self] c in
|
||||
if let strongSelf = self {
|
||||
strongSelf.push(c)
|
||||
}
|
||||
@ -13985,9 +14182,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
let controller = legacyAttachmentMenu(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, chatLocation: strongSelf.chatLocation, editMediaOptions: menuEditMediaOptions, saveEditedPhotos: settings.storeEditedPhotos, allowGrouping: true, hasSchedule: strongSelf.presentationInterfaceState.subject != .scheduledMessages && peer.id.namespace != Namespaces.Peer.SecretChat, canSendPolls: canSendPolls, updatedPresentationData: strongSelf.updatedPresentationData, parentController: legacyController, recentlyUsedInlineBots: strongSelf.recentlyUsedInlineBotsValue, initialCaption: inputText, openGallery: {
|
||||
self?.presentOldMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, present: { [weak self] c, _ in
|
||||
self?.effectiveNavigationController?.pushViewController(c)
|
||||
}, completion: { signals, silentPosting, scheduleTime in
|
||||
self?.presentOldMediaPicker(fileMode: false, editingMedia: editMediaOptions != nil, completion: { signals, silentPosting, scheduleTime in
|
||||
if !inputText.string.isEmpty {
|
||||
strongSelf.clearInputText()
|
||||
}
|
||||
@ -14179,9 +14374,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
private func presentFileGallery(editingMessage: Bool = false) {
|
||||
self.presentOldMediaPicker(fileMode: true, editingMedia: editingMessage, present: { [weak self] c, _ in
|
||||
self?.effectiveNavigationController?.pushViewController(c)
|
||||
}, completion: { [weak self] signals, silentPosting, scheduleTime in
|
||||
self.presentOldMediaPicker(fileMode: true, editingMedia: editingMessage, completion: { [weak self] signals, silentPosting, scheduleTime in
|
||||
if editingMessage {
|
||||
self?.editMessageMediaWithLegacySignals(signals)
|
||||
} else {
|
||||
@ -14388,7 +14581,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
present(controller, mediaPickerContext)
|
||||
}
|
||||
|
||||
private func presentOldMediaPicker(fileMode: Bool, editingMedia: Bool, present: @escaping (AttachmentContainable, AttachmentMediaPickerContext) -> Void, completion: @escaping ([Any], Bool, Int32) -> Void) {
|
||||
private func presentOldMediaPicker(fileMode: Bool, editingMedia: Bool, completion: @escaping ([Any], Bool, Int32) -> Void) {
|
||||
let engine = self.context.engine
|
||||
let _ = (self.context.sharedContext.accountManager.transaction { transaction -> Signal<(GeneratedMediaStoreSettings, EngineConfiguration.SearchBots), NoError> in
|
||||
let entry = transaction.getSharedData(ApplicationSpecificSharedDataKeys.generatedMediaStoreSettings)?.get(GeneratedMediaStoreSettings.self)
|
||||
@ -14496,7 +14689,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
}
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
present(legacyController, LegacyAssetPickerContext(controller: controller))
|
||||
strongSelf.effectiveNavigationController?.pushViewController(legacyController)
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -17695,13 +17888,13 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
let dismissWebAppContollers: () -> Void = {
|
||||
if let currentWebAppController = strongSelf.currentWebAppController {
|
||||
strongSelf.currentWebAppController = nil
|
||||
currentWebAppController.dismiss(animated: true, completion: nil)
|
||||
} else if let currentWebAppController = strongSelf.currentMenuWebAppController {
|
||||
strongSelf.currentMenuWebAppController = nil
|
||||
currentWebAppController.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
// if let currentWebAppController = strongSelf.currentWebAppController {
|
||||
// strongSelf.currentWebAppController = nil
|
||||
// currentWebAppController.dismiss(animated: true, completion: nil)
|
||||
// } else if let currentWebAppController = strongSelf.currentMenuWebAppController {
|
||||
// strongSelf.currentMenuWebAppController = nil
|
||||
// currentWebAppController.dismiss(animated: true, completion: nil)
|
||||
// }
|
||||
}
|
||||
|
||||
switch navigation {
|
||||
@ -17751,7 +17944,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
commit()
|
||||
case let .withBotApp(botAppStart):
|
||||
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peer in
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self] peer in
|
||||
if let strongSelf = self, let peer {
|
||||
strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload, concealed: concealed, commit: {
|
||||
dismissWebAppContollers()
|
||||
|
@ -194,20 +194,21 @@ class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
months = monthsValue
|
||||
text = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string
|
||||
case let .giftCode(_, fromGiveaway, channelId, monthsValue):
|
||||
giftSize.width += 34.0
|
||||
giftSize.height += 84.0
|
||||
textSpacing += 13.0
|
||||
|
||||
title = "Congratulations!"
|
||||
var peerName = ""
|
||||
if let channelId, let channel = item.message.peers[channelId] {
|
||||
peerName = EnginePeer(channel).compactDisplayTitle
|
||||
}
|
||||
if fromGiveaway {
|
||||
giftSize.width += 34.0
|
||||
giftSize.height += 84.0
|
||||
textSpacing += 20.0
|
||||
|
||||
title = "Congratulations!"
|
||||
var peerName = ""
|
||||
if let channelId, let channel = item.message.peers[channelId] {
|
||||
peerName = EnginePeer(channel).compactDisplayTitle
|
||||
}
|
||||
text = "You won a prize in a giveaway organized by **\(peerName)**.\n\nYour prize is a **Telegram Premium** subscription for **\(monthsValue)** months."
|
||||
} else {
|
||||
text = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string
|
||||
text = "You've received a gift from **\(peerName)**.\n\nYour gift is a **Telegram Premium** subscription for **\(monthsValue)** months."
|
||||
}
|
||||
|
||||
months = monthsValue
|
||||
buttonTitle = "Open Gift Link"
|
||||
hasServiceMessage = false
|
||||
|
@ -14,6 +14,7 @@ import Markdown
|
||||
import ShimmerEffect
|
||||
import AnimatedStickerNode
|
||||
import TelegramAnimatedStickerNode
|
||||
import AvatarNode
|
||||
|
||||
private let titleFont = Font.medium(15.0)
|
||||
private let textFont = Font.regular(13.0)
|
||||
@ -34,9 +35,13 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private let dateTitleNode: TextNode
|
||||
private let dateTextNode: TextNode
|
||||
|
||||
private let badgeBackgroundNode: ASImageNode
|
||||
private let badgeTextNode: TextNode
|
||||
|
||||
private var giveaway: TelegramMediaGiveaway?
|
||||
|
||||
private let buttonNode: ChatMessageAttachedContentButtonNode
|
||||
private let channelButton: PeerButtonNode
|
||||
|
||||
override var visibility: ListViewItemNodeVisibility {
|
||||
didSet {
|
||||
@ -76,7 +81,13 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
self.dateTitleNode = TextNode()
|
||||
self.dateTextNode = TextNode()
|
||||
|
||||
self.badgeBackgroundNode = ASImageNode()
|
||||
self.badgeBackgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.badgeTextNode = TextNode()
|
||||
|
||||
self.buttonNode = ChatMessageAttachedContentButtonNode()
|
||||
self.channelButton = PeerButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
@ -87,7 +98,10 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
self.addSubnode(self.dateTitleNode)
|
||||
self.addSubnode(self.dateTextNode)
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.channelButton)
|
||||
self.addSubnode(self.animationNode)
|
||||
self.addSubnode(self.badgeBackgroundNode)
|
||||
self.addSubnode(self.badgeTextNode)
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
@ -145,9 +159,15 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
let makeDateTitleLayout = TextNode.asyncLayout(self.dateTitleNode)
|
||||
let makeDateTextLayout = TextNode.asyncLayout(self.dateTextNode)
|
||||
|
||||
|
||||
let makeBadgeTextLayout = TextNode.asyncLayout(self.badgeTextNode)
|
||||
|
||||
let makeButtonLayout = ChatMessageAttachedContentButtonNode.asyncLayout(self.buttonNode)
|
||||
|
||||
let makeChannelLayout = PeerButtonNode.asyncLayout(self.channelButton)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, layoutConstants, _, _, constrainedSize, _ in
|
||||
var giveaway: TelegramMediaGiveaway?
|
||||
for media in item.message.media {
|
||||
@ -156,13 +176,27 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
var themeUpdated = false
|
||||
if currentItem?.presentationData.theme.theme !== item.presentationData.theme.theme {
|
||||
themeUpdated = true
|
||||
}
|
||||
|
||||
var incoming = item.message.effectivelyIncoming(item.context.account.peerId)
|
||||
if case .forwardedMessages = item.associatedData.subject {
|
||||
incoming = false
|
||||
}
|
||||
|
||||
let textColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.primaryTextColor : item.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
let accentColor = incoming ? item.presentationData.theme.theme.chat.message.incoming.accentTextColor : item.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
|
||||
var updatedBadgeImage: UIImage?
|
||||
if themeUpdated {
|
||||
updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: accentColor, strokeColor: .white, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil)
|
||||
}
|
||||
|
||||
let badgeString = NSAttributedString(string: "X\(giveaway?.quantity ?? 1)", font: Font.with(size: 10.0, design: .round , weight: .bold, traits: .monospacedNumbers), textColor: .white)
|
||||
|
||||
//TODO:localize
|
||||
let prizeTitleString = NSAttributedString(string: "Giveaway Prizes", font: titleFont, textColor: textColor)
|
||||
var prizeTextString: NSAttributedString?
|
||||
if let giveaway {
|
||||
@ -177,19 +211,40 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
let participantsTitleString = NSAttributedString(string: "Participants", font: titleFont, textColor: textColor)
|
||||
let participantsTextString = NSAttributedString(string: "All subscribers of this channel:", font: textFont, textColor: textColor)
|
||||
let participantsText: String
|
||||
if let giveaway {
|
||||
if giveaway.flags.contains(.onlyNewSubscribers) {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participantsText = "All users who join the channels below after this date:"
|
||||
} else {
|
||||
participantsText = "All users who join this channel below after this date:"
|
||||
}
|
||||
} else {
|
||||
if giveaway.channelPeerIds.count > 1 {
|
||||
participantsText = "All subscribers of the channels below:"
|
||||
} else {
|
||||
participantsText = "All subscribers of this channel:"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
participantsText = ""
|
||||
}
|
||||
|
||||
let participantsTextString = NSAttributedString(string: participantsText, font: textFont, textColor: textColor)
|
||||
|
||||
let dateTitleString = NSAttributedString(string: "Winners Selection Date", font: titleFont, textColor: textColor)
|
||||
var dateTextString: NSAttributedString?
|
||||
if let giveaway {
|
||||
dateTextString = NSAttributedString(string: stringForFullDate(timestamp: giveaway.untilDate, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat), font: textFont, textColor: textColor)
|
||||
}
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none)
|
||||
|
||||
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
||||
let sideInsets = layoutConstants.text.bubbleInsets.right * 2.0
|
||||
|
||||
let maxTextWidth = min(200.0, max(1.0, constrainedSize.width - 7.0 - sideInsets))
|
||||
|
||||
let (badgeTextLayout, badgeTextApply) = makeBadgeTextLayout(TextNodeLayoutArguments(attributedString: badgeString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (prizeTitleLayout, prizeTitleApply) = makePrizeTitleLayout(TextNodeLayoutArguments(attributedString: prizeTitleString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let (prizeTextLayout, prizeTextApply) = makePrizeTextLayout(TextNodeLayoutArguments(attributedString: prizeTextString, backgroundColor: nil, maximumNumberOfLines: 5, truncationType: .end, constrainedSize: CGSize(width: maxTextWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
@ -291,7 +346,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
titleHighlightedColor = bubbleColors.fill[0]
|
||||
}
|
||||
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, false, "HOW DOES IT WORK?", titleColor, titleHighlightedColor, false)
|
||||
let (buttonWidth, continueLayout) = makeButtonLayout(constrainedSize.width, buttonImage, buttonHighlightedImage, nil, nil, false, "LEARN MORE", titleColor, titleHighlightedColor, false)
|
||||
|
||||
let months = giveaway?.months ?? 0
|
||||
let animationName: String
|
||||
@ -319,15 +374,26 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
maxContentWidth = max(maxContentWidth, buttonWidth)
|
||||
maxContentWidth += 30.0
|
||||
|
||||
var channelPeer: EnginePeer?
|
||||
if let channelPeerId = giveaway?.channelPeerIds.first, let peer = item.message.peers[channelPeerId] {
|
||||
channelPeer = EnginePeer(peer)
|
||||
}
|
||||
let (_, continueChannelLayout) = makeChannelLayout(item.context, maxContentWidth - 30.0, channelPeer, accentColor, accentColor.withAlphaComponent(0.1))
|
||||
|
||||
let contentWidth = maxContentWidth + layoutConstants.text.bubbleInsets.right * 2.0
|
||||
|
||||
return (contentWidth, { boundingWidth in
|
||||
let (buttonSize, buttonApply) = continueLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
|
||||
let buttonSpacing: CGFloat = 4.0
|
||||
|
||||
let (channelButtonSize, channelButtonApply) = continueChannelLayout(boundingWidth - layoutConstants.text.bubbleInsets.right * 2.0)
|
||||
|
||||
let statusSizeAndApply = statusSuggestedWidthAndContinue?.1(boundingWidth - sideInsets)
|
||||
|
||||
var layoutSize = CGSize(width: contentWidth, height: 49.0 + prizeTitleLayout.size.height + prizeTextLayout.size.height + participantsTitleLayout.size.height + participantsTextLayout.size.height + dateTitleLayout.size.height + dateTextLayout.size.height + buttonSize.height + buttonSpacing + 100.0)
|
||||
var layoutSize = CGSize(width: contentWidth, height: 49.0 + prizeTitleLayout.size.height + prizeTextLayout.size.height + participantsTitleLayout.size.height + participantsTextLayout.size.height + dateTitleLayout.size.height + dateTextLayout.size.height + buttonSize.height + buttonSpacing + 120.0)
|
||||
|
||||
layoutSize.height += channelButtonSize.height
|
||||
|
||||
if let statusSizeAndApply = statusSizeAndApply {
|
||||
layoutSize.height += statusSizeAndApply.0.height - 4.0
|
||||
}
|
||||
@ -344,6 +410,8 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
|
||||
strongSelf.updateVisibility()
|
||||
|
||||
let _ = badgeTextApply()
|
||||
|
||||
let _ = prizeTitleApply()
|
||||
let _ = prizeTextApply()
|
||||
|
||||
@ -353,6 +421,7 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let _ = dateTitleApply()
|
||||
let _ = dateTextApply()
|
||||
|
||||
let _ = channelButtonApply()
|
||||
let _ = buttonApply()
|
||||
|
||||
let smallSpacing: CGFloat = 2.0
|
||||
@ -361,10 +430,18 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
var originY: CGFloat = 0.0
|
||||
|
||||
let iconSize = CGSize(width: 140.0, height: 140.0)
|
||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - iconSize.width) / 2.0), y: originY - 50.0), size: iconSize)
|
||||
strongSelf.animationNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - iconSize.width) / 2.0), y: originY - 40.0), size: iconSize)
|
||||
strongSelf.animationNode.updateLayout(size: iconSize)
|
||||
originY += 95.0
|
||||
|
||||
let badgeTextFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - badgeTextLayout.size.width) / 2.0) + 1.0, y: originY + 88.0), size: badgeTextLayout.size)
|
||||
strongSelf.badgeTextNode.frame = badgeTextFrame
|
||||
strongSelf.badgeBackgroundNode.frame = badgeTextFrame.insetBy(dx: -6.0, dy: -5.0).offsetBy(dx: -1.0, dy: 0.0)
|
||||
if let updatedBadgeImage {
|
||||
strongSelf.badgeBackgroundNode.image = updatedBadgeImage
|
||||
}
|
||||
|
||||
originY += 112.0
|
||||
|
||||
strongSelf.prizeTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - prizeTitleLayout.size.width) / 2.0), y: originY), size: prizeTitleLayout.size)
|
||||
originY += prizeTitleLayout.size.height + smallSpacing
|
||||
strongSelf.prizeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - prizeTextLayout.size.width) / 2.0), y: originY), size: prizeTextLayout.size)
|
||||
@ -373,7 +450,10 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
strongSelf.participantsTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTitleLayout.size.width) / 2.0), y: originY), size: participantsTitleLayout.size)
|
||||
originY += participantsTitleLayout.size.height + smallSpacing
|
||||
strongSelf.participantsTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - participantsTextLayout.size.width) / 2.0), y: originY), size: participantsTextLayout.size)
|
||||
originY += participantsTextLayout.size.height + largeSpacing
|
||||
originY += participantsTextLayout.size.height + smallSpacing * 2.0 + 3.0
|
||||
|
||||
strongSelf.channelButton.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - channelButtonSize.width) / 2.0), y: originY), size: channelButtonSize)
|
||||
originY += channelButtonSize.height + largeSpacing
|
||||
|
||||
strongSelf.dateTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((layoutSize.width - dateTitleLayout.size.width) / 2.0), y: originY), size: dateTitleLayout.size)
|
||||
originY += dateTitleLayout.size.height + smallSpacing
|
||||
@ -416,47 +496,6 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
}
|
||||
|
||||
private func updateVisibility() {
|
||||
// guard let item = self.item else {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let isPlaying = self.visibilityStatus == true
|
||||
// if self.isPlaying != isPlaying {
|
||||
// self.isPlaying = isPlaying
|
||||
// self.animationNode.visibility = isPlaying
|
||||
// }
|
||||
//
|
||||
// if isPlaying && self.setupTimestamp == nil {
|
||||
// self.setupTimestamp = CACurrentMediaTime()
|
||||
// }
|
||||
//
|
||||
// if isPlaying {
|
||||
// var alreadySeen = true
|
||||
//
|
||||
// if item.message.flags.contains(.Incoming) {
|
||||
// if let unreadRange = item.controllerInteraction.unreadMessageRange[UnreadMessageRangeKey(peerId: item.message.id.peerId, namespace: item.message.id.namespace)] {
|
||||
// if unreadRange.contains(item.message.id.id) {
|
||||
// alreadySeen = false
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// if item.controllerInteraction.playNextOutgoingGift && !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||
// alreadySeen = false
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if !item.controllerInteraction.seenOneTimeAnimatedMedia.contains(item.message.id) {
|
||||
// item.controllerInteraction.seenOneTimeAnimatedMedia.insert(item.message.id)
|
||||
// self.animationNode.playOnce()
|
||||
// }
|
||||
//
|
||||
// if !alreadySeen && self.animationNode.isPlaying {
|
||||
// item.controllerInteraction.playNextOutgoingGift = false
|
||||
// Queue.mainQueue().after(1.0) {
|
||||
// item.controllerInteraction.animateDiceSuccess(false, true)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
@ -516,3 +555,102 @@ class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
private final class PeerButtonNode: HighlightTrackingButtonNode {
|
||||
private let backgroundNode: ASImageNode
|
||||
private let textNode: TextNode
|
||||
private let avatarNode: AvatarNode
|
||||
|
||||
var currentBackgroundColor: UIColor?
|
||||
var pressed: (() -> Void)?
|
||||
|
||||
init() {
|
||||
self.textNode = TextNode()
|
||||
self.textNode.isUserInteractionEnabled = false
|
||||
|
||||
self.backgroundNode = ASImageNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 14.0))
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.backgroundNode)
|
||||
self.addSubnode(self.textNode)
|
||||
self.addSubnode(self.avatarNode)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.alpha = 1.0
|
||||
strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
self.pressed?()
|
||||
}
|
||||
|
||||
static func asyncLayout(_ current: PeerButtonNode?) -> (_ context: AccountContext, _ width: CGFloat, _ peer: EnginePeer?, _ titleColor: UIColor, _ backgroundColor: UIColor) -> (CGFloat, (CGFloat) -> (CGSize, () -> PeerButtonNode)) {
|
||||
let maybeMakeTextLayout = (current?.textNode).flatMap(TextNode.asyncLayout)
|
||||
|
||||
return { context, width, peer, titleColor, backgroundColor in
|
||||
let targetNode: PeerButtonNode
|
||||
if let current = current {
|
||||
targetNode = current
|
||||
} else {
|
||||
targetNode = PeerButtonNode()
|
||||
}
|
||||
|
||||
let makeTextLayout: (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode)
|
||||
if let maybeMakeTextLayout = maybeMakeTextLayout {
|
||||
makeTextLayout = maybeMakeTextLayout
|
||||
} else {
|
||||
makeTextLayout = TextNode.asyncLayout(targetNode.textNode)
|
||||
}
|
||||
|
||||
let inset: CGFloat = 1.0
|
||||
let avatarSize = CGSize(width: 22.0, height: 22.0)
|
||||
let spacing: CGFloat = 5.0
|
||||
|
||||
let (textSize, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: peer?.compactDisplayTitle ?? "", font: Font.medium(14.0), textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: max(1.0, width - avatarSize.width - (spacing + inset) * 2.0), height: CGFloat.greatestFiniteMagnitude), alignment: .left, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let refinedWidth = avatarSize.width + textSize.size.width + (spacing + inset) * 2.0
|
||||
return (refinedWidth, { _ in
|
||||
return (CGSize(width: refinedWidth, height: 24.0), {
|
||||
let _ = textApply()
|
||||
|
||||
let backgroundFrame = CGRect(origin: .zero, size: CGSize(width: refinedWidth, height: 24.0))
|
||||
let textFrame = CGRect(origin: CGPoint(x: inset + avatarSize.width + spacing, y: floorToScreenPixels((backgroundFrame.height - textSize.size.height) / 2.0)), size: textSize.size)
|
||||
targetNode.backgroundNode.frame = backgroundFrame
|
||||
|
||||
if targetNode.currentBackgroundColor != backgroundColor {
|
||||
targetNode.currentBackgroundColor = backgroundColor
|
||||
targetNode.backgroundNode.image = generateStretchableFilledCircleImage(radius: 12.0, color: backgroundColor, backgroundColor: nil)
|
||||
}
|
||||
|
||||
targetNode.avatarNode.setPeer(
|
||||
context: context,
|
||||
theme: context.sharedContext.currentPresentationData.with({ $0 }).theme,
|
||||
peer: peer,
|
||||
synchronousLoad: false
|
||||
)
|
||||
targetNode.avatarNode.frame = CGRect(origin: CGPoint(x: inset, y: inset), size: avatarSize)
|
||||
|
||||
targetNode.textNode.frame = textFrame
|
||||
|
||||
return targetNode
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
246
submodules/TelegramUI/Sources/GiveawayInfoAlertController.swift
Normal file
246
submodules/TelegramUI/Sources/GiveawayInfoAlertController.swift
Normal file
@ -0,0 +1,246 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import AlertUI
|
||||
import PresentationDataUtils
|
||||
import Markdown
|
||||
|
||||
private final class GiveawayInfoAlertContentNode: AlertContentNode {
|
||||
private let title: String
|
||||
private let text: String
|
||||
private let warning: String?
|
||||
|
||||
private let titleNode: ASTextNode
|
||||
private let textNode: ASTextNode
|
||||
fileprivate let warningBackgroundNode: ASImageNode
|
||||
fileprivate let warningTextNode: ImmediateTextNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
public var theme: PresentationTheme
|
||||
|
||||
public override var dismissOnOutsideTap: Bool {
|
||||
return self.isUserInteractionEnabled
|
||||
}
|
||||
|
||||
public init(theme: AlertControllerTheme, ptheme: PresentationTheme, title: String, text: String, warning: String?, actions: [TextAlertAction]) {
|
||||
self.theme = ptheme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.warning = warning
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.titleNode.maximumNumberOfLines = 0
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.maximumNumberOfLines = 0
|
||||
|
||||
self.warningBackgroundNode = ASImageNode()
|
||||
self.warningBackgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.warningTextNode = ImmediateTextNode()
|
||||
self.warningTextNode.maximumNumberOfLines = 0
|
||||
self.warningTextNode.lineSpacing = 0.1
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(theme: theme, action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.warningBackgroundNode)
|
||||
self.addSubnode(self.warningTextNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
|
||||
self.updateTheme(theme)
|
||||
}
|
||||
|
||||
public override func updateTheme(_ theme: AlertControllerTheme) {
|
||||
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
|
||||
|
||||
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor)
|
||||
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor)
|
||||
let attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||
|
||||
self.textNode.attributedText = attributedText
|
||||
|
||||
self.warningTextNode.attributedText = NSAttributedString(string: self.warning ?? "", font: Font.semibold(13.0), textColor: theme.destructiveColor, paragraphAlignment: .center)
|
||||
self.warningBackgroundNode.image = generateStretchableFilledCircleImage(radius: 5.0, color: theme.destructiveColor.withAlphaComponent(0.1))
|
||||
|
||||
self.actionNodesSeparator.backgroundColor = theme.separatorColor
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.updateTheme(theme)
|
||||
}
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
separatorNode.backgroundColor = theme.separatorColor
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
_ = self.updateLayout(size: size, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
public override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
var size = size
|
||||
size.width = min(size.width, 270.0)
|
||||
let measureSize = CGSize(width: size.width - 16.0 * 2.0, height: CGFloat.greatestFiniteMagnitude)
|
||||
|
||||
self.validLayout = size
|
||||
|
||||
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
|
||||
|
||||
let titleSize = self.titleNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
|
||||
origin.y += titleSize.height + 4.0
|
||||
|
||||
let textSize = self.textNode.measure(measureSize)
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
|
||||
origin.y += textSize.height + 6.0
|
||||
|
||||
let actionButtonHeight: CGFloat = 44.0
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
|
||||
var effectiveActionLayout = TextAlertContentActionLayout.horizontal
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
|
||||
if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
case .vertical:
|
||||
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
|
||||
}
|
||||
}
|
||||
if "".isEmpty {
|
||||
effectiveActionLayout = .vertical
|
||||
}
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
var contentWidth = max(titleSize.width, minActionsWidth)
|
||||
contentWidth = max(contentWidth, 234.0)
|
||||
|
||||
var actionsHeight: CGFloat = 0.0
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionsHeight = actionButtonHeight
|
||||
case .vertical:
|
||||
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
|
||||
}
|
||||
|
||||
let resultWidth = contentWidth + insets.left + insets.right
|
||||
|
||||
var warningHeight: CGFloat = 0.0
|
||||
if let _ = self.warning {
|
||||
let warningSize = self.warningTextNode.updateLayout(measureSize)
|
||||
let warningFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - warningSize.width) / 2.0), y: origin.y + 20.0), size: warningSize)
|
||||
transition.updateFrame(node: self.warningTextNode, frame: warningFrame)
|
||||
|
||||
transition.updateFrame(node: self.warningBackgroundNode, frame: warningFrame.insetBy(dx: -8.0, dy: -8.0))
|
||||
|
||||
warningHeight += warningSize.height + 26.0
|
||||
}
|
||||
|
||||
let resultSize = CGSize(width: resultWidth, height: titleSize.height + textSize.height + 8.0 + actionsHeight + warningHeight + insets.top + insets.bottom)
|
||||
|
||||
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
case .vertical:
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
|
||||
}
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
case .vertical:
|
||||
currentActionWidth = resultSize.width
|
||||
}
|
||||
|
||||
let actionNodeFrame: CGRect
|
||||
switch effectiveActionLayout {
|
||||
case .horizontal:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += currentActionWidth
|
||||
case .vertical:
|
||||
actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
|
||||
actionOffset += actionButtonHeight
|
||||
}
|
||||
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
func giveawayInfoAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, title: String, text: String, warning: String?, actions: [TextAlertAction]) -> AlertController {
|
||||
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let contentNode = GiveawayInfoAlertContentNode(theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, title: title, text: text, warning: warning, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
let presentationDataDisposable = (updatedPresentationData?.signal ?? context.sharedContext.presentationData).start(next: { [weak controller] presentationData in
|
||||
controller?.theme = AlertControllerTheme(presentationData: presentationData)
|
||||
})
|
||||
controller.dismissed = { _ in
|
||||
presentationDataDisposable.dispose()
|
||||
}
|
||||
|
||||
return controller
|
||||
}
|
@ -961,11 +961,50 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
|
||||
|> deliverOnMainQueue).startStandalone(next: { giftCode in
|
||||
if let giftCode {
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = PremiumGiftCodeScreen(context: context, giftCode: giftCode, forceDark: forceDark, action: {
|
||||
dismissImpl?()
|
||||
let controller = PremiumGiftCodeScreen(
|
||||
context: context,
|
||||
giftCode: giftCode,
|
||||
forceDark: forceDark,
|
||||
action: {
|
||||
dismissImpl?()
|
||||
|
||||
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
|
||||
})
|
||||
let _ = context.engine.payments.applyPremiumGiftCode(slug: slug).startStandalone()
|
||||
},
|
||||
openPeer: { peer in
|
||||
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
|
||||
},
|
||||
openMessage: { messageId in
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
||||
|> deliverOnMainQueue).startStandalone(next: { peer in
|
||||
guard let peer else {
|
||||
return
|
||||
}
|
||||
openPeer(peer, .chat(textInputState: nil, subject: .message(id: .id(messageId), highlight: true, timecode: nil), peekData: nil))
|
||||
})
|
||||
},
|
||||
shareLink: { link in
|
||||
let messages: [EnqueueMessage] = [.message(text: link, attributes: [], inlineStickers: [:], mediaReference: nil, replyToMessageId: nil, replyToStoryId: nil, localGroupingKey: nil, correlationId: nil, bubbleUpEmojiOrStickersets: [])]
|
||||
|
||||
let peerSelectionController = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled], multipleSelection: false, selectForumThreads: true))
|
||||
peerSelectionController.peerSelected = { [weak peerSelectionController] peer, threadId in
|
||||
if let _ = peerSelectionController {
|
||||
Queue.mainQueue().after(0.88) {
|
||||
HapticFeedback().success()
|
||||
}
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
(navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: peer.id == context.account.peerId ? presentationData.strings.GiftLink_LinkSharedToSavedMessages : presentationData.strings.GiftLink_LinkSharedToChat(peer.compactDisplayTitle).string), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .window(.root))
|
||||
|
||||
let _ = (enqueueMessages(account: context.account, peerId: peer.id, messages: messages)
|
||||
|> deliverOnMainQueue).startStandalone()
|
||||
if let peerSelectionController = peerSelectionController {
|
||||
peerSelectionController.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
navigationController?.pushViewController(peerSelectionController)
|
||||
}
|
||||
)
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismiss()
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import PeerInfoStoryGridScreen
|
||||
import TelegramAccountAuxiliaryMethods
|
||||
import PeerSelectionController
|
||||
import LegacyMessageInputPanel
|
||||
import StatisticsUI
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1807,6 +1808,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public func makeInstalledStickerPacksController(context: AccountContext, mode: InstalledStickerPacksControllerMode, forceTheme: PresentationTheme?) -> ViewController {
|
||||
return installedStickerPacksController(context: context, mode: mode, forceTheme: forceTheme)
|
||||
}
|
||||
|
||||
public func makeChannelStatsController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, peerId: EnginePeer.Id, boosts: Bool, boostStatus: ChannelBoostStatus?, statsDatacenterId: Int32) -> ViewController {
|
||||
return channelStatsController(context: context, updatedPresentationData: updatedPresentationData, peerId: peerId, section: boosts ? .boosts : .stats, boostStatus: nil, statsDatacenterId: statsDatacenterId)
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
|
||||
|
@ -90,9 +90,11 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n
|
||||
controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in
|
||||
openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData))
|
||||
}, parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root))
|
||||
case .boost:
|
||||
case .boost, .chatFolder:
|
||||
if let navigationController = controller.navigationController as? NavigationController {
|
||||
openResolvedUrlImpl(result, context: context, urlContext: peerId.flatMap { .chat(peerId: $0, updatedPresentationData: nil) } ?? .generic, navigationController: navigationController, forceExternal: false, openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, joinVoiceChat: nil, present: { c, a in }, dismissInput: {}, contentContext: nil)
|
||||
openResolvedUrlImpl(result, context: context, urlContext: peerId.flatMap { .chat(peerId: $0, updatedPresentationData: nil) } ?? .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigateToPeer in
|
||||
openResolvedPeerImpl(peer, navigateToPeer)
|
||||
}, sendFile: nil, sendSticker: nil, joinVoiceChat: nil, present: { c, a in }, dismissInput: {}, contentContext: nil)
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -338,7 +338,7 @@ func presentLegacyWebSearchGallery(context: AccountContext, peer: EnginePeer?, t
|
||||
|
||||
let (items, focusItem) = galleryItems(account: context.account, results: results, current: current, selectionContext: selectionContext, editingContext: editingContext)
|
||||
|
||||
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: false, allowCaptionEntities: true, hasTimer: false, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: false, hasCamera: false, recipientName: recipientName)!
|
||||
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: false, allowCaptionEntities: true, hasTimer: false, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: false, hasCamera: false, recipientName: recipientName, isScheduledMessages: false)!
|
||||
model.stickersContext = paintStickersContext
|
||||
controller.model = model
|
||||
model.controller = controller
|
||||
|
@ -225,12 +225,9 @@ public struct WebAppParameters {
|
||||
}
|
||||
|
||||
public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) -> [String: Any] {
|
||||
var backgroundColor = presentationTheme.list.plainBackgroundColor.rgb
|
||||
var secondaryBackgroundColor = presentationTheme.list.blocksBackgroundColor.rgb
|
||||
if presentationTheme.list.blocksBackgroundColor.rgb == presentationTheme.list.plainBackgroundColor.rgb {
|
||||
backgroundColor = presentationTheme.list.modalPlainBackgroundColor.rgb
|
||||
secondaryBackgroundColor = presentationTheme.list.plainBackgroundColor.rgb
|
||||
}
|
||||
let backgroundColor = presentationTheme.list.plainBackgroundColor.rgb
|
||||
let secondaryBackgroundColor = presentationTheme.list.blocksBackgroundColor.rgb
|
||||
|
||||
return [
|
||||
"bg_color": Int32(bitPattern: backgroundColor),
|
||||
"secondary_bg_color": Int32(bitPattern: secondaryBackgroundColor),
|
||||
@ -238,7 +235,13 @@ public func generateWebAppThemeParams(_ presentationTheme: PresentationTheme) ->
|
||||
"hint_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb),
|
||||
"link_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb),
|
||||
"button_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.fillColor.rgb),
|
||||
"button_text_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.foregroundColor.rgb)
|
||||
"button_text_color": Int32(bitPattern: presentationTheme.list.itemCheckColors.foregroundColor.rgb),
|
||||
"header_bg_color": Int32(bitPattern: presentationTheme.rootController.navigationBar.opaqueBackgroundColor.rgb),
|
||||
"accent_text_color": Int32(bitPattern: presentationTheme.list.itemAccentColor.rgb),
|
||||
"section_bg_color": Int32(bitPattern: presentationTheme.list.itemBlocksBackgroundColor.rgb),
|
||||
"section_header_text_color": Int32(bitPattern: presentationTheme.list.freeTextColor.rgb),
|
||||
"subtitle_text_color": Int32(bitPattern: presentationTheme.list.itemSecondaryTextColor.rgb),
|
||||
"destructive_text_color": Int32(bitPattern: presentationTheme.list.itemDestructiveColor.rgb)
|
||||
]
|
||||
}
|
||||
|
||||
@ -295,13 +298,13 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.topOverscrollNode = ASDisplayNode()
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
if self.presentationData.theme.list.plainBackgroundColor.rgb == 0x000000 {
|
||||
self.backgroundColor = self.presentationData.theme.list.itemBlocksBackgroundColor
|
||||
} else {
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
}
|
||||
|
||||
|
||||
let webView = WebAppWebView()
|
||||
webView.alpha = 0.0
|
||||
webView.navigationDelegate = self
|
||||
@ -768,7 +771,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
case "web_app_open_tg_link":
|
||||
if let json = json, let path = json["path_full"] as? String {
|
||||
controller.openUrl("https://t.me\(path)", false, { [weak controller] in
|
||||
controller?.dismiss()
|
||||
let _ = controller
|
||||
// controller?.dismiss()
|
||||
})
|
||||
}
|
||||
case "web_app_open_invoice":
|
||||
@ -990,6 +994,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
}
|
||||
self.invokeCustomMethod(requestId: requestId, method: method, params: paramsString ?? "{}")
|
||||
}
|
||||
case "web_app_setup_settings_button":
|
||||
if let json = json, let isVisible = json["is_visible"] as? Bool {
|
||||
self.controller?.hasSettings = isVisible
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -1009,12 +1017,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
let color: UIColor?
|
||||
var primaryTextColor: UIColor?
|
||||
var secondaryTextColor: UIColor?
|
||||
var backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
var secondaryBackgroundColor = self.presentationData.theme.list.blocksBackgroundColor
|
||||
if self.presentationData.theme.list.blocksBackgroundColor.rgb == self.presentationData.theme.list.plainBackgroundColor.rgb {
|
||||
backgroundColor = self.presentationData.theme.list.modalPlainBackgroundColor
|
||||
secondaryBackgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
}
|
||||
let backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
let secondaryBackgroundColor = self.presentationData.theme.list.blocksBackgroundColor
|
||||
if let headerColor = self.headerColor {
|
||||
color = headerColor
|
||||
let textColor = headerColor.lightness > 0.5 ? UIColor(rgb: 0x000000) : UIColor(rgb: 0xffffff)
|
||||
@ -1342,6 +1346,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
fileprivate let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
|
||||
private var presentationDataDisposable: Disposable?
|
||||
|
||||
private var hasSettings = false
|
||||
|
||||
public var openUrl: (String, Bool, @escaping () -> Void) -> Void = { _, _, _ in }
|
||||
public var getNavigationController: () -> NavigationController? = { return nil }
|
||||
public var completion: () -> Void = {}
|
||||
@ -1363,7 +1369,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.threadId = threadId
|
||||
|
||||
self.updatedPresentationData = updatedPresentationData
|
||||
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
|
||||
let updatedTheme = presentationData.theme.withModalBlocksBackground()
|
||||
presentationData = presentationData.withUpdated(theme: updatedTheme)
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings)
|
||||
|
||||
@ -1399,6 +1409,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
let updatedTheme = presentationData.theme.withModalBlocksBackground()
|
||||
let presentationData = presentationData.withUpdated(theme: updatedTheme)
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
strongSelf.updateNavigationBarTheme(transition: .immediate)
|
||||
@ -1469,11 +1481,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
|
||||
let peerId = self.peerId
|
||||
let botId = self.botId
|
||||
let url = self.url
|
||||
let forceHasSettings = self.forceHasSettings
|
||||
|
||||
let source = self.source
|
||||
|
||||
let hasSettings = self.hasSettings
|
||||
|
||||
let items = context.engine.messages.attachMenuBots()
|
||||
|> take(1)
|
||||
|> map { [weak self] attachMenuBots -> ContextController.Items in
|
||||
@ -1481,16 +1493,15 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
|
||||
let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId && !$0.flags.contains(.notActivated) })
|
||||
|
||||
let hasSettings: Bool
|
||||
if url == nil {
|
||||
if forceHasSettings {
|
||||
hasSettings = true
|
||||
} else {
|
||||
hasSettings = attachMenuBot?.flags.contains(.hasSettings) == true
|
||||
}
|
||||
} else {
|
||||
hasSettings = forceHasSettings
|
||||
}
|
||||
// if url == nil {
|
||||
// if forceHasSettings {
|
||||
// hasSettings = true
|
||||
// } else {
|
||||
// hasSettings = attachMenuBot?.flags.contains(.hasSettings) == true
|
||||
// }
|
||||
// } else {
|
||||
// hasSettings = forceHasSettings
|
||||
// }
|
||||
|
||||
if hasSettings {
|
||||
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in
|
||||
|
@ -88,9 +88,9 @@ final class WebAppWebView: WKWebView {
|
||||
return point.x > 30.0
|
||||
}
|
||||
self.allowsBackForwardNavigationGestures = false
|
||||
// if #available(iOS 16.4, *) {
|
||||
// self.isInspectable = true
|
||||
// }
|
||||
if #available(iOS 16.4, *) {
|
||||
self.isInspectable = true
|
||||
}
|
||||
|
||||
handleScriptMessageImpl = { [weak self] message in
|
||||
if let strongSelf = self {
|
||||
|
Loading…
x
Reference in New Issue
Block a user