Merge commit '09d2d0d716c96f924e8595533681169a06e063bd'

This commit is contained in:
Ali 2023-10-23 17:42:05 +04:00
commit 1f814962ad
24 changed files with 637 additions and 135 deletions

View File

@ -23,6 +23,7 @@ public final class ReactionIconView: PortalSourceView {
private var file: TelegramMediaFile? private var file: TelegramMediaFile?
private var animationCache: AnimationCache? private var animationCache: AnimationCache?
private var animationRenderer: MultiAnimationRenderer? private var animationRenderer: MultiAnimationRenderer?
private var contentTintColor: UIColor?
private var placeholderColor: UIColor? private var placeholderColor: UIColor?
private var size: CGSize? private var size: CGSize?
private var animateIdle: Bool? private var animateIdle: Bool?
@ -58,6 +59,7 @@ public final class ReactionIconView: PortalSourceView {
fileId: Int64, fileId: Int64,
animationCache: AnimationCache, animationCache: AnimationCache,
animationRenderer: MultiAnimationRenderer, animationRenderer: MultiAnimationRenderer,
tintColor: UIColor?,
placeholderColor: UIColor, placeholderColor: UIColor,
animateIdle: Bool, animateIdle: Bool,
reaction: MessageReaction.Reaction, reaction: MessageReaction.Reaction,
@ -66,6 +68,7 @@ public final class ReactionIconView: PortalSourceView {
self.context = context self.context = context
self.animationCache = animationCache self.animationCache = animationCache
self.animationRenderer = animationRenderer self.animationRenderer = animationRenderer
self.contentTintColor = tintColor
self.placeholderColor = placeholderColor self.placeholderColor = placeholderColor
self.size = size self.size = size
self.animateIdle = animateIdle self.animateIdle = animateIdle
@ -112,6 +115,8 @@ public final class ReactionIconView: PortalSourceView {
transition.updateFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)) transition.updateFrame(layer: animationLayer, frame: CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize))
} }
self.updateTintColor()
} }
public func updateIsAnimationHidden(isAnimationHidden: Bool, transition: ContainedViewLayoutTransition) { public func updateIsAnimationHidden(isAnimationHidden: Bool, transition: ContainedViewLayoutTransition) {
@ -170,6 +175,36 @@ public final class ReactionIconView: PortalSourceView {
animationLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize) animationLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
animationLayer.isVisibleForAnimations = animateIdle && context.sharedContext.energyUsageSettings.loopEmoji animationLayer.isVisibleForAnimations = animateIdle && context.sharedContext.energyUsageSettings.loopEmoji
self.updateTintColor()
}
private func updateTintColor() {
guard let file = self.file, let animationLayer = self.animationLayer else {
return
}
var accentTint = false
if file.isCustomTemplateEmoji {
accentTint = true
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
accentTint = true
}
default:
break
}
}
}
if accentTint, let tintColor = self.contentTintColor {
animationLayer.contentTintColor = tintColor
animationLayer.dynamicColor = tintColor
} else {
animationLayer.contentTintColor = nil
animationLayer.dynamicColor = nil
}
} }
func reset() { func reset() {
@ -769,6 +804,17 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
animateIdle = false animateIdle = false
} }
let tintColor: UIColor
if layout.backgroundLayout.colors.isSelected {
if layout.spec.component.colors.selectedForeground != 0 {
tintColor = UIColor(argb: layout.spec.component.colors.selectedForeground)
} else {
tintColor = .white
}
} else {
tintColor = UIColor(argb: layout.spec.component.colors.deselectedForeground)
}
iconView.update( iconView.update(
size: layout.imageFrame.size, size: layout.imageFrame.size,
context: layout.spec.component.context, context: layout.spec.component.context,
@ -776,6 +822,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
fileId: fileId, fileId: fileId,
animationCache: arguments.animationCache, animationCache: arguments.animationCache,
animationRenderer: arguments.animationRenderer, animationRenderer: arguments.animationRenderer,
tintColor: tintColor,
placeholderColor: layout.spec.component.chosenOrder != nil ? UIColor(argb: layout.spec.component.colors.selectedMediaPlaceholder) : UIColor(argb: layout.spec.component.colors.deselectedMediaPlaceholder), placeholderColor: layout.spec.component.chosenOrder != nil ? UIColor(argb: layout.spec.component.colors.selectedMediaPlaceholder) : UIColor(argb: layout.spec.component.colors.deselectedMediaPlaceholder),
animateIdle: animateIdle, animateIdle: animateIdle,
reaction: layout.spec.component.reaction.value, reaction: layout.spec.component.reaction.value,

View File

@ -218,6 +218,37 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
} }
reactionLayer.frame = iconFrame reactionLayer.frame = iconFrame
} }
self.updateReactionAccentColor()
}
func updateReactionAccentColor() {
guard let file = self.file, let reactionLayer = self.reactionLayer, let theme = self.theme else {
return
}
var accentTint = false
if file.isCustomTemplateEmoji {
accentTint = true
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
accentTint = true
}
default:
break
}
}
}
if accentTint {
reactionLayer.contentTintColor = theme.contextMenu.badgeFillColor
reactionLayer.dynamicColor = theme.contextMenu.badgeFillColor
} else {
reactionLayer.contentTintColor = nil
reactionLayer.dynamicColor = nil
}
} }
func update(presentationData: PresentationData, constrainedSize: CGSize, isSelected: Bool) -> CGSize { func update(presentationData: PresentationData, constrainedSize: CGSize, isSelected: Bool) -> CGSize {
@ -227,6 +258,8 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
if let iconNode = self.iconNode { if let iconNode = self.iconNode {
iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: presentationData.theme.contextMenu.primaryColor) iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: presentationData.theme.contextMenu.primaryColor)
} }
self.updateReactionLayer()
} }
let sideInset: CGFloat = 12.0 let sideInset: CGFloat = 12.0
@ -478,6 +511,35 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
} }
} }
func updateReactionAccentColor(theme: PresentationTheme) {
guard let file = self.file, let reactionLayer = self.reactionLayer else {
return
}
var accentTint = false
if file.isCustomTemplateEmoji {
accentTint = true
}
for attribute in file.attributes {
if case let .CustomEmoji(_, _, _, packReference) = attribute {
switch packReference {
case let .id(id, _):
if id == 773947703670341676 || id == 2964141614563343 {
accentTint = true
}
default:
break
}
}
}
if accentTint {
reactionLayer.contentTintColor = theme.contextMenu.badgeFillColor
reactionLayer.dynamicColor = theme.contextMenu.badgeFillColor
} else {
reactionLayer.contentTintColor = nil
reactionLayer.dynamicColor = nil
}
}
func update(size: CGSize, presentationData: PresentationData, item: EngineMessageReactionListContext.Item, isLast: Bool, syncronousLoad: Bool) { func update(size: CGSize, presentationData: PresentationData, item: EngineMessageReactionListContext.Item, isLast: Bool, syncronousLoad: Bool) {
let avatarInset: CGFloat = 12.0 let avatarInset: CGFloat = 12.0
let avatarSpacing: CGFloat = 8.0 let avatarSpacing: CGFloat = 8.0
@ -495,6 +557,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
if availableReaction.value == reaction { if availableReaction.value == reaction {
self.file = availableReaction.centerAnimation self.file = availableReaction.centerAnimation
self.updateReactionLayer() self.updateReactionLayer()
self.updateReactionAccentColor(theme: presentationData.theme)
break break
} }
} }
@ -507,6 +570,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
} }
strongSelf.file = file strongSelf.file = file
strongSelf.updateReactionLayer() strongSelf.updateReactionLayer()
strongSelf.updateReactionAccentColor(theme: presentationData.theme)
}).strict() }).strict()
} }
} else { } else {
@ -520,6 +584,7 @@ public final class ReactionListContextMenuContent: ContextControllerItemsContent
} }
} }
} }
self.updateReactionAccentColor(theme: presentationData.theme)
if self.item != item { if self.item != item {
self.item = item self.item = item

View File

@ -39,7 +39,6 @@ public final class SheetComponentEnvironment: Equatable {
public let sheetComponentTag = GenericComponentViewTag() public let sheetComponentTag = GenericComponentViewTag()
public final class SheetComponent<ChildEnvironmentType: Equatable>: Component { public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment) public typealias EnvironmentType = (ChildEnvironmentType, SheetComponentEnvironment)
public enum BackgroundColor: Equatable { public enum BackgroundColor: Equatable {
@ -54,15 +53,18 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
public let content: AnyComponent<ChildEnvironmentType> public let content: AnyComponent<ChildEnvironmentType>
public let backgroundColor: BackgroundColor public let backgroundColor: BackgroundColor
public let followContentSizeChanges: Bool
public let animateOut: ActionSlot<Action<()>> public let animateOut: ActionSlot<Action<()>>
public init( public init(
content: AnyComponent<ChildEnvironmentType>, content: AnyComponent<ChildEnvironmentType>,
backgroundColor: BackgroundColor, backgroundColor: BackgroundColor,
followContentSizeChanges: Bool = false,
animateOut: ActionSlot<Action<()>> animateOut: ActionSlot<Action<()>>
) { ) {
self.content = content self.content = content
self.backgroundColor = backgroundColor self.backgroundColor = backgroundColor
self.followContentSizeChanges = followContentSizeChanges
self.animateOut = animateOut self.animateOut = animateOut
} }
@ -73,6 +75,9 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
if lhs.backgroundColor != rhs.backgroundColor { if lhs.backgroundColor != rhs.backgroundColor {
return false return false
} }
if lhs.followContentSizeChanges != rhs.followContentSizeChanges {
return false
}
if lhs.animateOut != rhs.animateOut { if lhs.animateOut != rhs.animateOut {
return false return false
} }
@ -338,12 +343,15 @@ public final class SheetComponent<ChildEnvironmentType: Equatable>: Component {
} }
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil) transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize), completion: nil)
let previousContentSize = self.scrollView.contentSize
self.scrollView.contentSize = contentSize self.scrollView.contentSize = contentSize
self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height) + contentSize.height, left: 0.0, bottom: 0.0, right: 0.0) self.scrollView.contentInset = UIEdgeInsets(top: max(0.0, availableSize.height - contentSize.height) + contentSize.height, left: 0.0, bottom: 0.0, right: 0.0)
self.ignoreScrolling = false self.ignoreScrolling = false
if let currentAvailableSize = self.currentAvailableSize, currentAvailableSize.height != availableSize.height { if let currentAvailableSize = self.currentAvailableSize, currentAvailableSize.height != availableSize.height {
self.scrollView.contentOffset = CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)) self.scrollView.contentOffset = CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height))
} else if component.followContentSizeChanges, !previousContentSize.height.isZero, previousContentSize != contentSize {
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)), size: availableSize))
} }
if self.currentHasInputHeight != previousHasInputHeight { if self.currentHasInputHeight != previousHasInputHeight {
transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)), size: self.scrollView.bounds.size)) transition.setBounds(view: self.scrollView, bounds: CGRect(origin: CGPoint(x: 0.0, y: -(availableSize.height - contentSize.height)), size: self.scrollView.bounds.size))

View File

@ -0,0 +1,123 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import AccountContext
import TelegramPresentationData
import UndoUI
import PresentationDataUtils
//TODO:localize
public func PremiumBoostScreen(
context: AccountContext,
contentContext: Any?,
peerId: EnginePeer.Id,
isCurrent: Bool,
status: ChannelBoostStatus?,
myBoostStatus: MyBoostStatus?,
forceDark: Bool,
openPeer: @escaping (EnginePeer) -> Void,
presentController: @escaping (ViewController) -> Void,
pushController: @escaping (ViewController) -> Void,
dismissed: @escaping () -> Void
) {
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let peer, let status else {
return
}
var myBoostCount: Int32 = 0
var availableBoosts: [MyBoostStatus.Boost] = []
var occupiedBoosts: [MyBoostStatus.Boost] = []
if let myBoostStatus {
for boost in myBoostStatus.boosts {
if let boostPeer = boost.peer {
if boostPeer.id == peer.id {
myBoostCount += 1
} else {
occupiedBoosts.append(boost)
}
} else {
availableBoosts.append(boost)
}
}
}
var currentLevel = Int32(status.level)
var currentLevelBoosts = Int32(status.currentLevelBoosts)
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
if myBoostCount > 0 && status.boosts == currentLevelBoosts {
currentLevel = max(0, currentLevel - 1)
nextLevelBoosts = currentLevelBoosts
currentLevelBoosts = max(0, currentLevelBoosts - 1)
}
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount)
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount + 1)
var nextCount = Int32(status.boosts + 1)
var updateImpl: (() -> Void)?
var dismissImpl: (() -> Void)?
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: {
let dismiss = false
updateImpl?()
return dismiss
},
openPeer: { peer in
openPeer(peer)
})
pushController(controller)
controller.disposed = {
dismissed()
}
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
updateImpl = { [weak controller] in
if let _ = status.nextLevelBoosts {
if let availableBoost = availableBoosts.first {
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]).startStandalone()
controller?.updateSubject(nextSubject, count: nextCount)
availableBoosts.removeFirst()
nextCount += 1
} else if !occupiedBoosts.isEmpty, let myBoostStatus {
let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone()
let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "\(slots.count) boosts are reassigned from 1 other channel.", timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, action: { _ in return true })
presentController(undoController)
})
dismissImpl?()
pushController(replaceController)
} else {
let controller = textAlertController(
sharedContext: context.sharedContext,
updatedPresentationData: nil,
title: presentationData.strings.ChannelBoost_Error_PremiumNeededTitle,
text: presentationData.strings.ChannelBoost_Error_PremiumNeededText,
actions: [
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}),
TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Yes, action: {
dismissImpl?()
let controller = context.sharedContext.makePremiumIntroController(context: context, source: .channelBoost(peerId), forceDark: forceDark, dismissed: nil)
pushController(controller)
})
],
parseMarkdown: true
)
presentController(controller)
}
} else {
dismissImpl?()
}
}
dismissImpl = { [weak controller] in
controller?.dismissAnimated()
}
})
}

