mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
5389f949d9
98 changed files with 3655 additions and 2629 deletions
Binary file not shown.
Binary file not shown.
|
|
@ -15669,3 +15669,50 @@ Error: %8$@";
|
|||
|
||||
"Conversation.Summary.Limit.Title" = "AI Summary";
|
||||
"Conversation.Summary.Limit.Text" = "Summarize large messages with AI – unlimited with Telegram Premium.";
|
||||
|
||||
"Appearance.SendWithCmdEnter" = "Send Messages with ⌘+Enter";
|
||||
|
||||
"Notification.StarsGift.Crafted" = "You crafted a new unique collectible";
|
||||
|
||||
"Gift.Craft.Title" = "Craft Gift";
|
||||
"Gift.Craft.Description" = "Add up to **4 gifts** to craft new\n**$ %@**.\n\nIf crafting fails, all used gifts\nwill be lost.";
|
||||
"Gift.Craft.ViewVariants" = "View all new variants";
|
||||
"Gift.Craft.BackdropTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** backdrop.";
|
||||
"Gift.Craft.SymbolTooltip" = "There's **%1$@%** chance the crafted gift will have **%2$@** symbol.";
|
||||
"Gift.Craft.SuccessChanceSuffix" = "% Success Chance";
|
||||
"Gift.Craft.Craft" = "Craft %@";
|
||||
"Gift.Craft.Crafting.Title" = "Crafting";
|
||||
"Gift.Craft.Crafting.Description" = "If crafting fails, all used gifts\nwill be lost.";
|
||||
"Gift.Craft.Crafting.SuccessChance" = "%@% Success Chance";
|
||||
"Gift.Craft.CraftingFailed.Title" = "Crafting Failed";
|
||||
"Gift.Craft.CraftingFailed.Text_1" = "This crafting attempt was unsuccessful.\n**%@ gift** was lost.";
|
||||
"Gift.Craft.CraftingFailed.Text_any" = "This crafting attempt was unsuccessful.\n**%@ gifts** were lost.";
|
||||
"Gift.Craft.CraftingFailed.CraftAnotherGift" = "Craft Another Gift";
|
||||
"Gift.Craft.Info.Title" = "Gift Crafting";
|
||||
"Gift.Craft.Info.Description" = "Turn your gifts intro rare, epic,\nuncommon and legendary versions.";
|
||||
"Gift.Craft.Info.Paragraph1.Title" = "Get Rare Models";
|
||||
"Gift.Craft.Info.Paragraph1.Text" = "Select up to 4 gifts to craft a new exclusive model.";
|
||||
"Gift.Craft.Info.Paragraph2.Title" = "Maximize Chances";
|
||||
"Gift.Craft.Info.Paragraph2.Text" = "Combine more gifts to increase your odds of success.";
|
||||
"Gift.Craft.Info.Paragraph3.Title" = "Affect the Result";
|
||||
"Gift.Craft.Info.Paragraph3.Text" = "Use gifts with the same attribute to boost it's chance.";
|
||||
"Gift.Craft.Info.Understood" = "Understood";
|
||||
"Gift.Craft.Unavailable.Title" = "Try Later";
|
||||
"Gift.Craft.Unavailable.Text" = "You will be able to craft this gift on %@.";
|
||||
|
||||
"Gift.Attribute.Rare" = "rare";
|
||||
"Gift.Attribute.Legendary" = "legendary";
|
||||
"Gift.Attribute.Epic" = "epic";
|
||||
"Gift.Attribute.Uncommon" = "uncommon";
|
||||
|
||||
"SocksProxySetup.CheckStatus" = "Check Status";
|
||||
"SocksProxySetup.Warning.Title" = "Warning";
|
||||
"SocksProxySetup.Warning.Text" = "This action will expose your IP address to the admin of the proxy server.";
|
||||
"SocksProxySetup.Warning.Proceed" = "Proceed";
|
||||
|
||||
"Gift.Variants.CollectionInfo.CraftableModel_1" = "**%@** craftable model";
|
||||
"Gift.Variants.CollectionInfo.CraftableModel_any" = "**%@** craftable models";
|
||||
"Gift.Variants.ViewCraftableModels" = "View Craftable Models";
|
||||
"Gift.Variants.ViewPrimaryModels" = "View Primary Models";
|
||||
|
||||
"Gift.View.OnSale" = "On sale for %@";
|
||||
|
|
|
|||
|
|
@ -1294,6 +1294,7 @@ public protocol SharedAccountContext: AnyObject {
|
|||
var currentMediaInputSettings: Atomic<MediaInputSettings> { get }
|
||||
var currentStickerSettings: Atomic<StickerSettings> { get }
|
||||
var currentMediaDisplaySettings: Atomic<MediaDisplaySettings> { get }
|
||||
var currentChatSettings: Atomic<ChatSettings> { get }
|
||||
|
||||
var energyUsageSettings: EnergyUsageSettings { get }
|
||||
|
||||
|
|
@ -1453,13 +1454,13 @@ public protocol SharedAccountContext: AnyObject {
|
|||
func makeGiftViewScreen(context: AccountContext, message: EngineMessage, shareStory: ((StarGift.UniqueGift) -> Void)?) -> ViewController
|
||||
func makeGiftViewScreen(context: AccountContext, gift: StarGift.UniqueGift, shareStory: ((StarGift.UniqueGift) -> Void)?, openChatTheme: (() -> Void)?, dismissed: (() -> Void)?) -> ViewController
|
||||
func makeGiftWearPreviewScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute]?) -> ViewController
|
||||
func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController
|
||||
func makeGiftUpgradePreviewScreen(context: AccountContext, gift: StarGift.Gift, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController
|
||||
func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController
|
||||
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, peerId: EnginePeer.Id?, completion: @escaping (Signal<[GiftAuctionAcquiredGift], NoError>, [StarGift.UniqueGift.Attribute]?) -> Void) -> ViewController
|
||||
func makeGiftAuctionActiveBidsScreen(context: AccountContext) -> 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, onlyCrafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController
|
||||
func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, crafted: Bool, 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 makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift) -> ViewController
|
||||
func makeGiftDemoScreen(context: AccountContext) -> ViewController
|
||||
|
|
|
|||
|
|
@ -1489,7 +1489,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
}
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
if self.actionButtons.sendButton.supernode != nil && !self.actionButtons.sendButton.isHidden && !self.actionButtons.sendButton.alpha.isZero {
|
||||
self.sendButtonPressed()
|
||||
}
|
||||
|
|
@ -1497,7 +1497,7 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
|
|||
}
|
||||
|
||||
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
return self.chatInputTextNodeShouldReturn()
|
||||
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
|
||||
}
|
||||
|
||||
private func applyUpdateSendButtonIcon() {
|
||||
|
|
|
|||
|
|
@ -530,6 +530,11 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
if let view = leftItemView.view {
|
||||
if view.superview == nil {
|
||||
self.navigationBarContainer.addSubview(view)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
leftItemTransition.setFrame(view: view, frame: leftItemFrame)
|
||||
}
|
||||
|
|
@ -587,17 +592,19 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
}
|
||||
|
||||
if let bottomItem = component.bottomItem {
|
||||
var bottomItemTransition = transition
|
||||
let bottomItemView: ComponentView<Empty>
|
||||
if let current = self.bottomItemView {
|
||||
bottomItemView = current
|
||||
} else {
|
||||
bottomItemTransition = .immediate
|
||||
bottomItemView = ComponentView<Empty>()
|
||||
self.bottomItemView = bottomItemView
|
||||
}
|
||||
|
||||
let bottomInsets = ContainerViewLayout.concentricInsets(bottomInset: sheetEnvironment.safeInsets.bottom, innerDiameter: 52.0, sideInset: 30.0)
|
||||
let bottomItemSize = bottomItemView.update(
|
||||
transition: transition,
|
||||
transition: bottomItemTransition,
|
||||
component: bottomItem,
|
||||
environment: {},
|
||||
containerSize: CGSize(width: containerSize.width - bottomInsets.left - bottomInsets.right, height: 52.0)
|
||||
|
|
@ -606,12 +613,24 @@ public final class ResizableSheetComponent<ChildEnvironmentType: Sendable & Equa
|
|||
if let view = bottomItemView.view {
|
||||
if view.superview == nil {
|
||||
self.bottomContainer.addSubview(view)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
view.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25)
|
||||
view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: view, frame: bottomItemFrame)
|
||||
bottomItemTransition.setFrame(view: view, frame: bottomItemFrame)
|
||||
}
|
||||
} else if let bottomItemView = self.bottomItemView {
|
||||
self.bottomItemView = nil
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
if !transition.animation.isImmediate {
|
||||
bottomItemView.view?.layer.animateScale(from: 1.0, to: 0.01, duration: 0.25, removeOnCompletion: false)
|
||||
bottomItemView.view?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { _ in
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
})
|
||||
} else {
|
||||
bottomItemView.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: rawSideInset, y: availableSize.height - edgeEffectHeight), size: CGSize(width: fillingSize, height: edgeEffectHeight))
|
||||
|
|
|
|||
|
|
@ -191,9 +191,7 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
|
||||
self.scrollView = ScrollView()
|
||||
self.scrollView.delaysContentTouches = false
|
||||
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
}
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
|
|
@ -351,6 +349,9 @@ public final class SheetComponent<ChildEnvironmentType: Sendable & Equatable>: C
|
|||
})
|
||||
} else {
|
||||
var targetOffset: CGFloat = self.scrollView.contentSize.height + abs(contentView.frame.minY) + 6.0
|
||||
if self.currentHasInputHeight {
|
||||
targetOffset += 330.0
|
||||
}
|
||||
if self.isCentered {
|
||||
targetOffset = self.frame.height + self.scrollView.frame.height * 0.5
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ import GlassControls
|
|||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import EdgeEffect
|
||||
import RasterizedCompositionComponent
|
||||
import BadgeComponent
|
||||
|
||||
private let deleteImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: .white)
|
||||
private let actionImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionForward"), color: .white)
|
||||
|
|
@ -45,25 +47,6 @@ private let forwardImage = generateTintedImage(image: UIImage(bundleImageName: "
|
|||
|
||||
private let cloudFetchIcon = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/FileCloudFetch"), color: UIColor.white)
|
||||
|
||||
private let fullscreenOnImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Expand"), color: .white)
|
||||
private let fullscreenOffImage = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Collapse"), color: .white)
|
||||
|
||||
private let captionMaskImage = generateImage(CGSize(width: 1.0, height: 17.0), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let gradientColors = [UIColor.white.withAlphaComponent(1.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor] as CFArray
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: 17.0), options: CGGradientDrawingOptions())
|
||||
})
|
||||
|
||||
private let titleFont = Font.medium(15.0)
|
||||
private let dateFont = Font.regular(14.0)
|
||||
|
||||
enum ChatItemGalleryFooterContent: Equatable {
|
||||
case info
|
||||
case fetch(status: MediaResourceStatus, seekable: Bool)
|
||||
|
|
@ -128,6 +111,26 @@ class CaptionScrollWrapperNode: ASDisplayNode {
|
|||
}
|
||||
|
||||
final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScrollViewDelegate {
|
||||
public final class SettingsButtonState: Equatable {
|
||||
public let speed: String?
|
||||
public let quality: String?
|
||||
|
||||
public init(speed: String?, quality: String?) {
|
||||
self.speed = speed
|
||||
self.quality = quality
|
||||
}
|
||||
|
||||
public static func ==(lhs: SettingsButtonState, rhs: SettingsButtonState) -> Bool {
|
||||
if lhs.speed != rhs.speed {
|
||||
return false
|
||||
}
|
||||
if lhs.quality != rhs.quality {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private var presentationData: PresentationData
|
||||
private var theme: PresentationTheme
|
||||
|
|
@ -140,6 +143,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
private let textSelectionKnobContainer: UIView
|
||||
private let textSelectionKnobSurface: UIView
|
||||
private let scrollWrapperNode: CaptionScrollWrapperNode
|
||||
private let scrollWrapperMask: EdgeMaskView
|
||||
private let scrollWrapperEffect: VariableBlurEffect
|
||||
private let scrollNode: ASScrollNode
|
||||
|
||||
|
|
@ -178,7 +182,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayActionButton: Bool,
|
||||
displayEditButton: Bool,
|
||||
displayPictureInPictureButton: Bool,
|
||||
displaySettingsButton: Bool
|
||||
settingsButtonState: SettingsButtonState?,
|
||||
displayTextRecognitionButton: Bool,
|
||||
displayStickersButton: Bool
|
||||
)?
|
||||
|
||||
private var codeHighlightState: (id: EngineMessage.Id, specs: [CachedMessageSyntaxHighlight.Spec], disposable: Disposable)?
|
||||
|
|
@ -304,7 +310,6 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
didSet {
|
||||
if let scrubberView = self.scrubberView {
|
||||
scrubberView.setCollapsed(self.visibilityAlpha < 1.0, animated: false)
|
||||
self.view.addSubview(scrubberView)
|
||||
scrubberView.updateScrubbingVisual = { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
|
|
@ -338,8 +343,16 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
override func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.contentNode.alpha = alpha
|
||||
self.scrubberView?.setCollapsed(alpha < 1.0, animated: animated)
|
||||
|
||||
let transition: ComponentTransition = animated ? .easeInOut(duration: 0.2) : .immediate
|
||||
transition.animateView {
|
||||
self.contentNode.alpha = alpha
|
||||
}
|
||||
//transition.setAlpha(view: self.contentNode.view, alpha: alpha)
|
||||
|
||||
if let validLayout = self.validLayout {
|
||||
let _ = self.updateLayout(size: validLayout.0, metrics: validLayout.1, leftInset: validLayout.2, rightInset: validLayout.3, bottomInset: validLayout.4, contentInset: validLayout.5, transition: animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
private var hasExpandedCaptionPromise = ValuePromise<Bool>(false)
|
||||
|
|
@ -368,6 +381,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.scrollWrapperNode.backgroundColor = .clear
|
||||
self.scrollWrapperNode.isOpaque = false
|
||||
|
||||
self.scrollWrapperMask = EdgeMaskView()
|
||||
//self.scrollWrapperNode.view.addSubview(self.scrollWrapperMask)
|
||||
|
||||
self.scrollNode = ASScrollNode()
|
||||
self.scrollNode.clipsToBounds = false
|
||||
|
||||
|
|
@ -795,7 +811,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayActionButton: false,
|
||||
displayEditButton: false,
|
||||
displayPictureInPictureButton: false,
|
||||
displaySettingsButton: false
|
||||
settingsButtonState: nil,
|
||||
displayTextRecognitionButton: false,
|
||||
displayStickersButton: false
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -815,7 +833,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
}
|
||||
|
||||
func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayPictureInPictureButton: Bool = false, displaySettingsButton: Bool = false) {
|
||||
func setMessage(_ message: Message, displayInfo: Bool = true, translateToLanguage: String? = nil, peerIsCopyProtected: Bool = false, displayPictureInPictureButton: Bool = false, settingsButtonState: SettingsButtonState? = nil, displayTextRecognitionButton: Bool = false, displayStickersButton: Bool = false, animated: Bool = false) {
|
||||
self.currentMessage = message
|
||||
|
||||
var displayInfo = displayInfo
|
||||
|
|
@ -1061,10 +1079,12 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
displayActionButton: displayActionButton,
|
||||
displayEditButton: displayEditButton,
|
||||
displayPictureInPictureButton: displayPictureInPictureButton,
|
||||
displaySettingsButton: displaySettingsButton
|
||||
settingsButtonState: settingsButtonState,
|
||||
displayTextRecognitionButton: displayTextRecognitionButton,
|
||||
displayStickersButton: displayStickersButton
|
||||
)
|
||||
|
||||
self.requestLayout?(.immediate)
|
||||
self.requestLayout?(animated ? .animated(duration: 0.4, curve: .spring) : .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1169,6 +1189,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
let textSize = self.textNode.updateLayout(constrainSize)
|
||||
|
||||
var textOffset: CGFloat = 0.0
|
||||
var additionalTextHeight: CGFloat = 0.0
|
||||
if displayCaption {
|
||||
visibleTextHeight = textSize.height
|
||||
if visibleTextHeight > 100.0 {
|
||||
|
|
@ -1190,7 +1211,9 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
var maxTextOffset: CGFloat = size.height - bottomInset - 238.0 - UIScreenPixel
|
||||
if let _ = self.scrubberView {
|
||||
maxTextOffset -= 44.0
|
||||
//maxTextOffset -= 100.0
|
||||
maxTextOffset += 0.0
|
||||
additionalTextHeight += 44.0
|
||||
}
|
||||
textOffset = min(maxTextOffset, self.scrollNode.view.contentOffset.y)
|
||||
let originalPanelHeight = panelHeight
|
||||
|
|
@ -1198,47 +1221,22 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
|
||||
if self.scrollNode.view.isScrollEnabled {
|
||||
if self.scrollWrapperNode.view.mask == nil {
|
||||
self.scrollWrapperNode.layer.rasterizationScale = UIScreenScale
|
||||
let maskView = UIImageView()
|
||||
|
||||
let height: CGFloat = 70.0
|
||||
let baseGradientAlpha: CGFloat = 1.0
|
||||
let numSteps = 8
|
||||
let firstStep = 0
|
||||
let firstLocation = 0.0
|
||||
let colors = (0 ..< numSteps).map { i -> UIColor in
|
||||
if i < firstStep {
|
||||
return UIColor(white: 1.0, alpha: 1.0)
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
let value: CGFloat = 1.0 - bezierPoint(0.42, 0.0, 0.58, 1.0, step)
|
||||
return UIColor(white: 0.0, alpha: baseGradientAlpha * value)
|
||||
}
|
||||
}
|
||||
let locations = (0 ..< numSteps).map { i -> CGFloat in
|
||||
if i < firstStep {
|
||||
return 0.0
|
||||
} else {
|
||||
let step: CGFloat = CGFloat(i - firstStep) / CGFloat(numSteps - firstStep - 1)
|
||||
return (firstLocation + (1.0 - firstLocation) * step)
|
||||
}
|
||||
}
|
||||
|
||||
maskView.image = generateGradientImage(size: CGSize(width: 8.0, height: height), colors: colors, locations: locations)!.stretchableImage(withLeftCapWidth: 0, topCapHeight: 1)
|
||||
|
||||
//maskView.image = EdgeEffectView.generateEdgeGradient(baseHeight: originalPanelHeight + 20.0, isInverted: false)
|
||||
self.scrollWrapperNode.view.mask = maskView
|
||||
self.scrollWrapperNode.view.mask = self.scrollWrapperMask
|
||||
}
|
||||
} else {
|
||||
self.scrollWrapperNode.view.mask = nil
|
||||
}
|
||||
|
||||
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset + originalPanelHeight - 14.0))
|
||||
let scrollWrapperNodeFrame = CGRect(x: 0.0, y: 0.0, width: width, height: max(0.0, visibleTextPanelHeight + textOffset + originalPanelHeight - 14.0 + additionalTextHeight))
|
||||
if self.scrollWrapperNode.frame != scrollWrapperNodeFrame {
|
||||
self.scrollWrapperNode.frame = scrollWrapperNodeFrame
|
||||
self.scrollWrapperNode.view.mask?.frame = self.scrollWrapperNode.bounds
|
||||
self.scrollWrapperNode.view.mask?.layer.removeAllAnimations()
|
||||
self.scrollWrapperEffect.update(size: scrollWrapperNodeFrame.size, constantHeight: 80.0, placement: VariableBlurEffect.Placement(position: .bottom, extendsInwards: true), gradient: EdgeEffectView.generateEdgeGradientData(baseHeight: 80.0), transition: transition)
|
||||
do {
|
||||
let mask = self.scrollWrapperMask
|
||||
mask.update(size: self.scrollWrapperNode.bounds.size, color: UIColor.blue, gradientHeight: 90.0 + additionalTextHeight + contentInset, extensionHeight: 0.0, transition: ComponentTransition(transition))
|
||||
mask.frame = self.scrollWrapperNode.bounds
|
||||
mask.layer.removeAllAnimations()
|
||||
}
|
||||
self.scrollWrapperEffect.update(size: CGSize(width: scrollWrapperNodeFrame.width, height: scrollWrapperNodeFrame.height), constantHeight: 90.0, placement: VariableBlurEffect.Placement(position: .bottom, inwardsExtension: additionalTextHeight + contentInset), gradient: EdgeEffectView.generateEdgeGradientData(baseHeight: 90.0), transition: transition)
|
||||
}
|
||||
|
||||
if let buttonNode = self.buttonNode {
|
||||
|
|
@ -1295,7 +1293,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: buttonPanelInsets.left, y: scrubberY), size: CGSize(width: width - buttonPanelInsets.left - buttonPanelInsets.right, height: 44.0))
|
||||
scrubberView.updateLayout(size: scrubberFrame.size, leftInset: 0.0, rightInset: 0.0, transition: .immediate)
|
||||
scrubberView.updateLayout(size: scrubberFrame.size, leftInset: 0.0, rightInset: 0.0, isCollapsed: self.visibilityAlpha < 1.0, transition: transition)
|
||||
transition.updateBounds(layer: scrubberView.layer, bounds: CGRect(origin: CGPoint(), size: scrubberFrame.size))
|
||||
transition.updatePosition(layer: scrubberView.layer, position: CGPoint(x: scrubberFrame.midX, y: scrubberFrame.midY))
|
||||
}
|
||||
|
|
@ -1330,10 +1328,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displaySettingsButton {
|
||||
if let settingsButtonState = buttonsState.settingsButtonState {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("settings"),
|
||||
content: .icon("Chat/Context Menu/Settings"),
|
||||
content: .customIcon(id: AnyHashable("settings"), component: AnyComponent(SettingsNavigationIconComponent(
|
||||
speed: settingsButtonState.speed,
|
||||
quality: settingsButtonState.quality,
|
||||
isOpen: false
|
||||
))),
|
||||
action: { [weak self] in
|
||||
guard let self, let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View, let centerItemView = buttonPanelView.centerItemView else {
|
||||
return
|
||||
|
|
@ -1357,6 +1359,30 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayTextRecognitionButton {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("textRecognition"),
|
||||
content: .icon("Media Gallery/LiveTextIcon"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.textRecognitionButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayStickersButton {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("stickers"),
|
||||
content: .icon("Media Gallery/Stickers"),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.stickersButtonPressed()
|
||||
}
|
||||
))
|
||||
}
|
||||
if buttonsState.displayFullscreenButton && !metrics.isTablet {
|
||||
centerControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("fullscreen"),
|
||||
|
|
@ -2126,6 +2152,21 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
|
|||
self.controllerInteraction?.editMedia(message.id)
|
||||
}
|
||||
|
||||
private func textRecognitionButtonPressed() {
|
||||
guard let currentItemNode = self.controllerInteraction?.currentItemNode() as? ChatImageGalleryItemNode else {
|
||||
return
|
||||
}
|
||||
currentItemNode.textRecognitionButtonPressed()
|
||||
}
|
||||
|
||||
private func stickersButtonPressed() {
|
||||
if let currentItemNode = self.controllerInteraction?.currentItemNode() as? ChatImageGalleryItemNode {
|
||||
currentItemNode.openStickersButtonPressed()
|
||||
} else if let currentItemNode = self.controllerInteraction?.currentItemNode() as? UniversalVideoGalleryItemNode {
|
||||
currentItemNode.openStickersButtonPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private func pipButtonPressed() {
|
||||
guard let currentItemNode = self.controllerInteraction?.currentItemNode() as? UniversalVideoGalleryItemNode else {
|
||||
return
|
||||
|
|
@ -2333,3 +2374,225 @@ private final class PlaybackButtonNode: HighlightTrackingButtonNode {
|
|||
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0) + UIScreenPixel), size: textSize)
|
||||
}
|
||||
}
|
||||
|
||||
final class SettingsNavigationIconComponent: Component {
|
||||
let speed: String?
|
||||
let quality: String?
|
||||
let isOpen: Bool
|
||||
|
||||
init(speed: String?, quality: String?, isOpen: Bool) {
|
||||
self.speed = speed
|
||||
self.quality = quality
|
||||
self.isOpen = isOpen
|
||||
}
|
||||
|
||||
static func ==(lhs: SettingsNavigationIconComponent, rhs: SettingsNavigationIconComponent) -> Bool {
|
||||
if lhs.speed != rhs.speed {
|
||||
return false
|
||||
}
|
||||
if lhs.quality != rhs.quality {
|
||||
return false
|
||||
}
|
||||
if lhs.isOpen != rhs.isOpen {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let iconLayer: RasterizedCompositionMonochromeLayer
|
||||
|
||||
private let gearsLayer: RasterizedCompositionImageLayer
|
||||
private let dotLayer: RasterizedCompositionImageLayer
|
||||
|
||||
private var speedBadge: ComponentView<Empty>?
|
||||
private var qualityBadge: ComponentView<Empty>?
|
||||
|
||||
private var speedBadgeText: String?
|
||||
private var qualityBadgeText: String?
|
||||
|
||||
private let badgeFont: UIFont
|
||||
|
||||
private var isMenuOpen: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.iconLayer = RasterizedCompositionMonochromeLayer()
|
||||
|
||||
self.gearsLayer = RasterizedCompositionImageLayer()
|
||||
self.gearsLayer.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsNoDot"), color: .white)
|
||||
|
||||
self.dotLayer = RasterizedCompositionImageLayer()
|
||||
self.dotLayer.image = generateFilledCircleImage(diameter: 4.0, color: .white)
|
||||
|
||||
self.iconLayer.contentsLayer.addSublayer(self.gearsLayer)
|
||||
self.iconLayer.contentsLayer.addSublayer(self.dotLayer)
|
||||
|
||||
self.badgeFont = Font.with(size: 8.0, design: .round, weight: .bold)
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.layer.addSublayer(self.iconLayer)
|
||||
|
||||
let size = CGSize(width: 44.0, height: 44.0)
|
||||
|
||||
if let image = self.gearsLayer.image {
|
||||
let iconInnerInsets = UIEdgeInsets(top: 4.0, left: 8.0, bottom: 4.0, right: 6.0)
|
||||
let iconSize = CGSize(width: image.size.width + iconInnerInsets.left + iconInnerInsets.right, height: image.size.height + iconInnerInsets.top + iconInnerInsets.bottom)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
self.iconLayer.position = iconFrame.center
|
||||
self.iconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.contentsLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.contentsLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.maskedLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.maskedLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
self.iconLayer.maskedLayer.backgroundColor = UIColor.white.cgColor
|
||||
|
||||
let gearsFrame = CGRect(origin: CGPoint(x: floor((iconSize.width - image.size.width) * 0.5), y: floor((iconSize.height - image.size.height) * 0.5)), size: image.size)
|
||||
self.gearsLayer.position = gearsFrame.center
|
||||
self.gearsLayer.bounds = CGRect(origin: CGPoint(), size: gearsFrame.size)
|
||||
|
||||
if let dotImage = self.dotLayer.image {
|
||||
let dotFrame = CGRect(origin: CGPoint(x: gearsFrame.minX + floorToScreenPixels((gearsFrame.width - dotImage.size.width) * 0.5), y: gearsFrame.minY + floorToScreenPixels((gearsFrame.height - dotImage.size.height) * 0.5)), size: dotImage.size)
|
||||
self.dotLayer.position = dotFrame.center
|
||||
self.dotLayer.bounds = CGRect(origin: CGPoint(), size: dotFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setIsMenuOpen(isMenuOpen: Bool) {
|
||||
if self.isMenuOpen == isMenuOpen {
|
||||
return
|
||||
}
|
||||
self.isMenuOpen = isMenuOpen
|
||||
|
||||
let rotationTransition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
rotationTransition.updateTransform(layer: self.gearsLayer, transform: CGAffineTransformMakeRotation(isMenuOpen ? (CGFloat.pi * 2.0 / 6.0) : 0.0))
|
||||
self.gearsLayer.animateScale(from: 1.0, to: 1.07, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.gearsLayer.animateScale(from: 1.07, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
|
||||
self.dotLayer.animateScale(from: 1.0, to: 0.8, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.dotLayer.animateScale(from: 0.8, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
}
|
||||
|
||||
func setBadges(speed: String?, quality: String?, transition: ComponentTransition) {
|
||||
if self.speedBadgeText == speed && self.qualityBadgeText == quality {
|
||||
return
|
||||
}
|
||||
self.speedBadgeText = speed
|
||||
self.qualityBadgeText = quality
|
||||
|
||||
if let badgeText = speed {
|
||||
var badgeTransition = transition
|
||||
let speedBadge: ComponentView<Empty>
|
||||
if let current = self.speedBadge {
|
||||
speedBadge = current
|
||||
} else {
|
||||
speedBadge = ComponentView()
|
||||
self.speedBadge = speedBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = speedBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
if speedBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(speedBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: speedBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: speedBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: speedBadgeView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: badgeSize))
|
||||
}
|
||||
} else if let speedBadge = self.speedBadge {
|
||||
self.speedBadge = nil
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
let speedBadgeLayer = speedBadgeView.layer
|
||||
transition.setAlpha(layer: speedBadgeLayer, alpha: 0.0, completion: { [weak speedBadgeLayer] _ in
|
||||
speedBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: speedBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
if let badgeText = quality {
|
||||
var badgeTransition = transition
|
||||
let qualityBadge: ComponentView<Empty>
|
||||
if let current = self.qualityBadge {
|
||||
qualityBadge = current
|
||||
} else {
|
||||
qualityBadge = ComponentView()
|
||||
self.qualityBadge = qualityBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = qualityBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
if qualityBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(qualityBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: qualityBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: qualityBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: qualityBadgeView.layer, frame: CGRect(origin: CGPoint(x: self.iconLayer.bounds.width - badgeSize.width, y: self.iconLayer.bounds.height - badgeSize.height), size: badgeSize))
|
||||
}
|
||||
} else if let qualityBadge = self.qualityBadge {
|
||||
self.qualityBadge = nil
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
let qualityBadgeLayer = qualityBadgeView.layer
|
||||
transition.setAlpha(layer: qualityBadgeLayer, alpha: 0.0, completion: { [weak qualityBadgeLayer] _ in
|
||||
qualityBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: qualityBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: SettingsNavigationIconComponent, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.setBadges(speed: component.speed, quality: component.quality, transition: transition)
|
||||
self.setIsMenuOpen(isMenuOpen: component.isOpen)
|
||||
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ private let scrubberForegroundColor = UIColor.white
|
|||
private let scrubberBufferingColor = UIColor(rgb: 0xffffff, alpha: 0.5)
|
||||
|
||||
final class ChatVideoGalleryItemScrubberView: UIView {
|
||||
private var containerLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)?
|
||||
private var containerLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat, isCollapsed: Bool)?
|
||||
|
||||
private let backgroundContainer: GlassBackgroundContainerView
|
||||
private let backgroundView: GlassBackgroundView
|
||||
|
|
@ -80,6 +80,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
self.chapters = chapters
|
||||
self.scrubberNode = MediaPlayerScrubbingNode(content: .standard(lineHeight: 8.0, lineCap: .round, scrubberHandle: .none, backgroundColor: scrubberBackgroundColor, foregroundColor: scrubberForegroundColor, bufferingColor: scrubberBufferingColor, chapters: chapters))
|
||||
self.scrubberNode.layer.allowsGroupOpacity = true
|
||||
self.shimmerEffectNode = ShimmerEffectForegroundNode()
|
||||
|
||||
self.leftTimestampNode = MediaPlayerTimeTextNode(textColor: .white)
|
||||
|
|
@ -127,9 +128,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
self.backgroundView.contentView.addSubview(self.scrubberNode.view)
|
||||
self.backgroundView.contentView.addSubview(self.leftTimestampNode.view)
|
||||
self.backgroundView.contentView.addSubview(self.rightTimestampNode.view)
|
||||
self.addSubview(self.scrubberNode.view)
|
||||
//self.backgroundView.contentView.addSubview(self.infoNode.view)
|
||||
}
|
||||
|
||||
|
|
@ -146,20 +147,6 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
var isLoading = false
|
||||
var isCollapsed: Bool?
|
||||
func setCollapsed(_ collapsed: Bool, animated: Bool) {
|
||||
guard self.isCollapsed != collapsed else {
|
||||
return
|
||||
}
|
||||
|
||||
self.isCollapsed = collapsed
|
||||
|
||||
self.updateTimestampsVisibility(animated: animated)
|
||||
self.updateScrubberVisibility(animated: animated)
|
||||
|
||||
if let (size, _, _) = self.containerLayout {
|
||||
self.infoNode.alpha = size.width < size.height && !collapsed ? 1.0 : 0.0
|
||||
}
|
||||
}
|
||||
|
||||
func updateTimestampsVisibility(animated: Bool) {
|
||||
if self.isAnimatedOut {
|
||||
|
|
@ -174,15 +161,20 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
private func updateScrubberVisibility(animated: Bool) {
|
||||
var collapsed = self.isCollapsed
|
||||
var alpha: CGFloat = 1.0
|
||||
var controlAlpha: CGFloat = 1.0
|
||||
if let playbackStatus = self.playbackStatus, playbackStatus.duration <= 30.0 {
|
||||
alpha = self.isCollapsed == true ? 0.0 : 1.0
|
||||
controlAlpha = 1.0
|
||||
} else {
|
||||
alpha = self.isCollapsed == true ? 0.0 : 1.0
|
||||
controlAlpha = alpha
|
||||
collapsed = false
|
||||
}
|
||||
self.scrubberNode.setCollapsed(collapsed == true, animated: animated)
|
||||
let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.3, curve: .linear) : .immediate
|
||||
|
||||
ComponentTransition(transition).setAlpha(view: self.backgroundContainer, alpha: alpha)
|
||||
ComponentTransition(transition).setAlpha(view: self.scrubberNode.view, alpha: controlAlpha)
|
||||
}
|
||||
|
||||
func animateTo(_ timestamp: Double) {
|
||||
|
|
@ -232,8 +224,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
strongSelf.currentLeftString = leftString
|
||||
strongSelf.currentRightString = rightString
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -284,8 +276,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
strongSelf.infoNode.attributedText = NSAttributedString(string: chapter.title, font: textFont, textColor: .white)
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +307,7 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
strongSelf.infoNodePushed = infoNodePushed
|
||||
|
||||
if let layout = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, transition: .animated(duration: 0.35, curve: .spring))
|
||||
strongSelf.updateLayout(size: layout.0, leftInset: layout.1, rightInset: layout.2, isCollapsed: layout.3, transition: .animated(duration: 0.35, curve: .spring))
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -343,8 +335,8 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
strongSelf.infoNode.attributedText = NSAttributedString(string: text, font: textFont, textColor: .white)
|
||||
|
||||
if let (size, leftInset, rightInset) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, transition: .immediate)
|
||||
if let (size, leftInset, rightInset, isCollapsed) = strongSelf.containerLayout {
|
||||
strongSelf.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset, isCollapsed: isCollapsed, transition: .immediate)
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -356,8 +348,10 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (size, leftInset, rightInset)
|
||||
func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, isCollapsed: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = (size, leftInset, rightInset, isCollapsed)
|
||||
|
||||
self.isCollapsed = isCollapsed
|
||||
|
||||
let transition = ComponentTransition(transition)
|
||||
|
||||
|
|
@ -386,6 +380,11 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
rightTimestampOffset = 14.0
|
||||
infoOffset = 0.0
|
||||
|
||||
if isCollapsed {
|
||||
scrubberLeftInset = 0.0
|
||||
scrubberRightInset = 0.0
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.leftTimestampNode.view, frame: CGRect(origin: CGPoint(x: 16.0, y: leftTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
transition.setFrame(view: self.rightTimestampNode.view, frame: CGRect(origin: CGPoint(x: size.width - leftInset - rightInset - 60.0 - 16.0, y: rightTimestampOffset), size: CGSize(width: 60.0, height: 20.0)))
|
||||
|
||||
|
|
@ -397,8 +396,21 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
transition.setPosition(view: self.infoNode.view, position: CGPoint(x: size.width / 2.0, y: infoOffset + infoSize.height / 2.0))
|
||||
self.infoNode.alpha = size.width < size.height && self.isCollapsed == false ? 1.0 : 0.0
|
||||
|
||||
let scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight))
|
||||
self.scrubberNode.frame = scrubberFrame
|
||||
var scrubberFrame = CGRect(origin: CGPoint(x: scrubberLeftInset, y: 15.0), size: CGSize(width: size.width - leftInset - rightInset - scrubberLeftInset - scrubberRightInset, height: scrubberHeight))
|
||||
if isCollapsed {
|
||||
scrubberFrame.origin.y = size.height - scrubberHeight
|
||||
}
|
||||
transition.setFrame(view: self.scrubberNode.view, frame: scrubberFrame)
|
||||
|
||||
let scrubberTransition: ControlledTransition
|
||||
switch transition.animation {
|
||||
case let .curve(duration, curve):
|
||||
scrubberTransition = ControlledTransition(duration: duration, curve: curve.containedViewLayoutTransitionCurve, interactive: false)
|
||||
default:
|
||||
scrubberTransition = ControlledTransition(duration: 0.0, curve: .linear, interactive: false)
|
||||
}
|
||||
|
||||
self.scrubberNode.update(size: scrubberFrame.size, animator: scrubberTransition.legacyAnimator)
|
||||
self.shimmerEffectNode.updateAbsoluteRect(CGRect(origin: .zero, size: scrubberFrame.size), within: scrubberFrame.size)
|
||||
self.shimmerEffectNode.update(backgroundColor: .clear, foregroundColor: UIColor(rgb: 0xffffff, alpha: 0.75), horizontal: true, effectSize: nil, globalTimeOffset: false, duration: nil)
|
||||
self.shimmerEffectNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 4.0), size: CGSize(width: scrubberFrame.size.width, height: 5.0))
|
||||
|
|
@ -409,6 +421,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundView.update(size: size, cornerRadius: min(44.0 * 0.5, size.height * 0.5), isDark: true, tintColor: .init(kind: .panel), transition: transition)
|
||||
|
||||
self.updateTimestampsVisibility(animated: !transition.animation.isImmediate)
|
||||
self.updateScrubberVisibility(animated: !transition.animation.isImmediate)
|
||||
}
|
||||
|
||||
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
|
|
@ -452,6 +467,9 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
|
||||
self.backgroundContainer.alpha = 0.0
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.backgroundContainer, alpha: 1.0)
|
||||
|
||||
self.scrubberNode.alpha = 0.0
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.scrubberNode.view, alpha: 1.0)
|
||||
}
|
||||
|
||||
func animateOut(to scrubberTransition: GalleryItemScrubberTransition?, transition: ContainedViewLayoutTransition) {
|
||||
|
|
@ -488,5 +506,6 @@ final class ChatVideoGalleryItemScrubberView: UIView {
|
|||
}
|
||||
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
ComponentTransition.easeInOut(duration: 0.2).setAlpha(view: self.scrubberNode.view, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
}
|
||||
|
||||
self.headerEdgeEffectView = EdgeEffectView()
|
||||
self.headerEdgeEffectView.isUserInteractionEnabled = false
|
||||
|
||||
self.pager = GalleryPagerNode(pageGap: pageGap, disableTapNavigation: disableTapNavigation)
|
||||
self.footerNode = GalleryFooterNode(controllerInteraction: controllerInteraction)
|
||||
|
|
@ -303,7 +304,7 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
edgeEffectFrame.origin.y -= navigationBarHeight
|
||||
}
|
||||
transition.updateFrame(view: self.headerEdgeEffectView, frame: edgeEffectFrame)
|
||||
self.headerEdgeEffectView.update(content: .black, alpha: 0.35, rect: edgeEffectFrame, edge: .top, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: ComponentTransition(transition))
|
||||
self.headerEdgeEffectView.update(content: .black, alpha: 0.65, rect: edgeEffectFrame, edge: .top, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: ComponentTransition(transition))
|
||||
transition.updateAlpha(layer: self.headerEdgeEffectView.layer, alpha: self.areControlsHidden ? 0.0 : 1.0)
|
||||
|
||||
if let navigationBar = self.navigationBar {
|
||||
|
|
@ -363,16 +364,16 @@ open class GalleryControllerNode: ASDisplayNode, ASScrollViewDelegate, ASGesture
|
|||
self.areControlsHidden = hidden
|
||||
self.controlsVisibilityChanged?(!hidden)
|
||||
if animated {
|
||||
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
|
||||
self.navigationBar?.alpha = alpha
|
||||
self.statusBar?.updateAlpha(alpha, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
|
||||
self.updateThumbnailContainerNodeAlpha(.immediate)
|
||||
})
|
||||
self.footerNode.setVisibilityAlpha(alpha, animated: animated)
|
||||
self.updateThumbnailContainerNodeAlpha(.immediate)
|
||||
|
||||
if let (navigationBarHeight, layout) = self.containerLayout {
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
} else {
|
||||
let alpha: CGFloat = self.areControlsHidden ? 0.0 : 1.0
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import ComponentDisplayAdapters
|
|||
|
||||
public final class GalleryFooterNode: ASDisplayNode {
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
private var contentsFrame = CGRect()
|
||||
|
||||
private var currentThumbnailPanelHeight: CGFloat?
|
||||
private var currentFooterContentNode: GalleryFooterContentNode?
|
||||
|
|
@ -20,6 +21,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
self.controllerInteraction = controllerInteraction
|
||||
|
||||
self.edgeEffectView = EdgeEffectView()
|
||||
self.edgeEffectView.isUserInteractionEnabled = false
|
||||
|
||||
super.init()
|
||||
|
||||
|
|
@ -29,7 +31,8 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
private var visibilityAlpha: CGFloat = 1.0
|
||||
public func setVisibilityAlpha(_ alpha: CGFloat, animated: Bool) {
|
||||
self.visibilityAlpha = alpha
|
||||
self.edgeEffectView.alpha = alpha
|
||||
let transition: ComponentTransition = animated ? .easeInOut(duration: 0.2) : .immediate
|
||||
transition.setAlpha(view: self.edgeEffectView, alpha: alpha)
|
||||
self.currentFooterContentNode?.setVisibilityAlpha(alpha, animated: true)
|
||||
self.currentOverlayContentNode?.setVisibilityAlpha(alpha)
|
||||
}
|
||||
|
|
@ -132,13 +135,15 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - backgroundHeight + verticalOffset), size: CGSize(width: layout.size.width, height: backgroundHeight))
|
||||
}
|
||||
|
||||
self.contentsFrame = backgroundFrame
|
||||
|
||||
var edgeEffectFrame = backgroundFrame
|
||||
let edgeEffectHeight: CGFloat = 120.0
|
||||
let edgeEffectOffset: CGFloat = 70.0
|
||||
edgeEffectFrame.origin.y -= edgeEffectOffset
|
||||
edgeEffectFrame.size.height += edgeEffectOffset
|
||||
edgeEffectTransition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: .black, alpha: 0.35, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: edgeEffectTransition)
|
||||
self.edgeEffectView.update(content: .black, alpha: 0.65, rect: edgeEffectFrame, edge: .bottom, edgeSize: min(edgeEffectHeight, edgeEffectFrame.height), transition: edgeEffectTransition)
|
||||
|
||||
let contentTransition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
|
||||
if let overlayContentNode = self.currentOverlayContentNode {
|
||||
|
|
@ -171,7 +176,7 @@ public final class GalleryFooterNode: ASDisplayNode {
|
|||
if let overlayResult = self.currentOverlayContentNode?.hitTest(point, with: event) {
|
||||
return overlayResult
|
||||
}
|
||||
if !self.edgeEffectView.frame.contains(point) || self.visibilityAlpha < 1.0 {
|
||||
if !self.contentsFrame.contains(point) || self.visibilityAlpha < 1.0 {
|
||||
return nil
|
||||
}
|
||||
let result = super.hitTest(point, with: event)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ final class GalleryTitleView: UIView, NavigationBarTitleView {
|
|||
}
|
||||
|
||||
func setMessage(_ message: Message, presentationData: PresentationData, accountPeerId: PeerId, title: String?) {
|
||||
let authorNameText = stringForFullAuthorName(message: EngineMessage(message), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: accountPeerId)[0]//.joined(separator: " → ")
|
||||
let authorNameText = stringForFullAuthorName(message: EngineMessage(message), strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder, accountPeerId: accountPeerId).first ?? ""
|
||||
let dateText = humanReadableStringForTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: message.timestamp).string
|
||||
|
||||
self.authorNameNode.attributedText = NSAttributedString(string: authorNameText, font: titleFont, textColor: .white)
|
||||
|
|
|
|||
|
|
@ -232,6 +232,7 @@ class ChatImageGalleryItem: GalleryItem {
|
|||
final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
||||
private let context: AccountContext
|
||||
private var message: Message?
|
||||
private var displayInfo: Bool = false
|
||||
private var translateToLanguage: String?
|
||||
private var peerIsCopyProtected: Bool = false
|
||||
private var isSecret: Bool = false
|
||||
|
|
@ -242,6 +243,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
private let recognitionOverlayContentNode: ImageRecognitionOverlayContentNode
|
||||
|
||||
private var displayTextRecognitionButton: Bool = false
|
||||
private var displayStickersButton: Bool = false
|
||||
|
||||
private let moreBarButton: MoreHeaderButton
|
||||
|
||||
private var tilingNode: TilingNode?
|
||||
|
|
@ -445,6 +449,11 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
strongSelf.imageNode.addSubnode(recognizedContentNode)
|
||||
strongSelf.recognizedContentNode = recognizedContentNode
|
||||
strongSelf.recognitionOverlayContentNode.transitionIn()
|
||||
|
||||
if !strongSelf.displayTextRecognitionButton {
|
||||
strongSelf.displayTextRecognitionButton = true
|
||||
strongSelf.updateFooter(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
|
@ -452,11 +461,19 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
fileprivate func setMessage(_ message: Message, displayInfo: Bool, translateToLanguage: String?, peerIsCopyProtected: Bool, isSecret: Bool) {
|
||||
self.message = message
|
||||
self.displayInfo = displayInfo
|
||||
self.translateToLanguage = translateToLanguage
|
||||
self.peerIsCopyProtected = peerIsCopyProtected
|
||||
self.isSecret = isSecret
|
||||
self.imageNode.captureProtected = message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.isCopyProtected() || peerIsCopyProtected || isSecret || message.paidContent != nil
|
||||
self.footerContentNode.setMessage(message, displayInfo: displayInfo, translateToLanguage: translateToLanguage, peerIsCopyProtected: peerIsCopyProtected)
|
||||
self.updateFooter(animated: false)
|
||||
}
|
||||
|
||||
private func updateFooter(animated: Bool) {
|
||||
guard let message = self.message else {
|
||||
return
|
||||
}
|
||||
self.footerContentNode.setMessage(message, displayInfo: self.displayInfo, translateToLanguage: self.translateToLanguage, peerIsCopyProtected: self.peerIsCopyProtected, displayTextRecognitionButton: self.displayTextRecognitionButton, displayStickersButton: self.displayStickersButton, animated: animated)
|
||||
}
|
||||
|
||||
fileprivate func setImage(userLocation: MediaResourceUserLocation, imageReference: ImageMediaReference) {
|
||||
|
|
@ -497,9 +514,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
var barButtonItems: [UIBarButtonItem] = []
|
||||
if imageReference.media.flags.contains(.hasStickers) {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
|
||||
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_Stickers
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
self.displayStickersButton = true
|
||||
}
|
||||
if self.message != nil {
|
||||
let moreMenuItem = UIBarButtonItem(customDisplayNode: self.moreBarButton)!
|
||||
|
|
@ -509,6 +524,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
}
|
||||
self.contextAndMedia = (self.context, imageReference.abstract)
|
||||
self.updateFooter(animated: false)
|
||||
}
|
||||
|
||||
private func updateImageFromFile(path: String) {
|
||||
|
|
@ -768,11 +784,16 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func textRecognitionButtonPressed() {
|
||||
self.recognitionOverlayContentNode.isSelected = true
|
||||
self.recognitionOverlayContentNode.action?(true)
|
||||
}
|
||||
|
||||
@objc func openStickersButtonPressed() {
|
||||
guard let (context, media) = self.contextAndMedia else {
|
||||
return
|
||||
}
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: defaultDarkColorPresentationTheme)
|
||||
let topController = (self.baseNavigationController()?.topViewController as? ViewController)
|
||||
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
|
|
@ -800,7 +821,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
let baseNavigationController = strongSelf.baseNavigationController()
|
||||
baseNavigationController?.view.endEditing(true)
|
||||
let controller = StickerPackScreen(context: context, mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in
|
||||
let controller = StickerPackScreen(context: context, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), mainStickerPack: packs[0], stickerPacks: packs, sendSticker: nil, actionPerformed: { actions in
|
||||
if let (info, items, action) = actions.first {
|
||||
let animateInAsReplacement = false
|
||||
switch action {
|
||||
|
|
@ -1562,7 +1583,7 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
|
|||
private var validLayout: (CGSize, LayoutMetrics, UIEdgeInsets)?
|
||||
private var interfaceIsHidden: Bool = false
|
||||
|
||||
private var isSelected: Bool = false
|
||||
var isSelected: Bool = false
|
||||
|
||||
init(theme: PresentationTheme) {
|
||||
self.backgroundContainer = GlassBackgroundContainerView()
|
||||
|
|
@ -1619,33 +1640,24 @@ private class ImageRecognitionOverlayContentNode: GalleryOverlayContentNode {
|
|||
self.backgroundContainer.update(size: buttonSize, isDark: true, transition: transition)
|
||||
|
||||
self.backgroundView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize)
|
||||
let tintColor: GlassBackgroundView.TintColor
|
||||
if self.isSelected {
|
||||
tintColor = .init(kind: .custom(style: .default, color: UIColor(white: 1.0, alpha: 1.0)))
|
||||
} else {
|
||||
tintColor = .init(kind: .panel)
|
||||
}
|
||||
let tintColor: GlassBackgroundView.TintColor = .init(kind: .custom(style: .default, color: UIColor(white: 1.0, alpha: 1.0)))
|
||||
self.backgroundView.update(size: buttonSize, cornerRadius: buttonSize.height * 0.5, isDark: true, tintColor: tintColor, isInteractive: true, transition: transition)
|
||||
|
||||
self.iconView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: buttonSize)
|
||||
transition.setTintColor(view: self.iconView, color: self.isSelected ? .black : .white)
|
||||
transition.setTintColor(view: self.iconView, color: .black)
|
||||
|
||||
if self.appeared {
|
||||
if !self.isSelected && isHidden {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
} else {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 1.0)
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: self.isSelected ? 1.0 : 0.0)
|
||||
}
|
||||
} else {
|
||||
transition.setAlpha(view: self.backgroundContainer, alpha: 0.0)
|
||||
}
|
||||
|
||||
var buttonPosition: CGPoint
|
||||
if isHidden && !self.isSelected {
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 66.0 - 10.0, y: -52.0)
|
||||
} else {
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - (self.isSelected ? 24.0 : 70.0), y: insets.top - 50.0)
|
||||
}
|
||||
buttonPosition = CGPoint(x: size.width - insets.right - buttonSize.width - 16.0, y: insets.top - 50.0)
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: buttonPosition, size: buttonSize))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -611,226 +611,6 @@ final class MoreHeaderButton: HighlightableButtonNode {
|
|||
}
|
||||
}
|
||||
|
||||
final class SettingsHeaderButton: HighlightableButtonNode {
|
||||
let referenceNode: ContextReferenceContentNode
|
||||
let containerNode: ContextControllerSourceNode
|
||||
|
||||
private let iconLayer: RasterizedCompositionMonochromeLayer
|
||||
|
||||
private let gearsLayer: RasterizedCompositionImageLayer
|
||||
private let dotLayer: RasterizedCompositionImageLayer
|
||||
|
||||
private var speedBadge: ComponentView<Empty>?
|
||||
private var qualityBadge: ComponentView<Empty>?
|
||||
|
||||
private var speedBadgeText: String?
|
||||
private var qualityBadgeText: String?
|
||||
|
||||
private let badgeFont: UIFont
|
||||
|
||||
private var isMenuOpen: Bool = false
|
||||
|
||||
var contextAction: ((ASDisplayNode, ContextGesture?) -> Void)?
|
||||
|
||||
private let wide: Bool
|
||||
|
||||
init(wide: Bool = false) {
|
||||
self.wide = wide
|
||||
|
||||
self.referenceNode = ContextReferenceContentNode()
|
||||
self.containerNode = ContextControllerSourceNode()
|
||||
self.containerNode.animateScale = false
|
||||
|
||||
self.iconLayer = RasterizedCompositionMonochromeLayer()
|
||||
//self.iconLayer.backgroundColor = UIColor.green.cgColor
|
||||
|
||||
self.gearsLayer = RasterizedCompositionImageLayer()
|
||||
self.gearsLayer.image = generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/NavigationSettingsNoDot"), color: .white)
|
||||
|
||||
self.dotLayer = RasterizedCompositionImageLayer()
|
||||
self.dotLayer.image = generateFilledCircleImage(diameter: 4.0, color: .white)
|
||||
|
||||
self.iconLayer.contentsLayer.addSublayer(self.gearsLayer)
|
||||
self.iconLayer.contentsLayer.addSublayer(self.dotLayer)
|
||||
|
||||
self.badgeFont = Font.with(size: 8.0, design: .round, weight: .bold)
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.referenceNode)
|
||||
self.referenceNode.layer.addSublayer(self.iconLayer)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
self.containerNode.shouldBegin = { [weak self] location in
|
||||
guard let strongSelf = self, let _ = strongSelf.contextAction else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
self.containerNode.activated = { [weak self] gesture, _ in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.contextAction?(strongSelf.containerNode, gesture)
|
||||
}
|
||||
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 44.0, height: 44.0))
|
||||
self.referenceNode.frame = self.containerNode.bounds
|
||||
|
||||
if let image = self.gearsLayer.image {
|
||||
let iconInnerInsets = UIEdgeInsets(top: 4.0, left: 8.0, bottom: 4.0, right: 6.0)
|
||||
let iconSize = CGSize(width: image.size.width + iconInnerInsets.left + iconInnerInsets.right, height: image.size.height + iconInnerInsets.top + iconInnerInsets.bottom)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((self.containerNode.bounds.width - iconSize.width) / 2.0), y: floor((self.containerNode.bounds.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
self.iconLayer.position = iconFrame.center
|
||||
self.iconLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.contentsLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.contentsLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
|
||||
self.iconLayer.maskedLayer.position = CGRect(origin: CGPoint(), size: iconFrame.size).center
|
||||
self.iconLayer.maskedLayer.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
self.iconLayer.maskedLayer.backgroundColor = UIColor.white.cgColor
|
||||
|
||||
let gearsFrame = CGRect(origin: CGPoint(x: floor((iconSize.width - image.size.width) * 0.5), y: floor((iconSize.height - image.size.height) * 0.5)), size: image.size)
|
||||
self.gearsLayer.position = gearsFrame.center
|
||||
self.gearsLayer.bounds = CGRect(origin: CGPoint(), size: gearsFrame.size)
|
||||
|
||||
if let dotImage = self.dotLayer.image {
|
||||
let dotFrame = CGRect(origin: CGPoint(x: gearsFrame.minX + floorToScreenPixels((gearsFrame.width - dotImage.size.width) * 0.5), y: gearsFrame.minY + floorToScreenPixels((gearsFrame.height - dotImage.size.height) * 0.5)), size: dotImage.size)
|
||||
self.dotLayer.position = dotFrame.center
|
||||
self.dotLayer.bounds = CGRect(origin: CGPoint(), size: dotFrame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
self.view.isOpaque = false
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
return CGSize(width: 44.0, height: 44.0)
|
||||
}
|
||||
|
||||
func onLayout() {
|
||||
}
|
||||
|
||||
func setIsMenuOpen(isMenuOpen: Bool) {
|
||||
if self.isMenuOpen == isMenuOpen {
|
||||
return
|
||||
}
|
||||
self.isMenuOpen = isMenuOpen
|
||||
|
||||
let rotationTransition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .spring)
|
||||
rotationTransition.updateTransform(layer: self.gearsLayer, transform: CGAffineTransformMakeRotation(isMenuOpen ? (CGFloat.pi * 2.0 / 6.0) : 0.0))
|
||||
self.gearsLayer.animateScale(from: 1.0, to: 1.07, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.gearsLayer.animateScale(from: 1.07, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
|
||||
self.dotLayer.animateScale(from: 1.0, to: 0.8, duration: 0.1, removeOnCompletion: false, completion: { [weak self] finished in
|
||||
guard let self, finished else {
|
||||
return
|
||||
}
|
||||
self.dotLayer.animateScale(from: 0.8, to: 1.0, duration: 0.1, removeOnCompletion: true)
|
||||
})
|
||||
}
|
||||
|
||||
func setBadges(speed: String?, quality: String?, transition: ComponentTransition) {
|
||||
if self.speedBadgeText == speed && self.qualityBadgeText == quality {
|
||||
return
|
||||
}
|
||||
self.speedBadgeText = speed
|
||||
self.qualityBadgeText = quality
|
||||
|
||||
if let badgeText = speed {
|
||||
var badgeTransition = transition
|
||||
let speedBadge: ComponentView<Empty>
|
||||
if let current = self.speedBadge {
|
||||
speedBadge = current
|
||||
} else {
|
||||
speedBadge = ComponentView()
|
||||
self.speedBadge = speedBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = speedBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
if speedBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(speedBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: speedBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: speedBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: speedBadgeView.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: badgeSize))
|
||||
}
|
||||
} else if let speedBadge = self.speedBadge {
|
||||
self.speedBadge = nil
|
||||
if let speedBadgeView = speedBadge.view {
|
||||
let speedBadgeLayer = speedBadgeView.layer
|
||||
transition.setAlpha(layer: speedBadgeLayer, alpha: 0.0, completion: { [weak speedBadgeLayer] _ in
|
||||
speedBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: speedBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
|
||||
if let badgeText = quality {
|
||||
var badgeTransition = transition
|
||||
let qualityBadge: ComponentView<Empty>
|
||||
if let current = self.qualityBadge {
|
||||
qualityBadge = current
|
||||
} else {
|
||||
qualityBadge = ComponentView()
|
||||
self.qualityBadge = qualityBadge
|
||||
badgeTransition = badgeTransition.withAnimation(.none)
|
||||
}
|
||||
let badgeSize = qualityBadge.update(
|
||||
transition: badgeTransition,
|
||||
component: AnyComponent(BadgeComponent(
|
||||
text: badgeText,
|
||||
font: self.badgeFont,
|
||||
cornerRadius: .custom(3.0),
|
||||
insets: UIEdgeInsets(top: 1.33, left: 1.66, bottom: 1.33, right: 1.66),
|
||||
outerInsets: UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
if qualityBadgeView.layer.superlayer == nil {
|
||||
self.iconLayer.contentsLayer.addSublayer(qualityBadgeView.layer)
|
||||
|
||||
transition.animateAlpha(layer: qualityBadgeView.layer, from: 0.0, to: 1.0)
|
||||
transition.animateScale(layer: qualityBadgeView.layer, from: 0.001, to: 1.0)
|
||||
}
|
||||
badgeTransition.setFrame(layer: qualityBadgeView.layer, frame: CGRect(origin: CGPoint(x: self.iconLayer.bounds.width - badgeSize.width, y: self.iconLayer.bounds.height - badgeSize.height), size: badgeSize))
|
||||
}
|
||||
} else if let qualityBadge = self.qualityBadge {
|
||||
self.qualityBadge = nil
|
||||
if let qualityBadgeView = qualityBadge.view {
|
||||
let qualityBadgeLayer = qualityBadgeView.layer
|
||||
transition.setAlpha(layer: qualityBadgeLayer, alpha: 0.0, completion: { [weak qualityBadgeLayer] _ in
|
||||
qualityBadgeLayer?.removeFromSuperlayer()
|
||||
})
|
||||
transition.setScale(layer: qualityBadgeLayer, scale: 0.001)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private final class NativePictureInPictureContentImpl: NSObject, AVPictureInPictureControllerDelegate {
|
||||
private final class PlaybackDelegate: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||
|
|
@ -1113,8 +893,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var moreBarButtonRate: Double = 1.0
|
||||
private var moreBarButtonRateTimestamp: Double?
|
||||
|
||||
private let settingsBarButton: SettingsHeaderButton
|
||||
|
||||
private var videoNode: UniversalVideoNode?
|
||||
private var videoNodeUserInteractionEnabled: Bool = false
|
||||
private var videoFramePreview: FramePreview?
|
||||
|
|
@ -1139,7 +917,8 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private var dismissOnOrientationChange = false
|
||||
private var keepSoundOnDismiss = false
|
||||
private var hasPictureInPicture = false
|
||||
private var hasSettingsButton = false
|
||||
private var displayStickersButton: Bool = false
|
||||
private var settingsButtonState: ChatItemGalleryFooterContentNode.SettingsButtonState?
|
||||
|
||||
private var pictureInPictureButton: UIBarButtonItem?
|
||||
|
||||
|
|
@ -1155,7 +934,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
private let statusDisposable = MetaDisposable()
|
||||
|
||||
private let moreButtonStateDisposable = MetaDisposable()
|
||||
private let settingsButtonStateDisposable = MetaDisposable()
|
||||
private let mediaPlaybackStateDisposable = MetaDisposable()
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
|
@ -1215,9 +993,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self.moreBarButton.isUserInteractionEnabled = true
|
||||
self.moreBarButton.setContent(.more(optionsCircleImage(dark: false)))
|
||||
|
||||
self.settingsBarButton = SettingsHeaderButton()
|
||||
self.settingsBarButton.isUserInteractionEnabled = true
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
|
@ -1247,7 +1022,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
|
||||
self.moreBarButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
|
||||
//self.settingsBarButton.addTarget(self, action: #selector(self.settingsButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
self.footerContentNode.interacting = { [weak self] value in
|
||||
self?.isInteractingPromise.set(value)
|
||||
|
|
@ -1256,8 +1030,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
self.statusButtonNode.addSubnode(self.statusNode)
|
||||
self.statusButtonNode.addTarget(self, action: #selector(self.statusButtonPressed), forControlEvents: .touchUpInside)
|
||||
|
||||
//self.addSubnode(self.statusButtonNode)
|
||||
|
||||
self.footerContentNode.playbackControl = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
if !strongSelf.isPaused {
|
||||
|
|
@ -1391,7 +1163,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
deinit {
|
||||
self.statusDisposable.dispose()
|
||||
self.moreButtonStateDisposable.dispose()
|
||||
self.settingsButtonStateDisposable.dispose()
|
||||
self.mediaPlaybackStateDisposable.dispose()
|
||||
self.scrubbingFrameDisposable?.dispose()
|
||||
self.hideControlsDisposable?.dispose()
|
||||
|
|
@ -1864,15 +1635,27 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.settingsBarButton.setBadges(speed: rateString, quality: qualityString, transition: .spring(duration: 0.35))
|
||||
}))
|
||||
|
||||
self.settingsButtonStateDisposable.set((self.isShowingSettingsMenuPromise.get()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] isShowingSettingsMenu in
|
||||
guard let self else {
|
||||
return
|
||||
if self.settingsButtonState != nil {
|
||||
let settingsButtonState = ChatItemGalleryFooterContentNode.SettingsButtonState(
|
||||
speed: rateString,
|
||||
quality: qualityString
|
||||
)
|
||||
if self.settingsButtonState != settingsButtonState {
|
||||
self.settingsButtonState = settingsButtonState
|
||||
if let validLayout = self.validLayout {
|
||||
if let contentInfo = item.contentInfo {
|
||||
switch contentInfo {
|
||||
case let .message(message, _):
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, settingsButtonState: self.settingsButtonState, displayStickersButton: self.displayStickersButton, animated: true)
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
}
|
||||
}
|
||||
|
||||
self.containerLayoutUpdated(validLayout.layout, navigationBarHeight: validLayout.navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring))
|
||||
}
|
||||
}
|
||||
}
|
||||
self.settingsBarButton.setIsMenuOpen(isMenuOpen: isShowingSettingsMenu)
|
||||
}))
|
||||
|
||||
self.statusDisposable.set((combineLatest(queue: .mainQueue(), videoNode.status, mediaFileStatus)
|
||||
|
|
@ -1936,11 +1719,13 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
isPaused = false
|
||||
}
|
||||
} else if strongSelf.actionAtEnd == .stop {
|
||||
strongSelf.isPlayingPromise.set(false)
|
||||
strongSelf.isPlaying = false
|
||||
if strongSelf.isCentral == true {
|
||||
if !item.isSecret && !strongSelf.playOnDismiss {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
if strongSelf.isPlaying {
|
||||
strongSelf.isPlayingPromise.set(false)
|
||||
strongSelf.isPlaying = false
|
||||
if strongSelf.isCentral == true {
|
||||
if !item.isSecret && !strongSelf.playOnDismiss {
|
||||
strongSelf.updateControlsVisibility(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2030,9 +1815,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
|
||||
var barButtonItems: [UIBarButtonItem] = []
|
||||
if hasLinkedStickers {
|
||||
let rightBarButtonItem = UIBarButtonItem(image: generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: .white), style: .plain, target: self, action: #selector(self.openStickersButtonPressed))
|
||||
rightBarButtonItem.accessibilityLabel = self.presentationData.strings.Gallery_VoiceOver_Stickers
|
||||
barButtonItems.append(rightBarButtonItem)
|
||||
self.displayStickersButton = true
|
||||
}
|
||||
|
||||
if forceEnablePiP || (!isAnimated && !disablePlayerControls && !disablePictureInPicture) {
|
||||
|
|
@ -2080,9 +1863,6 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
|
||||
if !isAnimated && !disablePlayerControls {
|
||||
/*let settingsMenuItem = UIBarButtonItem(customDisplayNode: self.settingsBarButton)!
|
||||
settingsMenuItem.accessibilityLabel = self.presentationData.strings.Settings_Title
|
||||
barButtonItems.append(settingsMenuItem)*/
|
||||
hasSettingsButton = true
|
||||
}
|
||||
|
||||
|
|
@ -2093,7 +1873,12 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
}
|
||||
}
|
||||
|
||||
self.hasSettingsButton = hasSettingsButton
|
||||
if hasSettingsButton {
|
||||
self.settingsButtonState = ChatItemGalleryFooterContentNode.SettingsButtonState(
|
||||
speed: nil,
|
||||
quality: nil
|
||||
)
|
||||
}
|
||||
self._rightBarButtonItems.set(.single(barButtonItems))
|
||||
|
||||
videoNode.playbackCompleted = { [weak self, weak videoNode] in
|
||||
|
|
@ -2148,7 +1933,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
switch contentInfo {
|
||||
case let .message(message, _):
|
||||
isAd = message.adAttribute != nil
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, displaySettingsButton: self.hasSettingsButton)
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, settingsButtonState: self.settingsButtonState, displayStickersButton: self.displayStickersButton)
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
}
|
||||
|
|
@ -3111,7 +2896,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
|
|||
switch contentInfo {
|
||||
case let .message(message, _):
|
||||
isAd = message.adAttribute != nil
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, displaySettingsButton: self.hasSettingsButton)
|
||||
self.footerContentNode.setMessage(message, displayInfo: !item.displayInfoOnTop, peerIsCopyProtected: item.peerIsCopyProtected, displayPictureInPictureButton: self.hasPictureInPicture, settingsButtonState: self.settingsButtonState, displayStickersButton: self.displayStickersButton)
|
||||
case let .webPage(webPage, media, _):
|
||||
self.footerContentNode.setWebPage(webPage, media: media)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ public class ItemListActionItem: ListViewItem, ItemListItem {
|
|||
|
||||
public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
|
|
@ -107,7 +108,10 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
public init() {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = .white
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.maskNode = ASImageNode()
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
|
@ -132,6 +136,20 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.addSubnode(self.activateArea)
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (_ item: ItemListActionItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, (Bool) -> Void) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
|
||||
|
||||
|
|
@ -211,6 +229,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.bottomStripeNode.backgroundColor = itemSeparatorColor
|
||||
strongSelf.backgroundNode.backgroundColor = itemBackgroundColor
|
||||
strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor
|
||||
strongSelf.highlightNode.backgroundColor = item.presentationData.theme.list.itemSearchHighlightColor
|
||||
}
|
||||
|
||||
let _ = titleApply()
|
||||
|
|
@ -273,6 +292,7 @@ public class ItemListActionItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.presentationData.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: params.width - params.rightInset - bottomStripeInset - separatorRightInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -891,8 +891,13 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: floor((bounds.size.height - node.lineHeight) / 2.0)), size: CGSize(width: bounds.size.width, height: node.lineHeight))
|
||||
let foregroundContentFrame = CGRect(origin: CGPoint(), size: CGSize(width: backgroundFrame.size.width, height: backgroundFrame.size.height))
|
||||
|
||||
node.backgroundNode.position = CGPoint(x: backgroundFrame.midX, y: backgroundFrame.midY)
|
||||
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||
if let animator {
|
||||
animator.updatePosition(layer: node.backgroundNode.layer, position: CGPoint(x: backgroundFrame.midX, y: backgroundFrame.midY), completion: nil)
|
||||
animator.updateBounds(layer: node.backgroundNode.layer, bounds: CGRect(origin: CGPoint(), size: backgroundFrame.size), completion: nil)
|
||||
} else {
|
||||
node.backgroundNode.position = CGPoint(x: backgroundFrame.midX, y: backgroundFrame.midY)
|
||||
node.backgroundNode.bounds = CGRect(origin: CGPoint(), size: backgroundFrame.size)
|
||||
}
|
||||
|
||||
node.foregroundContentNode.position = CGPoint(x: foregroundContentFrame.midX, y: foregroundContentFrame.midY)
|
||||
node.foregroundContentNode.bounds = CGRect(origin: CGPoint(), size: foregroundContentFrame.size)
|
||||
|
|
@ -1090,10 +1095,14 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode {
|
|||
switch self.contentNodes {
|
||||
case let .standard(node):
|
||||
if let handleNodeContainer = node.handleNodeContainer, handleNodeContainer.isUserInteractionEnabled, handleNodeContainer.frame.insetBy(dx: 0.0, dy: -16.0).contains(point) {
|
||||
if let handleNode = node.handleNode, handleNode.convert(handleNode.bounds, to: self).insetBy(dx: -32.0, dy: -16.0).contains(point) {
|
||||
if case .none = node.handle {
|
||||
return handleNodeContainer.view
|
||||
} else {
|
||||
return nil
|
||||
if let handleNode = node.handleNode, handleNode.convert(handleNode.bounds, to: self).insetBy(dx: -32.0, dy: -16.0).contains(point) {
|
||||
return handleNodeContainer.view
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ private func generateBubbleShadowImage(shadow: UIColor, diameter: CGFloat, shado
|
|||
|
||||
final class ReactionContextBackgroundNode: ASDisplayNode {
|
||||
struct GlassParams {
|
||||
var isDark: Bool
|
||||
var isTinted: Bool
|
||||
|
||||
init(isTinted: Bool) {
|
||||
init(isDark: Bool, isTinted: Bool) {
|
||||
self.isDark = isDark
|
||||
self.isTinted = isTinted
|
||||
}
|
||||
}
|
||||
|
|
@ -244,7 +246,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
transition.updateFrame(view: self.backgroundView, frame: contentBounds, beginWithCurrentState: true)
|
||||
self.backgroundView.update(size: contentBounds.size, transition: transition)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
if let glass = self.glass, let glassBackgroundView = self.glassBackgroundView {
|
||||
var glassBackgroundFrame = contentBounds.insetBy(dx: 10.0, dy: 10.0)
|
||||
glassBackgroundFrame.size.height -= 8.0
|
||||
transition.updateFrame(view: glassBackgroundView.container, frame: glassBackgroundFrame, beginWithCurrentState: true)
|
||||
|
|
@ -255,7 +257,7 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
} else {
|
||||
glassTintColor = .init(kind: .panel)
|
||||
}
|
||||
glassBackgroundView.container.update(size: glassBackgroundFrame.size, isDark: true, transition: ComponentTransition(transition))
|
||||
glassBackgroundView.container.update(size: glassBackgroundFrame.size, isDark: glass.isDark, transition: ComponentTransition(transition))
|
||||
glassBackgroundView.view.update(size: glassBackgroundFrame.size, cornerRadius: 23.0, isDark: true, tintColor: glassTintColor, transition: ComponentTransition(transition))
|
||||
|
||||
transition.updateFrame(view: self.backgroundTintView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: contentBounds.width, height: contentBounds.height)).insetBy(dx: -10.0, dy: -10.0))
|
||||
|
|
@ -283,6 +285,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
self.backgroundClippingLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.01, delay: mainCircleDelay)
|
||||
self.backgroundClippingLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
self.backgroundShadowLayer.animateSpring(from: 0.01 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: mainCircleDuration, delay: mainCircleDelay)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
glassBackgroundView.container.alpha = 0.0
|
||||
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: glassBackgroundView.container, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func animateInFromAnchorRect(size: CGSize, sourceBackgroundFrame: CGRect) {
|
||||
|
|
@ -304,6 +313,13 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
self.backgroundClippingLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: visualSourceBackgroundFrame.size)), to: NSValue(cgRect: self.backgroundClippingLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
self.backgroundShadowLayer.animateSpring(from: NSValue(cgPoint: CGPoint(x: sourceShadowFrame.midX - size.width / 2.0, y: 0.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.backgroundShadowLayer.animateSpring(from: NSValue(cgRect: CGRect(origin: CGPoint(), size: sourceShadowFrame.size)), to: NSValue(cgRect: self.backgroundShadowLayer.bounds), keyPath: "bounds", duration: springDuration, delay: springDelay, initialVelocity: 0.0, damping: springDamping)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
glassBackgroundView.container.alpha = 0.0
|
||||
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: glassBackgroundView.container, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut() {
|
||||
|
|
@ -314,5 +330,10 @@ final class ReactionContextBackgroundNode: ASDisplayNode {
|
|||
self.largeCircleShadowLayer.animateAlpha(from: CGFloat(self.largeCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
//self.smallCircleLayer.animateAlpha(from: CGFloat(self.smallCircleLayer.opacity), to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.smallCircleShadowLayer.animateAlpha(from: CGFloat(self.smallCircleShadowLayer.opacity), to: 0.0, duration: 0.1, removeOnCompletion: false)
|
||||
|
||||
if let glassBackgroundView = self.glassBackgroundView {
|
||||
let transition: ComponentTransition = .easeInOut(duration: 0.2)
|
||||
transition.setAlpha(view: glassBackgroundView.container, alpha: 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -510,7 +510,7 @@ public final class ReactionContextNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
self.backgroundMaskNode = ASDisplayNode()
|
||||
var backgroundGlassParams: ReactionContextBackgroundNode.GlassParams?
|
||||
if case let .glass(isTinted) = style {
|
||||
backgroundGlassParams = ReactionContextBackgroundNode.GlassParams(isTinted: isTinted)
|
||||
backgroundGlassParams = ReactionContextBackgroundNode.GlassParams(isDark: presentationData.theme.overallDarkAppearance, isTinted: isTinted)
|
||||
}
|
||||
self.backgroundNode = ReactionContextBackgroundNode(glass: backgroundGlassParams, largeCircleSize: largeCircleSize, smallCircleSize: smallCircleSize, maskNode: self.backgroundMaskNode)
|
||||
self.leftBackgroundMaskNode = ASDisplayNode()
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/Settings/PeerSelectionScreen",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/Utils/DeviceModel",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -224,7 +224,7 @@ public func logoutOptionsController(context: AccountContext, navigationControlle
|
|||
|
||||
context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, forceUpdate: false, openPeer: { peer, navigation in
|
||||
}, sendFile: nil, sendSticker: nil, sendEmoji: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { controller, arguments in
|
||||
pushControllerImpl?(controller)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}, dismissInput: {}, contentContext: nil, progress: nil, completion: nil)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -623,15 +623,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
|
||||
let removeSessionImpl: (Int64, @escaping () -> Void) -> Void = { sessionId, completion in
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateSessionText),
|
||||
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateSession, color: .destructive, action: {
|
||||
dismissAction()
|
||||
let controller = textAlertController(
|
||||
context: context,
|
||||
title: nil,
|
||||
text: presentationData.strings.AuthSessions_TerminateSessionText,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateSession, action: {
|
||||
completion()
|
||||
|
||||
updateState {
|
||||
|
|
@ -649,11 +646,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
}
|
||||
context.sharedContext.updateNotificationTokensRegistration()
|
||||
}))
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical
|
||||
)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}
|
||||
|
||||
let removeWebSessionImpl: (Int64) -> Void = { sessionId in
|
||||
|
|
@ -690,16 +688,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
removeSessionImpl(sessionId, {})
|
||||
}, terminateOtherSessions: {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let controller = ActionSheetController(presentationData: presentationData)
|
||||
let dismissAction: () -> Void = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
controller.setItemGroups([
|
||||
ActionSheetItemGroup(items: [
|
||||
ActionSheetTextItem(title: presentationData.strings.AuthSessions_TerminateOtherSessionsText),
|
||||
ActionSheetButtonItem(title: presentationData.strings.AuthSessions_TerminateOtherSessions, color: .destructive, action: {
|
||||
dismissAction()
|
||||
|
||||
let controller = textAlertController(
|
||||
context: context,
|
||||
title: nil,
|
||||
text: presentationData.strings.AuthSessions_TerminateOtherSessionsText,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultDestructiveAction, title: presentationData.strings.AuthSessions_TerminateOtherSessions, action: {
|
||||
updateState {
|
||||
return $0.withUpdatedTerminatingOtherSessions(true)
|
||||
}
|
||||
|
|
@ -715,11 +709,12 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
|
|||
}
|
||||
context.sharedContext.updateNotificationTokensRegistration()
|
||||
}))
|
||||
})
|
||||
]),
|
||||
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
|
||||
])
|
||||
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
|
||||
}),
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {})
|
||||
],
|
||||
actionLayout: .vertical
|
||||
)
|
||||
presentControllerImpl?(controller, nil)
|
||||
}, openSession: { session in
|
||||
let controller = RecentSessionScreen(context: context, subject: .session(session), updateAcceptSecretChats: { value in
|
||||
updateSessionDisposable.set(activeSessionsContext.updateSessionAcceptsSecretChats(session, accepts: value).start())
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -30,6 +30,7 @@ import ContextUI
|
|||
import QuickReactionSetupController
|
||||
import AvatarEditorScreen
|
||||
import PeerSelectionScreen
|
||||
import DeviceModel
|
||||
|
||||
enum SettingsSearchableItemIcon {
|
||||
case profile
|
||||
|
|
@ -3895,7 +3896,7 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
|
|||
present(.push, themeSettingsController(context: context, focusOnItemTag: itemTag))
|
||||
}
|
||||
|
||||
return [
|
||||
var items: [SettingsSearchableItem] = [
|
||||
SettingsSearchableItem(
|
||||
id: "appearance",
|
||||
title: strings.Settings_Appearance,
|
||||
|
|
@ -4094,14 +4095,31 @@ private func appearanceSearchableItems(context: AccountContext) -> [SettingsSear
|
|||
),
|
||||
SettingsSearchableItem(
|
||||
id: "appearance/tap-for-next-media",
|
||||
title: strings.Appearance_ShowNextMediaOnTap,
|
||||
icon: icon,
|
||||
breadcrumbs: [strings.Settings_Appearance],
|
||||
isVisible: false,
|
||||
present: { context, _, present in
|
||||
presentAppearanceSettings(context, present, .tapForNextMedia)
|
||||
}
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
if DeviceModel.current.isIpad {
|
||||
items.append(
|
||||
SettingsSearchableItem(
|
||||
id: "appearance/send-with-cmd-enter",
|
||||
title: strings.Appearance_SendWithCmdEnter,
|
||||
icon: icon,
|
||||
breadcrumbs: [strings.Settings_Appearance],
|
||||
isVisible: false,
|
||||
present: { context, _, present in
|
||||
presentAppearanceSettings(context, present, .sendWithCmdEnter)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
private func languageSearchableItems(context: AccountContext, localizations: [LocalizationInfo]) -> [SettingsSearchableItem] {
|
||||
|
|
|
|||
|
|
@ -12,12 +12,7 @@ import AppBundle
|
|||
private func generateBorderImage(theme: PresentationTheme, bordered: Bool, selected: Bool) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.setFillColor(theme.list.itemBlocksBackgroundColor.cgColor)
|
||||
context.fill(bounds)
|
||||
|
||||
context.setBlendMode(.clear)
|
||||
context.fillEllipse(in: bounds)
|
||||
context.setBlendMode(.normal)
|
||||
context.clear(bounds)
|
||||
|
||||
let lineWidth: CGFloat
|
||||
if selected {
|
||||
|
|
@ -113,8 +108,10 @@ private final class ThemeSettingsAppIconNode : ASDisplayNode {
|
|||
|
||||
override init() {
|
||||
self.iconNode = ASImageNode()
|
||||
self.iconNode.clipsToBounds = true
|
||||
self.iconNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
|
||||
self.iconNode.isLayerBacked = true
|
||||
self.iconNode.cornerRadius = 15.0
|
||||
|
||||
self.overlayNode = ASImageNode()
|
||||
self.overlayNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 63.0, height: 63.0))
|
||||
|
|
@ -199,6 +196,7 @@ private let selectedTextFont = Font.medium(12.0)
|
|||
|
||||
class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
|
@ -219,6 +217,9 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
|
|
@ -234,6 +235,20 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.addSubnode(self.containerNode)
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: ThemeSettingsAppIconItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
return { item, params, neighbors in
|
||||
let contentSize: CGSize
|
||||
|
|
@ -280,6 +295,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.highlightNode.backgroundColor = item.theme.list.itemSearchHighlightColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
|
|
@ -321,6 +337,7 @@ class ThemeSettingsAppIconItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import ThemeCarouselItem
|
|||
import ThemeAccentColorScreen
|
||||
import WallpaperGridScreen
|
||||
import PeerNameColorItem
|
||||
import DeviceModel
|
||||
|
||||
private final class ThemeSettingsControllerArguments {
|
||||
let context: AccountContext
|
||||
|
|
@ -38,13 +39,34 @@ private final class ThemeSettingsControllerArguments {
|
|||
let openBubbleSettings: () -> Void
|
||||
let openPowerSavingSettings: () -> Void
|
||||
let openStickersAndEmoji: () -> Void
|
||||
let toggleSendWithCmdEnter: (Bool) -> Void
|
||||
let toggleShowNextMediaOnTap: (Bool) -> Void
|
||||
let selectAppIcon: (PresentationAppIcon) -> Void
|
||||
let editTheme: (PresentationCloudTheme) -> Void
|
||||
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
|
||||
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, openNameColorSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
|
||||
init(
|
||||
context: AccountContext,
|
||||
selectTheme: @escaping (PresentationThemeReference) -> Void,
|
||||
openThemeSettings: @escaping () -> Void,
|
||||
openWallpaperSettings: @escaping () -> Void,
|
||||
openNameColorSettings: @escaping () -> Void,
|
||||
selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void,
|
||||
openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void,
|
||||
toggleNightTheme: @escaping (Bool) -> Void,
|
||||
openAutoNightTheme: @escaping () -> Void,
|
||||
openTextSize: @escaping () -> Void,
|
||||
openBubbleSettings: @escaping () -> Void,
|
||||
openPowerSavingSettings: @escaping () -> Void,
|
||||
openStickersAndEmoji: @escaping () -> Void,
|
||||
toggleSendWithCmdEnter: @escaping (Bool) -> Void,
|
||||
toggleShowNextMediaOnTap: @escaping (Bool) -> Void,
|
||||
selectAppIcon: @escaping (PresentationAppIcon) -> Void,
|
||||
editTheme: @escaping (PresentationCloudTheme) -> Void,
|
||||
themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void,
|
||||
colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.selectTheme = selectTheme
|
||||
self.openThemeSettings = openThemeSettings
|
||||
|
|
@ -58,6 +80,7 @@ private final class ThemeSettingsControllerArguments {
|
|||
self.openBubbleSettings = openBubbleSettings
|
||||
self.openPowerSavingSettings = openPowerSavingSettings
|
||||
self.openStickersAndEmoji = openStickersAndEmoji
|
||||
self.toggleSendWithCmdEnter = toggleSendWithCmdEnter
|
||||
self.toggleShowNextMediaOnTap = toggleShowNextMediaOnTap
|
||||
self.selectAppIcon = selectAppIcon
|
||||
self.editTheme = editTheme
|
||||
|
|
@ -84,6 +107,7 @@ public enum ThemeSettingsEntryTag: ItemListItemTag {
|
|||
case powerSaving
|
||||
case stickersAndEmoji
|
||||
case animations
|
||||
case sendWithCmdEnter
|
||||
case tapForNextMedia
|
||||
case nightMode
|
||||
|
||||
|
|
@ -112,6 +136,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
case powerSaving
|
||||
case stickersAndEmoji
|
||||
case otherHeader(PresentationTheme, String)
|
||||
case sendWithCmdEnter(PresentationTheme, String, Bool)
|
||||
case showNextMediaOnTap(PresentationTheme, String, Bool)
|
||||
case showNextMediaOnTapInfo(PresentationTheme, String)
|
||||
|
||||
|
|
@ -127,7 +152,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
return ThemeSettingsControllerSection.icon.rawValue
|
||||
case .powerSaving, .stickersAndEmoji:
|
||||
return ThemeSettingsControllerSection.message.rawValue
|
||||
case .otherHeader, .showNextMediaOnTap, .showNextMediaOnTapInfo:
|
||||
case .otherHeader, .sendWithCmdEnter, .showNextMediaOnTap, .showNextMediaOnTapInfo:
|
||||
return ThemeSettingsControllerSection.other.rawValue
|
||||
}
|
||||
}
|
||||
|
|
@ -164,10 +189,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
return 13
|
||||
case .otherHeader:
|
||||
return 14
|
||||
case .showNextMediaOnTap:
|
||||
case .sendWithCmdEnter:
|
||||
return 15
|
||||
case .showNextMediaOnTapInfo:
|
||||
case .showNextMediaOnTap:
|
||||
return 16
|
||||
case .showNextMediaOnTapInfo:
|
||||
return 17
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +290,12 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
case let .sendWithCmdEnter(lhsTheme, lhsTitle, lhsValue):
|
||||
if case let .sendWithCmdEnter(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .showNextMediaOnTap(lhsTheme, lhsTitle, lhsValue):
|
||||
if case let .showNextMediaOnTap(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
|
||||
return true
|
||||
|
|
@ -351,6 +384,10 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
})
|
||||
case let .otherHeader(_, text):
|
||||
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
|
||||
case let .sendWithCmdEnter(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleSendWithCmdEnter(value)
|
||||
}, tag: ThemeSettingsEntryTag.sendWithCmdEnter)
|
||||
case let .showNextMediaOnTap(_, title, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
arguments.toggleShowNextMediaOnTap(value)
|
||||
|
|
@ -361,7 +398,21 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
|
|||
}
|
||||
}
|
||||
|
||||
private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]], accountPeer: EnginePeer?, nameColors: PeerNameColors) -> [ThemeSettingsControllerEntry] {
|
||||
private func themeSettingsControllerEntries(
|
||||
presentationData: PresentationData,
|
||||
presentationThemeSettings: PresentationThemeSettings,
|
||||
chatSettings: ChatSettings,
|
||||
mediaSettings: MediaDisplaySettings,
|
||||
themeReference: PresentationThemeReference,
|
||||
availableThemes: [PresentationThemeReference],
|
||||
availableAppIcons: [PresentationAppIcon],
|
||||
currentAppIconName: String?,
|
||||
isPremium: Bool,
|
||||
chatThemes: [PresentationThemeReference],
|
||||
animatedEmojiStickers: [String: [StickerPackItem]],
|
||||
accountPeer: EnginePeer?,
|
||||
nameColors: PeerNameColors
|
||||
) -> [ThemeSettingsControllerEntry] {
|
||||
var entries: [ThemeSettingsControllerEntry] = []
|
||||
|
||||
let strings = presentationData.strings
|
||||
|
|
@ -437,6 +488,9 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
|
|||
}
|
||||
|
||||
entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased()))
|
||||
if DeviceModel.current.isIpad {
|
||||
entries.append(.sendWithCmdEnter(presentationData.theme, strings.Appearance_SendWithCmdEnter, chatSettings.sendWithCmdEnter))
|
||||
}
|
||||
entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap))
|
||||
entries.append(.showNextMediaOnTapInfo(presentationData.theme, strings.Appearance_ShowNextMediaOnTapInfo))
|
||||
|
||||
|
|
@ -557,6 +611,10 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in
|
||||
}))
|
||||
})
|
||||
}, toggleSendWithCmdEnter: { value in
|
||||
let _ = updateChatSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedSendWithCmdEnter(value)
|
||||
}).start()
|
||||
}, toggleShowNextMediaOnTap: { value in
|
||||
let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
|
||||
return current.withUpdatedShowNextMediaOnTap(value)
|
||||
|
|
@ -1035,9 +1093,26 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
})
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)))
|
||||
let signal = combineLatest(
|
||||
queue: .mainQueue(),
|
||||
context.sharedContext.presentationData,
|
||||
context.sharedContext.accountManager.sharedData(keys: [
|
||||
ApplicationSpecificSharedDataKeys.presentationThemeSettings,
|
||||
ApplicationSpecificSharedDataKeys.chatSettings,
|
||||
ApplicationSpecificSharedDataKeys.mediaDisplaySettings,
|
||||
SharedDataKeys.chatThemes
|
||||
]),
|
||||
cloudThemes.get(),
|
||||
availableAppIcons,
|
||||
currentAppIconName.get(),
|
||||
removedThemeIndexesPromise.get(),
|
||||
animatedEmojiStickers,
|
||||
context.account.postbox.peerView(id: context.account.peerId),
|
||||
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
)
|
||||
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
|
||||
let chatSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) ?? ChatSettings.defaultSettings
|
||||
let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
|
||||
|
||||
let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
|
||||
|
|
@ -1077,7 +1152,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
|
|||
chatThemes.insert(.builtin(.dayClassic), at: 0)
|
||||
|
||||
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, chatSettings: chatSettings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers, accountPeer: accountPeer, nameColors: context.peerNameColors), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -289,9 +289,9 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.addSubnode(self.gridNode)
|
||||
|
||||
self.titleContainer.addSubnode(self.titleNode)
|
||||
self.addSubnode(self.topContainerNode)
|
||||
self.addSubnode(self.titleContainer)
|
||||
|
||||
self.addSubnode(self.topContainerNode)
|
||||
self.addSubnode(self.bottomContainerNode)
|
||||
|
||||
self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in
|
||||
|
|
@ -654,6 +654,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
self.reorderingGestureRecognizer = reorderingGestureRecognizer
|
||||
self.gridNode.view.addGestureRecognizer(reorderingGestureRecognizer)
|
||||
|
||||
self.gridNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.gridNode.clipsToBounds = true
|
||||
self.gridNode.layer.cornerRadius = 40.0
|
||||
|
||||
self.topEdgeEffectView.clipsToBounds = true
|
||||
self.topEdgeEffectView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.topEdgeEffectView.layer.cornerRadius = 40.0
|
||||
self.topContainerNode.view.addSubview(self.topEdgeEffectView)
|
||||
self.bottomContainerNode.view.addSubview(self.bottomEdgeEffectView)
|
||||
}
|
||||
|
|
@ -1891,7 +1898,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
|
||||
transition.updateFrame(node: self.bottomContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: 90.0)))
|
||||
|
||||
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset))
|
||||
let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top))
|
||||
|
||||
let itemsPerRow = 5
|
||||
let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
|
||||
|
|
@ -1923,12 +1930,13 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
|
||||
let initialRevealedRowCount: CGFloat = 4.5
|
||||
|
||||
let topInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
topInset = 0.0
|
||||
topInset = titleAreaInset
|
||||
} else {
|
||||
topInset = insets.top + max(0.0, layout.size.height - floor(initialRevealedRowCount * itemWidth) - insets.top - actionAreaHeight - titleAreaInset)
|
||||
}
|
||||
|
||||
let additionalGridBottomInset = max(0.0, gridFrame.size.height - actionAreaHeight - contentHeight)
|
||||
let gridInsets = UIEdgeInsets(top: topInset, left: gridLeftInset, bottom: actionAreaHeight + additionalGridBottomInset, right: layout.size.width - fillingWidth - gridLeftInset)
|
||||
|
||||
|
|
@ -2112,6 +2120,10 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
buttonView.frame = buttonFrame
|
||||
}
|
||||
|
||||
let topEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 90.0))
|
||||
transition.updateFrame(view: self.topEdgeEffectView, frame: topEdgeEffectFrame)
|
||||
self.topEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: topEdgeEffectFrame, edge: .top, edgeSize: topEdgeEffectFrame.height, transition: ComponentTransition(transition))
|
||||
|
||||
let bottomEdgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: actionAreaHeight - 90.0), size: CGSize(width: layout.size.width, height: 90.0))
|
||||
transition.updateFrame(view: self.bottomEdgeEffectView, frame: bottomEdgeEffectFrame)
|
||||
self.bottomEdgeEffectView.update(content: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor, blur: true, alpha: 0.65, rect: bottomEdgeEffectFrame, edge: .bottom, edgeSize: bottomEdgeEffectFrame.height, transition: ComponentTransition(transition))
|
||||
|
|
@ -2133,7 +2145,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
return
|
||||
}
|
||||
|
||||
let minBackgroundY = gridFrame.minY - titleAreaInset
|
||||
let minBackgroundY = gridFrame.minY
|
||||
let unclippedBackgroundY = gridFrame.minY - presentationLayout.contentOffset.y - titleAreaInset
|
||||
|
||||
let offsetFromInitialPosition = presentationLayout.contentOffset.y + gridInsets.top
|
||||
|
|
@ -2173,7 +2185,7 @@ private final class StickerPackContainer: ASDisplayNode {
|
|||
var titleContainerFrame: CGRect
|
||||
if case .regular = layout.metrics.widthClass {
|
||||
backgroundFrame.origin.y = min(0.0, backgroundFrame.origin.y)
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0)), size: CGSize())
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: floor((56.0) / 2.0) + 10.0), size: CGSize())
|
||||
} else {
|
||||
titleContainerFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((56.0) / 2.0) + 10.0), size: CGSize())
|
||||
}
|
||||
|
|
@ -3103,12 +3115,12 @@ private final class StickerPackContextReferenceContentSource: ContextReferenceCo
|
|||
|
||||
|
||||
private func generateShadowImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 140.0, height: 140.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 220.0, height: 220.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.saveGState()
|
||||
context.setShadow(offset: CGSize(), blur: 60.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 20.0, height: 20.0), cornerRadius: 10.0).cgPath
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 60.0, y: 60.0, width: 100.0, height: 100.0), cornerRadius: 40.0).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
|
||||
|
|
@ -3117,7 +3129,7 @@ private func generateShadowImage() -> UIImage? {
|
|||
context.setBlendMode(.clear)
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 70, topCapHeight: 70)
|
||||
})?.stretchableImage(withLeftCapWidth: 110, topCapHeight: 110)
|
||||
}
|
||||
|
||||
private func generateArrowImage(color: UIColor) -> UIImage? {
|
||||
|
|
@ -3290,38 +3302,6 @@ private class ReorderingGestureRecognizer: UIGestureRecognizer {
|
|||
}
|
||||
}
|
||||
|
||||
private func generateShadowImage(corners: CACornerMask, radius: CGFloat) -> UIImage? {
|
||||
return generateImage(CGSize(width: 120.0, height: 120), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
// context.saveGState()
|
||||
context.setShadow(offset: CGSize(), blur: 28.0, color: UIColor(white: 0.0, alpha: 0.4).cgColor)
|
||||
|
||||
var rectCorners: UIRectCorner = []
|
||||
if corners.contains(.layerMinXMinYCorner) {
|
||||
rectCorners.insert(.topLeft)
|
||||
}
|
||||
if corners.contains(.layerMaxXMinYCorner) {
|
||||
rectCorners.insert(.topRight)
|
||||
}
|
||||
if corners.contains(.layerMinXMaxYCorner) {
|
||||
rectCorners.insert(.bottomLeft)
|
||||
}
|
||||
if corners.contains(.layerMaxXMaxYCorner) {
|
||||
rectCorners.insert(.bottomRight)
|
||||
}
|
||||
|
||||
let path = UIBezierPath(roundedRect: CGRect(x: 30.0, y: 30.0, width: 60.0, height: 60.0), byRoundingCorners: rectCorners, cornerRadii: CGSize(width: radius, height: radius)).cgPath
|
||||
context.addPath(path)
|
||||
context.fillPath()
|
||||
// context.restoreGState()
|
||||
|
||||
// context.setBlendMode(.clear)
|
||||
// context.addPath(path)
|
||||
// context.fillPath()
|
||||
})?.stretchableImage(withLeftCapWidth: 60, topCapHeight: 60)
|
||||
}
|
||||
|
||||
private final class CopyView: UIView {
|
||||
let shadow: UIImageView
|
||||
var snapshotView: UIView?
|
||||
|
|
|
|||
|
|
@ -997,6 +997,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||
dict[2029777832] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityEpic($0) }
|
||||
dict[-822614104] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityLegendary($0) }
|
||||
dict[-259174037] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityRare($0) }
|
||||
dict[-607231095] = { return Api.StarGiftAttributeRarity.parse_starGiftAttributeRarityUncommon($0) }
|
||||
dict[1118831432] = { return Api.StarGiftAuctionAcquiredGift.parse_starGiftAuctionAcquiredGift($0) }
|
||||
dict[984483112] = { return Api.StarGiftAuctionRound.parse_starGiftAuctionRound($0) }
|
||||
dict[178266597] = { return Api.StarGiftAuctionRound.parse_starGiftAuctionRoundExtendable($0) }
|
||||
|
|
|
|||
|
|
@ -1318,6 +1318,7 @@ public extension Api {
|
|||
case starGiftAttributeRarityEpic
|
||||
case starGiftAttributeRarityLegendary
|
||||
case starGiftAttributeRarityRare
|
||||
case starGiftAttributeRarityUncommon
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
|
|
@ -1342,6 +1343,11 @@ public extension Api {
|
|||
buffer.appendInt32(-259174037)
|
||||
}
|
||||
break
|
||||
case .starGiftAttributeRarityUncommon:
|
||||
if boxed {
|
||||
buffer.appendInt32(-607231095)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1355,6 +1361,8 @@ public extension Api {
|
|||
return ("starGiftAttributeRarityLegendary", [])
|
||||
case .starGiftAttributeRarityRare:
|
||||
return ("starGiftAttributeRarityRare", [])
|
||||
case .starGiftAttributeRarityUncommon:
|
||||
return ("starGiftAttributeRarityUncommon", [])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1378,6 +1386,9 @@ public extension Api {
|
|||
public static func parse_starGiftAttributeRarityRare(_ reader: BufferReader) -> StarGiftAttributeRarity? {
|
||||
return Api.StarGiftAttributeRarity.starGiftAttributeRarityRare
|
||||
}
|
||||
public static func parse_starGiftAttributeRarityUncommon(_ reader: BufferReader) -> StarGiftAttributeRarity? {
|
||||
return Api.StarGiftAttributeRarity.starGiftAttributeRarityUncommon
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
|
|
@ -2127,76 +2138,3 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsAmount: TypeConstructorDescription {
|
||||
public class Cons_starsAmount {
|
||||
public var amount: Int64
|
||||
public var nanos: Int32
|
||||
public init(amount: Int64, nanos: Int32) {
|
||||
self.amount = amount
|
||||
self.nanos = nanos
|
||||
}
|
||||
}
|
||||
public class Cons_starsTonAmount {
|
||||
public var amount: Int64
|
||||
public init(amount: Int64) {
|
||||
self.amount = amount
|
||||
}
|
||||
}
|
||||
case starsAmount(Cons_starsAmount)
|
||||
case starsTonAmount(Cons_starsTonAmount)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsAmount(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1145654109)
|
||||
}
|
||||
serializeInt64(_data.amount, buffer: buffer, boxed: false)
|
||||
serializeInt32(_data.nanos, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .starsTonAmount(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(1957618656)
|
||||
}
|
||||
serializeInt64(_data.amount, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsAmount(let _data):
|
||||
return ("starsAmount", [("amount", _data.amount as Any), ("nanos", _data.nanos as Any)])
|
||||
case .starsTonAmount(let _data):
|
||||
return ("starsTonAmount", [("amount", _data.amount as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StarsAmount.starsAmount(Cons_starsAmount(amount: _1!, nanos: _2!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StarsAmount.starsTonAmount(Cons_starsTonAmount(amount: _1!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,76 @@
|
|||
public extension Api {
|
||||
enum StarsAmount: TypeConstructorDescription {
|
||||
public class Cons_starsAmount {
|
||||
public var amount: Int64
|
||||
public var nanos: Int32
|
||||
public init(amount: Int64, nanos: Int32) {
|
||||
self.amount = amount
|
||||
self.nanos = nanos
|
||||
}
|
||||
}
|
||||
public class Cons_starsTonAmount {
|
||||
public var amount: Int64
|
||||
public init(amount: Int64) {
|
||||
self.amount = amount
|
||||
}
|
||||
}
|
||||
case starsAmount(Cons_starsAmount)
|
||||
case starsTonAmount(Cons_starsTonAmount)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .starsAmount(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1145654109)
|
||||
}
|
||||
serializeInt64(_data.amount, buffer: buffer, boxed: false)
|
||||
serializeInt32(_data.nanos, buffer: buffer, boxed: false)
|
||||
break
|
||||
case .starsTonAmount(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(1957618656)
|
||||
}
|
||||
serializeInt64(_data.amount, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .starsAmount(let _data):
|
||||
return ("starsAmount", [("amount", _data.amount as Any), ("nanos", _data.nanos as Any)])
|
||||
case .starsTonAmount(let _data):
|
||||
return ("starsTonAmount", [("amount", _data.amount as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_starsAmount(_ reader: BufferReader) -> StarsAmount? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StarsAmount.starsAmount(Cons_starsAmount(amount: _1!, nanos: _2!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_starsTonAmount(_ reader: BufferReader) -> StarsAmount? {
|
||||
var _1: Int64?
|
||||
_1 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StarsAmount.starsTonAmount(Cons_starsTonAmount(amount: _1!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StarsGiftOption: TypeConstructorDescription {
|
||||
public class Cons_starsGiftOption {
|
||||
|
|
@ -1662,186 +1735,3 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StickerSetCovered: TypeConstructorDescription {
|
||||
public class Cons_stickerSetCovered {
|
||||
public var set: Api.StickerSet
|
||||
public var cover: Api.Document
|
||||
public init(set: Api.StickerSet, cover: Api.Document) {
|
||||
self.set = set
|
||||
self.cover = cover
|
||||
}
|
||||
}
|
||||
public class Cons_stickerSetFullCovered {
|
||||
public var set: Api.StickerSet
|
||||
public var packs: [Api.StickerPack]
|
||||
public var keywords: [Api.StickerKeyword]
|
||||
public var documents: [Api.Document]
|
||||
public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) {
|
||||
self.set = set
|
||||
self.packs = packs
|
||||
self.keywords = keywords
|
||||
self.documents = documents
|
||||
}
|
||||
}
|
||||
public class Cons_stickerSetMultiCovered {
|
||||
public var set: Api.StickerSet
|
||||
public var covers: [Api.Document]
|
||||
public init(set: Api.StickerSet, covers: [Api.Document]) {
|
||||
self.set = set
|
||||
self.covers = covers
|
||||
}
|
||||
}
|
||||
public class Cons_stickerSetNoCovered {
|
||||
public var set: Api.StickerSet
|
||||
public init(set: Api.StickerSet) {
|
||||
self.set = set
|
||||
}
|
||||
}
|
||||
case stickerSetCovered(Cons_stickerSetCovered)
|
||||
case stickerSetFullCovered(Cons_stickerSetFullCovered)
|
||||
case stickerSetMultiCovered(Cons_stickerSetMultiCovered)
|
||||
case stickerSetNoCovered(Cons_stickerSetNoCovered)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .stickerSetCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(1678812626)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
_data.cover.serialize(buffer, true)
|
||||
break
|
||||
case .stickerSetFullCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(1087454222)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.packs.count))
|
||||
for item in _data.packs {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.keywords.count))
|
||||
for item in _data.keywords {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.documents.count))
|
||||
for item in _data.documents {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .stickerSetMultiCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(872932635)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.covers.count))
|
||||
for item in _data.covers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .stickerSetNoCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(2008112412)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .stickerSetCovered(let _data):
|
||||
return ("stickerSetCovered", [("set", _data.set as Any), ("cover", _data.cover as Any)])
|
||||
case .stickerSetFullCovered(let _data):
|
||||
return ("stickerSetFullCovered", [("set", _data.set as Any), ("packs", _data.packs as Any), ("keywords", _data.keywords as Any), ("documents", _data.documents as Any)])
|
||||
case .stickerSetMultiCovered(let _data):
|
||||
return ("stickerSetMultiCovered", [("set", _data.set as Any), ("covers", _data.covers as Any)])
|
||||
case .stickerSetNoCovered(let _data):
|
||||
return ("stickerSetNoCovered", [("set", _data.set as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
var _2: Api.Document?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Document
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StickerSetCovered.stickerSetCovered(Cons_stickerSetCovered(set: _1!, cover: _2!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
var _2: [Api.StickerPack]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
|
||||
}
|
||||
var _3: [Api.StickerKeyword]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self)
|
||||
}
|
||||
var _4: [Api.Document]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.StickerSetCovered.stickerSetFullCovered(Cons_stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
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.StickerSetCovered.stickerSetMultiCovered(Cons_stickerSetMultiCovered(set: _1!, covers: _2!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StickerSetCovered.stickerSetNoCovered(Cons_stickerSetNoCovered(set: _1!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,186 @@
|
|||
public extension Api {
|
||||
enum StickerSetCovered: TypeConstructorDescription {
|
||||
public class Cons_stickerSetCovered {
|
||||
public var set: Api.StickerSet
|
||||
public var cover: Api.Document
|
||||
public init(set: Api.StickerSet, cover: Api.Document) {
|
||||
self.set = set
|
||||
self.cover = cover
|
||||
}
|
||||
}
|
||||
public class Cons_stickerSetFullCovered {
|
||||
public var set: Api.StickerSet
|
||||
public var packs: [Api.StickerPack]
|
||||
public var keywords: [Api.StickerKeyword]
|
||||
public var documents: [Api.Document]
|
||||
public init(set: Api.StickerSet, packs: [Api.StickerPack], keywords: [Api.StickerKeyword], documents: [Api.Document]) {
|
||||
self.set = set
|
||||
self.packs = packs
|
||||
self.keywords = keywords
|
||||
self.documents = documents
|
||||
}
|
||||
}
|
||||
public class Cons_stickerSetMultiCovered {
|
||||
public var set: Api.StickerSet
|
||||
public var covers: [Api.Document]
|
||||
public init(set: Api.StickerSet, covers: [Api.Document]) {
|
||||
self.set = set
|
||||
self.covers = covers
|
||||
}
|
||||
}
|
||||
public class Cons_stickerSetNoCovered {
|
||||
public var set: Api.StickerSet
|
||||
public init(set: Api.StickerSet) {
|
||||
self.set = set
|
||||
}
|
||||
}
|
||||
case stickerSetCovered(Cons_stickerSetCovered)
|
||||
case stickerSetFullCovered(Cons_stickerSetFullCovered)
|
||||
case stickerSetMultiCovered(Cons_stickerSetMultiCovered)
|
||||
case stickerSetNoCovered(Cons_stickerSetNoCovered)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .stickerSetCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(1678812626)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
_data.cover.serialize(buffer, true)
|
||||
break
|
||||
case .stickerSetFullCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(1087454222)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.packs.count))
|
||||
for item in _data.packs {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.keywords.count))
|
||||
for item in _data.keywords {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.documents.count))
|
||||
for item in _data.documents {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .stickerSetMultiCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(872932635)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.covers.count))
|
||||
for item in _data.covers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
case .stickerSetNoCovered(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(2008112412)
|
||||
}
|
||||
_data.set.serialize(buffer, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .stickerSetCovered(let _data):
|
||||
return ("stickerSetCovered", [("set", _data.set as Any), ("cover", _data.cover as Any)])
|
||||
case .stickerSetFullCovered(let _data):
|
||||
return ("stickerSetFullCovered", [("set", _data.set as Any), ("packs", _data.packs as Any), ("keywords", _data.keywords as Any), ("documents", _data.documents as Any)])
|
||||
case .stickerSetMultiCovered(let _data):
|
||||
return ("stickerSetMultiCovered", [("set", _data.set as Any), ("covers", _data.covers as Any)])
|
||||
case .stickerSetNoCovered(let _data):
|
||||
return ("stickerSetNoCovered", [("set", _data.set as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_stickerSetCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
var _2: Api.Document?
|
||||
if let signature = reader.readInt32() {
|
||||
_2 = Api.parse(reader, signature: signature) as? Api.Document
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
if _c1 && _c2 {
|
||||
return Api.StickerSetCovered.stickerSetCovered(Cons_stickerSetCovered(set: _1!, cover: _2!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetFullCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
var _2: [Api.StickerPack]?
|
||||
if let _ = reader.readInt32() {
|
||||
_2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerPack.self)
|
||||
}
|
||||
var _3: [Api.StickerKeyword]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.StickerKeyword.self)
|
||||
}
|
||||
var _4: [Api.Document]?
|
||||
if let _ = reader.readInt32() {
|
||||
_4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.StickerSetCovered.stickerSetFullCovered(Cons_stickerSetFullCovered(set: _1!, packs: _2!, keywords: _3!, documents: _4!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetMultiCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
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.StickerSetCovered.stickerSetMultiCovered(Cons_stickerSetMultiCovered(set: _1!, covers: _2!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
public static func parse_stickerSetNoCovered(_ reader: BufferReader) -> StickerSetCovered? {
|
||||
var _1: Api.StickerSet?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.StickerSet
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
if _c1 {
|
||||
return Api.StickerSetCovered.stickerSetNoCovered(Cons_stickerSetNoCovered(set: _1!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum StoriesStealthMode: TypeConstructorDescription {
|
||||
public class Cons_storiesStealthMode {
|
||||
|
|
@ -1532,64 +1715,3 @@ public extension Api {
|
|||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
enum TopPeerCategoryPeers: TypeConstructorDescription {
|
||||
public class Cons_topPeerCategoryPeers {
|
||||
public var category: Api.TopPeerCategory
|
||||
public var count: Int32
|
||||
public var peers: [Api.TopPeer]
|
||||
public init(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) {
|
||||
self.category = category
|
||||
self.count = count
|
||||
self.peers = peers
|
||||
}
|
||||
}
|
||||
case topPeerCategoryPeers(Cons_topPeerCategoryPeers)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .topPeerCategoryPeers(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(-75283823)
|
||||
}
|
||||
_data.category.serialize(buffer, true)
|
||||
serializeInt32(_data.count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.peers.count))
|
||||
for item in _data.peers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .topPeerCategoryPeers(let _data):
|
||||
return ("topPeerCategoryPeers", [("category", _data.category as Any), ("count", _data.count as Any), ("peers", _data.peers as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? {
|
||||
var _1: Api.TopPeerCategory?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory
|
||||
}
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: [Api.TopPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.TopPeerCategoryPeers.topPeerCategoryPeers(Cons_topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,64 @@
|
|||
public extension Api {
|
||||
enum TopPeerCategoryPeers: TypeConstructorDescription {
|
||||
public class Cons_topPeerCategoryPeers {
|
||||
public var category: Api.TopPeerCategory
|
||||
public var count: Int32
|
||||
public var peers: [Api.TopPeer]
|
||||
public init(category: Api.TopPeerCategory, count: Int32, peers: [Api.TopPeer]) {
|
||||
self.category = category
|
||||
self.count = count
|
||||
self.peers = peers
|
||||
}
|
||||
}
|
||||
case topPeerCategoryPeers(Cons_topPeerCategoryPeers)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .topPeerCategoryPeers(let _data):
|
||||
if boxed {
|
||||
buffer.appendInt32(-75283823)
|
||||
}
|
||||
_data.category.serialize(buffer, true)
|
||||
serializeInt32(_data.count, buffer: buffer, boxed: false)
|
||||
buffer.appendInt32(481674261)
|
||||
buffer.appendInt32(Int32(_data.peers.count))
|
||||
for item in _data.peers {
|
||||
item.serialize(buffer, true)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .topPeerCategoryPeers(let _data):
|
||||
return ("topPeerCategoryPeers", [("category", _data.category as Any), ("count", _data.count as Any), ("peers", _data.peers as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
public static func parse_topPeerCategoryPeers(_ reader: BufferReader) -> TopPeerCategoryPeers? {
|
||||
var _1: Api.TopPeerCategory?
|
||||
if let signature = reader.readInt32() {
|
||||
_1 = Api.parse(reader, signature: signature) as? Api.TopPeerCategory
|
||||
}
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: [Api.TopPeer]?
|
||||
if let _ = reader.readInt32() {
|
||||
_3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.TopPeer.self)
|
||||
}
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
if _c1 && _c2 && _c3 {
|
||||
return Api.TopPeerCategoryPeers.topPeerCategoryPeers(Cons_topPeerCategoryPeers(category: _1!, count: _2!, peers: _3!))
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public extension Api {
|
||||
indirect enum Update: TypeConstructorDescription {
|
||||
public class Cons_updateBotBusinessConnect {
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
|
|||
guard let gift = StarGift(apiStarGift: apiGift) else {
|
||||
return nil
|
||||
}
|
||||
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0, canCraftAt: canCraftAt))
|
||||
return TelegramMediaAction(action: .starGiftUnique(gift: gift, isUpgrade: (flags & (1 << 0)) != 0, isTransferred: (flags & (1 << 1)) != 0, savedToProfile: (flags & (1 << 2)) != 0, canExportDate: canExportAt, transferStars: transferStars, isRefunded: (flags & (1 << 5)) != 0, isPrepaidUpgrade: (flags & (1 << 11)) != 0, peerId: peer?.peerId, senderId: fromId?.peerId, savedId: savedId, resaleAmount: resaleAmount.flatMap { CurrencyAmount(apiAmount: $0) }, canTransferDate: canTransferDate, canResaleDate: canResaleDate, dropOriginalDetailsStars: dropOriginalDetailsStars, assigned: (flags & (1 << 13)) != 0, fromOffer: (flags & (1 << 14)) != 0, canCraftAt: canCraftAt, isCrafted: (flags & (1 << 16)) != 0))
|
||||
case let .messageActionPaidMessagesRefunded(messageActionPaidMessagesRefundedData):
|
||||
let (count, stars) = (messageActionPaidMessagesRefundedData.count, messageActionPaidMessagesRefundedData.stars)
|
||||
return TelegramMediaAction(action: .paidMessagesRefunded(count: count, stars: stars))
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
case giftStars(currency: String, amount: Int64, count: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?)
|
||||
case prizeStars(amount: Int64, isUnclaimed: Bool, boostPeerId: PeerId?, transactionId: String?, giveawayMessageId: MessageId?)
|
||||
case starGift(gift: StarGift, convertStars: Int64?, text: String?, entities: [MessageTextEntity]?, nameHidden: Bool, savedToProfile: Bool, converted: Bool, upgraded: Bool, canUpgrade: Bool, upgradeStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, upgradeMessageId: Int32?, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, prepaidUpgradeHash: String?, giftMessageId: Int32?, upgradeSeparate: Bool, isAuctionAcquired: Bool, toPeerId: EnginePeer.Id?, number: Int32?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool, canCraftAt: Int32?)
|
||||
case starGiftUnique(gift: StarGift, isUpgrade: Bool, isTransferred: Bool, savedToProfile: Bool, canExportDate: Int32?, transferStars: Int64?, isRefunded: Bool, isPrepaidUpgrade: Bool, peerId: EnginePeer.Id?, senderId: EnginePeer.Id?, savedId: Int64?, resaleAmount: CurrencyAmount?, canTransferDate: Int32?, canResaleDate: Int32?, dropOriginalDetailsStars: Int64?, assigned: Bool, fromOffer: Bool, canCraftAt: Int32?, isCrafted: Bool)
|
||||
case paidMessagesRefunded(count: Int32, stars: Int64)
|
||||
case paidMessagesPriceEdited(stars: Int64, broadcastMessagesAllowed: Bool)
|
||||
case conferenceCall(ConferenceCall)
|
||||
|
|
@ -405,7 +405,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else if let stars = decoder.decodeOptionalInt64ForKey("resaleStars") {
|
||||
resaleAmount = CurrencyAmount(amount: StarsAmount(value: stars, nanos: 0), currency: .stars)
|
||||
}
|
||||
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false), canCraftAt: decoder.decodeOptionalInt32ForKey("canCraftAt"))
|
||||
self = .starGiftUnique(gift: decoder.decodeObjectForKey("gift", decoder: { StarGift(decoder: $0) }) as! StarGift, isUpgrade: decoder.decodeBoolForKey("isUpgrade", orElse: false), isTransferred: decoder.decodeBoolForKey("isTransferred", orElse: false), savedToProfile: decoder.decodeBoolForKey("savedToProfile", orElse: false), canExportDate: decoder.decodeOptionalInt32ForKey("canExportDate"), transferStars: decoder.decodeOptionalInt64ForKey("transferStars"), isRefunded: decoder.decodeBoolForKey("isRefunded", orElse: false), isPrepaidUpgrade: decoder.decodeBoolForKey("isPrepaidUpgrade", orElse: false), peerId: decoder.decodeOptionalInt64ForKey("peerId").flatMap { EnginePeer.Id($0) }, senderId: decoder.decodeOptionalInt64ForKey("senderId").flatMap { EnginePeer.Id($0) }, savedId: decoder.decodeOptionalInt64ForKey("savedId"), resaleAmount: resaleAmount, canTransferDate: decoder.decodeOptionalInt32ForKey("canTransferDate"), canResaleDate: decoder.decodeOptionalInt32ForKey("canResaleDate"), dropOriginalDetailsStars: decoder.decodeOptionalInt64ForKey("dropOriginalDetailsStars"), assigned: decoder.decodeBoolForKey("assigned", orElse: false), fromOffer: decoder.decodeBoolForKey("fromOffer", orElse: false), canCraftAt: decoder.decodeOptionalInt32ForKey("canCraftAt"), isCrafted: decoder.decodeBoolForKey("isCrafted", orElse: false))
|
||||
case 46:
|
||||
self = .paidMessagesRefunded(count: decoder.decodeInt32ForKey("count", orElse: 0), stars: decoder.decodeInt64ForKey("stars", orElse: 0))
|
||||
case 47:
|
||||
|
|
@ -806,7 +806,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "number")
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer, canCraftAt):
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, dropOriginalDetailsStars, assigned, fromOffer, canCraftAt, isCrafted):
|
||||
encoder.encodeInt32(45, forKey: "_rawValue")
|
||||
encoder.encodeObject(gift, forKey: "gift")
|
||||
encoder.encodeBool(isUpgrade, forKey: "isUpgrade")
|
||||
|
|
@ -867,6 +867,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
} else {
|
||||
encoder.encodeNil(forKey: "canCraftAt")
|
||||
}
|
||||
|
||||
encoder.encodeBool(isCrafted, forKey: "isCrafted")
|
||||
case let .paidMessagesRefunded(count, stars):
|
||||
encoder.encodeInt32(46, forKey: "_rawValue")
|
||||
encoder.encodeInt32(count, forKey: "count")
|
||||
|
|
@ -976,7 +978,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
|
|||
peerIds.append(toPeerId)
|
||||
}
|
||||
return peerIds
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _, _):
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, peerId, senderId, _, _, _, _, _, _, _, _, _):
|
||||
var peerIds: [PeerId] = []
|
||||
if let peerId {
|
||||
peerIds.append(peerId)
|
||||
|
|
|
|||
|
|
@ -460,12 +460,13 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
case rare
|
||||
case epic
|
||||
case legendary
|
||||
case uncommon
|
||||
|
||||
public var permilleValue: Int32 {
|
||||
switch self {
|
||||
case let .permille(value):
|
||||
return value
|
||||
case .rare, .epic, .legendary:
|
||||
case .rare, .epic, .legendary, .uncommon:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
|
@ -529,6 +530,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
return .epic
|
||||
case 3:
|
||||
return .legendary
|
||||
case 4:
|
||||
return .uncommon
|
||||
default:
|
||||
let permille = try container.decodeIfPresent(Int32.self, forKey: .rarity) ?? 0
|
||||
return .permille(permille)
|
||||
|
|
@ -583,6 +586,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
return .epic
|
||||
case 3:
|
||||
return .legendary
|
||||
case 4:
|
||||
return .uncommon
|
||||
default:
|
||||
let permille = decoder.decodeInt32ForKey(CodingKeys.rarity.rawValue, orElse: 0)
|
||||
return .permille(permille)
|
||||
|
|
@ -642,6 +647,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
try container.encode(Int32(2), forKey: .rarityType)
|
||||
case .legendary:
|
||||
try container.encode(Int32(3), forKey: .rarityType)
|
||||
case .uncommon:
|
||||
try container.encode(Int32(4), forKey: .rarityType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -688,6 +695,8 @@ public enum StarGift: Equatable, Codable, PostboxCoding {
|
|||
encoder.encodeInt32(2, forKey: CodingKeys.rarityType.rawValue)
|
||||
case .legendary:
|
||||
encoder.encodeInt32(3, forKey: CodingKeys.rarityType.rawValue)
|
||||
case .uncommon:
|
||||
encoder.encodeInt32(4, forKey: CodingKeys.rarityType.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1552,7 +1561,7 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift
|
|||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType, flags: forwardInfo.flags)
|
||||
}
|
||||
var media = currentMessage.media
|
||||
if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt) = action.action, case let .unique(uniqueGift) = gift {
|
||||
if let action = media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt, isCrafted) = action.action, case let .unique(uniqueGift) = gift {
|
||||
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
|
||||
media = [
|
||||
TelegramMediaAction(
|
||||
|
|
@ -1574,7 +1583,8 @@ func _internal_dropStarGiftOriginalDetails(account: Account, reference: StarGift
|
|||
dropOriginalDetailsStars: nil,
|
||||
assigned: assigned,
|
||||
fromOffer: fromOffer,
|
||||
canCraftAt: canCraftAt
|
||||
canCraftAt: canCraftAt,
|
||||
isCrafted: isCrafted
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
@ -1681,7 +1691,7 @@ func _internal_upgradeStarGift(account: Account, formId: Int64?, reference: Star
|
|||
let message = updateNewMessageData.message
|
||||
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
@ -3214,12 +3224,6 @@ private final class CraftGiftsContextImpl {
|
|||
return _internal_craftStarGift(account: self.account, references: references)
|
||||
}
|
||||
|
||||
func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||
self.gifts.insert(gift, at: 0)
|
||||
self.count = self.count + 1
|
||||
self.pushState()
|
||||
}
|
||||
|
||||
func removeGifts(references: [StarGiftReference]) {
|
||||
let referencesSet = Set(references)
|
||||
self.gifts.removeAll { gift in
|
||||
|
|
@ -3353,12 +3357,6 @@ public final class CraftGiftsContext {
|
|||
}
|
||||
}
|
||||
|
||||
public func addGift(gift: ProfileGiftsContext.State.StarGift) {
|
||||
self.impl.with { impl in
|
||||
impl.addGift(gift: gift)
|
||||
}
|
||||
}
|
||||
|
||||
public func removeGifts(references: [StarGiftReference]) {
|
||||
self.impl.with { impl in
|
||||
impl.removeGifts(references: references)
|
||||
|
|
@ -3423,7 +3421,7 @@ func _internal_craftStarGift(account: Account, references: [StarGiftReference])
|
|||
let message = updateNewMessageData.message
|
||||
if let message = StoreMessage(apiMessage: message, accountPeerId: account.peerId, peerIsForum: false) {
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, _, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
@ -3542,6 +3540,8 @@ extension StarGift.UniqueGift.Attribute {
|
|||
return .epic
|
||||
case .starGiftAttributeRarityLegendary:
|
||||
return .legendary
|
||||
case .starGiftAttributeRarityUncommon:
|
||||
return .uncommon
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1658,7 +1658,7 @@ func _internal_sendStarsPaymentForm(account: Account, formId: Int64, source: Bot
|
|||
case .giftCode, .stars, .starsGift, .starsChatSubscription, .starGift, .starGiftUpgrade, .starGiftTransfer, .premiumGift, .starGiftResale, .starGiftPrepaidUpgrade, .starGiftDropOriginalDetails, .starGiftAuctionBid:
|
||||
receiptMessageId = nil
|
||||
}
|
||||
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt) = action.action, case let .Id(messageId) = message.id {
|
||||
} else if case let .starGiftUnique(gift, _, _, savedToProfile, canExportDate, transferStars, isRefunded, _, peerId, _, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftAt, _) = action.action, case let .Id(messageId) = message.id {
|
||||
let reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
|
|||
|
|
@ -217,9 +217,10 @@ public final class InitialPresentationDataAndSettings {
|
|||
public let mediaInputSettings: MediaInputSettings
|
||||
public let mediaDisplaySettings: MediaDisplaySettings
|
||||
public let stickerSettings: StickerSettings
|
||||
public let chatSettings: ChatSettings
|
||||
public let experimentalUISettings: ExperimentalUISettings
|
||||
|
||||
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) {
|
||||
public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, chatSettings: ChatSettings, experimentalUISettings: ExperimentalUISettings) {
|
||||
self.presentationData = presentationData
|
||||
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
|
||||
self.autodownloadSettings = autodownloadSettings
|
||||
|
|
@ -228,6 +229,7 @@ public final class InitialPresentationDataAndSettings {
|
|||
self.mediaInputSettings = mediaInputSettings
|
||||
self.mediaDisplaySettings = mediaDisplaySettings
|
||||
self.stickerSettings = stickerSettings
|
||||
self.chatSettings = chatSettings
|
||||
self.experimentalUISettings = experimentalUISettings
|
||||
}
|
||||
}
|
||||
|
|
@ -245,6 +247,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
var experimentalUISettings: PreferencesEntry?
|
||||
var contactSynchronizationSettings: PreferencesEntry?
|
||||
var stickerSettings: PreferencesEntry?
|
||||
var chatSettings: PreferencesEntry?
|
||||
|
||||
init(
|
||||
localizationSettings: PreferencesEntry?,
|
||||
|
|
@ -257,7 +260,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
mediaDisplaySettings: PreferencesEntry?,
|
||||
experimentalUISettings: PreferencesEntry?,
|
||||
contactSynchronizationSettings: PreferencesEntry?,
|
||||
stickerSettings: PreferencesEntry?
|
||||
stickerSettings: PreferencesEntry?,
|
||||
chatSettings: PreferencesEntry?
|
||||
) {
|
||||
self.localizationSettings = localizationSettings
|
||||
self.presentationThemeSettings = presentationThemeSettings
|
||||
|
|
@ -270,6 +274,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
self.experimentalUISettings = experimentalUISettings
|
||||
self.contactSynchronizationSettings = contactSynchronizationSettings
|
||||
self.stickerSettings = stickerSettings
|
||||
self.chatSettings = chatSettings
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +290,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
let experimentalUISettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings)
|
||||
let contactSynchronizationSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.contactSynchronizationSettings)
|
||||
let stickerSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.stickerSettings)
|
||||
let chatSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.chatSettings)
|
||||
|
||||
return InternalData(
|
||||
localizationSettings: localizationSettings,
|
||||
|
|
@ -297,7 +303,8 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
mediaDisplaySettings: mediaDisplaySettings,
|
||||
experimentalUISettings: experimentalUISettings,
|
||||
contactSynchronizationSettings: contactSynchronizationSettings,
|
||||
stickerSettings: stickerSettings
|
||||
stickerSettings: stickerSettings,
|
||||
chatSettings: chatSettings
|
||||
)
|
||||
}
|
||||
|> deliverOn(Queue(name: "PresentationData-Load", qos: .userInteractive))
|
||||
|
|
@ -365,6 +372,13 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
stickerSettings = StickerSettings.defaultSettings
|
||||
}
|
||||
|
||||
let chatSettings: ChatSettings
|
||||
if let value = internalData.chatSettings?.get(ChatSettings.self) {
|
||||
chatSettings = value
|
||||
} else {
|
||||
chatSettings = ChatSettings.defaultSettings
|
||||
}
|
||||
|
||||
let experimentalUISettings: ExperimentalUISettings = internalData.experimentalUISettings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||
|
||||
let contactSettings: ContactSynchronizationSettings = internalData.contactSynchronizationSettings?.get(ContactSynchronizationSettings.self) ?? ContactSynchronizationSettings.defaultSettings
|
||||
|
|
@ -413,7 +427,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager<Te
|
|||
|
||||
let chatBubbleCorners = PresentationChatBubbleCorners(mainRadius: CGFloat(themeSettings.chatBubbleSettings.mainRadius), auxiliaryRadius: CGFloat(themeSettings.chatBubbleSettings.auxiliaryRadius), mergeBubbleCorners: themeSettings.chatBubbleSettings.mergeBubbleCorners)
|
||||
|
||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, experimentalUISettings: experimentalUISettings)
|
||||
return InitialPresentationDataAndSettings(presentationData: PresentationData(strings: stringsValue, theme: theme, autoNightModeTriggered: autoNightModeTriggered, chatWallpaper: effectiveChatWallpaper, chatFontSize: chatFontSize, chatBubbleCorners: chatBubbleCorners, listsFontSize: listsFontSize, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, nameSortOrder: nameSortOrder, reduceMotion: themeSettings.reduceMotion, largeEmoji: themeSettings.largeEmoji), automaticMediaDownloadSettings: automaticMediaDownloadSettings, autodownloadSettings: autodownloadSettings, callListSettings: callListSettings, inAppNotificationSettings: inAppNotificationSettings, mediaInputSettings: mediaInputSettings, mediaDisplaySettings: mediaDisplaySettings, stickerSettings: stickerSettings, chatSettings: chatSettings, experimentalUISettings: experimentalUISettings)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1262,7 +1262,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_Sent(authorName, starsPrice)._tuple, body: bodyAttributes, argumentAttributes: attributes)
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, fromOffer, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, _, isPrepaidUpgrade, peerId, senderId, _, resaleStars, _, _, _, assigned, fromOffer, _, isCrafted):
|
||||
if case let .unique(gift) = gift {
|
||||
if !forAdditionalServiceMessage && !"".isEmpty {
|
||||
attributedString = NSAttributedString(string: "\(gift.title) #\(presentationStringsFormattedNumber(gift.number, dateTimeFormat.groupingSeparator))", font: titleFont, textColor: primaryTextColor)
|
||||
|
|
@ -1338,7 +1338,11 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
|||
attributedString = addAttributesToStringWithRanges(strings.Notification_StarsGift_BoughtYou(giftTitle, starsString)._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes, 1: boldAttributes])
|
||||
}
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_TransferYou, font: titleFont, textColor: primaryTextColor)
|
||||
if isCrafted {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_Crafted, font: titleFont, textColor: primaryTextColor)
|
||||
} else {
|
||||
attributedString = NSAttributedString(string: strings.Notification_StarsGift_TransferYou, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
}
|
||||
} else if let senderId, let peer = message.peers[senderId] {
|
||||
if let peerId, let targetPeer = message.peers[peerId] {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
private let context: AccountContext
|
||||
private let subject: MessageActionUrlAuthResult
|
||||
|
||||
var peer: EnginePeer?
|
||||
|
||||
fileprivate var inProgress = false
|
||||
var allowWrite = true
|
||||
weak var controller: ViewController?
|
||||
|
|
@ -58,8 +60,17 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
init(context: AccountContext, subject: MessageActionUrlAuthResult) {
|
||||
self.context = context
|
||||
self.subject = subject
|
||||
|
||||
|
||||
super.init()
|
||||
|
||||
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] peer in
|
||||
guard let self, let peer else {
|
||||
return
|
||||
}
|
||||
self.peer = peer
|
||||
self.updated()
|
||||
})
|
||||
}
|
||||
|
||||
func displayPhoneNumberConfirmation(commit: @escaping (Bool) -> Void) {
|
||||
|
|
@ -78,7 +89,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
let alertController = textAlertController(
|
||||
context: self.context,
|
||||
title: "Phone Number",
|
||||
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**. Allow access?",
|
||||
text: "**\(domain)** wants to access your phone number **\(phoneNumber)**.\n\nAllow access?",
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: "Deny", action: {
|
||||
commit(false)
|
||||
|
|
@ -99,13 +110,12 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
|
||||
static var body: Body {
|
||||
let closeButton = Child(GlassBarButtonComponent.self)
|
||||
let peerButton = Child(AvatarComponent.self)
|
||||
let avatar = Child(AvatarComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let description = Child(MultilineTextComponent.self)
|
||||
|
||||
let clientSection = Child(ListSectionComponent.self)
|
||||
let optionsSection = Child(ListSectionComponent.self)
|
||||
|
||||
let cancelButton = Child(ButtonComponent.self)
|
||||
let doneButton = Child(ButtonComponent.self)
|
||||
|
||||
|
|
@ -147,6 +157,24 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
if let peer = state.peer {
|
||||
let peerButton = peerButton.update(
|
||||
component: AvatarComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
peer: peer
|
||||
),
|
||||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(peerButton
|
||||
.position(CGPoint(x: context.availableSize.width - 16.0 - peerButton.size.width / 2.0, y: 16.0 + peerButton.size.height / 2.0))
|
||||
)
|
||||
}
|
||||
|
||||
var contentHeight: CGFloat = 32.0
|
||||
let avatar = avatar.update(
|
||||
|
|
@ -320,7 +348,7 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
contentHeight += clientSection.size.height
|
||||
|
||||
if flags.contains(.requestWriteAccess) {
|
||||
contentHeight += 22.0
|
||||
contentHeight += 38.0
|
||||
|
||||
var optionsSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
optionsSectionItems.append(AnyComponentWithIdentity(id: "allowWrite", component: AnyComponent(ListActionItemComponent(
|
||||
|
|
@ -441,10 +469,6 @@ private final class AuthConfirmationSheetContent: CombinedComponent {
|
|||
contentHeight += doneButton.size.height
|
||||
contentHeight += buttonInsets.bottom
|
||||
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: 16.0 + closeButton.size.width / 2.0, y: 16.0 + closeButton.size.height / 2.0))
|
||||
)
|
||||
|
||||
return CGSize(width: context.availableSize.width, height: contentHeight)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -466,6 +466,9 @@ public final class ButtonComponent: Component {
|
|||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.button.isExclusiveTouch = true
|
||||
self.layer.rasterizationScale = UIScreenScale
|
||||
|
||||
self.addSubview(self.containerView)
|
||||
self.addSubview(self.button)
|
||||
|
||||
|
|
@ -477,12 +480,24 @@ public final class ButtonComponent: Component {
|
|||
case .glass:
|
||||
let transition = ComponentTransition(animation: .curve(duration: highlighted ? 0.25 : 0.35, curve: .spring))
|
||||
if highlighted {
|
||||
self.layer.shouldRasterize = true
|
||||
|
||||
let highlightedColor = component.background.color.withMultiplied(hue: 1.0, saturation: 0.77, brightness: 1.01)
|
||||
transition.setBackgroundColor(view: self.containerView, color: highlightedColor)
|
||||
transition.setScale(view: self.containerView, scale: 1.05)
|
||||
transition.setScale(view: self.containerView, scale: 1.05, completion: { finished in
|
||||
if finished {
|
||||
self.layer.shouldRasterize = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.layer.shouldRasterize = true
|
||||
|
||||
transition.setBackgroundColor(view: self.containerView, color: component.background.color)
|
||||
transition.setScale(view: self.containerView, scale: 1.0)
|
||||
transition.setScale(view: self.containerView, scale: 1.0, completion: { finished in
|
||||
if finished {
|
||||
self.layer.shouldRasterize = false
|
||||
}
|
||||
})
|
||||
}
|
||||
case .legacy:
|
||||
if highlighted {
|
||||
|
|
@ -598,6 +613,7 @@ public final class ButtonComponent: Component {
|
|||
if contentView.superview == nil {
|
||||
contentTransition = .immediate
|
||||
animateIn = true
|
||||
contentView.layer.rasterizationScale = UIScreenScale
|
||||
contentView.isUserInteractionEnabled = false
|
||||
contentContainerView.addSubview(contentView)
|
||||
|
||||
|
|
@ -609,7 +625,10 @@ public final class ButtonComponent: Component {
|
|||
contentTransition.setAlpha(view: contentView, alpha: contentAlpha)
|
||||
|
||||
if animateIn && previousContentItem != nil && !transition.animation.isImmediate {
|
||||
contentView.layer.animateScale(from: 0.4, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
contentView.layer.shouldRasterize = true
|
||||
contentView.layer.animateScale(from: 0.4, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in
|
||||
contentView.layer.shouldRasterize = false
|
||||
})
|
||||
contentView.layer.animateAlpha(from: 0.0, to: contentAlpha, duration: 0.1)
|
||||
contentView.layer.animatePosition(from: CGPoint(x: 0.0, y: -size.height * 0.15), to: CGPoint(), duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
|
@ -617,6 +636,7 @@ public final class ButtonComponent: Component {
|
|||
|
||||
if let previousContentItem, let previousContentView = previousContentItem.view.view {
|
||||
if !transition.animation.isImmediate {
|
||||
previousContentView.layer.shouldRasterize = true
|
||||
previousContentView.layer.animateScale(from: 1.0, to: 0.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||
previousContentView.layer.animateAlpha(from: contentAlpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
||||
previousContentView?.removeFromSuperview()
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
@property (nonatomic, copy) bool (^ _Nullable shouldPaste)();
|
||||
@property (nonatomic, copy) bool (^ _Nullable shouldRespondToAction)(SEL _Nullable);
|
||||
@property (nonatomic, copy) ChatInputTextViewImplTargetForAction * _Nullable (^ _Nullable targetForAction)(SEL _Nullable);
|
||||
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)();
|
||||
@property (nonatomic, copy) bool (^ _Nullable shouldReturn)(UIKeyModifierFlags);
|
||||
@property (nonatomic, copy) void (^ _Nullable backspaceWhileEmpty)();
|
||||
@property (nonatomic, copy) void (^ _Nullable dropAutocorrectioniOS16)();
|
||||
|
||||
|
|
|
|||
|
|
@ -136,15 +136,22 @@
|
|||
}
|
||||
|
||||
- (NSArray *)keyCommands {
|
||||
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handlePlainReturn:)];
|
||||
UIKeyCommand *plainReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:kNilOptions action:@selector(handleReturn:)];
|
||||
UIKeyCommand *cmdReturn = [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:UIKeyModifierCommand action:@selector(handleReturn:)];
|
||||
return @[
|
||||
plainReturn
|
||||
plainReturn,
|
||||
cmdReturn
|
||||
];
|
||||
}
|
||||
|
||||
- (void)handlePlainReturn:(id)__unused sender {
|
||||
- (void)handleReturn:(UIKeyCommand *)__unused sender {
|
||||
UIKeyModifierFlags modifierFlags = 0;
|
||||
if ([sender isKindOfClass:[UIKeyCommand class]]) {
|
||||
modifierFlags = sender.modifierFlags;
|
||||
}
|
||||
|
||||
if (_shouldReturn) {
|
||||
_shouldReturn();
|
||||
_shouldReturn(modifierFlags);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import TextNodeWithEntities
|
|||
|
||||
public protocol ChatInputTextNodeDelegate: AnyObject {
|
||||
func chatInputTextNodeDidUpdateText()
|
||||
func chatInputTextNodeShouldReturn() -> Bool
|
||||
func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool
|
||||
func chatInputTextNodeDidChangeSelection(dueToEditing: Bool)
|
||||
func chatInputTextNodeDidBeginEditing()
|
||||
func chatInputTextNodeDidFinishEditing()
|
||||
|
|
@ -1180,11 +1180,11 @@ public final class ChatInputTextView: ChatInputTextViewImpl, UITextViewDelegate,
|
|||
}
|
||||
return self.customDelegate?.chatInputTextNodeShouldPaste() ?? true
|
||||
}
|
||||
self.shouldReturn = { [weak self] in
|
||||
self.shouldReturn = { [weak self] modifierFlags in
|
||||
guard let self else {
|
||||
return true
|
||||
}
|
||||
return self.customDelegate?.chatInputTextNodeShouldReturn() ?? true
|
||||
return self.customDelegate?.chatInputTextNodeShouldReturn(modifierFlags: modifierFlags) ?? true
|
||||
}
|
||||
self.backspaceWhileEmpty = { [weak self] in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
switch action.action {
|
||||
case let .starGift(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .starGiftUnique(gift, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
releasedBy = gift.releasedBy
|
||||
default:
|
||||
break
|
||||
|
|
@ -690,7 +690,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
|
|||
}
|
||||
}
|
||||
}
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, fromOffer, _):
|
||||
case let .starGiftUnique(gift, isUpgrade, _, _, _, _, isRefunded, _, _, _, _, _, _, _, _, _, fromOffer, _, _):
|
||||
if case let .unique(uniqueGift) = gift {
|
||||
isStarGift = true
|
||||
|
||||
|
|
|
|||
|
|
@ -2520,7 +2520,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
case let .audio(recorder, isLocked):
|
||||
let hadAudioRecorder = self.mediaActionButtons.micButton.audioRecorder != nil
|
||||
if !hadAudioRecorder, isLocked {
|
||||
self.mediaActionButtons.micButton.lock()
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.mediaActionButtons.micButton.lock()
|
||||
}
|
||||
}
|
||||
self.mediaActionButtons.micButton.audioRecorder = recorder
|
||||
audioRecordingTimeNode.audioRecorder = recorder
|
||||
|
|
@ -4528,15 +4530,29 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
|
|||
}
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
var shouldSendMessage = false
|
||||
if self.sendActionButtons.sendButton.supernode != nil && !self.sendActionButtons.sendButton.isHidden && !self.sendActionButtons.sendContainerNode.alpha.isZero {
|
||||
self.sendButtonPressed()
|
||||
if let context = self.context, context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
|
||||
if modifierFlags.contains(.command) {
|
||||
shouldSendMessage = true
|
||||
}
|
||||
} else {
|
||||
if modifierFlags.isEmpty {
|
||||
shouldSendMessage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
if shouldSendMessage {
|
||||
self.sendButtonPressed()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@objc public func editableTextNodeShouldReturn(_ editableTextNode: ASEditableTextNode) -> Bool {
|
||||
return self.chatInputTextNodeShouldReturn()
|
||||
return self.chatInputTextNodeShouldReturn(modifierFlags: [])
|
||||
}
|
||||
|
||||
private func applyUpdateSendButtonIcon() {
|
||||
|
|
|
|||
|
|
@ -674,6 +674,7 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
context: reactionItems.context,
|
||||
animationCache: reactionItems.animationCache,
|
||||
presentationData: presentationData,
|
||||
//style: .glass(isTinted: false),
|
||||
items: reactionItems.reactionItems,
|
||||
selectedItems: reactionItems.selectedReactionItems,
|
||||
title: reactionItems.reactionsTitle,
|
||||
|
|
@ -1058,12 +1059,17 @@ final class ContextControllerExtractedPresentationNode: ASDisplayNode, ContextCo
|
|||
|
||||
transition.updateFrame(node: self.contentRectDebugNode, frame: contentRect, beginWithCurrentState: true)
|
||||
|
||||
var totalActionsHeight: CGFloat = actionsSize.height
|
||||
if additionalActionsSize.height != 0.0 {
|
||||
totalActionsHeight += 10.0 + additionalActionsSize.height
|
||||
}
|
||||
|
||||
var actionsFrame: CGRect
|
||||
if let contextExtractableContainer {
|
||||
let _ = contextExtractableContainer
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY), size: actionsSize)
|
||||
} else if case let .reference(source) = self.source, let actionsPosition = source.transitionInfo()?.actionsPosition, case .top = actionsPosition {
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY - contentActionsSpacing - actionsSize.height), size: actionsSize)
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.minY - contentActionsSpacing - totalActionsHeight), size: actionsSize)
|
||||
} else {
|
||||
actionsFrame = CGRect(origin: CGPoint(x: actionsSideInset, y: contentRect.maxY + contentActionsSpacing), size: actionsSize)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -591,11 +591,11 @@ public final class VariableBlurEffect {
|
|||
}
|
||||
|
||||
public let position: Position
|
||||
public let extendsInwards: Bool
|
||||
public let inwardsExtension: CGFloat?
|
||||
|
||||
public init(position: Position, extendsInwards: Bool) {
|
||||
public init(position: Position, inwardsExtension: CGFloat?) {
|
||||
self.position = position
|
||||
self.extendsInwards = extendsInwards
|
||||
self.inwardsExtension = inwardsExtension
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -664,7 +664,7 @@ public final class VariableBlurEffect {
|
|||
let mainEffectFrame: CGRect
|
||||
let additionalEffectFrame: CGRect
|
||||
|
||||
if params.placement.extendsInwards {
|
||||
if params.placement.inwardsExtension != nil {
|
||||
mainEffectFrame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))
|
||||
additionalEffectFrame = CGRect()
|
||||
} else if params.placement.position == .bottom {
|
||||
|
|
@ -698,8 +698,8 @@ public final class VariableBlurEffect {
|
|||
let isHeightUpdated = gradient.height != self.params?.gradient.height || size.height != self.params?.size.height
|
||||
|
||||
if isGradientUpdated {
|
||||
if params.placement.extendsInwards {
|
||||
let baseHeight = max(1.0, params.gradient.height)
|
||||
if let inwardsExtension = params.placement.inwardsExtension {
|
||||
let baseHeight = max(1.0, params.gradient.height + inwardsExtension)
|
||||
let resizingInverted = params.placement.position != .bottom
|
||||
self.gradientImage = generateImage(CGSize(width: 1.0, height: baseHeight), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
|
|
@ -712,13 +712,17 @@ public final class VariableBlurEffect {
|
|||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
if params.placement.position == .bottom {
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: size.height), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: max(0.0, size.height - inwardsExtension)), end: CGPoint(x: 0.0, y: 0.0), options: CGGradientDrawingOptions())
|
||||
if inwardsExtension > 0.0 {
|
||||
context.setFillColor(UIColor.white.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height - inwardsExtension), size: CGSize(width: size.width, height: inwardsExtension)))
|
||||
}
|
||||
} else {
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
}
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: resizingInverted ? baseHeight : 0.0, left: 0.0, bottom: resizingInverted ? 0.0 : baseHeight, right: 0.0), resizingMode: .stretch)
|
||||
} else {
|
||||
self.gradientImage = EdgeEffectView.generateEdgeGradient(baseHeight: max(1.0, params.gradient.height), isInverted: params.placement.position == .bottom, extendsInwards: params.placement.extendsInwards)
|
||||
self.gradientImage = EdgeEffectView.generateEdgeGradient(baseHeight: max(1.0, params.gradient.height), isInverted: params.placement.position == .bottom, extendsInwards: params.placement.inwardsExtension != nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -775,6 +779,62 @@ public final class VariableBlurView: UIView {
|
|||
}
|
||||
|
||||
public func update(size: CGSize, constantHeight: CGFloat, isInverted: Bool, gradient: VariableBlurEffect.Gradient, transition: ContainedViewLayoutTransition) {
|
||||
self.effect?.update(size: size, constantHeight: constantHeight, placement: VariableBlurEffect.Placement(position: isInverted ? .bottom : .top, extendsInwards: false), gradient: gradient, transition: transition)
|
||||
self.effect?.update(size: size, constantHeight: constantHeight, placement: VariableBlurEffect.Placement(position: isInverted ? .bottom : .top, inwardsExtension: nil), gradient: gradient, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class EdgeMaskView: UIView {
|
||||
private struct MaskParams: Equatable {
|
||||
let gradientHeight: CGFloat
|
||||
let extensionHeight: CGFloat
|
||||
|
||||
init(gradientHeight: CGFloat, extensionHeight: CGFloat) {
|
||||
self.gradientHeight = gradientHeight
|
||||
self.extensionHeight = extensionHeight
|
||||
}
|
||||
}
|
||||
|
||||
private let imageView: UIImageView
|
||||
|
||||
private var maskParams: MaskParams?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
self.imageView = UIImageView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.imageView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
public func update(size: CGSize, color: UIColor, gradientHeight: CGFloat, extensionHeight: CGFloat, transition: ComponentTransition) {
|
||||
let maskParams = MaskParams(gradientHeight: gradientHeight, extensionHeight: extensionHeight)
|
||||
if maskParams != self.maskParams {
|
||||
self.maskParams = maskParams
|
||||
|
||||
let baseHeight = max(1.0, maskParams.gradientHeight + maskParams.extensionHeight)
|
||||
let resizingInverted = !"".isEmpty
|
||||
self.imageView.image = generateImage(CGSize(width: 1.0, height: baseHeight), opaque: false, rotatedContext: { size, context in
|
||||
let bounds = CGRect(origin: CGPoint(), size: size)
|
||||
context.clear(bounds)
|
||||
|
||||
let gradientColors = [UIColor.white.withAlphaComponent(1.0).cgColor, UIColor.white.withAlphaComponent(0.0).cgColor] as CFArray
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: gradientColors, locations: &locations)!
|
||||
|
||||
if "".isEmpty {
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height - maskParams.extensionHeight), options: CGGradientDrawingOptions())
|
||||
} else {
|
||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
}
|
||||
})?.resizableImage(withCapInsets: UIEdgeInsets(top: resizingInverted ? baseHeight : 0.0, left: 0.0, bottom: resizingInverted ? 0.0 : baseHeight, right: 0.0), resizingMode: .stretch).withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
self.imageView.tintColor = color
|
||||
transition.setFrame(view: self.imageView, frame: CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import UIKitRuntimeUtils
|
|||
public final class GiftCompositionComponent: Component {
|
||||
public class ExternalState {
|
||||
public fileprivate(set) var previewPatternColor: UIColor?
|
||||
public fileprivate(set) var backgroundColor: UIColor?
|
||||
public fileprivate(set) var previewModel: StarGift.UniqueGift.Attribute?
|
||||
public fileprivate(set) var previewBackdrop: StarGift.UniqueGift.Attribute?
|
||||
public fileprivate(set) var previewSymbol: StarGift.UniqueGift.Attribute?
|
||||
|
|
@ -766,6 +767,7 @@ public final class GiftCompositionComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
component.externalState?.backgroundColor = backgroundColor
|
||||
component.externalState?.previewPatternColor = secondBackgroundColor
|
||||
|
||||
var animateBackdropSwipe = false
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ swift_library(
|
|||
"//submodules/TelegramUI/Components/SpaceWarpView",
|
||||
"//submodules/ConfettiEffect",
|
||||
"//submodules/TelegramNotices",
|
||||
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ final class CraftTableComponent: Component {
|
|||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func setupFailAnimation() {
|
||||
func setupFailureAnimation() {
|
||||
guard !self.didSetupFinishAnimation else {
|
||||
return
|
||||
}
|
||||
|
|
@ -127,41 +127,29 @@ final class CraftTableComponent: Component {
|
|||
}
|
||||
for i in 0 ..< min(2, availableStickers.count) {
|
||||
if let sticker = availableStickers[i].view {
|
||||
self.animationView.setSticker(sticker, face: 3 - i, mirror: isUpsideDown)
|
||||
let face: Int
|
||||
if isUpsideDown {
|
||||
face = i + 1
|
||||
} else {
|
||||
face = 3 - i
|
||||
}
|
||||
self.animationView.setSticker(sticker, face: face, mirror: isUpsideDown)
|
||||
}
|
||||
}
|
||||
|
||||
self.state?.updated()
|
||||
self.flipFaces = isUpsideDown
|
||||
|
||||
Queue.mainQueue().after(0.3, {
|
||||
self.failWillFinish = true
|
||||
self.component?.willFinish(false)
|
||||
})
|
||||
|
||||
if let failOverlayView = self.failOverlay.view as? LottieComponent.View {
|
||||
failOverlayView.isHidden = false
|
||||
failOverlayView.onFrameUpdate = { [weak self] frameIndex in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if frameIndex >= 5 && !self.failDidStartCrossAnimation {
|
||||
self.failDidStartCrossAnimation = true
|
||||
self.craftFailPlayOnce.invoke(Void())
|
||||
}
|
||||
if frameIndex >= 65 && !self.failDidBringToFront {
|
||||
self.failDidBringToFront = true
|
||||
failOverlayView.superview?.bringSubviewToFront(failOverlayView)
|
||||
|
||||
self.animationView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
|
||||
}
|
||||
if frameIndex >= 75 && !self.failWillFinish {
|
||||
self.failWillFinish = true
|
||||
self.component?.willFinish(false)
|
||||
}
|
||||
if frameIndex >= 82 && !self.failDidFinish {
|
||||
self.failDidFinish = true
|
||||
|
||||
failOverlayView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.component?.finished(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.craftFailOverlayPlayOnce.invoke(Void())
|
||||
Queue.mainQueue().after(0.5, {
|
||||
self.failDidFinish = true
|
||||
self.component?.finished(nil)
|
||||
})
|
||||
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.4))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +159,8 @@ final class CraftTableComponent: Component {
|
|||
}
|
||||
self.didSetupFinishAnimation = true
|
||||
|
||||
self.animationView.isSuccess = true
|
||||
|
||||
self.animationView.onFinishApproach = { [weak self] isUpsideDown in
|
||||
guard let self else {
|
||||
return
|
||||
|
|
@ -270,10 +260,10 @@ final class CraftTableComponent: Component {
|
|||
if index == 0 {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "background", component: AnyComponent(
|
||||
RoundedRectangle(color: component.buttonColor, cornerRadius: 28.0)
|
||||
FilledRoundedRectangleComponent(color: component.buttonColor, cornerRadius: .value(28.0), smoothCorners: true)
|
||||
))
|
||||
)
|
||||
if !component.isCrafting {
|
||||
if !component.isCrafting || self.isFailed {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "glass", component: AnyComponent(
|
||||
GlassBackgroundComponent(size: CGSize(width: cubeSide, height: cubeSide), cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)))
|
||||
|
|
@ -282,11 +272,20 @@ final class CraftTableComponent: Component {
|
|||
}
|
||||
if self.isFailed {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "fail", component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "CraftFail"),
|
||||
size: CGSize(width: 96.0, height: 96.0),
|
||||
playOnce: self.craftFailPlayOnce
|
||||
AnyComponentWithIdentity(id: "faildial", component: AnyComponent(
|
||||
DialIndicatorComponent(
|
||||
content: AnyComponentWithIdentity(id: "gift", component: AnyComponent(BundleIconComponent(name: "Premium/GiftCrash", tintColor: .white))),
|
||||
backgroundColor: .white.withAlphaComponent(0.1),
|
||||
foregroundColor: .white,
|
||||
diameter: 84.0,
|
||||
contentSize: CGSize(width: 44.0, height: 44.0),
|
||||
lineWidth: 5.0,
|
||||
fontSize: 18.0,
|
||||
progress: 0.0,
|
||||
value: component.gifts.count,
|
||||
suffix: "",
|
||||
isVisible: true,
|
||||
isFlipped: self.flipFaces
|
||||
)
|
||||
))
|
||||
)
|
||||
|
|
@ -300,7 +299,9 @@ final class CraftTableComponent: Component {
|
|||
diameter: 84.0,
|
||||
lineWidth: 5.0,
|
||||
fontSize: 18.0,
|
||||
percentage: permilleValue / 10,
|
||||
progress: CGFloat(permilleValue / 10 / 100),
|
||||
value: permilleValue / 10,
|
||||
suffix: "%",
|
||||
isVisible: !component.isCrafting
|
||||
)
|
||||
))
|
||||
|
|
@ -318,7 +319,7 @@ final class CraftTableComponent: Component {
|
|||
} else {
|
||||
faceItems.append(
|
||||
AnyComponentWithIdentity(id: "background", component: AnyComponent(
|
||||
RoundedRectangle(color: component.buttonColor, cornerRadius: 28.0)
|
||||
FilledRoundedRectangleComponent(color: component.buttonColor, cornerRadius: .value(28.0), smoothCorners: true)
|
||||
))
|
||||
)
|
||||
faceItems.append(
|
||||
|
|
@ -412,35 +413,36 @@ final class CraftTableComponent: Component {
|
|||
self.setupSuccessAnimation(uniqueGift)
|
||||
}
|
||||
case .fail:
|
||||
self.setupFailAnimation()
|
||||
self.setupFailureAnimation()
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if self.isFailed {
|
||||
let failOverlaySize = self.failOverlay.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "CraftFailOverlay"),
|
||||
size: CGSize(width: availableSize.width, height: availableSize.width),
|
||||
playOnce: self.craftFailOverlayPlayOnce
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: availableSize.width)
|
||||
)
|
||||
let failOverlayFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - failOverlaySize.width) / 2.0), y: floor((availableSize.height - failOverlaySize.height) / 2.0)), size: failOverlaySize)
|
||||
if let failOverlayView = self.failOverlay.view {
|
||||
if failOverlayView.superview == nil {
|
||||
failOverlayView.isHidden = true
|
||||
self.insertSubview(failOverlayView, belowSubview: self.animationView)
|
||||
}
|
||||
failOverlayView.frame = failOverlayFrame
|
||||
}
|
||||
}
|
||||
// if self.isFailed {
|
||||
// let failOverlaySize = self.failOverlay.update(
|
||||
// transition: .immediate,
|
||||
// component: AnyComponent(
|
||||
// LottieComponent(
|
||||
// content: LottieComponent.AppBundleContent(name: "CraftFailOverlay"),
|
||||
// size: CGSize(width: availableSize.width, height: availableSize.width),
|
||||
// playOnce: self.craftFailOverlayPlayOnce
|
||||
// )
|
||||
// ),
|
||||
// environment: {},
|
||||
// containerSize: CGSize(width: availableSize.width, height: availableSize.width)
|
||||
// )
|
||||
// let failOverlayFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - failOverlaySize.width) / 2.0), y: floor((availableSize.height - failOverlaySize.height) / 2.0)), size: failOverlaySize)
|
||||
// if let failOverlayView = self.failOverlay.view {
|
||||
// if failOverlayView.superview == nil {
|
||||
// failOverlayView.isHidden = true
|
||||
// self.insertSubview(failOverlayView, belowSubview: self.animationView)
|
||||
// }
|
||||
// failOverlayView.frame = failOverlayFrame
|
||||
// }
|
||||
// }
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
|
@ -544,9 +546,9 @@ final class GiftSlotComponent: Component {
|
|||
self.state = state
|
||||
|
||||
let backgroundFrame = CGRect(origin: .zero, size: availableSize).insetBy(dx: 1.0, dy: 1.0)
|
||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), transition: .immediate)
|
||||
self.backgroundView.update(size: backgroundFrame.size, cornerRadius: 28.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), isInteractive: true, transition: .immediate)
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
if component.gift == nil && component.isCrafting {
|
||||
if component.gift == nil && component.isCrafting && previousComponent?.isCrafting == false {
|
||||
transition.setBlur(layer: self.backgroundView.layer, radius: 10.0)
|
||||
self.backgroundView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false)
|
||||
transition.setBlur(layer: self.addIcon.layer, radius: 10.0)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ final class CubeAnimationView: UIView {
|
|||
private var finishDelayTimerY: Timer?
|
||||
private var cubeScale: Float = 1.0
|
||||
private var hasFiredFinishApproach = false
|
||||
|
||||
var isSuccess = false
|
||||
|
||||
var onFinishApproach: ((Bool) -> Void)?
|
||||
|
||||
|
|
@ -485,7 +487,7 @@ final class CubeAnimationView: UIView {
|
|||
let duration: TimeInterval = 0.2
|
||||
let startQuad = Quad(rect: sticker.frame)
|
||||
let animationView: UIView
|
||||
if let snapshot = sticker.snapshotView(afterScreenUpdates: true) {
|
||||
if let snapshot = sticker.snapshotView(afterScreenUpdates: false) {
|
||||
self.warpSnapshot?.removeFromSuperview()
|
||||
self.warpSnapshot = snapshot
|
||||
|
||||
|
|
@ -606,7 +608,7 @@ final class CubeAnimationView: UIView {
|
|||
}
|
||||
let stickerIndex = indices[index]
|
||||
if self.stickers.indices.contains(stickerIndex) {
|
||||
let isLast = stickerIndex == indices.count - 1
|
||||
let isLast = index == indices.count - 1
|
||||
self.launchStickerView(self.stickers[stickerIndex], emphasized: isLast, willFinish: isLast)
|
||||
}
|
||||
self.scheduleStickerSequence(from: index + 1, indices: indices)
|
||||
|
|
@ -697,10 +699,12 @@ final class CubeAnimationView: UIView {
|
|||
let upsideDown = abs(shortestAngleDelta(from: self.rotation.x, to: Float.pi)) < (Float.pi / 2)
|
||||
self.onFinishApproach?(upsideDown)
|
||||
}
|
||||
if absRemaining <= self.finishSuccessScaleTriggerAngle {
|
||||
if self.isSuccess, absRemaining <= self.finishSuccessScaleTriggerAngle {
|
||||
let raw = (self.finishSuccessScaleTriggerAngle - absRemaining) / self.finishSuccessScaleTriggerAngle
|
||||
let eased = raw * raw * (3 - 2 * raw)
|
||||
self.cubeScale = 1.0 + (self.finishSuccessScale - 1.0) * eased
|
||||
} else if !self.isSuccess {
|
||||
self.cubeScale = 1.0
|
||||
}
|
||||
if abs(remaining) < 0.0008 && abs(self.angularVelocity.y) < 0.0015 {
|
||||
self.finishRotationY = self.finishTargetYUnwrapped
|
||||
|
|
|
|||
|
|
@ -13,29 +13,41 @@ final class DialIndicatorComponent: Component {
|
|||
let backgroundColor: UIColor
|
||||
let foregroundColor: UIColor
|
||||
let diameter: CGFloat
|
||||
let contentSize: CGSize?
|
||||
let lineWidth: CGFloat
|
||||
let fontSize: CGFloat
|
||||
let percentage: Int
|
||||
let progress: CGFloat
|
||||
let value: Int
|
||||
let suffix: String
|
||||
let isVisible: Bool
|
||||
let isFlipped: Bool
|
||||
|
||||
public init(
|
||||
content: AnyComponentWithIdentity<Empty>,
|
||||
backgroundColor: UIColor,
|
||||
foregroundColor: UIColor,
|
||||
diameter: CGFloat,
|
||||
contentSize: CGSize? = nil,
|
||||
lineWidth: CGFloat,
|
||||
fontSize: CGFloat,
|
||||
percentage: Int,
|
||||
isVisible: Bool = true
|
||||
progress: CGFloat,
|
||||
value: Int,
|
||||
suffix: String,
|
||||
isVisible: Bool = true,
|
||||
isFlipped: Bool = false,
|
||||
) {
|
||||
self.content = content
|
||||
self.backgroundColor = backgroundColor
|
||||
self.foregroundColor = foregroundColor
|
||||
self.diameter = diameter
|
||||
self.contentSize = contentSize
|
||||
self.lineWidth = lineWidth
|
||||
self.fontSize = fontSize
|
||||
self.percentage = percentage
|
||||
self.progress = progress
|
||||
self.value = value
|
||||
self.suffix = suffix
|
||||
self.isVisible = isVisible
|
||||
self.isFlipped = isFlipped
|
||||
}
|
||||
|
||||
public static func ==(lhs: DialIndicatorComponent, rhs: DialIndicatorComponent) -> Bool {
|
||||
|
|
@ -51,18 +63,30 @@ final class DialIndicatorComponent: Component {
|
|||
if lhs.diameter != rhs.diameter {
|
||||
return false
|
||||
}
|
||||
if lhs.contentSize != rhs.contentSize {
|
||||
return false
|
||||
}
|
||||
if lhs.lineWidth != rhs.lineWidth {
|
||||
return false
|
||||
}
|
||||
if lhs.fontSize != rhs.fontSize {
|
||||
return false
|
||||
}
|
||||
if lhs.percentage != rhs.percentage {
|
||||
if lhs.progress != rhs.progress {
|
||||
return false
|
||||
}
|
||||
if lhs.value != rhs.value {
|
||||
return false
|
||||
}
|
||||
if lhs.suffix != rhs.suffix {
|
||||
return false
|
||||
}
|
||||
if lhs.isVisible != rhs.isVisible {
|
||||
return false
|
||||
}
|
||||
if lhs.isFlipped != rhs.isFlipped {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -82,7 +106,7 @@ final class DialIndicatorComponent: Component {
|
|||
|
||||
self.backgroundLayer.lineCap = .round
|
||||
self.foregroundLayer.lineCap = .round
|
||||
|
||||
|
||||
self.addSubview(self.containerView)
|
||||
|
||||
self.containerView.layer.addSublayer(self.backgroundLayer)
|
||||
|
|
@ -119,7 +143,7 @@ final class DialIndicatorComponent: Component {
|
|||
self.foregroundLayer.path = CGPath(ellipseIn: pathFrame, transform: nil)
|
||||
self.foregroundLayer.transform = CATransform3DMakeRotation(.pi / 2.0, 0.0, 0.0, 1.0)
|
||||
self.foregroundLayer.strokeStart = strokeStart
|
||||
transition.setShapeLayerStrokeEnd(layer: self.foregroundLayer, strokeEnd: strokeStart + (strokeEnd - strokeStart) * (CGFloat(component.percentage) / 100.0))
|
||||
transition.setShapeLayerStrokeEnd(layer: self.foregroundLayer, strokeEnd: strokeStart + (strokeEnd - strokeStart) * component.progress)
|
||||
self.foregroundLayer.frame = CGRect(origin: .zero, size: pathSize)
|
||||
|
||||
if previousComponent?.content.id != component.content.id {
|
||||
|
|
@ -136,7 +160,8 @@ final class DialIndicatorComponent: Component {
|
|||
self.content = ComponentView()
|
||||
}
|
||||
|
||||
let contentFrame = CGRect(origin: CGPoint(x: 8.0, y: 8.0), size: CGSize(width: component.diameter - 16.0, height: component.diameter - 16.0))
|
||||
let contentSize = component.contentSize ?? CGSize(width: component.diameter - 16.0, height: component.diameter - 16.0)
|
||||
let contentFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((pathSize.width - contentSize.width) / 2.0), y: floorToScreenPixels((pathSize.height - contentSize.height) / 2.0)), size: contentSize)
|
||||
let _ = self.content.update(
|
||||
transition: .immediate,
|
||||
component: component.content.component,
|
||||
|
|
@ -154,10 +179,12 @@ final class DialIndicatorComponent: Component {
|
|||
contentView.frame = contentFrame
|
||||
}
|
||||
|
||||
let labelItems: [AnimatedTextComponent.Item] = [
|
||||
AnimatedTextComponent.Item(id: "percent", content: .number(component.percentage, minDigits: 1)),
|
||||
AnimatedTextComponent.Item(id: "suffix", content: .text("%"))
|
||||
var labelItems: [AnimatedTextComponent.Item] = [
|
||||
AnimatedTextComponent.Item(id: "percent", content: .number(component.value, minDigits: 1))
|
||||
]
|
||||
if !component.suffix.isEmpty {
|
||||
labelItems.append(AnimatedTextComponent.Item(id: "suffix", content: .text(component.suffix)))
|
||||
}
|
||||
|
||||
let labelSize = self.label.update(
|
||||
transition: transition,
|
||||
|
|
@ -181,6 +208,8 @@ final class DialIndicatorComponent: Component {
|
|||
transition.setAlpha(view: self.containerView, alpha: component.isVisible ? 1.0 : 0.0)
|
||||
transition.setBlur(layer: self.containerView.layer, radius: component.isVisible ? 0.0 : 10.0)
|
||||
|
||||
self.containerView.transform = CGAffineTransform(rotationAngle: component.isFlipped ? .pi : 0.0)
|
||||
|
||||
self.containerView.frame = CGRect(origin: .zero, size: pathSize)
|
||||
|
||||
return pathSize
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,7 @@ import ResizableSheetComponent
|
|||
import TooltipUI
|
||||
import GlassBarButtonComponent
|
||||
import ConfettiEffect
|
||||
import GiftLoadingShimmerView
|
||||
|
||||
final class SelectGiftPageContent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -74,6 +75,7 @@ final class SelectGiftPageContent: Component {
|
|||
private let myGiftsTitle = ComponentView<Empty>()
|
||||
private var gifts: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
private let myGiftsPlaceholder = ComponentView<Empty>()
|
||||
private let loadingView = GiftLoadingShimmerView()
|
||||
|
||||
private let storeGiftsTitle = ComponentView<Empty>()
|
||||
private let storeGifts = ComponentView<Empty>()
|
||||
|
|
@ -94,6 +96,8 @@ final class SelectGiftPageContent: Component {
|
|||
|
||||
self.layer.cornerRadius = 40.0
|
||||
self.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
|
||||
self.addSubview(self.loadingView)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -204,10 +208,25 @@ final class SelectGiftPageContent: Component {
|
|||
}
|
||||
let itemWidth = (availableSize.width - itemSideInset * 2.0 - itemSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
|
||||
let itemSize = CGSize(width: itemWidth, height: itemWidth)
|
||||
var itemFrame = CGRect(origin: CGPoint(x: itemSideInset, y: contentHeight), size: itemSize)
|
||||
|
||||
|
||||
var isLoading = false
|
||||
if self.availableGifts.isEmpty, case .loading = (self.craftState?.dataState ?? .loading) {
|
||||
isLoading = true
|
||||
}
|
||||
let loadingTransition: ComponentTransition = .easeInOut(duration: 0.25)
|
||||
let loadingSize = CGSize(width: availableSize.width, height: 180.0)
|
||||
if isLoading {
|
||||
contentHeight += 120.0
|
||||
self.loadingView.update(size: loadingSize, theme: environment.theme, itemSize: itemSize, showFilters: false, isPlain: true, transition: .immediate)
|
||||
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
|
||||
} else {
|
||||
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
|
||||
}
|
||||
transition.setFrame(view: self.loadingView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight - 170.0), size: loadingSize))
|
||||
|
||||
var itemFrame = CGRect(origin: CGPoint(x: itemSideInset, y: contentHeight), size: itemSize)
|
||||
var itemsHeight: CGFloat = 0.0
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
for gift in self.availableGifts {
|
||||
let isVisible = "".isEmpty
|
||||
|
|
@ -272,17 +291,15 @@ final class SelectGiftPageContent: Component {
|
|||
)
|
||||
if let itemView = visibleItem.view {
|
||||
if itemView.superview == nil {
|
||||
self.addSubview(itemView)
|
||||
|
||||
if let _ = self.loadingView.superview {
|
||||
self.insertSubview(itemView, belowSubview: self.loadingView)
|
||||
} else {
|
||||
self.addSubview(itemView)
|
||||
}
|
||||
if !transition.animation.isImmediate {
|
||||
let delay = ((itemFrame.minY - contentHeight) / itemSize.height) * 0.07
|
||||
itemView.layer.animateScale(from: 0.01, to: 1.0, duration: 0.25, delay: delay)
|
||||
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, delay: delay)
|
||||
itemView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
itemView.isUserInteractionEnabled = !component.selectedGiftIds.contains(gift.gift.id)
|
||||
itemView.alpha = component.selectedGiftIds.contains(gift.gift.id) ? 0.4 : 1.0
|
||||
itemView.layer.allowsGroupOpacity = itemView.alpha < 1.0
|
||||
itemTransition.setFrame(view: itemView, frame: itemFrame)
|
||||
}
|
||||
}
|
||||
|
|
@ -315,7 +332,7 @@ final class SelectGiftPageContent: Component {
|
|||
for id in removeIds {
|
||||
self.gifts.removeValue(forKey: id)
|
||||
}
|
||||
|
||||
|
||||
if let state = self.craftState, case .ready = state.dataState, self.availableGifts.isEmpty {
|
||||
contentHeight += 10.0
|
||||
let myGiftsPlaceholderSize = self.myGiftsPlaceholder.update(
|
||||
|
|
@ -379,6 +396,7 @@ final class SelectGiftPageContent: Component {
|
|||
starsContext: component.context.starsContext!,
|
||||
peerId: component.context.account.peerId,
|
||||
gift: component.genericGift,
|
||||
isPlain: true,
|
||||
confirmPurchaseImmediately: true,
|
||||
starsTopUpOptions: component.starsTopUpOptions,
|
||||
scrollToTop: {},
|
||||
|
|
@ -530,9 +548,9 @@ private final class SheetContainerComponent: CombinedComponent {
|
|||
backgroundColor: nil,
|
||||
isDark: theme.overallDarkAppearance,
|
||||
state: .glass,
|
||||
component: AnyComponentWithIdentity(id: "back", component: AnyComponent(
|
||||
component: AnyComponentWithIdentity(id: "close", component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Navigation/Back",
|
||||
name: "Navigation/Close",
|
||||
tintColor: theme.chat.inputPanel.panelControlColor
|
||||
)
|
||||
)),
|
||||
|
|
|
|||
|
|
@ -792,9 +792,7 @@ public final class GiftItemComponent: Component {
|
|||
func formatPercentage(_ value: Float) -> String {
|
||||
return String(format: "%0.1f", value).replacingOccurrences(of: ".0", with: "").replacingOccurrences(of: ",0", with: "") + "%"
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
|
||||
|
||||
let badgeString: String
|
||||
var badgeColor: UIColor?
|
||||
switch rarity {
|
||||
|
|
@ -805,21 +803,28 @@ public final class GiftItemComponent: Component {
|
|||
badgeString = formatPercentage(Float(value) * 0.1)
|
||||
}
|
||||
case .epic:
|
||||
badgeString = "epic"
|
||||
badgeString = component.strings.Gift_Attribute_Epic
|
||||
badgeColor = UIColor(rgb: 0xaf52de)
|
||||
case .legendary:
|
||||
badgeString = "legendary"
|
||||
badgeString = component.strings.Gift_Attribute_Legendary
|
||||
badgeColor = UIColor(rgb: 0xd57e32)
|
||||
case .rare:
|
||||
badgeString = "rare"
|
||||
badgeColor = UIColor(rgb: 0x79993d)
|
||||
badgeString = component.strings.Gift_Attribute_Rare
|
||||
badgeColor = UIColor(rgb: 0x25a3b9)
|
||||
case .uncommon:
|
||||
badgeString = component.strings.Gift_Attribute_Uncommon
|
||||
badgeColor = UIColor(rgb: 0x22b447)
|
||||
}
|
||||
|
||||
var badgeTextColor = isColored ? .white : component.theme.list.itemSecondaryTextColor
|
||||
var badgeBackgroundColor = isColored ? UIColor(white: 0.0, alpha: 0.2) : component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.06)
|
||||
if let badgeColor {
|
||||
badgeTextColor = badgeColor
|
||||
badgeBackgroundColor = badgeColor.withMultipliedAlpha(0.1)
|
||||
if let backgroundColor {
|
||||
badgeBackgroundColor = badgeColor.mixedWith(backgroundColor, alpha: 0.9)
|
||||
} else {
|
||||
badgeBackgroundColor = badgeColor.withMultipliedAlpha(0.1)
|
||||
}
|
||||
}
|
||||
let badgeTextSize = self.badgeText.update(
|
||||
transition: .spring(duration: 0.2),
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ public final class GiftLoadingShimmerView: UIView {
|
|||
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
public func update(size: CGSize, theme: PresentationTheme, showFilters: Bool = false, isPlain: Bool = false, transition: ContainedViewLayoutTransition) {
|
||||
public func update(size: CGSize, theme: PresentationTheme, itemSize: CGSize? = nil, showFilters: Bool = false, isPlain: Bool = false, transition: ContainedViewLayoutTransition) {
|
||||
let backgroundColor = isPlain ? theme.list.itemBlocksBackgroundColor : theme.list.blocksBackgroundColor
|
||||
let color = theme.list.itemSecondaryTextColor.mixedWith(theme.list.blocksBackgroundColor, alpha: 0.85)
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ public final class GiftLoadingShimmerView: UIView {
|
|||
|
||||
let optionSpacing: CGFloat = 10.0
|
||||
let optionWidth = (size.width - sideInset * 2.0 - optionSpacing * 2.0) / 3.0
|
||||
let itemSize = CGSize(width: optionWidth, height: 154.0)
|
||||
let itemSize = itemSize ?? CGSize(width: optionWidth, height: 154.0)
|
||||
|
||||
context.setBlendMode(.copy)
|
||||
context.setFillColor(UIColor.clear.cgColor)
|
||||
|
|
|
|||
|
|
@ -1605,7 +1605,7 @@ private final class GiftSetupScreenComponent: Component {
|
|||
guard let self, let component = self.component, let controller = self.environment?.controller(), let upgradePreview else {
|
||||
return
|
||||
}
|
||||
let previewController = component.context.sharedContext.makeGiftUpgradePreviewScreen(context: component.context, attributes: upgradePreview.attributes, peerName: peerName)
|
||||
let previewController = component.context.sharedContext.makeGiftUpgradePreviewScreen(context: component.context, gift: gift, attributes: upgradePreview.attributes, peerName: peerName)
|
||||
controller.push(previewController)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ swift_library(
|
|||
"//submodules/AccountContext",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/ItemListUI",
|
||||
"//submodules/ContextUI",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/Components/SheetComponent",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,174 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import AccountContext
|
||||
import MultilineTextComponent
|
||||
import MultilineTextWithEntitiesComponent
|
||||
import TextFormat
|
||||
import TelegramStringFormatting
|
||||
import Markdown
|
||||
|
||||
final class BalanceComponent: Component {
|
||||
private let context: AccountContext
|
||||
private let theme: PresentationTheme
|
||||
private let action: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
theme: PresentationTheme,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.theme = theme
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: BalanceComponent, rhs: BalanceComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public final class View: HighlightTrackingButton {
|
||||
private var text = ComponentView<Empty>()
|
||||
|
||||
private var component: BalanceComponent?
|
||||
private var componentState: EmptyComponentState?
|
||||
|
||||
private var starsBalance: Int64 = 0
|
||||
private var tonBalance: Int64 = 0
|
||||
private var balanceDisposable: Disposable?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.balanceDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func pressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
|
||||
private var isUpdating = false
|
||||
func update(component: BalanceComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
if self.balanceDisposable == nil {
|
||||
if let starsContext = component.context.starsContext, let tonContext = component.context.tonContext {
|
||||
self.balanceDisposable = combineLatest(queue: Queue.mainQueue(),
|
||||
starsContext.state,
|
||||
tonContext.state
|
||||
).start(next: { [weak self] starsState, tonState in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.starsBalance = starsState?.balance.value ?? 0
|
||||
self.tonBalance = tonState?.balance.value ?? 0
|
||||
if !self.isUpdating {
|
||||
self.componentState?.updated()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var rawString: String = ""
|
||||
let starsBalanceString = "**⭐️\(presentationStringsFormattedNumber(Int32(clamping: self.starsBalance), presentationData.dateTimeFormat.groupingSeparator))**"
|
||||
if self.tonBalance > 0 {
|
||||
let tonBalanceString = "**💎\(formatTonAmountText(self.tonBalance, dateTimeFormat: presentationData.dateTimeFormat))**"
|
||||
rawString = starsBalanceString + "\n" + tonBalanceString
|
||||
} else {
|
||||
rawString = presentationData.strings.Stars_Purchase_Balance + "\n" + starsBalanceString
|
||||
}
|
||||
|
||||
let attributedText = parseMarkdownIntoAttributedString(
|
||||
rawString,
|
||||
attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(12.0), textColor: component.theme.rootController.navigationBar.primaryTextColor),
|
||||
linkAttribute: { _ in
|
||||
return nil
|
||||
}
|
||||
),
|
||||
textAlignment: .right
|
||||
).mutableCopy() as! NSMutableAttributedString
|
||||
let starRange = (attributedText.string as NSString).range(of: "⭐️")
|
||||
if starRange.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: starRange)
|
||||
attributedText.addAttribute(.baselineOffset, value: 1.0, range: starRange)
|
||||
}
|
||||
let tonRange = (attributedText.string as NSString).range(of: "💎")
|
||||
if tonRange.location != NSNotFound {
|
||||
attributedText.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .ton(tinted: false)), range: tonRange)
|
||||
attributedText.addAttribute(.baselineOffset, value: 1.0, range: tonRange)
|
||||
}
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
text: .plain(attributedText),
|
||||
horizontalAlignment: .right,
|
||||
maximumNumberOfLines: 2,
|
||||
lineSpacing: 0.1,
|
||||
displaysAsynchronously: false
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let inset: CGFloat = 12.0
|
||||
let size = CGSize(width: textSize.width + inset * 2.0, height: 44.0)
|
||||
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
textView.isUserInteractionEnabled = false
|
||||
self.addSubview(textView)
|
||||
|
||||
if !transition.animation.isImmediate {
|
||||
transition.animateAlpha(view: textView, from: 0.0, to: 1.0)
|
||||
}
|
||||
}
|
||||
textView.frame = CGRect(origin: CGPoint(x: inset, y: 8.0 - UIScreenPixel), size: textSize)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@ import LottieComponent
|
|||
import GiftLoadingShimmerView
|
||||
import EdgeEffect
|
||||
import GlassBackgroundComponent
|
||||
import ContextUI
|
||||
|
||||
private let minimumCountToDisplayFilters = 18
|
||||
|
||||
|
|
@ -45,6 +46,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
let starsContext: StarsContext
|
||||
let peerId: EnginePeer.Id
|
||||
let gift: StarGift.Gift
|
||||
let isPlain: Bool
|
||||
let confirmPurchaseImmediately: Bool
|
||||
let starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>?
|
||||
let scrollToTop: () -> Void
|
||||
|
|
@ -64,6 +66,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
starsContext: StarsContext,
|
||||
peerId: EnginePeer.Id,
|
||||
gift: StarGift.Gift,
|
||||
isPlain: Bool,
|
||||
confirmPurchaseImmediately: Bool,
|
||||
starsTopUpOptions: Signal<[StarsTopUpOption]?, NoError>?,
|
||||
scrollToTop: @escaping () -> Void,
|
||||
|
|
@ -82,6 +85,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
self.starsContext = starsContext
|
||||
self.peerId = peerId
|
||||
self.gift = gift
|
||||
self.isPlain = isPlain
|
||||
self.confirmPurchaseImmediately = confirmPurchaseImmediately
|
||||
self.starsTopUpOptions = starsTopUpOptions
|
||||
self.scrollToTop = scrollToTop
|
||||
|
|
@ -1075,7 +1079,7 @@ public final class GiftStoreContentComponent: Component {
|
|||
|
||||
let loadingSize = CGSize(width: availableSize.width, height: min(1000.0, availableSize.height))
|
||||
if isLoading && self.showLoading {
|
||||
self.loadingView.update(size: loadingSize, theme: component.theme, showFilters: !showingFilters, isPlain: true, transition: .immediate)
|
||||
self.loadingView.update(size: loadingSize, theme: component.theme, showFilters: !showingFilters, isPlain: component.isPlain, transition: .immediate)
|
||||
loadingTransition.setAlpha(view: self.loadingView, alpha: 1.0)
|
||||
} else {
|
||||
loadingTransition.setAlpha(view: self.loadingView, alpha: 0.0)
|
||||
|
|
@ -1145,10 +1149,8 @@ final class GiftStoreScreenComponent: Component {
|
|||
|
||||
private let edgeEffectView: EdgeEffectView
|
||||
|
||||
private let balanceBackgroundView: GlassBackgroundView
|
||||
private let balanceTitle = ComponentView<Empty>()
|
||||
private let balanceValue = ComponentView<Empty>()
|
||||
private let balanceIcon = ComponentView<Empty>()
|
||||
private let balance = ComponentView<Empty>()
|
||||
private let balanceBackgroundView: GlassContextExtractableContainer
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
|
|
@ -1165,7 +1167,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
private var isUpdating: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.balanceBackgroundView = GlassBackgroundView()
|
||||
self.balanceBackgroundView = GlassContextExtractableContainer()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
|
|
@ -1211,6 +1213,68 @@ final class GiftStoreScreenComponent: Component {
|
|||
contentView.updateScrolling(bounds: bounds, interactive: interactive, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
func presentBalanceMenu() {
|
||||
guard let component = self.component, let starsContext = component.context.starsContext, let tonContext = component.context.tonContext, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
let tonBalance = tonContext.currentState?.balance.value ?? 0
|
||||
if tonBalance == 0 {
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
|
||||
controller.push(controller)
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let sourceView = self.balanceBackgroundView
|
||||
|
||||
let items: Signal<[ContextMenuItem], NoError> = combineLatest(
|
||||
queue: Queue.mainQueue(),
|
||||
starsContext.state,
|
||||
tonContext.state
|
||||
)
|
||||
|> take(1)
|
||||
|> map { starsState, tonState -> [ContextMenuItem] in
|
||||
let starsBalance = starsState?.balance ?? .zero
|
||||
let tonBalance = tonState?.balance.value ?? 0
|
||||
|
||||
var items: [ContextMenuItem] = []
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: "My Stars",
|
||||
textLayout: .secondLineWithValue(formatStarsAmountText(starsBalance, dateTimeFormat: presentationData.dateTimeFormat)),
|
||||
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stars"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
|
||||
environment.controller()?.push(controller)
|
||||
}
|
||||
)))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(
|
||||
text: "My TON",
|
||||
textLayout: .secondLineWithValue(formatTonAmountText(tonBalance, dateTimeFormat: presentationData.dateTimeFormat)),
|
||||
icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Ton"), color: theme.contextMenu.primaryColor) },
|
||||
action: { [weak self] _, f in
|
||||
f(.dismissWithoutContent)
|
||||
guard let self, let component = self.component, let environment = self.environment else {
|
||||
return
|
||||
}
|
||||
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
|
||||
environment.controller()?.push(controller)
|
||||
}
|
||||
)))
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
let contextController = makeContextController(presentationData: presentationData, source: .reference(GiftStoreReferenceContentSource(controller: controller, sourceView: sourceView)), items: items |> map { ContextController.Items(content: .list($0)) }, gesture: nil)
|
||||
controller.presentInGlobalOverlay(contextController)
|
||||
}
|
||||
|
||||
func update(component: GiftStoreScreenComponent, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
|
|
@ -1255,42 +1319,37 @@ final class GiftStoreScreenComponent: Component {
|
|||
let edgeEffectFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: edgeEffectHeight))
|
||||
transition.setFrame(view: self.edgeEffectView, frame: edgeEffectFrame)
|
||||
self.edgeEffectView.update(content: environment.theme.list.blocksBackgroundColor, blur: true, rect: edgeEffectFrame, edge: .top, edgeSize: min(30, edgeEffectFrame.height), transition: transition)
|
||||
|
||||
let balanceTitleSize = self.balanceTitle.update(
|
||||
|
||||
|
||||
let balanceSize = self.balance.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: strings.Stars_Purchase_Balance,
|
||||
font: Font.regular(14.0),
|
||||
textColor: environment.theme.actionSheet.primaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
component: AnyComponent(
|
||||
BalanceComponent(
|
||||
context: component.context,
|
||||
theme: environment.theme,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.presentBalanceMenu()
|
||||
}
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let formattedBalance = formatStarsAmountText(self.starsState?.balance ?? StarsAmount.zero, dateTimeFormat: environment.dateTimeFormat)
|
||||
let smallLabelFont = Font.regular(11.0)
|
||||
let labelFont = Font.semibold(14.0)
|
||||
let balanceText = tonAmountAttributedString(formattedBalance, integralFont: labelFont, fractionalFont: smallLabelFont, color: environment.theme.actionSheet.primaryTextColor, decimalSeparator: environment.dateTimeFormat.decimalSeparator)
|
||||
|
||||
let balanceValueSize = self.balanceValue.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(balanceText),
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let balanceIconSize = self.balanceIcon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(name: "Premium/Stars/StarSmall", tintColor: nil)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
|
||||
let balanceFrame = CGRect(origin: .zero, size: balanceSize)
|
||||
if let balanceView = self.balance.view {
|
||||
if balanceView.superview == nil {
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceView)
|
||||
}
|
||||
balanceView.frame = balanceFrame
|
||||
|
||||
let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceSize)
|
||||
|
||||
transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame)
|
||||
self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
|
||||
}
|
||||
if self.balanceBackgroundView.superview == nil {
|
||||
component.overNavigationContainer.addSubview(self.balanceBackgroundView)
|
||||
}
|
||||
|
|
@ -1300,32 +1359,6 @@ final class GiftStoreScreenComponent: Component {
|
|||
topInset = environment.statusBarHeight - 6.0
|
||||
}
|
||||
|
||||
if let balanceTitleView = self.balanceTitle.view, let balanceValueView = self.balanceValue.view, let balanceIconView = self.balanceIcon.view {
|
||||
if balanceTitleView.superview == nil {
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceTitleView)
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceValueView)
|
||||
self.balanceBackgroundView.contentView.addSubview(balanceIconView)
|
||||
}
|
||||
|
||||
let topBalanceOriginY = (44.0 - balanceTitleSize.height - balanceValueSize.height) / 2.0
|
||||
|
||||
let balanceSideInset: CGFloat = 12.0
|
||||
var balanceBackgroundSize = CGSize(width: balanceTitleSize.width + balanceSideInset * 2.0, height: 44.0)
|
||||
balanceBackgroundSize.width = max(balanceBackgroundSize.width, balanceValueSize.width + balanceIconSize.width + 2.0 + balanceSideInset * 2.0)
|
||||
|
||||
let balanceBackgroundFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - 16.0 - balanceBackgroundSize.width, y: environment.navigationHeight - 60.0 + 2.0 + floor((60.0 - 44.0) * 0.5)), size: balanceBackgroundSize)
|
||||
|
||||
transition.setFrame(view: self.balanceBackgroundView, frame: balanceBackgroundFrame)
|
||||
self.balanceBackgroundView.update(size: balanceBackgroundFrame.size, cornerRadius: balanceBackgroundFrame.height * 0.5, isDark: environment.theme.overallDarkAppearance, tintColor: .init(kind: .panel), transition: transition)
|
||||
|
||||
balanceTitleView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceTitleSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height / 2.0)
|
||||
balanceTitleView.bounds = CGRect(origin: .zero, size: balanceTitleSize)
|
||||
balanceValueView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width / 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0)
|
||||
balanceValueView.bounds = CGRect(origin: .zero, size: balanceValueSize)
|
||||
balanceIconView.center = CGPoint(x: balanceBackgroundFrame.width - balanceSideInset - balanceValueSize.width - balanceIconSize.width / 2.0 - 2.0, y: topBalanceOriginY + balanceTitleSize.height + balanceValueSize.height / 2.0 - UIScreenPixel)
|
||||
balanceIconView.bounds = CGRect(origin: .zero, size: balanceIconSize)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
|
|
@ -1360,6 +1393,7 @@ final class GiftStoreScreenComponent: Component {
|
|||
starsContext: component.starsContext,
|
||||
peerId: component.peerId,
|
||||
gift: component.gift,
|
||||
isPlain: false,
|
||||
confirmPurchaseImmediately: false,
|
||||
starsTopUpOptions: nil,
|
||||
scrollToTop: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -1,267 +0,0 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import ComponentFlow
|
||||
import TelegramCore
|
||||
import BundleIconComponent
|
||||
import MultilineTextComponent
|
||||
import MoreButtonNode
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import TelegramStringFormatting
|
||||
|
||||
final class PriceButtonComponent: Component {
|
||||
let price: CurrencyAmount
|
||||
let dateTimeFormat: PresentationDateTimeFormat
|
||||
|
||||
init(
|
||||
price: CurrencyAmount,
|
||||
dateTimeFormat: PresentationDateTimeFormat
|
||||
) {
|
||||
self.price = price
|
||||
self.dateTimeFormat = dateTimeFormat
|
||||
}
|
||||
|
||||
static func ==(lhs: PriceButtonComponent, rhs: PriceButtonComponent) -> Bool {
|
||||
return lhs.price == rhs.price && lhs.dateTimeFormat == rhs.dateTimeFormat
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private let icon = UIImageView()
|
||||
private let text = ComponentView<Empty>()
|
||||
|
||||
private var component: PriceButtonComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundView.clipsToBounds = true
|
||||
self.addSubview(self.backgroundView)
|
||||
|
||||
self.backgroundView.addSubview(self.icon)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: PriceButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let previousComponent = self.component
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
if previousComponent?.price.currency != component.price.currency {
|
||||
switch component.price.currency {
|
||||
case .stars:
|
||||
self.icon.image = UIImage(bundleImageName: "Premium/Stars/ButtonStar")?.withRenderingMode(.alwaysTemplate)
|
||||
case .ton:
|
||||
self.icon.image = UIImage(bundleImageName: "Ads/TonAbout")?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundSize = CGSize(width: 42.0, height: 30.0)
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: formatCurrencyAmountText(component.price, dateTimeFormat: component.dateTimeFormat),
|
||||
font: Font.semibold(11.0),
|
||||
textColor: UIColor(rgb: 0xffffff)
|
||||
))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
let textFrame = CGRect(origin: CGPoint(x: 32.0, y: floorToScreenPixels((backgroundSize.height - textSize.height) / 2.0)), size: textSize)
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.backgroundView.addSubview(textView)
|
||||
}
|
||||
transition.setFrame(view: textView, frame: textFrame)
|
||||
}
|
||||
backgroundSize.width += textSize.width
|
||||
|
||||
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
|
||||
|
||||
let backgroundColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
|
||||
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
|
||||
|
||||
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
|
||||
if let iconSize = self.icon.image?.size {
|
||||
let iconFrame = CGRect(origin: CGPoint(x: 12.0, y: floorToScreenPixels((backgroundSize.height - iconSize.height) / 2.0)), size: iconSize)
|
||||
transition.setFrame(view: self.icon, frame: iconFrame)
|
||||
}
|
||||
self.icon.tintColor = UIColor(rgb: 0xffffff)
|
||||
|
||||
return backgroundSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
final class ButtonsComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let isOverlay: Bool
|
||||
let showMoreButton: Bool
|
||||
let closePressed: () -> Void
|
||||
let morePressed: (ASDisplayNode, ContextGesture?) -> Void
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
isOverlay: Bool,
|
||||
showMoreButton: Bool,
|
||||
closePressed: @escaping () -> Void,
|
||||
morePressed: @escaping (ASDisplayNode, ContextGesture?) -> Void
|
||||
) {
|
||||
self.theme = theme
|
||||
self.isOverlay = isOverlay
|
||||
self.showMoreButton = showMoreButton
|
||||
self.closePressed = closePressed
|
||||
self.morePressed = morePressed
|
||||
}
|
||||
|
||||
static func ==(lhs: ButtonsComponent, rhs: ButtonsComponent) -> Bool {
|
||||
return lhs.theme === rhs.theme && lhs.isOverlay == rhs.isOverlay && lhs.showMoreButton == rhs.showMoreButton
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
private let backgroundView = UIView()
|
||||
|
||||
private let closeButton = HighlightTrackingButton()
|
||||
private let closeIcon = UIImageView()
|
||||
private let moreNode = MoreButtonNode(theme: defaultPresentationTheme, size: CGSize(width: 36.0, height: 36.0), encircled: false)
|
||||
|
||||
private var component: ButtonsComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.backgroundView.clipsToBounds = true
|
||||
self.addSubview(self.backgroundView)
|
||||
|
||||
self.closeIcon.image = generateCloseButtonImage()
|
||||
self.moreNode.updateColor(.white, transition: .immediate)
|
||||
|
||||
self.backgroundView.addSubview(self.moreNode.view)
|
||||
self.backgroundView.addSubview(self.closeButton)
|
||||
self.backgroundView.addSubview(self.closeIcon)
|
||||
|
||||
self.closeButton.highligthedChanged = { [weak self] highlighted in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if highlighted {
|
||||
self.closeIcon.layer.removeAnimation(forKey: "opacity")
|
||||
self.closeIcon.alpha = 0.6
|
||||
} else {
|
||||
self.closeIcon.alpha = 1.0
|
||||
self.closeIcon.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
self.closeButton.addTarget(self, action: #selector(self.closePressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func closePressed() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.closePressed()
|
||||
}
|
||||
|
||||
func update(component: ButtonsComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let backgroundSize = CGSize(width: component.showMoreButton ? 70.0 : 30.0, height: 30.0)
|
||||
self.backgroundView.layer.cornerRadius = backgroundSize.height / 2.0
|
||||
|
||||
let backgroundColor: UIColor = component.isOverlay ? UIColor(rgb: 0xffffff, alpha: 0.1) : UIColor(rgb: 0x808084, alpha: 0.1)
|
||||
let foregroundColor: UIColor = component.isOverlay ? .white : component.theme.actionSheet.inputClearButtonColor
|
||||
transition.setBackgroundColor(view: self.backgroundView, color: backgroundColor)
|
||||
transition.setTintColor(view: self.closeIcon, color: foregroundColor)
|
||||
|
||||
let backgroundFrame = CGRect(origin: .zero, size: backgroundSize)
|
||||
transition.setFrame(view: self.backgroundView, frame: backgroundFrame)
|
||||
|
||||
transition.setFrame(view: self.moreNode.view, frame: CGRect(origin: CGPoint(x: -7.0, y: -4.0), size: CGSize(width: 36.0, height: 36.0)))
|
||||
transition.setAlpha(view: self.moreNode.view, alpha: component.showMoreButton ? 1.0 : 0.0)
|
||||
self.moreNode.action = { [weak self] node, gesture in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.morePressed(node, gesture)
|
||||
}
|
||||
|
||||
let closeFrame = CGRect(origin: CGPoint(x: backgroundSize.width - 30.0 - (component.showMoreButton ? 3.0 : 0.0), y: 0.0), size: CGSize(width: 30.0, height: 30.0))
|
||||
transition.setFrame(view: self.closeIcon, frame: closeFrame)
|
||||
transition.setFrame(view: self.closeButton, frame: closeFrame)
|
||||
|
||||
return backgroundSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
private func generateCloseButtonImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(UIColor.white.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
})?.withRenderingMode(.alwaysTemplate)
|
||||
}
|
||||
|
||||
func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(foregroundColor.cgColor)
|
||||
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.strokePath()
|
||||
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
|
@ -746,8 +746,8 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
|
||||
}
|
||||
|
||||
|
|
@ -1083,7 +1083,7 @@ private final class GiftAuctionViewSheetContent: CombinedComponent {
|
|||
guard let state, let attributes = state.giftUpgradeAttributes else {
|
||||
return
|
||||
}
|
||||
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), onlyCrafted: false, attributes: attributes, selectedAttributes: nil, focusedAttribute: nil)
|
||||
let variantsController = component.context.sharedContext.makeGiftUpgradeVariantsScreen(context: component.context, gift: .generic(gift), crafted: false, attributes: attributes, selectedAttributes: nil, focusedAttribute: nil)
|
||||
environment.controller()?.push(variantsController)
|
||||
}, animateScale: false),
|
||||
availableSize: CGSize(width: context.availableSize.width - 64.0, height: context.availableSize.height),
|
||||
|
|
|
|||
|
|
@ -294,8 +294,8 @@ private final class GiftAuctionWearPreviewSheetContent: CombinedComponent {
|
|||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
secondaryTextColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultiplied(hue: 1.0, saturation: 1.02, brightness: 1.25).mixedWith(UIColor.white, alpha: 0.3)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
|
||||
let context: AccountContext
|
||||
let gift: StarGift
|
||||
let onlyCrafted: Bool
|
||||
let crafted: Bool
|
||||
let attributes: [StarGift.UniqueGift.Attribute]
|
||||
let selectedAttributes: [StarGift.UniqueGift.Attribute]?
|
||||
let focusedAttribute: StarGift.UniqueGift.Attribute?
|
||||
|
|
@ -37,14 +37,14 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
init(
|
||||
context: AccountContext,
|
||||
gift: StarGift,
|
||||
onlyCrafted: Bool,
|
||||
crafted: Bool,
|
||||
attributes: [StarGift.UniqueGift.Attribute],
|
||||
selectedAttributes: [StarGift.UniqueGift.Attribute]?,
|
||||
focusedAttribute: StarGift.UniqueGift.Attribute?
|
||||
) {
|
||||
self.context = context
|
||||
self.gift = gift
|
||||
self.onlyCrafted = onlyCrafted
|
||||
self.crafted = crafted
|
||||
self.attributes = attributes
|
||||
self.selectedAttributes = selectedAttributes
|
||||
self.focusedAttribute = focusedAttribute
|
||||
|
|
@ -112,6 +112,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
private var giftItems: [AnyHashable: ComponentView<Empty>] = [:]
|
||||
|
||||
private var selectedSection: SelectedSection = .models
|
||||
private var displayCraftableModels = false
|
||||
|
||||
private let giftCompositionExternalState = GiftCompositionComponent.ExternalState()
|
||||
|
||||
|
|
@ -122,7 +123,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
private var previewBackdropIndex: Int = 0
|
||||
private var previewSymbolIndex: Int = 0
|
||||
|
||||
private var previewModels: [StarGift.UniqueGift.Attribute] = []
|
||||
private var previewPrimaryModels: [StarGift.UniqueGift.Attribute] = []
|
||||
private var previewCraftableModels: [StarGift.UniqueGift.Attribute] = []
|
||||
private var previewBackdrops: [StarGift.UniqueGift.Attribute] = []
|
||||
private var previewSymbols: [StarGift.UniqueGift.Attribute] = []
|
||||
|
||||
|
|
@ -130,10 +132,15 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
private var selectedBackdrop: StarGift.UniqueGift.Attribute?
|
||||
private var selectedSymbol: StarGift.UniqueGift.Attribute?
|
||||
|
||||
private var modelCount: Int32 = 0
|
||||
private var craftableModelCount: Int32 = 0
|
||||
private var primaryModelCount: Int32 = 0
|
||||
private var backdropCount: Int32 = 0
|
||||
private var symbolCount: Int32 = 0
|
||||
|
||||
private var currentDescriptionHeight: CGFloat = 0.0
|
||||
|
||||
private var cachedSmallChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var component: GiftUpgradeVariantsScreenComponent?
|
||||
|
|
@ -202,6 +209,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
self.containerView.addSubview(self.navigationBarContainer)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
|
||||
self.alpha = 0.0
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
|
|
@ -271,6 +280,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
}
|
||||
|
||||
func animateIn() {
|
||||
self.alpha = 1.0
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
|
|
@ -290,8 +300,9 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
}
|
||||
|
||||
private func previewTimerTick() {
|
||||
guard !self.previewModels.isEmpty else { return }
|
||||
self.previewModelIndex = (self.previewModelIndex + 1) % self.previewModels.count
|
||||
let previewModels = self.displayCraftableModels ? self.previewCraftableModels : self.previewPrimaryModels
|
||||
guard !previewModels.isEmpty else { return }
|
||||
self.previewModelIndex = (self.previewModelIndex + 1) % previewModels.count
|
||||
|
||||
let previousSymbolIndex = self.previewSymbolIndex
|
||||
var randomSymbolIndex = previousSymbolIndex
|
||||
|
|
@ -326,13 +337,15 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
}
|
||||
|
||||
private var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = []
|
||||
private func updateEffectiveGifts(attributes: [StarGift.UniqueGift.Attribute], onlyCrafted: Bool) {
|
||||
private func updateEffectiveGifts(attributes: [StarGift.UniqueGift.Attribute]) {
|
||||
var effectiveGifts: [[StarGift.UniqueGift.Attribute]] = []
|
||||
switch self.selectedSection {
|
||||
case .models:
|
||||
let models = Array(attributes.filter({ attribute in
|
||||
if case let .model(_, _, _, crafted) = attribute {
|
||||
if onlyCrafted && !crafted {
|
||||
if self.displayCraftableModels && !crafted {
|
||||
return false
|
||||
} else if !self.displayCraftableModels && crafted {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -344,7 +357,8 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
effectiveGifts.append([model])
|
||||
}
|
||||
case .backdrops:
|
||||
let selectedModel = self.selectedModel ?? self.previewModels[self.previewModelIndex]
|
||||
let previewModels = self.displayCraftableModels ? self.previewCraftableModels : previewPrimaryModels
|
||||
let selectedModel = self.selectedModel ?? previewModels[self.previewModelIndex]
|
||||
let selectedSymbol = self.selectedSymbol ?? self.previewSymbols[self.previewSymbolIndex]
|
||||
let backdrops = Array(attributes.filter({ attribute in
|
||||
if case .backdrop = attribute {
|
||||
|
|
@ -400,7 +414,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
let optionWidth = (fillingSize - 16.0 * 2.0 - optionSpacing * 2.0) / 3.0
|
||||
let optionSize = CGSize(width: optionWidth, height: 126.0)
|
||||
|
||||
let topInset: CGFloat = 393.0
|
||||
let topInset: CGFloat = 375.0 + self.currentDescriptionHeight
|
||||
|
||||
var validIds: [AnyHashable] = []
|
||||
var itemFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset + 9.0), size: optionSize)
|
||||
|
|
@ -604,15 +618,19 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
let sideInset: CGFloat = rawSideInset + 16.0
|
||||
|
||||
if self.component == nil {
|
||||
var modelCount: Int32 = 0
|
||||
self.displayCraftableModels = component.crafted
|
||||
|
||||
var primaryModelCount: Int32 = 0
|
||||
var craftableModelCount: Int32 = 0
|
||||
var backdropCount: Int32 = 0
|
||||
var symbolCount: Int32 = 0
|
||||
for attribute in component.attributes {
|
||||
switch attribute {
|
||||
case let .model(_, _, _, crafted):
|
||||
if component.onlyCrafted && !crafted {
|
||||
if crafted {
|
||||
craftableModelCount += 1
|
||||
} else {
|
||||
modelCount += 1
|
||||
primaryModelCount += 1
|
||||
}
|
||||
case .backdrop:
|
||||
backdropCount += 1
|
||||
|
|
@ -622,13 +640,14 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
break
|
||||
}
|
||||
}
|
||||
self.modelCount = modelCount
|
||||
self.primaryModelCount = primaryModelCount
|
||||
self.craftableModelCount = craftableModelCount
|
||||
self.backdropCount = backdropCount
|
||||
self.symbolCount = symbolCount
|
||||
|
||||
let randomModels = Array(component.attributes.filter({ attribute in
|
||||
let randomPrimaryModels = Array(component.attributes.filter({ attribute in
|
||||
if case let .model(_, _, _, crafted) = attribute {
|
||||
if component.onlyCrafted && !crafted {
|
||||
if crafted {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
@ -636,7 +655,19 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
return false
|
||||
}
|
||||
}).shuffled().prefix(15))
|
||||
self.previewModels = randomModels
|
||||
self.previewPrimaryModels = randomPrimaryModels
|
||||
|
||||
let randomCraftableModels = Array(component.attributes.filter({ attribute in
|
||||
if case let .model(_, _, _, crafted) = attribute {
|
||||
if !crafted {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}).shuffled().prefix(15))
|
||||
self.previewCraftableModels = randomCraftableModels
|
||||
|
||||
let randomBackdrops = Array(component.attributes.filter({ attribute in
|
||||
if case .backdrop = attribute {
|
||||
|
|
@ -684,7 +715,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
self.updateEffectiveGifts(attributes: component.attributes, onlyCrafted: component.onlyCrafted)
|
||||
self.updateEffectiveGifts(attributes: component.attributes)
|
||||
}
|
||||
|
||||
self.component = component
|
||||
|
|
@ -705,14 +736,15 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
var badgeColor: UIColor = .white.withAlphaComponent(0.4)
|
||||
|
||||
var attributes: [StarGift.UniqueGift.Attribute] = []
|
||||
if !self.previewModels.isEmpty {
|
||||
let previewModels = self.displayCraftableModels ? self.previewCraftableModels : self.previewPrimaryModels
|
||||
if !previewModels.isEmpty {
|
||||
if self.isPlaying {
|
||||
attributes.append(self.previewModels[self.previewModelIndex])
|
||||
attributes.append(previewModels[self.previewModelIndex])
|
||||
attributes.append(self.previewBackdrops[self.previewBackdropIndex])
|
||||
attributes.append(self.previewSymbols[self.previewSymbolIndex])
|
||||
} else {
|
||||
if self.selectedModel == nil {
|
||||
self.selectedModel = self.previewModels[self.previewModelIndex]
|
||||
self.selectedModel = previewModels[self.previewModelIndex]
|
||||
}
|
||||
if self.selectedBackdrop == nil {
|
||||
self.selectedBackdrop = self.previewBackdrops[self.previewBackdropIndex]
|
||||
|
|
@ -739,7 +771,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
buttonColor = UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2)
|
||||
|
||||
badgeColor = UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05)
|
||||
let outer = UIColor(rgb: UInt32(bitPattern: outerColor))
|
||||
|
|
@ -786,8 +818,11 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
contentHeight += headerSize.height
|
||||
|
||||
var titleText: String = ""
|
||||
if case let .generic(gift) = component.gift {
|
||||
switch component.gift {
|
||||
case let .generic(gift):
|
||||
titleText = gift.title ?? ""
|
||||
case let .unique(gift):
|
||||
titleText = gift.title
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
|
|
@ -838,7 +873,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
}
|
||||
|
||||
let attributeSpacing: CGFloat = 10.0
|
||||
let attributeWidth: CGFloat = floor((fillingSize - attributeSpacing * CGFloat(attributes.count - 1)) / CGFloat(attributes.count))
|
||||
let attributeWidth: CGFloat = floor((fillingSize - 32.0 - attributeSpacing * CGFloat(attributes.count - 1)) / CGFloat(attributes.count))
|
||||
let attributeHeight: CGFloat = 45.0
|
||||
|
||||
for i in 0 ..< attributes.count {
|
||||
|
|
@ -911,7 +946,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
self.selectedSection = id
|
||||
self.isPlaying = false
|
||||
|
||||
self.updateEffectiveGifts(attributes: component.attributes, onlyCrafted: component.onlyCrafted)
|
||||
self.updateEffectiveGifts(attributes: component.attributes)
|
||||
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
})),
|
||||
environment: {},
|
||||
|
|
@ -932,17 +967,22 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
let itemHeight: CGFloat = 126.0
|
||||
let itemSpacing: CGFloat = 10.0
|
||||
|
||||
let descriptionText: String
|
||||
var descriptionText: String
|
||||
let itemCount: Int32
|
||||
switch self.selectedSection {
|
||||
case .models:
|
||||
//TODO:localize
|
||||
if component.onlyCrafted {
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo("**\(self.modelCount)** craftable models").string
|
||||
if self.displayCraftableModels {
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_CraftableModel(self.craftableModelCount)).string
|
||||
itemCount = self.craftableModelCount
|
||||
descriptionText += "\n[\(environment.strings.Gift_Variants_ViewPrimaryModels) >]()"
|
||||
} else {
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.modelCount)).string
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Model(self.primaryModelCount)).string
|
||||
itemCount = self.primaryModelCount
|
||||
|
||||
if self.craftableModelCount > 0 {
|
||||
descriptionText += "\n[\(environment.strings.Gift_Variants_ViewCraftableModels) >]()"
|
||||
}
|
||||
}
|
||||
itemCount = self.modelCount
|
||||
case .backdrops:
|
||||
descriptionText = environment.strings.Gift_Variants_CollectionInfo(environment.strings.Gift_Variants_CollectionInfo_Backdrop(self.backdropCount)).string
|
||||
itemCount = self.backdropCount
|
||||
|
|
@ -954,14 +994,45 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
let descriptionFont = Font.regular(13.0)
|
||||
let descriptionBoldFont = Font.semibold(13.0)
|
||||
let descriptionTextColor = theme.list.itemSecondaryTextColor
|
||||
let descriptionMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionTextColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), linkAttribute: { contents in
|
||||
let descriptionLinkColor = theme.list.itemAccentColor
|
||||
let descriptionMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionTextColor), bold: MarkdownAttributeSet(font: descriptionBoldFont, textColor: descriptionTextColor), link: MarkdownAttributeSet(font: descriptionFont, textColor: descriptionLinkColor), linkAttribute: { contents in
|
||||
return (TelegramTextAttributes.URL, contents)
|
||||
})
|
||||
|
||||
if self.cachedSmallChevronImage == nil || self.cachedSmallChevronImage?.1 !== environment.theme {
|
||||
self.cachedSmallChevronImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/InlineTextRightArrow"), color: descriptionLinkColor)!, theme)
|
||||
}
|
||||
|
||||
let descriptionAttributedString = parseMarkdownIntoAttributedString(descriptionText, attributes: descriptionMarkdownAttributes, textAlignment: .center).mutableCopy() as! NSMutableAttributedString
|
||||
if let range = descriptionAttributedString.string.range(of: ">"), let chevronImage = self.cachedSmallChevronImage?.0 {
|
||||
descriptionAttributedString.addAttribute(.attachment, value: chevronImage, range: NSRange(range, in: descriptionAttributedString.string))
|
||||
}
|
||||
|
||||
let descriptionSize = self.descriptionText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .markdown(text: descriptionText, attributes: descriptionMarkdownAttributes)
|
||||
text: .plain(descriptionAttributedString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 3,
|
||||
lineSpacing: 0.2,
|
||||
highlightColor: descriptionLinkColor.withAlphaComponent(0.1),
|
||||
highlightInset: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: -8.0),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
|
||||
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak self] attributes, _ in
|
||||
if let self, let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String {
|
||||
self.displayCraftableModels = !self.displayCraftableModels
|
||||
self.isPlaying = false
|
||||
|
||||
self.updateEffectiveGifts(attributes: component.attributes)
|
||||
self.state?.updated(transition: ComponentTransition(animation: .curve(duration: 0.4, curve: .spring)))
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
|
|
@ -973,6 +1044,7 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
}
|
||||
descriptionView.frame = descriptionFrame
|
||||
}
|
||||
self.currentDescriptionHeight = descriptionSize.height
|
||||
contentHeight += descriptionSize.height
|
||||
contentHeight += 26.0
|
||||
|
||||
|
|
@ -1061,6 +1133,13 @@ private final class GiftUpgradeVariantsScreenComponent: Component {
|
|||
self.previewTimerTick()
|
||||
}
|
||||
self.state?.updated(transition: .easeInOut(duration: 0.25))
|
||||
|
||||
if let buttonView = self.playbackButton.view {
|
||||
buttonView.isUserInteractionEnabled = false
|
||||
Queue.mainQueue().after(0.3, {
|
||||
buttonView.isUserInteractionEnabled = true
|
||||
})
|
||||
}
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
|
@ -1156,7 +1235,7 @@ public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
|
|||
public init(
|
||||
context: AccountContext,
|
||||
gift: StarGift,
|
||||
onlyCrafted: Bool = false,
|
||||
crafted: Bool = false,
|
||||
attributes: [StarGift.UniqueGift.Attribute],
|
||||
selectedAttributes: [StarGift.UniqueGift.Attribute]?,
|
||||
focusedAttribute: StarGift.UniqueGift.Attribute?
|
||||
|
|
@ -1166,7 +1245,7 @@ public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
|
|||
super.init(context: context, component: GiftUpgradeVariantsScreenComponent(
|
||||
context: context,
|
||||
gift: gift,
|
||||
onlyCrafted: onlyCrafted,
|
||||
crafted: crafted,
|
||||
attributes: attributes,
|
||||
selectedAttributes: selectedAttributes,
|
||||
focusedAttribute: focusedAttribute
|
||||
|
|
@ -1194,7 +1273,9 @@ public class GiftUpgradeVariantsScreen: ViewControllerComponentContainer {
|
|||
self.didPlayAppearAnimation = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? GiftUpgradeVariantsScreenComponent.View {
|
||||
componentView.animateIn()
|
||||
Queue.mainQueue().justDispatch {
|
||||
componentView.animateIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1352,8 +1433,8 @@ private final class AttributeInfoComponent: Component {
|
|||
}
|
||||
|
||||
var badgeString = ""
|
||||
var badgeColor = component.badgeColor
|
||||
if let rarity {
|
||||
//TODO:localize
|
||||
switch rarity {
|
||||
case let .permille(value):
|
||||
if value == 0 {
|
||||
|
|
@ -1362,11 +1443,17 @@ private final class AttributeInfoComponent: Component {
|
|||
badgeString = formatPercentage(Float(value) * 0.1)
|
||||
}
|
||||
case .epic:
|
||||
badgeString = "epic"
|
||||
badgeString = component.strings.Gift_Attribute_Epic
|
||||
badgeColor = UIColor(rgb: 0xaf52de)
|
||||
case .legendary:
|
||||
badgeString = "legendary"
|
||||
badgeString = component.strings.Gift_Attribute_Legendary
|
||||
badgeColor = UIColor(rgb: 0xd57e32)
|
||||
case .rare:
|
||||
badgeString = "rare"
|
||||
badgeString = component.strings.Gift_Attribute_Rare
|
||||
badgeColor = UIColor(rgb: 0x25a3b9)
|
||||
case .uncommon:
|
||||
badgeString = component.strings.Gift_Attribute_Uncommon
|
||||
badgeColor = UIColor(rgb: 0x22b447)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1406,8 +1493,7 @@ private final class AttributeInfoComponent: Component {
|
|||
|
||||
let badgeBackgroundFrame = badgeFrame.insetBy(dx: -5.5, dy: -2.0)
|
||||
transition.setFrame(layer: self.badgeBackground, frame: badgeBackgroundFrame)
|
||||
transition.setBackgroundColor(layer: self.badgeBackground, color: component.badgeColor)
|
||||
|
||||
transition.setBackgroundColor(layer: self.badgeBackground, color: badgeColor)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ public func buyStarGiftImpl(
|
|||
parseMarkdown: true
|
||||
)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
|
||||
beforeCompletion()
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +209,8 @@ public func buyStarGiftImpl(
|
|||
context.sharedContext.applicationBindings.openUrl(fragmentUrl)
|
||||
}
|
||||
))
|
||||
|
||||
beforeCompletion()
|
||||
} else {
|
||||
proceed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import PeerTableCellComponent
|
|||
import AvatarComponent
|
||||
import GlassControls
|
||||
import GlassBarButtonComponent
|
||||
import GlassBackgroundComponent
|
||||
|
||||
private final class GiftViewSheetContent: CombinedComponent {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
|
@ -748,7 +749,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
|
||||
self.subject = .profileGift(peerId, gift.withGift(.unique(uniqueGift.withAttributes(updatedAttributes))))
|
||||
case let .message(message):
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt) = action.action, case let .unique(uniqueGift) = gift {
|
||||
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, isRefunded, isPrepaidUpgrade, peerId, senderId, savedId, resaleAmount, canTransferDate, canResaleDate, _, assigned, fromOffer, canCraftAt, isCrafted) = action.action, case let .unique(uniqueGift) = gift {
|
||||
let updatedAttributes = uniqueGift.attributes.filter { $0.attributeType != .originalInfo }
|
||||
let updatedMedia: [Media] = [
|
||||
TelegramMediaAction(
|
||||
|
|
@ -770,7 +771,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
dropOriginalDetailsStars: nil,
|
||||
assigned: assigned,
|
||||
fromOffer: fromOffer,
|
||||
canCraftAt: canCraftAt
|
||||
canCraftAt: canCraftAt,
|
||||
isCrafted: isCrafted
|
||||
)
|
||||
)
|
||||
]
|
||||
|
|
@ -1114,11 +1116,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
|
||||
if let canCraftDate = arguments.canCraftDate, currentTime < canCraftDate {
|
||||
let dateString = stringForFullDate(timestamp: canCraftDate, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat)
|
||||
//TODO:localize
|
||||
let alertController = textAlertController(
|
||||
context: self.context,
|
||||
title: "Try Later",
|
||||
text: "You will be able to craft this gift on \(dateString).",
|
||||
title: presentationData.strings.Gift_Craft_Unavailable_Title,
|
||||
text: presentationData.strings.Gift_Craft_Unavailable_Text(dateString).string,
|
||||
actions: [
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})
|
||||
],
|
||||
|
|
@ -1405,15 +1406,26 @@ 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 {
|
||||
guard let controller = self.getController() as? GiftViewScreen else {
|
||||
return
|
||||
}
|
||||
|
||||
var gift: StarGift?
|
||||
var selectedAttributes: [StarGift.UniqueGift.Attribute]?
|
||||
if case let .unique(uniqueGift) = arguments.gift {
|
||||
selectedAttributes = uniqueGift.attributes
|
||||
if let arguments = self.subject.arguments {
|
||||
gift = arguments.gift
|
||||
if case let .unique(uniqueGift) = arguments.gift {
|
||||
selectedAttributes = uniqueGift.attributes
|
||||
}
|
||||
} else if case let .upgradePreview(genericGift, _, _) = self.subject {
|
||||
gift = .generic(genericGift)
|
||||
}
|
||||
|
||||
self.giftVariantsDisposable.set((self.context.engine.payments.getStarGiftUpgradeAttributes(giftId: arguments.gift.giftId)
|
||||
guard let gift else {
|
||||
return
|
||||
}
|
||||
|
||||
self.giftVariantsDisposable.set((self.context.engine.payments.getStarGiftUpgradeAttributes(giftId: gift.giftId)
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] attributes in
|
||||
guard let self, let attributes else {
|
||||
|
|
@ -1421,8 +1433,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
let variantsController = self.context.sharedContext.makeGiftUpgradeVariantsScreen(
|
||||
context: self.context,
|
||||
gift: arguments.gift,
|
||||
onlyCrafted: false,
|
||||
gift: gift,
|
||||
crafted: false,
|
||||
attributes: attributes,
|
||||
selectedAttributes: selectedAttributes,
|
||||
focusedAttribute: attribute
|
||||
|
|
@ -2387,8 +2399,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
static var body: Body {
|
||||
let priceButton = Child(PlainButtonComponent.self)
|
||||
|
||||
let buttons = Child(GlassControlPanelComponent.self)
|
||||
let animation = Child(GiftCompositionComponent.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
|
|
@ -2398,9 +2408,9 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let description = Child(MultilineTextComponent.self)
|
||||
let animatedDescription = Child(HStack<Empty>.self)
|
||||
|
||||
let transferButton = Child(PlainButtonComponent.self)
|
||||
let wearButton = Child(PlainButtonComponent.self)
|
||||
let resellButton = Child(PlainButtonComponent.self)
|
||||
let transferButton = Child(HeaderButtonComponent.self)
|
||||
let wearButton = Child(HeaderButtonComponent.self)
|
||||
let resellButton = Child(HeaderButtonComponent.self)
|
||||
|
||||
let wearAvatar = Child(AvatarComponent.self)
|
||||
let wearPeerName = Child(MultilineTextComponent.self)
|
||||
|
|
@ -2420,7 +2430,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let upgradePerks = Child(List<Empty>.self)
|
||||
let upgradeKeepName = Child(PlainButtonComponent.self)
|
||||
let upgradePriceButton = Child(PlainButtonComponent.self)
|
||||
let variantsMeasureDescription = Child(MultilineTextComponent.self)
|
||||
let upgradeDescriptionMeasure = Child(MultilineTextComponent.self)
|
||||
|
||||
let priceButtonMeasure = Child(MultilineTextWithEntitiesComponent.self)
|
||||
let priceButton = Child(GlassBarButtonComponent.self)
|
||||
|
||||
let spaceRegex = try? NSRegularExpression(pattern: "\\[(.*?)\\]", options: [])
|
||||
|
||||
|
|
@ -2592,7 +2605,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
} else if state.inUpgradePreview, let attributes = state.upgradePreview?.attributes {
|
||||
headerHeight = 246.0
|
||||
headerSubject = .preview(attributes)
|
||||
} else if case let .upgradePreview(attributes, _) = component.subject {
|
||||
} else if case let .upgradePreview(_, attributes, _) = component.subject {
|
||||
headerHeight = 246.0
|
||||
headerSubject = .preview(attributes)
|
||||
} else if case let .wearPreview(_, attributes) = component.subject, let attributes {
|
||||
|
|
@ -2606,6 +2619,19 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
headerSubject = nil
|
||||
}
|
||||
|
||||
var buttonsBackground: GlassControlGroupComponent.Background = .panel
|
||||
if let uniqueGift, let backdropAttribute = uniqueGift.attributes.first(where: { attribute in
|
||||
if case .backdrop = attribute {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, _, outerColor, _, _, _) = backdropAttribute {
|
||||
buttonsBackground = .color(UIColor(rgb: UInt32(bitPattern: outerColor)).mixedWith(.white, alpha: 0.2))
|
||||
} else if showUpgradePreview, let backgroundColor = giftCompositionExternalState.backgroundColor {
|
||||
buttonsBackground = .color(backgroundColor.mixedWith(.white, alpha: 0.2))
|
||||
}
|
||||
|
||||
var ownerPeerId: EnginePeer.Id?
|
||||
if let uniqueGift {
|
||||
if case let .peerId(peerId) = uniqueGift.owner {
|
||||
|
|
@ -2719,6 +2745,16 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let tableTextColor = theme.list.itemPrimaryTextColor
|
||||
let tableLinkColor = theme.list.itemAccentColor
|
||||
|
||||
var resellAmount: CurrencyAmount?
|
||||
var selling = false
|
||||
if let uniqueGift {
|
||||
if uniqueGift.resellForTonOnly {
|
||||
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .ton })
|
||||
} else {
|
||||
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })
|
||||
}
|
||||
}
|
||||
|
||||
if let headerSubject {
|
||||
let animation = animation.update(
|
||||
component: GiftCompositionComponent(
|
||||
|
|
@ -2857,7 +2893,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
title = environment.strings.Gift_Upgrade_GiftTitle
|
||||
uniqueText = strings.Gift_Upgrade_Unique_GiftDescription(peerName).string
|
||||
tradableText = strings.Gift_Upgrade_Tradable_GiftDescription(peerName).string
|
||||
} else if case let .upgradePreview(_, peerName) = component.subject {
|
||||
} else if case let .upgradePreview(_, _, peerName) = component.subject {
|
||||
var peerName = peerName
|
||||
if peerName.count > 22 {
|
||||
peerName = "\(peerName.prefix(22))…"
|
||||
|
|
@ -2871,28 +2907,27 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
tradableText = strings.Gift_Upgrade_Tradable_Description
|
||||
}
|
||||
|
||||
let upgradeTitle = upgradeTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: title,
|
||||
font: Font.bold(20.0),
|
||||
textColor: .white,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
var variant1: GiftItemComponent.Subject?
|
||||
var variant2: GiftItemComponent.Subject?
|
||||
var variant3: GiftItemComponent.Subject?
|
||||
|
||||
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 upgradeAttributes: [StarGift.UniqueGift.Attribute]?
|
||||
|
||||
if case let .generic(gift) = component.subject.arguments?.gift {
|
||||
variant1 = .starGift(gift: gift, price: "")
|
||||
variant2 = .starGift(gift: gift, price: "")
|
||||
variant3 = .starGift(gift: gift, price: "")
|
||||
}
|
||||
|
||||
if let upgradePreview = state.upgradePreview {
|
||||
upgradeAttributes = upgradePreview.attributes
|
||||
} else if case let .upgradePreview(_, attributes, _) = component.subject {
|
||||
upgradeAttributes = attributes
|
||||
}
|
||||
|
||||
if let upgradeAttributes {
|
||||
var i = 0
|
||||
for attribute in upgradePreview.attributes {
|
||||
for attribute in upgradeAttributes {
|
||||
if case .model = attribute {
|
||||
switch i {
|
||||
case 0:
|
||||
|
|
@ -2907,23 +2942,39 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let variant1, let variant2, let variant3 {
|
||||
var buttonColor: UIColor = UIColor.white.withAlphaComponent(0.16)
|
||||
if let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||
buttonColor = previewPatternColor
|
||||
if let backgroundColor = giftCompositionExternalState.backgroundColor {
|
||||
buttonColor = backgroundColor.mixedWith(.white, alpha: 0.2)
|
||||
}
|
||||
|
||||
let variantsMeasureDescription = variantsMeasureDescription.update(
|
||||
let upgradeDescriptionMeasure = upgradeDescriptionMeasure.update(
|
||||
component: MultilineTextComponent(text: .plain(NSAttributedString(string: strings.Gift_Upgrade_ViewAllVariants, font: Font.semibold(13.0), textColor: .clear))),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(variantsMeasureDescription
|
||||
context.add(upgradeDescriptionMeasure
|
||||
.position(CGPoint(x: -10000.0, y: -10000.0))
|
||||
)
|
||||
|
||||
let variantsButtonSize = CGSize(width: variantsMeasureDescription.size.width + 87.0, height: 24.0)
|
||||
|
||||
let variantsButtonSize = CGSize(width: upgradeDescriptionMeasure.size.width + 87.0, height: 24.0)
|
||||
|
||||
let upgradeTitle = upgradeTitle.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: title,
|
||||
font: Font.bold(20.0),
|
||||
textColor: .white,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0 - 60.0, height: CGFloat.greatestFiniteMagnitude),
|
||||
transition: .immediate
|
||||
)
|
||||
let upgradeDescription = upgradeDescription.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: variantsButtonSize,
|
||||
|
|
@ -3063,7 +3114,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
originY += 16.0
|
||||
|
||||
if case .upgradePreview = component.subject {
|
||||
|
||||
} else if !incoming {
|
||||
} else {
|
||||
let checkTheme = CheckComponent.Theme(
|
||||
|
|
@ -3120,19 +3170,15 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
var descriptionText: String
|
||||
var hasDescriptionButton = false
|
||||
if let uniqueGift {
|
||||
titleString = uniqueGift.title
|
||||
var isCrafted = false
|
||||
titleString = uniqueGift.title + " **#\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))**"
|
||||
descriptionText = "\(strings.Gift_Unique_Collectible) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
for attribute in uniqueGift.attributes {
|
||||
if case let .model(_, _, _, crafted) = attribute {
|
||||
isCrafted = crafted
|
||||
if case let .model(name, _, _, _) = attribute {
|
||||
descriptionText = name
|
||||
}
|
||||
}
|
||||
//TODO:localize
|
||||
let prefix: String = isCrafted ? "Crafted Collectible" : strings.Gift_Unique_Collectible
|
||||
descriptionText = "\(prefix) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))"
|
||||
|
||||
if let releasedBy = uniqueGift.releasedBy, let peer = state.peerMap[releasedBy], let addressName = peer.addressName {
|
||||
descriptionText = strings.Gift_Unique_CollectibleBy("#\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))", "[@\(addressName)]()").string
|
||||
descriptionText = strings.Gift_View_ReleasedBy("[@\(addressName)]()").string
|
||||
hasDescriptionButton = true
|
||||
releasedByPeer = peer
|
||||
}
|
||||
|
|
@ -3204,14 +3250,24 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
descriptionText = modifiedString
|
||||
}
|
||||
|
||||
let titleFont = Font.bold(20.0)
|
||||
let smallTitleFont = Font.bold(15.0)
|
||||
let titleAttributedString: NSAttributedString
|
||||
if let uniqueGift {
|
||||
let numberFont = uniqueGift.number < 1000 ? titleFont : smallTitleFont
|
||||
titleAttributedString = parseMarkdownIntoAttributedString(titleString, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: titleFont, textColor: .white), bold: MarkdownAttributeSet(font: numberFont, textColor: vibrantColor), link: MarkdownAttributeSet(font: titleFont, textColor: .white), linkAttribute: { _ in return nil }), textAlignment: .center)
|
||||
} else {
|
||||
titleAttributedString = NSAttributedString(
|
||||
string: titleString,
|
||||
font: titleFont,
|
||||
textColor: theme.actionSheet.primaryTextColor,
|
||||
paragraphAlignment: .center
|
||||
)
|
||||
}
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: titleString,
|
||||
font: Font.bold(20.0),
|
||||
textColor: uniqueGift != nil ? .white : theme.actionSheet.primaryTextColor,
|
||||
paragraphAlignment: .center
|
||||
)),
|
||||
text: .plain(titleAttributedString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
|
|
@ -3269,6 +3325,88 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
})
|
||||
descriptionOffset += subtitle.size.height
|
||||
}
|
||||
|
||||
if let resellAmount {
|
||||
if incoming || ownerPeerId == component.context.account.peerId {
|
||||
var valueString = formatCurrencyAmountText(resellAmount, dateTimeFormat: environment.dateTimeFormat)
|
||||
switch resellAmount.currency {
|
||||
case .stars:
|
||||
valueString = "⭐️\(valueString)"
|
||||
case .ton:
|
||||
valueString = "💎\(valueString)"
|
||||
}
|
||||
let priceButtonAttributedString = NSMutableAttributedString(string: strings.Gift_View_OnSale(valueString).string, font: Font.regular(13.0), textColor: .white)
|
||||
let starRange = (priceButtonAttributedString.string as NSString).range(of: "⭐️")
|
||||
if starRange.location != NSNotFound {
|
||||
priceButtonAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: true)), range: starRange)
|
||||
priceButtonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: starRange)
|
||||
}
|
||||
let tonRange = (priceButtonAttributedString.string as NSString).range(of: "💎")
|
||||
if tonRange.location != NSNotFound {
|
||||
priceButtonAttributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 1, file: nil, custom: .ton(tinted: true)), range: tonRange)
|
||||
priceButtonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: tonRange)
|
||||
}
|
||||
|
||||
let priceButtonMeasure = priceButtonMeasure.update(
|
||||
component: MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
text: .plain(priceButtonAttributedString)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(priceButtonMeasure
|
||||
.position(CGPoint(x: -10000.0, y: -10000.0))
|
||||
)
|
||||
|
||||
var buttonColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
|
||||
if case let .color(color) = buttonsBackground {
|
||||
buttonColor = color
|
||||
}
|
||||
|
||||
let priceButtonSize = CGSize(width: priceButtonMeasure.size.width + 18.0, height: 19.0)
|
||||
let priceButton = priceButton.update(
|
||||
component: GlassBarButtonComponent(
|
||||
size: priceButtonSize,
|
||||
backgroundColor: buttonColor,
|
||||
isDark: true,
|
||||
state: .tintedGlass,
|
||||
component: AnyComponentWithIdentity(id: "label", component: AnyComponent(
|
||||
MultilineTextWithEntitiesComponent(
|
||||
context: component.context,
|
||||
animationCache: component.context.animationCache,
|
||||
animationRenderer: component.context.animationRenderer,
|
||||
placeholderColor: .white,
|
||||
text: .plain(priceButtonAttributedString)
|
||||
)
|
||||
)),
|
||||
action: { [weak state] _ in
|
||||
state?.resellGift(update: true)
|
||||
}
|
||||
),
|
||||
availableSize: priceButtonSize,
|
||||
transition: context.transition
|
||||
)
|
||||
headerComponents.append({
|
||||
context.add(priceButton
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 207.0 + descriptionOffset + priceButton.size.height / 2.0))
|
||||
.appear(.default(scale: true, alpha: true))
|
||||
.disappear(.default(scale: true, alpha: true))
|
||||
)
|
||||
})
|
||||
|
||||
descriptionText = ""
|
||||
originY += 7.0
|
||||
}
|
||||
if case let .uniqueGift(_, recipientPeerId) = component.subject, recipientPeerId != nil {
|
||||
} else if ownerPeerId != component.context.account.peerId {
|
||||
selling = true
|
||||
}
|
||||
}
|
||||
|
||||
var useDescriptionTint = false
|
||||
if !descriptionText.isEmpty {
|
||||
var linkColor = theme.actionSheet.controlAccentColor
|
||||
|
|
@ -3756,18 +3894,19 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
let buttonWidth = floor(context.availableSize.width - sideInset * 2.0 - buttonSpacing * CGFloat(buttonsCount - 1)) / CGFloat(buttonsCount)
|
||||
let buttonHeight: CGFloat = 58.0
|
||||
|
||||
var buttonColor: UIColor = UIColor(rgb: 0xffffff, alpha: 0.1)
|
||||
if case let .color(color) = buttonsBackground {
|
||||
buttonColor = color
|
||||
}
|
||||
|
||||
var buttonOriginX = sideInset
|
||||
if canTransfer {
|
||||
let transferButton = transferButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HeaderButtonComponent(
|
||||
title: strings.Gift_View_Header_Transfer,
|
||||
iconName: "Premium/Collectible/Transfer",
|
||||
isLocked: isMyHostedUniqueGift
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
component: HeaderButtonComponent(
|
||||
title: strings.Gift_View_Header_Transfer,
|
||||
buttonColor: buttonColor,
|
||||
iconName: "Premium/Collectible/Transfer",
|
||||
isLocked: isMyHostedUniqueGift,
|
||||
action: { [weak state] in
|
||||
state?.transferGift()
|
||||
}
|
||||
|
|
@ -3788,19 +3927,15 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
}
|
||||
|
||||
let wearButton = wearButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HeaderButtonComponent(
|
||||
title: isWearing ? strings.Gift_View_Header_TakeOff : strings.Gift_View_Header_Wear,
|
||||
iconName: isWearing ? "Premium/Collectible/Unwear" : "Premium/Collectible/Wear"
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
component: HeaderButtonComponent(
|
||||
title: isWearing ? strings.Gift_View_Header_TakeOff : strings.Gift_View_Header_Wear,
|
||||
buttonColor: buttonColor,
|
||||
iconName: isWearing ? "Premium/Collectible/Unwear" : "Premium/Collectible/Wear",
|
||||
action: { [weak state] in
|
||||
if let state {
|
||||
if isWearing {
|
||||
state.commitTakeOff()
|
||||
|
||||
|
||||
state.showAttributeInfo(tag: state.statusTag, text: strings.Gift_View_TookOff("\(uniqueGift.title) #\(formatCollectibleNumber(uniqueGift.number, dateTimeFormat: environment.dateTimeFormat))").string)
|
||||
} else {
|
||||
if let controller = controller() as? GiftViewScreen {
|
||||
|
|
@ -3820,7 +3955,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
canWear = component.context.isPremium
|
||||
}
|
||||
let _ = (ApplicationSpecificNotice.getStarGiftWearTips(accountManager: component.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).start(next: { [weak state] count in
|
||||
|> deliverOnMainQueue).start(next: { [weak state] count in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
|
|
@ -3851,15 +3986,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
|
||||
if canResell {
|
||||
let resellButton = resellButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
HeaderButtonComponent(
|
||||
title: (uniqueGift.resellAmounts ?? []).isEmpty ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
|
||||
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist",
|
||||
isLocked: isMyHostedUniqueGift
|
||||
)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
component: HeaderButtonComponent(
|
||||
title: (uniqueGift.resellAmounts ?? []).isEmpty ? strings.Gift_View_Sell : strings.Gift_View_Unlist,
|
||||
buttonColor: buttonColor,
|
||||
iconName: (uniqueGift.resellAmounts ?? []).isEmpty ? "Premium/Collectible/Sell" : "Premium/Collectible/Unlist",
|
||||
isLocked: isMyHostedUniqueGift,
|
||||
action: { [weak state] in
|
||||
state?.resellGift()
|
||||
}
|
||||
|
|
@ -4103,14 +4234,17 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
badgeString = formatPercentage(Float(value) * 0.1)
|
||||
}
|
||||
case .epic:
|
||||
badgeString = "epic"
|
||||
badgeString = strings.Gift_Attribute_Epic
|
||||
badgeColor = UIColor(rgb: 0xaf52de)
|
||||
case .legendary:
|
||||
badgeString = "legendary"
|
||||
badgeString = strings.Gift_Attribute_Legendary
|
||||
badgeColor = UIColor(rgb: 0xd57e32)
|
||||
case .rare:
|
||||
badgeString = "rare"
|
||||
badgeColor = UIColor(rgb: 0x79993d)
|
||||
badgeString = strings.Gift_Attribute_Rare
|
||||
badgeColor = UIColor(rgb: 0x25a3b9)
|
||||
case .uncommon:
|
||||
badgeString = strings.Gift_Attribute_Uncommon
|
||||
badgeColor = UIColor(rgb: 0x22b447)
|
||||
}
|
||||
items.append(AnyComponentWithIdentity(
|
||||
id: AnyHashable(1),
|
||||
|
|
@ -4406,44 +4540,6 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
component()
|
||||
}
|
||||
|
||||
var resellAmount: CurrencyAmount?
|
||||
var selling = false
|
||||
if let uniqueGift {
|
||||
if uniqueGift.resellForTonOnly {
|
||||
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .ton })
|
||||
} else {
|
||||
resellAmount = uniqueGift.resellAmounts?.first(where: { $0.currency == .stars })
|
||||
}
|
||||
if let resellAmount, wearPeerNameChild == nil {
|
||||
if incoming || ownerPeerId == component.context.account.peerId {
|
||||
let priceButton = priceButton.update(
|
||||
component: PlainButtonComponent(
|
||||
content: AnyComponent(
|
||||
PriceButtonComponent(price: resellAmount, dateTimeFormat: environment.dateTimeFormat)
|
||||
),
|
||||
effectAlignment: .center,
|
||||
action: { [weak state] in
|
||||
state?.resellGift(update: true)
|
||||
},
|
||||
animateScale: false
|
||||
),
|
||||
availableSize: CGSize(width: 150.0, height: 30.0),
|
||||
transition: context.transition
|
||||
)
|
||||
context.add(priceButton
|
||||
.position(CGPoint(x: environment.safeInsets.left + 16.0 + priceButton.size.width / 2.0, y: 76.0 + priceButton.size.height / 2.0))
|
||||
.appear(.default(scale: true, alpha: true))
|
||||
.disappear(.default(scale: true, alpha: true))
|
||||
)
|
||||
}
|
||||
if case let .uniqueGift(_, recipientPeerId) = component.subject, recipientPeerId != nil {
|
||||
} else if ownerPeerId != component.context.account.peerId {
|
||||
selling = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var isChatTheme = false
|
||||
if let controller = controller() as? GiftViewScreen, controller.openChatTheme != nil {
|
||||
isChatTheme = true
|
||||
|
|
@ -5121,20 +5217,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
)
|
||||
originY += upgradePriceButton.size.height
|
||||
}
|
||||
|
||||
var buttonsBackground: GlassControlGroupComponent.Background = .panel
|
||||
if let uniqueGift, let backdropAttribute = uniqueGift.attributes.first(where: { attribute in
|
||||
if case .backdrop = attribute {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}), case let .backdrop(_, _, innerColor, _, _, _, _) = backdropAttribute {
|
||||
buttonsBackground = .color(UIColor(rgb: UInt32(bitPattern: innerColor)).withMultipliedBrightnessBy(1.05))
|
||||
} else if showUpgradePreview, let previewPatternColor = giftCompositionExternalState.previewPatternColor {
|
||||
buttonsBackground = .color(previewPatternColor.withMultipliedBrightnessBy(1.05))
|
||||
}
|
||||
|
||||
|
||||
var isBackButton = false
|
||||
if state.inWearPreview || state.inUpgradePreview {
|
||||
isBackButton = true
|
||||
|
|
@ -5164,16 +5247,27 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
var rightControlItems: [GlassControlGroupComponent.Item] = []
|
||||
if uniqueGift != nil && !showWearPreview {
|
||||
if let _ = component.subject.arguments?.canCraftDate {
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("craft"),
|
||||
content: .icon("Premium/Craft"),
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.craftGift()
|
||||
var canCraft = false
|
||||
if let data = component.context.currentAppConfiguration.with({ $0 }).data {
|
||||
if let _ = data["ios_enable_crafting"] {
|
||||
canCraft = true
|
||||
} else if let isDev = data["dev"] as? Double, isDev == 1.0 {
|
||||
canCraft = true
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
if canCraft {
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
id: AnyHashable("craft"),
|
||||
content: .icon("Premium/Craft"),
|
||||
action: { [weak state] in
|
||||
guard let state else {
|
||||
return
|
||||
}
|
||||
state.craftGift()
|
||||
}
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
rightControlItems.append(GlassControlGroupComponent.Item(
|
||||
|
|
@ -5188,6 +5282,11 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
))
|
||||
}
|
||||
|
||||
var buttonsIsDark = theme.overallDarkAppearance
|
||||
if case .color = buttonsBackground {
|
||||
buttonsIsDark = true
|
||||
}
|
||||
|
||||
let buttons = buttons.update(
|
||||
component: GlassControlPanelComponent(
|
||||
theme: theme,
|
||||
|
|
@ -5201,6 +5300,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
|||
background: buttonsBackground
|
||||
),
|
||||
centerAlignmentIfPossible: true,
|
||||
isDark: buttonsIsDark,
|
||||
tag: state.controlButtonsTag
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 16.0 * 2.0, height: 44.0),
|
||||
|
|
@ -5396,7 +5496,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
case uniqueGift(StarGift.UniqueGift, EnginePeer.Id?)
|
||||
case profileGift(EnginePeer.Id, ProfileGiftsContext.State.StarGift)
|
||||
case soldOutGift(StarGift.Gift)
|
||||
case upgradePreview([StarGift.UniqueGift.Attribute], String)
|
||||
case upgradePreview(StarGift.Gift, [StarGift.UniqueGift.Attribute], String)
|
||||
case wearPreview(StarGift, [StarGift.UniqueGift.Attribute]?)
|
||||
|
||||
var arguments: (
|
||||
|
|
@ -5449,7 +5549,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
|
|||
|
||||
let fromPeerId = senderId ?? message.author?.id
|
||||
return (message.id.peerId, fromPeerId, message.author?.debugDisplayTitle, message.author?.compactDisplayTitle, message.id, reference, message.flags.contains(.Incoming), gift, message.timestamp, convertStars, text, entities, nameHidden, savedToProfile, nil, converted, upgraded, isRefunded, canUpgrade, upgradeStars, nil, nil, nil, upgradeMessageId, nil, nil, prepaidUpgradeHash, upgradeSeparate, nil, toPeerId, number, nil)
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftDate):
|
||||
case let .starGiftUnique(gift, isUpgrade, isTransferred, savedToProfile, canExportDate, transferStars, _, _, peerId, senderId, savedId, _, canTransferDate, canResaleDate, dropOriginalDetailsStars, _, _, canCraftDate, _):
|
||||
var reference: StarGiftReference
|
||||
if let peerId, let savedId {
|
||||
reference = .peer(peerId: peerId, id: savedId)
|
||||
|
|
@ -6050,25 +6150,34 @@ private final class GiftViewContextReferenceContentSource: ContextReferenceConte
|
|||
}
|
||||
}
|
||||
|
||||
private final class HeaderButtonComponent: CombinedComponent {
|
||||
private final class HeaderButtonComponent: Component {
|
||||
let title: String
|
||||
let buttonColor: UIColor
|
||||
let iconName: String
|
||||
let isLocked: Bool
|
||||
let action: () -> Void
|
||||
|
||||
public init(
|
||||
title: String,
|
||||
buttonColor: UIColor,
|
||||
iconName: String,
|
||||
isLocked: Bool = false
|
||||
isLocked: Bool = false,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.buttonColor = buttonColor
|
||||
self.iconName = iconName
|
||||
self.isLocked = isLocked
|
||||
self.action = action
|
||||
}
|
||||
|
||||
static func ==(lhs: HeaderButtonComponent, rhs: HeaderButtonComponent) -> Bool {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.buttonColor != rhs.buttonColor {
|
||||
return false
|
||||
}
|
||||
if lhs.iconName != rhs.iconName {
|
||||
return false
|
||||
}
|
||||
|
|
@ -6078,79 +6187,205 @@ private final class HeaderButtonComponent: CombinedComponent {
|
|||
return true
|
||||
}
|
||||
|
||||
static var body: Body {
|
||||
let background = Child(RoundedRectangle.self)
|
||||
let title = Child(MultilineTextComponent.self)
|
||||
let icon = Child(BundleIconComponent.self)
|
||||
let lockIcon = Child(BundleIconComponent.self)
|
||||
final class View: UIView {
|
||||
private var component: HeaderButtonComponent?
|
||||
private weak var componentState: EmptyComponentState?
|
||||
|
||||
return { context in
|
||||
let component = context.component
|
||||
private let backgroundView = GlassBackgroundView()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let lockIcon = ComponentView<Empty>()
|
||||
private let button = HighlightTrackingButton()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
let background = background.update(
|
||||
component: RoundedRectangle(
|
||||
color: UIColor.white.withAlphaComponent(0.16),
|
||||
cornerRadius: 16.0
|
||||
self.addSubview(self.backgroundView)
|
||||
self.backgroundView.contentView.addSubview(self.button)
|
||||
|
||||
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
if let component = self.component {
|
||||
component.action()
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: HeaderButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.component = component
|
||||
self.componentState = state
|
||||
|
||||
let bounds = CGRect(origin: .zero, size: availableSize)
|
||||
|
||||
self.backgroundView.update(size: bounds.size, cornerRadius: 16.0, isDark: true, tintColor: .init(kind: .custom(style: .default, color: component.buttonColor)), isInteractive: true, transition: transition)
|
||||
transition.setFrame(view: self.backgroundView, frame: bounds)
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: component.iconName,
|
||||
tintColor: UIColor.white
|
||||
)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
context.add(background
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
iconView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - iconSize.width) / 2.0), y: floorToScreenPixels(22.0 - iconSize.height * 0.5)), size: iconSize)
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.title,
|
||||
font: Font.regular(11.0),
|
||||
textColor: UIColor.white,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
)
|
||||
),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - 16.0, height: availableSize.height)
|
||||
)
|
||||
|
||||
let icon = icon.update(
|
||||
component: BundleIconComponent(
|
||||
name: component.iconName,
|
||||
tintColor: UIColor.white
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
)
|
||||
context.add(icon
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: 22.0))
|
||||
)
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: component.title,
|
||||
font: Font.regular(11.0),
|
||||
textColor: UIColor.white,
|
||||
paragraphAlignment: .natural
|
||||
)),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 1
|
||||
),
|
||||
availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height),
|
||||
transition: .immediate
|
||||
)
|
||||
var totalTitleWidth = title.size.width
|
||||
var titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
|
||||
var totalTitleWidth = titleSize.width
|
||||
var titleOriginX = availableSize.width / 2.0 - totalTitleWidth / 2.0
|
||||
if component.isLocked {
|
||||
let titleSpacing: CGFloat = 3.0
|
||||
let lockIcon = lockIcon.update(
|
||||
component: BundleIconComponent(
|
||||
name: "Chat List/StatusLockIcon",
|
||||
tintColor: UIColor.white
|
||||
|
||||
let lockIconSize = self.lockIcon.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(
|
||||
BundleIconComponent(
|
||||
name: "Chat List/StatusLockIcon",
|
||||
tintColor: .white
|
||||
)
|
||||
),
|
||||
availableSize: context.availableSize,
|
||||
transition: .immediate
|
||||
environment: {},
|
||||
containerSize: availableSize
|
||||
)
|
||||
totalTitleWidth += lockIcon.size.width + titleSpacing
|
||||
titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
|
||||
context.add(lockIcon
|
||||
.position(CGPoint(x: titleOriginX + lockIcon.size.width / 2.0, y: 42.0))
|
||||
)
|
||||
titleOriginX += lockIcon.size.width + titleSpacing
|
||||
totalTitleWidth += lockIconSize.width + titleSpacing
|
||||
titleOriginX = availableSize.width / 2.0 - totalTitleWidth / 2.0
|
||||
|
||||
if let lockIconView = self.lockIcon.view {
|
||||
if lockIconView.superview == nil {
|
||||
lockIconView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(lockIconView)
|
||||
}
|
||||
lockIconView.frame = CGRect(origin: CGPoint(x: titleOriginX, y: floorToScreenPixels(42.0 - lockIconSize.height * 0.5)), size: lockIconSize)
|
||||
}
|
||||
titleOriginX += lockIconSize.width + titleSpacing
|
||||
}
|
||||
context.add(title
|
||||
.position(CGPoint(x: titleOriginX + title.size.width / 2.0, y: 42.0))
|
||||
)
|
||||
|
||||
return context.availableSize
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
titleView.isUserInteractionEnabled = false
|
||||
self.backgroundView.contentView.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = CGRect(origin: CGPoint(x: titleOriginX, y: floorToScreenPixels(42.0 - titleSize.height * 0.5)), size: titleSize)
|
||||
}
|
||||
|
||||
self.button.frame = bounds
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// static var body: Body {
|
||||
// let background = Child(RoundedRectangle.self)
|
||||
// let title = Child(MultilineTextComponent.self)
|
||||
// let icon = Child(BundleIconComponent.self)
|
||||
// let lockIcon = Child(BundleIconComponent.self)
|
||||
//
|
||||
// return { context in
|
||||
// let component = context.component
|
||||
//
|
||||
// let background = background.update(
|
||||
// component: RoundedRectangle(
|
||||
// color: UIColor.white.withAlphaComponent(0.16),
|
||||
// cornerRadius: 16.0
|
||||
// ),
|
||||
// availableSize: context.availableSize,
|
||||
// transition: .immediate
|
||||
// )
|
||||
// context.add(background
|
||||
// .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||
// )
|
||||
//
|
||||
// let icon = icon.update(
|
||||
// component: BundleIconComponent(
|
||||
// name: component.iconName,
|
||||
// tintColor: UIColor.white
|
||||
// ),
|
||||
// availableSize: context.availableSize,
|
||||
// transition: .immediate
|
||||
// )
|
||||
// context.add(icon
|
||||
// .position(CGPoint(x: context.availableSize.width / 2.0, y: 22.0))
|
||||
// )
|
||||
//
|
||||
// let title = title.update(
|
||||
// component: MultilineTextComponent(
|
||||
// text: .plain(NSAttributedString(
|
||||
// string: component.title,
|
||||
// font: Font.regular(11.0),
|
||||
// textColor: UIColor.white,
|
||||
// paragraphAlignment: .natural
|
||||
// )),
|
||||
// horizontalAlignment: .center,
|
||||
// maximumNumberOfLines: 1
|
||||
// ),
|
||||
// availableSize: CGSize(width: context.availableSize.width - 16.0, height: context.availableSize.height),
|
||||
// transition: .immediate
|
||||
// )
|
||||
// var totalTitleWidth = title.size.width
|
||||
// var titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
|
||||
// if component.isLocked {
|
||||
// let titleSpacing: CGFloat = 3.0
|
||||
// let lockIcon = lockIcon.update(
|
||||
// component: BundleIconComponent(
|
||||
// name: "Chat List/StatusLockIcon",
|
||||
// tintColor: UIColor.white
|
||||
// ),
|
||||
// availableSize: context.availableSize,
|
||||
// transition: .immediate
|
||||
// )
|
||||
// totalTitleWidth += lockIcon.size.width + titleSpacing
|
||||
// titleOriginX = context.availableSize.width / 2.0 - totalTitleWidth / 2.0
|
||||
// context.add(lockIcon
|
||||
// .position(CGPoint(x: titleOriginX + lockIcon.size.width / 2.0, y: 42.0))
|
||||
// )
|
||||
// titleOriginX += lockIcon.size.width + titleSpacing
|
||||
// }
|
||||
// context.add(title
|
||||
// .position(CGPoint(x: titleOriginX + title.size.width / 2.0, y: 42.0))
|
||||
// )
|
||||
//
|
||||
// return context.availableSize
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private struct GiftViewConfiguration {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,31 @@ import LottieComponent
|
|||
|
||||
public final class GlassControlGroupComponent: Component {
|
||||
public final class Item: Equatable {
|
||||
public enum Content: Hashable {
|
||||
public enum Content: Equatable {
|
||||
case icon(String)
|
||||
case text(String)
|
||||
case animation(String)
|
||||
case customIcon(id: AnyHashable, component: AnyComponent<Empty>)
|
||||
|
||||
enum Id: Hashable {
|
||||
case icon(String)
|
||||
case text(String)
|
||||
case animation(String)
|
||||
case customIcon(AnyHashable)
|
||||
}
|
||||
|
||||
var id: Id {
|
||||
switch self {
|
||||
case let .icon(icon):
|
||||
return .icon(icon)
|
||||
case let .text(text):
|
||||
return .text(text)
|
||||
case let .animation(animation):
|
||||
return .animation(animation)
|
||||
case let .customIcon(id, _):
|
||||
return .customIcon(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public let id: AnyHashable
|
||||
|
|
@ -143,7 +164,7 @@ public final class GlassControlGroupComponent: Component {
|
|||
var validIds: [AnyHashable] = []
|
||||
var isInteractive = false
|
||||
for item in component.items {
|
||||
let itemId = ItemId(id: item.id, contentId: item.content)
|
||||
let itemId = ItemId(id: item.id, contentId: item.content.id)
|
||||
|
||||
validIds.append(itemId)
|
||||
|
||||
|
|
@ -160,7 +181,7 @@ public final class GlassControlGroupComponent: Component {
|
|||
if item.action != nil {
|
||||
isInteractive = true
|
||||
}
|
||||
|
||||
|
||||
let content: AnyComponent<Empty>
|
||||
var itemInsets = UIEdgeInsets()
|
||||
switch item.content {
|
||||
|
|
@ -189,6 +210,8 @@ public final class GlassControlGroupComponent: Component {
|
|||
size: CGSize(width: 32.0, height: 32.0),
|
||||
playOnce: playOnce
|
||||
))
|
||||
case let .customIcon(_, customIcon):
|
||||
content = customIcon
|
||||
}
|
||||
|
||||
var minItemWidth: CGFloat = availableSize.height
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ public final class GlassControlPanelComponent: Component {
|
|||
public let rightItem: Item?
|
||||
public let centralItem: Item?
|
||||
public let centerAlignmentIfPossible: Bool
|
||||
public let isDark: Bool?
|
||||
public let tag: AnyObject?
|
||||
|
||||
public init(
|
||||
|
|
@ -44,6 +45,7 @@ public final class GlassControlPanelComponent: Component {
|
|||
centralItem: Item?,
|
||||
rightItem: Item?,
|
||||
centerAlignmentIfPossible: Bool = false,
|
||||
isDark: Bool? = nil,
|
||||
tag: AnyObject? = nil
|
||||
) {
|
||||
self.theme = theme
|
||||
|
|
@ -51,6 +53,7 @@ public final class GlassControlPanelComponent: Component {
|
|||
self.centralItem = centralItem
|
||||
self.rightItem = rightItem
|
||||
self.centerAlignmentIfPossible = centerAlignmentIfPossible
|
||||
self.isDark = isDark
|
||||
self.tag = tag
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +73,9 @@ public final class GlassControlPanelComponent: Component {
|
|||
if lhs.centerAlignmentIfPossible != rhs.centerAlignmentIfPossible {
|
||||
return false
|
||||
}
|
||||
if lhs.isDark != rhs.isDark {
|
||||
return false
|
||||
}
|
||||
if lhs.tag !== rhs.tag {
|
||||
return false
|
||||
}
|
||||
|
|
@ -298,7 +304,7 @@ public final class GlassControlPanelComponent: Component {
|
|||
}
|
||||
|
||||
transition.setFrame(view: self.glassContainerView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
self.glassContainerView.update(size: availableSize, isDark: component.theme.overallDarkAppearance, transition: transition)
|
||||
self.glassContainerView.update(size: availableSize, isDark: component.isDark ?? component.theme.overallDarkAppearance, transition: transition)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3734,7 +3734,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
|
|||
}
|
||||
} else if case let .gift(gift) = subject {
|
||||
isGift = true
|
||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil, assigned: false, fromOffer: false, canCraftAt: nil))]
|
||||
let media: [Media] = [TelegramMediaAction(action: .starGiftUnique(gift: .unique(gift), isUpgrade: false, isTransferred: false, savedToProfile: false, canExportDate: nil, transferStars: nil, isRefunded: false, isPrepaidUpgrade: false, peerId: nil, senderId: nil, savedId: nil, resaleAmount: nil, canTransferDate: nil, canResaleDate: nil, dropOriginalDetailsStars: nil, assigned: false, fromOffer: false, canCraftAt: nil, isCrafted: false))]
|
||||
let message = Message(stableId: 0, stableVersion: 0, id: MessageId(peerId: self.context.account.peerId, namespace: Namespaces.Message.Cloud, id: -1), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 0, flags: [], tags: [], globalTags: [], localTags: [], customTags: [], forwardInfo: nil, author: nil, text: "", attributes: [], media: media, peers: SimpleDictionary(), associatedMessages: SimpleDictionary(), associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil, associatedStories: [:])
|
||||
messages = .single([message])
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -197,6 +197,8 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
}
|
||||
|
||||
private let backgroundNode: ASDisplayNode
|
||||
private let highlightNode: ASDisplayNode
|
||||
|
||||
private let topStripeNode: ASDisplayNode
|
||||
private let bottomStripeNode: ASDisplayNode
|
||||
private let maskNode: ASImageNode
|
||||
|
|
@ -225,6 +227,9 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
|
||||
self.highlightNode = ASDisplayNode()
|
||||
self.highlightNode.isLayerBacked = true
|
||||
|
||||
self.topStripeNode = ASDisplayNode()
|
||||
self.topStripeNode.isLayerBacked = true
|
||||
|
||||
|
|
@ -297,6 +302,20 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
self.item?.openSetCustom?()
|
||||
}
|
||||
|
||||
public func displayHighlight() {
|
||||
if self.backgroundNode.supernode != nil {
|
||||
self.insertSubnode(self.highlightNode, aboveSubnode: self.backgroundNode)
|
||||
} else {
|
||||
self.insertSubnode(self.highlightNode, at: 0)
|
||||
}
|
||||
|
||||
Queue.mainQueue().after(1.2, {
|
||||
self.highlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in
|
||||
self.highlightNode.removeFromSupernode()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func asyncLayout() -> (_ item: MessagePriceItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
|
||||
let currentItem = self.item
|
||||
|
||||
|
|
@ -329,6 +348,7 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
|
||||
strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
|
||||
strongSelf.highlightNode.backgroundColor = item.theme.list.itemSearchHighlightColor
|
||||
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
|
|
@ -370,6 +390,7 @@ private class MessagePriceItemNode: ListViewItemNode, ItemListItemNode {
|
|||
strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners, glass: item.systemStyle == .glass) : nil
|
||||
|
||||
strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.highlightNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
|
||||
strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
|
||||
strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
|
||||
strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset - params.rightInset - separatorRightInset, height: separatorHeight))
|
||||
|
|
|
|||
|
|
@ -587,8 +587,7 @@ public final class PeerInfoCoverComponent: Component {
|
|||
self.avatarPatternContentLayers.append(itemLayer)
|
||||
}
|
||||
|
||||
//itemLayer.frame = itemFrame
|
||||
transition.setFrame(layer: itemLayer, frame: itemFrame)
|
||||
itemLayer.frame = itemFrame
|
||||
itemLayer.layerTintColor = UIColor(white: 0.0, alpha: 0.8).cgColor
|
||||
transition.setAlpha(layer: itemLayer, alpha: 1.0 - itemScaleFraction)
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ private func generateNumberOffsets() -> [CGPoint] {
|
|||
CGPoint(x: 0.9900000000000001, y: 0.0),
|
||||
CGPoint(x: 0.66, y: 0.0),
|
||||
CGPoint(x: 0.5775, y: 0.0),
|
||||
CGPoint(x: 1.2375, y: 0.0),
|
||||
CGPoint(x: 0.7425, y: 0.0),
|
||||
CGPoint(x: 0.9900000000000001, y: 0.0),
|
||||
CGPoint(x: 1.32, y: 0.0),
|
||||
CGPoint(x: 1.155, y: 0.0),
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
let tipsPeerDisposable = MetaDisposable()
|
||||
|
||||
let cachedFaq = Promise<ResolvedUrl?>(nil)
|
||||
private var didSetCachedFaq = false
|
||||
var didSetCachedFaq = false
|
||||
|
||||
weak var copyProtectionTooltipController: TooltipController?
|
||||
weak var emojiStatusSelectionController: ViewController?
|
||||
|
|
@ -4804,10 +4804,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
|||
self.headerNode.navigationButtonContainer.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
||||
|
||||
if self.isSettings {
|
||||
if !self.didSetCachedFaq {
|
||||
self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init)))
|
||||
self.didSetCachedFaq = true
|
||||
}
|
||||
self.setupFaqIfNeeded()
|
||||
|
||||
if let settings = self.data?.globalSettings {
|
||||
self.searchDisplayController = SearchDisplayController(
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import PremiumUI
|
|||
import TelegramPresentationData
|
||||
import PresentationDataUtils
|
||||
import PasswordSetupUI
|
||||
import InstantPageCache
|
||||
|
||||
extension PeerInfoScreenNode {
|
||||
func openSettings(section: PeerInfoSettingsSection) {
|
||||
|
|
@ -298,7 +299,16 @@ extension PeerInfoScreenNode {
|
|||
}
|
||||
}
|
||||
|
||||
func setupFaqIfNeeded() {
|
||||
if !self.didSetCachedFaq {
|
||||
self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init)))
|
||||
self.didSetCachedFaq = true
|
||||
}
|
||||
}
|
||||
|
||||
func openFaq(anchor: String? = nil) {
|
||||
self.setupFaqIfNeeded()
|
||||
|
||||
let presentationData = self.presentationData
|
||||
let progressSignal = Signal<Never, NoError> { [weak self] subscriber in
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
|
|
@ -314,6 +324,7 @@ extension PeerInfoScreenNode {
|
|||
let progressDisposable = progressSignal.start()
|
||||
|
||||
let _ = (self.cachedFaq.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] resolvedUrl in
|
||||
progressDisposable.dispose()
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ public final class PlainButtonComponent: Component {
|
|||
super.init(frame: frame)
|
||||
|
||||
self.isExclusiveTouch = true
|
||||
self.layer.rasterizationScale = UIScreenScale
|
||||
|
||||
self.contentContainer.isUserInteractionEnabled = false
|
||||
self.addSubview(self.contentContainer)
|
||||
|
|
@ -130,8 +131,13 @@ public final class PlainButtonComponent: Component {
|
|||
self.contentContainer.alpha = 0.7
|
||||
}
|
||||
if animateScale {
|
||||
self.layer.shouldRasterize = true
|
||||
let transition = ComponentTransition(animation: .curve(duration: 0.2, curve: .easeInOut))
|
||||
transition.setScale(layer: self.contentContainer.layer, scale: topScale)
|
||||
transition.setScale(layer: self.contentContainer.layer, scale: topScale, completion: { finished in
|
||||
if finished {
|
||||
self.layer.shouldRasterize = false
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if animateAlpha {
|
||||
|
|
@ -140,15 +146,21 @@ public final class PlainButtonComponent: Component {
|
|||
}
|
||||
|
||||
if animateScale {
|
||||
self.layer.shouldRasterize = true
|
||||
|
||||
let transition = ComponentTransition(animation: .none)
|
||||
transition.setScale(layer: self.contentContainer.layer, scale: 1.0)
|
||||
|
||||
|
||||
self.contentContainer.layer.animateScale(from: topScale, to: maxScale, duration: 0.13, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, removeOnCompletion: false, completion: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue)
|
||||
self.contentContainer.layer.animateScale(from: maxScale, to: 1.0, duration: 0.1, timingFunction: CAMediaTimingFunctionName.easeIn.rawValue, completion: { finished in
|
||||
if finished {
|
||||
self.layer.shouldRasterize = false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,15 +195,14 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
commit()
|
||||
return
|
||||
}
|
||||
//TODO:localize
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let alertController = textAlertController(
|
||||
context: context,
|
||||
title: "Warning",
|
||||
text: "This action will expose your IP address to the admin of the proxy server.",
|
||||
title: presentationData.strings.SocksProxySetup_Warning_Title,
|
||||
text: presentationData.strings.SocksProxySetup_Warning_Text,
|
||||
actions: [
|
||||
TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}),
|
||||
TextAlertAction(type: .defaultAction, title: "Proceed", action: {
|
||||
TextAlertAction(type: .defaultAction, title: presentationData.strings.SocksProxySetup_Warning_Proceed, action: {
|
||||
commit()
|
||||
})
|
||||
]
|
||||
|
|
@ -253,14 +252,11 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
availableSize: CGSize(width: 44.0, height: 44.0),
|
||||
transition: .immediate
|
||||
)
|
||||
|
||||
let titleText: String = "Proxy"
|
||||
let buttonText: String = "Connect Proxy"
|
||||
|
||||
|
||||
let title = title.update(
|
||||
component: MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: titleText,
|
||||
string: strings.SocksProxySetup_Title,
|
||||
font: Font.semibold(17.0),
|
||||
textColor: theme.actionSheet.primaryTextColor,
|
||||
paragraphAlignment: .center
|
||||
|
|
@ -323,7 +319,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
))
|
||||
}
|
||||
|
||||
var statusText = "Check Status"
|
||||
var statusText = strings.SocksProxySetup_CheckStatus
|
||||
var statusColor = tableLinkColor
|
||||
var statusIsActive = true
|
||||
if let status = state.status {
|
||||
|
|
@ -378,7 +374,7 @@ private final class ProxyServerPreviewSheetContent: CombinedComponent {
|
|||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: buttonText, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.SocksProxySetup_ConnectAndSave, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
|
||||
),
|
||||
displaysProgress: state.inProgress,
|
||||
action: {
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ public final class TextFieldComponent: Component {
|
|||
}
|
||||
}
|
||||
|
||||
public func chatInputTextNodeShouldReturn() -> Bool {
|
||||
public func chatInputTextNodeShouldReturn(modifierFlags: UIKeyModifierFlags) -> Bool {
|
||||
guard let component = self.component else {
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,19 +142,21 @@ public final class VideoPlaybackControlsComponent: Component {
|
|||
|
||||
self.isUserInteractionEnabled = component.isVisible
|
||||
|
||||
let containerInset: CGFloat = 32.0
|
||||
|
||||
let size = CGSize(width: component.layoutParams.sideButtonSize * 2.0 + component.layoutParams.centerButtonSize + component.layoutParams.spacing * 2.0, height: component.layoutParams.centerButtonSize)
|
||||
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize))
|
||||
let centerButtonFrame = CGRect(origin: CGPoint(x: component.layoutParams.sideButtonSize + component.layoutParams.spacing, y: floorToScreenPixels((size.height - component.layoutParams.centerButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.centerButtonSize, height: component.layoutParams.centerButtonSize))
|
||||
let rightButtonFrame = CGRect(origin: CGPoint(x: size.width - component.layoutParams.sideButtonSize, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize))
|
||||
let leftButtonFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize)).offsetBy(dx: containerInset, dy: containerInset)
|
||||
let centerButtonFrame = CGRect(origin: CGPoint(x: component.layoutParams.sideButtonSize + component.layoutParams.spacing, y: floorToScreenPixels((size.height - component.layoutParams.centerButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.centerButtonSize, height: component.layoutParams.centerButtonSize)).offsetBy(dx: containerInset, dy: containerInset)
|
||||
let rightButtonFrame = CGRect(origin: CGPoint(x: size.width - component.layoutParams.sideButtonSize, y: floorToScreenPixels((size.height - component.layoutParams.sideButtonSize) * 0.5)), size: CGSize(width: component.layoutParams.sideButtonSize, height: component.layoutParams.sideButtonSize)).offsetBy(dx: containerInset, dy: containerInset)
|
||||
|
||||
if isVisibleChanged && !transition.animation.isImmediate {
|
||||
self.backgroundContainer.isHidden = true
|
||||
self.backgroundContainer.isHidden = false
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size))
|
||||
self.backgroundContainer.update(size: size, isDark: true, transition: transition)
|
||||
transition.setFrame(view: self.backgroundContainer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: -containerInset, dy: -containerInset))
|
||||
self.backgroundContainer.update(size: CGSize(width: size.width + containerInset * 2.0, height: size.height + containerInset * 2.0), isDark: true, transition: transition)
|
||||
|
||||
let areSideButtonsVisible = component.isVisible && component.displaySeekControls
|
||||
let buttonsTintColor: GlassBackgroundView.TintColor = .init(kind: .custom(style: .clear, color: UIColor(white: 0.0, alpha: 0.2)))
|
||||
|
|
|
|||
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "star (3).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/star (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stars.imageset/star (3).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ton (3).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/ton (3).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Ton.imageset/ton (3).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Craft/Chance.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Craft/Chance.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "puzzle.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Craft/Chance.imageset/puzzle.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Craft/Chance.imageset/puzzle.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
12
submodules/TelegramUI/Images.xcassets/Premium/Craft/Rare.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Craft/Rare.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "diamond (2).pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Craft/Rare.imageset/diamond (2).pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Craft/Rare.imageset/diamond (2).pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/Craft/Result.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/Craft/Result.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "chance.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/Craft/Result.imageset/chance.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/Craft/Result.imageset/chance.pdf
vendored
Normal file
Binary file not shown.
12
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "giftcrash.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Premium/GiftCrash.imageset/giftcrash.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -15,16 +15,15 @@ import ChatControllerInteraction
|
|||
|
||||
extension ChatControllerImpl {
|
||||
var keyShortcutsInternal: [KeyShortcut] {
|
||||
if !self.traceVisibility() || !isTopmostChatController(self) {
|
||||
if !isTopmostChatController(self) {
|
||||
return []
|
||||
}
|
||||
|
||||
let strings = self.presentationData.strings
|
||||
|
||||
var inputShortcuts: [KeyShortcut]
|
||||
var inputShortcuts: [KeyShortcut] = []
|
||||
if self.chatDisplayNode.isInputViewFocused {
|
||||
inputShortcuts = [
|
||||
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {}),
|
||||
KeyShortcut(input: "B", modifiers: [.command], action: { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.interfaceInteraction?.updateTextInputStateAndMode { current, inputMode in
|
||||
|
|
@ -80,6 +79,16 @@ extension ChatControllerImpl {
|
|||
}
|
||||
})
|
||||
]
|
||||
|
||||
if self.context.sharedContext.currentChatSettings.with({ $0 }).sendWithCmdEnter {
|
||||
inputShortcuts.append(
|
||||
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", modifiers: [.command], action: {})
|
||||
)
|
||||
} else {
|
||||
inputShortcuts.append(
|
||||
KeyShortcut(title: strings.KeyCommand_SendMessage, input: "\r", action: {})
|
||||
)
|
||||
}
|
||||
} else if UIResponder.currentFirst() == nil {
|
||||
inputShortcuts = [
|
||||
KeyShortcut(title: strings.KeyCommand_FocusOnInputField, input: "\r", action: { [weak self] in
|
||||
|
|
|
|||
|
|
@ -2256,6 +2256,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
if let value = self.backgroundNode.makeEdgeEffectNode() {
|
||||
bottomBackgroundEdgeEffectNode = value
|
||||
self.bottomBackgroundEdgeEffectNode = value
|
||||
value.isUserInteractionEnabled = false
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
|
||||
}
|
||||
}
|
||||
|
|
@ -2466,6 +2467,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
|||
if let value = self.backgroundNode.makeEdgeEffectNode() {
|
||||
topBackgroundEdgeEffectNode = value
|
||||
self.topBackgroundEdgeEffectNode = value
|
||||
value.isUserInteractionEnabled = false
|
||||
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ func textInputAccessoryPanel(
|
|||
|
||||
let alertController = AlertScreen(
|
||||
context: context,
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical),
|
||||
configuration: AlertScreen.Configuration(actionAlignment: .vertical, allowInputInset: true),
|
||||
content: content,
|
||||
actions: [
|
||||
.init(title: strings.Conversation_ForwardOptions_ShowOptions, action: {
|
||||
|
|
|
|||
|
|
@ -1136,7 +1136,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
|||
let sendGiftTitle: String
|
||||
var isIncoming = message.effectivelyIncoming(context.account.peerId)
|
||||
for media in message.media {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action {
|
||||
if let action = media as? TelegramMediaAction, case let .starGiftUnique(_, isUpgrade, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = action.action {
|
||||
if isUpgrade && message.author?.id == context.account.peerId {
|
||||
isIncoming = true
|
||||
}
|
||||
|
|
@ -2725,14 +2725,14 @@ private final class ChatDeleteMessageContextItemNode: ASDisplayNode, ContextMenu
|
|||
return (CGSize(width: max(textSize.width, statusSize.width) + sideInset + rightTextInset, height: verticalInset * 2.0 + combinedTextHeight), { size, transition in
|
||||
self.validLayout = size
|
||||
let verticalOrigin = floor((size.height - combinedTextHeight) / 2.0)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin), size: textSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: sideInset + 42.0, y: verticalOrigin), size: textSize)
|
||||
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
|
||||
|
||||
transition.updateFrame(node: self.textIconNode, frame: CGRect(origin: CGPoint(x: sideInset, y: verticalOrigin + verticalSpacing + textSize.height + floorToScreenPixels((statusSize.height - textIconSize.height) / 2.0) + 1.0), size: textIconSize))
|
||||
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: sideInset + textIconSize.width + 2.0, y: verticalOrigin + verticalSpacing + textSize.height), size: statusSize))
|
||||
transition.updateFrame(node: self.textIconNode, frame: CGRect(origin: CGPoint(x: sideInset + 42.0, y: verticalOrigin + verticalSpacing + textSize.height + floorToScreenPixels((statusSize.height - textIconSize.height) / 2.0) + 1.0), size: textIconSize))
|
||||
transition.updateFrameAdditive(node: self.statusNode, frame: CGRect(origin: CGPoint(x: sideInset + 42.0 + textIconSize.width + 2.0, y: verticalOrigin + verticalSpacing + textSize.height), size: statusSize))
|
||||
|
||||
if !iconSize.width.isZero {
|
||||
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: size.width - standardIconWidth - iconSideInset + floor((standardIconWidth - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
transition.updateFrameAdditive(node: self.iconNode, frame: CGRect(origin: CGPoint(x: iconSideInset + 12.0, y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||
|
|
|
|||
|
|
@ -255,6 +255,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
public let currentMediaDisplaySettings: Atomic<MediaDisplaySettings>
|
||||
private var mediaDisplaySettingsDisposable: Disposable?
|
||||
|
||||
public let currentChatSettings: Atomic<ChatSettings>
|
||||
private var chatSettingsDisposable: Disposable?
|
||||
|
||||
public let currentStickerSettings: Atomic<StickerSettings>
|
||||
private var stickerSettingsDisposable: Disposable?
|
||||
|
||||
|
|
@ -339,6 +342,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings)
|
||||
self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings)
|
||||
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
|
||||
self.currentChatSettings = Atomic(value: initialPresentationDataAndSettings.chatSettings)
|
||||
|
||||
if automaticEnergyUsageShouldBeOnNow(settings: self.currentAutomaticMediaDownloadSettings) {
|
||||
self.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
|
||||
|
|
@ -485,6 +489,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
}
|
||||
})
|
||||
|
||||
self.chatSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.chatSettings])
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
|
||||
if let strongSelf = self {
|
||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) {
|
||||
let _ = strongSelf.currentChatSettings.swap(settings)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let immediateExperimentalUISettingsValue = self.immediateExperimentalUISettingsValue
|
||||
let _ = immediateExperimentalUISettingsValue.swap(initialPresentationDataAndSettings.experimentalUISettings)
|
||||
|
||||
|
|
@ -1078,6 +1091,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
self.inAppNotificationSettingsDisposable?.dispose()
|
||||
self.mediaInputSettingsDisposable?.dispose()
|
||||
self.mediaDisplaySettingsDisposable?.dispose()
|
||||
self.chatSettingsDisposable?.dispose()
|
||||
self.stickerSettingsDisposable?.dispose()
|
||||
self.callDisposable?.dispose()
|
||||
self.groupCallDisposable?.dispose()
|
||||
self.callStateDisposable?.dispose()
|
||||
|
|
@ -3852,8 +3867,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return GiftViewScreen(context: context, subject: .wearPreview(gift, attributes))
|
||||
}
|
||||
|
||||
public func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController {
|
||||
return GiftViewScreen(context: context, subject: .upgradePreview(attributes, peerName))
|
||||
public func makeGiftUpgradePreviewScreen(context: AccountContext, gift: StarGift.Gift, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController {
|
||||
return GiftViewScreen(context: context, subject: .upgradePreview(gift, attributes, peerName))
|
||||
}
|
||||
|
||||
public func makeGiftAuctionInfoScreen(context: AccountContext, auctionContext: GiftAuctionContext, completion: (() -> Void)?) -> ViewController {
|
||||
|
|
@ -3876,8 +3891,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||
return giftOfferAlertController(context: context, updatedPresentationData: updatedPresentationData, gift: gift, peer: peer, amount: amount, commit: commit)
|
||||
}
|
||||
|
||||
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, onlyCrafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
|
||||
return GiftUpgradeVariantsScreen(context: context, gift: gift, onlyCrafted: onlyCrafted, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute)
|
||||
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, crafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
|
||||
return GiftUpgradeVariantsScreen(context: context, gift: gift, crafted: crafted, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute)
|
||||
}
|
||||
|
||||
public func makeGiftAuctionWearPreviewScreen(context: AccountContext, auctionContext: GiftAuctionContext, acquiredGifts: Signal<[GiftAuctionAcquiredGift], NoError>?, attributes: [StarGift.UniqueGift.Attribute], completion: @escaping () -> Void) -> ViewController {
|
||||
|
|
|
|||
49
submodules/TelegramUIPreferences/Sources/ChatSettings.swift
Normal file
49
submodules/TelegramUIPreferences/Sources/ChatSettings.swift
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import Foundation
|
||||
import TelegramCore
|
||||
import SwiftSignalKit
|
||||
|
||||
public struct ChatSettings: Codable, Equatable {
|
||||
public let sendWithCmdEnter: Bool
|
||||
|
||||
public static var defaultSettings: ChatSettings {
|
||||
return ChatSettings(sendWithCmdEnter: false)
|
||||
}
|
||||
|
||||
public init(sendWithCmdEnter: Bool) {
|
||||
self.sendWithCmdEnter = sendWithCmdEnter
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.sendWithCmdEnter = (try container.decode(Int32.self, forKey: "sendWithCmdEnter")) != 0
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encode((self.sendWithCmdEnter ? 1 : 0) as Int32, forKey: "sendWithCmdEnter")
|
||||
}
|
||||
|
||||
public static func ==(lhs: ChatSettings, rhs: ChatSettings) -> Bool {
|
||||
return lhs.sendWithCmdEnter == rhs.sendWithCmdEnter
|
||||
}
|
||||
|
||||
public func withUpdatedSendWithCmdEnter(_ sendWithCmdEnter: Bool) -> ChatSettings {
|
||||
return ChatSettings(sendWithCmdEnter: sendWithCmdEnter)
|
||||
}
|
||||
}
|
||||
|
||||
public func updateChatSettingsInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (ChatSettings) -> ChatSettings) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.chatSettings, { entry in
|
||||
let currentSettings: ChatSettings
|
||||
if let entry = entry?.get(ChatSettings.self) {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = ChatSettings.defaultSettings
|
||||
}
|
||||
return SharedPreferencesEntry(f(currentSettings))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
|||
case drawingSettings = 19
|
||||
case mediaDisplaySettings = 20
|
||||
case updateSettings = 21
|
||||
case chatSettings = 22
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificSharedDataKeys {
|
||||
|
|
@ -68,6 +69,7 @@ public struct ApplicationSpecificSharedDataKeys {
|
|||
public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue)
|
||||
public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue)
|
||||
public static let updateSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.updateSettings.rawValue)
|
||||
public static let chatSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.chatSettings.rawValue)
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ final class WallpaperEdgeEffectNodeImpl: ASDisplayNode, WallpaperEdgeEffectNode
|
|||
return
|
||||
}
|
||||
self.contentNode.contents = parentNode.contentNode.contents
|
||||
self.contentNode.contentMode = parentNode.contentNode.contentMode
|
||||
self.contentNode.backgroundColor = parentNode.contentNode.backgroundColor
|
||||
self.contentNode.alpha = parentNode.contentNode.alpha
|
||||
self.contentNode.isHidden = parentNode.contentNode.isHidden
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue