# Conflicts:
#	submodules/TelegramUI/Components/LiquidLens/Sources/LiquidLensView.swift
#	submodules/TelegramUI/Components/TabBarComponent/Sources/TabBarComponent.swift
This commit is contained in:
Isaac 2025-12-20 03:05:26 +08:00
commit 4c6b273fe1
147 changed files with 8916 additions and 4310 deletions

View file

@ -1 +1 @@
c796824aa8245ce7309426caa3c4816024abd71d21f3805988aa953a4e826169
c796824aa8245ce7309426caa3c4816024abd71d21f3805988aa953a4e826168

View file

@ -15459,6 +15459,8 @@ Error: %8$@";
"Notification.StarsGiftOffer.Expired" = "%1$@ didn't respond to your offer for %2$@ within %3$@ your %4$@ have been refunded";
"Notification.StarsGiftOffer.Expired.Stars_1" = "%@ Star";
"Notification.StarsGiftOffer.Expired.Stars_any" = "%@ Stars";
"Notification.StarsGiftOffer.Expired.Hours_1" = "%@ hours";
"Notification.StarsGiftOffer.Expired.Hours_any" = "%@ hours";
"Notification.StarsGiftOffer.ExpiredYou" = "The offer from %1$@ to buy your %2$@ for %3$@ has expired";
"Notification.StarsGiftOffer.ExpiredYou.Stars_1" = "%@ Star";
"Notification.StarsGiftOffer.ExpiredYou.Stars_any" = "%@ Stars";
@ -15623,3 +15625,6 @@ Error: %8$@";
"Chat.GiftPurchaseOffer.AcceptConfirmation.BadValue" = "The value of this gift is **%@** higher than the offer.";
"Chat.GiftPurchaseOffer.AcceptConfirmation.Confirm" = "Confirm Sale";
"Notification.PremiumGift.DaysTitle_1" = "%@ Day Premium";
"Notification.PremiumGift.DaysTitle_any" = "%@ Days Premium";

View file

@ -1409,8 +1409,8 @@ public protocol SharedAccountContext: AnyObject {
func makeGiftAuctionBidScreen(context: AccountContext, toPeerId: EnginePeer.Id, text: String?, entities: [MessageTextEntity]?, hideName: Bool, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?) -> ViewController
func makeGiftAuctionViewScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> ViewController
func makeGiftOfferScreen(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
func makeGiftUpgradeVariantsPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]) -> ViewController
func makeGiftOfferScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, gift: StarGift.UniqueGift, peer: EnginePeer, amount: CurrencyAmount, commit: @escaping () -> Void) -> ViewController
func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController
func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController
func makeGiftDemoScreen(context: AccountContext) -> ViewController
func makeStorySharingScreen(context: AccountContext, subject: StorySharingSubject, parentController: ViewController) -> ViewController

View file

@ -44,6 +44,8 @@ swift_library(
"//submodules/TelegramUI/Components/MinimizedContainer",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/LiquidLens",
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
],
visibility = [
"//visibility:public",

View file

@ -24,6 +24,8 @@ import LegacyMessageInputPanelInputView
import ReactionSelectionNode
import TopMessageReactions
import GlassBackgroundComponent
import LiquidLens
import TabSelectionRecognizer
private let legacyButtonSize = CGSize(width: 88.0, height: 49.0)
private let glassButtonSize = CGSize(width: 72.0, height: 62.0)
@ -911,13 +913,21 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
private let containerNode: ASDisplayNode
private var backgroundView: GlassBackgroundView?
private var liquidLensView: LiquidLensView?
private let backgroundNode: NavigationBackgroundNode
private let scrollNode: ASScrollNode
private let separatorNode: ASDisplayNode
private let selectionNode: ASImageNode
private var buttonViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var itemsContainer = UIView()
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var selectedItemsContainer = UIView()
private var selectedItemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var itemSizes: [AnyHashable: CGSize] = [:]
private var tabSelectionRecognizer: TabSelectionRecognizer?
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
private var textInputPanelNode: AttachmentTextInputPanelNode?
private var progressNode: LoadingProgressNode?
@ -987,7 +997,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.mainButtonNode = MainButtonNode()
self.secondaryButtonNode = MainButtonNode()
super.init()
self.addSubnode(self.containerNode)
@ -995,7 +1005,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
switch style {
case .glass:
self.scrollNode.cornerRadius = glassButtonSize.height * 0.5
self.scrollNode.addSubnode(self.selectionNode)
case .legacy:
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.separatorNode)
@ -1419,6 +1428,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
self.view.accessibilityTraits = .tabBar
}
func requestLayout(transition: ContainedViewLayoutTransition) {
if let layout = self.validLayout {
let _ = self.update(layout: layout, buttons: self.buttons, isSelecting: self.isSelecting, selectionCount: self.selectionCount, elevateProgress: self.elevateProgress, transition: transition)
}
}
@objc private func mainButtonPressed() {
self.onMainButtonPressed()
}
@ -1473,6 +1488,88 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
}
}
private func item(at point: CGPoint) -> AnyHashable? {
let contentOffset = self.scrollNode.view.contentOffset.x
let point = point.offsetBy(dx: contentOffset, dy: 0.0)
var closestItem: (AnyHashable, CGFloat)?
for (id, itemView) in self.itemViews {
if itemView.frame.contains(point) {
return id
} else {
let distance = abs(point.x - itemView.center.x)
if let closestItemValue = closestItem {
if closestItemValue.1 > distance {
closestItem = (id, distance)
}
} else {
closestItem = (id, distance)
}
}
}
return closestItem?.0
}
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
guard let liquidLensView = self.liquidLensView else {
return
}
let location = recognizer.location(in: liquidLensView.contentView)
switch recognizer.state {
case .began:
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
let startX = itemView.frame.minX - 4.0
self.selectionGestureState = (startX, startX, itemId)
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
case .changed:
if var selectionGestureState = self.selectionGestureState {
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self.view).x
if let itemId = self.item(at: location) {
selectionGestureState.itemId = itemId
}
self.selectionGestureState = selectionGestureState
self.requestLayout(transition: .immediate)
}
case .ended, .cancelled:
if let selectionGestureState = self.selectionGestureState {
self.selectionGestureState = nil
if case .ended = recognizer.state {
guard let index = self.buttons.firstIndex(where: { AnyHashable($0.key) == selectionGestureState.itemId }) else {
return
}
let button = self.buttons[index]
if self.selectionChanged(button) {
self.selectedIndex = index
if self.buttons.count > 5, let button = self.itemViews[button.key] {
let transition = ComponentTransition.spring(duration: 0.4)
let scrollView = self.scrollNode.view
let targetRect = button.frame.insetBy(dx: -35.0, dy: 0.0)
var newBounds = scrollView.bounds
if targetRect.minX < scrollView.bounds.minX {
newBounds.origin.x = targetRect.minX
}
else if targetRect.maxX > scrollView.bounds.maxX {
newBounds.origin.x = targetRect.maxX - scrollView.bounds.width
}
let minX = 0.0
let maxX = scrollView.contentSize.width - scrollView.bounds.width
newBounds.origin.x = max(minX, min(newBounds.origin.x, maxX))
transition.setBounds(view: scrollView, bounds: newBounds)
self.updateItemContainers(contentOffset: newBounds.minX, transition: transition)
}
}
}
self.requestLayout(transition: .animated(duration: 0.4, curve: .spring))
}
default:
break
}
}
func updateViews(transition: ComponentTransition) {
guard let layout = self.validLayout else {
return
@ -1486,18 +1583,16 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
case .legacy:
panelSideInset = 3.0
}
let visibleRect = self.scrollNode.bounds.insetBy(dx: -180.0, dy: 0.0)
var distanceBetweenNodes = floorToScreenPixels((width - panelSideInset * 2.0 - self.buttonSize.width) / CGFloat(max(1, self.buttons.count - 1)))
let internalWidth = distanceBetweenNodes * CGFloat(self.buttons.count - 1)
var buttonWidth = self.buttonSize.width
var leftNodeOriginX: CGFloat
var maxButtonsToFit = 6
var maxButtonsToFit = 5
switch self.panelStyle {
case .glass:
leftNodeOriginX = layout.safeInsets.left + 3.0 + buttonWidth / 2.0
if layout.size.width < 400.0 {
if layout.size.width < 420.0 {
maxButtonsToFit = 5
}
case .legacy:
@ -1521,25 +1616,28 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
var selectionFrame = CGRect()
var mostRightX = 0.0
for i in 0 ..< self.buttons.count {
let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let originX = floorToScreenPixels(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - buttonWidth / 2.0)
let buttonFrame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: CGSize(width: buttonWidth, height: self.buttonSize.height))
mostRightX = buttonFrame.maxX
if !visibleRect.intersects(buttonFrame) {
continue
}
let type = self.buttons[i]
let _ = validIds.insert(type.key)
var buttonTransition = transition
let buttonView: ComponentHostView<Empty>
if let current = self.buttonViews[type.key] {
let selectedButtonView: ComponentHostView<Empty>
if let current = self.itemViews[type.key], let currentSelected = self.selectedItemViews[type.key] {
buttonView = current
selectedButtonView = currentSelected
} else {
buttonTransition = .immediate
buttonView = ComponentHostView<Empty>()
self.buttonViews[type.key] = buttonView
self.scrollNode.view.addSubview(buttonView)
self.itemViews[type.key] = buttonView
self.itemsContainer.addSubview(buttonView)
selectedButtonView = ComponentHostView<Empty>()
self.selectedItemViews[type.key] = selectedButtonView
self.selectedItemsContainer.addSubview(selectedButtonView)
}
if case let .app(bot) = type {
@ -1578,7 +1676,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isSelected: i == self.selectedIndex,
isSelected: false,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
action: { [weak self] in
@ -1587,7 +1685,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
strongSelf.selectedIndex = i
strongSelf.updateViews(transition: .init(animation: .curve(duration: 0.2, curve: .spring)))
if strongSelf.buttons.count > 5, let button = strongSelf.buttonViews[type.key] {
if strongSelf.buttons.count > 5, let button = strongSelf.itemViews[type.key] {
strongSelf.scrollNode.view.scrollRectToVisible(button.frame.insetBy(dx: -35.0, dy: 0.0), animated: true)
}
}
@ -1601,11 +1699,32 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
environment: {},
containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
)
self.itemSizes[type.key] = actualButtonSize
let _ = selectedButtonView.update(
transition: buttonTransition,
component: AnyComponent(AttachButtonComponent(
context: self.context,
style: self.panelStyle == .glass ? .glass : .legacy,
type: type,
isFirstOrLast: i == 0 || i == self.buttons.count - 1,
isSelected: true,
strings: self.presentationData.strings,
theme: self.presentationData.theme,
action: {
},
longPressAction: {
})
),
environment: {},
containerSize: CGSize(width: buttonWidth, height: self.buttonSize.height)
)
if i == self.selectedIndex {
selectionFrame = CGRect(origin: CGPoint(x: buttonFrame.midX - actualButtonSize.width * 0.5, y: buttonFrame.minY), size: actualButtonSize)
}
buttonTransition.setFrame(view: buttonView, frame: buttonFrame)
buttonTransition.setFrame(view: selectedButtonView, frame: buttonFrame)
var accessibilityTitle = ""
switch type {
case .gallery:
@ -1634,14 +1753,14 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
buttonView.accessibilityTraits = [.button]
}
var removeIds: [AnyHashable] = []
for (id, itemView) in self.buttonViews {
for (id, itemView) in self.itemViews {
if !validIds.contains(id) {
removeIds.append(id)
itemView.removeFromSuperview()
}
}
for id in removeIds {
self.buttonViews.removeValue(forKey: id)
self.itemViews.removeValue(forKey: id)
}
selectionFrame = selectionFrame.insetBy(dx: 1.0, dy: 4.0)
@ -1916,31 +2035,54 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
textPanelWidth = layout.size.width - panelSideInset * 2.0
}
self.updateViews(transition: .immediate)
var selectionFrame = CGRect()
if self.selectedIndex >= 0 && self.selectedIndex < self.buttons.count, let itemView = self.itemViews[self.buttons[self.selectedIndex].key], let itemSize = self.itemSizes[self.buttons[self.selectedIndex].key] {
let contentOffset = self.scrollNode.view.contentOffset.x
selectionFrame = CGRect(origin: CGPoint(x: itemView.center.x - itemSize.width / 2.0 - contentOffset, y: itemView.frame.minY), size: itemSize).insetBy(dx: -3.0, dy: 0.0)
}
let lensSelection: (x: CGFloat, width: CGFloat)
if let selectionGestureState = self.selectionGestureState {
lensSelection = (selectionGestureState.currentX, selectionFrame.width)
} else {
lensSelection = (selectionFrame.minX, selectionFrame.width)
}
let glassPanelHeight: CGFloat = 62.0
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: self.buttonSize.height + insets.bottom))
if case .glass = self.panelStyle {
let backgroundView: GlassBackgroundView
if let current = self.backgroundView {
backgroundView = current
let liquidLensView: LiquidLensView
if let current = self.liquidLensView {
liquidLensView = current
} else {
backgroundView = GlassBackgroundView()
self.containerNode.view.addSubview(backgroundView)
self.containerNode.view.addSubview(self.scrollNode.view)
self.backgroundView = backgroundView
liquidLensView = LiquidLensView(useBackgroundContainer: false)
self.containerNode.view.addSubview(liquidLensView)
//self.containerNode.view.addSubview(self.scrollNode.view)
self.liquidLensView = liquidLensView
liquidLensView.contentView.addSubview(self.itemsContainer)
liquidLensView.selectedContentView.addSubview(self.selectedItemsContainer)
self.itemsContainer.clipsToBounds = true
self.itemsContainer.layer.cornerRadius = glassPanelHeight * 0.5
self.selectedItemsContainer.clipsToBounds = true
self.selectedItemsContainer.layer.cornerRadius = glassPanelHeight * 0.5
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
self.tabSelectionRecognizer = tabSelectionRecognizer
liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
}
let panelSize = CGSize(width: isSelecting ? textPanelWidth : layout.size.width - layout.safeInsets.left - layout.safeInsets.right - panelSideInset * 2.0, height: isSelecting ? textPanelHeight - 11.0 : glassPanelHeight)
let backgroundViewColor: UIColor
if self.presentationData.theme.overallDarkAppearance {
backgroundViewColor = self.presentationData.theme.list.modalBlocksBackgroundColor.withAlphaComponent(0.55)
} else {
backgroundViewColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.75)
}
let backgroundOriginX: CGFloat = isSelecting ? panelSideInset : floorToScreenPixels((layout.size.width - panelSize.width) / 2.0)
backgroundView.update(size: panelSize, cornerRadius: isSelecting ? 20.0 : glassPanelHeight * 0.5, isDark: self.presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .custom, color: backgroundViewColor), transition: ComponentTransition(transition))
transition.updatePosition(layer: backgroundView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
transition.updateBounds(layer: backgroundView.layer, bounds: CGRect(origin: .zero, size: panelSize))
liquidLensView.update(size: panelSize, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: panelSize.height), isDark: self.presentationData.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: isSelecting, transition: ComponentTransition(transition))
transition.updatePosition(layer: liquidLensView.layer, position: CGPoint(x: backgroundOriginX + panelSize.width * 0.5, y: panelSize.height * 0.5))
transition.updateBounds(layer: liquidLensView.layer, bounds: CGRect(origin: .zero, size: panelSize))
transition.updateFrame(view: self.itemsContainer, frame: CGRect(origin: .zero, size: panelSize))
transition.updateFrame(view: self.selectedItemsContainer, frame: CGRect(origin: .zero, size: panelSize))
}
var containerTransition: ContainedViewLayoutTransition
@ -2002,8 +2144,15 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
let alphaTransition = ContainedViewLayoutTransition.animated(duration: isSelecting ? 0.1 : 0.25, curve: .easeInOut)
alphaTransition.updateAlpha(node: self.scrollNode, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
containerTransition.updateTransformScale(node: self.scrollNode, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
if let backgroundView = self.backgroundView {
containerTransition.updateTransformScale(layer: backgroundView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0)
alphaTransition.updateAlpha(layer: self.itemsContainer.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
containerTransition.updateTransformScale(layer: self.itemsContainer.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
alphaTransition.updateAlpha(layer: self.selectedItemsContainer.layer, alpha: isSelecting || isAnyButtonVisible ? 0.0 : 1.0)
containerTransition.updateTransformScale(layer: self.selectedItemsContainer.layer, scale: isSelecting || isAnyButtonVisible ? 0.85 : 1.0)
if let liquidLensView = self.liquidLensView {
containerTransition.updateTransformScale(layer: liquidLensView.layer, scale: isAnyButtonVisible ? 0.85 : 1.0)
}
if isSelectingUpdated {
@ -2022,7 +2171,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 1.0)
let componentTransition = ComponentTransition.easeInOut(duration: 0.25)
componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 0.0, toRadius: 10.0)
componentTransition.animateBlur(layer: self.itemsContainer.layer, fromRadius: 0.0, toRadius: 10.0)
let blurTransition = ComponentTransition.easeInOut(duration: 0.18)
transition.animateTransformScale(node: textInputPanelNode.opaqueActionButtons, from: 0.01)
@ -2054,7 +2203,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
alphaTransition.updateAlpha(node: textInputPanelNode, alpha: 0.0)
let componentTransition = ComponentTransition.easeInOut(duration: 0.25)
componentTransition.animateBlur(layer: self.scrollNode.layer, fromRadius: 10.0, toRadius: 0.0)
componentTransition.animateBlur(layer: self.itemsContainer.layer, fromRadius: 10.0, toRadius: 0.0)
let blurTransition = ComponentTransition.easeInOut(duration: 0.18)
transition.animateTransformScale(layer: textInputPanelNode.opaqueActionButtons.layer, from: CGPoint(x: 1.0, y: 1.0), to: CGPoint(x: 0.01, y: 0.01))
@ -2086,8 +2235,6 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrameAsPositionAndBounds(node: self.scrollNode, frame: CGRect(origin: CGPoint(x: self.isSelecting ? panelSideInset - defaultPanelSideInset : panelSideInset, y: self.isSelecting ? -11.0 : 0.0), size: CGSize(width: layout.size.width - panelSideInset * 2.0, height: self.buttonSize.height)))
}
self.updateViews(transition: .immediate)
if let progress = self.loadingProgress {
let loadingProgressNode: LoadingProgressNode
if let current = self.progressNode {
@ -2183,8 +2330,13 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
return containerFrame.height
}
func updateItemContainers(contentOffset: CGFloat, transition: ComponentTransition) {
let transform = CATransform3DMakeTranslation(-contentOffset, 0.0, 0.0)
transition.setSublayerTransform(view: self.itemsContainer, transform: transform)
transition.setSublayerTransform(view: self.selectedItemsContainer, transform: transform)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
self.updateItemContainers(contentOffset: scrollView.contentOffset.x, transition: .immediate)
self.updateViews(transition: .immediate)
}
}

View file

@ -50,6 +50,7 @@ swift_library(
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/Utils/DeviceModel",
"//submodules/LegacyMediaPickerUI",
"//submodules/TelegramUI/Components/AlertComponent",
],
visibility = [
"//visibility:public",

View file

@ -23,6 +23,7 @@ import SaveProgressScreen
import DeviceModel
import LegacyMediaPickerUI
import PassKit
import AlertComponent
private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
private final class PendingTask {
@ -1258,12 +1259,20 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var completed = false
let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: message, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
if !completed {
completed = true
completionHandler()
}
})])
let alertController = AlertScreen(
context: self.context,
title: nil,
text: message,
actions: [
.init(title: presentationData.strings.Common_OK, type: .default, action: {
if !completed {
completed = true
completionHandler()
}
})
]
)
alertController.dismissed = { byOutsideTap in
if byOutsideTap {
if !completed {
@ -1278,17 +1287,26 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var completed = false
let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: message, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
if !completed {
completed = true
completionHandler(false)
}
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
if !completed {
completed = true
completionHandler(true)
}
})])
let alertController = AlertScreen(
context: self.context,
title: nil,
text: message,
actions: [
.init(title: presentationData.strings.Common_Cancel, action: {
if !completed {
completed = true
completionHandler(false)
}
}),
.init(title: presentationData.strings.Common_OK, type: .default, action: {
if !completed {
completed = true
completionHandler(true)
}
})
]
)
alertController.dismissed = { byOutsideTap in
if byOutsideTap {
if !completed {
@ -1356,17 +1374,25 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
private func presentDownloadConfirmation(fileName: String, proceed: @escaping (Bool) -> Void) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
var completed = false
let alertController = textAlertController(context: self.context, updatedPresentationData: nil, title: nil, text: presentationData.strings.WebBrowser_Download_Confirmation(fileName).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
if !completed {
completed = true
proceed(false)
}
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebBrowser_Download_Download, action: {
if !completed {
completed = true
proceed(true)
}
})])
let alertController = AlertScreen(
context: self.context,
title: nil,
text: presentationData.strings.WebBrowser_Download_Confirmation(fileName).string,
actions: [
.init(title: presentationData.strings.Common_Cancel, action: {
if !completed {
completed = true
proceed(false)
}
}),
.init(title: presentationData.strings.WebBrowser_Download_Download, type: .default, action: {
if !completed {
completed = true
proceed(true)
}
})
]
)
alertController.dismissed = { byOutsideTap in
if byOutsideTap {
if !completed {

View file

@ -4535,7 +4535,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
)
interaction.present(alertController, nil)
dismissImpl = { [weak alertController] in
alertController?.dismissAnimated()
alertController?.dismiss()
}
},
isChannelsTabExpanded: recentItems.isChannelsTabExpanded,

View file

@ -43,6 +43,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
public let handleSpoilers: Bool
public let manualVisibilityControl: Bool
public let resetAnimationsOnVisibilityChange: Bool
public let enableLooping: Bool
public let displaysAsynchronously: Bool
public let maxWidth: CGFloat?
public let highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)?
@ -71,6 +72,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
handleSpoilers: Bool = false,
manualVisibilityControl: Bool = false,
resetAnimationsOnVisibilityChange: Bool = false,
enableLooping: Bool = true,
displaysAsynchronously: Bool = true,
maxWidth: CGFloat? = nil,
highlightAction: (([NSAttributedString.Key: Any]) -> NSAttributedString.Key?)? = nil,
@ -99,6 +101,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.handleSpoilers = handleSpoilers
self.manualVisibilityControl = manualVisibilityControl
self.resetAnimationsOnVisibilityChange = resetAnimationsOnVisibilityChange
self.enableLooping = enableLooping
self.displaysAsynchronously = displaysAsynchronously
self.maxWidth = maxWidth
self.tapAction = tapAction
@ -142,6 +145,9 @@ public final class MultilineTextWithEntitiesComponent: Component {
if lhs.resetAnimationsOnVisibilityChange != rhs.resetAnimationsOnVisibilityChange {
return false
}
if lhs.enableLooping != rhs.enableLooping {
return false
}
if lhs.displaysAsynchronously != rhs.displaysAsynchronously {
return false
}
@ -250,6 +256,7 @@ public final class MultilineTextWithEntitiesComponent: Component {
self.textNode.tapAttributeAction = component.tapAction
self.textNode.longTapAttributeAction = component.longTapAction
self.textNode.spoilerColor = component.spoilerColor
self.textNode.enableLooping = component.enableLooping
self.textNode.resetEmojiToFirstFrameAutomatically = component.resetAnimationsOnVisibilityChange

View file

@ -974,8 +974,9 @@ public final class PagerComponent<ChildEnvironmentType: Equatable, TopPanelEnvir
transition.setFrame(view: contentView.tintMaskContainer, frame: contentFrame)
}
let _ = bottomPanelHeight
if let contentViewWithBackground = contentView.view.componentView as? PagerContentViewWithBackground {
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.width, height: availableSize.height)), topPanelHeight: topPanelHeight, bottomPanelHeight: bottomPanelHeight, externalTintMaskContainer: component.externalTintMaskContainer == nil ? nil : contentView.tintMaskContainer, transition: contentTransition)
contentViewWithBackground.pagerUpdateBackground(backgroundFrame: CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.width, height: availableSize.height)), topPanelHeight: topPanelHeight, bottomPanelHeight: 0.0, externalTintMaskContainer: component.externalTintMaskContainer == nil ? nil : contentView.tintMaskContainer, transition: contentTransition)
}
}
}

View file

@ -38,7 +38,6 @@ public final class SheetComponentEnvironment: Equatable {
}
}
public let sheetComponentTag = GenericComponentViewTag()
public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: Component {
public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment)

View file

@ -9,11 +9,7 @@ public class KeyShortcutsController: UIResponder {
private var viewControllerEnumerator: (@escaping (ContainableController) -> Bool) -> Void
public static var isAvailable: Bool {
if #available(iOSApplicationExtension 8.0, iOS 8.0, *), UIDevice.current.userInterfaceIdiom == .pad {
return true
} else {
return false
}
return true
}
public init(enumerator: @escaping (@escaping (ContainableController) -> Bool) -> Void) {

View file

@ -403,8 +403,8 @@ public class ItemListPermanentInviteLinkItemNode: ListViewItemNode, ItemListItem
}
let fieldHeight: CGFloat = 52.0
let fieldSpacing: CGFloat = 16.0
let buttonHeight: CGFloat = 50.0
let fieldSpacing: CGFloat = 10.0
let buttonHeight: CGFloat = 52.0
let justCreatedCallSeparatorSpacing: CGFloat = 16.0
let justCreatedCallTextSpacing: CGFloat = 45.0

View file

@ -57,6 +57,7 @@ swift_library(
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/AlertComponent",
],
visibility = [
"//visibility:public",

View file

@ -33,6 +33,7 @@ import ComponentFlow
import BundleIconComponent
import LottieComponent
import GlassBarButtonComponent
import AlertComponent
final class MediaPickerInteraction {
let downloadManager: AssetDownloadManager
@ -2717,16 +2718,22 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
} else {
text = self.presentationData.strings.Attachment_CancelSelectionAlertText
}
let controller = textAlertController(context: self.context, title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertNo, action: {
}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, action: { [weak self] in
self?.dismissAllTooltips()
completion()
})])
controller.dismissed = { [weak self] _ in
let alertController = AlertScreen(
context: self.context,
title: nil,
text: text,
actions: [
.init(title: self.presentationData.strings.Attachment_CancelSelectionAlertNo),
.init(title: self.presentationData.strings.Attachment_CancelSelectionAlertYes, type: .default, action: { [weak self] in
self?.dismissAllTooltips()
completion()
}),
]
)
alertController.dismissed = { [weak self] _ in
self?.isDismissing = false
}
self.present(controller, in: .window(.root))
self.present(alertController, in: .window(.root))
} else {
completion()
}

View file

