Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2026-01-29 14:07:16 +01:00
commit 5389f949d9
98 changed files with 3655 additions and 2629 deletions

View file

@ -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 %@";

View file

@ -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

View file

@ -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() {

View file

@ -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))

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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))
}

View file

@ -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)
}

View file

@ -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))

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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()

View file

@ -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",

View file

@ -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)
})
}

View file

@ -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())

View file

@ -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] {

View file

@ -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))

View file

@ -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))
}

View file

@ -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?

View file

@ -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) }

View file

@ -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
}
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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
}
}
}
}

View file

@ -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 {

View file

@ -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))

View file

@ -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)

View file

@ -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
}
}

View file

@ -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)

View file

@ -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)
}
}

View file

@ -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] {

View file

@ -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)
}
}

View file

@ -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()

View file

@ -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)();

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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() {

View file

@ -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)
}

View file

@ -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))
}
}

View file

@ -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

View file

@ -49,6 +49,7 @@ swift_library(
"//submodules/TelegramUI/Components/SpaceWarpView",
"//submodules/ConfettiEffect",
"//submodules/TelegramNotices",
"//submodules/TelegramUI/Components/Gifts/GiftLoadingShimmerView",
],
visibility = [
"//visibility:public",

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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
)
)),

View file

@ -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),

View file

@ -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)

View file

@ -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)
})
}

View file

@ -24,6 +24,7 @@ swift_library(
"//submodules/AccountContext",
"//submodules/AppBundle",
"//submodules/ItemListUI",
"//submodules/ContextUI",
"//submodules/TelegramStringFormatting",
"//submodules/PresentationDataUtils",
"//submodules/Components/SheetComponent",

View file

@ -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)
}
}

View file

@ -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

View file

@ -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()
})
}

View file

@ -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),

View file

@ -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)
}

View file

@ -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
}

View file

@ -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()
}

View file

@ -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 {

View file

@ -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

View file

@ -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
}

View file

@ -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 {

View file

@ -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))

View file

@ -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)

View file

@ -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),

View file

@ -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(

View file

@ -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()

View file

@ -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
}
})
})
}
}

View file

@ -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: {

View file

@ -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
}

View file

@ -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)))

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "star (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "ton (3).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "puzzle.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,9 @@
{
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"provides-namespace" : true
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "diamond (2).pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "chance.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "giftcrash.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -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

View file

@ -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)
}
}

View file

@ -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: {

View file

@ -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)))

View file

@ -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 {

View 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))
})
}
}

View file

@ -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 {

View file

@ -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