View File

@ -1262,11 +1262,6 @@ public class PremiumDemoScreen: ViewControllerComponentContainer {
self.view.disablesInteractiveModalDismiss = true self.view.disablesInteractiveModalDismiss = true
} }
public override func replace(with controller: ViewController) {
self.dismissAnimated()
super.replace(with: controller)
}
public func dismissAnimated() { public func dismissAnimated() {
if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View { if let view = self.node.hostView.findTaggedView(tag: SheetComponent<ViewControllerComponentContainer.Environment>.View.Tag()) as? SheetComponent<ViewControllerComponentContainer.Environment>.View {
view.dismissAnimated() view.dismissAnimated()

View File

@ -162,6 +162,9 @@ public class PremiumLimitDisplayComponent: Component {
private let badgeLabel: BadgeLabelView private let badgeLabel: BadgeLabelView
private let badgeLabelMaskView = UIImageView() private let badgeLabelMaskView = UIImageView()
private var badgeTailPosition: CGFloat = 0.0
private var badgeShapeArguments: (Double, Double, CGSize, CGFloat, CGFloat)?
private let hapticFeedback = HapticFeedback() private let hapticFeedback = HapticFeedback()
override init(frame: CGRect) { override init(frame: CGRect) {
@ -232,6 +235,10 @@ public class PremiumLimitDisplayComponent: Component {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
deinit {
self.badgeShapeAnimator?.invalidate()
}
private var didPlayAppearanceAnimation = false private var didPlayAppearanceAnimation = false
func playAppearanceAnimation(component: PremiumLimitDisplayComponent, badgeFullSize: CGSize, from: CGFloat? = nil) { func playAppearanceAnimation(component: PremiumLimitDisplayComponent, badgeFullSize: CGSize, from: CGFloat? = nil) {
if from == nil { if from == nil {
@ -534,16 +541,29 @@ public class PremiumLimitDisplayComponent: Component {
if badgePosition > 1.0 - 0.15 { if badgePosition > 1.0 - 0.15 {
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 1.0, y: 1.0)) progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 1.0, y: 1.0))
progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 1.0).cgPath)
if component.isPremiumDisabled || progressTransition.animation.isImmediate {
self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 1.0).cgPath
} else {
self.badgeShapeArguments = (CACurrentMediaTime(), 0.5, badgeSize, self.badgeTailPosition, 1.0)
self.animateBadgeTailPositionChange()
}
self.badgeTailPosition = 1.0
if let _ = self.badgeView.layer.animation(forKey: "appearance1") { if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
} else { } else {
self.badgeView.center = CGPoint(x: 3.0 + (size.width - 6.0) * badgePosition + 3.0, y: 82.0) self.badgeView.center = CGPoint(x: 3.0 + (size.width - 6.0) * badgePosition + 3.0, y: 82.0)
} }
} else if badgePosition < 0.15 { } else if badgePosition < 0.15 {
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.0, y: 1.0)) progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.0, y: 1.0))
progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.0).cgPath)
if component.isPremiumDisabled || progressTransition.animation.isImmediate {
self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.0).cgPath
} else {
self.badgeShapeArguments = (CACurrentMediaTime(), 0.5, badgeSize, self.badgeTailPosition, 0.0)
self.animateBadgeTailPositionChange()
}
self.badgeTailPosition = 0.0
if let _ = self.badgeView.layer.animation(forKey: "appearance1") { if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
@ -552,7 +572,14 @@ public class PremiumLimitDisplayComponent: Component {
} }
} else { } else {
progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0)) progressTransition.setAnchorPoint(layer: self.badgeView.layer, anchorPoint: CGPoint(x: 0.5, y: 1.0))
progressTransition.setShapeLayerPath(layer: self.badgeShapeLayer, path: generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.5).cgPath)
if component.isPremiumDisabled || progressTransition.animation.isImmediate {
self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: component.isPremiumDisabled ? nil : 0.5).cgPath
} else {
self.badgeShapeArguments = (CACurrentMediaTime(), 0.5, badgeSize, self.badgeTailPosition, 0.5)
self.animateBadgeTailPositionChange()
}
self.badgeTailPosition = 0.5
if let _ = self.badgeView.layer.animation(forKey: "appearance1") { if let _ = self.badgeView.layer.animation(forKey: "appearance1") {
@ -617,6 +644,36 @@ public class PremiumLimitDisplayComponent: Component {
return size return size
} }
private var badgeShapeAnimator: ConstantDisplayLinkAnimator?
private func animateBadgeTailPositionChange() {
if self.badgeShapeAnimator == nil {
self.badgeShapeAnimator = ConstantDisplayLinkAnimator(update: { [weak self] in
self?.animateBadgeTailPositionChange()
})
self.badgeShapeAnimator?.isPaused = true
}
if let (startTime, duration, badgeSize, initial, target) = self.badgeShapeArguments {
self.badgeShapeAnimator?.isPaused = false
let t = CGFloat(max(0.0, min(1.0, (CACurrentMediaTime() - startTime) / duration)))
let value = initial + (target - initial) * t
self.badgeShapeLayer.path = generateRoundedRectWithTailPath(rectSize: badgeSize, tailPosition: value).cgPath
if t >= 1.0 {
self.badgeShapeArguments = nil
self.badgeShapeAnimator?.isPaused = true
self.badgeShapeAnimator?.invalidate()
self.badgeShapeAnimator = nil
}
} else {
self.badgeShapeAnimator?.isPaused = true
self.badgeShapeAnimator?.invalidate()
self.badgeShapeAnimator = nil
}
}
private func setupGradientAnimations() { private func setupGradientAnimations() {
guard let _ = self.component else { guard let _ = self.component else {
return return
@ -1143,12 +1200,14 @@ private final class LimitSheetContent: CombinedComponent {
string = strings.ChannelBoost_HelpUpgradeChannelText(peer.compactDisplayTitle, boostsString, storiesString).string string = strings.ChannelBoost_HelpUpgradeChannelText(peer.compactDisplayTitle, boostsString, storiesString).string
} }
actionButtonText = strings.ChannelBoost_BoostChannel actionButtonText = strings.ChannelBoost_BoostChannel
buttonIconName = "Premium/BoostChannel"
} else { } else {
titleText = strings.ChannelBoost_MaxLevelReached titleText = strings.ChannelBoost_MaxLevelReached
string = strings.ChannelBoost_BoostedChannelReachedLevel("\(level)", storiesString).string string = strings.ChannelBoost_BoostedChannelReachedLevel("\(level)", storiesString).string
actionButtonText = strings.Common_OK actionButtonText = strings.Common_OK
buttonIconName = nil
actionButtonHasGloss = false
} }
buttonIconName = "Premium/BoostChannel"
} }
buttonAnimationName = nil buttonAnimationName = nil
defaultTitle = strings.ChannelBoost_Level("\(level)").string defaultTitle = strings.ChannelBoost_Level("\(level)").string
@ -1161,11 +1220,13 @@ private final class LimitSheetContent: CombinedComponent {
let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText let prefixString = isCurrent ? strings.ChannelBoost_YouBoostedChannelText(peer.compactDisplayTitle).string : strings.ChannelBoost_YouBoostedOtherChannelText
let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1) let storiesString = strings.ChannelBoost_StoriesPerDay(level + 1)
if let _ = remaining {
// buttonIconName = nil
// actionButtonText = environment.strings.Common_OK
actionButtonText = "Boost Again" actionButtonText = "Boost Again"
} else {
buttonIconName = nil
actionButtonText = environment.strings.Common_OK
actionButtonHasGloss = false
}
if let remaining { if let remaining {
let boostsString = strings.ChannelBoost_MoreBoosts(remaining) let boostsString = strings.ChannelBoost_MoreBoosts(remaining)
if level == 0 { if level == 0 {
@ -1209,7 +1270,6 @@ private final class LimitSheetContent: CombinedComponent {
reachedMaximumLimit = false reachedMaximumLimit = false
} }
let contentSize: CGSize let contentSize: CGSize
if state.initialized { if state.initialized {
let title = title.update( let title = title.update(
@ -1391,6 +1451,7 @@ private final class LimitSheetContent: CombinedComponent {
})) }))
) )
} else if let alternateTextChild { } else if let alternateTextChild {
textOffset += 9.0
textSize = alternateTextChild.size textSize = alternateTextChild.size
context.add(alternateTextChild context.add(alternateTextChild
.position(CGPoint(x: context.availableSize.width / 2.0, y: textOffset)) .position(CGPoint(x: context.availableSize.width / 2.0, y: textOffset))
@ -1498,7 +1559,7 @@ private final class LimitSheetContent: CombinedComponent {
.position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.maxY + 50.0 + giftText.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: buttonFrame.maxY + 50.0 + giftText.size.height / 2.0))
) )
additionalContentHeight += giftText.size.height + 50.0 additionalContentHeight += giftText.size.height + 30.0
} }
contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + additionalContentHeight + 5.0 + environment.safeInsets.bottom) contentSize = CGSize(width: context.availableSize.width, height: buttonFrame.maxY + additionalContentHeight + 5.0 + environment.safeInsets.bottom)
@ -1598,6 +1659,7 @@ private final class LimitSheetComponent: CombinedComponent {
openGift: context.component.openGift openGift: context.component.openGift
)), )),
backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor),
followContentSizeChanges: true,
animateOut: animateOut animateOut: animateOut
), ),
environment: { environment: {

View File

@ -19,6 +19,8 @@ import PeerListItemComponent
import TelegramStringFormatting import TelegramStringFormatting
import AvatarNode import AvatarNode
//TODO:localize
private final class ReplaceBoostScreenComponent: CombinedComponent { private final class ReplaceBoostScreenComponent: CombinedComponent {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment
@ -799,6 +801,7 @@ public class ReplaceBoostScreen: ViewController {
self?.node.selectedSlots = selectedSlots self?.node.selectedSlots = selectedSlots
} }
presentControllerImpl = { [weak self] c in presentControllerImpl = { [weak self] c in
self?.dismissAllTooltips()
self?.present(c, in: .window(.root)) self?.present(c, in: .window(.root))
} }
@ -819,15 +822,29 @@ public class ReplaceBoostScreen: ViewController {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
@objc private func cancelPressed() {
self.dismiss(animated: true, completion: nil)
}
override open func loadDisplayNode() { override open func loadDisplayNode() {
self.displayNode = Node(context: self.context, controller: self, component: self.component) self.displayNode = Node(context: self.context, controller: self, component: self.component)
self.displayNodeDidLoad() self.displayNodeDidLoad()
} }
fileprivate func dismissAllTooltips() {
self.window?.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismiss()
}
})
self.forEachController({ controller in
if let controller = controller as? UndoOverlayController {
controller.dismiss()
}
return true
})
}
@objc private func cancelPressed() {
self.dismiss(animated: true, completion: nil)
}
public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { public override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
self.view.endEditing(true) self.view.endEditing(true)

View File

@ -133,9 +133,9 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
return { item, params, neighbors in return { item, params, neighbors in
if currentBackgroundNode == nil { if currentBackgroundNode == nil {
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false) currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
}
currentBackgroundNode?.update(wallpaper: item.wallpaper) currentBackgroundNode?.update(wallpaper: item.wallpaper)
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
}
let insets: UIEdgeInsets let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
@ -189,6 +189,11 @@ class ForwardPrivacyChatPreviewItemNode: ListViewItemNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
if let currentBackgroundNode {
currentBackgroundNode.update(wallpaper: item.wallpaper)
currentBackgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
}
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
var topOffset: CGFloat = 8.0 var topOffset: CGFloat = 8.0

View File

@ -261,9 +261,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
return { item, params, neighbors in return { item, params, neighbors in
if currentBackgroundNode == nil { if currentBackgroundNode == nil {
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false) currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
}
currentBackgroundNode?.update(wallpaper: item.wallpaper) currentBackgroundNode?.update(wallpaper: item.wallpaper)
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners) currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
}
let insets: UIEdgeInsets let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
@ -330,6 +330,11 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
strongSelf.item = item strongSelf.item = item
if let currentBackgroundNode {
currentBackgroundNode.update(wallpaper: item.wallpaper)
currentBackgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
}
if strongSelf.genericReactionEffectDisposable == nil { if strongSelf.genericReactionEffectDisposable == nil {
strongSelf.loadNextGenericReactionEffect(context: item.context) strongSelf.loadNextGenericReactionEffect(context: item.context)
} }

View File

@ -128,7 +128,7 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
case let .messageActionSetSameChatWallPaper(wallpaper): case let .messageActionSetSameChatWallPaper(wallpaper):
return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper))) return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper)))
case let .messageActionGiftCode(flags, boostPeer, months, slug): case let .messageActionGiftCode(flags, boostPeer, months, slug):
return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, boostPeerId: boostPeer?.peerId, months: months)) return TelegramMediaAction(action: .giftCode(slug: slug, fromGiveaway: (flags & (1 << 0)) != 0, isUnclaimed: (flags & (1 << 1)) != 0, boostPeerId: boostPeer?.peerId, months: months))
case .messageActionGiveawayLaunch: case .messageActionGiveawayLaunch:
return TelegramMediaAction(action: .giveawayLaunched) return TelegramMediaAction(action: .giveawayLaunched)
} }

