mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge commit 'eabf0985ef59bea5dde543427c80c9a223077046'
This commit is contained in:
commit
c5ed3139b7
@ -3703,6 +3703,18 @@ Unused sets are archived when you add more.";
|
||||
"NotificationsSound.Pulse" = "Pulse";
|
||||
"NotificationsSound.Synth" = "Synth";
|
||||
|
||||
"NotificationsSound.Rebound" = "Rebound";
|
||||
"NotificationsSound.Antic" = "Antic";
|
||||
"NotificationsSound.Cheers" = "Cheers";
|
||||
"NotificationsSound.Droplet" = "Droplet";
|
||||
"NotificationsSound.Handoff" = "Handoff";
|
||||
"NotificationsSound.Milestone" = "Milestone";
|
||||
"NotificationsSound.Passage" = "Passage";
|
||||
"NotificationsSound.Portal" = "Portal";
|
||||
"NotificationsSound.Rattle" = "Rattle";
|
||||
"NotificationsSound.Slide" = "Slide";
|
||||
"NotificationsSound.Welcome" = "Welcome";
|
||||
|
||||
"NotificationsSound.Tritone" = "Tri-tone";
|
||||
"NotificationsSound.Tremolo" = "Tremolo";
|
||||
"NotificationsSound.Alert" = "Alert";
|
||||
@ -9813,10 +9825,6 @@ Sorry for the inconvenience.";
|
||||
"Premium.Stories.Format.Title" = "Links and Formatting";
|
||||
"Premium.Stories.Format.Text" = "Add links and formatting in captions to your stories.";
|
||||
|
||||
"Premium.MaxExpiringStoriesText" = "You can post **%@** stories in **24** hours. Subscribe to **Telegram Premium** to increase this limit to **%@**.";
|
||||
"Premium.MaxExpiringStoriesNoPremiumText" = "You have reached the limit of **%@** stories per **24** hours.";
|
||||
"Premium.MaxExpiringStoriesFinalText" = "You have reached the limit of **%@** stories per **24** hours.";
|
||||
|
||||
"Premium.MaxStoriesWeeklyText" = "You can post **%@** stories in a week. Upgrade to **Telegram Premium** to increase this limit to **%@**.";
|
||||
"Premium.MaxStoriesWeeklyNoPremiumText" = "You have reached the limit of **%@** stories per week.";
|
||||
"Premium.MaxStoriesWeeklyFinalText" = "You have reached the limit of **%@** stories per week.";
|
||||
@ -13565,6 +13573,7 @@ Sorry for the inconvenience.";
|
||||
"FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added";
|
||||
|
||||
"Stars.Purchase.UpgradeStarGiftInfo" = "Buy Stars to upgrade your gift into a unique collectible.";
|
||||
"Stars.Purchase.TransferStarGiftInfo" = "Buy Stars to transfer ownership of your unique collectible.";
|
||||
|
||||
"Gift.Send.Upgrade" = "Make Unique for %@";
|
||||
"Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()";
|
||||
@ -13994,9 +14003,6 @@ Sorry for the inconvenience.";
|
||||
"Share.VideoStartAt" = "Start at %@";
|
||||
"SendStarReactions.SubtitleFrom" = "from %@";
|
||||
|
||||
"Stars.AccountRevenue.Proceeds.Info" = "Stars from your total balance can be withdrawn as rewards 21 days after they are earned.";
|
||||
"Stars.AccountRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()";
|
||||
|
||||
"Notification.PaidMessageRefund.Stars_1" = "%@ Star";
|
||||
"Notification.PaidMessageRefund.Stars_any" = "%@ Stars";
|
||||
"Notification.PaidMessageRefund" = "%1$@ refunded you %2$@";
|
||||
@ -14006,3 +14012,20 @@ Sorry for the inconvenience.";
|
||||
"Notification.PaidMessagePriceChanged.Stars_any" = "%@ Stars";
|
||||
"Notification.PaidMessagePriceChanged" = "%1$@ changed price to %2$@ per message";
|
||||
"Notification.PaidMessagePriceChangedYou" = "You changed price to %1$@ per message";
|
||||
|
||||
"Premium.MaxExpiringStoriesTextNumberFormat_1" = "**%d** story";
|
||||
"Premium.MaxExpiringStoriesTextNumberFormat_any" = "**%d** stories";
|
||||
"Premium.MaxExpiringStoriesTextPremiumNumberFormat_1" = "**%d** story";
|
||||
"Premium.MaxExpiringStoriesTextPremiumNumberFormat_any" = "**%d** stories";
|
||||
"Premium.MaxExpiringStoriesTextFormat" = "You can post %@ in **24** hours. Subscribe to **Telegram Premium** to increase this limit to **%@**.";
|
||||
|
||||
"Premium.MaxExpiringStoriesNoPremiumTextNumberFormat_1" = "**%d** story";
|
||||
"Premium.MaxExpiringStoriesNoPremiumTextNumberFormat_any" = "**%d** stories";
|
||||
"Premium.MaxExpiringStoriesNoPremiumTextFormat" = "You have reached the limit of %@ per **24** hours.";
|
||||
|
||||
"Premium.MaxExpiringStoriesFinalTextNumberFormat_1" = "**%d** story";
|
||||
"Premium.MaxExpiringStoriesFinalTextNumberFormat_any" = "**%d** stories";
|
||||
"Premium.MaxExpiringStoriesFinalTextFormat" = "You have reached the limit of %@ stories per **24** hours.";
|
||||
|
||||
"Stars.AccountRevenue.Proceeds.Info" = "Stars from your total balance can be withdrawn as rewards 21 days after they are earned.";
|
||||
"Stars.AccountRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()";
|
||||
|
@ -137,6 +137,7 @@ public enum StarsPurchasePurpose: Equatable {
|
||||
case unlockMedia(requiredStars: Int64)
|
||||
case starGift(peerId: EnginePeer.Id, requiredStars: Int64)
|
||||
case upgradeStarGift(requiredStars: Int64)
|
||||
case transferStarGift(requiredStars: Int64)
|
||||
case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64)
|
||||
}
|
||||
|
||||
|
@ -4098,7 +4098,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.secretIconNode = iconNode
|
||||
}
|
||||
iconNode.image = currentSecretIconImage
|
||||
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size))
|
||||
transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleLeftOffset, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size))
|
||||
titleOffset += currentSecretIconImage.size.width + 3.0
|
||||
} else if let secretIconNode = strongSelf.secretIconNode {
|
||||
strongSelf.secretIconNode = nil
|
||||
|
@ -129,7 +129,7 @@ public final class TextAlertContentActionNode: HighlightableButtonNode {
|
||||
if let range = attributedString.string.range(of: "$") {
|
||||
attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string))
|
||||
attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string))
|
||||
}
|
||||
|
||||
self.setAttributedTitle(attributedString, for: [])
|
||||
|
@ -234,7 +234,7 @@ public final class InAppPurchaseManager: NSObject {
|
||||
private let disposableSet = DisposableDict<String>()
|
||||
|
||||
private var lastRequestTimestamp: Double?
|
||||
|
||||
|
||||
public init(engine: SomeTelegramEngine) {
|
||||
self.engine = engine
|
||||
|
||||
|
@ -64,6 +64,7 @@ class EmojiHeaderComponent: Component {
|
||||
}
|
||||
|
||||
weak var animateFrom: UIView?
|
||||
var sourceRect: CGRect?
|
||||
weak var containerView: UIView?
|
||||
|
||||
let statusView: ComponentHostView<Empty>
|
||||
@ -116,8 +117,13 @@ class EmojiHeaderComponent: Component {
|
||||
|
||||
let initialPosition = self.statusView.center
|
||||
let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView)
|
||||
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: 0.0)
|
||||
|
||||
var sourceOffset: CGPoint = .zero
|
||||
if let sourceRect = self.sourceRect {
|
||||
sourceOffset = CGPoint(x: sourceRect.center.x - animateFrom.frame.width / 2.0, y: 0.0)
|
||||
}
|
||||
let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: sourceOffset.x, dy: sourceOffset.y)
|
||||
|
||||
containerView.addSubview(self.statusView)
|
||||
self.statusView.center = targetPosition
|
||||
|
||||
@ -127,6 +133,7 @@ class EmojiHeaderComponent: Component {
|
||||
self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
|
||||
Queue.mainQueue().after(0.55, {
|
||||
self.statusView.layer.removeAllAnimations()
|
||||
self.addSubview(self.statusView)
|
||||
self.statusView.center = initialPosition
|
||||
})
|
||||
|
@ -3865,6 +3865,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
}
|
||||
|
||||
public weak var sourceView: UIView?
|
||||
public var sourceRect: CGRect?
|
||||
public weak var containerView: UIView?
|
||||
public var animationColor: UIColor?
|
||||
|
||||
@ -4046,6 +4047,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer {
|
||||
|
||||
if let sourceView = self.sourceView {
|
||||
view.animateFrom = sourceView
|
||||
view.sourceRect = self.sourceRect
|
||||
view.containerView = self.containerView
|
||||
|
||||
view.animateIn()
|
||||
|
@ -1088,7 +1088,14 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
let premiumLimit = state.premiumLimits.maxExpiringStoriesCount
|
||||
iconName = "Premium/Stories"
|
||||
badgeText = "\(limit)"
|
||||
string = component.count >= premiumLimit ? strings.Premium_MaxExpiringStoriesFinalText("\(premiumLimit)").string : strings.Premium_MaxExpiringStoriesText("\(limit)", "\(premiumLimit)").string
|
||||
if component.count >= premiumLimit {
|
||||
let limitNumberString = strings.Premium_MaxExpiringStoriesFinalTextNumberFormat(Int32(premiumLimit))
|
||||
string = strings.Premium_MaxExpiringStoriesFinalTextFormat(limitNumberString).string
|
||||
} else {
|
||||
let limitNumberString = strings.Premium_MaxExpiringStoriesTextNumberFormat(Int32(limit))
|
||||
let premiumLimitNumberString = strings.Premium_MaxExpiringStoriesTextPremiumNumberFormat(Int32(premiumLimit))
|
||||
string = strings.Premium_MaxExpiringStoriesTextFormat(limitNumberString, premiumLimitNumberString).string
|
||||
}
|
||||
defaultValue = ""
|
||||
premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)"
|
||||
badgePosition = max(0.32, CGFloat(component.count) / CGFloat(premiumLimit))
|
||||
@ -1096,7 +1103,8 @@ private final class LimitSheetContent: CombinedComponent {
|
||||
|
||||
if isPremiumDisabled {
|
||||
badgeText = "\(limit)"
|
||||
string = strings.Premium_MaxExpiringStoriesNoPremiumText("\(limit)").string
|
||||
let numberString = strings.Premium_MaxExpiringStoriesNoPremiumTextNumberFormat(Int32(limit))
|
||||
string = strings.Premium_MaxExpiringStoriesNoPremiumTextFormat(numberString).string
|
||||
}
|
||||
buttonAnimationName = nil
|
||||
case .storiesWeekly:
|
||||
|
@ -1038,7 +1038,6 @@ public final class StarsContext {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init(account: Account) {
|
||||
self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: {
|
||||
return StarsContextImpl(account: account)
|
||||
|
@ -175,7 +175,7 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
public let outgoingDateAndStatusStarsIcon: UIImage
|
||||
public let mediaStarsIcon: UIImage
|
||||
public let freeStarsIcon: UIImage
|
||||
|
||||
|
||||
public let incomingDateAndStatusPinnedIcon: UIImage
|
||||
public let outgoingDateAndStatusPinnedIcon: UIImage
|
||||
public let mediaPinnedIcon: UIImage
|
||||
@ -369,7 +369,7 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
|
||||
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
|
||||
|
||||
|
||||
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
||||
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
||||
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||
@ -496,7 +496,7 @@ public final class PrincipalThemeEssentialGraphics {
|
||||
self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||
self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)!
|
||||
self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)!
|
||||
|
||||
|
||||
let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")!
|
||||
self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)!
|
||||
self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)!
|
||||
|
@ -1899,7 +1899,7 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
default:
|
||||
self.isUserInteractionEnabled = false
|
||||
}
|
||||
|
||||
|
||||
let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom))
|
||||
|
||||
var contentSize = CGSize()
|
||||
|
@ -267,7 +267,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
private var replyCountNode: TextNode?
|
||||
private var starsIcon: ASImageNode?
|
||||
private var starsCountNode: TextNode?
|
||||
|
||||
|
||||
private var type: ChatMessageDateAndStatusType?
|
||||
private var theme: ChatPresentationThemeData?
|
||||
private var layoutSize: CGSize?
|
||||
@ -322,13 +322,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var currentImpressionIcon = self.impressionIcon
|
||||
var currentRepliesIcon = self.repliesIcon
|
||||
var currentStarsIcon = self.starsIcon
|
||||
|
||||
|
||||
let currentType = self.type
|
||||
let currentTheme = self.theme
|
||||
|
||||
let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode)
|
||||
let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode)
|
||||
|
||||
|
||||
let reactionButtonsContainer = self.reactionButtonsContainer
|
||||
|
||||
return { [weak self] arguments in
|
||||
@ -345,7 +345,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
var impressionImage: UIImage?
|
||||
var repliesImage: UIImage?
|
||||
var starsImage: UIImage?
|
||||
|
||||
|
||||
let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType
|
||||
|
||||
let graphics = PresentationResourcesChat.principalGraphics(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, bubbleCorners: arguments.presentationData.chatBubbleCorners)
|
||||
@ -693,7 +693,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
|
||||
var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)?
|
||||
|
||||
|
||||
let reactionSize: CGFloat = 8.0
|
||||
let reactionSpacing: CGFloat = 2.0
|
||||
let reactionTrailingSpacing: CGFloat = 6.0
|
||||
@ -712,6 +712,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
|
||||
let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0)))
|
||||
reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0
|
||||
if arguments.starsCount != nil {
|
||||
reactionInset += 3.0
|
||||
}
|
||||
replyCountLayoutAndApply = layoutAndApply
|
||||
} else if arguments.isPinned {
|
||||
reactionInset += 12.0
|
||||
@ -1283,6 +1286,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode {
|
||||
let replyCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size)
|
||||
animation.animator.updateFrame(layer: node.layer, frame: replyCountFrame, completion: nil)
|
||||
reactionOffset += 4.0 + layout.size.width
|
||||
if currentStarsIcon != nil {
|
||||
reactionOffset += 8.0
|
||||
}
|
||||
} else if let replyCountNode = strongSelf.replyCountNode {
|
||||
strongSelf.replyCountNode = nil
|
||||
if animation.isAnimated {
|
||||
|
@ -317,18 +317,20 @@ private final class ChatMessagePaymentAlertContentNode: AlertContentNode, ASGest
|
||||
}
|
||||
}
|
||||
|
||||
private class ChatMessagePaymentAlertController: AlertController {
|
||||
public class ChatMessagePaymentAlertController: AlertController {
|
||||
private let context: AccountContext?
|
||||
private let presentationData: PresentationData
|
||||
private weak var parentNavigationController: NavigationController?
|
||||
|
||||
private let showBalance: Bool
|
||||
|
||||
private let balance = ComponentView<Empty>()
|
||||
|
||||
init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?) {
|
||||
public init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?, showBalance: Bool = true) {
|
||||
self.context = context
|
||||
self.presentationData = presentationData
|
||||
self.parentNavigationController = navigationController
|
||||
|
||||
self.showBalance = showBalance
|
||||
|
||||
super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode)
|
||||
|
||||
self.willDismiss = { [weak self] in
|
||||
@ -350,16 +352,16 @@ private class ChatMessagePaymentAlertController: AlertController {
|
||||
}
|
||||
}
|
||||
|
||||
override func dismissAnimated() {
|
||||
public override func dismissAnimated() {
|
||||
super.dismissAnimated()
|
||||
|
||||
self.animateOut()
|
||||
}
|
||||
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
if let context = self.context, let _ = self.parentNavigationController {
|
||||
if let context = self.context, let _ = self.parentNavigationController, self.showBalance {
|
||||
let insets = layout.insets(options: .statusBar)
|
||||
let balanceSize = self.balance.update(
|
||||
transition: .immediate,
|
||||
|
@ -177,7 +177,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget {
|
||||
|
||||
switch content {
|
||||
case let .animation(animationData):
|
||||
let animationDataResource = animationData.resource._parse()
|
||||
guard let animationDataResource = animationData.resource._parse() else {
|
||||
return
|
||||
}
|
||||
|
||||
let loadAnimation: () -> Void = { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
|
@ -65,12 +65,15 @@ public final class EntityKeyboardAnimationData: Equatable {
|
||||
case stickerPackThumbnail(id: Int64, accessHash: Int64, info: StickerPackCollectionInfo.Accessor)
|
||||
case file(PartialMediaReference?, TelegramMediaFile.Accessor)
|
||||
|
||||
func _parse() -> MediaResourceReference {
|
||||
func _parse() -> MediaResourceReference? {
|
||||
switch self {
|
||||
case let .resource(resource):
|
||||
return resource
|
||||
case let .stickerPackThumbnail(id, accessHash, info):
|
||||
return .stickerPackThumbnail(stickerPack: .id(id: id, accessHash: accessHash), resource: info._parse().thumbnail!.resource)
|
||||
guard let thumbnail = info._parse().thumbnail else {
|
||||
return nil
|
||||
}
|
||||
return .stickerPackThumbnail(stickerPack: .id(id: id, accessHash: accessHash), resource: thumbnail.resource)
|
||||
case let .file(partialReference, file):
|
||||
let file = file._parse()
|
||||
if let partialReference {
|
||||
@ -1740,7 +1743,9 @@ public final class EmojiPagerContentComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
component.animationRenderer.setFrameIndex(itemId: animationData.resource._parse().resource.id.stringRepresentation, size: itemLayer.pixelSize, frameIndex: sourceItem.frameIndex, placeholder: sourceItem.placeholder)
|
||||
if let resource = animationData.resource._parse() {
|
||||
component.animationRenderer.setFrameIndex(itemId: resource.resource.id.stringRepresentation, size: itemLayer.pixelSize, frameIndex: sourceItem.frameIndex, placeholder: sourceItem.placeholder)
|
||||
}
|
||||
} else {
|
||||
let distance = itemLayer.position.y - itemLayout.frame(groupIndex: 0, itemIndex: 0).midY
|
||||
let maxDistance = self.bounds.height
|
||||
|
@ -216,6 +216,8 @@ final class GiftOptionsScreenComponent: Component {
|
||||
private var starsStateDisposable: Disposable?
|
||||
private var starsState: StarsContext.State?
|
||||
|
||||
private let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||
|
||||
private var component: GiftOptionsScreenComponent?
|
||||
private(set) weak var state: State?
|
||||
private var environment: EnvironmentType?
|
||||
@ -508,59 +510,94 @@ final class GiftOptionsScreenComponent: Component {
|
||||
gift: transferGift,
|
||||
peer: peer,
|
||||
transferStars: gift.transferStars ?? 0,
|
||||
commit: { [weak controller] in
|
||||
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start()
|
||||
|
||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
|
||||
if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId {
|
||||
foundController = true
|
||||
break
|
||||
}
|
||||
navigationController: controller.navigationController as? NavigationController,
|
||||
commit: { [weak self, weak controller] in
|
||||
let proceed: (Bool) -> Void = { waitForTopUp in
|
||||
if waitForTopUp, let starsContext = context.starsContext {
|
||||
let _ = (starsContext.onUpdate
|
||||
|> deliverOnMainQueue).start(next: {
|
||||
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start()
|
||||
})
|
||||
} else {
|
||||
let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id)
|
||||
|> deliverOnMainQueue).start()
|
||||
}
|
||||
if !foundController {
|
||||
if let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
controllers.append(controller)
|
||||
}
|
||||
|
||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
} else {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
|
||||
|
||||
if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) }
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId {
|
||||
foundController = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundController {
|
||||
if let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
controllers.append(controller)
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
} else {
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) }
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation {
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
controllers.append(chatController)
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
controllers.append(chatController)
|
||||
if let completion = component.completion {
|
||||
completion()
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
}
|
||||
|
||||
if let completion = component.completion {
|
||||
completion()
|
||||
|
||||
if let self, let transferStars = gift.transferStars, transferStars > 0, let starsContext = context.starsContext, let starsState = self.starsState {
|
||||
if starsState.balance < StarsAmount(value: transferStars, nanos: 0) {
|
||||
let _ = (self.optionsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] options in
|
||||
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
|
||||
context: context,
|
||||
starsContext: starsContext,
|
||||
options: options ?? [],
|
||||
purpose: .transferStarGift(requiredStars: transferStars),
|
||||
completion: { stars in
|
||||
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||
proceed(true)
|
||||
}
|
||||
)
|
||||
controller?.push(purchaseController)
|
||||
})
|
||||
} else {
|
||||
proceed(false)
|
||||
}
|
||||
} else {
|
||||
proceed(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -590,6 +627,11 @@ final class GiftOptionsScreenComponent: Component {
|
||||
self.state?.updated()
|
||||
}
|
||||
})
|
||||
|
||||
if let state = component.starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
||||
self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions()
|
||||
|> map(Optional.init))
|
||||
}
|
||||
}
|
||||
self.component = component
|
||||
|
||||
@ -1241,7 +1283,6 @@ final class GiftOptionsScreenComponent: Component {
|
||||
continue
|
||||
}
|
||||
let starsGiftOption = premiumOptions.first(where: { $0.currency == "XTR" && $0.months == option.months })
|
||||
|
||||
premiumProducts.append(
|
||||
PremiumGiftProduct(
|
||||
giftOption: CachedPremiumGiftOption(
|
||||
|
@ -49,6 +49,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
|
||||
"//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
"//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -12,6 +12,7 @@ import AppBundle
|
||||
import AvatarNode
|
||||
import Markdown
|
||||
import GiftItemComponent
|
||||
import ChatMessagePaymentAlertController
|
||||
|
||||
private final class GiftTransferAlertContentNode: AlertContentNode {
|
||||
private let context: AccountContext
|
||||
@ -251,7 +252,14 @@ private final class GiftTransferAlertContentNode: AlertContentNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func giftTransferAlertController(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, transferStars: Int64, commit: @escaping () -> Void) -> AlertController {
|
||||
public func giftTransferAlertController(
|
||||
context: AccountContext,
|
||||
gift: StarGift.UniqueGift,
|
||||
peer: EnginePeer,
|
||||
transferStars: Int64,
|
||||
navigationController: NavigationController?,
|
||||
commit: @escaping () -> Void
|
||||
) -> AlertController {
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
let strings = presentationData.strings
|
||||
|
||||
@ -267,7 +275,6 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
|
||||
}
|
||||
|
||||
var dismissImpl: ((Bool) -> Void)?
|
||||
var contentNode: GiftTransferAlertContentNode?
|
||||
let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: {
|
||||
dismissImpl?(true)
|
||||
commit()
|
||||
@ -275,9 +282,9 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift.
|
||||
dismissImpl?(true)
|
||||
})]
|
||||
|
||||
contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
|
||||
let contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions)
|
||||
|
||||
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
|
||||
let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, showBalance: transferStars > 0)
|
||||
dismissImpl = { [weak controller] animated in
|
||||
if animated {
|
||||
controller?.dismissAnimated()
|
||||
|
@ -147,13 +147,7 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
|
||||
var keepOriginalInfo = false
|
||||
|
||||
private var optionsDisposable: Disposable?
|
||||
private(set) var options: [StarsTopUpOption] = [] {
|
||||
didSet {
|
||||
self.optionsPromise.set(self.options)
|
||||
}
|
||||
}
|
||||
private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil)
|
||||
private let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
@ -269,13 +263,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
}
|
||||
|
||||
if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
||||
self.optionsDisposable = (context.engine.payments.starsTopUpOptions()
|
||||
|> deliverOnMainQueue).start(next: { [weak self] options in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.options = options
|
||||
})
|
||||
self.optionsPromise.set(context.engine.payments.starsTopUpOptions()
|
||||
|> map(Optional.init))
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,8 +340,12 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
self.inProgress = true
|
||||
self.updated()
|
||||
|
||||
if let controller = self.getController() as? GiftViewScreen {
|
||||
controller.showBalance = false
|
||||
}
|
||||
|
||||
self.upgradeDisposable = (self.upgradeGift(formId, self.keepOriginalInfo)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak starsContext] result in
|
||||
guard let self, let controller = self.getController() as? GiftViewScreen else {
|
||||
return
|
||||
}
|
||||
@ -363,6 +356,10 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
controller.subject = self.subject
|
||||
controller.animateSuccess()
|
||||
self.updated(transition: .spring(duration: 0.4))
|
||||
|
||||
Queue.mainQueue().after(0.5) {
|
||||
starsContext?.load(force: true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -382,10 +379,13 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
starsContext: starsContext,
|
||||
options: options ?? [],
|
||||
purpose: .upgradeStarGift(requiredStars: price),
|
||||
completion: { [weak starsContext] stars in
|
||||
guard let starsContext else {
|
||||
completion: { [weak self, weak starsContext] stars in
|
||||
guard let self, let starsContext else {
|
||||
return
|
||||
}
|
||||
self.inProgress = true
|
||||
self.updated()
|
||||
|
||||
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||
let _ = (starsContext.onUpdate
|
||||
|> deliverOnMainQueue).start(next: {
|
||||
@ -2230,9 +2230,8 @@ private final class GiftViewSheetContent: CombinedComponent {
|
||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0))
|
||||
)
|
||||
|
||||
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
|
||||
|
||||
return contentSize
|
||||
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
||||
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +111,7 @@ public final class PeerInfoCoverComponent: Component {
|
||||
public let files: [Int64: TelegramMediaFile]
|
||||
public let isDark: Bool
|
||||
public let avatarCenter: CGPoint
|
||||
public let avatarSize: CGSize
|
||||
public let avatarScale: CGFloat
|
||||
public let defaultHeight: CGFloat
|
||||
public let gradientOnTop: Bool
|
||||
@ -124,6 +125,7 @@ public final class PeerInfoCoverComponent: Component {
|
||||
files: [Int64: TelegramMediaFile],
|
||||
isDark: Bool,
|
||||
avatarCenter: CGPoint,
|
||||
avatarSize: CGSize = CGSize(width: 100.0, height: 100.0),
|
||||
avatarScale: CGFloat,
|
||||
defaultHeight: CGFloat,
|
||||
gradientOnTop: Bool = false,
|
||||
@ -136,6 +138,7 @@ public final class PeerInfoCoverComponent: Component {
|
||||
self.files = files
|
||||
self.isDark = isDark
|
||||
self.avatarCenter = avatarCenter
|
||||
self.avatarSize = avatarSize
|
||||
self.avatarScale = avatarScale
|
||||
self.defaultHeight = defaultHeight
|
||||
self.gradientOnTop = gradientOnTop
|
||||
@ -160,6 +163,9 @@ public final class PeerInfoCoverComponent: Component {
|
||||
if lhs.avatarCenter != rhs.avatarCenter {
|
||||
return false
|
||||
}
|
||||
if lhs.avatarSize != rhs.avatarSize {
|
||||
return false
|
||||
}
|
||||
if lhs.avatarScale != rhs.avatarScale {
|
||||
return false
|
||||
}
|
||||
@ -492,7 +498,7 @@ public final class PeerInfoCoverComponent: Component {
|
||||
transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame)
|
||||
transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction)
|
||||
|
||||
var baseDistance: CGFloat = 72.0
|
||||
var baseDistance: CGFloat = component.avatarSize.width / 2.0 + 22.0
|
||||
var baseRowDistance: CGFloat = 28.0
|
||||
var baseItemSize: CGFloat = 26.0
|
||||
if availableSize.width <= 60.0 {
|
||||
@ -516,7 +522,7 @@ public final class PeerInfoCoverComponent: Component {
|
||||
let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance
|
||||
|
||||
let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0)))
|
||||
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.56), t: itemDistanceFraction, reverse: false)
|
||||
let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction * 1.6, t: itemDistanceFraction, reverse: false)
|
||||
let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction
|
||||
|
||||
var itemAngle: CGFloat
|
||||
|
@ -219,14 +219,14 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
||||
}
|
||||
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.statusBarHeight), size: component.topLeftButtonsSize))
|
||||
excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - component.topRightButtonsSize.width, y: component.statusBarHeight), size: component.topRightButtonsSize))
|
||||
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + 56.0), size: CGSize(width: component.titleWidth, height: 72.0)))
|
||||
excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + component.avatarSize.height / 2.0 + 6.0), size: CGSize(width: component.titleWidth, height: 100.0)))
|
||||
if component.bottomHeight > 0.0 {
|
||||
excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.defaultHeight - component.bottomHeight), size: CGSize(width: availableSize.width, height: component.bottomHeight)))
|
||||
}
|
||||
|
||||
let positionGenerator = PositionGenerator(
|
||||
containerSize: CGSize(width: availableSize.width, height: component.defaultHeight),
|
||||
centerFrame: CGSize(width: 100, height: 100).centered(around: avatarCenter),
|
||||
centerFrame: component.avatarSize.centered(around: avatarCenter),
|
||||
exclusionZones: excludeRects,
|
||||
minimumDistance: 42.0,
|
||||
edgePadding: 5.0,
|
||||
@ -304,8 +304,8 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
||||
}
|
||||
iconLayer.glowing = component.hasBackground
|
||||
|
||||
let itemDistanceFraction = max(0.0, min(1.0, iconPosition.distance / 100.0))
|
||||
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.56), t: itemDistanceFraction, reverse: false)
|
||||
let itemDistanceFraction = max(0.0, min(0.5, (iconPosition.distance - component.avatarSize.width / 2.0) / 144.0))
|
||||
let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.33), t: itemDistanceFraction, reverse: false)
|
||||
|
||||
func interpolatePosition(from: PositionGenerator.Position, to: PositionGenerator.Position, t: CGFloat) -> PositionGenerator.Position {
|
||||
let clampedT = max(0, min(1, t))
|
||||
@ -316,13 +316,16 @@ public final class PeerInfoGiftsCoverComponent: Component {
|
||||
return PositionGenerator.Position(distance: interpolatedDistance, angle: interpolatedAngle, scale: from.scale)
|
||||
}
|
||||
|
||||
let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + .pi * 0.18, scale: iconPosition.scale)
|
||||
let toAngle: CGFloat = .pi * 0.18
|
||||
let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + toAngle, scale: iconPosition.scale)
|
||||
let effectivePosition = interpolatePosition(from: iconPosition, to: centerPosition, t: itemScaleFraction)
|
||||
let effectiveAngle = toAngle * itemScaleFraction
|
||||
|
||||
let position = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter)
|
||||
let absolutePosition = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter)
|
||||
|
||||
iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize))
|
||||
iconTransition.setPosition(layer: iconLayer, position: position)
|
||||
iconTransition.setPosition(layer: iconLayer, position: absolutePosition)
|
||||
iconLayer.updateRotation(effectiveAngle, transition: iconTransition)
|
||||
iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction))
|
||||
iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction)
|
||||
|
||||
@ -585,7 +588,12 @@ private class GiftIconLayer: SimpleLayer {
|
||||
|
||||
override func layoutSublayers() {
|
||||
self.shadowLayer.frame = CGRect(origin: .zero, size: self.bounds.size).insetBy(dx: -8.0, dy: -8.0)
|
||||
self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size)
|
||||
self.animationLayer.bounds = CGRect(origin: .zero, size: self.bounds.size)
|
||||
self.animationLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0)
|
||||
}
|
||||
|
||||
func updateRotation(_ angle: CGFloat, transition: ComponentTransition) {
|
||||
self.animationLayer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)
|
||||
}
|
||||
|
||||
func startAnimations(index: Int) {
|
||||
@ -719,7 +727,6 @@ private struct PositionGenerator {
|
||||
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
|
||||
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
|
||||
|
||||
// Get the absolute position to check boundaries and collisions
|
||||
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
|
||||
|
||||
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
|
||||
@ -766,9 +773,7 @@ private struct PositionGenerator {
|
||||
let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2)
|
||||
let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next())
|
||||
|
||||
// Get the absolute position to check boundaries and collisions
|
||||
let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint)
|
||||
|
||||
if absolutePosition.x - itemSize.width/2 < self.edgePadding ||
|
||||
absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding ||
|
||||
absolutePosition.y - itemSize.height/2 < self.edgePadding ||
|
||||
|
@ -74,6 +74,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
||||
|
||||
func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) {
|
||||
let sideInset: CGFloat = 24.0
|
||||
let expandedSideInset: CGFloat = 16.0
|
||||
|
||||
let maximumExpandOffset: CGFloat = 14.0
|
||||
let expandOffset: CGFloat = -expandFraction * maximumExpandOffset
|
||||
@ -188,7 +189,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
||||
self.currentRightButtons = rightButtons
|
||||
|
||||
var nextRegularButtonOrigin = size.width - sideInset - 8.0
|
||||
var nextExpandedButtonOrigin = size.width - sideInset - 8.0
|
||||
var nextExpandedButtonOrigin = size.width - expandedSideInset
|
||||
for spec in rightButtons.reversed() {
|
||||
let buttonNode: PeerInfoHeaderNavigationButton
|
||||
var wasAdded = false
|
||||
@ -257,7 +258,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
||||
for key in removeKeys {
|
||||
if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) {
|
||||
if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch {
|
||||
buttonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in
|
||||
buttonNode.layer.animateAlpha(from: buttonNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in
|
||||
buttonNode?.removeFromSupernode()
|
||||
})
|
||||
buttonNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false)
|
||||
@ -268,7 +269,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
|
||||
}
|
||||
} else {
|
||||
var nextRegularButtonOrigin = size.width - sideInset - 8.0
|
||||
var nextExpandedButtonOrigin = size.width - sideInset - 8.0
|
||||
var nextExpandedButtonOrigin = size.width - expandedSideInset
|
||||
|
||||
for spec in rightButtons.reversed() {
|
||||
var key = spec.key
|
||||
|
@ -2289,6 +2289,9 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
}
|
||||
if !buttonKeys.isEmpty {
|
||||
backgroundDefaultHeight = 327.0
|
||||
if metrics.isTablet {
|
||||
backgroundDefaultHeight += 60.0
|
||||
}
|
||||
}
|
||||
hasBackground = true
|
||||
} else if let peer {
|
||||
@ -2308,6 +2311,7 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
files: [:],
|
||||
isDark: presentationData.theme.overallDarkAppearance,
|
||||
avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0),
|
||||
avatarSize: apparentAvatarFrame.size,
|
||||
avatarScale: avatarScale,
|
||||
defaultHeight: backgroundDefaultHeight,
|
||||
gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45),
|
||||
|
@ -379,7 +379,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
let optionSpacing: CGFloat = 10.0
|
||||
let itemsSideInset = params.sideInset + 16.0
|
||||
|
||||
let defaultItemsInRow = params.size.width > params.size.height ? 5 : 3
|
||||
let defaultItemsInRow = params.size.width > params.size.height || params.size.width > 414.0 ? 5 : 3
|
||||
let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow))
|
||||
let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow)
|
||||
let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow)
|
||||
@ -613,7 +613,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
|
||||
var bottomScrollInset: CGFloat = 0.0
|
||||
var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
|
||||
var contentHeight = ceil(CGFloat(starsProducts.count) / CGFloat(defaultItemsInRow)) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0
|
||||
|
||||
let size = params.size
|
||||
let sideInset = params.sideInset
|
||||
@ -677,13 +677,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
let buttonSideInset = sideInset + 16.0
|
||||
let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0)
|
||||
var bottomPanelHeight = max(8.0, bottomInset) + buttonSize.height + 8.0
|
||||
let effectiveBottomInset = max(8.0, bottomInset)
|
||||
var bottomPanelHeight = effectiveBottomInset + buttonSize.height + 8.0
|
||||
if params.visibleHeight < 110.0 {
|
||||
scrollOffset -= bottomPanelHeight
|
||||
}
|
||||
|
||||
let panelTransition = ComponentTransition.immediate
|
||||
panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize))
|
||||
panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - effectiveBottomInset - buttonSize.height - scrollOffset), size: buttonSize))
|
||||
panelTransition.setAlpha(view: panelButton.view, alpha: panelAlpha)
|
||||
let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate)
|
||||
|
||||
@ -754,7 +755,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
if panelCheckView.superview == nil {
|
||||
self.view.addSubview(panelCheckView)
|
||||
}
|
||||
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - bottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
|
||||
panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - effectiveBottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize)
|
||||
panelTransition.setAlpha(view: panelCheckView, alpha: panelAlpha)
|
||||
}
|
||||
panelButton.isHidden = true
|
||||
@ -1015,7 +1016,26 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone()
|
||||
if self.context.isPremium {
|
||||
let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone()
|
||||
} else {
|
||||
let text = strings.Gift_View_TooltipPremiumWearing
|
||||
let tooltipController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .premiumPaywall(title: nil, text: text, customUndoText: nil, timeout: nil, linkAction: nil),
|
||||
position: .bottom,
|
||||
animateInAsReplacement: false,
|
||||
appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0),
|
||||
action: { [weak self] action in
|
||||
if let self, case .info = action {
|
||||
let premiumController = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .messageEffects, forceDark: false, dismissed: nil)
|
||||
self.parentController?.push(premiumController)
|
||||
}
|
||||
return false
|
||||
}
|
||||
)
|
||||
self.parentController?.present(tooltipController, in: .current)
|
||||
}
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ swift_library(
|
||||
"//submodules/AsyncDisplayKit",
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import ComponentFlow
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
@ -38,6 +39,10 @@ public final class StarsBalanceOverlayComponent: Component {
|
||||
private let action = ComponentView<Empty>()
|
||||
|
||||
private var component: StarsBalanceOverlayComponent?
|
||||
private var state: EmptyComponentState?
|
||||
|
||||
private var balance: Int64 = 0
|
||||
private var balanceDisposable: Disposable?
|
||||
|
||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
@ -53,17 +58,43 @@ public final class StarsBalanceOverlayComponent: Component {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.balanceDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func tapped() {
|
||||
if let component = self.component {
|
||||
component.action()
|
||||
}
|
||||
}
|
||||
|
||||
private var isUpdating = false
|
||||
func update(component: StarsBalanceOverlayComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
self.component = component
|
||||
|
||||
if self.balanceDisposable == nil, let starsContext = component.context.starsContext {
|
||||
self.balanceDisposable = (starsContext.state
|
||||
|> map { state -> Int64 in
|
||||
return state?.balance.value ?? 0
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
|> deliverOnMainQueue).start(next: { [weak self] balance in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.balance = balance
|
||||
if !self.isUpdating {
|
||||
self.state?.updated()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
let balance = presentationStringsFormattedNumber(Int32(component.context.starsContext?.currentState?.balance.value ?? 0), presentationData.dateTimeFormat.groupingSeparator)
|
||||
let balance = presentationStringsFormattedNumber(Int32(self.balance), presentationData.dateTimeFormat.groupingSeparator)
|
||||
|
||||
let attributedText = parseMarkdownIntoAttributedString(
|
||||
presentationData.strings.StarsBalance_YourBalance("**⭐️\(balance)**").string,
|
||||
@ -121,23 +152,27 @@ public final class StarsBalanceOverlayComponent: Component {
|
||||
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.addSubview(textView)
|
||||
self.backgroundView.addSubview(textView)
|
||||
}
|
||||
textView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 10.0), size: textSize)
|
||||
}
|
||||
|
||||
if let actionView = self.action.view {
|
||||
if actionView.superview == nil {
|
||||
self.addSubview(actionView)
|
||||
self.backgroundView.addSubview(actionView)
|
||||
}
|
||||
actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize)
|
||||
}
|
||||
|
||||
self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate)
|
||||
self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate)
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size))
|
||||
transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - size.width) / 2.0), y: 0.0), size: size))
|
||||
|
||||
return size
|
||||
return CGSize(width: availableSize.width, height: size.height)
|
||||
}
|
||||
|
||||
public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
return self.backgroundView.frame.contains(point)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,6 +241,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent {
|
||||
textString = strings.Stars_Purchase_StarGiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
||||
case .upgradeStarGift:
|
||||
textString = strings.Stars_Purchase_UpgradeStarGiftInfo
|
||||
case .transferStarGift:
|
||||
textString = strings.Stars_Purchase_TransferStarGiftInfo
|
||||
case let .sendMessage(peerId, _):
|
||||
if peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
textString = strings.Stars_Purchase_SendMessageInfo(component.peers.first?.value.compactDisplayTitle ?? "").string
|
||||
@ -828,7 +830,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent {
|
||||
titleText = strings.Stars_Purchase_GetStars
|
||||
case .gift:
|
||||
titleText = strings.Stars_Purchase_GiftStars
|
||||
case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .sendMessage(_, requiredStars):
|
||||
case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars):
|
||||
titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars))
|
||||
}
|
||||
|
||||
@ -1274,6 +1276,8 @@ private extension StarsPurchasePurpose {
|
||||
return requiredStars
|
||||
case let .upgradeStarGift(requiredStars):
|
||||
return requiredStars
|
||||
case let .transferStarGift(requiredStars):
|
||||
return requiredStars
|
||||
case let .sendMessage(_, requiredStars):
|
||||
return requiredStars
|
||||
default:
|
||||
|
@ -1530,15 +1530,15 @@ private final class StarsTransactionSheetContent: CombinedComponent {
|
||||
.position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY))
|
||||
)
|
||||
originY += button.size.height
|
||||
originY += 7.0
|
||||
}
|
||||
|
||||
context.add(closeButton
|
||||
.position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0))
|
||||
)
|
||||
|
||||
let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom)
|
||||
|
||||
return contentSize
|
||||
let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom
|
||||
return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,6 +358,7 @@ public final class AccountContextImpl: AccountContext {
|
||||
let _ = currentAppConfiguration.swap(value)
|
||||
})
|
||||
|
||||
let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
|
||||
self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes()))
|
||||
if !temp {
|
||||
let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
|
||||
|
@ -13,6 +13,7 @@ import PresentationDataUtils
|
||||
import UndoUI
|
||||
import UrlHandling
|
||||
import TelegramPresentationData
|
||||
import ChatInterfaceState
|
||||
|
||||
func openWebAppImpl(
|
||||
context: AccountContext,
|
||||
@ -182,7 +183,7 @@ func openWebAppImpl(
|
||||
var isInline = false
|
||||
var botId = botPeer.id
|
||||
var botName = botName
|
||||
var botAddress = ""
|
||||
var botAddress = botPeer.addressName ?? ""
|
||||
var botVerified = botPeer.isVerified
|
||||
if case let .inline(bot) = source {
|
||||
isInline = true
|
||||
@ -367,8 +368,20 @@ public extension ChatControllerImpl {
|
||||
}
|
||||
}
|
||||
}
|
||||
let inputString = "@\(botAddress) \(query)"
|
||||
if let chatController {
|
||||
chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, "@\(botAddress) \(query)", nil)
|
||||
chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, inputString, nil)
|
||||
} else if let selectedPeer, let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController {
|
||||
let textInputState = ChatTextInputState(inputText: NSAttributedString(string: inputString))
|
||||
let _ = (ChatInterfaceState.update(engine: context.engine, peerId: selectedPeer.id, threadId: nil, { currentState in
|
||||
return currentState.withUpdatedComposeInputState(textInputState)
|
||||
})
|
||||
|> deliverOnMainQueue).startStandalone(completed: { [weak navigationController] in
|
||||
guard let navigationController else {
|
||||
return
|
||||
}
|
||||
context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(selectedPeer), subject: nil, updateTextInputState: textInputState, peekData: nil))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5795,7 +5795,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
if case let .known(value) = cachedData.businessIntro {
|
||||
businessIntro = value
|
||||
}
|
||||
sendPaidMessageStars = cachedData.sendPaidMessageStars
|
||||
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
} else {
|
||||
sendPaidMessageStars = cachedData.sendPaidMessageStars
|
||||
}
|
||||
} else if let cachedData = peerView.cachedData as? CachedGroupData {
|
||||
var invitedBy: Peer?
|
||||
if let invitedByPeerId = cachedData.invitedBy {
|
||||
|
@ -3,6 +3,7 @@ import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import LocalizedPeerData
|
||||
@ -14,6 +15,7 @@ import TextNodeWithEntities
|
||||
import AnimationCache
|
||||
import MultiAnimationRenderer
|
||||
import AccountContext
|
||||
import PremiumUI
|
||||
|
||||
private enum ChatReportPeerTitleButton: Equatable {
|
||||
case block
|
||||
@ -344,7 +346,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let context: AccountContext
|
||||
private let animationCache: AnimationCache
|
||||
private let animationRenderer: MultiAnimationRenderer
|
||||
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
|
||||
private let closeButton: HighlightableButtonNode
|
||||
@ -354,12 +356,17 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
private let emojiSeparatorNode: ASDisplayNode
|
||||
|
||||
private var theme: PresentationTheme?
|
||||
private var presentationInterfaceState: ChatPresentationInterfaceState?
|
||||
|
||||
private var inviteInfoNode: ChatInfoTitlePanelInviteInfoNode?
|
||||
private var peerNearbyInfoNode: ChatInfoTitlePanelPeerNearbyInfoNode?
|
||||
|
||||
private var cachedChevronImage: (UIImage, PresentationTheme)?
|
||||
|
||||
private var emojiStatusPackDisposable = MetaDisposable()
|
||||
private var emojiStatusFileId: Int64?
|
||||
private var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>()
|
||||
|
||||
private var tapGestureRecognizer: UITapGestureRecognizer?
|
||||
|
||||
init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) {
|
||||
@ -391,6 +398,10 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.addSubnode(self.closeButton)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.emojiStatusPackDisposable.dispose()
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
@ -405,27 +416,33 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
}
|
||||
|
||||
private func openPremiumEmojiStatusDemo() {
|
||||
guard let navigationController = self.interfaceInteraction?.getNavigationController() else {
|
||||
guard let navigationController = self.interfaceInteraction?.getNavigationController(), let peerId = self.presentationInterfaceState?.chatLocation.peerId, let emojiStatus = self.presentationInterfaceState?.renderedPeer?.peer?.emojiStatus, case let .emoji(fileId) = emojiStatus.content else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.context.isPremium {
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .animatedEmoji, forceDark: false, dismissed: nil)
|
||||
navigationController.pushViewController(controller)
|
||||
} else {
|
||||
var replaceImpl: ((ViewController) -> Void)?
|
||||
let controller = self.context.sharedContext.makePremiumDemoController(context: self.context, subject: .emojiStatus, forceDark: false, action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .animatedEmoji, forceDark: false, dismissed: nil)
|
||||
replaceImpl?(controller)
|
||||
}, dismissed: nil)
|
||||
replaceImpl = { [weak controller] c in
|
||||
controller?.replace(with: c)
|
||||
let source: Signal<PremiumSource, NoError> = self.emojiStatusFileAndPackTitle.get()
|
||||
|> take(1)
|
||||
|> mapToSignal { emojiStatusFileAndPack -> Signal<PremiumSource, NoError> in
|
||||
if let (file, pack) = emojiStatusFileAndPack {
|
||||
return .single(.emojiStatus(peerId, fileId, file, pack))
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
navigationController.pushViewController(controller)
|
||||
}
|
||||
|
||||
let _ = (source
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak self, weak navigationController] source in
|
||||
guard let self, let navigationController else {
|
||||
return
|
||||
}
|
||||
let controller = PremiumIntroScreen(context: self.context, source: source)
|
||||
if let textView = self.emojiStatusTextNode?.view {
|
||||
controller.sourceView = textView
|
||||
controller.sourceRect = CGRect(origin: .zero, size: CGSize(width: textView.frame.height, height: textView.frame.height))
|
||||
}
|
||||
controller.containerView = navigationController.view
|
||||
navigationController.pushViewController(controller)
|
||||
})
|
||||
}
|
||||
|
||||
override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
|
||||
@ -436,7 +453,8 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
self.emojiSeparatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
|
||||
}
|
||||
|
||||
self.presentationInterfaceState = interfaceState
|
||||
|
||||
var panelHeight: CGFloat = 40.0
|
||||
|
||||
let contentRightInset: CGFloat = 14.0 + rightInset
|
||||
@ -583,7 +601,43 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
}
|
||||
}
|
||||
|
||||
if let emojiStatus = emojiStatus, case .emoji = emojiStatus.content {
|
||||
if let emojiStatus = emojiStatus, case let .emoji(fileId) = emojiStatus.content {
|
||||
if self.emojiStatusFileId != fileId {
|
||||
self.emojiStatusFileId = fileId
|
||||
|
||||
let emojiFileAndPack = self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId])
|
||||
|> mapToSignal { result in
|
||||
if let emojiFile = result.first?.value {
|
||||
for attribute in emojiFile.attributes {
|
||||
if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference {
|
||||
return self.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false)
|
||||
|> filter { result in
|
||||
if case .result = result {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|> mapToSignal { result -> Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError> in
|
||||
if case let .result(_, items, _) = result {
|
||||
return .single(items.first.flatMap { ($0.file._parse(), result) })
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
self.emojiStatusPackDisposable.set(emojiFileAndPack.startStrict(next: { [weak self] fileAndPackTitle in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.emojiStatusFileAndPackTitle.set(.single(fileAndPackTitle))
|
||||
}))
|
||||
}
|
||||
|
||||
self.emojiSeparatorNode.isHidden = false
|
||||
|
||||
transition.updateFrame(node: self.emojiSeparatorNode, frame: CGRect(origin: CGPoint(x: leftInset + 12.0, y: 40.0), size: CGSize(width: width - leftInset - rightInset - 24.0, height: UIScreenPixel)))
|
||||
|
@ -972,6 +972,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
if groupCallController.view.superview == nil {
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||
}
|
||||
} else if let streamController = self.streamController {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
if streamController.view.superview == nil {
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(streamController)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -1358,8 +1363,23 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
let streamController = MediaStreamComponentController(call: group)
|
||||
streamController.navigationPresentation = .flatModal
|
||||
streamController.parentNavigationController = navigationController
|
||||
|
||||
let thisCallIsOnScreenPromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
streamController.onViewDidAppear = {
|
||||
thisCallIsOnScreenPromise.set(true)
|
||||
}
|
||||
streamController.onViewDidDisappear = {
|
||||
thisCallIsOnScreenPromise.set(false)
|
||||
}
|
||||
|
||||
self.streamController = streamController
|
||||
|
||||
self.mainWindow?.hostView.containerView.endEditing(true)
|
||||
|
||||
thisCallIsOnScreenPromise.set(true)
|
||||
self.hasGroupCallOnScreenPromise.set(thisCallIsOnScreenPromise.get())
|
||||
beginDisplayingCallStatusBar.set(.single(Void()))
|
||||
|
||||
navigationController.pushViewController(streamController)
|
||||
}
|
||||
}
|
||||
@ -1787,6 +1807,11 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController)
|
||||
}
|
||||
} else if let streamController = self.streamController {
|
||||
if streamController.isNodeLoaded && streamController.view.superview == nil {
|
||||
mainWindow.hostView.containerView.endEditing(true)
|
||||
(mainWindow.viewController as? NavigationController)?.pushViewController(streamController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2877,62 +2902,102 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
}
|
||||
|
||||
let optionsPromise = Promise<[StarsTopUpOption]?>(nil)
|
||||
if let state = context.starsContext?.currentState, state.balance < StarsAmount(value: 100, nanos: 0) {
|
||||
optionsPromise.set(context.engine.payments.starsTopUpOptions()
|
||||
|> map(Optional.init))
|
||||
}
|
||||
|
||||
presentTransferAlertImpl = { [weak controller] peer in
|
||||
guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else {
|
||||
return
|
||||
}
|
||||
let alertController = giftTransferAlertController(context: context, gift: gift, peer: peer, transferStars: transferStars, commit: { [weak controller] in
|
||||
completion?([peer.id])
|
||||
|
||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is ContactSelectionController) }
|
||||
|
||||
if !isChannelGift {
|
||||
if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
if let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
controllers.append(controller)
|
||||
let alertController = giftTransferAlertController(
|
||||
context: context,
|
||||
gift: gift,
|
||||
peer: peer,
|
||||
transferStars: transferStars,
|
||||
navigationController: controller.navigationController as? NavigationController,
|
||||
commit: { [weak controller] in
|
||||
let proceed: (Bool) -> Void = { waitForTopUp in
|
||||
completion?([peer.id])
|
||||
|
||||
guard let controller, let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation {
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
var controllers = navigationController.viewControllers
|
||||
controllers = controllers.filter { !($0 is ContactSelectionController) }
|
||||
|
||||
if !isChannelGift {
|
||||
if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
if let controller = context.sharedContext.makePeerInfoController(
|
||||
context: context,
|
||||
updatedPresentationData: nil,
|
||||
peer: peer._asPeer(),
|
||||
mode: .gifts,
|
||||
avatarInitiallyExpanded: false,
|
||||
fromChat: false,
|
||||
requestsContext: nil
|
||||
) {
|
||||
controllers.append(controller)
|
||||
}
|
||||
} else {
|
||||
var foundController = false
|
||||
for controller in controllers.reversed() {
|
||||
if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation {
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
foundController = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
controllers.append(chatController)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !foundController {
|
||||
let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil)
|
||||
chatController.hintPlayNextOutgoingGift()
|
||||
controllers.append(chatController)
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
let tooltipController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .forward(savedMessages: false, text: presentationData.strings.Gift_Transfer_Success("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return true }
|
||||
)
|
||||
if let lastController = controllers.last as? ViewController {
|
||||
lastController.present(tooltipController, in: .window(.root))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
navigationController.setViewControllers(controllers, animated: true)
|
||||
|
||||
Queue.mainQueue().after(0.3) {
|
||||
let tooltipController = UndoOverlayController(
|
||||
presentationData: presentationData,
|
||||
content: .forward(savedMessages: false, text: presentationData.strings.Gift_Transfer_Success("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string),
|
||||
elevatedLayout: false,
|
||||
action: { _ in return true }
|
||||
)
|
||||
if let lastController = controllers.last as? ViewController {
|
||||
lastController.present(tooltipController, in: .window(.root))
|
||||
|
||||
if transferStars > 0, let starsContext = context.starsContext, let starsState = starsContext.currentState {
|
||||
if starsState.balance < StarsAmount(value: transferStars, nanos: 0) {
|
||||
let _ = (optionsPromise.get()
|
||||
|> filter { $0 != nil }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] options in
|
||||
let purchaseController = context.sharedContext.makeStarsPurchaseScreen(
|
||||
context: context,
|
||||
starsContext: starsContext,
|
||||
options: options ?? [],
|
||||
purpose: .transferStarGift(requiredStars: transferStars),
|
||||
completion: { stars in
|
||||
starsContext.add(balance: StarsAmount(value: stars, nanos: 0))
|
||||
proceed(true)
|
||||
}
|
||||
)
|
||||
controller?.push(purchaseController)
|
||||
})
|
||||
} else {
|
||||
proceed(false)
|
||||
}
|
||||
} else {
|
||||
proceed(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
controller.present(alertController, in: .window(.root))
|
||||
}
|
||||
|
||||
|
@ -2232,6 +2232,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
|
||||
self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString)
|
||||
|
||||
controller.isFullscreen = isFullscreen
|
||||
if isFullscreen {
|
||||
controller.requestAttachmentMenuExpansion()
|
||||
}
|
||||
|
||||
if let (layout, _) = self.validLayout, case .regular = layout.metrics.widthClass {
|
||||
if let snapshotView = self.webView?.snapshotView(afterScreenUpdates: false) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "11.8.1",
|
||||
"app": "11.8.2",
|
||||
"xcode": "16.2",
|
||||
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
|
||||
"macos": "15"
|
||||
|
Loading…
x
Reference in New Issue
Block a user