@ -123,6 +123,8 @@ swift_library(
"//submodules/TelegramUI/Components/Premium/PremiumCoinComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/Gifts/TableComponent",
"//submodules/TelegramUI/Components/Gifts/PeerTableCellComponent",
],
visibility = [
"//visibility:public",

View file

@ -493,7 +493,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
}
})
case let .starsMore(theme, title):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesItemList.downArrowImage(theme), title: title, sectionId: self.section, editing: false, action: {
arguments.expandStars()
})
case let .starsInfo(_, text):
@ -521,14 +521,14 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
if case let .channel(channel) = peer, case .group = channel.info {
isGroup = true
}
return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
return ItemListPeerItem(presentationData: presentationData, systemStyle: .glass, dateTimeFormat: PresentationDateTimeFormat(), nameDisplayOrder: presentationData.nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: boosts.flatMap { .text(isGroup ? presentationData.strings.BoostGift_GroupBoosts($0) : presentationData.strings.BoostGift_ChannelsBoosts($0), .secondary) } ?? .none, label: .none, editing: ItemListPeerItemEditing(editable: boosts == nil, editing: false, revealed: isRevealed), switchValue: nil, enabled: true, selectable: peer.id != arguments.context.account.peerId, sectionId: self.section, action: {
}, setPeerIdWithRevealedOptions: { lhs, rhs in
arguments.setItemIdWithRevealedOptions(lhs, rhs)
}, removePeer: { id in
arguments.removeChannel(id)
})
case let .channelAdd(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.roundPlusIconImage(theme), title: text, alwaysPlain: false, hasSeparator: true, sectionId: self.section, height: .compactPeerList, color: .accent, editing: false, action: {
return ItemListPeerActionItem(presentationData: presentationData, systemStyle: .glass, 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):
@ -582,7 +582,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
arguments.openPremiumIntro()
})
case let .prizeDescription(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.showPrizeDescription = value
@ -590,7 +590,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
}
})
case let .prizeDescriptionText(_, placeholder, value, count):
return ItemListSingleLineInputItem(presentationData: presentationData, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, maxLength: 128, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in
return ItemListSingleLineInputItem(presentationData: presentationData, systemStyle: .glass, title: NSAttributedString(string: "\(count)"), text: value, placeholder: placeholder, returnKeyType: .done, spacing: 24.0, maxLength: 128, tag: CreateGiveawayEntryTag.description, sectionId: self.section, textUpdated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.prizeDescription = value
@ -616,7 +616,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
} else {
text = presentationData.strings.InviteLink_Create_TimeLimitExpiryDateNever
}
return ItemListDisclosureItem(presentationData: presentationData, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, title: presentationData.strings.BoostGift_DateEnds, label: text, labelStyle: active ? .coloredText(theme.list.itemAccentColor) : .text, sectionId: self.section, style: .blocks, disclosureStyle: .none, action: {
arguments.dismissInput()
var focus = false
arguments.updateState { state in
@ -677,7 +677,7 @@ private enum CreateGiveawayEntry: ItemListNodeEntry {
case let .timeInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .winners(_, text, value):
return ItemListSwitchItem(presentationData: presentationData, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: text, value: value, sectionId: self.section, style: .blocks, updated: { value in
arguments.updateState { state in
var updatedState = state
updatedState.showWinners = value

View file

@ -23,6 +23,8 @@ import InvisibleInkDustNode
import PremiumStarComponent
import GlassBarButtonComponent
import ButtonComponent
import TableComponent
import PeerTableCellComponent
private final class PremiumGiftCodeSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -326,7 +328,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
title: strings.GiftLink_From,
component: AnyComponent(
Button(
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: fromPeer)),
content: AnyComponent(PeerTableCellComponent(context: context.component.context, theme: theme, strings: strings, peer: fromPeer)),
action: {
if let peer = fromPeer, peer.id != accountContext.account.peerId {
component.openPeer(peer)
@ -344,7 +346,7 @@ private final class PremiumGiftCodeSheetContent: CombinedComponent {
title: strings.GiftLink_To,
component: AnyComponent(
Button(
content: AnyComponent(PeerCellComponent(context: context.component.context, textColor: tableLinkColor, peer: toPeer)),
content: AnyComponent(PeerTableCellComponent(context: context.component.context, theme: theme, strings: strings, peer: toPeer)),
action: {
if toPeer.id != accountContext.account.peerId {
component.openPeer(toPeer)
@ -857,306 +859,6 @@ final class GiftLinkButtonContentComponent: CombinedComponent {
}
}
private final class TableComponent: CombinedComponent {
class Item: Equatable {
public let id: AnyHashable
public let title: String
public let component: AnyComponent<Empty>
public init<IdType: Hashable>(id: IdType, title: String, component: AnyComponent<Empty>) {
self.id = AnyHashable(id)
self.title = title
self.component = component
}
public static func == (lhs: Item, rhs: Item) -> Bool {
if lhs.id != rhs.id {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.component != rhs.component {
return false
}
return true
}
}
private let theme: PresentationTheme
private let items: [Item]
public init(theme: PresentationTheme, items: [Item]) {
self.theme = theme
self.items = items
}
public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
if lhs.items != rhs.items {
return false
}
return true
}
final class State: ComponentState {
var cachedBorderImage: (UIImage, PresentationTheme)?
}
func makeState() -> State {
return State()
}
public static var body: Body {
let leftColumnBackground = Child(Rectangle.self)
let verticalBorder = Child(Rectangle.self)
let titleChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let valueChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let borderChildren = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
let outerBorder = Child(Image.self)
return { context in
let verticalPadding: CGFloat = 11.0
let horizontalPadding: CGFloat = 12.0
let borderWidth: CGFloat = 1.0
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
var leftColumnWidth: CGFloat = 0.0
var updatedTitleChildren: [_UpdatedChildComponent] = []
var updatedValueChildren: [_UpdatedChildComponent] = []
var updatedBorderChildren: [_UpdatedChildComponent] = []
for item in context.component.items {
let titleChild = titleChildren[item.id].update(
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: item.title, font: Font.regular(15.0), textColor: context.component.theme.list.itemPrimaryTextColor))
)),
availableSize: context.availableSize,
transition: context.transition
)
updatedTitleChildren.append(titleChild)
if titleChild.size.width > leftColumnWidth {
leftColumnWidth = titleChild.size.width
}
}
leftColumnWidth = max(100.0, leftColumnWidth + horizontalPadding * 2.0)
let rightColumnWidth = context.availableSize.width - leftColumnWidth
var i = 0
var rowHeights: [Int: CGFloat] = [:]
var totalHeight: CGFloat = 0.0
for item in context.component.items {
let titleChild = updatedTitleChildren[i]
let valueChild = valueChildren[item.id].update(
component: item.component,
availableSize: CGSize(width: rightColumnWidth - horizontalPadding * 2.0, height: context.availableSize.height),
transition: context.transition
)
updatedValueChildren.append(valueChild)
let rowHeight = max(40.0, max(titleChild.size.height, valueChild.size.height) + verticalPadding * 2.0)
rowHeights[i] = rowHeight
totalHeight += rowHeight
if i < context.component.items.count - 1 {
let borderChild = borderChildren[item.id].update(
component: AnyComponent(Rectangle(color: borderColor)),
availableSize: CGSize(width: context.availableSize.width, height: borderWidth),
transition: context.transition
)
updatedBorderChildren.append(borderChild)
}
i += 1
}
let leftColumnBackground = leftColumnBackground.update(
component: Rectangle(color: context.component.theme.list.itemInputField.backgroundColor),
availableSize: CGSize(width: leftColumnWidth, height: totalHeight),
transition: context.transition
)
context.add(
leftColumnBackground
.position(CGPoint(x: leftColumnWidth / 2.0, y: totalHeight / 2.0))
)
let borderImage: UIImage
if let (currentImage, theme) = context.state.cachedBorderImage, theme === context.component.theme {
borderImage = currentImage
} else {
let borderRadius: CGFloat = 14.0
borderImage = generateImage(CGSize(width: borderRadius * 2.0 + 6.0, height: borderRadius * 2.0 + 6.0), rotatedContext: { size, context in
let bounds = CGRect(origin: .zero, size: size)
context.setFillColor(backgroundColor.cgColor)
context.fill(bounds)
let path = CGPath(roundedRect: bounds.insetBy(dx: borderWidth / 2.0, dy: borderWidth / 2.0), cornerWidth: borderRadius, cornerHeight: borderRadius, transform: nil)
context.setBlendMode(.clear)
context.addPath(path)
context.fillPath()
context.setBlendMode(.normal)
context.setStrokeColor(borderColor.cgColor)
context.setLineWidth(borderWidth)
context.addPath(path)
context.strokePath()
})!.stretchableImage(withLeftCapWidth: Int(borderRadius), topCapHeight: Int(borderRadius))
context.state.cachedBorderImage = (borderImage, context.component.theme)
}
let outerBorder = outerBorder.update(
component: Image(image: borderImage),
availableSize: CGSize(width: context.availableSize.width, height: totalHeight),
transition: context.transition
)
context.add(outerBorder
.position(CGPoint(x: context.availableSize.width / 2.0, y: totalHeight / 2.0))
)
let verticalBorder = verticalBorder.update(
component: Rectangle(color: borderColor),
availableSize: CGSize(width: borderWidth, height: totalHeight),
transition: context.transition
)
context.add(
verticalBorder
.position(CGPoint(x: leftColumnWidth - borderWidth / 2.0, y: totalHeight / 2.0))
)
i = 0
var originY: CGFloat = 0.0
for (titleChild, valueChild) in zip(updatedTitleChildren, updatedValueChildren) {
let rowHeight = rowHeights[i] ?? 0.0
let titleFrame = CGRect(origin: CGPoint(x: horizontalPadding, y: originY + verticalPadding), size: titleChild.size)
let valueFrame = CGRect(origin: CGPoint(x: leftColumnWidth + horizontalPadding, y: originY + verticalPadding), size: valueChild.size)
context.add(titleChild
.position(titleFrame.center)
)
context.add(valueChild
.position(valueFrame.center)
)
if i < updatedBorderChildren.count {
let borderChild = updatedBorderChildren[i]
context.add(borderChild
.position(CGPoint(x: context.availableSize.width / 2.0, y: originY + rowHeight - borderWidth / 2.0))
)
}
originY += rowHeight
i += 1
}
return CGSize(width: context.availableSize.width, height: totalHeight)
}
}
}
private final class PeerCellComponent: Component {
let context: AccountContext
let textColor: UIColor
let peer: EnginePeer?
init(context: AccountContext, textColor: UIColor, peer: EnginePeer?) {
self.context = context
self.textColor = textColor
self.peer = peer
}
static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.textColor !== rhs.textColor {
return false
}
if lhs.peer != rhs.peer {
return false
}
return true
}
final class View: UIView {
private let avatarNode: AvatarNode
private let text = ComponentView<Empty>()
private var component: PeerCellComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 13.0))
super.init(frame: frame)
self.addSubnode(self.avatarNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
self.avatarNode.setPeer(
context: component.context,
theme: component.context.sharedContext.currentPresentationData.with({ $0 }).theme,
peer: component.peer,
synchronousLoad: true
)
let avatarSize = CGSize(width: 22.0, height: 22.0)
let spacing: CGFloat = 6.0
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: component.peer?.compactDisplayTitle ?? "", font: Font.regular(15.0), textColor: component.textColor, paragraphAlignment: .left))
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height)
)
let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height)
let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize)
self.avatarNode.frame = avatarFrame
if let view = self.text.view {
if view.superview == nil {
self.addSubview(view)
}
let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
transition.setFrame(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: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
private final class DustComponent: Component {
let color: UIColor

View file

@ -1679,34 +1679,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
}
})
self.newPerksDisposable = combineLatest(
queue: Queue.mainQueue(),
ApplicationSpecificNotice.dismissedBusinessBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedBusinessLinksBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedBusinessIntroBadge(accountManager: context.sharedContext.accountManager),
ApplicationSpecificNotice.dismissedBusinessChatbotsBadge(accountManager: context.sharedContext.accountManager)
).startStrict(next: { [weak self] dismissedBusinessBadge, dismissedBusinessLinksBadge, dismissedBusinessIntroBadge, dismissedBusinessChatbotsBadge in
guard let self else {
return
}
var newPerks: [String] = []
if !dismissedBusinessBadge {
newPerks.append(PremiumPerk.business.identifier)
}
if !dismissedBusinessLinksBadge {
newPerks.append(PremiumPerk.businessLinks.identifier)
}
if !dismissedBusinessIntroBadge {
newPerks.append(PremiumPerk.businessIntro.identifier)
}
if !dismissedBusinessChatbotsBadge {
newPerks.append(PremiumPerk.businessChatBots.identifier)
}
self.newPerks = newPerks
self.updated()
})
self.adsEnabledDisposable = (context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AdsEnabled(id: context.account.peerId))
|> deliverOnMainQueue).start(next: { [weak self] adsEnabled in
guard let self else {
@ -2219,7 +2192,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
demoSubject = .todo
case .business:
demoSubject = .business
let _ = ApplicationSpecificNotice.setDismissedBusinessBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
default:
demoSubject = .doubleLimits
}
@ -2418,7 +2390,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeChatbotSetupScreen(context: accountContext, initialData: initialData))
})
let _ = ApplicationSpecificNotice.setDismissedBusinessChatbotsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
case .businessIntro:
let _ = (accountContext.sharedContext.makeBusinessIntroSetupScreenInitialData(context: accountContext)
|> take(1)
@ -2428,7 +2399,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeBusinessIntroSetupScreen(context: accountContext, initialData: initialData))
})
let _ = ApplicationSpecificNotice.setDismissedBusinessIntroBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
case .businessLinks:
let _ = (accountContext.sharedContext.makeBusinessLinksSetupScreenInitialData(context: accountContext)
|> take(1)
@ -2438,7 +2408,6 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
}
push(accountContext.sharedContext.makeBusinessLinksSetupScreen(context: accountContext, initialData: initialData))
})
let _ = ApplicationSpecificNotice.setDismissedBusinessLinksBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
default:
fatalError()
}
@ -2457,13 +2426,10 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent {
demoSubject = .businessAwayMessage
case .businessChatBots:
demoSubject = .businessChatBots
let _ = ApplicationSpecificNotice.setDismissedBusinessChatbotsBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
case .businessIntro:
demoSubject = .businessIntro
let _ = ApplicationSpecificNotice.setDismissedBusinessIntroBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
case .businessLinks:
demoSubject = .businessLinks
let _ = ApplicationSpecificNotice.setDismissedBusinessLinksBadge(accountManager: accountContext.sharedContext.accountManager).startStandalone()
default:
fatalError()
}

View file

@ -19,6 +19,7 @@ swift_library(
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/UrlWhitelist:UrlWhitelist",
"//submodules/TelegramUI/Components/AlertComponent",
],
visibility = [
"//visibility:public",

View file

@ -4,35 +4,116 @@ import AlertUI
import AccountContext
import SwiftSignalKit
import TelegramPresentationData
import AlertComponent
public func textAlertController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil) -> AlertController {
public func textAlertController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
forceTheme: PresentationTheme? = nil,
title: String?,
text: String,
actions: [TextAlertAction],
actionLayout: TextAlertContentActionLayout = .horizontal,
allowInputInset: Bool = true,
parseMarkdown: Bool = false,
dismissOnOutsideTap: Bool = true,
linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
) -> ViewController {
return textAlertController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, forceTheme: forceTheme, title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap, linkAction: linkAction)
}
public func textAlertController(sharedContext: SharedAccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, forceTheme: PresentationTheme? = nil, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil) -> AlertController {
public func textAlertController(
sharedContext: SharedAccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil,
forceTheme: PresentationTheme? = nil,
title: String?,
text: String,
actions: [TextAlertAction],
actionLayout: TextAlertContentActionLayout = .horizontal,
allowInputInset: Bool = true,
parseMarkdown: Bool = false,
dismissOnOutsideTap: Bool = true,
linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil
) -> ViewController {
var presentationData = updatedPresentationData?.initial ?? sharedContext.currentPresentationData.with { $0 }
if let forceTheme = forceTheme {
if let forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
return textAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: presentationData), themeSignal: (updatedPresentationData?.signal ?? sharedContext.presentationData) |> map {
let updatedPresentationDataSignal = (updatedPresentationData?.signal ?? sharedContext.presentationData) |> map {
presentationData in
var presentationData = presentationData
if let forceTheme = forceTheme {
presentationData = presentationData.withUpdated(theme: forceTheme)
}
return AlertControllerTheme(presentationData: presentationData)
}), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, parseMarkdown: parseMarkdown, dismissOnOutsideTap: dismissOnOutsideTap, linkAction: linkAction)
return presentationData
}
let mappedActions: [AlertScreen.Action] = actions.map { action in
let mappedType: AlertScreen.Action.ActionType
switch action.type {
case .genericAction:
mappedType = .generic
case .defaultAction:
mappedType = .default
case .destructiveAction:
mappedType = .destructive
case .defaultDestructiveAction:
mappedType = .defaultDestructive
}
return AlertScreen.Action(
title: action.title,
type: mappedType,
action: action.action
)
}
let controller = AlertScreen(
configuration: AlertScreen.Configuration(
actionAlignment: actionLayout == .vertical ? .vertical : .default,
dismissOnOutsideTap: dismissOnOutsideTap,
allowInputInset: allowInputInset
),
title: title,
text: text,
actions: mappedActions,
updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal)
)
return controller
}
public func textAlertController(sharedContext: SharedAccountContext, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissOnOutsideTap: Bool = true) -> AlertController {
public func textAlertController(
sharedContext: SharedAccountContext,
title: String?,
text: String,
actions: [TextAlertAction],
actionLayout: TextAlertContentActionLayout = .horizontal,
allowInputInset: Bool = true,
dismissOnOutsideTap: Bool = true
) -> AlertController {
return textAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: sharedContext.currentPresentationData.with { $0 }), themeSignal: sharedContext.presentationData |> map { presentationData in AlertControllerTheme(presentationData: presentationData) }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissOnOutsideTap: dismissOnOutsideTap)
}
public func richTextAlertController(context: AccountContext, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissAutomatically: Bool = true) -> AlertController {
public func richTextAlertController(
context: AccountContext,
title: NSAttributedString?,
text: NSAttributedString,
actions: [TextAlertAction],
actionLayout: TextAlertContentActionLayout = .horizontal,
allowInputInset: Bool = true,
dismissAutomatically: Bool = true
) -> AlertController {
return richTextAlertController(alertContext: AlertControllerContext(theme: AlertControllerTheme(presentationData: context.sharedContext.currentPresentationData.with { $0 }), themeSignal: context.sharedContext.presentationData |> map { presentationData in AlertControllerTheme(presentationData: presentationData) }), title: title, text: text, actions: actions, actionLayout: actionLayout, allowInputInset: allowInputInset, dismissAutomatically: dismissAutomatically)
}
public func textWithEntitiesAlertController(context: AccountContext, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, dismissAutomatically: Bool = true) -> AlertController {
public func textWithEntitiesAlertController(
context: AccountContext,
title: NSAttributedString?,
text: NSAttributedString,
actions: [TextAlertAction],
actionLayout: TextAlertContentActionLayout = .horizontal,
allowInputInset: Bool = true,
dismissAutomatically: Bool = true
) -> AlertController {
return textWithEntitiesAlertController(
alertContext: AlertControllerContext(
theme: AlertControllerTheme(presentationData: context.sharedContext.currentPresentationData.with { $0 }),

View file

@ -6,6 +6,7 @@ import AccountContext
import OverlayStatusController
import UrlWhitelist
import TelegramPresentationData
import AlertComponent
public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url: String, concealed: Bool, skipUrlAuth: Bool = false, skipConcealedAlert: Bool = false, forceDark: Bool = false, present: @escaping (ViewController) -> Void, openResolved: @escaping (ResolvedUrl) -> Void, progress: Promise<Bool>? = nil, alertDisplayUpdated: ((ViewController?) -> Void)? = nil) -> Disposable {
var concealed = concealed
@ -95,8 +96,10 @@ public func openUserGeneratedUrl(context: AccountContext, peerId: PeerId?, url:
let alertController = textAlertController(context: context, forceTheme: forceDark ? presentationData.theme : nil, title: nil, text: presentationData.strings.Generic_OpenHiddenLinkAlert(displayUrl).string, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
disposable.set(openImpl())
})])
alertController.dismissed = { _ in
alertDisplayUpdated?(nil)
if let alertController = alertController as? AlertScreen {
alertController.dismissed = { _ in
alertDisplayUpdated?(nil)
}
}
present(alertController)
alertDisplayUpdated?(alertController)

View file

@ -31,6 +31,15 @@ swift_library(
"//submodules/LegacyComponents:LegacyComponents",
"//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI",
"//submodules/ImageContentAnalysis:ImageContentAnalysis",
"//submodules/ComponentFlow",
"//submodules/Components/SheetComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
],
visibility = [
"//visibility:public",

File diff suppressed because it is too large Load diff

View file

@ -131,6 +131,7 @@ swift_library(
"//submodules/Components/BundleIconComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/TelegramUI/Components/SliderComponent",
"//submodules/TelegramUI/Components/AlertComponent",
],
visibility = [
"//visibility:public",

View file

@ -16,6 +16,7 @@ import AccountUtils
import PremiumUI
import PasswordSetupUI
import StorageUsageScreen
import AlertComponent
private struct DeleteAccountOptionsArguments {
let changePhoneNumber: () -> Void
@ -102,43 +103,43 @@ private enum DeleteAccountOptionsEntry: ItemListNodeEntry, Equatable {
let arguments = arguments as! DeleteAccountOptionsArguments
switch self {
case let .changePhoneNumber(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.changePhoneNumber, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.changePhoneNumber()
})
case let .addAccount(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteAddAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteAddAccount, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.addAccount()
})
case let .changePrivacy(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.security, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.security, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.setupPrivacy()
})
case let .setTwoStepAuth(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteSetTwoStepAuth, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.setupTwoStepAuth()
})
case let .setPasscode(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteSetPasscode, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.setPasscode()
})
case let .clearCache(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.dataAndStorage, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.dataAndStorage, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.clearCache()
})
case let .clearSyncedContacts(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.clearSynced, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.clearSyncedContacts()
})
case let .deleteChats(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.deleteChats, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.deleteChats()
})
case let .contactSupport(_, title, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
return ItemListDisclosureItem(presentationData: presentationData, systemStyle: .glass, icon: PresentationResourcesSettings.support, title: title, label: text, labelStyle: .multilineDetailText, sectionId: self.section, style: .blocks, disclosureStyle: .arrow, action: {
arguments.contactSupport()
})
case let .deleteAccount(_, title):
return ItemListActionItem(presentationData: presentationData, title: title, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: title, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.deleteAccount()
})
}
@ -362,32 +363,36 @@ public func deleteAccountOptionsController(context: AccountContext, navigationCo
}, dismissInput: {}, contentContext: nil, progress: nil, completion: nil)
})
}
let alertController = textAlertController(context: context, title: nil, text: presentationData.strings.Settings_FAQ_Intro, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.Settings_FAQ_Button, action: {
openFaq(resolvedUrlPromise)
dismissImpl?()
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
supportPeerDisposable.set((supportPeer.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerId in
guard let peerId = peerId else {
return
}
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
let alertController = AlertScreen(
context: context,
title: nil,
text: presentationData.strings.Settings_FAQ_Intro,
actions: [
.init(title: presentationData.strings.Settings_FAQ_Button, action: {
openFaq(resolvedUrlPromise)
dismissImpl?()
}),
.init(title: presentationData.strings.Common_OK, type: .default, action: {
supportPeerDisposable.set((supportPeer.get()
|> take(1)
|> deliverOnMainQueue).start(next: { peerId in
guard let peerId else {
return
}
if let navigationController = navigationController {
dismissImpl?()
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
}
})
}))
})
])
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer else {
return
}
if let navigationController = navigationController {
dismissImpl?()
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer)))
}
})
}))
})
]
)
alertController.dismissed = { _ in
addAppLogEvent(postbox: context.account.postbox, type: "deactivate.options_support_cancel")
}

View file

@ -1406,10 +1406,10 @@ public func privacyAndSecurityController(
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = textAlertController(
context: context, title: emailPattern, text: presentationData.strings.PrivacySettings_LoginEmailAlertText, actions: [
TextAlertAction(type: .genericAction, title: presentationData.strings.PrivacySettings_LoginEmailAlertChange, action: {
TextAlertAction(type: .defaultAction, title: presentationData.strings.PrivacySettings_LoginEmailAlertChange, action: {
setupEmailImpl?(emailPattern)
}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
})
], actionLayout: .vertical

View file

@ -417,6 +417,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1279654347] = { return Api.InputMedia.parse_inputMediaPhoto($0) }
dict[-440664550] = { return Api.InputMedia.parse_inputMediaPhotoExternal($0) }
dict[261416433] = { return Api.InputMedia.parse_inputMediaPoll($0) }
dict[-207018934] = { return Api.InputMedia.parse_inputMediaStakeDice($0) }
dict[-1979852936] = { return Api.InputMedia.parse_inputMediaStory($0) }
dict[-1614454818] = { return Api.InputMedia.parse_inputMediaTodo($0) }
dict[58495792] = { return Api.InputMedia.parse_inputMediaUploadedDocument($0) }
@ -660,7 +661,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1386050360] = { return Api.MessageExtendedMedia.parse_messageExtendedMediaPreview($0) }
dict[1313731771] = { return Api.MessageFwdHeader.parse_messageFwdHeader($0) }
dict[1882335561] = { return Api.MessageMedia.parse_messageMediaContact($0) }
dict[1065280907] = { return Api.MessageMedia.parse_messageMediaDice($0) }
dict[147581959] = { return Api.MessageMedia.parse_messageMediaDice($0) }
dict[1389939929] = { return Api.MessageMedia.parse_messageMediaDocument($0) }
dict[1038967584] = { return Api.MessageMedia.parse_messageMediaEmpty($0) }
dict[-38694904] = { return Api.MessageMedia.parse_messageMediaGame($0) }
@ -1127,6 +1128,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-302247650] = { return Api.Update.parse_updateDraftMessage($0) }
dict[457133559] = { return Api.Update.parse_updateEditChannelMessage($0) }
dict[-469536605] = { return Api.Update.parse_updateEditMessage($0) }
dict[-73640838] = { return Api.Update.parse_updateEmojiGameInfo($0) }
dict[386986326] = { return Api.Update.parse_updateEncryptedChatTyping($0) }
dict[956179895] = { return Api.Update.parse_updateEncryptedMessagesRead($0) }
dict[-1264392051] = { return Api.Update.parse_updateEncryption($0) }
@ -1417,6 +1419,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-253500010] = { return Api.messages.Dialogs.parse_dialogsNotModified($0) }
dict[1910543603] = { return Api.messages.Dialogs.parse_dialogsSlice($0) }
dict[-1506535550] = { return Api.messages.DiscussionMessage.parse_discussionMessage($0) }
dict[1155883043] = { return Api.messages.EmojiGameInfo.parse_emojiGameDiceInfo($0) }
dict[1508266805] = { return Api.messages.EmojiGameInfo.parse_emojiGameUnavailable($0) }
dict[-1585592191] = { return Api.messages.EmojiGameOutcome.parse_emojiGameOutcome($0) }
dict[-2011186869] = { return Api.messages.EmojiGroups.parse_emojiGroups($0) }
dict[1874111879] = { return Api.messages.EmojiGroups.parse_emojiGroupsNotModified($0) }
dict[410107472] = { return Api.messages.ExportedChatInvite.parse_exportedChatInvite($0) }
@ -2573,6 +2578,10 @@ public extension Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.DiscussionMessage:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.EmojiGameInfo:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.EmojiGameOutcome:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.EmojiGroups:
_1.serialize(buffer, boxed)
case let _1 as Api.messages.ExportedChatInvite:

View file

@ -13,6 +13,7 @@ public extension Api {
case inputMediaPhoto(flags: Int32, id: Api.InputPhoto, ttlSeconds: Int32?)
case inputMediaPhotoExternal(flags: Int32, url: String, ttlSeconds: Int32?)
case inputMediaPoll(flags: Int32, poll: Api.Poll, correctAnswers: [Buffer]?, solution: String?, solutionEntities: [Api.MessageEntity]?)
case inputMediaStakeDice(gameHash: String, tonAmount: Int64, clientSeed: Buffer)
case inputMediaStory(peer: Api.InputPeer, id: Int32)
case inputMediaTodo(todo: Api.TodoList)
case inputMediaUploadedDocument(flags: Int32, file: Api.InputFile, thumb: Api.InputFile?, mimeType: String, attributes: [Api.DocumentAttribute], stickers: [Api.InputDocument]?, videoCover: Api.InputPhoto?, videoTimestamp: Int32?, ttlSeconds: Int32?)
@ -148,6 +149,14 @@ public extension Api {
item.serialize(buffer, true)
}}
break
case .inputMediaStakeDice(let gameHash, let tonAmount, let clientSeed):
if boxed {
buffer.appendInt32(-207018934)
}
serializeString(gameHash, buffer: buffer, boxed: false)
serializeInt64(tonAmount, buffer: buffer, boxed: false)
serializeBytes(clientSeed, buffer: buffer, boxed: false)
break
case .inputMediaStory(let peer, let id):
if boxed {
buffer.appendInt32(-1979852936)
@ -245,6 +254,8 @@ public extension Api {
return ("inputMediaPhotoExternal", [("flags", flags as Any), ("url", url as Any), ("ttlSeconds", ttlSeconds as Any)])
case .inputMediaPoll(let flags, let poll, let correctAnswers, let solution, let solutionEntities):
return ("inputMediaPoll", [("flags", flags as Any), ("poll", poll as Any), ("correctAnswers", correctAnswers as Any), ("solution", solution as Any), ("solutionEntities", solutionEntities as Any)])
case .inputMediaStakeDice(let gameHash, let tonAmount, let clientSeed):
return ("inputMediaStakeDice", [("gameHash", gameHash as Any), ("tonAmount", tonAmount as Any), ("clientSeed", clientSeed as Any)])
case .inputMediaStory(let peer, let id):
return ("inputMediaStory", [("peer", peer as Any), ("id", id as Any)])
case .inputMediaTodo(let todo):
@ -533,6 +544,23 @@ public extension Api {
return nil
}
}
public static func parse_inputMediaStakeDice(_ reader: BufferReader) -> InputMedia? {
var _1: String?
_1 = parseString(reader)
var _2: Int64?
_2 = reader.readInt64()
var _3: Buffer?
_3 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.InputMedia.inputMediaStakeDice(gameHash: _1!, tonAmount: _2!, clientSeed: _3!)
}
else {
return nil
}
}
public static func parse_inputMediaStory(_ reader: BufferReader) -> InputMedia? {
var _1: Api.InputPeer?
if let signature = reader.readInt32() {
@ -990,113 +1018,3 @@ public extension Api {
}
}
public extension Api {
enum InputPaymentCredentials: TypeConstructorDescription {
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPaymentCredentials(let flags, let data):
if boxed {
buffer.appendInt32(873977640)
}
serializeInt32(flags, buffer: buffer, boxed: false)
data.serialize(buffer, true)
break
case .inputPaymentCredentialsApplePay(let paymentData):
if boxed {
buffer.appendInt32(178373535)
}
paymentData.serialize(buffer, true)
break
case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed {
buffer.appendInt32(-1966921727)
}
paymentToken.serialize(buffer, true)
break
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
if boxed {
buffer.appendInt32(-1056001329)
}
serializeString(id, buffer: buffer, boxed: false)
serializeBytes(tmpPassword, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPaymentCredentials(let flags, let data):
return ("inputPaymentCredentials", [("flags", flags as Any), ("data", data as Any)])
case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData as Any)])
case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken as Any)])
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
return ("inputPaymentCredentialsSaved", [("id", id as Any), ("tmpPassword", tmpPassword as Any)])
}
}
public static func parse_inputPaymentCredentials(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.DataJSON?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentials(flags: _1!, data: _2!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsApplePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsApplePay(paymentData: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsSaved(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: String?
_1 = parseString(reader)
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsSaved(id: _1!, tmpPassword: _2!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,113 @@
public extension Api {
enum InputPaymentCredentials: TypeConstructorDescription {
case inputPaymentCredentials(flags: Int32, data: Api.DataJSON)
case inputPaymentCredentialsApplePay(paymentData: Api.DataJSON)
case inputPaymentCredentialsGooglePay(paymentToken: Api.DataJSON)
case inputPaymentCredentialsSaved(id: String, tmpPassword: Buffer)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .inputPaymentCredentials(let flags, let data):
if boxed {
buffer.appendInt32(873977640)
}
serializeInt32(flags, buffer: buffer, boxed: false)
data.serialize(buffer, true)
break
case .inputPaymentCredentialsApplePay(let paymentData):
if boxed {
buffer.appendInt32(178373535)
}
paymentData.serialize(buffer, true)
break
case .inputPaymentCredentialsGooglePay(let paymentToken):
if boxed {
buffer.appendInt32(-1966921727)
}
paymentToken.serialize(buffer, true)
break
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
if boxed {
buffer.appendInt32(-1056001329)
}
serializeString(id, buffer: buffer, boxed: false)
serializeBytes(tmpPassword, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .inputPaymentCredentials(let flags, let data):
return ("inputPaymentCredentials", [("flags", flags as Any), ("data", data as Any)])
case .inputPaymentCredentialsApplePay(let paymentData):
return ("inputPaymentCredentialsApplePay", [("paymentData", paymentData as Any)])
case .inputPaymentCredentialsGooglePay(let paymentToken):
return ("inputPaymentCredentialsGooglePay", [("paymentToken", paymentToken as Any)])
case .inputPaymentCredentialsSaved(let id, let tmpPassword):
return ("inputPaymentCredentialsSaved", [("id", id as Any), ("tmpPassword", tmpPassword as Any)])
}
}
public static func parse_inputPaymentCredentials(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.DataJSON?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentials(flags: _1!, data: _2!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsApplePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsApplePay(paymentData: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsGooglePay(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: Api.DataJSON?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.DataJSON
}
let _c1 = _1 != nil
if _c1 {
return Api.InputPaymentCredentials.inputPaymentCredentialsGooglePay(paymentToken: _1!)
}
else {
return nil
}
}
public static func parse_inputPaymentCredentialsSaved(_ reader: BufferReader) -> InputPaymentCredentials? {
var _1: String?
_1 = parseString(reader)
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.InputPaymentCredentials.inputPaymentCredentialsSaved(id: _1!, tmpPassword: _2!)
}
else {
return nil
}
}
}
}
public extension Api {
indirect enum InputPeer: TypeConstructorDescription {
case inputPeerChannel(channelId: Int64, accessHash: Int64)

View file

@ -709,7 +709,7 @@ public extension Api {
public extension Api {
indirect enum MessageMedia: TypeConstructorDescription {
case messageMediaContact(phoneNumber: String, firstName: String, lastName: String, vcard: String, userId: Int64)
case messageMediaDice(value: Int32, emoticon: String)
case messageMediaDice(flags: Int32, value: Int32, emoticon: String, gameOutcome: Api.messages.EmojiGameOutcome?)
case messageMediaDocument(flags: Int32, document: Api.Document?, altDocuments: [Api.Document]?, videoCover: Api.Photo?, videoTimestamp: Int32?, ttlSeconds: Int32?)
case messageMediaEmpty
case messageMediaGame(game: Api.Game)
@ -740,12 +740,14 @@ public extension Api {
serializeString(vcard, buffer: buffer, boxed: false)
serializeInt64(userId, buffer: buffer, boxed: false)
break
case .messageMediaDice(let value, let emoticon):
case .messageMediaDice(let flags, let value, let emoticon, let gameOutcome):
if boxed {
buffer.appendInt32(1065280907)
buffer.appendInt32(147581959)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(value, buffer: buffer, boxed: false)
serializeString(emoticon, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {gameOutcome!.serialize(buffer, true)}
break
case .messageMediaDocument(let flags, let document, let altDocuments, let videoCover, let videoTimestamp, let ttlSeconds):
if boxed {
@ -930,8 +932,8 @@ public extension Api {
switch self {
case .messageMediaContact(let phoneNumber, let firstName, let lastName, let vcard, let userId):
return ("messageMediaContact", [("phoneNumber", phoneNumber as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("vcard", vcard as Any), ("userId", userId as Any)])
case .messageMediaDice(let value, let emoticon):
return ("messageMediaDice", [("value", value as Any), ("emoticon", emoticon as Any)])
case .messageMediaDice(let flags, let value, let emoticon, let gameOutcome):
return ("messageMediaDice", [("flags", flags as Any), ("value", value as Any), ("emoticon", emoticon as Any), ("gameOutcome", gameOutcome as Any)])
case .messageMediaDocument(let flags, let document, let altDocuments, let videoCover, let videoTimestamp, let ttlSeconds):
return ("messageMediaDocument", [("flags", flags as Any), ("document", document as Any), ("altDocuments", altDocuments as Any), ("videoCover", videoCover as Any), ("videoTimestamp", videoTimestamp as Any), ("ttlSeconds", ttlSeconds as Any)])
case .messageMediaEmpty:
@ -995,12 +997,20 @@ public extension Api {
public static func parse_messageMediaDice(_ reader: BufferReader) -> MessageMedia? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _2: Int32?
_2 = reader.readInt32()
var _3: String?
_3 = parseString(reader)
var _4: Api.messages.EmojiGameOutcome?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_4 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameOutcome
} }
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.MessageMedia.messageMediaDice(value: _1!, emoticon: _2!)
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
if _c1 && _c2 && _c3 && _c4 {
return Api.MessageMedia.messageMediaDice(flags: _1!, value: _2!, emoticon: _3!, gameOutcome: _4)
}
else {
return nil

View file

@ -1247,6 +1247,7 @@ public extension Api {
case updateDraftMessage(flags: Int32, peer: Api.Peer, topMsgId: Int32?, savedPeerId: Api.Peer?, draft: Api.DraftMessage)
case updateEditChannelMessage(message: Api.Message, pts: Int32, ptsCount: Int32)
case updateEditMessage(message: Api.Message, pts: Int32, ptsCount: Int32)
case updateEmojiGameInfo(info: Api.messages.EmojiGameInfo)
case updateEncryptedChatTyping(chatId: Int32)
case updateEncryptedMessagesRead(chatId: Int32, maxDate: Int32, date: Int32)
case updateEncryption(chat: Api.EncryptedChat, date: Int32)
@ -1887,6 +1888,12 @@ public extension Api {
serializeInt32(pts, buffer: buffer, boxed: false)
serializeInt32(ptsCount, buffer: buffer, boxed: false)
break
case .updateEmojiGameInfo(let info):
if boxed {
buffer.appendInt32(-73640838)
}
info.serialize(buffer, true)
break
case .updateEncryptedChatTyping(let chatId):
if boxed {
buffer.appendInt32(386986326)
@ -2774,6 +2781,8 @@ public extension Api {
return ("updateEditChannelMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
case .updateEditMessage(let message, let pts, let ptsCount):
return ("updateEditMessage", [("message", message as Any), ("pts", pts as Any), ("ptsCount", ptsCount as Any)])
case .updateEmojiGameInfo(let info):
return ("updateEmojiGameInfo", [("info", info as Any)])
case .updateEncryptedChatTyping(let chatId):
return ("updateEncryptedChatTyping", [("chatId", chatId as Any)])
case .updateEncryptedMessagesRead(let chatId, let maxDate, let date):
@ -4126,6 +4135,19 @@ public extension Api {
return nil
}
}
public static func parse_updateEmojiGameInfo(_ reader: BufferReader) -> Update? {
var _1: Api.messages.EmojiGameInfo?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo
}
let _c1 = _1 != nil
if _c1 {
return Api.Update.updateEmojiGameInfo(info: _1!)
}
else {
return nil
}
}
public static func parse_updateEncryptedChatTyping(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()

View file

@ -1060,6 +1060,120 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum EmojiGameInfo: TypeConstructorDescription {
case emojiGameDiceInfo(flags: Int32, gameHash: String, prevStake: Int64, currentStreak: Int32, params: [Int32], playsLeft: Int32?)
case emojiGameUnavailable
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .emojiGameDiceInfo(let flags, let gameHash, let prevStake, let currentStreak, let params, let playsLeft):
if boxed {
buffer.appendInt32(1155883043)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeString(gameHash, buffer: buffer, boxed: false)
serializeInt64(prevStake, buffer: buffer, boxed: false)
serializeInt32(currentStreak, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(params.count))
for item in params {
serializeInt32(item, buffer: buffer, boxed: false)
}
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(playsLeft!, buffer: buffer, boxed: false)}
break
case .emojiGameUnavailable:
if boxed {
buffer.appendInt32(1508266805)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .emojiGameDiceInfo(let flags, let gameHash, let prevStake, let currentStreak, let params, let playsLeft):
return ("emojiGameDiceInfo", [("flags", flags as Any), ("gameHash", gameHash as Any), ("prevStake", prevStake as Any), ("currentStreak", currentStreak as Any), ("params", params as Any), ("playsLeft", playsLeft as Any)])
case .emojiGameUnavailable:
return ("emojiGameUnavailable", [])
}
}
public static func parse_emojiGameDiceInfo(_ reader: BufferReader) -> EmojiGameInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: String?
_2 = parseString(reader)
var _3: Int64?
_3 = reader.readInt64()
var _4: Int32?
_4 = reader.readInt32()
var _5: [Int32]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
}
var _6: Int32?
if Int(_1!) & Int(1 << 0) != 0 {_6 = reader.readInt32() }
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.messages.EmojiGameInfo.emojiGameDiceInfo(flags: _1!, gameHash: _2!, prevStake: _3!, currentStreak: _4!, params: _5!, playsLeft: _6)
}
else {
return nil
}
}
public static func parse_emojiGameUnavailable(_ reader: BufferReader) -> EmojiGameInfo? {
return Api.messages.EmojiGameInfo.emojiGameUnavailable
}
}
}
public extension Api.messages {
enum EmojiGameOutcome: TypeConstructorDescription {
case emojiGameOutcome(seed: Buffer, tonAmount: Int64)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .emojiGameOutcome(let seed, let tonAmount):
if boxed {
buffer.appendInt32(-1585592191)
}
serializeBytes(seed, buffer: buffer, boxed: false)
serializeInt64(tonAmount, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .emojiGameOutcome(let seed, let tonAmount):
return ("emojiGameOutcome", [("seed", seed as Any), ("tonAmount", tonAmount as Any)])
}
}
public static func parse_emojiGameOutcome(_ reader: BufferReader) -> EmojiGameOutcome? {
var _1: Buffer?
_1 = parseBytes(reader)
var _2: Int64?
_2 = reader.readInt64()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.EmojiGameOutcome.emojiGameOutcome(seed: _1!, tonAmount: _2!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum EmojiGroups: TypeConstructorDescription {
case emojiGroups(hash: Int32, groups: [Api.EmojiGroup])
@ -1260,155 +1374,3 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum FavedStickers: TypeConstructorDescription {
case favedStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document])
case favedStickersNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .favedStickers(let hash, let packs, let stickers):
if boxed {
buffer.appendInt32(750063767)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(packs.count))
for item in packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stickers.count))
for item in stickers {
item.serialize(buffer, true)
}
break
case .favedStickersNotModified:
if boxed {
buffer.appendInt32(-1634752813)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .favedStickers(let hash, let packs, let stickers):
return ("favedStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any)])
case .favedStickersNotModified:
return ("favedStickersNotModified", [])
}
}
public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.Document]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.FavedStickers.favedStickers(hash: _1!, packs: _2!, stickers: _3!)
}
else {
return nil
}
}
public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? {
return Api.messages.FavedStickers.favedStickersNotModified
}
}
}
public extension Api.messages {
enum FeaturedStickers: TypeConstructorDescription {
case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64])
case featuredStickersNotModified(count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
if boxed {
buffer.appendInt32(-1103615738)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sets.count))
for item in sets {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(unread.count))
for item in unread {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .featuredStickersNotModified(let count):
if boxed {
buffer.appendInt32(-958657434)
}
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)])
case .featuredStickersNotModified(let count):
return ("featuredStickersNotModified", [("count", count as Any)])
}
}
public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: [Api.StickerSetCovered]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
}
var _5: [Int64]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!)
}
else {
return nil
}
}
public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,155 @@
public extension Api.messages {
enum FavedStickers: TypeConstructorDescription {
case favedStickers(hash: Int64, packs: [Api.StickerPack], stickers: [Api.Document])
case favedStickersNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .favedStickers(let hash, let packs, let stickers):
if boxed {
buffer.appendInt32(750063767)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(packs.count))
for item in packs {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stickers.count))
for item in stickers {
item.serialize(buffer, true)
}
break
case .favedStickersNotModified:
if boxed {
buffer.appendInt32(-1634752813)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .favedStickers(let hash, let packs, let stickers):
return ("favedStickers", [("hash", hash as Any), ("packs", packs as Any), ("stickers", stickers as Any)])
case .favedStickersNotModified:
return ("favedStickersNotModified", [])
}
}
public static func parse_favedStickers(_ reader: BufferReader) -> FavedStickers? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.StickerPack]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
}
var _3: [Api.Document]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.messages.FavedStickers.favedStickers(hash: _1!, packs: _2!, stickers: _3!)
}
else {
return nil
}
}
public static func parse_favedStickersNotModified(_ reader: BufferReader) -> FavedStickers? {
return Api.messages.FavedStickers.favedStickersNotModified
}
}
}
public extension Api.messages {
enum FeaturedStickers: TypeConstructorDescription {
case featuredStickers(flags: Int32, hash: Int64, count: Int32, sets: [Api.StickerSetCovered], unread: [Int64])
case featuredStickersNotModified(count: Int32)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
if boxed {
buffer.appendInt32(-1103615738)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(hash, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(sets.count))
for item in sets {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(unread.count))
for item in unread {
serializeInt64(item, buffer: buffer, boxed: false)
}
break
case .featuredStickersNotModified(let count):
if boxed {
buffer.appendInt32(-958657434)
}
serializeInt32(count, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .featuredStickers(let flags, let hash, let count, let sets, let unread):
return ("featuredStickers", [("flags", flags as Any), ("hash", hash as Any), ("count", count as Any), ("sets", sets as Any), ("unread", unread as Any)])
case .featuredStickersNotModified(let count):
return ("featuredStickersNotModified", [("count", count as Any)])
}
}
public static func parse_featuredStickers(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: Int32?
_3 = reader.readInt32()
var _4: [Api.StickerSetCovered]?
if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerSetCovered.self)
}
var _5: [Int64]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 570911930, elementType: Int64.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 {
return Api.messages.FeaturedStickers.featuredStickers(flags: _1!, hash: _2!, count: _3!, sets: _4!, unread: _5!)
}
else {
return nil
}
}
public static func parse_featuredStickersNotModified(_ reader: BufferReader) -> FeaturedStickers? {
var _1: Int32?
_1 = reader.readInt32()
let _c1 = _1 != nil
if _c1 {
return Api.messages.FeaturedStickers.featuredStickersNotModified(count: _1!)
}
else {
return nil
}
}
}
}
public extension Api.messages {
enum ForumTopics: TypeConstructorDescription {
case forumTopics(flags: Int32, count: Int32, topics: [Api.ForumTopic], messages: [Api.Message], chats: [Api.Chat], users: [Api.User], pts: Int32)
@ -1490,61 +1642,3 @@ public extension Api.messages {
}
}
public extension Api.messages {
enum SavedGifs: TypeConstructorDescription {
case savedGifs(hash: Int64, gifs: [Api.Document])
case savedGifsNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedGifs(let hash, let gifs):
if boxed {
buffer.appendInt32(-2069878259)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(gifs.count))
for item in gifs {
item.serialize(buffer, true)
}
break
case .savedGifsNotModified:
if boxed {
buffer.appendInt32(-402498398)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedGifs(let hash, let gifs):
return ("savedGifs", [("hash", hash as Any), ("gifs", gifs as Any)])
case .savedGifsNotModified:
return ("savedGifsNotModified", [])
}
}
public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.Document]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.SavedGifs.savedGifs(hash: _1!, gifs: _2!)
}
else {
return nil
}
}
public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? {
return Api.messages.SavedGifs.savedGifsNotModified
}
}
}

View file

@ -1,3 +1,61 @@
public extension Api.messages {
enum SavedGifs: TypeConstructorDescription {
case savedGifs(hash: Int64, gifs: [Api.Document])
case savedGifsNotModified
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .savedGifs(let hash, let gifs):
if boxed {
buffer.appendInt32(-2069878259)
}
serializeInt64(hash, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(gifs.count))
for item in gifs {
item.serialize(buffer, true)
}
break
case .savedGifsNotModified:
if boxed {
buffer.appendInt32(-402498398)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .savedGifs(let hash, let gifs):
return ("savedGifs", [("hash", hash as Any), ("gifs", gifs as Any)])
case .savedGifsNotModified:
return ("savedGifsNotModified", [])
}
}
public static func parse_savedGifs(_ reader: BufferReader) -> SavedGifs? {
var _1: Int64?
_1 = reader.readInt64()
var _2: [Api.Document]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.messages.SavedGifs.savedGifs(hash: _1!, gifs: _2!)
}
else {
return nil
}
}
public static func parse_savedGifsNotModified(_ reader: BufferReader) -> SavedGifs? {
return Api.messages.SavedGifs.savedGifsNotModified
}
}
}
public extension Api.messages {
enum SavedReactionTags: TypeConstructorDescription {
case savedReactionTags(tags: [Api.SavedReactionTag], hash: Int64)
@ -1436,179 +1494,3 @@ public extension Api.payments {
}
}
public extension Api.payments {
enum PaymentReceipt: TypeConstructorDescription {
case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User])
case paymentReceiptStars(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
if boxed {
buffer.appendInt32(1891958275)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeInt64(providerId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
invoice.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)}
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(credentialsTitle, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users):
if boxed {
buffer.appendInt32(-625215430)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
invoice.serialize(buffer, true)
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(transactionId, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)])
case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users):
return ("paymentReceiptStars", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("transactionId", transactionId as Any), ("users", users as Any)])
}
}
public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int64?
_4 = reader.readInt64()
var _5: String?
_5 = parseString(reader)
var _6: String?
_6 = parseString(reader)
var _7: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _8: Api.Invoice?
if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.Invoice
}
var _9: Api.PaymentRequestedInfo?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
} }
var _10: Api.ShippingOption?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.ShippingOption
} }
var _11: Int64?
if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() }
var _12: String?
_12 = parseString(reader)
var _13: Int64?
_13 = reader.readInt64()
var _14: String?
_14 = parseString(reader)
var _15: [Api.User]?
if let _ = reader.readInt32() {
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.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 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _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.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!)
}
else {
return nil
}
}
public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
var _4: String?
_4 = parseString(reader)
var _5: String?
_5 = parseString(reader)
var _6: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _7: Api.Invoice?
if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.Invoice
}
var _8: String?
_8 = parseString(reader)
var _9: Int64?
_9 = reader.readInt64()
var _10: String?
_10 = parseString(reader)
var _11: [Api.User]?
if let _ = reader.readInt32() {
_11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
let _c11 = _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.payments.PaymentReceipt.paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,179 @@
public extension Api.payments {
enum PaymentReceipt: TypeConstructorDescription {
case paymentReceipt(flags: Int32, date: Int32, botId: Int64, providerId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, info: Api.PaymentRequestedInfo?, shipping: Api.ShippingOption?, tipAmount: Int64?, currency: String, totalAmount: Int64, credentialsTitle: String, users: [Api.User])
case paymentReceiptStars(flags: Int32, date: Int32, botId: Int64, title: String, description: String, photo: Api.WebDocument?, invoice: Api.Invoice, currency: String, totalAmount: Int64, transactionId: String, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
if boxed {
buffer.appendInt32(1891958275)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeInt64(providerId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
invoice.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {info!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {shipping!.serialize(buffer, true)}
if Int(flags) & Int(1 << 3) != 0 {serializeInt64(tipAmount!, buffer: buffer, boxed: false)}
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(credentialsTitle, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users):
if boxed {
buffer.appendInt32(-625215430)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false)
serializeInt64(botId, buffer: buffer, boxed: false)
serializeString(title, buffer: buffer, boxed: false)
serializeString(description, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {photo!.serialize(buffer, true)}
invoice.serialize(buffer, true)
serializeString(currency, buffer: buffer, boxed: false)
serializeInt64(totalAmount, buffer: buffer, boxed: false)
serializeString(transactionId, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .paymentReceipt(let flags, let date, let botId, let providerId, let title, let description, let photo, let invoice, let info, let shipping, let tipAmount, let currency, let totalAmount, let credentialsTitle, let users):
return ("paymentReceipt", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("providerId", providerId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("info", info as Any), ("shipping", shipping as Any), ("tipAmount", tipAmount as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("credentialsTitle", credentialsTitle as Any), ("users", users as Any)])
case .paymentReceiptStars(let flags, let date, let botId, let title, let description, let photo, let invoice, let currency, let totalAmount, let transactionId, let users):
return ("paymentReceiptStars", [("flags", flags as Any), ("date", date as Any), ("botId", botId as Any), ("title", title as Any), ("description", description as Any), ("photo", photo as Any), ("invoice", invoice as Any), ("currency", currency as Any), ("totalAmount", totalAmount as Any), ("transactionId", transactionId as Any), ("users", users as Any)])
}
}
public static func parse_paymentReceipt(_ reader: BufferReader) -> PaymentReceipt? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
var _4: Int64?
_4 = reader.readInt64()
var _5: String?
_5 = parseString(reader)
var _6: String?
_6 = parseString(reader)
var _7: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _8: Api.Invoice?
if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.Invoice
}
var _9: Api.PaymentRequestedInfo?
if Int(_1!) & Int(1 << 0) != 0 {if let signature = reader.readInt32() {
_9 = Api.parse(reader, signature: signature) as? Api.PaymentRequestedInfo
} }
var _10: Api.ShippingOption?
if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() {
_10 = Api.parse(reader, signature: signature) as? Api.ShippingOption
} }
var _11: Int64?
if Int(_1!) & Int(1 << 3) != 0 {_11 = reader.readInt64() }
var _12: String?
_12 = parseString(reader)
var _13: Int64?
_13 = reader.readInt64()
var _14: String?
_14 = parseString(reader)
var _15: [Api.User]?
if let _ = reader.readInt32() {
_15 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.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 = (Int(_1!) & Int(1 << 2) == 0) || _7 != nil
let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 0) == 0) || _9 != nil
let _c10 = (Int(_1!) & Int(1 << 1) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 3) == 0) || _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.payments.PaymentReceipt.paymentReceipt(flags: _1!, date: _2!, botId: _3!, providerId: _4!, title: _5!, description: _6!, photo: _7, invoice: _8!, info: _9, shipping: _10, tipAmount: _11, currency: _12!, totalAmount: _13!, credentialsTitle: _14!, users: _15!)
}
else {
return nil
}
}
public static func parse_paymentReceiptStars(_ reader: BufferReader) -> PaymentReceipt? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: Int64?
_3 = reader.readInt64()
var _4: String?
_4 = parseString(reader)
var _5: String?
_5 = parseString(reader)
var _6: Api.WebDocument?
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.WebDocument
} }
var _7: Api.Invoice?
if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.Invoice
}
var _8: String?
_8 = parseString(reader)
var _9: Int64?
_9 = reader.readInt64()
var _10: String?
_10 = parseString(reader)
var _11: [Api.User]?
if let _ = reader.readInt32() {
_11 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
let _c11 = _11 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 {
return Api.payments.PaymentReceipt.paymentReceiptStars(flags: _1!, date: _2!, botId: _3!, title: _4!, description: _5!, photo: _6, invoice: _7!, currency: _8!, totalAmount: _9!, transactionId: _10!, users: _11!)
}
else {
return nil
}
}
}
}
public extension Api.payments {
indirect enum PaymentResult: TypeConstructorDescription {
case paymentResult(updates: Api.Updates)
@ -1568,113 +1744,3 @@ public extension Api.phone {
}
}
public extension Api.phone {
enum JoinAsPeers: TypeConstructorDescription {
case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .joinAsPeers(let peers, let chats, let users):
if boxed {
buffer.appendInt32(-1343921601)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(peers.count))
for item in peers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .joinAsPeers(let peers, let chats, let users):
return ("joinAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? {
var _1: [Api.Peer]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.phone.JoinAsPeers.joinAsPeers(peers: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum PhoneCall: TypeConstructorDescription {
case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .phoneCall(let phoneCall, let users):
if boxed {
buffer.appendInt32(-326966976)
}
phoneCall.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .phoneCall(let phoneCall, let users):
return ("phoneCall", [("phoneCall", phoneCall as Any), ("users", users as Any)])
}
}
public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? {
var _1: Api.PhoneCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.PhoneCall
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.phone.PhoneCall.phoneCall(phoneCall: _1!, users: _2!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,113 @@
public extension Api.phone {
enum JoinAsPeers: TypeConstructorDescription {
case joinAsPeers(peers: [Api.Peer], chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .joinAsPeers(let peers, let chats, let users):
if boxed {
buffer.appendInt32(-1343921601)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(peers.count))
for item in peers {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .joinAsPeers(let peers, let chats, let users):
return ("joinAsPeers", [("peers", peers as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_joinAsPeers(_ reader: BufferReader) -> JoinAsPeers? {
var _1: [Api.Peer]?
if let _ = reader.readInt32() {
_1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Peer.self)
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.phone.JoinAsPeers.joinAsPeers(peers: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
}
}
public extension Api.phone {
enum PhoneCall: TypeConstructorDescription {
case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .phoneCall(let phoneCall, let users):
if boxed {
buffer.appendInt32(-326966976)
}
phoneCall.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .phoneCall(let phoneCall, let users):
return ("phoneCall", [("phoneCall", phoneCall as Any), ("users", users as Any)])
}
}
public static func parse_phoneCall(_ reader: BufferReader) -> PhoneCall? {
var _1: Api.PhoneCall?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.PhoneCall
}
var _2: [Api.User]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.phone.PhoneCall.phoneCall(phoneCall: _1!, users: _2!)
}
else {
return nil
}
}
}
}
public extension Api.photos {
enum Photo: TypeConstructorDescription {
case photo(photo: Api.Photo, users: [Api.User])
@ -1384,141 +1494,3 @@ public extension Api.stories {
}
}
public extension Api.stories {
enum PeerStories: TypeConstructorDescription {
case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .peerStories(let stories, let chats, let users):
if boxed {
buffer.appendInt32(-890861720)
}
stories.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .peerStories(let stories, let chats, let users):
return ("peerStories", [("stories", stories as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? {
var _1: Api.PeerStories?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.PeerStories
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.stories.PeerStories.peerStories(stories: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
}
}
public extension Api.stories {
enum Stories: TypeConstructorDescription {
case stories(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users):
if boxed {
buffer.appendInt32(1673780490)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stories.count))
for item in stories {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(pinnedToTop!.count))
for item in pinnedToTop! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users):
return ("stories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("pinnedToTop", pinnedToTop as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_stories(_ reader: BufferReader) -> Stories? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.StoryItem]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self)
}
var _4: [Int32]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.stories.Stories.stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}

View file

@ -1,3 +1,141 @@
public extension Api.stories {
enum PeerStories: TypeConstructorDescription {
case peerStories(stories: Api.PeerStories, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .peerStories(let stories, let chats, let users):
if boxed {
buffer.appendInt32(-890861720)
}
stories.serialize(buffer, true)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .peerStories(let stories, let chats, let users):
return ("peerStories", [("stories", stories as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_peerStories(_ reader: BufferReader) -> PeerStories? {
var _1: Api.PeerStories?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.PeerStories
}
var _2: [Api.Chat]?
if let _ = reader.readInt32() {
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _3: [Api.User]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
if _c1 && _c2 && _c3 {
return Api.stories.PeerStories.peerStories(stories: _1!, chats: _2!, users: _3!)
}
else {
return nil
}
}
}
}
public extension Api.stories {
enum Stories: TypeConstructorDescription {
case stories(flags: Int32, count: Int32, stories: [Api.StoryItem], pinnedToTop: [Int32]?, chats: [Api.Chat], users: [Api.User])
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users):
if boxed {
buffer.appendInt32(1673780490)
}
serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(count, buffer: buffer, boxed: false)
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(stories.count))
for item in stories {
item.serialize(buffer, true)
}
if Int(flags) & Int(1 << 0) != 0 {buffer.appendInt32(481674261)
buffer.appendInt32(Int32(pinnedToTop!.count))
for item in pinnedToTop! {
serializeInt32(item, buffer: buffer, boxed: false)
}}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(chats.count))
for item in chats {
item.serialize(buffer, true)
}
buffer.appendInt32(481674261)
buffer.appendInt32(Int32(users.count))
for item in users {
item.serialize(buffer, true)
}
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .stories(let flags, let count, let stories, let pinnedToTop, let chats, let users):
return ("stories", [("flags", flags as Any), ("count", count as Any), ("stories", stories as Any), ("pinnedToTop", pinnedToTop as Any), ("chats", chats as Any), ("users", users as Any)])
}
}
public static func parse_stories(_ reader: BufferReader) -> Stories? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int32?
_2 = reader.readInt32()
var _3: [Api.StoryItem]?
if let _ = reader.readInt32() {
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StoryItem.self)
}
var _4: [Int32]?
if Int(_1!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
_4 = Api.parseVector(reader, elementSignature: -1471112230, elementType: Int32.self)
} }
var _5: [Api.Chat]?
if let _ = reader.readInt32() {
_5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self)
}
var _6: [Api.User]?
if let _ = reader.readInt32() {
_6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self)
}
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 {
return Api.stories.Stories.stories(flags: _1!, count: _2!, stories: _3!, pinnedToTop: _4, chats: _5!, users: _6!)
}
else {
return nil
}
}
}
}
public extension Api.stories {
enum StoryReactionsList: TypeConstructorDescription {
case storyReactionsList(flags: Int32, count: Int32, reactions: [Api.StoryReaction], chats: [Api.Chat], users: [Api.User], nextOffset: String?)

View file

@ -6245,6 +6245,21 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getEmojiGameInfo() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.EmojiGameInfo>) {
let buffer = Buffer()
buffer.appendInt32(-75592537)
return (FunctionDescription(name: "messages.getEmojiGameInfo", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.messages.EmojiGameInfo? in
let reader = BufferReader(buffer)
var result: Api.messages.EmojiGameInfo?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.messages.EmojiGameInfo
}
return result
})
}
}
public extension Api.functions.messages {
static func getEmojiGroups(hash: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.EmojiGroups>) {
let buffer = Buffer()

View file

@ -139,6 +139,7 @@ enum AccountStateMutationOperation {
case UpdateMonoForumNoPaidException(peerId: PeerId, threadId: Int64, isFree: Bool)
case UpdateStarGiftAuctionState(giftId: Int64, state: GiftAuctionContext.State.AuctionState)
case UpdateStarGiftAuctionMyState(giftId: Int64, state: GiftAuctionContext.State.MyState)
case UpdateEmojiGameInfo(info: EmojiGameInfo)
}
struct HoleFromPreviousState {
@ -737,9 +738,13 @@ struct AccountMutableState {
self.addOperation(.UpdateStarGiftAuctionMyState(giftId: giftId, state: state))
}
mutating func updateEmojiGameInfo(info: EmojiGameInfo) {
self.addOperation(.UpdateEmojiGameInfo(info: info))
}
mutating func addOperation(_ operation: AccountStateMutationOperation) {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .UpdateWallpaper, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateMessagesPinned, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
break
case let .AddMessages(messages, location):
for message in messages {
@ -892,6 +897,7 @@ struct AccountReplayedFinalState {
let addedConferenceInvitationMessagesIds: [MessageId]
let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState]
let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState]
let updatedEmojiGameInfo: EmojiGameInfo?
}
struct AccountFinalStateEvents {
@ -927,12 +933,13 @@ struct AccountFinalStateEvents {
let addedConferenceInvitationMessagesIds: [MessageId]
let updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState]
let updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState]
let updatedEmojiGameInfo: EmojiGameInfo?
var isEmpty: Bool {
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty
return self.addedIncomingMessageIds.isEmpty && self.addedReactionEvents.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.sentScheduledMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.groupCallMessageUpdates.isEmpty && self.storyUpdates.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.dismissBotWebViews.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty && !self.updateConfig && !self.isPremiumUpdated && self.updatedStarsBalance.isEmpty && self.updatedTonBalance.isEmpty && self.updatedStarsRevenueStatus.isEmpty && self.reportMessageDelivery.isEmpty && self.addedConferenceInvitationMessagesIds.isEmpty && self.updatedStarGiftAuctionState.isEmpty && self.updatedStarGiftAuctionMyState.isEmpty && self.updatedEmojiGameInfo == nil
}
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:]) {
init(addedIncomingMessageIds: [MessageId] = [], addedReactionEvents: [(reactionAuthor: Peer, reaction: MessageReaction.Reaction, message: Message, timestamp: Int32)] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, GroupCallParticipantsContext.Update)] = [], groupCallMessageUpdates: [GroupCallMessageUpdate] = [], storyUpdates: [InternalStoryUpdate] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], dismissBotWebViews: [Int64] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set<PeerId> = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [PeerAndBoundThreadId: MessageId.Id] = [:], updateConfig: Bool = false, isPremiumUpdated: Bool = false, updatedStarsBalance: [PeerId: StarsAmount] = [:], updatedTonBalance: [PeerId: StarsAmount] = [:], updatedStarsRevenueStatus: [PeerId: StarsRevenueStats.Balances] = [:], sentScheduledMessageIds: Set<MessageId> = Set(), reportMessageDelivery: Set<MessageId> = Set(), addedConferenceInvitationMessagesIds: [MessageId] = [], updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:], updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:], updatedEmojiGameInfo: EmojiGameInfo? = nil) {
self.addedIncomingMessageIds = addedIncomingMessageIds
self.addedReactionEvents = addedReactionEvents
self.wasScheduledMessageIds = wasScheduledMessageIds
@ -965,6 +972,7 @@ struct AccountFinalStateEvents {
self.addedConferenceInvitationMessagesIds = addedConferenceInvitationMessagesIds
self.updatedStarGiftAuctionState = updatedStarGiftAuctionState
self.updatedStarGiftAuctionMyState = updatedStarGiftAuctionMyState
self.updatedEmojiGameInfo = updatedEmojiGameInfo
}
init(state: AccountReplayedFinalState) {
@ -1000,6 +1008,7 @@ struct AccountFinalStateEvents {
self.addedConferenceInvitationMessagesIds = state.addedConferenceInvitationMessagesIds
self.updatedStarGiftAuctionState = state.updatedStarGiftAuctionState
self.updatedStarGiftAuctionMyState = state.updatedStarGiftAuctionMyState
self.updatedEmojiGameInfo = state.updatedEmojiGameInfo
}
func union(with other: AccountFinalStateEvents) -> AccountFinalStateEvents {
@ -1068,7 +1077,8 @@ struct AccountFinalStateEvents {
reportMessageDelivery: reportMessageDelivery,
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds,
updatedStarGiftAuctionState: self.updatedStarGiftAuctionState.merging(other.updatedStarGiftAuctionState, uniquingKeysWith: { lhs, _ in lhs }),
updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs })
updatedStarGiftAuctionMyState: self.updatedStarGiftAuctionMyState.merging(other.updatedStarGiftAuctionMyState, uniquingKeysWith: { lhs, _ in lhs }),
updatedEmojiGameInfo: self.updatedEmojiGameInfo
)
}
}

View file

@ -457,8 +457,15 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
}
return (TelegramMediaTodo(flags: flags, text: todoText, textEntities: todoEntities, items: list.map(TelegramMediaTodo.Item.init(apiItem:)), completions: todoCompletions), nil, nil, nil, nil, nil)
}
case let .messageMediaDice(value, emoticon):
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil, nil, nil)
case let .messageMediaDice(_, value, emoticon, apiGameOutcome):
var gameOutcome: TelegramMediaDice.GameOutcome?
switch apiGameOutcome {
case let .emojiGameOutcome(seed, tonAmount):
gameOutcome = TelegramMediaDice.GameOutcome(seed: seed.makeData(), tonAmount: tonAmount)
default:
break
}
return (TelegramMediaDice(emoji: emoticon, value: value, gameOutcome: gameOutcome), nil, nil, nil, nil, nil)
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, nil, nil)

View file

@ -343,8 +343,26 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post
let inputTodo = Api.InputMedia.inputMediaTodo(todo: .todoList(flags: flags, title: .textWithEntities(text: todo.text, entities: apiEntitiesFromMessageTextEntities(todo.textEntities, associatedPeers: SimpleDictionary())), list: todo.items.map { $0.apiItem }))
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputTodo, text), reuploadInfo: nil, cacheReferenceKey: nil)))
} else if let dice = media as? TelegramMediaDice {
let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
if let tonAmount = dice.tonAmount {
let seedBytes = malloc(32)!
let _ = SecRandomCopyBytes(nil, 32, seedBytes.assumingMemoryBound(to: UInt8.self))
let clientSeed = MemoryBuffer(memory: seedBytes, capacity: 32, length: 32, freeWhenDone: true)
return postbox.transaction { transaction -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
let gameInfo = currentEmojiGameInfo(transaction: transaction)
if case let .available(info) = gameInfo {
let inputStakeDice = Api.InputMedia.inputMediaStakeDice(gameHash: info.gameHash, tonAmount: tonAmount, clientSeed: Buffer(buffer: clientSeed))
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputStakeDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
} else {
return .fail(.generic)
}
}
|> castError(PendingMessageUploadError.self)
|> switchToLatest
} else {
let inputDice = Api.InputMedia.inputMediaDice(emoticon: dice.emoji)
return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil)))
}
} else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content {
var flags: Int32 = 0
flags |= 1 << 2

View file

@ -1906,6 +1906,8 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
}
case let .updateStarGiftAuctionUserState(giftId, userState):
updatedState.updateStarGiftAuctionMyState(giftId: giftId, state: GiftAuctionContext.State.MyState(apiAuctionUserState: userState))
case let .updateEmojiGameInfo(info):
updatedState.updateEmojiGameInfo(info: EmojiGameInfo(apiEmojiGameInfo: info))
default:
break
}
@ -3629,7 +3631,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation])
var currentAddQuickReplyMessages: OptimizeAddMessagesState?
for operation in operations {
switch operation {
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState:
case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll, .UpdateMessageReactions, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .AddPeerLiveTypingDraftUpdate, .UpdateCachedPeerData, .UpdatePinnedItemIds, .UpdatePinnedSavedItemIds, .UpdatePinnedTopic, .UpdatePinnedTopicOrder, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipants, .UpdateGroupCall, .UpdateGroupCallChainBlocks, .UpdateGroupCallMessage, .UpdateGroupCallOpaqueMessage, .UpdateAutoremoveTimeout, .UpdateAttachMenuBots, .UpdateAudioTranscription, .UpdateConfig, .UpdateExtendedMedia, .ResetForumTopic, .UpdateStory, .UpdateReadStories, .UpdateStoryStealthMode, .UpdateStorySentReaction, .UpdateNewAuthorization, .UpdateWallpaper, .UpdateStarsBalance, .UpdateStarsRevenueStatus, .UpdateStarsReactionsDefaultPrivacy, .ReportMessageDelivery, .UpdateMonoForumNoPaidException, .UpdateStarGiftAuctionState, .UpdateStarGiftAuctionMyState, .UpdateEmojiGameInfo:
if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty {
result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location))
}
@ -3772,6 +3774,7 @@ func replayFinalState(
var reportMessageDelivery = Set<MessageId>()
var updatedStarGiftAuctionState: [Int64: GiftAuctionContext.State.AuctionState] = [:]
var updatedStarGiftAuctionMyState: [Int64: GiftAuctionContext.State.MyState] = [:]
var updatedEmojiGameInfo: EmojiGameInfo?
var holesFromPreviousStateMessageIds: [MessageId] = []
var clearHolesFromPreviousStateForChannelMessagesWithPts: [PeerIdAndMessageNamespace: Int32] = [:]
@ -5339,6 +5342,8 @@ func replayFinalState(
updatedStarGiftAuctionState[giftId] = state
case let .UpdateStarGiftAuctionMyState(giftId, state):
updatedStarGiftAuctionMyState[giftId] = state
case let .UpdateEmojiGameInfo(info):
updatedEmojiGameInfo = info
}
}
@ -5889,6 +5894,7 @@ func replayFinalState(
reportMessageDelivery: reportMessageDelivery,
addedConferenceInvitationMessagesIds: addedConferenceInvitationMessagesIds,
updatedStarGiftAuctionState: updatedStarGiftAuctionState,
updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState
updatedStarGiftAuctionMyState: updatedStarGiftAuctionMyState,
updatedEmojiGameInfo: updatedEmojiGameInfo
)
}

View file

@ -374,6 +374,7 @@ public final class AccountStateManager {
private let appliedQtsPromise = Promise<Int32?>(nil)
private let appliedQtsDisposable = MetaDisposable()
private let reportMessageDeliveryDisposable = DisposableSet()
private let updateEmojiGameInfoDisposable = MetaDisposable()
let updateConfigRequested: (() -> Void)?
let isPremiumUpdated: (() -> Void)?
@ -414,6 +415,7 @@ public final class AccountStateManager {
self.appliedMaxMessageIdDisposable.dispose()
self.appliedQtsDisposable.dispose()
self.reportMessageDeliveryDisposable.dispose()
self.updateEmojiGameInfoDisposable.dispose()
}
public func reset() {
@ -1137,6 +1139,11 @@ public final class AccountStateManager {
if !events.updatedStarGiftAuctionMyState.isEmpty {
strongSelf.notifyUpdatedStarGiftAuctionMyState(events.updatedStarGiftAuctionMyState)
}
if let updatedEmojiGameInfo = events.updatedEmojiGameInfo {
strongSelf.updateEmojiGameInfoDisposable.set(strongSelf.postbox.transaction({ transaction in
updateEmojiGameInfo(transaction: transaction, { _ in return updatedEmojiGameInfo })
}).start())
}
if !events.updatedCalls.isEmpty {
for call in events.updatedCalls {
strongSelf.callSessionManager?.updateSession(call, completion: { _ in })

View file

@ -125,6 +125,7 @@ final class AccountTaskManager {
tasks.add(managedPeerColorUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
tasks.add(managedStarGiftsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
tasks.add(managedSavedMusicIdsUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
tasks.add(managedEmojiGameUpdates(postbox: self.stateManager.postbox, network: self.stateManager.network).start())
self.managedTopReactionsDisposable.set(managedTopReactions(postbox: self.stateManager.postbox, network: self.stateManager.network).start())

View file

@ -0,0 +1,104 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
public enum EmojiGameInfo: Codable, Equatable {
private enum CodingKeys: String, CodingKey {
case type
case info
}
public struct Info: Codable, Equatable {
public let gameHash: String
public let previousStake: Int64
public let currentStreak: Int32
public let parameters: [Int32]
public let playsLeft: Int32?
}
case available(Info)
case unavailable
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(Int32.self, forKey: .type)
switch type {
case 1:
self = .available(try container.decode(Info.self, forKey: .info))
default:
self = .unavailable
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .available(info):
try container.encode(Int32(1), forKey: .type)
try container.encode(info, forKey: .info)
case .unavailable:
try container.encode(Int32(0), forKey: .type)
}
}
}
extension EmojiGameInfo {
init(apiEmojiGameInfo: Api.messages.EmojiGameInfo) {
switch apiEmojiGameInfo {
case let .emojiGameDiceInfo(_, gameHash, prevStake, currentStreak, params, playsLeft):
self = .available(Info(gameHash: gameHash, previousStake: prevStake, currentStreak: currentStreak, parameters: params, playsLeft: playsLeft))
case .emojiGameUnavailable:
self = .unavailable
}
}
}
public func currentEmojiGameInfo(transaction: Transaction) -> EmojiGameInfo {
if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.emojiGameInfo())?.get(EmojiGameInfo.self) {
return entry
} else {
return .unavailable
}
}
func updateEmojiGameInfo(transaction: Transaction, _ f: (EmojiGameInfo) -> EmojiGameInfo) {
let current = currentEmojiGameInfo(transaction: transaction)
let updated = f(current)
if updated != current {
transaction.setPreferencesEntry(key: PreferencesKeys.emojiGameInfo(), value: PreferencesEntry(updated))
}
}
func updateEmojiGameInfoOnce(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
return network.request(Api.functions.messages.getEmojiGameInfo())
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.EmojiGameInfo?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<Void, NoError> in
guard let result else {
return .complete()
}
return postbox.transaction { transaction -> Void in
let info = EmojiGameInfo(apiEmojiGameInfo: result)
updateEmojiGameInfo(transaction: transaction) { _ in
return info
}
}
}
}
func managedEmojiGameUpdates(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let poll = Signal<Void, NoError> { subscriber in
return updateEmojiGameInfoOnce(postbox: postbox, network: network).start(completed: {
subscriber.putCompletion()
})
}
return (poll |> then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}

View file

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 220
return 221
}
public func parseMessage(_ data: Data!) -> Any! {

View file

@ -323,6 +323,7 @@ private enum PreferencesKeyValues: Int32 {
case persistentChatInterfaceData = 45
case globalPostSearchState = 46
case savedMusicIds = 47
case emojiGameInfo = 48
}
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
@ -591,6 +592,12 @@ public struct PreferencesKeys {
key.setInt32(0, value: PreferencesKeyValues.savedMusicIds.rawValue)
return key
}
public static func emojiGameInfo() -> ValueBoxKey {
let key = ValueBoxKey(length: 4)
key.setInt32(0, value: PreferencesKeyValues.emojiGameInfo.rawValue)
return key
}
}
private enum SharedDataKeyValues: Int32 {

View file

@ -1,29 +1,57 @@
import Foundation
import Postbox
public final class TelegramMediaDice: Media, Equatable {
public struct GameOutcome: Equatable {
let seed: Data
public let tonAmount: Int64
}
public let emoji: String
public let tonAmount: Int64?
public let value: Int32?
public let gameOutcome: GameOutcome?
public let id: MediaId? = nil
public let peerIds: [PeerId] = []
public init(emoji: String, value: Int32? = nil) {
public init(emoji: String, tonAmount: Int64? = nil, value: Int32? = nil, gameOutcome: GameOutcome? = nil) {
self.emoji = emoji
self.tonAmount = tonAmount
self.value = value
self.gameOutcome = gameOutcome
}
public init(decoder: PostboxDecoder) {
self.emoji = decoder.decodeStringForKey("e", orElse: "🎲")
self.tonAmount = decoder.decodeOptionalInt64ForKey("ta")
self.value = decoder.decodeOptionalInt32ForKey("v")
if let seed = decoder.decodeDataForKey("gos"), let tonAmount = decoder.decodeOptionalInt64ForKey("goa") {
self.gameOutcome = GameOutcome(seed: seed, tonAmount: tonAmount)
} else {
self.gameOutcome = nil
}
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.emoji, forKey: "e")
if let tonAmount = self.tonAmount {
encoder.encodeInt64(tonAmount, forKey: "ta")
} else {
encoder.encodeNil(forKey: "ta")
}
if let value = self.value {
encoder.encodeInt32(value, forKey: "v")
} else {
encoder.encodeNil(forKey: "v")
}
if let gameOutcome = self.gameOutcome {
encoder.encodeData(gameOutcome.seed, forKey: "gos")
encoder.encodeInt64(gameOutcome.tonAmount, forKey: "goa")
} else {
encoder.encodeNil(forKey: "gos")
encoder.encodeNil(forKey: "goa")
}
}
public static func ==(lhs: TelegramMediaDice, rhs: TelegramMediaDice) -> Bool {
@ -35,9 +63,15 @@ public final class TelegramMediaDice: Media, Equatable {
if self.emoji != other.emoji {
return false
}
if self.tonAmount != other.tonAmount {
return false
}
if self.value != other.value {
return false
}
if self.gameOutcome != other.gameOutcome {
return false
}
return true
}
return false

View file

@ -571,5 +571,26 @@ public extension TelegramEngine.EngineData.Item {
return value
}
}
public struct EmojiGame: TelegramEngineDataItem, PostboxViewDataItem {
public typealias Result = EmojiGameInfo
public init() {
}
var key: PostboxViewKey {
return .preferences(keys: Set([PreferencesKeys.emojiGameInfo()]))
}
func extract(view: PostboxView) -> Result {
guard let view = view as? PreferencesView else {
preconditionFailure()
}
guard let emojiGameInfo = view.values[PreferencesKeys.emojiGameInfo()]?.get(EmojiGameInfo.self) else {
return .unavailable
}
return emojiGameInfo
}
}
}
}

View file

@ -193,12 +193,8 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case incomingVideoMessagePlayOnceTip = 62
case outgoingVideoMessagePlayOnceTip = 63
case savedMessageTagLabelSuggestion = 65
case dismissedBusinessBadge = 68
case monetizationIntroDismissed = 70
case businessBotMessageTooltip = 71
case dismissedBusinessIntroBadge = 72
case dismissedBusinessLinksBadge = 73
case dismissedBusinessChatbotsBadge = 74
case captionAboveMediaTooltip = 75
case channelSendGiftTooltip = 76
case starGiftWearTips = 77
@ -519,10 +515,6 @@ private struct ApplicationSpecificNoticeKeys {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.savedMessageTagLabelSuggestion.key)
}
static func dismissedBusinessBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessBadge.key)
}
static func dismissedBirthdayPremiumGiftTip(peerId: PeerId) -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: dismissedBirthdayPremiumGiftTipNamespace), key: noticeKey(peerId: peerId, key: 0))
}
@ -543,18 +535,6 @@ private struct ApplicationSpecificNoticeKeys {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.businessBotMessageTooltip.key)
}
static func dismissedBusinessIntroBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessIntroBadge.key)
}
static func dismissedBusinessLinksBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessLinksBadge.key)
}
static func dismissedBusinessChatbotsBadge() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.dismissedBusinessChatbotsBadge.key)
}
static func captionAboveMediaTooltip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.captionAboveMediaTooltip.key)
}
@ -2154,27 +2134,6 @@ public struct ApplicationSpecificNotice {
return Int(previousValue)
}
}
public static func setDismissedBusinessBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessBadge(), entry)
}
}
|> ignoreValues
}
public static func dismissedBusinessBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessBadge())
|> map { view -> Bool in
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
return true
} else {
return false
}
}
|> take(1)
}
public static func dismissedBirthdayPremiumGiftTip(accountManager: AccountManager<TelegramAccountManagerTypes>, peerId: PeerId) -> Signal<Int32?, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBirthdayPremiumGiftTip(peerId: peerId))
@ -2286,69 +2245,6 @@ public struct ApplicationSpecificNotice {
}
}
public static func setDismissedBusinessLinksBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessLinksBadge(), entry)
}
}
|> ignoreValues
}
public static func dismissedBusinessLinksBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessLinksBadge())
|> map { view -> Bool in
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
return true
} else {
return false
}
}
|> take(1)
}
public static func setDismissedBusinessIntroBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessIntroBadge(), entry)
}
}
|> ignoreValues
}
public static func dismissedBusinessIntroBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessIntroBadge())
|> map { view -> Bool in
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
return true
} else {
return false
}
}
|> take(1)
}
public static func setDismissedBusinessChatbotsBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Never, NoError> {
return accountManager.transaction { transaction -> Void in
if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
transaction.setNotice(ApplicationSpecificNoticeKeys.dismissedBusinessChatbotsBadge(), entry)
}
}
|> ignoreValues
}
public static func dismissedBusinessChatbotsBadge(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Bool, NoError> {
return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedBusinessChatbotsBadge())
|> map { view -> Bool in
if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
return true
} else {
return false
}
}
|> take(1)
}
public static func getCaptionAboveMediaTooltip(accountManager: AccountManager<TelegramAccountManagerTypes>) -> Signal<Int32, NoError> {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.captionAboveMediaTooltip())?.get(ApplicationSpecificCounterNotice.self) {

View file

@ -107,7 +107,7 @@ public final class PermissionContentNode: ASDisplayNode {
self.textNode.displaysAsynchronously = false
self.textNode.isAccessibilityElement = true
self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), height: 52.0, cornerRadius: 9.0, isShimmering: true)
self.actionButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), glass: true, height: 52.0, cornerRadius: 26.0, isShimmering: true)
self.footerNode = ImmediateTextNode()
self.footerNode.textAlignment = .center

View file

@ -36,15 +36,17 @@ public final class PermissionController: ViewController {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.splashScreen = splashScreen
let navigationBarPresentationData: NavigationBarPresentationData
if splashScreen {
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
} else {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
}
// let navigationBarPresentationData: NavigationBarPresentationData
// if splashScreen {
// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear, style: .glass), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
// } else {
let navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)
// }
super.init(navigationBarPresentationData: navigationBarPresentationData)
self._hasGlassStyle = true
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.updateThemeAndStrings()
@ -86,15 +88,15 @@ public final class PermissionController: ViewController {
private func updateThemeAndStrings() {
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
let navigationBarPresentationData: NavigationBarPresentationData
if self.splashScreen {
navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
} else {
navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData)
}
// let navigationBarPresentationData: NavigationBarPresentationData
// if self.splashScreen {
// navigationBarPresentationData = NavigationBarPresentationData(theme: NavigationBarTheme(overallDarkAppearance: self.presentationData.theme.overallDarkAppearance, buttonColor: self.presentationData.theme.rootController.navigationBar.accentTextColor, disabledButtonColor: self.presentationData.theme.rootController.navigationBar.disabledButtonColor, primaryTextColor: self.presentationData.theme.rootController.navigationBar.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear), strings: NavigationBarStrings(presentationStrings: self.presentationData.strings))
// } else {
let navigationBarPresentationData = NavigationBarPresentationData(presentationData: self.presentationData, style: .glass)
// }
self.navigationBar?.updatePresentationData(navigationBarPresentationData, transition: .immediate)
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
//self.navigationItem.backBarButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil)
if self.navigationItem.rightBarButtonItem != nil {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Permissions_Skip, style: .plain, target: self, action: #selector(PermissionController.nextPressed))
}

View file

@ -1764,6 +1764,17 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
resultTitleString = strings.Conversation_StoryExpiredMentionTextOutgoing(compactPeerName)
}
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
} else if let dice = media as? TelegramMediaDice, let gameOutcome = dice.gameOutcome {
if let value = dice.value, value > 1 {
//TODO:localize
let value = formatTonAmountText(gameOutcome.tonAmount, dateTimeFormat: dateTimeFormat)
let attributedText = NSMutableAttributedString(string: "You won $\(value)", font: titleFont, textColor: primaryTextColor)
if let range = attributedText.string.range(of: "$") {
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: true)), range: NSRange(range, in: attributedText.string))
attributedText.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: attributedText.string))
}
attributedString = attributedText
}
}
}

View file

@ -508,6 +508,8 @@ swift_library(
"//submodules/TelegramUI/Components/Chat/ChatSearchNavigationContentNode",
"//submodules/TelegramUI/Components/Settings/PasskeysScreen",
"//submodules/TelegramUI/Components/Gifts/GiftDemoScreen",
"//submodules/TelegramUI/Components/EmojiGameStakeScreen",
"//submodules/TelegramUI/Components/AlertComponent",
] + select({
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
"//build-system:ios_sim_arm64": [],

View file

@ -12,9 +12,17 @@ swift_library(
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/TelegramPresentationData",
"//submodules/ComponentFlow",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/Markdown",
"//submodules/TextFormat",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/ActivityIndicatorComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
],
visibility = [
"//visibility:public",

View file

@ -0,0 +1,195 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import AccountContext
import TelegramPresentationData
import MultilineTextComponent
import GlassBackgroundComponent
import ActivityIndicatorComponent
private let titleFont = Font.medium(17.0)
private let boldTitleFont = Font.semibold(17.0)
final class AlertActionComponent: Component {
typealias EnvironmentType = AlertComponentEnvironment
static let actionHeight: CGFloat = 48.0
struct Theme: Equatable {
enum Font {
case regular
case bold
}
let background: UIColor
let foreground: UIColor
let secondary: UIColor
let font: Font
}
let theme: Theme
let title: String
let isHighlighted: Bool
let progress: Signal<Bool, NoError>
init(
theme: Theme,
title: String,
isHighlighted: Bool,
progress: Signal<Bool, NoError>
) {
self.theme = theme
self.title = title
self.isHighlighted = isHighlighted
self.progress = progress
}
static func ==(lhs: AlertActionComponent, rhs: AlertActionComponent) -> Bool {
if lhs.theme != rhs.theme {
return false
}
if lhs.title != rhs.title {
return false
}
if lhs.isHighlighted != rhs.isHighlighted {
return false
}
return true
}
final class View: UIView {
private let backgroundView = UIView()
private let title = ComponentView<Empty>()
private var activity: ComponentView<Empty>?
private var component: AlertActionComponent?
private weak var state: EmptyComponentState?
private var progressDisposable: Disposable?
private var hasProgress = false
private var isUpdating = false
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundView.clipsToBounds = true
self.addSubview(self.backgroundView)
}
required init?(coder: NSCoder) {
preconditionFailure()
}
func update(component: AlertActionComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
}
if self.component == nil {
self.progressDisposable = (component.progress
|> deliverOnMainQueue).start(next: { [weak self] hasProgress in
guard let self else {
return
}
self.hasProgress = hasProgress
if !self.isUpdating {
self.state?.updated(transition: .easeInOut(duration: 0.25))
}
})
}
self.component = component
self.state = state
let attributedString = NSMutableAttributedString(string: component.title, font: component.theme.font == .bold ? boldTitleFont : titleFont, textColor: .white, paragraphAlignment: .center)
if let range = attributedString.string.range(of: "$") {
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.foregroundColor, value: UIColor.white, range: NSRange(range, in: attributedString.string))
attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string))
}
let titlePadding: CGFloat = 16.0
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(attributedString),
horizontalAlignment: .center,
maximumNumberOfLines: 1,
tintColor: component.theme.foreground
)),
environment: {},
containerSize: CGSize(width: availableSize.width - titlePadding * 2.0, height: availableSize.height)
)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
titleView.bounds = CGRect(origin: .zero, size: titleSize)
transition.setAlpha(view: titleView, alpha: self.hasProgress ? 0.0 : 1.0)
}
if self.hasProgress {
let activity: ComponentView<Empty>
if let current = self.activity {
activity = current
} else {
activity = ComponentView()
self.activity = activity
}
let activitySize = activity.update(
transition: transition,
component: AnyComponent(ActivityIndicatorComponent(color: component.theme.secondary)),
environment: {},
containerSize: availableSize
)
if let activityView = activity.view {
if activityView.superview == nil {
self.addSubview(activityView)
transition.animateAlpha(view: activityView, from: 0.0, to: 1.0)
}
activityView.bounds = CGRect(origin: .zero, size: activitySize)
}
} else if let activity = self.activity {
self.activity = nil
if let activityView = activity.view {
transition.setAlpha(view: activityView, alpha: 0.0, completion: { _ in
activityView.removeFromSuperview()
})
}
}
transition.setBackgroundColor(view: self.backgroundView, color: component.theme.background)
transition.setAlpha(view: self.backgroundView, alpha: component.isHighlighted ? 0.35 : 1.0)
self.backgroundView.layer.cornerRadius = availableSize.height * 0.5
return CGSize(width: titleSize.width + titlePadding * 2.0, height: availableSize.height)
}
func applySize(size: CGSize, transition: ComponentTransition) {
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
if let titleView = self.title.view {
let titleSize = titleView.bounds.size
let titleFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
transition.setFrame(view: titleView, frame: titleFrame)
}
if let activityView = self.activity?.view {
let activitySize = activityView.bounds.size
let activityFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - activitySize.width) / 2.0), y: floorToScreenPixels((size.height - activitySize.height) / 2.0)), size: activitySize)
transition.setFrame(view: activityView, frame: activityFrame)
}
}
}
func makeView() -> View {
return View(frame: CGRect())
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,218 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import TelegramPresentationData
import MultilineTextComponent
import Markdown
import TextFormat
private let titleFont = Font.bold(17.0)
private let defaultTextFont = Font.regular(15.0)
private let defaultBoldTextFont = Font.semibold(15.0)
private let smallTextFont = Font.regular(14.0)
private let smallBoldTextFont = Font.semibold(14.0)
public final class AlertTitleComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
let title: String
public init(
title: String
) {
self.title = title
}
public static func ==(lhs: AlertTitleComponent, rhs: AlertTitleComponent) -> Bool {
if lhs.title != rhs.title {
return false
}
return true
}
public final class View: UIView {
private let title = ComponentView<Empty>()
private var component: AlertTitleComponent?
private weak var state: EmptyComponentState?
func update(component: AlertTitleComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let titleSize = self.title.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(
string: component.title,
font: titleFont,
textColor: environment.theme.actionSheet.primaryTextColor
)),
maximumNumberOfLines: 0
)),
environment: {},
containerSize: availableSize
)
let titleFrame = CGRect(origin: .zero, size: titleSize)
if let titleView = self.title.view {
if titleView.superview == nil {
self.addSubview(titleView)
}
transition.setFrame(view: titleView, frame: titleFrame)
}
return CGSize(width: availableSize.width, height: titleSize.height)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
public final class AlertTextComponent: Component {
public typealias EnvironmentType = AlertComponentEnvironment
public enum Content: Equatable {
case plain(String)
case attributed(NSAttributedString)
}
public enum Color {
case primary
case secondary
case destructive
}
public enum TextSize {
case `default`
case small
}
let content: Content
let color: Color
let textSize: TextSize
let action: ([NSAttributedString.Key: Any]) -> Void
public init(
content: Content,
color: Color = .primary,
textSize: TextSize = .default,
action: @escaping ([NSAttributedString.Key: Any]) -> Void = { _ in }
) {
self.content = content
self.color = color
self.textSize = textSize
self.action = action
}
public static func ==(lhs: AlertTextComponent, rhs: AlertTextComponent) -> Bool {
if lhs.content != rhs.content {
return false
}
if lhs.textSize != rhs.textSize {
return false
}
if lhs.color != rhs.color {
return false
}
return true
}
public final class View: UIView {
private let text = ComponentView<Empty>()
private var component: AlertTextComponent?
private weak var state: EmptyComponentState?
func update(component: AlertTextComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let textColor: UIColor
switch component.color {
case .primary:
textColor = environment.theme.actionSheet.primaryTextColor
case .secondary:
textColor = environment.theme.actionSheet.secondaryTextColor
case .destructive:
textColor = environment.theme.actionSheet.destructiveActionTextColor
}
let linkColor = environment.theme.actionSheet.controlAccentColor
let textFont: UIFont
let boldTextFont: UIFont
switch component.textSize {
case .default:
textFont = defaultTextFont
boldTextFont = defaultBoldTextFont
case .small:
textFont = smallTextFont
boldTextFont = smallBoldTextFont
}
var finalText: NSAttributedString
switch component.content {
case let .plain(text):
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: textFont, textColor: textColor),
bold: MarkdownAttributeSet(font: boldTextFont, textColor: textColor),
link: MarkdownAttributeSet(font: textFont, textColor: linkColor),
linkAttribute: { contents in
return (TelegramTextAttributes.URL, contents)
}
)
finalText = parseMarkdownIntoAttributedString(text, attributes: markdownAttributes)
case let .attributed(attributedText):
finalText = attributedText
}
let textSize = self.text.update(
transition: transition,
component: AnyComponent(MultilineTextComponent(
text: .plain(finalText),
maximumNumberOfLines: 0,
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.action(attributes)
}
)),
environment: {},
containerSize: availableSize
)
let textFrame = CGRect(origin: .zero, size: textSize)
if let textView = self.text.view {
if textView.superview == nil {
self.addSubview(textView)
}
transition.setFrame(view: textView, frame: textFrame)
}
return CGSize(width: availableSize.width, height: textSize.height)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,395 @@
import Foundation
import UIKit
import Display
import TelegramPresentationData
import ComponentFlow
import ComponentDisplayAdapters
import AsyncDisplayKit
private let alertWidth: CGFloat = 270.0
public enum ComponentAlertActionType {
case genericAction
case defaultAction
case destructiveAction
case defaultDestructiveAction
}
public struct ComponentAlertAction {
public let type: ComponentAlertActionType
public let title: String
public let action: () -> Void
public init(type: ComponentAlertActionType, title: String, action: @escaping () -> Void) {
self.type = type
self.title = title
self.action = action
}
}
public final class ComponentAlertContentActionNode: HighlightableButtonNode {
private var theme: AlertControllerTheme
public var action: ComponentAlertAction {
didSet {
self.updateTitle()
}
}
private let backgroundNode: ASDisplayNode
public var highlightedUpdated: (Bool) -> Void = { _ in }
public init(theme: AlertControllerTheme, action: ComponentAlertAction) {
self.theme = theme
self.action = action
self.backgroundNode = ASDisplayNode()
self.backgroundNode.isLayerBacked = true
self.backgroundNode.alpha = 0.0
super.init()
self.titleNode.maximumNumberOfLines = 2
self.highligthedChanged = { [weak self] value in
if let strongSelf = self {
strongSelf.setHighlighted(value, animated: true)
}
}
self.updateTheme(theme)
}
public override func didLoad() {
super.didLoad()
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
self.pointerInteraction = PointerInteraction(node: self, style: .hover, willEnter: { [weak self] in
if let strongSelf = self {
strongSelf.setHighlighted(true, animated: false)
}
}, willExit: { [weak self] in
if let strongSelf = self {
strongSelf.setHighlighted(false, animated: false)
}
})
}
public func performAction() {
if self.actionEnabled {
self.action.action()
}
}
public func setHighlighted(_ highlighted: Bool, animated: Bool) {
self.highlightedUpdated(highlighted)
if highlighted {
if self.backgroundNode.supernode == nil {
self.insertSubnode(self.backgroundNode, at: 0)
}
self.backgroundNode.alpha = 1.0
} else {
if animated {
UIView.animate(withDuration: 0.3, animations: {
self.backgroundNode.alpha = 0.0
})
} else {
self.backgroundNode.alpha = 0.0
}
}
}
public var actionEnabled: Bool = true {
didSet {
self.isUserInteractionEnabled = self.actionEnabled
self.updateTitle()
}
}
public func updateTheme(_ theme: AlertControllerTheme) {
self.theme = theme
self.backgroundNode.backgroundColor = theme.highlightedItemColor
self.updateTitle()
}
private func updateTitle() {
var font = Font.regular(theme.baseFontSize)
var color: UIColor
switch self.action.type {
case .defaultAction, .genericAction:
color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor
case .destructiveAction, .defaultDestructiveAction:
color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor
}
switch self.action.type {
case .defaultAction, .defaultDestructiveAction:
font = Font.semibold(theme.baseFontSize)
case .destructiveAction, .genericAction:
break
}
self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: [])
self.accessibilityLabel = self.action.title
self.accessibilityTraits = [.button]
}
@objc func pressed() {
self.action.action()
}
override public func layout() {
super.layout()
self.backgroundNode.frame = self.bounds
}
}
public enum ComponentAlertContentActionLayout {
case horizontal
case vertical
}
public final class ComponentAlertContentNode: AlertContentNode {
private var theme: AlertControllerTheme
private let actionLayout: ComponentAlertContentActionLayout
private let content: AnyComponent<Empty>
private let contentView = ComponentView<Empty>()
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [ComponentAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
private let _dismissOnOutsideTap: Bool
override public var dismissOnOutsideTap: Bool {
return self._dismissOnOutsideTap
}
private var highlightedItemIndex: Int? = nil
public init(theme: AlertControllerTheme, content: AnyComponent<Empty>, actions: [ComponentAlertAction], actionLayout: ComponentAlertContentActionLayout, dismissOnOutsideTap: Bool) {
self.theme = theme
self.actionLayout = actionLayout
self._dismissOnOutsideTap = dismissOnOutsideTap
self.content = content
self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isUserInteractionEnabled = false
self.actionNodesSeparator.backgroundColor = theme.separatorColor
self.actionNodes = actions.map { action -> ComponentAlertContentActionNode in
return ComponentAlertContentActionNode(theme: theme, action: action)
}
var actionVerticalSeparators: [ASDisplayNode] = []
if actions.count > 1 {
for _ in 0 ..< actions.count - 1 {
let separatorNode = ASDisplayNode()
separatorNode.isLayerBacked = true
separatorNode.backgroundColor = theme.separatorColor
actionVerticalSeparators.append(separatorNode)
}
}
self.actionVerticalSeparators = actionVerticalSeparators
super.init()
self.addSubnode(self.actionNodesSeparator)
var i = 0
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
let index = i
actionNode.highlightedUpdated = { [weak self] highlighted in
if highlighted {
self?.highlightedItemIndex = index
}
}
i += 1
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
}
func setHighlightedItemIndex(_ index: Int?, update: Bool = false) {
self.highlightedItemIndex = index
if update {
var i = 0
for actionNode in self.actionNodes {
if i == index {
actionNode.setHighlighted(true, animated: false)
} else {
actionNode.setHighlighted(false, animated: false)
}
i += 1
}
}
}
override public func decreaseHighlightedIndex() {
let currentHighlightedIndex = self.highlightedItemIndex ?? 0
self.setHighlightedItemIndex(max(0, currentHighlightedIndex - 1), update: true)
}
override public func increaseHighlightedIndex() {
let currentHighlightedIndex = self.highlightedItemIndex ?? -1
self.setHighlightedItemIndex(min(self.actionNodes.count - 1, currentHighlightedIndex + 1), update: true)
}
override public func performHighlightedAction() {
guard let highlightedItemIndex = self.highlightedItemIndex else {
return
}
var i = 0
for itemNode in self.actionNodes {
if i == highlightedItemIndex {
itemNode.performAction()
return
}
i += 1
}
}
override public func updateTheme(_ theme: AlertControllerTheme) {
self.theme = theme
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)
}
}
override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
self.validLayout = size
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
var size = size
size.width = min(size.width, alertWidth)
let contentSize = self.contentView.update(
transition: ComponentTransition(transition),
component: self.content,
environment: {},
containerSize: CGSize(width: size.width - insets.left - insets.right, height: 10000.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 = self.actionLayout
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)
}
}
let resultSize: CGSize
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let contentWidth = alertWidth - insets.left - insets.right
let contentFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - contentSize.width) / 2.0), y: insets.top), size: contentSize)
if let contentComponentView = self.contentView.view {
if contentComponentView.superview == nil {
self.view.insertSubview(contentComponentView, belowSubview: self.actionNodesSeparator.view)
transition.updateFrame(view: contentComponentView, frame: contentFrame)
}
}
resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: contentSize.height + actionsHeight + insets.top + insets.bottom)
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
}
}
public func componentAlertController(theme: AlertControllerTheme, content: AnyComponent<Empty>, actions: [ComponentAlertAction], actionLayout: ComponentAlertContentActionLayout = .horizontal, dismissOnOutsideTap: Bool = true) -> AlertController {
var dismissImpl: (() -> Void)?
let controller = AlertController(theme: theme, contentNode: ComponentAlertContentNode(theme: theme, content: content, actions: actions.map { action in
return ComponentAlertAction(type: action.type, title: action.title, action: {
dismissImpl?()
action.action()
})
}, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap))
dismissImpl = { [weak controller] in
controller?.dismissAnimated()
}
return controller
}