View File

@ -109,7 +109,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case requestedPeer(buttonId: Int32, peerId: PeerId) case requestedPeer(buttonId: Int32, peerId: PeerId)
case setChatWallpaper(wallpaper: TelegramWallpaper) case setChatWallpaper(wallpaper: TelegramWallpaper)
case setSameChatWallpaper(wallpaper: TelegramWallpaper) case setSameChatWallpaper(wallpaper: TelegramWallpaper)
case giftCode(slug: String, fromGiveaway: Bool, boostPeerId: PeerId?, months: Int32) case giftCode(slug: String, fromGiveaway: Bool, isUnclaimed: Bool, boostPeerId: PeerId?, months: Int32)
case giveawayLaunched case giveawayLaunched
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -206,7 +206,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case 35: case 35:
self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) }) self = .botAppAccessGranted(appName: decoder.decodeOptionalStringForKey("app"), type: decoder.decodeOptionalInt32ForKey("atp").flatMap { BotSendMessageAccessGrantedType(rawValue: $0) })
case 36: case 36:
self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), boostPeerId: PeerId(decoder.decodeInt64ForKey("pi", orElse: 0)), months: decoder.decodeInt32ForKey("months", orElse: 0)) self = .giftCode(slug: decoder.decodeStringForKey("slug", orElse: ""), fromGiveaway: decoder.decodeBoolForKey("give", orElse: false), isUnclaimed: decoder.decodeBoolForKey("unclaimed", orElse: false), boostPeerId: PeerId(decoder.decodeInt64ForKey("pi", orElse: 0)), months: decoder.decodeInt32ForKey("months", orElse: 0))
case 37: case 37:
self = .giveawayLaunched self = .giveawayLaunched
default: default:
@ -388,10 +388,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
} else { } else {
encoder.encodeNil(forKey: "atp") encoder.encodeNil(forKey: "atp")
} }
case let .giftCode(slug, fromGiveaway, boostPeerId, months): case let .giftCode(slug, fromGiveaway, unclaimed, boostPeerId, months):
encoder.encodeInt32(36, forKey: "_rawValue") encoder.encodeInt32(36, forKey: "_rawValue")
encoder.encodeString(slug, forKey: "slug") encoder.encodeString(slug, forKey: "slug")
encoder.encodeBool(fromGiveaway, forKey: "give") encoder.encodeBool(fromGiveaway, forKey: "give")
encoder.encodeBool(unclaimed, forKey: "unclaimed")
if let boostPeerId = boostPeerId { if let boostPeerId = boostPeerId {
encoder.encodeInt64(boostPeerId.toInt64(), forKey: "pi") encoder.encodeInt64(boostPeerId.toInt64(), forKey: "pi")
} else { } else {
@ -421,7 +422,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
return peerIds return peerIds
case let .requestedPeer(_, peerId): case let .requestedPeer(_, peerId):
return [peerId] return [peerId]
case let .giftCode(_, _, boostPeerId, _): case let .giftCode(_, _, _, boostPeerId, _):
return boostPeerId.flatMap { [$0] } ?? [] return boostPeerId.flatMap { [$0] } ?? []
default: default:
return [] return []

View File

@ -166,6 +166,7 @@ private final class StatusReactionNode: ASDisplayNode {
fileId: fileId, fileId: fileId,
animationCache: animationCache, animationCache: animationCache,
animationRenderer: animationRenderer, animationRenderer: animationRenderer,
tintColor: nil,
placeholderColor: placeholderColor, placeholderColor: placeholderColor,
animateIdle: animateIdle, animateIdle: animateIdle,
reaction: value, reaction: value,

View File

@ -189,6 +189,7 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
} }
} }
//TODO:localize
override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) { override public func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeLabelLayout = TextNode.asyncLayout(self.labelNode) let makeLabelLayout = TextNode.asyncLayout(self.labelNode)
let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
@ -220,17 +221,22 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
case let .giftPremium(_, _, monthsValue, _, _): case let .giftPremium(_, _, monthsValue, _, _):
months = monthsValue months = monthsValue
text = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string text = item.presentationData.strings.Notification_PremiumGift_Subtitle(item.presentationData.strings.Notification_PremiumGift_Months(months)).string
case let .giftCode(_, fromGiveaway, channelId, monthsValue): case let .giftCode(_, fromGiveaway, unclaimed, channelId, monthsValue):
giftSize.width += 34.0 giftSize.width += 34.0
giftSize.height += 84.0
textSpacing += 13.0 textSpacing += 13.0
if unclaimed {
title = "Unclaimed Prize"
} else {
title = "Congratulations!" title = "Congratulations!"
}
var peerName = "" var peerName = ""
if let channelId, let channel = item.message.peers[channelId] { if let channelId, let channel = item.message.peers[channelId] {
peerName = EnginePeer(channel).compactDisplayTitle peerName = EnginePeer(channel).compactDisplayTitle
} }
if fromGiveaway { if unclaimed {
text = "You have an unclaimed prize from a giveaway by **\(peerName)**.\n\nThis prize is a **Telegram Premium** subscription for **\(monthsValue)** months."
} else if fromGiveaway {
text = "You won a prize in a giveaway organized by **\(peerName)**.\n\nYour prize is a **Telegram Premium** subscription for **\(monthsValue)** months." text = "You won a prize in a giveaway organized by **\(peerName)**.\n\nYour prize is a **Telegram Premium** subscription for **\(monthsValue)** months."
} else { } else {
text = "You've received a gift from **\(peerName)**.\n\nYour gift is a **Telegram Premium** subscription for **\(monthsValue)** months." text = "You've received a gift from **\(peerName)**.\n\nYour gift is a **Telegram Premium** subscription for **\(monthsValue)** months."
@ -273,6 +279,8 @@ public class ChatMessageGiftBubbleContentNode: ChatMessageBubbleContentNode {
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: buttonTitle, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: giftSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
giftSize.height = titleLayout.size.height + subtitleLayout.size.height + 225.0
var labelRects = labelLayout.linesRects() var labelRects = labelLayout.linesRects()
if labelRects.count > 1 { if labelRects.count > 1 {
let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width }) let sortedIndices = (0 ..< labelRects.count).sorted(by: { labelRects[$0].width > labelRects[$1].width })

View File

@ -449,6 +449,7 @@ public final class MessageInputActionButtonComponent: Component {
fileId: animationFileId ?? reactionFile?.fileId.id ?? 0, fileId: animationFileId ?? reactionFile?.fileId.id ?? 0,
animationCache: component.context.animationCache, animationCache: component.context.animationCache,
animationRenderer: component.context.animationRenderer, animationRenderer: component.context.animationRenderer,
tintColor: nil,
placeholderColor: UIColor(white: 1.0, alpha: 0.2), placeholderColor: UIColor(white: 1.0, alpha: 0.2),
animateIdle: false, animateIdle: false,
reaction: reaction, reaction: reaction,

View File

@ -168,9 +168,9 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
return { item, params, neighbors in return { item, params, neighbors in
if currentBackgroundNode == nil { if currentBackgroundNode == nil {
currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false) currentBackgroundNode = createWallpaperBackgroundNode(context: item.context, forChatDisplay: false)
}
currentBackgroundNode?.update(wallpaper: item.wallpaper) currentBackgroundNode?.update(wallpaper: item.wallpaper)
currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners) currentBackgroundNode?.updateBubbleTheme(bubbleTheme: item.componentTheme, bubbleCorners: item.chatBubbleCorners)
}
let insets: UIEdgeInsets let insets: UIEdgeInsets
let separatorHeight = UIScreenPixel let separatorHeight = UIScreenPixel
@ -250,6 +250,11 @@ final class PeerNameColorChatPreviewItemNode: ListViewItemNode {
if let strongSelf = self { if let strongSelf = self {
strongSelf.item = item strongSelf.item = item
if let currentBackgroundNode {
currentBackgroundNode.update(wallpaper: item.wallpaper)
currentBackgroundNode.updateBubbleTheme(bubbleTheme: item.theme, bubbleCorners: item.chatBubbleCorners)
}
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize) strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId { if let currentItem, currentItem.messageItems.first?.nameColor != item.messageItems.first?.nameColor || currentItem.messageItems.first?.backgroundEmojiId != item.messageItems.first?.backgroundEmojiId {

View File

@ -412,7 +412,8 @@ public func PeerNameColorScreen(
updatedState.inProgress = true updatedState.inProgress = true
return updatedState return updatedState
} }
let _ = context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0).startStandalone(next: { let _ = (context.engine.peers.updatePeerNameColorAndEmoji(peerId: peerId, nameColor: nameColor ?? .blue, backgroundEmojiId: backgroundEmojiId ?? 0)
|> deliverOnMainQueue).startStandalone(next: {
}, error: { error in }, error: { error in
if case .channelBoostRequired = error { if case .channelBoostRequired = error {
let _ = combineLatest( let _ = combineLatest(

View File

@ -25,6 +25,7 @@ import TelegramUIPreferences
import UndoUI import UndoUI
import TelegramStringFormatting import TelegramStringFormatting
//TODO:localize
final class ShareWithPeersScreenComponent: Component { final class ShareWithPeersScreenComponent: Component {
typealias EnvironmentType = ViewControllerComponentContainer.Environment typealias EnvironmentType = ViewControllerComponentContainer.Environment

View File

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

View File

@ -0,0 +1,193 @@
%PDF-1.7
1 0 obj
<< /Type /XObject
/Length 2 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
1.000000 0.584314 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
q
1.000000 0.000000 -0.000000 1.000000 5.000000 3.864258 cm
1.000000 1.000000 1.000000 scn
17.269485 19.786890 m
18.869095 20.947046 20.890579 18.978296 19.730423 17.325953 c
19.405228 16.877710 16.109329 12.034937 13.402297 8.967554 c
12.690383 11.103296 11.038039 12.755640 8.911086 13.458765 c
11.978469 16.165796 16.821243 19.461695 17.269485 19.786890 c
h
8.867141 8.932398 m
8.164016 9.635523 7.267532 10.136499 6.476516 10.285913 c
6.590774 10.927515 6.889602 11.481226 7.408157 12.034937 c
7.496047 12.131617 7.583938 12.219507 7.680618 12.307398 c
9.965775 11.982203 11.908156 10.022242 12.242141 7.737085 c
12.154250 7.640406 12.066360 7.552515 11.960891 7.464624 c
11.415969 6.954859 10.879836 6.664820 10.229445 6.541773 c
10.071242 7.332788 9.570266 8.229273 8.867141 8.932398 c
h
0.385696 3.527124 m
-0.062546 4.072046 -0.141648 4.564234 0.262649 4.889429 c
0.746047 5.267359 1.378860 4.915796 2.081985 5.460718 c
2.864211 6.058374 2.310500 7.394312 3.690383 8.466578 c
5.043899 9.565210 7.074172 9.204859 8.287063 7.666773 c
9.623000 5.961695 9.368118 3.869898 7.592727 2.481226 c
5.465774 0.811304 2.064407 1.400171 0.385696 3.527124 c
h
f
n
Q
endstream
endobj
2 0 obj
2104
endobj
3 0 obj
<< /Type /XObject
/Length 4 0 R
/Group << /Type /Group
/S /Transparency
>>
/Subtype /Form
/Resources << >>
/BBox [ 0.000000 0.000000 30.000000 30.000000 ]
>>
stream
/DeviceRGB CS
/DeviceRGB cs
q
1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
0.000000 0.000000 0.000000 scn
0.000000 18.799999 m
0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c
1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c
5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c
18.799999 30.000000 l
22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c
27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c
30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c
30.000000 11.200001 l
30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c
28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c
24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c
11.200000 0.000000 l
7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c
2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c
0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c
0.000000 18.799999 l
h
f
n
Q
endstream
endobj
4 0 obj
944
endobj
5 0 obj
<< /XObject << /X1 1 0 R >>
/ExtGState << /E1 << /SMask << /Type /Mask
/G 3 0 R
/S /Alpha
>>
/Type /ExtGState
>> >>
>>
endobj
6 0 obj
<< /Length 7 0 R >>
stream
/DeviceRGB CS
/DeviceRGB cs
q
/E1 gs
/X1 Do
Q
endstream
endobj
7 0 obj
46
endobj
8 0 obj
<< /Annots []
/Type /Page
/MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
/Resources 5 0 R
/Contents 6 0 R
/Parent 9 0 R
>>
endobj
9 0 obj
<< /Kids [ 8 0 R ]
/Count 1
/Type /Pages
>>
endobj
10 0 obj
<< /Pages 9 0 R
/Type /Catalog
>>
endobj
xref
0 11
0000000000 65535 f
0000000010 00000 n
0000002362 00000 n
0000002385 00000 n
0000003577 00000 n
0000003599 00000 n
0000003897 00000 n
0000003999 00000 n
0000004020 00000 n
0000004193 00000 n
0000004267 00000 n
trailer
<< /ID [ (some) (id) ]
/Root 10 0 R
/Size 11
>>
startxref
4327
%%EOF

View File

@ -948,7 +948,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration)) let controller = PremiumIntroScreen(context: strongSelf.context, source: .gift(from: fromPeerId, to: toPeerId, duration: duration))
strongSelf.push(controller) strongSelf.push(controller)
return true return true
case let .giftCode(slug, _, _, _): case let .giftCode(slug, _, _, _, _):
strongSelf.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id) strongSelf.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id)
return true return true
case let .suggestedProfilePhoto(image): case let .suggestedProfilePhoto(image):

View File

@ -842,101 +842,49 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
} }
}) })
case let .boost(peerId, status, myBoostStatus): case let .boost(peerId, status, myBoostStatus):
let _ = myBoostStatus
var forceDark = false
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
forceDark = true
}
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> deliverOnMainQueue).startStandalone(next: { peer in
guard let peer, let status else {
return
}
var myBoostCount: Int32 = 0
var availableBoosts: [MyBoostStatus.Boost] = []
var occupiedBoosts: [MyBoostStatus.Boost] = []
if let myBoostStatus {
for boost in myBoostStatus.boosts {
if let boostPeer = boost.peer {
if boostPeer.id == peer.id {
myBoostCount += 1
} else {
occupiedBoosts.append(boost)
}
} else {
availableBoosts.append(boost)
}
}
}
var isCurrent = false var isCurrent = false
if case let .chat(chatPeerId, _) = urlContext, chatPeerId == peerId { if case let .chat(chatPeerId, _) = urlContext, chatPeerId == peerId {
isCurrent = true isCurrent = true
} }
var forceDark = false
var currentLevel = Int32(status.level) if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {
var currentLevelBoosts = Int32(status.currentLevelBoosts) forceDark = true
var nextLevelBoosts = status.nextLevelBoosts.flatMap(Int32.init)
if myBoostCount > 0 && status.boosts == currentLevelBoosts {
currentLevel = max(0, currentLevel - 1)
nextLevelBoosts = currentLevelBoosts
currentLevelBoosts = max(0, currentLevelBoosts - 1)
} }
let subject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount) var dismissedImpl: (() -> Void)?
let nextSubject: PremiumLimitScreen.Subject = .storiesChannelBoost(peer: peer, isCurrent: isCurrent, level: currentLevel, currentLevelBoosts: currentLevelBoosts, nextLevelBoosts: nextLevelBoosts, link: nil, myBoostCount: myBoostCount + 1)
var nextCount = Int32(status.boosts + 1)
var updateImpl: (() -> Void)?
var dismissImpl: (() -> Void)?
let controller = PremiumLimitScreen(context: context, subject: subject, count: Int32(status.boosts), forceDark: forceDark, action: {
let dismiss = false
updateImpl?()
return dismiss
},
openPeer: { peer in
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
})
navigationController?.pushViewController(controller)
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext { if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
storyProgressPauseContext.update(controller)
let updateExternalController = storyProgressPauseContext.update let updateExternalController = storyProgressPauseContext.update
controller.disposed = { dismissedImpl = {
updateExternalController(nil) updateExternalController(nil)
} }
} }
updateImpl = { [weak controller] in PremiumBoostScreen(
if let _ = status.nextLevelBoosts { context: context,
if let availableBoost = availableBoosts.first { contentContext: contentContext,
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: [availableBoost.slot]).startStandalone() peerId: peerId,
controller?.updateSubject(nextSubject, count: nextCount) isCurrent: isCurrent,
status: status,
myBoostStatus: myBoostStatus,
forceDark: forceDark,
openPeer: { peer in
openPeer(peer, .chat(textInputState: nil, subject: nil, peekData: nil))
},
presentController: { [weak navigationController] c in
(navigationController?.viewControllers.last as? ViewController)?.present(c, in: .window(.root))
},
pushController: { [weak navigationController] c in
navigationController?.pushViewController(c)
availableBoosts.removeFirst() if c is PremiumLimitScreen {
nextCount += 1 if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
} else if !occupiedBoosts.isEmpty, let myBoostStatus { storyProgressPauseContext.update(c)
let replaceController = ReplaceBoostScreen(context: context, peerId: peerId, myBoostStatus: myBoostStatus, replaceBoosts: { slots in
let _ = context.engine.peers.applyChannelBoost(peerId: peerId, slots: slots).startStandalone()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let undoController = UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "\(slots.count) boosts are reassigned from 1 other channel.", timeout: nil, customUndoText: nil), elevatedLayout: true, position: .bottom, action: { _ in return true })
(navigationController?.viewControllers.last as? ViewController)?.present(undoController, in: .window(.root))
})
dismissImpl?()
navigationController?.pushViewController(replaceController)
}
} else {
dismissImpl?()
} }
} }
dismissImpl = { [weak controller] in }, dismissed: {
controller?.dismissAnimated() dismissedImpl?()
} }
}) )
case let .premiumGiftCode(slug): case let .premiumGiftCode(slug):
var forceDark = false var forceDark = false
if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance { if let updatedPresentationData, updatedPresentationData.initial.theme.overallDarkAppearance {

View File

@ -156,7 +156,11 @@ private final class PeerInfoScreenDisclosureItemNode: PeerInfoScreenItemNode {
self.textNode.attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: textColorValue) self.textNode.attributedText = NSAttributedString(string: item.text, font: titleFont, textColor: textColorValue)
let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude)) let textSize = self.textNode.updateLayout(CGSize(width: width - (leftInset + rightInset), height: .greatestFiniteMagnitude))
let labelSize = self.labelNode.updateLayout(CGSize(width: width - textSize.width - (leftInset + rightInset), height: .greatestFiniteMagnitude)) var labelConstrainWidth = width - textSize.width - (leftInset + rightInset)
if case .semitransparentBadge = item.label {
labelConstrainWidth -= 16.0
}
let labelSize = self.labelNode.updateLayout(CGSize(width: labelConstrainWidth, height: .greatestFiniteMagnitude))
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: textSize) let textFrame = CGRect(origin: CGPoint(x: leftInset, y: 12.0), size: textSize)