View file

@ -95,6 +95,8 @@ swift_library(
"//submodules/TelegramCallsUI",
"//submodules/TelegramUI/Components/Stories/StoryContainerScreen",
"//submodules/TelegramUI/Components/ChatEntityKeyboardInputNode",
"//submodules/TelegramUI/Components/LiquidLens",
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
],
visibility = [
"//visibility:public",

View file

@ -2097,7 +2097,7 @@ private final class CameraScreenComponent: CombinedComponent {
if !isSticker, case .none = component.cameraState.recording, component.cameraState.isStreaming == .none && !state.isTransitioning && hasAllRequiredAccess && component.cameraState.collageProgress < 1.0 - .ulpOfOne {
let availableModeControlSize: CGSize
if isTablet {
availableModeControlSize = CGSize(width: panelWidth, height: 120.0)
availableModeControlSize = CGSize(width: floor(panelWidth), height: 120.0)
} else {
availableModeControlSize = availableSize
}
@ -2131,7 +2131,6 @@ private final class CameraScreenComponent: CombinedComponent {
modeControlPosition = CGPoint(x: availableSize.width / 2.0, y: availableSize.height - environment.safeInsets.bottom + modeControl.size.height / 2.0 + controlsBottomInset + 16.0)
}
context.add(modeControl
.clipsToBounds(true)
.position(modeControlPosition)
.appear(.default(alpha: true))
.disappear(.default(alpha: true))

View file

@ -5,6 +5,8 @@ import ComponentFlow
import MultilineTextComponent
import TelegramPresentationData
import GlassBackgroundComponent
import LiquidLens
import TabSelectionRecognizer
extension CameraMode {
func title(strings: PresentationStrings) -> String {
@ -70,28 +72,17 @@ final class ModeComponent: Component {
final class View: UIView, ComponentTaggedView {
private var component: ModeComponent?
private var state: EmptyComponentState?
final class ItemView: HighlightTrackingButton {
var pressed: () -> Void = {
}
init() {
super.init(frame: .zero)
self.isExclusiveTouch = true
self.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
required init(coder: NSCoder) {
preconditionFailure()
}
@objc func buttonPressed() {
self.pressed()
}
func update(isTablet: Bool, value: String, selected: Bool, tintColor: UIColor) -> CGSize {
let accentColor: UIColor
let normalColor: UIColor
@ -113,9 +104,15 @@ final class ModeComponent: Component {
}
private var backgroundView = UIView()
private var glassContainerView = GlassBackgroundContainerView()
private var selectionView = GlassBackgroundView()
private var itemViews: [Int32: ItemView] = [:]
private var backgroundContainer = GlassBackgroundContainerView()
private let liquidLensView: LiquidLensView
private var itemViews: [AnyHashable: ItemView] = [:]
private var selectedItemViews: [AnyHashable: ItemView] = [:]
private var tabSelectionRecognizer: TabSelectionRecognizer?
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
public func matches(tag: Any) -> Bool {
if let component = self.component, let componentTag = component.tag {
@ -128,16 +125,23 @@ final class ModeComponent: Component {
}
init() {
self.liquidLensView = LiquidLensView(useBackgroundContainer: false)
super.init(frame: CGRect())
self.backgroundView.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.11)
self.backgroundView.layer.cornerRadius = 24.0
self.layer.allowsGroupOpacity = true
self.addSubview(self.backgroundView)
self.backgroundView.addSubview(self.glassContainerView)
self.glassContainerView.contentView.addSubview(self.selectionView)
self.backgroundView.addSubview(self.backgroundContainer)
self.backgroundContainer.contentView.addSubview(self.liquidLensView)
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
self.tabSelectionRecognizer = tabSelectionRecognizer
self.liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
}
required init?(coder aDecoder: NSCoder) {
@ -162,14 +166,69 @@ final class ModeComponent: Component {
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
return self.backgroundView.frame.contains(point)
}
func update(component: ModeComponent, availableSize: CGSize, transition: ComponentTransition) -> CGSize {
self.component = component
let isTablet = component.isTablet
let updatedMode = component.updatedMode
private func item(at point: CGPoint) -> AnyHashable? {
var closestItem: (AnyHashable, CGFloat)?
for (id, itemView) in self.itemViews {
if itemView.frame.contains(point) {
return id
} else {
let distance = abs(point.x - itemView.center.x)
if let closestItemValue = closestItem {
if closestItemValue.1 > distance {
closestItem = (id, distance)
}
} else {
closestItem = (id, distance)
}
}
}
return closestItem?.0
}
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
guard let component = self.component else {
return
}
let location = recognizer.location(in: self.liquidLensView.contentView)
switch recognizer.state {
case .began:
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
let startX = itemView.frame.minX - 4.0
self.selectionGestureState = (startX, startX, itemId)
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
}
case .changed:
if var selectionGestureState = self.selectionGestureState {
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
if let itemId = self.item(at: location) {
selectionGestureState.itemId = itemId
}
self.selectionGestureState = selectionGestureState
self.state?.updated(transition: .immediate, isLocal: true)
}
case .ended, .cancelled:
if let selectionGestureState = self.selectionGestureState {
self.selectionGestureState = nil
if case .ended = recognizer.state {
guard let item = component.availableModes.first(where: { AnyHashable($0.rawValue) == selectionGestureState.itemId }) else {
return
}
component.updatedMode(item)
}
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
}
default:
break
}
}
func update(component: ModeComponent, availableSize: CGSize, state: EmptyComponentState, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let isTablet = component.isTablet
self.glassContainerView.isHidden = component.isTablet
self.backgroundView.backgroundColor = component.isTablet ? .clear : UIColor(rgb: 0xffffff, alpha: 0.11)
let inset: CGFloat = 23.0
@ -180,25 +239,34 @@ final class ModeComponent: Component {
var selectedCenter = itemFrame.minX
var selectedFrame = itemFrame
var validKeys: Set<Int32> = Set()
var validKeys: Set<AnyHashable> = Set()
for mode in component.availableModes.reversed() {
let id = mode.rawValue
validKeys.insert(id)
let itemView: ItemView
if let current = self.itemViews[id] {
let selectedItemView: ItemView
if let current = self.itemViews[id], let currentSelected = self.selectedItemViews[id] {
itemView = current
selectedItemView = currentSelected
} else {
itemView = ItemView()
self.backgroundView.addSubview(itemView)
itemView.isUserInteractionEnabled = false
self.itemViews[id] = itemView
}
itemView.pressed = {
updatedMode(mode)
self.liquidLensView.contentView.addSubview(itemView)
selectedItemView = ItemView()
selectedItemView.isUserInteractionEnabled = false
self.selectedItemViews[id] = selectedItemView
self.liquidLensView.selectedContentView.addSubview(selectedItemView)
}
let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: mode == component.currentMode, tintColor: component.tintColor)
let itemSize = itemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: false, tintColor: component.tintColor)
itemView.bounds = CGRect(origin: .zero, size: itemSize)
let _ = selectedItemView.update(isTablet: component.isTablet, value: mode.title(strings: component.strings), selected: true, tintColor: component.tintColor)
selectedItemView.bounds = CGRect(origin: .zero, size: itemSize)
itemFrame = CGRect(origin: itemFrame.origin, size: itemSize)
if mode == component.currentMode {
@ -207,12 +275,14 @@ final class ModeComponent: Component {
if isTablet {
itemView.center = CGPoint(x: availableSize.width / 2.0, y: itemFrame.midY)
selectedItemView.center = itemView.center
if mode == component.currentMode {
selectedCenter = itemFrame.midY
}
itemFrame = itemFrame.offsetBy(dx: 0.0, dy: tabletButtonSize.height + spacing)
} else {
itemView.center = CGPoint(x: itemFrame.midX, y: itemFrame.midY)
selectedItemView.center = itemView.center
if mode == component.currentMode {
selectedCenter = itemFrame.midX
}
@ -221,7 +291,7 @@ final class ModeComponent: Component {
i += 1
}
var removeKeys: [Int32] = []
var removeKeys: [AnyHashable] = []
for (id, itemView) in self.itemViews {
if !validKeys.contains(id) {
removeKeys.append(id)
@ -248,12 +318,19 @@ final class ModeComponent: Component {
}
let containerFrame = CGRect(origin: .zero, size: self.backgroundView.frame.size)
transition.setFrame(view: self.glassContainerView, frame: containerFrame)
transition.setFrame(view: self.backgroundContainer, frame: containerFrame)
let selectionFrame = selectedFrame.insetBy(dx: -20.0, dy: 3.0)
self.glassContainerView.update(size: containerFrame.size, isDark: true, transition: .immediate)
self.selectionView.update(size: selectionFrame.size, cornerRadius: selectionFrame.height * 0.5, isDark: true, tintColor: .init(kind: .custom, color: UIColor(rgb: 0xffffff, alpha: 0.16)), transition: transition)
transition.setFrame(view: self.selectionView, frame: selectionFrame)
let selectionFrame = selectedFrame.insetBy(dx: -23.0, dy: 3.0)
let lensSelection: (x: CGFloat, width: CGFloat)
if let selectionGestureState = self.selectionGestureState {
lensSelection = (selectionGestureState.currentX, selectionFrame.width)
} else {
lensSelection = (selectionFrame.minX, selectionFrame.width)
}
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: containerFrame.size))
self.liquidLensView.update(size: containerFrame.size, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: selectionFrame.height), isDark: true, isLifted: self.selectionGestureState != nil, isCollapsed: false, transition: transition)
self.backgroundContainer.update(size: containerFrame.size, isDark: true, transition: .immediate)
return size
}
@ -264,7 +341,7 @@ final class ModeComponent: Component {
}
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
}
}

View file

@ -440,6 +440,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode {
if node.iconNode == nil {
let iconNode = ASImageNode()
iconNode.contentMode = .center
iconNode.isUserInteractionEnabled = false
node.iconNode = iconNode
node.addSubnode(iconNode)
}

View file

@ -32,6 +32,7 @@ swift_library(
"//submodules/WallpaperBackgroundNode",
"//submodules/LocalMediaResources",
"//submodules/AppBundle",
"//submodules/TelegramStringFormatting",
"//submodules/ChatPresentationInterfaceState",
"//submodules/TelegramUI/Components/TextNodeWithEntities",
"//submodules/TelegramUI/Components/ChatControllerInteraction",

View file

@ -47,6 +47,7 @@ import ManagedDiceAnimationNode
import MessageHaptics
import ChatMessageTransitionNode
import ChatMessageSuggestedPostInfoNode
import TelegramStringFormatting
private let nameFont = Font.medium(14.0)
private let inlineBotPrefixFont = Font.regular(14.0)
@ -99,6 +100,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
private var swipeToReplyNode: ChatMessageSwipeToReplyNode?
private var swipeToReplyFeedback: HapticFeedback?
private let labelNode: TextNodeWithEntities
private var labelBackgroundNode: WallpaperBubbleBackgroundNode?
private let labelBackgroundMaskNode: ASImageNode
private var cachedMaskLabelBackgroundImage: (CGPoint, UIImage, [CGRect])?
private var selectionNode: ChatMessageSelectionNode?
private var deliveryFailedNode: ChatMessageDeliveryFailedNode?
private var shareButtonNode: ChatMessageShareButton?
@ -161,6 +167,12 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
self.textNode.textNode.displaysAsynchronously = false
self.textNode.textNode.isUserInteractionEnabled = false
self.labelNode = TextNodeWithEntities()
self.labelNode.textNode.isUserInteractionEnabled = false
self.labelNode.textNode.displaysAsynchronously = false
self.labelBackgroundMaskNode = ASImageNode()
super.init(rotated: rotated)
self.containerNode.shouldBegin = { [weak self] location in
@ -469,9 +481,22 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
} else if let telegramDice = self.telegramDice, let diceNode = self.animationNode as? ManagedDiceAnimationNode {
if let value = telegramDice.value {
let wasRolling = diceNode.isRolling
diceNode.setState(value == 0 ? .rolling : .value(value, true))
if wasRolling && !diceNode.isRolling {
Queue.mainQueue().after(3.0, {
self.labelNode.textNode.alpha = 1.0
self.labelBackgroundNode?.alpha = 1.0
self.labelNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
self.labelBackgroundNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
})
}
} else {
diceNode.setState(.rolling)
self.labelNode.textNode.alpha = 0.0
self.labelBackgroundNode?.alpha = 0.0
}
} else if self.telegramFile == nil && self.telegramDice == nil {
let (emoji, fitz) = item.message.text.basicEmoji
@ -816,6 +841,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let actionButtonsLayout = ChatMessageActionButtonsNode.asyncLayout(self.actionButtonsNode)
let reactionButtonsLayout = ChatMessageReactionButtonsNode.asyncLayout(self.reactionButtonsNode)
let makeLabelLayout = TextNodeWithEntities.asyncLayout(self.labelNode)
let cachedMaskLabelBackgroundImage = self.cachedMaskLabelBackgroundImage
let makeForwardInfoLayout = ChatMessageForwardInfoNode.asyncLayout(self.forwardInfoNode)
let viaBotLayout = TextNode.asyncLayout(self.viaBotNode)
@ -850,6 +878,44 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let avatarInset: CGFloat
var hasAvatar = false
let labelAttributedText = universalServiceMessageString(presentationData: (item.presentationData.theme.theme, item.presentationData.theme.wallpaper), strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: EngineMessage(item.message), accountPeerId: item.context.account.peerId, forChatList: false, forForumOverview: false, forAdditionalServiceMessage: true)
let (labelLayout, labelApply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: labelAttributedText, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
var labelRects = labelLayout.linesRects()
if labelRects.count > 1 {
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })
for i in 0 ..< sortedIndices.count {
let index = sortedIndices[i]
for j in -1 ... 1 {
if j != 0 && index + j >= 0 && index + j < sortedIndices.count {
if abs(labelRects[index + j].width - labelRects[index].width) < 40.0 {
labelRects[index + j].size.width = max(labelRects[index + j].width, labelRects[index].width)
labelRects[index].size.width = labelRects[index + j].size.width
}
}
}
}
}
for i in 0 ..< labelRects.count {
labelRects[i] = labelRects[i].insetBy(dx: -7.0, dy: floor((labelRects[i].height - 22.0) / 2.0))
labelRects[i].size.height = 22.0
labelRects[i].origin.x = floor((labelLayout.size.width - labelRects[i].width) / 2.0)
}
let backgroundMaskImage: (CGPoint, UIImage)?
var backgroundMaskUpdated = false
if labelLayout.size.height > 0.0 {
if let (currentOffset, currentImage, currentRects) = cachedMaskLabelBackgroundImage, currentRects == labelRects {
backgroundMaskImage = (currentOffset, currentImage)
} else {
backgroundMaskImage = LinkHighlightingNode.generateImage(color: .black, inset: 0.0, innerRadius: 11.0, outerRadius: 11.0, rects: labelRects, useModernPathCalculation: false)
backgroundMaskUpdated = true
}
} else {
backgroundMaskImage = nil
}
switch item.chatLocation {
case let .peer(peerId):
if peerId != item.context.account.peerId {
@ -1493,6 +1559,51 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
let _ = labelApply(TextNodeWithEntities.Arguments(
context: item.context,
cache: item.controllerInteraction.presentationContext.animationCache,
renderer: item.controllerInteraction.presentationContext.animationRenderer,
placeholderColor: item.presentationData.theme.theme.chat.message.freeform.withWallpaper.reactionInactiveBackground,
attemptSynchronous: synchronousLoads
))
let labelFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - labelLayout.size.width) / 2.0), y: 2.0), size: labelLayout.size)
strongSelf.labelNode.textNode.frame = labelFrame
if strongSelf.labelNode.textNode.supernode == nil, labelLayout.size.height > 0.0 {
strongSelf.addSubnode(strongSelf.labelNode.textNode)
}
let baseBackgroundFrame = labelFrame.offsetBy(dx: 0.0, dy: -11.0)
if let (offset, image) = backgroundMaskImage {
if strongSelf.labelBackgroundNode == nil {
if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
backgroundNode.alpha = strongSelf.labelNode.textNode.alpha
strongSelf.labelBackgroundNode = backgroundNode
strongSelf.insertSubnode(backgroundNode, at: 0)
}
}
if backgroundMaskUpdated, let backgroundNode = strongSelf.labelBackgroundNode {
if labelRects.count == 1 {
backgroundNode.clipsToBounds = true
backgroundNode.cornerRadius = labelRects[0].height / 2.0
backgroundNode.view.mask = nil
} else {
backgroundNode.clipsToBounds = false
backgroundNode.cornerRadius = 0.0
backgroundNode.view.mask = strongSelf.labelBackgroundMaskNode.view
}
}
if let backgroundNode = strongSelf.labelBackgroundNode {
backgroundNode.layer.frame = CGRect(origin: CGPoint(x: baseBackgroundFrame.minX + offset.x, y: baseBackgroundFrame.minY + offset.y), size: image.size)
}
strongSelf.labelBackgroundMaskNode.image = image
strongSelf.labelBackgroundMaskNode.frame = CGRect(origin: CGPoint(), size: image.size)
strongSelf.cachedMaskLabelBackgroundImage = (offset, image, labelRects)
}
var updatedImageFrame: CGRect
var contextContentFrame: CGRect
if let _ = emojiString {

View file

@ -455,7 +455,9 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
switch action.action {
case let .giftPremium(_, _, daysValue, _, _, giftText, giftEntities):
months = max(3, Int32(round(Float(daysValue) / 30.0)))
if months == 12 {
if daysValue < 30 {
title = item.presentationData.strings.Notification_PremiumGift_DaysTitle(daysValue)
} else if months == 12 {
title = item.presentationData.strings.Notification_PremiumGift_YearsTitle(1)
} else {
title = item.presentationData.strings.Notification_PremiumGift_MonthsTitle(months)
@ -513,7 +515,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
title = item.presentationData.strings.Notification_StarsGiveaway_Title
let starsString = item.presentationData.strings.Notification_StarsGiveaway_Subtitle_Stars(Int32(clamping: count)).replacingOccurrences(of: " ", with: "\u{00A0}")
text = item.presentationData.strings.Notification_StarsGiveaway_Subtitle(peerName, starsString).string
case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue, _, _, _, _, giftText, giftEntities):
case let .giftCode(_, fromGiveaway, unclaimed, channelId, daysValue, _, _, _, _, giftText, giftEntities):
let monthsValue = max(3, Int32(round(Float(daysValue) / 30.0)))
if channelId == nil {
months = monthsValue
if months == 12 {

View file

@ -100,7 +100,7 @@ public struct InteractiveEmojiConfiguration {
}
public static func with(appConfiguration: AppConfiguration) -> InteractiveEmojiConfiguration {
if let data = appConfiguration.data, let emojis = data["emojies_send_dice"] as? [String] {
if let data = appConfiguration.data, var emojis = data["emojies_send_dice"] as? [String] {
var successParameters: [String: InteractiveEmojiSuccessParameters] = [:]
if let success = data["emojies_send_dice_success"] as? [String: [String: Double]] {
for (key, dict) in success {
@ -109,6 +109,11 @@ public struct InteractiveEmojiConfiguration {
}
}
}
#if DEBUG
if !emojis.contains("🎲") {
emojis.append("🎲")
}
#endif
return InteractiveEmojiConfiguration(emojis: emojis, successParameters: successParameters)
} else {
return .defaultValue
@ -126,6 +131,10 @@ public final class ManagedDiceAnimationNode: ManagedAnimationNode {
private let configuration = Promise<InteractiveEmojiConfiguration?>()
private let emojis = Promise<[TelegramMediaFile]>()
public var isRolling: Bool {
return self.diceState == .rolling
}
public var success: (() -> Void)?
public init(context: AccountContext, emoji: String) {

View file

@ -33,6 +33,8 @@ import LegacyMessageInputPanelInputView
import AttachmentTextInputPanelNode
import GlassBackgroundComponent
private let keyboardCornerRadius: CGFloat = 30.0
public final class EmptyInputView: UIView, UIInputViewAudioFeedback {
public var enableInputClicksWhenVisible: Bool {
return true
@ -490,7 +492,7 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
self.clippingView = UIView()
self.clippingView.clipsToBounds = true
self.clippingView.layer.cornerRadius = 20.0
self.clippingView.layer.cornerRadius = keyboardCornerRadius
self.entityKeyboardView = ComponentHostView<Empty>()
@ -1904,8 +1906,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
theme: interfaceState.theme,
strings: interfaceState.strings,
isContentInFocus: isVisible,
containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -34.0 : 0.0, left: leftInset, bottom: keyboardBottomInset, right: rightInset),
topPanelInsets: UIEdgeInsets(),
containerInsets: UIEdgeInsets(top: self.isEmojiSearchActive ? -42.0 : 0.0, left: leftInset, bottom: keyboardBottomInset, right: rightInset),
topPanelInsets: UIEdgeInsets(top: 0.0, left: 5.0, bottom: 0.0, right: 5.0),
emojiContent: emojiContent,
stickerContent: stickerContent,
maskContent: nil,
@ -2030,16 +2032,16 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
backgroundFrame.size.height += 32.0
if backgroundChromeView.image == nil {
backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: 20.0 * 2.0, height: 20.0 * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear)
backgroundChromeView.image = GlassBackgroundView.generateForegroundImage(size: CGSize(width: keyboardCornerRadius * 2.0, height: keyboardCornerRadius * 2.0), isDark: interfaceState.theme.overallDarkAppearance, fillColor: .clear)
}
if backgroundTintView.image == nil {
backgroundTintView.image = generateStretchableFilledCircleImage(diameter: 20.0 * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
backgroundTintView.image = generateStretchableFilledCircleImage(diameter: keyboardCornerRadius * 2.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
backgroundTintView.tintColor = interfaceState.theme.chat.inputMediaPanel.backgroundColor
transition.updateFrame(view: backgroundView, frame: backgroundFrame)
backgroundView.updateColor(color: .clear, forceKeepBlur: true, transition: .immediate)
backgroundView.update(size: backgroundFrame.size, cornerRadius: 20.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition)
backgroundView.update(size: backgroundFrame.size, cornerRadius: keyboardCornerRadius, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition)
transition.updateFrame(view: backgroundChromeView, frame: backgroundFrame.insetBy(dx: -1.0, dy: 0.0))

View file

@ -0,0 +1,55 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "EmojiGameStakeScreen",
module_name = "EmojiGameStakeScreen",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/TelegramCore",
"//submodules/SSignalKit/SwiftSignalKit",
"//submodules/ComponentFlow",
"//submodules/Components/ViewControllerComponent",
"//submodules/Components/ComponentDisplayAdapters",
"//submodules/Components/MultilineTextComponent",
"//submodules/Components/BalancedTextComponent",
"//submodules/TelegramPresentationData",
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/ItemListUI",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SheetComponent",
"//submodules/UndoUI",
"//submodules/TextFormat",
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/ListActionItemComponent",
"//submodules/TelegramUI/Components/ScrollComponent",
"//submodules/TelegramUI/Components/Premium/PremiumStarComponent",
"//submodules/TelegramUI/Components/ButtonComponent",
"//submodules/Components/BundleIconComponent",
"//submodules/PasswordSetupUI",
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
"//submodules/TelegramUI/Components/TabSelectorComponent",
"//submodules/TelegramUI/Components/Stars/BalanceNeededScreen",
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
"//submodules/TelegramUI/Components/LottieComponent",
"//submodules/TelegramUI/Components/LottieComponentResourceContent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/Components/MultilineTextWithEntitiesComponent",
"//submodules/TelegramUI/Components/PlainButtonComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -53,6 +53,9 @@ swift_library(
"//submodules/TelegramUI/Components/BatchVideoRendering",
"//submodules/TelegramUI/Components/GifVideoLayer",
"//submodules/TelegramUI/Components/GlassBackgroundComponent",
"//submodules/TelegramUI/Components/EdgeEffect",
"//submodules/TelegramUI/Components/LiquidLens",
"//submodules/TelegramUI/Components/TabSelectionRecognizer",
],
visibility = [
"//visibility:public",

View file

@ -419,13 +419,13 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
context.setFillColor(color.withMultipliedAlpha(0.2).cgColor)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 21.0).cgPath)
context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: 30.0).cgPath)
context.fillPath()
context.setFillColor(color.cgColor)
let plusSize = CGSize(width: 3.5, height: 28.0)
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height).offsetBy(dx: 0.0, dy: -17.0), cornerRadius: plusSize.width / 2.0).cgPath)
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width).offsetBy(dx: 0.0, dy: -17.0), cornerRadius: plusSize.width / 2.0).cgPath)
let plusSize = CGSize(width: 4.0, height: 27.0)
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height).offsetBy(dx: 0.0, dy: -18.0), cornerRadius: plusSize.width / 2.0).cgPath)
context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width).offsetBy(dx: 0.0, dy: -18.0), cornerRadius: plusSize.width / 2.0).cgPath)
context.fillPath()
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
@ -437,7 +437,7 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
let components = string.components(separatedBy: "\n")
for component in components {
context.saveGState()
let attributedString = NSAttributedString(string: component, attributes: [NSAttributedString.Key.font: Font.medium(17.0), NSAttributedString.Key.foregroundColor: color])
let attributedString = NSAttributedString(string: component, attributes: [NSAttributedString.Key.font: Font.medium(16.0), NSAttributedString.Key.foregroundColor: color])
let line = CTLineCreateWithAttributedString(attributedString)
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)

View file

@ -4026,7 +4026,7 @@ public final class EmojiPagerContentComponent: Component {
private func updateTopPanelSeparator(transition: ComponentTransition) {
if let topPanelSeparator = self.topPanelSeparator {
var offset = self.scrollView.contentOffset.y
let startOffset: CGFloat = 40.0 - self.topPanelHeight
let startOffset: CGFloat = 46.0 - self.topPanelHeight
let endOffset: CGFloat = startOffset + 10.0
offset = min(max(offset, startOffset), endOffset)

View file

@ -403,7 +403,7 @@ public final class EntityKeyboardComponent: Component {
if let _ = component.maskContent?.inputInteractionHolder.inputInteraction?.openStickerSettings {
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "masks", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Chat/Input/Media/EntityInputSettingsIcon",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: {
maskContent.inputInteractionHolder.inputInteraction?.openStickerSettings?()
}
@ -417,7 +417,7 @@ public final class EntityKeyboardComponent: Component {
if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage {
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "gifs", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Media Editor/AddImage",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: {
addImage()
}
@ -537,7 +537,7 @@ public final class EntityKeyboardComponent: Component {
if let _ = component.stickerContent?.inputInteractionHolder.inputInteraction?.openStickerSettings {
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Chat/Input/Media/EntityInputSettingsIcon",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: {
stickerContent.inputInteractionHolder.inputInteraction?.openStickerSettings?()
}
@ -546,7 +546,7 @@ public final class EntityKeyboardComponent: Component {
if let addImage = component.stickerContent?.inputInteractionHolder.inputInteraction?.addImage {
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "stickers", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Media Editor/AddImage",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: {
addImage()
}
@ -649,7 +649,7 @@ public final class EntityKeyboardComponent: Component {
if let _ = deleteBackwards {
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Chat/Input/Media/EntityInputGlobeIcon",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: { [weak self] in
guard let strongSelf = self, let component = strongSelf.component else {
return
@ -660,7 +660,7 @@ public final class EntityKeyboardComponent: Component {
} else if let addImage = component.emojiContent?.inputInteractionHolder.inputInteraction?.addImage {
contentAccessoryLeftButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Media Editor/AddImage",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: {
addImage()
}
@ -671,7 +671,7 @@ public final class EntityKeyboardComponent: Component {
if let _ = deleteBackwards {
contentAccessoryRightButtons.append(AnyComponentWithIdentity(id: "emoji", component: AnyComponent(EntityKeyboardBottomPanelButton(
icon: "Chat/Input/Media/EntityInputClearIcon",
color: component.theme.chat.inputPanel.inputControlColor,
theme: component.theme,
action: {
deleteBackwards?()
AudioServicesPlaySystemSound(1155)
@ -721,7 +721,8 @@ public final class EntityKeyboardComponent: Component {
topPanel: AnyComponent(EntityKeyboardTopContainerPanelComponent(
theme: component.theme,
overflowHeight: component.hiddenInputHeight,
topInset: component.externalTopPanelContainer == nil ? 6.0 : 0.0,
topInset: component.externalTopPanelContainer == nil ? 8.0 : 0.0,
height: component.externalTopPanelContainer == nil ? 40.0 : 34.0,
displayBackground: component.externalTopPanelContainer != nil ? .none : component.displayTopPanelBackground
)),
externalTopPanelContainer: component.externalTopPanelContainer,

View file

@ -2,6 +2,7 @@ import Foundation
import UIKit
import Display
import ComponentFlow
import TelegramPresentationData
import PagerComponent
import ComponentDisplayAdapters
import BundleIconComponent
@ -10,18 +11,18 @@ import AppBundle
final class EntityKeyboardBottomPanelButton: Component {
let icon: String
let color: UIColor
let theme: PresentationTheme
let action: () -> Void
let holdAction: (() -> Void)?
init(
icon: String,
color: UIColor,
theme: PresentationTheme,
action: @escaping () -> Void,
holdAction: (() -> Void)? = nil
) {
self.icon = icon
self.color = color
self.theme = theme
self.action = action
self.holdAction = holdAction
}
@ -30,7 +31,7 @@ final class EntityKeyboardBottomPanelButton: Component {
if lhs.icon != rhs.icon {
return false
}
if lhs.color != rhs.color {
if lhs.theme !== rhs.theme {
return false
}
if (lhs.holdAction == nil) != (rhs.holdAction == nil) {
@ -39,7 +40,9 @@ final class EntityKeyboardBottomPanelButton: Component {
return true
}
final class View: HighlightTrackingButton {
final class View: UIView {
private let backgroundView: GlassBackgroundView
let buttonView: HighlightTrackingButton
let iconView: GlassBackgroundView.ContentImageView
let tintMaskContainer: UIView
@ -48,15 +51,19 @@ final class EntityKeyboardBottomPanelButton: Component {
var component: EntityKeyboardBottomPanelButton?
private var currentIsHighlighted: Bool = false {
didSet {
if self.currentIsHighlighted != oldValue {
self.updateAlpha(transition: .immediate)
}
}
}
// private var currentIsHighlighted: Bool = false {
// didSet {
// if self.currentIsHighlighted != oldValue {
// self.updateAlpha(transition: .immediate)
// }
// }
// }
override init(frame: CGRect) {
self.backgroundView = GlassBackgroundView()
self.buttonView = HighlightTrackingButton()
self.iconView = GlassBackgroundView.ContentImageView()
self.iconView.isUserInteractionEnabled = false
@ -65,9 +72,11 @@ final class EntityKeyboardBottomPanelButton: Component {
super.init(frame: frame)
self.addSubview(self.iconView)
self.addSubview(self.backgroundView)
self.backgroundView.contentView.addSubview(self.iconView)
self.backgroundView.contentView.addSubview(self.buttonView)
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
self.buttonView.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
}
required init?(coder: NSCoder) {
@ -86,65 +95,65 @@ final class EntityKeyboardBottomPanelButton: Component {
}
}
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
self.currentIsHighlighted = true
self.holdActionTriggerred = false
if self.component?.holdAction != nil {
self.holdActionTriggerred = true
self.component?.action()
self.holdActionTimer?.invalidate()
let holdActionTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.holdActionTimer?.invalidate()
strongSelf.component?.holdAction?()
strongSelf.beginExecuteHoldActionTimer()
})
self.holdActionTimer = holdActionTimer
RunLoop.main.add(holdActionTimer, forMode: .common)
}
return super.beginTracking(touch, with: event)
}
// override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
// self.currentIsHighlighted = true
//
// self.holdActionTriggerred = false
//
// if self.component?.holdAction != nil {
// self.holdActionTriggerred = true
// self.component?.action()
//
// self.holdActionTimer?.invalidate()
// let holdActionTimer = Timer(timeInterval: 0.5, repeats: false, block: { [weak self] _ in
// guard let strongSelf = self else {
// return
// }
// strongSelf.holdActionTimer?.invalidate()
// strongSelf.component?.holdAction?()
// strongSelf.beginExecuteHoldActionTimer()
// })
// self.holdActionTimer = holdActionTimer
// RunLoop.main.add(holdActionTimer, forMode: .common)
// }
//
// return super.beginTracking(touch, with: event)
// }
private func beginExecuteHoldActionTimer() {
self.holdActionTimer?.invalidate()
let holdActionTimer = Timer(timeInterval: 0.1, repeats: true, block: { [weak self] _ in
guard let strongSelf = self else {
return
}
strongSelf.component?.holdAction?()
})
self.holdActionTimer = holdActionTimer
RunLoop.main.add(holdActionTimer, forMode: .common)
}
// private func beginExecuteHoldActionTimer() {
// self.holdActionTimer?.invalidate()
// let holdActionTimer = Timer(timeInterval: 0.1, repeats: true, block: { [weak self] _ in
// guard let strongSelf = self else {
// return
// }
// strongSelf.component?.holdAction?()
// })
// self.holdActionTimer = holdActionTimer
// RunLoop.main.add(holdActionTimer, forMode: .common)
// }
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
self.currentIsHighlighted = false
self.holdActionTimer?.invalidate()
self.holdActionTimer = nil
super.endTracking(touch, with: event)
}
override public func cancelTracking(with event: UIEvent?) {
self.currentIsHighlighted = false
self.holdActionTimer?.invalidate()
self.holdActionTimer = nil
super.cancelTracking(with: event)
}
// override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
// self.currentIsHighlighted = false
//
// self.holdActionTimer?.invalidate()
// self.holdActionTimer = nil
//
// super.endTracking(touch, with: event)
// }
//
// override public func cancelTracking(with event: UIEvent?) {
// self.currentIsHighlighted = false
//
// self.holdActionTimer?.invalidate()
// self.holdActionTimer = nil
//
// super.cancelTracking(with: event)
// }
private func updateAlpha(transition: ComponentTransition) {
let alpha: CGFloat = self.currentIsHighlighted ? 0.6 : 1.0
transition.setAlpha(view: self.iconView, alpha: alpha)
}
// private func updateAlpha(transition: ComponentTransition) {
// let alpha: CGFloat = self.currentIsHighlighted ? 0.6 : 1.0
// transition.setAlpha(view: self.iconView, alpha: alpha)
// }
func update(component: EntityKeyboardBottomPanelButton, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
if self.component?.icon != component.icon {
@ -153,15 +162,21 @@ final class EntityKeyboardBottomPanelButton: Component {
self.component = component
self.iconView.tintColor = component.color
self.iconView.tintColor = component.theme.chat.inputPanel.panelControlColor
let size = CGSize(width: 38.0, height: 38.0)
let size = CGSize(width: 40.0, height: 40.0)
if let image = self.iconView.image {
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), size: image.size)
self.iconView.frame = iconFrame
}
let tintColor: GlassBackgroundView.TintColor = .init(kind: .panel, color: component.theme.chat.inputPanel.inputBackgroundColor.withMultipliedAlpha(0.7))
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
self.backgroundView.update(size: size, cornerRadius: size.height * 0.5, isDark: component.theme.overallDarkAppearance, tintColor: tintColor, isInteractive: true, transition: transition)
self.buttonView.frame = CGRect(origin: .zero, size: size)
return size
}
}

View file

@ -8,6 +8,9 @@ import TelegramCore
import ComponentDisplayAdapters
import BundleIconComponent
import GlassBackgroundComponent
import EdgeEffect
import LiquidLens
import TabSelectionRecognizer
private final class BottomPanelIconComponent: Component {
let title: String
@ -57,22 +60,15 @@ private final class BottomPanelIconComponent: Component {
super.init(frame: frame)
self.addSubview(self.contentView)
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func tapGesture(_ recognizer: UITapGestureRecognizer) {
if case .ended = recognizer.state {
self.component?.action()
}
}
func update(component: BottomPanelIconComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
if self.component?.title != component.title {
let text = NSAttributedString(string: component.title, font: Font.medium(15.0), textColor: .white)
let text = NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: .white)
let textBounds = text.boundingRect(with: CGSize(width: 120.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
self.contentView.image = generateImage(CGSize(width: ceil(textBounds.width), height: ceil(textBounds.height)), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
@ -89,19 +85,9 @@ private final class BottomPanelIconComponent: Component {
let textSize = self.contentView.image?.size ?? CGSize()
let size = CGSize(width: textSize.width + textInset * 2.0, height: 28.0)
let color = component.theme.chat.inputPanel.inputControlColor
self.contentView.tintColor = component.theme.chat.inputPanel.panelControlColor
if self.contentView.tintColor != color {
if !transition.animation.isImmediate {
UIView.animate(withDuration: 0.15, delay: 0.0, options: [], animations: {
self.contentView.tintColor = color
}, completion: nil)
} else {
self.contentView.tintColor = color
}
}
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0 - 1.0), size: textSize))
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: (size.height - textSize.height) / 2.0), size: textSize))
return size
}
@ -163,17 +149,27 @@ final class EntityKeyboardBottomPanelComponent: Component {
private var leftAccessoryButton: AccessoryButtonView?
private var rightAccessoryButton: AccessoryButtonView?
private var iconViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var highlightedIconBackgroundView: UIView
private var highlightedTintIconBackgroundView: UIView
private let edgeEffectView: EdgeEffectView
private let backgroundContainer: GlassBackgroundContainerView
private let liquidLensView: LiquidLensView
private var itemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var selectedItemViews: [AnyHashable: ComponentHostView<Empty>] = [:]
private var tabSelectionRecognizer: TabSelectionRecognizer?
private var selectionGestureState: (startX: CGFloat, currentX: CGFloat, itemId: AnyHashable)?
let tintContentMask: UIView
private var component: EntityKeyboardBottomPanelComponent?
private var state: EmptyComponentState?
private var environment: PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>?
override init(frame: CGRect) {
self.tintContentMask = UIView()
self.edgeEffectView = EdgeEffectView()
self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true, customBlurRadius: 10.0)
self.separatorView = UIView()
@ -182,51 +178,102 @@ final class EntityKeyboardBottomPanelComponent: Component {
self.tintSeparatorView.isUserInteractionEnabled = false
self.tintSeparatorView.backgroundColor = UIColor(white: 0.0, alpha: 0.7)
self.tintContentMask.addSubview(self.tintSeparatorView)
self.highlightedIconBackgroundView = UIView()
self.highlightedIconBackgroundView.isUserInteractionEnabled = false
self.highlightedIconBackgroundView.layer.cornerRadius = 10.0
self.highlightedIconBackgroundView.clipsToBounds = true
self.highlightedTintIconBackgroundView = UIView()
self.highlightedTintIconBackgroundView.isUserInteractionEnabled = false
self.highlightedTintIconBackgroundView.layer.cornerRadius = 10.0
self.highlightedTintIconBackgroundView.clipsToBounds = true
self.highlightedTintIconBackgroundView.backgroundColor = UIColor(white: 0.0, alpha: 0.1)
self.tintContentMask.addSubview(self.highlightedTintIconBackgroundView)
self.backgroundContainer = GlassBackgroundContainerView()
self.liquidLensView = LiquidLensView(useBackgroundContainer: false)
super.init(frame: frame)
self.addSubview(self.backgroundView)
self.addSubview(self.highlightedIconBackgroundView)
self.addSubview(self.separatorView)
self.addSubview(self.edgeEffectView)
self.addSubview(self.backgroundContainer)
self.backgroundContainer.contentView.addSubview(self.liquidLensView)
let tabSelectionRecognizer = TabSelectionRecognizer(target: self, action: #selector(self.onTabSelectionGesture(_:)))
self.tabSelectionRecognizer = tabSelectionRecognizer
self.liquidLensView.addGestureRecognizer(tabSelectionRecognizer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func item(at point: CGPoint) -> AnyHashable? {
var closestItem: (AnyHashable, CGFloat)?
for (id, itemView) in self.itemViews {
if itemView.frame.contains(point) {
return id
} else {
let distance = abs(point.x - itemView.center.x)
if let closestItemValue = closestItem {
if closestItemValue.1 > distance {
closestItem = (id, distance)
}
} else {
closestItem = (id, distance)
}
}
}
return closestItem?.0
}
@objc private func onTabSelectionGesture(_ recognizer: TabSelectionRecognizer) {
guard let environment = self.environment else {
return
}
let location = recognizer.location(in: self.liquidLensView.contentView)
switch recognizer.state {
case .began:
if let itemId = self.item(at: location), let itemView = self.itemViews[itemId] {
let startX = itemView.frame.minX - 4.0
self.selectionGestureState = (startX, startX, itemId)
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
}
case .changed:
if var selectionGestureState = self.selectionGestureState {
selectionGestureState.currentX = selectionGestureState.startX + recognizer.translation(in: self).x
if let itemId = self.item(at: location) {
selectionGestureState.itemId = itemId
}
self.selectionGestureState = selectionGestureState
self.state?.updated(transition: .immediate, isLocal: true)
}
case .ended, .cancelled:
if let selectionGestureState = self.selectionGestureState {
self.selectionGestureState = nil
if case .ended = recognizer.state {
guard let item = environment.contentIcons.first(where: { $0.id == selectionGestureState.itemId }) else {
return
}
environment.navigateToContentId(item.id)
}
self.state?.updated(transition: .spring(duration: 0.4), isLocal: true)
}
default:
break
}
}
func update(component: EntityKeyboardBottomPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
if self.component?.theme !== component.theme {
self.separatorView.backgroundColor = component.theme.list.itemPlainSeparatorColor.withMultipliedAlpha(0.5)
self.backgroundView.updateColor(color: component.theme.chat.inputPanel.panelBackgroundColor.withMultipliedAlpha(1.0), transition: .immediate)
self.highlightedIconBackgroundView.backgroundColor = component.theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor
}
let intrinsicHeight: CGFloat = 34.0
let height = intrinsicHeight + component.containerInsets.bottom
let height = intrinsicHeight + component.containerInsets.bottom + 20.0
let accessoryButtonOffset: CGFloat
if component.containerInsets.bottom > 0.0 {
accessoryButtonOffset = 2.0
accessoryButtonOffset = 0.0
} else {
accessoryButtonOffset = -2.0
}
self.component = component
self.state = state
let panelEnvironment = environment[PagerComponentPanelEnvironment<EntityKeyboardTopContainerPanelEnvironment>.self].value
self.environment = panelEnvironment
let activeContentId = panelEnvironment.activeContentId
var leftAccessoryButtonComponent: AnyComponentWithIdentity<Empty>?
@ -257,7 +304,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
environment: {},
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
)
let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 2.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize)
let leftAccessoryButtonFrame = CGRect(origin: CGPoint(x: component.containerInsets.left + 18.0, y: accessoryButtonOffset), size: leftAccessoryButtonSize)
leftAccessoryButtonTransition.setFrame(view: leftAccessoryButton.view, frame: leftAccessoryButtonFrame)
if let leftAccessoryButtonView = leftAccessoryButton.view.componentView as? PagerTopPanelView {
if leftAccessoryButtonView.tintContentMask.superview == nil {
@ -328,7 +375,7 @@ final class EntityKeyboardBottomPanelComponent: Component {
containerSize: CGSize(width: .greatestFiniteMagnitude, height: intrinsicHeight)
)
let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 2.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize)
let rightAccessoryButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - component.containerInsets.right - 18.0 - rightAccessoryButtonSize.width, y: accessoryButtonOffset), size: rightAccessoryButtonSize)
rightAccessoryButtonTransition.setFrame(view: rightAccessoryButton.view, frame: rightAccessoryButtonFrame)
if let rightAccessoryButtonView = rightAccessoryButton.view.componentView as? PagerTopPanelView {
if rightAccessoryButtonView.tintContentMask.superview == nil {
@ -374,23 +421,32 @@ final class EntityKeyboardBottomPanelComponent: Component {
var iconInfos: [AnyHashable: (size: CGSize, transition: ComponentTransition)] = [:]
var iconTotalSize = CGSize()
let iconSpacing: CGFloat = 4.0
let iconSpacing: CGFloat = 0.0
let navigateToContentId = panelEnvironment.navigateToContentId
var lensSelection: (x: CGFloat, width: CGFloat) = (0.0, 0.0)
if panelEnvironment.contentIcons.count > 1 {
for icon in panelEnvironment.contentIcons {
validIconIds.append(icon.id)
var iconTransition = transition
let iconView: ComponentHostView<Empty>
if let current = self.iconViews[icon.id] {
let selectedIconView: ComponentHostView<Empty>
if let current = self.itemViews[icon.id], let currentSelected = self.selectedItemViews[icon.id] {
iconView = current
selectedIconView = currentSelected
} else {
iconTransition = .immediate
iconView = ComponentHostView<Empty>()
self.iconViews[icon.id] = iconView
self.addSubview(iconView)
iconView.isUserInteractionEnabled = false
selectedIconView = ComponentHostView<Empty>()
selectedIconView.isUserInteractionEnabled = false
self.itemViews[icon.id] = iconView
self.selectedItemViews[icon.id] = selectedIconView
self.liquidLensView.contentView.addSubview(iconView)
self.liquidLensView.selectedContentView.addSubview(selectedIconView)
}
let iconSize = iconView.update(
@ -407,65 +463,70 @@ final class EntityKeyboardBottomPanelComponent: Component {
containerSize: CGSize(width: 28.0, height: 28.0)
)
let _ = selectedIconView.update(
transition: iconTransition,
component: AnyComponent(BottomPanelIconComponent(
title: icon.title,
isHighlighted: icon.id == activeContentId,
theme: component.theme,
action: {
navigateToContentId(icon.id)
}
)),
environment: {},
containerSize: CGSize(width: 28.0, height: 28.0)
)
iconInfos[icon.id] = (size: iconSize, transition: iconTransition)
if !iconTotalSize.width.isZero {
iconTotalSize.width += iconSpacing
iconTotalSize.width += iconSpacing - 8.0
}
iconTotalSize.width += iconSize.width
iconTotalSize.height = max(iconTotalSize.height, iconSize.height)
}
}
var nextIconOrigin = CGPoint(x: floor((availableSize.width - iconTotalSize.width) / 2.0), y: floor((intrinsicHeight - iconTotalSize.height) / 2.0))
if component.containerInsets.bottom > 0.0 {
nextIconOrigin.y += 3.0
}
let tabsSize = CGSize(width: iconTotalSize.width, height: 40.0)
var nextIconOrigin = CGPoint(x: floor((tabsSize.width - iconTotalSize.width) / 2.0), y: floor((tabsSize.height - iconTotalSize.height) / 2.0))
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: .zero, size: availableSize))
self.backgroundContainer.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
if panelEnvironment.contentIcons.count > 1 {
for icon in panelEnvironment.contentIcons {
guard let iconInfo = iconInfos[icon.id], let iconView = self.iconViews[icon.id] else {
guard let iconInfo = iconInfos[icon.id], let iconView = self.itemViews[icon.id], let selectedIconView = self.selectedItemViews[icon.id] else {
continue
}
let iconFrame = CGRect(origin: nextIconOrigin, size: iconInfo.size)
iconInfo.transition.setFrame(view: iconView, frame: iconFrame, completion: nil)
if let iconView = iconView.componentView as? BottomPanelIconComponent.View {
if iconView.tintMaskContainer.superview == nil {
self.tintContentMask.addSubview(iconView.tintMaskContainer)
}
iconInfo.transition.setFrame(view: iconView.tintMaskContainer, frame: iconFrame, completion: nil)
}
iconInfo.transition.setFrame(view: selectedIconView, frame: iconFrame, completion: nil)
if let activeContentId = activeContentId, activeContentId == icon.id {
self.highlightedIconBackgroundView.isHidden = false
self.highlightedTintIconBackgroundView.isHidden = false
transition.setFrame(view: self.highlightedIconBackgroundView, frame: iconFrame)
transition.setFrame(view: self.highlightedTintIconBackgroundView, frame: iconFrame)
let cornerRadius: CGFloat = min(iconFrame.width, iconFrame.height) / 2.0
transition.setCornerRadius(layer: self.highlightedIconBackgroundView.layer, cornerRadius: cornerRadius)
transition.setCornerRadius(layer: self.highlightedTintIconBackgroundView.layer, cornerRadius: cornerRadius)
lensSelection = (iconFrame.origin.x, iconFrame.width)
}
nextIconOrigin.x += iconInfo.size.width + iconSpacing
nextIconOrigin.x += iconInfo.size.width + iconSpacing - 8.0
}
}
if activeContentId == nil {
self.highlightedIconBackgroundView.isHidden = true
if let selectionGestureState = self.selectionGestureState {
lensSelection = (selectionGestureState.currentX, lensSelection.width)
}
transition.setFrame(view: self.liquidLensView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - tabsSize.width) / 2.0), y: 0.0), size: tabsSize))
self.liquidLensView.update(size: tabsSize, selectionOrigin: CGPoint(x: lensSelection.x, y: 0.0), selectionSize: CGSize(width: lensSelection.width, height: tabsSize.height), isDark: component.theme.overallDarkAppearance, isLifted: self.selectionGestureState != nil, isCollapsed: activeContentId == nil, transition: transition)
var removedIconViewIds: [AnyHashable] = []
for (id, iconView) in self.iconViews {
for (id, iconView) in self.itemViews {
if !validIconIds.contains(id) {
removedIconViewIds.append(id)
iconView.removeFromSuperview()
}
}
for id in removedIconViewIds {
self.iconViews.removeValue(forKey: id)
self.itemViews.removeValue(forKey: id)
}
transition.setFrame(view: self.separatorView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
@ -473,8 +534,11 @@ final class EntityKeyboardBottomPanelComponent: Component {
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: height)))
//self.backgroundView.update(size: CGSize(width: availableSize.width, height: height), transition: transition.containedViewLayoutTransition)
self.component = component
let edgeEffectHeight: CGFloat = 80.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: height - edgeEffectHeight), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
self.edgeEffectView.update(content: component.theme.chat.inputMediaPanel.backgroundColor.withMultipliedAlpha(0.8), rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, 50.0), transition: transition)
return CGSize(width: availableSize.width, height: height)
}

View file

@ -9,15 +9,18 @@ import Postbox
public final class EntityKeyboardTopContainerPanelEnvironment: Equatable {
let isContentInFocus: Bool
let height: CGFloat
let visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)>
let isExpandedUpdated: (Bool, ComponentTransition) -> Void
init(
isContentInFocus: Bool,
height: CGFloat,
visibilityFractionUpdated: ActionSlot<(CGFloat, ComponentTransition)>,
isExpandedUpdated: @escaping (Bool, ComponentTransition) -> Void
) {
self.isContentInFocus = isContentInFocus
self.height = height
self.visibilityFractionUpdated = visibilityFractionUpdated
self.isExpandedUpdated = isExpandedUpdated
}
@ -26,6 +29,9 @@ public final class EntityKeyboardTopContainerPanelEnvironment: Equatable {
if lhs.isContentInFocus != rhs.isContentInFocus {
return false
}
if lhs.height != rhs.height {
return false
}
if lhs.visibilityFractionUpdated !== rhs.visibilityFractionUpdated {
return false
}
@ -39,17 +45,20 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
let theme: PresentationTheme
let overflowHeight: CGFloat
let topInset: CGFloat
let height: CGFloat
let displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
init(
theme: PresentationTheme,
overflowHeight: CGFloat,
topInset: CGFloat,
height: CGFloat,
displayBackground: EntityKeyboardComponent.DisplayTopPanelBackground
) {
self.theme = theme
self.overflowHeight = overflowHeight
self.topInset = topInset
self.height = height
self.displayBackground = displayBackground
}
@ -60,6 +69,9 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
if lhs.overflowHeight != rhs.overflowHeight {
return false
}
if lhs.height != rhs.height {
return false
}
if lhs.topInset != rhs.topInset {
return false
}
@ -105,7 +117,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
}
func update(component: EntityKeyboardTopContainerPanelComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
let intrinsicHeight: CGFloat = 34.0
let intrinsicHeight: CGFloat = component.height
let height = intrinsicHeight + component.topInset
let panelEnvironment = environment[PagerComponentPanelEnvironment.self].value
@ -165,6 +177,7 @@ final class EntityKeyboardTopContainerPanelComponent: Component {
environment: {
EntityKeyboardTopContainerPanelEnvironment(
isContentInFocus: panelEnvironment.isContentInFocus,
height: intrinsicHeight,
visibilityFractionUpdated: panelView.visibilityFractionUpdated,
isExpandedUpdated: { [weak self] isExpanded, transition in
guard let strongSelf = self else {

View file

@ -2009,7 +2009,7 @@ public final class EntityKeyboardTopPanelComponent: Component {
let panelEnvironment = environment[EntityKeyboardTopContainerPanelEnvironment.self].value
self.environment = panelEnvironment
let isExpanded = availableSize.height > 34.0
let isExpanded = availableSize.height > panelEnvironment.height
let wasExpanded = self.isExpanded
self.isExpanded = isExpanded

View file

@ -23,7 +23,6 @@ public final class GiftCompositionComponent: Component {
public fileprivate(set) var previewSymbol: StarGift.UniqueGift.Attribute?
public init() {
self.previewPatternColor = nil
}
}

View file

@ -3,238 +3,237 @@ import UIKit
import SwiftSignalKit
import AsyncDisplayKit
import Display
import Postbox
import ComponentFlow
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import AvatarNode
import Markdown
import AlertComponent
private final class GiftAuctionTransferAlertContentNode: AlertContentNode {
private let strings: PresentationStrings
private let title: String
private let text: String
private let titleNode: ASTextNode
private let textNode: ASTextNode
private let avatarNode: AvatarNode
private let arrowNode: ASImageNode
private let secondAvatarNode: AvatarNode
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var validLayout: CGSize?
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, fromPeer: EnginePeer, toPeer: EnginePeer, title: String, text: String, actions: [TextAlertAction]) {
self.strings = strings
self.title = title
self.text = text
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 0
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 0
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.displayWithoutProcessing = true
self.secondAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
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.avatarNode)
self.addSubnode(self.arrowNode)
self.addSubnode(self.secondAvatarNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.updateTheme(theme)
self.avatarNode.setPeer(context: context, theme: ptheme, peer: fromPeer)
self.secondAvatarNode.setPeer(context: context, theme: ptheme, peer: toPeer)
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
linkAttribute: { url in
return ("URL", url)
}
), textAlignment: .center)
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor)
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)
}
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 270.0)
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let avatarSize = CGSize(width: 60.0, height: 60.0)
self.avatarNode.updateSize(size: avatarSize)
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
if let arrowImage = self.arrowNode.image {
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
}
let secondAvatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
transition.updateFrame(node: self.secondAvatarNode, frame: secondAvatarFrame)
origin.y += avatarSize.height + 10.0
//private final class GiftAuctionTransferAlertContentNode: AlertContentNode {
// private let strings: PresentationStrings
// private let title: String
// private let text: String
//
// private let titleNode: ASTextNode
// private let textNode: ASTextNode
// private let avatarNode: AvatarNode
// private let arrowNode: ASImageNode
// private let secondAvatarNode: AvatarNode
//
// private let actionNodesSeparator: ASDisplayNode
// private let actionNodes: [TextAlertContentActionNode]
// private let actionVerticalSeparators: [ASDisplayNode]
//
// private var validLayout: CGSize?
//
// override var dismissOnOutsideTap: Bool {
// return self.isUserInteractionEnabled
// }
//
// init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, fromPeer: EnginePeer, toPeer: EnginePeer, title: String, text: String, actions: [TextAlertAction]) {
// self.strings = strings
// self.title = title
// self.text = text
//
// self.titleNode = ASTextNode()
// self.titleNode.maximumNumberOfLines = 0
//
// self.textNode = ASTextNode()
// self.textNode.maximumNumberOfLines = 0
//
// self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
//
// self.arrowNode = ASImageNode()
// self.arrowNode.displaysAsynchronously = false
// self.arrowNode.displayWithoutProcessing = true
//
// self.secondAvatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
//
// 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.avatarNode)
// self.addSubnode(self.arrowNode)
// self.addSubnode(self.secondAvatarNode)
//
// self.addSubnode(self.actionNodesSeparator)
//
// for actionNode in self.actionNodes {
// self.addSubnode(actionNode)
// }
//
// for separatorNode in self.actionVerticalSeparators {
// self.addSubnode(separatorNode)
// }
//
// self.updateTheme(theme)
//
// self.avatarNode.setPeer(context: context, theme: ptheme, peer: fromPeer)
// self.secondAvatarNode.setPeer(context: context, theme: ptheme, peer: toPeer)
// }
//
// override func updateTheme(_ theme: AlertControllerTheme) {
// self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
// self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
// body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
// bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
// link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
// linkAttribute: { url in
// return ("URL", url)
// }
// ), textAlignment: .center)
// self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor)
//
// 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)
// }
// }
//
// override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
// var size = size
// size.width = min(size.width, 270.0)
//
// self.validLayout = size
//
// var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
//
// let avatarSize = CGSize(width: 60.0, height: 60.0)
// self.avatarNode.updateSize(size: avatarSize)
//
// let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
// transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
//
// if let arrowImage = self.arrowNode.image {
// let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
// transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
// }
//
// let secondAvatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
// transition.updateFrame(node: self.secondAvatarNode, frame: secondAvatarFrame)
//
// origin.y += avatarSize.height + 10.0
//
// let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
// 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(CGSize(width: size.width - 32.0, height: size.height))
// 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 + 10.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)
// }
// }
//
// let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
//
// let contentWidth = max(size.width, minActionsWidth)
//
// var actionsHeight: CGFloat = 0.0
// switch effectiveActionLayout {
// case .horizontal:
// actionsHeight = actionButtonHeight
// case .vertical:
// actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
// }
//
// let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 16.0 + 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
// }
//}
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
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(CGSize(width: size.width - 32.0, height: size.height))
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 + 10.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)
}
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
let contentWidth = max(size.width, minActionsWidth)
var actionsHeight: CGFloat = 0.0
switch effectiveActionLayout {
case .horizontal:
actionsHeight = actionButtonHeight
case .vertical:
actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
}
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 16.0 + 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 giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer, toPeer: EnginePeer, commit: @escaping () -> Void) -> AlertController {
func giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer, toPeer: EnginePeer, commit: @escaping () -> Void) -> AlertScreen {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let title = strings.Gift_AuctionTransfer_Title
let text: String
if fromPeer.id == context.account.peerId {
text = strings.Gift_AuctionTransfer_TextFromYourself(toPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
@ -243,25 +242,74 @@ func giftAuctionTransferController(context: AccountContext, fromPeer: EnginePeer
} else {
text = strings.Gift_AuctionTransfer_Text(fromPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), toPeer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder)).string
}
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
// content.append(AnyComponentWithIdentity(
// id: "header",
// component: AnyComponent(
// AlertTransferHeaderComponent(
// fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
// AvatarComponent(
// context: context,
// theme: presentationData.theme,
// peer: fromPeer
// )
// )),
// toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent(
// AvatarComponent(
// context: context,
// theme: presentationData.theme,
// peer: toPeer
// )
// ))
// )
// )
// ))
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: title)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(text))
)
))
let alertController = AlertScreen(
context: context,
configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
content: content,
actions: [
.init(title: strings.Gift_AuctionTransfer_Change, type: .default, action: {
commit()
}),
.init(title: strings.Common_Cancel)
]
)
return alertController
var dismissImpl: ((Bool) -> Void)?
var contentNode: GiftAuctionTransferAlertContentNode?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
}), TextAlertAction(type: .defaultAction, title: strings.Gift_AuctionTransfer_Change, action: {
dismissImpl?(true)
commit()
})]
contentNode = GiftAuctionTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, fromPeer: fromPeer, toPeer: toPeer, title: strings.Gift_AuctionTransfer_Title, text: text, actions: actions)
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
}
}
return controller
// var dismissImpl: ((Bool) -> Void)?
// var contentNode: GiftAuctionTransferAlertContentNode?
// let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
// dismissImpl?(true)
// }), TextAlertAction(type: .defaultAction, title: strings.Gift_AuctionTransfer_Change, action: {
// dismissImpl?(true)
// commit()
// })]
//
// contentNode = GiftAuctionTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, fromPeer: fromPeer, toPeer: toPeer, title: strings.Gift_AuctionTransfer_Title, text: text, actions: actions)
//
// let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
// dismissImpl = { [weak controller] animated in
// if animated {
// controller?.dismissAnimated()
// } else {
// controller?.dismiss()
// }
// }
// return controller
}

View file

@ -991,7 +991,7 @@ final class GiftOptionsScreenComponent: Component {
controller.present(alertController, in: .current)
dismissAlertImpl = { [weak alertController] in
alertController?.dismissAnimated()
alertController?.dismiss()
}
}

View file

@ -64,6 +64,9 @@ swift_library(
"//submodules/TelegramUI/Components/SegmentControlComponent",
"//submodules/TelegramUI/Components/Gifts/GiftRemainingCountComponent",
"//submodules/TelegramUI/Components/Gifts/InfoParagraphComponent",
"//submodules/TelegramUI/Components/Gifts/TableComponent",
"//submodules/TelegramUI/Components/Gifts/PeerTableCellComponent",
"//submodules/TelegramUI/Components/AlertComponent",
],
visibility = [
"//visibility:public",

View file

@ -19,6 +19,8 @@ import TelegramStringFormatting
import GlassBarButtonComponent
import GiftItemComponent
import EdgeEffect
import TableComponent
import PeerTableCellComponent
private final class GiftAuctionAcquiredScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -373,7 +375,7 @@ private final class GiftAuctionAcquiredScreenComponent: Component {
title: environment.strings.Gift_Acquired_Recipient,
component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: environment.theme,
strings: environment.strings,

View file

@ -372,7 +372,7 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
if self.backgroundHandleView.image == nil {
self.backgroundHandleView.image = generateStretchableFilledCircleImage(diameter: 5.0, color: .white)?.withRenderingMode(.alwaysTemplate)
}
self.backgroundHandleView.tintColor = environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(environment.theme.overallDarkAppearance ? 0.2 : 0.07)
self.backgroundHandleView.tintColor = theme.list.itemPrimaryTextColor.withMultipliedAlpha(theme.overallDarkAppearance ? 0.2 : 0.07)
let backgroundHandleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - 36.0) * 0.5), y: 5.0), size: CGSize(width: 36.0, height: 5.0))
if self.backgroundHandleView.superview == nil {
self.navigationBarContainer.addSubview(self.backgroundHandleView)
@ -383,13 +383,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
transition: .immediate,
component: AnyComponent(GlassBarButtonComponent(
size: CGSize(width: 40.0, height: 40.0),
backgroundColor: environment.theme.rootController.navigationBar.glassBarButtonBackgroundColor,
backgroundColor: theme.rootController.navigationBar.glassBarButtonBackgroundColor,
isDark: environment.theme.overallDarkAppearance,
state: .generic,
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
BundleIconComponent(
name: "Navigation/Close",
tintColor: environment.theme.chat.inputPanel.panelControlColor
tintColor: theme.chat.inputPanel.panelControlColor
)
)),
action: { [weak self] _ in
@ -413,14 +413,13 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
let containerInset: CGFloat = environment.statusBarHeight + 10.0
contentHeight += environment.safeInsets.bottom
var initialContentHeight = contentHeight
let clippingY: CGFloat
let titleText: String = environment.strings.Gift_ActiveAuctions_Title(Int32(self.auctionStates.count))
let titleSize = self.title.update(
transition: .immediate,
component: AnyComponent(MultilineTextComponent(
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
text: .plain(NSAttributedString(string: titleText, font: Font.semibold(17.0), textColor: theme.list.itemPrimaryTextColor))
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
@ -434,12 +433,12 @@ private final class GiftAuctionActiveBidsScreenComponent: Component {
transition.setFrame(view: titleView, frame: titleFrame)
}
initialContentHeight = contentHeight
let initialContentHeight = contentHeight
let edgeEffectHeight: CGFloat = 80.0
let edgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: 0.0), size: CGSize(width: fillingSize, height: edgeEffectHeight))
transition.setFrame(view: self.topEdgeEffectView, frame: edgeEffectFrame)
self.topEdgeEffectView.update(content: environment.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
self.topEdgeEffectView.update(content: theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 1.0, rect: edgeEffectFrame, edge: .top, edgeSize: edgeEffectFrame.height, transition: transition)
if self.topEdgeEffectView.superview == nil {
self.navigationBarContainer.insertSubview(self.topEdgeEffectView, at: 0)
}

View file

@ -28,6 +28,7 @@ import ButtonComponent
import UndoUI
import LottieComponent
import AnimatedTextComponent
import TableComponent
private final class GiftAuctionViewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -1075,7 +1076,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
guard let state, let attributes = state.giftUpgradeAttributes else {
return
}
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsPreviewScreen(context: component.context, gift: .generic(gift), attributes: attributes)
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), attributes: attributes, selectedAttributes: nil, focusedAttribute: nil)
environment.controller()?.push(variantsController)
}, animateScale: false),
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),

View file

@ -3,478 +3,29 @@ import UIKit
import AsyncDisplayKit
import Display
import ComponentFlow
import SwiftSignalKit
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import AvatarNode
import Markdown
import GiftItemComponent
import ChatMessagePaymentAlertController
import ActivityIndicator
import TooltipUI
import MultilineTextComponent
import BalancedTextComponent
import BundleIconComponent
import TelegramStringFormatting
private final class GiftOfferAlertContentNode: AlertContentNode {
private let context: AccountContext
private let strings: PresentationStrings
private var presentationTheme: PresentationTheme
private let title: String
private let text: String
private let amount: CurrencyAmount
private let gift: StarGift.UniqueGift
private let titleNode: ASTextNode
private let giftView = ComponentView<Empty>()
private let textNode: ASTextNode
private let arrowNode: ASImageNode
private let avatarNode: AvatarNode
private let tableView = ComponentView<Empty>()
private let valueDelta = ComponentView<Empty>()
private let modelButtonTag = GenericComponentViewTag()
private let backdropButtonTag = GenericComponentViewTag()
private let symbolButtonTag = GenericComponentViewTag()
fileprivate var getController: () -> ViewController? = { return nil}
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var activityIndicator: ActivityIndicator?
private var validLayout: CGSize?
var inProgress = false {
didSet {
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
}
}
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(
context: AccountContext,
theme: AlertControllerTheme,
ptheme: PresentationTheme,
strings: PresentationStrings,
gift: StarGift.UniqueGift,
peer: EnginePeer,
title: String,
text: String,
amount: CurrencyAmount,
actions: [TextAlertAction]
) {
self.context = context
self.strings = strings
self.presentationTheme = ptheme
self.title = title
self.text = text
self.amount = amount
self.gift = gift
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 0
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 0
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.displayWithoutProcessing = true
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
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.arrowNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.updateTheme(theme)
self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
linkAttribute: { url in
return ("URL", url)
}
), textAlignment: .center)
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9))
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)
}
}
fileprivate func dismissAllTooltips() {
guard let controller = self.getController() else {
return
}
controller.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
})
controller.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
return true
})
}
func showAttributeInfo(tag: Any, text: String) {
guard let controller = self.getController() else {
return
}
self.dismissAllTooltips()
guard let sourceView = self.tableView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else {
return
}
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .dismiss(consume: false)
})
controller.present(tooltipController, in: .current)
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 310.0)
let strings = self.strings
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let avatarSize = CGSize(width: 60.0, height: 60.0)
self.avatarNode.updateSize(size: avatarSize)
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
let _ = self.giftView.update(
transition: .immediate,
component: AnyComponent(
GiftItemComponent(
context: self.context,
theme: self.presentationTheme,
strings: strings,
peer: nil,
subject: .uniqueGift(gift: self.gift, price: nil),
mode: .thumbnail
)
),
environment: {},
containerSize: avatarSize
)
if let view = self.giftView.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = giftFrame
}
if let arrowImage = self.arrowNode.image {
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
}
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
origin.y += avatarSize.height + 17.0
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
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 + 5.0
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
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 + 10.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
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
let contentWidth = max(size.width, minActionsWidth)
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
let tableFont = Font.regular(15.0)
let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor
var tableItems: [TableComponent.Item] = []
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
.model, .pattern, .backdrop, .originalInfo
]
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
for attribute in self.gift.attributes {
attributeMap[attribute.attributeType] = attribute
}
for type in order {
if let attribute = attributeMap[type] {
let id: String?
let title: String?
let value: NSAttributedString
let percentage: Float?
let tag: AnyObject?
switch attribute {
case let .model(name, _, rarity):
id = "model"
title = strings.Gift_Unique_Model
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = self.modelButtonTag
case let .backdrop(name, _, _, _, _, _, rarity):
id = "backdrop"
title = strings.Gift_Unique_Backdrop
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = self.backdropButtonTag
case let .pattern(name, _, rarity):
id = "pattern"
title = strings.Gift_Unique_Symbol
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = self.symbolButtonTag
case .originalInfo:
continue
}
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(
MultilineTextComponent(text: .plain(value))
)
)
)
if let percentage, let tag {
items.append(AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: self.context,
text: formatPercentage(percentage),
color: self.presentationTheme.list.itemAccentColor
)),
action: { [weak self] in
self?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
}
).tagged(tag))
))
}
let itemComponent = AnyComponent(
HStack(items, spacing: 4.0)
)
tableItems.append(.init(
id: id,
title: title,
hasBackground: false,
component: itemComponent
))
}
}
let tableSize = self.tableView.update(
transition: .immediate,
component: AnyComponent(
TableComponent(
theme: self.presentationTheme,
items: tableItems,
semiTransparent: true
)
),
environment: {},
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
)
let tableFrame = CGRect(origin: CGPoint(x: 16.0, y: avatarSize.height + titleSize.height + textSize.height + 60.0), size: tableSize)
if let view = self.tableView.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = tableFrame
}
var valueDeltaHeight: CGFloat = 0.0
if let valueAmount = self.gift.valueUsdAmount {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: self.context.currentAppConfiguration.with { $0 })
let usdRate: Double
switch self.amount.currency {
case .stars:
usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0
case .ton:
usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0
}
let offerUsdValue = Double(self.amount.amount.value) * usdRate
let giftUsdValue = Double(valueAmount) / 100.0
let fraction = giftUsdValue / offerUsdValue
let percentage = Int(fraction * 100) - 100
if percentage > 20 {
let textColor = self.presentationTheme.list.itemDestructiveColor
let markdownAttributes = MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: textColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: textColor),
linkAttribute: { url in
return ("URL", url)
}
)
let valueDeltaSize = self.valueDelta.update(
transition: .immediate,
component: AnyComponent(
BalancedTextComponent(
text: .markdown(text: strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string, attributes: markdownAttributes),
horizontalAlignment: .center,
maximumNumberOfLines: 0
)
),
environment: {},
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
)
let valueDeltaFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - valueDeltaSize.width) / 2.0), y: avatarSize.height + titleSize.height + textSize.height + 73.0 + tableSize.height), size: valueDeltaSize)
if let view = self.valueDelta.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = valueDeltaFrame
}
valueDeltaHeight += valueDeltaSize.height + 10.0
}
}
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + valueDeltaHeight + 40.0 + 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
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
do {
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
do {
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
do {
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
}
if self.inProgress {
let activityIndicator: ActivityIndicator
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
self.addSubnode(activityIndicator)
}
if let actionNode = self.actionNodes.first {
actionNode.isUserInteractionEnabled = false
actionNode.isHidden = false
let indicatorSize = CGSize(width: 22.0, height: 22.0)
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
}
}
return resultSize
}
}
import AlertComponent
import TableComponent
public func giftOfferAlertController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?,
gift: StarGift.UniqueGift,
peer: EnginePeer,
amount: CurrencyAmount,
commit: @escaping () -> Void
) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
) -> ViewController {
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
let title = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Title
@ -502,28 +53,367 @@ public func giftOfferAlertController(
let giftTitle = "\(gift.title) #\(formatCollectibleNumber(gift.number, dateTimeFormat: presentationData.dateTimeFormat))"
let text = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_Text(giftTitle, peer.displayTitle(strings: strings, displayOrder: presentationData.nameDisplayOrder), priceString, finalPriceString).string
var contentNode: GiftOfferAlertContentNode?
var dismissImpl: ((Bool) -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in
contentNode?.inProgress = true
commit()
dismissImpl?(true)
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
})]
let tableFont = Font.regular(15.0)
let tableTextColor = presentationData.theme.list.itemPrimaryTextColor
contentNode = GiftOfferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, amount: amount, actions: actions)
let modelButtonTag = GenericComponentViewTag()
let backdropButtonTag = GenericComponentViewTag()
let symbolButtonTag = GenericComponentViewTag()
var showAttributeInfoImpl: ((Any, String) -> Void)?
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: nil, chatPeerId: context.account.peerId, showBalance: false)
contentNode?.getController = { [weak controller] in
return controller
var tableItems: [TableComponent.Item] = []
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
.model, .pattern, .backdrop, .originalInfo
]
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
for attribute in gift.attributes {
attributeMap[attribute.attributeType] = attribute
}
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
for type in order {
if let attribute = attributeMap[type] {
let id: String?
let title: String?
let value: NSAttributedString
let percentage: Float?
let tag: AnyObject?
switch attribute {
case let .model(name, _, rarity):
id = "model"
title = strings.Gift_Unique_Model
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = modelButtonTag
case let .backdrop(name, _, _, _, _, _, rarity):
id = "backdrop"
title = strings.Gift_Unique_Backdrop
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = backdropButtonTag
case let .pattern(name, _, rarity):
id = "pattern"
title = strings.Gift_Unique_Symbol
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = symbolButtonTag
case .originalInfo:
continue
}
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(
MultilineTextComponent(text: .plain(value))
)
)
)
if let percentage, let tag {
items.append(AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: context,
text: formatPercentage(percentage),
color: presentationData.theme.list.itemAccentColor
)),
action: {
showAttributeInfoImpl?(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
}
).tagged(tag))
))
}
let itemComponent = AnyComponent(
HStack(items, spacing: 4.0)
)
tableItems.append(.init(
id: id,
title: title,
hasBackground: false,
component: itemComponent
))
}
}
return controller
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "header",
component: AnyComponent(
AlertTransferHeaderComponent(
fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
GiftItemComponent(
context: context,
theme: presentationData.theme,
strings: strings,
peer: nil,
subject: .uniqueGift(gift: gift, price: nil),
mode: .thumbnail
)
)),
toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent(
AvatarComponent(
context: context,
theme: presentationData.theme,
peer: peer
)
))
)
)
))
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: title)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(text))
)
))
content.append(AnyComponentWithIdentity(
id: "table",
component: AnyComponent(
AlertTableComponent(items: tableItems)
)
))
if let valueAmount = gift.valueUsdAmount {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
let usdRate: Double
switch amount.currency {
case .stars:
usdRate = Double(resaleConfiguration.usdWithdrawRate) / 1000.0 / 100.0
case .ton:
usdRate = Double(resaleConfiguration.tonUsdRate) / 1000.0 / 1000000.0
}
let offerUsdValue = Double(amount.amount.value) * usdRate
let giftUsdValue = Double(valueAmount) / 100.0
let fraction = giftUsdValue / offerUsdValue
let percentage = Int(fraction * 100) - 100
if percentage > 20 {
let warningText = strings.Chat_GiftPurchaseOffer_AcceptConfirmation_BadValue("\(percentage)%").string
content.append(AnyComponentWithIdentity(
id: "warning",
component: AnyComponent(
AlertTextComponent(content: .plain(warningText), color: .destructive, textSize: .small)
)
))
}
}
let updatedPresentationDataSignal = updatedPresentationData?.signal ?? context.sharedContext.presentationData
let alertController = AlertScreen(
configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
content: content,
actions: [
.init(title: buttonText, type: .default, action: {
commit()
}),
.init(title: strings.Common_Cancel)
],
updatedPresentationData: (initial: presentationData, signal: updatedPresentationDataSignal)
)
var dismissAllTooltipsImpl: (() -> Void)?
showAttributeInfoImpl = { [weak alertController] tag, text in
dismissAllTooltipsImpl?()
guard let alertController, let sourceView = alertController.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: alertController.view) else {
return
}
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .dismiss(consume: false)
})
alertController.present(tooltipController, in: .current)
}
dismissAllTooltipsImpl = { [weak alertController] in
guard let alertController else {
return
}
alertController.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
})
alertController.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
return true
})
}
return alertController
}
final class AlertTransferHeaderComponent: Component {
typealias EnvironmentType = AlertComponentEnvironment
let fromComponent: AnyComponentWithIdentity<Empty>
let toComponent: AnyComponentWithIdentity<Empty>
public init(
fromComponent: AnyComponentWithIdentity<Empty>,
toComponent: AnyComponentWithIdentity<Empty>
) {
self.fromComponent = fromComponent
self.toComponent = toComponent
}
public static func ==(lhs: AlertTransferHeaderComponent, rhs: AlertTransferHeaderComponent) -> Bool {
if lhs.fromComponent != rhs.fromComponent {
return false
}
if lhs.toComponent != rhs.toComponent {
return false
}
return true
}
public final class View: UIView {
private let from = ComponentView<Empty>()
private let to = ComponentView<Empty>()
private let arrow = ComponentView<Empty>()
private var component: AlertTransferHeaderComponent?
private weak var state: EmptyComponentState?
func update(component: AlertTransferHeaderComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let size = CGSize(width: 148.0, height: 60.0)
let sideInset = floorToScreenPixels((availableSize.width - size.width) / 2.0)
let fromSize = self.from.update(
transition: transition,
component: component.fromComponent.component,
environment: {},
containerSize: CGSize(width: 60.0, height: 60.0)
)
let fromFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: fromSize)
if let fromView = self.from.view {
if fromView.superview == nil {
self.addSubview(fromView)
}
transition.setFrame(view: fromView, frame: fromFrame)
}
let arrowSize = self.arrow.update(
transition: transition,
component: AnyComponent(
BundleIconComponent(name: "Peer Info/AlertArrow", tintColor: environment.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.2))
),
environment: {},
containerSize: availableSize
)
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - arrowSize.width) / 2.0), y: floorToScreenPixels((size.height - arrowSize.height) / 2.0)), size: arrowSize)
if let arrowView = self.arrow.view {
if arrowView.superview == nil {
self.addSubview(arrowView)
}
transition.setFrame(view: arrowView, frame: arrowFrame)
}
let toSize = self.to.update(
transition: transition,
component: component.toComponent.component,
environment: {},
containerSize: CGSize(width: 60.0, height: 60.0)
)
let toFrame = CGRect(origin: CGPoint(x: availableSize.width - toSize.width - sideInset, y: 0.0), size: toSize)
if let toView = self.to.view {
if toView.superview == nil {
self.addSubview(toView)
}
transition.setFrame(view: toView, frame: toFrame)
}
return CGSize(width: availableSize.width, height: size.height + 11.0)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class AlertTableComponent: Component {
typealias EnvironmentType = AlertComponentEnvironment
let items: [TableComponent.Item]
public init(
items: [TableComponent.Item]
) {
self.items = items
}
public static func ==(lhs: AlertTableComponent, rhs: AlertTableComponent) -> Bool {
if lhs.items != rhs.items {
return false
}
return true
}
public final class View: UIView {
private let table = ComponentView<Empty>()
private var component: AlertTableComponent?
private weak var state: EmptyComponentState?
func update(component: AlertTableComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let environment = environment[AlertComponentEnvironment.self]
let tableSize = self.table.update(
transition: transition,
component: AnyComponent(
TableComponent(
theme: environment.theme,
items: component.items,
semiTransparent: true
)
),
environment: {},
containerSize: CGSize(width: availableSize.width + 20.0, height: availableSize.height)
)
let tableFrame = CGRect(origin: CGPoint(x: -10.0, y: 5.0), size: tableSize)
if let tableView = self.table.view {
if tableView.superview == nil {
self.addSubview(tableView)
}
transition.setFrame(view: tableView, frame: tableFrame)
}
return CGSize(width: availableSize.width, height: tableSize.height + 10.0)
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<AlertComponentEnvironment>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -15,6 +15,7 @@ import ActivityIndicator
import MultilineTextWithEntitiesComponent
import TelegramStringFormatting
import TextFormat
import AlertComponent
private final class GiftRemoveInfoAlertContentNode: AlertContentNode {
private let context: AccountContext

View file

@ -6,421 +6,15 @@ import ComponentFlow
import Postbox
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import AppBundle
import AvatarNode
import Markdown
import GiftItemComponent
import ChatMessagePaymentAlertController
import ActivityIndicator
import TooltipUI
import MultilineTextComponent
import TelegramStringFormatting
private final class GiftTransferAlertContentNode: AlertContentNode {
private let context: AccountContext
private let strings: PresentationStrings
private var presentationTheme: PresentationTheme
private let title: String
private let text: String
private let gift: StarGift.UniqueGift
private let titleNode: ASTextNode
private let giftView = ComponentView<Empty>()
private let textNode: ASTextNode
private let arrowNode: ASImageNode
private let avatarNode: AvatarNode
private let tableView = ComponentView<Empty>()
private let modelButtonTag = GenericComponentViewTag()
private let backdropButtonTag = GenericComponentViewTag()
private let symbolButtonTag = GenericComponentViewTag()
fileprivate var getController: () -> ViewController? = { return nil}
private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode]
private var activityIndicator: ActivityIndicator?
private var validLayout: CGSize?
var inProgress = false {
didSet {
if let size = self.validLayout {
let _ = self.updateLayout(size: size, transition: .immediate)
}
}
}
override var dismissOnOutsideTap: Bool {
return self.isUserInteractionEnabled
}
init(
context: AccountContext,
theme: AlertControllerTheme,
ptheme: PresentationTheme,
strings: PresentationStrings,
gift: StarGift.UniqueGift,
peer: EnginePeer,
title: String,
text: String,
actions: [TextAlertAction]
) {
self.context = context
self.strings = strings
self.presentationTheme = ptheme
self.title = title
self.text = text
self.gift = gift
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 0
self.textNode = ASTextNode()
self.textNode.maximumNumberOfLines = 0
self.arrowNode = ASImageNode()
self.arrowNode.displaysAsynchronously = false
self.arrowNode.displayWithoutProcessing = true
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
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.arrowNode)
self.addSubnode(self.avatarNode)
self.addSubnode(self.actionNodesSeparator)
for actionNode in self.actionNodes {
self.addSubnode(actionNode)
}
for separatorNode in self.actionVerticalSeparators {
self.addSubnode(separatorNode)
}
self.updateTheme(theme)
self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
}
override func updateTheme(_ theme: AlertControllerTheme) {
self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor)
self.textNode.attributedText = parseMarkdownIntoAttributedString(self.text, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
link: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
linkAttribute: { url in
return ("URL", url)
}
), textAlignment: .center)
self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.secondaryColor.withAlphaComponent(0.9))
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)
}
}
fileprivate func dismissAllTooltips() {
guard let controller = self.getController() else {
return
}
controller.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
})
controller.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
return true
})
}
func showAttributeInfo(tag: Any, text: String) {
guard let controller = self.getController() else {
return
}
self.dismissAllTooltips()
guard let sourceView = self.tableView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: controller.view) else {
return
}
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
let tooltipController = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .dismiss(consume: false)
})
controller.present(tooltipController, in: .current)
}
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
var size = size
size.width = min(size.width, 310.0)
let strings = self.strings
self.validLayout = size
var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
let avatarSize = CGSize(width: 60.0, height: 60.0)
self.avatarNode.updateSize(size: avatarSize)
let giftFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) - 44.0, y: origin.y), size: avatarSize)
let _ = self.giftView.update(
transition: .immediate,
component: AnyComponent(
GiftItemComponent(
context: self.context,
theme: self.presentationTheme,
strings: strings,
peer: nil,
subject: .uniqueGift(gift: self.gift, price: nil),
mode: .thumbnail
)
),
environment: {},
containerSize: avatarSize
)
if let view = self.giftView.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = giftFrame
}
if let arrowImage = self.arrowNode.image {
let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
}
let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0) + 44.0, y: origin.y), size: avatarSize)
transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
origin.y += avatarSize.height + 17.0
let titleSize = self.titleNode.measure(CGSize(width: size.width - 32.0, height: size.height))
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 + 5.0
let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
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 + 10.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
for actionNode in self.actionNodes {
let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
}
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
let contentWidth = max(size.width, minActionsWidth)
let actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
let tableFont = Font.regular(15.0)
let tableTextColor = self.presentationTheme.list.itemPrimaryTextColor
var tableItems: [TableComponent.Item] = []
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
.model, .pattern, .backdrop, .originalInfo
]
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
for attribute in self.gift.attributes {
attributeMap[attribute.attributeType] = attribute
}
for type in order {
if let attribute = attributeMap[type] {
let id: String?
let title: String?
let value: NSAttributedString
let percentage: Float?
let tag: AnyObject?
switch attribute {
case let .model(name, _, rarity):
id = "model"
title = strings.Gift_Unique_Model
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = self.modelButtonTag
case let .backdrop(name, _, _, _, _, _, rarity):
id = "backdrop"
title = strings.Gift_Unique_Backdrop
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = self.backdropButtonTag
case let .pattern(name, _, rarity):
id = "pattern"
title = strings.Gift_Unique_Symbol
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = self.symbolButtonTag
case .originalInfo:
continue
}
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(
MultilineTextComponent(text: .plain(value))
)
)
)
if let percentage, let tag {
items.append(AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: self.context,
text: formatPercentage(percentage),
color: self.presentationTheme.list.itemAccentColor
)),
action: { [weak self] in
self?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
}
).tagged(tag))
))
}
let itemComponent = AnyComponent(
HStack(items, spacing: 4.0)
)
tableItems.append(.init(
id: id,
title: title,
hasBackground: false,
component: itemComponent
))
}
}
if let valueAmount = self.gift.valueAmount, let valueCurrency = self.gift.valueCurrency {
tableItems.append(.init(
id: "fiatValue",
title: strings.Gift_Unique_Value,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "~\(formatCurrencyAmount(valueAmount, currency: valueCurrency))", font: tableFont, textColor: tableTextColor)))
),
insets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 12.0)
))
}
let tableSize = self.tableView.update(
transition: .immediate,
component: AnyComponent(
TableComponent(
theme: self.presentationTheme,
items: tableItems
)
),
environment: {},
containerSize: CGSize(width: contentWidth - 32.0, height: size.height)
)
let tableFrame = CGRect(origin: CGPoint(x: 16.0, y: avatarSize.height + titleSize.height + textSize.height + 60.0), size: tableSize)
if let view = self.tableView.view {
if view.superview == nil {
self.view.addSubview(view)
}
view.frame = tableFrame
}
let resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + tableSize.height + actionsHeight + 40.0 + 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
var separatorIndex = -1
var nodeIndex = 0
for actionNode in self.actionNodes {
if separatorIndex >= 0 {
let separatorNode = self.actionVerticalSeparators[separatorIndex]
do {
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
do {
currentActionWidth = resultSize.width
}
let actionNodeFrame: CGRect
do {
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
}
if self.inProgress {
let activityIndicator: ActivityIndicator
if let current = self.activityIndicator {
activityIndicator = current
} else {
activityIndicator = ActivityIndicator(type: .custom(self.presentationTheme.list.freeInputField.controlColor, 18.0, 1.5, false))
self.addSubnode(activityIndicator)
}
if let actionNode = self.actionNodes.first {
actionNode.isUserInteractionEnabled = false
actionNode.isHidden = false
let indicatorSize = CGSize(width: 22.0, height: 22.0)
transition.updateFrame(node: activityIndicator, frame: CGRect(origin: CGPoint(x: actionNode.frame.minX + floor((actionNode.frame.width - indicatorSize.width) / 2.0), y: actionNode.frame.minY + floor((actionNode.frame.height - indicatorSize.height) / 2.0)), size: indicatorSize))
}
}
return resultSize
}
}
import AlertComponent
import TableComponent
public func giftTransferAlertController(
context: AccountContext,
@ -429,7 +23,7 @@ public func giftTransferAlertController(
transferStars: Int64,
navigationController: NavigationController?,
commit: @escaping () -> Void
) -> AlertController {
) -> AlertScreen {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
@ -444,27 +38,176 @@ public func giftTransferAlertController(
buttonText = strings.Gift_Transfer_Confirmation_TransferFree
}
var contentNode: GiftTransferAlertContentNode?
var dismissImpl: ((Bool) -> Void)?
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { [weak contentNode] in
contentNode?.inProgress = true
commit()
}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
dismissImpl?(true)
})]
let tableFont = Font.regular(15.0)
let tableTextColor = presentationData.theme.list.itemPrimaryTextColor
contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
let modelButtonTag = GenericComponentViewTag()
let backdropButtonTag = GenericComponentViewTag()
let symbolButtonTag = GenericComponentViewTag()
var showAttributeInfoImpl: ((Any, String) -> Void)?
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode!, navigationController: navigationController, chatPeerId: context.account.peerId, showBalance: transferStars > 0)
contentNode?.getController = { [weak controller] in
return controller
var tableItems: [TableComponent.Item] = []
let order: [StarGift.UniqueGift.Attribute.AttributeType] = [
.model, .pattern, .backdrop, .originalInfo
]
var attributeMap: [StarGift.UniqueGift.Attribute.AttributeType: StarGift.UniqueGift.Attribute] = [:]
for attribute in gift.attributes {
attributeMap[attribute.attributeType] = attribute
}
dismissImpl = { [weak controller] animated in
if animated {
controller?.dismissAnimated()
} else {
controller?.dismiss()
for type in order {
if let attribute = attributeMap[type] {
let id: String?
let title: String?
let value: NSAttributedString
let percentage: Float?
let tag: AnyObject?
switch attribute {
case let .model(name, _, rarity):
id = "model"
title = strings.Gift_Unique_Model
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = modelButtonTag
case let .backdrop(name, _, _, _, _, _, rarity):
id = "backdrop"
title = strings.Gift_Unique_Backdrop
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = backdropButtonTag
case let .pattern(name, _, rarity):
id = "pattern"
title = strings.Gift_Unique_Symbol
value = NSAttributedString(string: name, font: tableFont, textColor: tableTextColor)
percentage = Float(rarity) * 0.1
tag = symbolButtonTag
case .originalInfo:
continue
}
var items: [AnyComponentWithIdentity<Empty>] = []
items.append(
AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(
MultilineTextComponent(text: .plain(value))
)
)
)
if let percentage, let tag {
items.append(AnyComponentWithIdentity(
id: AnyHashable(1),
component: AnyComponent(Button(
content: AnyComponent(ButtonContentComponent(
context: context,
text: formatPercentage(percentage),
color: presentationData.theme.list.itemAccentColor
)),
action: {
showAttributeInfoImpl?(tag, strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
}
).tagged(tag))
))
}
let itemComponent = AnyComponent(
HStack(items, spacing: 4.0)
)
tableItems.append(.init(
id: id,
title: title,
hasBackground: false,
component: itemComponent
))
}
}
return controller
var content: [AnyComponentWithIdentity<AlertComponentEnvironment>] = []
content.append(AnyComponentWithIdentity(
id: "header",
component: AnyComponent(
AlertTransferHeaderComponent(
fromComponent: AnyComponentWithIdentity(id: "gift", component: AnyComponent(
GiftItemComponent(
context: context,
theme: presentationData.theme,
strings: strings,
peer: nil,
subject: .uniqueGift(gift: gift, price: nil),
mode: .thumbnail
)
)),
toComponent: AnyComponentWithIdentity(id: "avatar", component: AnyComponent(
AvatarComponent(
context: context,
theme: presentationData.theme,
peer: peer
)
))
)
)
))
content.append(AnyComponentWithIdentity(
id: "title",
component: AnyComponent(
AlertTitleComponent(title: title)
)
))
content.append(AnyComponentWithIdentity(
id: "text",
component: AnyComponent(
AlertTextComponent(content: .plain(text))
)
))
content.append(AnyComponentWithIdentity(
id: "table",
component: AnyComponent(
AlertTableComponent(items: tableItems)
)
))
let alertController = AlertScreen(
context: context,
configuration: AlertScreen.Configuration(actionAlignment: .vertical, dismissOnOutsideTap: true, allowInputInset: false),
content: content,
actions: [
.init(title: buttonText, type: .default, action: {
commit()
}),
.init(title: strings.Common_Cancel)
]
)
var dismissAllTooltipsImpl: (() -> Void)?
showAttributeInfoImpl = { [weak alertController] tag, text in
dismissAllTooltipsImpl?()
guard let alertController, let sourceView = alertController.node.hostView.findTaggedView(tag: tag), let absoluteLocation = sourceView.superview?.convert(sourceView.center, to: alertController.view) else {
return
}
let location = CGRect(origin: CGPoint(x: absoluteLocation.x, y: absoluteLocation.y - 12.0), size: CGSize())
let tooltipController = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: .plain(text: text), style: .wide, location: .point(location, .bottom), displayDuration: .default, inset: 16.0, shouldDismissOnTouch: { _, _ in
return .dismiss(consume: false)
})
alertController.present(tooltipController, in: .current)
}
dismissAllTooltipsImpl = { [weak alertController] in
guard let alertController else {
return
}
alertController.window?.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
})
alertController.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss(inPlace: false)
}
return true
})
}
return alertController
}

View file

@ -17,6 +17,7 @@ import LottieComponent
import ProfileLevelRatingBarComponent
import TextFormat
import TelegramStringFormatting
import TableComponent
private final class GiftUpgradeCostScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment

View file

@ -24,24 +24,30 @@ import SegmentControlComponent
import GiftAnimationComponent
import GlassBackgroundComponent
private final class GiftUpgradePreviewScreenComponent: Component {
private final class GiftUpgradeVariantsScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
let context: AccountContext
let gift: StarGift
let attributes: [StarGift.UniqueGift.Attribute]
let selectedAttributes: [StarGift.UniqueGift.Attribute]?
let focusedAttribute: StarGift.UniqueGift.Attribute?
init(
context: AccountContext,
gift: StarGift,
attributes: [StarGift.UniqueGift.Attribute]
attributes: [StarGift.UniqueGift.Attribute],
selectedAttributes: [StarGift.UniqueGift.Attribute]?,
focusedAttribute: StarGift.UniqueGift.Attribute?
) {
self.context = context
self.gift = gift
self.attributes = attributes
self.selectedAttributes = selectedAttributes
self.focusedAttribute = focusedAttribute
}
static func ==(lhs: GiftUpgradePreviewScreenComponent, rhs: GiftUpgradePreviewScreenComponent) -> Bool {
static func ==(lhs: GiftUpgradeVariantsScreenComponent, rhs: GiftUpgradeVariantsScreenComponent) -> Bool {
return true
}
@ -127,7 +133,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
private var ignoreScrolling: Bool = false
private var component: GiftUpgradePreviewScreenComponent?
private var component: GiftUpgradeVariantsScreenComponent?
private weak var state: EmptyComponentState?
private var isUpdating: Bool = false
private var environment: ViewControllerComponentContainer.Environment?
@ -426,7 +432,11 @@ private final class GiftUpgradePreviewScreenComponent: Component {
rarity = rarityValue
modelAttribute = attribute
isSelected = self.selectedModel == attribute
if case let .model(_, selectedFile, _) = self.selectedModel {
isSelected = file.fileId == selectedFile.fileId
} else {
isSelected = false
}
}
case let .backdrop(name, id, _, _, _, _, rarityValue):
itemId += "\(id)"
@ -435,7 +445,11 @@ private final class GiftUpgradePreviewScreenComponent: Component {
rarity = rarityValue
backdropAttribute = attribute
isSelected = self.selectedBackdrop == attribute
if case let .backdrop(_, selectedId, _, _, _, _, _) = self.selectedBackdrop {
isSelected = id == selectedId
} else {
isSelected = false
}
}
case let .pattern(name, file, rarityValue):
itemId += "\(file.fileId.id)"
@ -444,7 +458,11 @@ private final class GiftUpgradePreviewScreenComponent: Component {
rarity = rarityValue
symbolAttribute = attribute
isSelected = self.selectedSymbol == attribute
if case let .pattern(_, selectedFile, _) = self.selectedSymbol {
isSelected = file.fileId == selectedFile.fileId
} else {
isSelected = false
}
}
default:
break
@ -557,7 +575,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}
}
func update(component: GiftUpgradePreviewScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
func update(component: GiftUpgradeVariantsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
self.isUpdating = true
defer {
self.isUpdating = false
@ -626,6 +644,34 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}).shuffled().prefix(15))
self.previewSymbols = randomSymbols
if let selectedAttributes = component.selectedAttributes {
self.isPlaying = false
for attribute in selectedAttributes {
switch attribute {
case .model:
self.selectedModel = attribute
case .pattern:
self.selectedSymbol = attribute
case .backdrop:
self.selectedBackdrop = attribute
default:
break
}
}
}
if let focusedAttribute = component.focusedAttribute {
switch focusedAttribute {
case .model:
self.selectedSection = .models
case .pattern:
self.selectedSection = .symbols
case .backdrop:
self.selectedSection = .backdrops
default:
break
}
}
self.updateEffectiveGifts(attributes: component.attributes)
}
@ -826,6 +872,16 @@ private final class GiftUpgradePreviewScreenComponent: Component {
contentHeight += 16.0
let selectedId: AnyHashable
switch self.selectedSection {
case .models:
selectedId = AnyHashable(SelectedSection.models)
case .backdrops:
selectedId = AnyHashable(SelectedSection.backdrops)
case .symbols:
selectedId = AnyHashable(SelectedSection.symbols)
}
let segmentedSize = self.segmentControl.update(
transition: transition,
component: AnyComponent(SegmentControlComponent(
@ -835,7 +891,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.backdrops), title: environment.strings.Gift_Variants_Backdrops),
SegmentControlComponent.Item(id: AnyHashable(SelectedSection.symbols), title: environment.strings.Gift_Variants_Symbols)
],
selectedId: "models",
selectedId: selectedId,
action: { [weak self] id in
guard let self, let component = self.component, let id = id.base as? SelectedSection else {
return
@ -1074,7 +1130,7 @@ private final class GiftUpgradePreviewScreenComponent: Component {
}
}
public class GiftUpgradePreviewScreen: ViewControllerComponentContainer {
public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
private let context: AccountContext
private var didPlayAppearAnimation: Bool = false
@ -1083,14 +1139,18 @@ public class GiftUpgradePreviewScreen: ViewControllerComponentContainer {
public init(
context: AccountContext,
gift: StarGift,
attributes: [StarGift.UniqueGift.Attribute]
attributes: [StarGift.UniqueGift.Attribute],
selectedAttributes: [StarGift.UniqueGift.Attribute]?,
focusedAttribute: StarGift.UniqueGift.Attribute?
) {
self.context = context
super.init(context: context, component: GiftUpgradePreviewScreenComponent(
super.init(context: context, component: GiftUpgradeVariantsScreenComponent(
context: context,
gift: gift,
attributes: attributes
attributes: attributes,
selectedAttributes: selectedAttributes,
focusedAttribute: focusedAttribute
), navigationBarAppearance: .none, theme: .default)
self.statusBar.statusBarStyle = .Ignore
@ -1114,7 +1174,7 @@ public class GiftUpgradePreviewScreen: ViewControllerComponentContainer {
if !self.didPlayAppearAnimation {
self.didPlayAppearAnimation = true
if let componentView = self.node.hostView.componentView as? GiftUpgradePreviewScreenComponent.View {
if let componentView = self.node.hostView.componentView as? GiftUpgradeVariantsScreenComponent.View {
componentView.animateIn()
}
}
@ -1124,7 +1184,7 @@ public class GiftUpgradePreviewScreen: ViewControllerComponentContainer {
if !self.isDismissed {
self.isDismissed = true
if let componentView = self.node.hostView.componentView as? GiftUpgradePreviewScreenComponent.View {
if let componentView = self.node.hostView.componentView as? GiftUpgradeVariantsScreenComponent.View {
componentView.animateOut(completion: { [weak self] in
completion?()
self?.dismiss(animated: false)

View file

@ -25,6 +25,7 @@ import GiftAnimationComponent
import ContextUI
import GiftItemComponent
import GlassBarButtonComponent
import TableComponent
private final class GiftValueSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment

View file

@ -41,6 +41,8 @@ import ProfileLevelRatingBarComponent
import AnimatedTextComponent
import InfoParagraphComponent
import ChatMessagePaymentAlertController
import TableComponent
import PeerTableCellComponent
private final class GiftViewSheetContent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -121,6 +123,8 @@ private final class GiftViewSheetContent: CombinedComponent {
let levelsDisposable = MetaDisposable()
var nextGiftToUpgrade: ProfileGiftsContext.State.StarGift?
var giftVariantsDisposable = MetaDisposable()
var buyForm: BotPaymentForm?
var buyFormDisposable: Disposable?
var buyDisposable: Disposable?
@ -375,6 +379,7 @@ private final class GiftViewSheetContent: CombinedComponent {
self.buyDisposable?.dispose()
self.levelsDisposable.dispose()
self.starsTopUpOptionsDisposable?.dispose()
self.giftVariantsDisposable.dispose()
}
func openPeer(_ peer: EnginePeer, gifts: Bool = false, dismiss: Bool = true) {
@ -1351,6 +1356,33 @@ private final class GiftViewSheetContent: CombinedComponent {
})
}
func openUpgradeVariants(attribute: StarGift.UniqueGift.Attribute? = nil) {
guard let controller = self.getController() as? GiftViewScreen, let arguments = self.subject.arguments else {
return
}
var selectedAttributes: [StarGift.UniqueGift.Attribute]?
if case let .unique(uniqueGift) = arguments.gift {
selectedAttributes = uniqueGift.attributes
}
self.giftVariantsDisposable.set((self.context.engine.payments.getStarGiftUpgradeAttributes(giftId: arguments.gift.giftId)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] attributes in
guard let self, let attributes else {
return
}
let variantsController = self.context.sharedContext.makeGiftUpgradeVariantsScreen(
context: self.context,
gift: arguments.gift,
attributes: attributes,
selectedAttributes: selectedAttributes,
focusedAttribute: attribute
)
controller.push(variantsController)
}))
}
func showAttributeInfo(tag: Any, text: String) {
guard let controller = self.getController() as? GiftViewScreen else {
return
@ -2514,7 +2546,7 @@ private final class GiftViewSheetContent: CombinedComponent {
let upgradeNextButton = Child(PlainButtonComponent.self)
let upgradeTitle = Child(MultilineTextComponent.self)
let upgradeDescription = Child(BalancedTextComponent.self)
let upgradeDescription = Child(PlainButtonComponent.self)
let upgradePerks = Child(List<Empty>.self)
let upgradeKeepName = Child(PlainButtonComponent.self)
let upgradePriceButton = Child(PlainButtonComponent.self)
@ -2721,10 +2753,10 @@ private final class GiftViewSheetContent: CombinedComponent {
}
headerSubject = .unique(state.justUpgraded ? state.upgradePreview?.attributes : nil, uniqueGift)
} else if state.inUpgradePreview, let attributes = state.upgradePreview?.attributes {
headerHeight = 258.0
headerHeight = 246.0
headerSubject = .preview(attributes)
} else if case let .upgradePreview(attributes, _) = component.subject {
headerHeight = 258.0
headerHeight = 246.0
headerSubject = .preview(attributes)
} else if case let .wearPreview(_, attributes) = component.subject, let attributes {
headerHeight = 200.0
@ -2978,9 +3010,9 @@ private final class GiftViewSheetContent: CombinedComponent {
originY += 16.0
} else if showUpgradePreview {
let title: String
let description: String
//let description: String
let uniqueText: String
let transferableText: String
//let transferableText: String
let tradableText: String
if !incoming, case let .profileGift(peerId, _) = subject, let peer = state.peerMap[peerId] {
var peerName = peer.compactDisplayTitle
@ -2988,9 +3020,9 @@ private final class GiftViewSheetContent: CombinedComponent {
peerName = "\(peerName.prefix(22))"
}
title = environment.strings.Gift_Upgrade_GiftTitle
description = environment.strings.Gift_Upgrade_GiftDescription(peerName).string
// description = environment.strings.Gift_Upgrade_GiftDescription(peerName).string
uniqueText = strings.Gift_Upgrade_Unique_GiftDescription(peerName).string
transferableText = strings.Gift_Upgrade_Transferable_GiftDescription(peerName).string
// transferableText = strings.Gift_Upgrade_Transferable_GiftDescription(peerName).string
tradableText = strings.Gift_Upgrade_Tradable_GiftDescription(peerName).string
} else if case let .upgradePreview(_, peerName) = component.subject {
var peerName = peerName
@ -2998,15 +3030,15 @@ private final class GiftViewSheetContent: CombinedComponent {
peerName = "\(peerName.prefix(22))"
}
title = environment.strings.Gift_Upgrade_IncludeTitle
description = environment.strings.Gift_Upgrade_IncludeDescription(peerName).string
// description = environment.strings.Gift_Upgrade_IncludeDescription(peerName).string
uniqueText = strings.Gift_Upgrade_Unique_IncludeDescription
transferableText = strings.Gift_Upgrade_Transferable_IncludeDescription
// transferableText = strings.Gift_Upgrade_Transferable_IncludeDescription
tradableText = strings.Gift_Upgrade_Tradable_IncludeDescription
} else {
title = environment.strings.Gift_Upgrade_Title
description = environment.strings.Gift_Upgrade_Description
// description = environment.strings.Gift_Upgrade_Description
uniqueText = strings.Gift_Upgrade_Unique_Description
transferableText = strings.Gift_Upgrade_Transferable_Description
// transferableText = strings.Gift_Upgrade_Transferable_Description
tradableText = strings.Gift_Upgrade_Tradable_Description
}
@ -3024,39 +3056,109 @@ private final class GiftViewSheetContent: CombinedComponent {
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
transition: .immediate
)
let upgradeDescription = upgradeDescription.update(
component: BalancedTextComponent(
text: .plain(NSAttributedString(
string: description,
font: Font.regular(13.0),
textColor: .white,
paragraphAlignment: .center
)),
horizontalAlignment: .center,
maximumNumberOfLines: 5,
lineSpacing: 0.2,
tintColor: vibrantColor
),
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 50.0, height: CGFloat.greatestFiniteMagnitude),
transition: context.transition
)
let spacing: CGFloat = 6.0
let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height
headerComponents.append({
context.add(upgradeTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0)))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
if case let .generic(gift) = component.subject.arguments?.gift, let upgradePreview = state.upgradePreview {
var variant1: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant2: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var variant3: GiftItemComponent.Subject = .starGift(gift: gift, price: "")
var i = 0
for attribute in upgradePreview.attributes {
if case .model = attribute {
switch i {
case 0:
variant1 = .preview(attributes: [attribute], rarity: 0)
case 1:
variant2 = .preview(attributes: [attribute], rarity: 0)
case 2:
variant3 = .preview(attributes: [attribute], rarity: 0)
default:
break
}
i += 1
}
}
//TODO:localize
var buttonColor: UIColor = UIColor.white.withAlphaComponent(0.16)
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
buttonColor = previewPatternColor
}
let upgradeDescription = upgradeDescription.update(
component: PlainButtonComponent(
content: AnyComponent(
ZStack([
AnyComponentWithIdentity(id: "background", component: AnyComponent(
FilledRoundedRectangleComponent(color: buttonColor, cornerRadius: .minEdge, smoothCorners: false)
)),
AnyComponentWithIdentity(id: "label", component: AnyComponent(HStack([
AnyComponentWithIdentity(id: "icon1", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant1,
isPlaceholder: false,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "icon2", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant2,
isPlaceholder: false,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "icon3", component: AnyComponent(
GiftItemComponent(
context: component.context,
theme: theme,
strings: strings,
peer: nil,
subject: variant3,
isPlaceholder: false,
mode: .tableIcon
)
)),
AnyComponentWithIdentity(id: "text", component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: "View all variants", font: Font.semibold(13.0), textColor: .white)))
)),
AnyComponentWithIdentity(id: "arrow", component: AnyComponent(
BundleIconComponent(name: "Item List/InlineTextRightArrow", tintColor: .white)
))
], spacing: 3.0)))
])
),
action: { [weak state] in
state?.openUpgradeVariants()
},
animateScale: false
),
availableSize: CGSize(width: 190.0, height: 24.0),
transition: context.transition
)
let spacing: CGFloat = 6.0
let totalHeight: CGFloat = upgradeTitle.size.height + spacing + upgradeDescription.size.height
context.add(upgradeDescription
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(212.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0)))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
})
headerComponents.append({
context.add(upgradeTitle
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(194.0 - totalHeight / 2.0 + upgradeTitle.size.height / 2.0)))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
context.add(upgradeDescription
.position(CGPoint(x: context.availableSize.width / 2.0, y: floor(198.0 + totalHeight / 2.0 - upgradeDescription.size.height / 2.0)))
.appear(.default(alpha: true))
.disappear(.default(alpha: true))
)
})
}
originY += 24.0
let textColor = theme.actionSheet.primaryTextColor
@ -3078,20 +3180,20 @@ private final class GiftViewSheetContent: CombinedComponent {
))
)
)
items.append(
AnyComponentWithIdentity(
id: "transferable",
component: AnyComponent(InfoParagraphComponent(
title: strings.Gift_Upgrade_Transferable_Title,
titleColor: textColor,
text: transferableText,
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/Collectible/Transferable",
iconColor: linkColor
))
)
)
// items.append(
// AnyComponentWithIdentity(
// id: "transferable",
// component: AnyComponent(InfoParagraphComponent(
// title: strings.Gift_Upgrade_Transferable_Title,
// titleColor: textColor,
// text: transferableText,
// textColor: secondaryTextColor,
// accentColor: linkColor,
// iconName: "Premium/Collectible/Transferable",
// iconColor: linkColor
// ))
// )
// )
items.append(
AnyComponentWithIdentity(
id: "tradable",
@ -3106,6 +3208,21 @@ private final class GiftViewSheetContent: CombinedComponent {
))
)
)
//TODO:localize
items.append(
AnyComponentWithIdentity(
id: "wearable",
component: AnyComponent(InfoParagraphComponent(
title: "Wearable",
titleColor: textColor,
text: "Display gifts on your page and set them as profile covers or statuses.",
textColor: secondaryTextColor,
accentColor: linkColor,
iconName: "Premium/Collectible/Transferable",
iconColor: linkColor
))
)
)
let perksSideInset = sideInset + 16.0
let upgradePerks = upgradePerks.update(
@ -3598,7 +3715,7 @@ private final class GiftViewSheetContent: CombinedComponent {
id: AnyHashable(0),
component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: theme,
strings: strings,
@ -3631,7 +3748,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} else {
ownerComponent = AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: theme,
strings: strings,
@ -3682,7 +3799,7 @@ private final class GiftViewSheetContent: CombinedComponent {
title: strings.Gift_Unique_Telegram,
component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: theme,
strings: strings,
@ -3712,7 +3829,7 @@ private final class GiftViewSheetContent: CombinedComponent {
id: AnyHashable(0),
component: AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: theme,
strings: strings,
@ -3742,7 +3859,7 @@ private final class GiftViewSheetContent: CombinedComponent {
} else {
fromComponent = AnyComponent(Button(
content: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: theme,
strings: strings,
@ -3767,7 +3884,7 @@ private final class GiftViewSheetContent: CombinedComponent {
id: "from_anon",
title: strings.Gift_View_From,
component: AnyComponent(
PeerCellComponent(
PeerTableCellComponent(
context: component.context,
theme: theme,
strings: strings,
@ -4151,7 +4268,7 @@ private final class GiftViewSheetContent: CombinedComponent {
color: theme.list.itemAccentColor
)),
action: { [weak state] in
state?.showAttributeInfo(tag: tag, text: strings.Gift_Unique_AttributeDescription(formatPercentage(percentage)).string)
state?.openUpgradeVariants(attribute: attribute)
}
).tagged(tag))
))
@ -5784,115 +5901,6 @@ func formatPercentage(_ value: Float) -> String {
return String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + "%"
}
final class PeerCellComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let peer: EnginePeer?
init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer?) {
self.context = context
self.theme = theme
self.strings = strings
self.peer = peer
}
static func ==(lhs: PeerCellComponent, rhs: PeerCellComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.peer != rhs.peer {
return false
}
return true
}
final class View: UIView {
private let avatarNode: AvatarNode
private let text = ComponentView<Empty>()
private var component: PeerCellComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 8.0))
super.init(frame: frame)
self.addSubnode(self.avatarNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: PeerCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let avatarSize = CGSize(width: 22.0, height: 22.0)
let spacing: CGFloat = 6.0
var peerName: String
let avatarOverride: AvatarNodeImageOverride?
if let peerValue = component.peer {
peerName = peerValue.compactDisplayTitle
if peerName.count > 40 {
peerName = "\(peerName.prefix(40))"
}
avatarOverride = nil
} else {
peerName = component.strings.Gift_View_HiddenName
avatarOverride = .anonymousSavedMessagesIcon(isColored: true)
}
let avatarNaturalSize = CGSize(width: 40.0, height: 40.0)
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride)
self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize)
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left))
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height)
)
let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height)
let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize)
self.avatarNode.frame = avatarFrame
if let view = self.text.view {
if view.superview == nil {
self.addSubview(view)
}
let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
transition.setFrame(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: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}
final class HeaderContentComponent: Component {
let attributedText: NSAttributedString
@ -6250,6 +6258,10 @@ final class AvatarComponent: Component {
}
func update(component: AvatarComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
if self.component == nil {
self.avatarNode.font = avatarPlaceholderFont(size: 42.0 * floor(availableSize.width / 100.0))
}
self.component = component
self.state = state

View file

@ -0,0 +1,26 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "PeerTableCellComponent",
module_name = "PeerTableCellComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/TelegramCore",
"//submodules/Components/MultilineTextComponent",
"//submodules/AccountContext",
"//submodules/AvatarNode",
],
visibility = [
"//visibility:public",
],
)

View file

@ -0,0 +1,123 @@
import Foundation
import UIKit
import ComponentFlow
import Display
import TelegramCore
import TelegramPresentationData
import MultilineTextComponent
import AvatarNode
import AccountContext
public final class PeerTableCellComponent: Component {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
let peer: EnginePeer?
public init(
context: AccountContext,
theme: PresentationTheme,
strings: PresentationStrings,
peer: EnginePeer?
) {
self.context = context
self.theme = theme
self.strings = strings
self.peer = peer
}
public static func ==(lhs: PeerTableCellComponent, rhs: PeerTableCellComponent) -> Bool {
if lhs.context !== rhs.context {
return false
}
if lhs.theme !== rhs.theme {
return false
}
if lhs.strings !== rhs.strings {
return false
}
if lhs.peer != rhs.peer {
return false
}
return true
}
public final class View: UIView {
private let avatarNode: AvatarNode
private let text = ComponentView<Empty>()
private var component: PeerTableCellComponent?
private weak var state: EmptyComponentState?
override init(frame: CGRect) {
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 12.0))
super.init(frame: frame)
self.addSubnode(self.avatarNode)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: PeerTableCellComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
self.component = component
self.state = state
let avatarSize = CGSize(width: 22.0, height: 22.0)
let spacing: CGFloat = 6.0
var peerName: String
let avatarOverride: AvatarNodeImageOverride?
if let peerValue = component.peer {
peerName = peerValue.compactDisplayTitle
if peerName.count > 40 {
peerName = "\(peerName.prefix(40))"
}
avatarOverride = nil
} else {
peerName = component.strings.Gift_View_HiddenName
avatarOverride = .anonymousSavedMessagesIcon(isColored: true)
}
let avatarNaturalSize = CGSize(width: 40.0, height: 40.0)
self.avatarNode.setPeer(context: component.context, theme: component.theme, peer: component.peer, overrideImage: avatarOverride)
self.avatarNode.bounds = CGRect(origin: .zero, size: avatarNaturalSize)
let textSize = self.text.update(
transition: .immediate,
component: AnyComponent(
MultilineTextComponent(
text: .plain(NSAttributedString(string: peerName, font: Font.regular(15.0), textColor: component.peer != nil ? component.theme.list.itemAccentColor : component.theme.list.itemPrimaryTextColor, paragraphAlignment: .left))
)
),
environment: {},
containerSize: CGSize(width: availableSize.width - avatarSize.width - spacing, height: availableSize.height)
)
let size = CGSize(width: avatarSize.width + textSize.width + spacing, height: textSize.height)
let avatarFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - avatarSize.height) / 2.0)), size: avatarSize)
self.avatarNode.frame = avatarFrame
if let view = self.text.view {
if view.superview == nil {
self.addSubview(view)
}
let textFrame = CGRect(origin: CGPoint(x: avatarSize.width + spacing, y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
transition.setFrame(view: view, frame: textFrame)
}
return size
}
}
public func makeView() -> View {
return View(frame: CGRect())
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
}
}