View File

@ -1568,12 +1568,6 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
})) }))
} }
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemNameColor, label: .semitransparentBadge(EnginePeer(channel).compactDisplayTitle, (data.peer?.nameColor ?? .blue).color), text: "Channel Color", icon: UIImage(bundleImageName: "Settings/Menu/Appearance"), action: {
interaction.editingOpenNameColorSetup()
}))
}
if (isCreator && (channel.addressName?.isEmpty ?? true)) || (!channel.flags.contains(.isCreator) && channel.adminRights?.rights.contains(.canInviteUsers) == true) { if (isCreator && (channel.addressName?.isEmpty ?? true)) || (!channel.flags.contains(.isCreator) && channel.adminRights?.rights.contains(.canInviteUsers) == true) {
let invitesText: String let invitesText: String
if let count = data.invitations?.count, count > 0 { if let count = data.invitations?.count, count > 0 {
@ -1626,6 +1620,12 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
})) }))
} }
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemNameColor, label: .semitransparentBadge(EnginePeer(channel).compactDisplayTitle, (data.peer?.nameColor ?? .blue).color), text: "Channel Color", icon: UIImage(bundleImageName: "Chat/Info/NameColorIcon"), action: {
interaction.editingOpenNameColorSetup()
}))
}
if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendSomething)) { if isCreator || (channel.adminRights != nil && channel.hasPermission(.sendSomething)) {
let messagesShouldHaveSignatures: Bool let messagesShouldHaveSignatures: Bool
switch channel.info { switch channel.info {