View file

@ -0,0 +1,23 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "TableComponent",
module_name = "TableComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Display",
"//submodules/Postbox",
"//submodules/ComponentFlow",
"//submodules/TelegramPresentationData",
"//submodules/Components/MultilineTextComponent",
],
visibility = [
"//visibility:public",
],
)

View file

@ -5,9 +5,9 @@ import Display
import TelegramPresentationData
import MultilineTextComponent
final class TableComponent: CombinedComponent {
class Item: Equatable {
enum TitleFont {
public final class TableComponent: CombinedComponent {
public class Item: Equatable {
public enum TitleFont {
case regular
case bold
}
@ -19,7 +19,14 @@ final class TableComponent: CombinedComponent {
public let component: AnyComponent<Empty>
public let insets: UIEdgeInsets?
public init<IdType: Hashable>(id: IdType, title: String?, titleFont: TitleFont = .regular, hasBackground: Bool = false, component: AnyComponent<Empty>, insets: UIEdgeInsets? = nil) {
public init<IdType: Hashable>(
id: IdType,
title: String?,
titleFont: TitleFont = .regular,
hasBackground: Bool = false,
component: AnyComponent<Empty>,
insets: UIEdgeInsets? = nil
) {
self.id = AnyHashable(id)
self.title = title
self.titleFont = titleFont
@ -74,13 +81,13 @@ final class TableComponent: CombinedComponent {
return true
}
final class State: ComponentState {
public final class State: ComponentState {
var cachedLastBackgroundImage: (UIImage, PresentationTheme)?
var cachedLeftColumnImage: (UIImage, PresentationTheme)?
var cachedBorderImage: (UIImage, PresentationTheme)?
}
func makeState() -> State {
public func makeState() -> State {
return State()
}
@ -98,11 +105,15 @@ final class TableComponent: CombinedComponent {
let horizontalPadding: CGFloat = 12.0
let borderWidth: CGFloat = 1.0
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
let borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
var secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor
let borderColor: UIColor
let secondaryBackgroundColor: UIColor
if context.component.semiTransparent {
secondaryBackgroundColor = borderColor.withMultipliedAlpha(0.5)
borderColor = context.component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.1)
secondaryBackgroundColor = context.component.theme.actionSheet.primaryTextColor.withMultipliedAlpha(0.05)
} else {
let backgroundColor = context.component.theme.actionSheet.opaqueItemBackgroundColor
borderColor = backgroundColor.mixedWith(context.component.theme.list.itemBlocksSeparatorColor, alpha: 0.6)
secondaryBackgroundColor = context.component.theme.overallDarkAppearance ? context.component.theme.list.itemModalBlocksBackgroundColor : context.component.theme.list.itemInputField.backgroundColor
}
var leftColumnWidth: CGFloat = 0.0

View file

@ -66,17 +66,17 @@ public final class LiquidLensView: UIView {
private struct Params: Equatable {
var size: CGSize
var selectionX: CGFloat
var selectionWidth: CGFloat
var selectionOrigin: CGPoint
var selectionSize: CGSize
var inset: CGFloat
var isDark: Bool
var isLifted: Bool
var isCollapsed: Bool
init(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, inset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) {
init(size: CGSize, selectionOrigin: CGPoint, selectionSize: CGSize, inset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool) {
self.size = size
self.selectionX = selectionX
self.selectionWidth = selectionWidth
self.selectionOrigin = selectionOrigin
self.selectionSize = selectionSize
self.inset = inset
self.isLifted = isLifted
self.isDark = isDark
@ -121,12 +121,12 @@ public final class LiquidLensView: UIView {
private var liftedDisplayLink: SharedDisplayLinkDriver.Link?
public var selectionX: CGFloat? {
return self.params?.selectionX
public var selectionOrigin: CGPoint? {
return self.params?.selectionOrigin
}
public var selectionWidth: CGFloat? {
return self.params?.selectionWidth
public var selectionSize: CGSize? {
return self.params?.selectionSize
}
public private(set) var isAnimating: Bool = false {
@ -286,8 +286,8 @@ public final class LiquidLensView: UIView {
lensView.perform(NSSelectorFromString("setLiftedContainerView:"), with: view)
}
public func update(size: CGSize, selectionX: CGFloat, selectionWidth: CGFloat, inset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) {
let params = Params(size: size, selectionX: selectionX, selectionWidth: selectionWidth, inset: inset, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed)
public func update(size: CGSize, selectionOrigin: CGPoint, selectionSize: CGSize, inset: CGFloat, isDark: Bool, isLifted: Bool, isCollapsed: Bool = false, transition: ComponentTransition) {
let params = Params(size: size, selectionOrigin: selectionOrigin, selectionSize: selectionSize, inset: inset, isDark: isDark, isLifted: isLifted, isCollapsed: isCollapsed)
if self.params == params {
return
}
@ -444,7 +444,7 @@ public final class LiquidLensView: UIView {
transition.setCornerRadius(layer: self.liftedContainerView.layer, cornerRadius: params.size.height * 0.5)
}
let baseLensFrame = CGRect(origin: CGPoint(x: params.selectionX, y: 0.0), size: CGSize(width: params.selectionWidth, height: params.size.height))
let baseLensFrame = CGRect(origin: CGPoint(x: params.selectionOrigin.x, y: 0.0), size: CGSize(width: params.selectionSize.width, height: params.size.height))
self.updateLens(params: LensParams(baseFrame: baseLensFrame, inset: params.inset, isLifted: params.isLifted), transition: transition)
if let legacyContentMaskView = self.legacyContentMaskView {

View file

@ -1170,6 +1170,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
transition: transition,
component: AnyComponent(ButtonComponent(
background: ButtonComponent.Background(
style: .glass,
color: environment.theme.list.itemCheckColors.fillColor,
foreground: environment.theme.list.itemCheckColors.foregroundColor,
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8)
@ -1186,7 +1187,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - bottomPanelButtonInsets.left - bottomPanelButtonInsets.right, height: 50.0)
containerSize: CGSize(width: availableSize.width - bottomPanelButtonInsets.left - bottomPanelButtonInsets.right, height: 52.0)
)
let bottomPanelHeight: CGFloat = bottomPanelButtonInsets.top + bottomPanelButtonSize.height + bottomPanelButtonInsets.bottom + bottomPanelTextSize.height + 8.0 + environment.safeInsets.bottom

View file

@ -1130,11 +1130,11 @@ private final class JoinAffiliateProgramScreenComponent: Component {
)),
background: AnyComponent(FilledRoundedRectangleComponent(
color: environment.theme.list.itemInputField.backgroundColor,
cornerRadius: .value(8.0),
cornerRadius: .minEdge,
smoothCorners: true
)),
effectAlignment: .center,
minSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0),
minSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0),
contentInsets: UIEdgeInsets(top: 0.0, left: 10.0, bottom: 0.0, right: 10.0),
action: { [weak self] in
guard let self, case let .active(active) = self.currentMode else {
@ -1148,7 +1148,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
animateContents: false
)),
environment: {},
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 52.0)
)
let linkTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - linkTextSize.width) * 0.5), y: contentHeight), size: linkTextSize)
if let linkTextView = self.linkText.view {
@ -1170,6 +1170,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
actionButtonTitle = environment.strings.AffiliateProgram_ActionCopyLink
}
let buttonSideInset: CGFloat = 30.0
let actionButtonSize = self.actionButton.update(
transition: transition,
component: AnyComponent(ButtonComponent(
@ -1208,7 +1209,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
}
)),
environment: {},
containerSize: CGSize(width: availableSize.width - 30.0 * 2.0, height: 52.0)
containerSize: CGSize(width: availableSize.width - buttonSideInset * 2.0, height: 52.0)
)
let bottomTextSize = self.bottomText.update(
@ -1238,7 +1239,7 @@ private final class JoinAffiliateProgramScreenComponent: Component {
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight), size: CGSize(width: availableSize.width, height: bottomPanelHeight))
transition.setFrame(view: self.bottomPanelContainer, frame: bottomPanelFrame)
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: actionButtonSize)
let actionButtonFrame = CGRect(origin: CGPoint(x: buttonSideInset, y: 0.0), size: actionButtonSize)
if let actionButtonView = self.actionButton.view {
if actionButtonView.superview == nil {
self.bottomPanelContainer.addSubview(actionButtonView)

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