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

This commit is contained in:
Ilya Laktyushin 2023-12-19 22:55:22 +04:00
commit 0d2bdf5132
27 changed files with 676 additions and 488 deletions

View File

@ -10771,3 +10771,5 @@ Sorry for the inconvenience.";
"ChatList.PremiumXmasGiftTitle" = "Send gifts to **your friends**! 🎄";
"ChatList.PremiumXmasGiftText" = "Gift Telegram Premium for Christmas.";
"ReassignBoost.DescriptionWithLink" = "To boost **%1$@**, reassign a previous boost or [gift Telegram Premium]() to a friend to get **%2$@** additional boosts.";

View File

@ -52,6 +52,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
public let maxReadStoryId: Int32?
public let recommendedChannels: RecommendedChannels?
public let audioTranscriptionTrial: AudioTranscription.TrialState
public let chatThemes: [TelegramTheme]
public init(
automaticDownloadPeerType: MediaAutoDownloadPeerType,
@ -77,7 +78,8 @@ public final class ChatMessageItemAssociatedData: Equatable {
translateToLanguage: String? = nil,
maxReadStoryId: Int32? = nil,
recommendedChannels: RecommendedChannels? = nil,
audioTranscriptionTrial: AudioTranscription.TrialState = .defaultValue
audioTranscriptionTrial: AudioTranscription.TrialState = .defaultValue,
chatThemes: [TelegramTheme] = []
) {
self.automaticDownloadPeerType = automaticDownloadPeerType
self.automaticDownloadPeerId = automaticDownloadPeerId
@ -103,6 +105,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
self.maxReadStoryId = maxReadStoryId
self.recommendedChannels = recommendedChannels
self.audioTranscriptionTrial = audioTranscriptionTrial
self.chatThemes = chatThemes
}
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
@ -175,6 +178,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
if lhs.audioTranscriptionTrial != rhs.audioTranscriptionTrial {
return false
}
if lhs.chatThemes != rhs.chatThemes {
return false
}
return true
}
}

View File

@ -634,14 +634,17 @@ public class AttachmentTextInputPanelNode: ASDisplayNode, TGCaptionPanelView, AS
let fieldMaxHeight = textFieldMaxHeight(maxHeight, metrics: metrics)
var textFieldMinHeight: CGFloat = 35.0
var textFieldRealInsets = UIEdgeInsets()
if let presentationInterfaceState = self.presentationInterfaceState {
textFieldMinHeight = calclulateTextFieldMinHeight(presentationInterfaceState, metrics: metrics)
textFieldRealInsets = calculateTextFieldRealInsets(presentationInterfaceState)
}
let textFieldHeight: CGFloat
if let textInputNode = self.textInputNode {
let maxTextWidth = width - textFieldInsets.left - textFieldInsets.right - self.textInputViewInternalInsets.left - self.textInputViewInternalInsets.right
let measuredHeight = textInputNode.textHeightForWidth(maxTextWidth, rightInset: 0.0)
let measuredHeight = textInputNode.textHeightForWidth(maxTextWidth, rightInset: textFieldRealInsets.right)
let unboundTextFieldHeight = max(textFieldMinHeight, ceil(measuredHeight))
let maxNumberOfLines = min(12, (Int(fieldMaxHeight - 11.0) - 33) / 22)

View File

@ -722,15 +722,38 @@ private final class LimitSheetContent: CombinedComponent {
var levelsHeight: CGFloat = 0.0
var levelItems: [AnyComponentWithIdentity<Empty>] = []
var nameColorsAtLevel: [(Int32, Int32)] = []
var nameColorsCountMap: [Int32: Int32] = [:]
for color in context.component.context.peerNameColors.displayOrder {
if let level = context.component.context.peerNameColors.nameColorsChannelMinRequiredBoostLevel[color] {
if let current = nameColorsCountMap[level] {
nameColorsCountMap[level] = current + 1
} else {
nameColorsCountMap[level] = 1
}
}
}
for (key, value) in nameColorsCountMap {
nameColorsAtLevel.append((key, value))
}
if let nextLevels {
for level in nextLevels {
var perks: [LevelSectionComponent.Perk] = []
perks.append(.story(level))
perks.append(.reaction(level))
if level >= premiumConfiguration.minChannelNameColorLevel {
perks.append(.nameColor(7))
var nameColorsCount: Int32 = 0
for (colorLevel, count) in nameColorsAtLevel {
if level >= colorLevel && colorLevel == 1 {
nameColorsCount = count
}
}
if nameColorsCount > 0 {
perks.append(.nameColor(nameColorsCount))
}
if level >= premiumConfiguration.minChannelProfileColorLevel {
let delta = min(level - premiumConfiguration.minChannelProfileColorLevel + 1, 2)
perks.append(.profileColor(8 * delta))
@ -738,10 +761,17 @@ private final class LimitSheetContent: CombinedComponent {
if level >= premiumConfiguration.minChannelProfileIconLevel {
perks.append(.profileIcon)
}
if level >= premiumConfiguration.minChannelNameColorLevel {
let delta = min(level - premiumConfiguration.minChannelNameColorLevel + 1, 3)
perks.append(.linkColor(7 * delta))
var linkColorsCount: Int32 = 0
for (colorLevel, count) in nameColorsAtLevel {
if level >= colorLevel {
linkColorsCount += count
}
}
if linkColorsCount > 0 {
perks.append(.linkColor(linkColorsCount))
}
if level >= premiumConfiguration.minChannelNameIconLevel {
perks.append(.linkIcon)
}

View File

@ -28,14 +28,16 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
let initiallySelectedSlot: Int32?
let selectedSlotsUpdated: ([Int32]) -> Void
let presentController: (ViewController) -> Void
let giftPremium: () -> Void
init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, initiallySelectedSlot: Int32?, selectedSlotsUpdated: @escaping ([Int32]) -> Void, presentController: @escaping (ViewController) -> Void) {
init(context: AccountContext, peerId: EnginePeer.Id, myBoostStatus: MyBoostStatus, initiallySelectedSlot: Int32?, selectedSlotsUpdated: @escaping ([Int32]) -> Void, presentController: @escaping (ViewController) -> Void, giftPremium: @escaping () -> Void) {
self.context = context
self.peerId = peerId
self.myBoostStatus = myBoostStatus
self.initiallySelectedSlot = initiallySelectedSlot
self.selectedSlotsUpdated = selectedSlotsUpdated
self.presentController = presentController
self.giftPremium = giftPremium
}
static func ==(lhs: ReplaceBoostScreenComponent, rhs: ReplaceBoostScreenComponent) -> Bool {
@ -170,14 +172,26 @@ private final class ReplaceBoostScreenComponent: CombinedComponent {
if channelName.count > 48 {
channelName = "\(channelName.prefix(48))..."
}
let descriptionString = strings.ReassignBoost_Description(channelName, "\(premiumConfiguration.boostsPerGiftCount)").string
let descriptionString = strings.ReassignBoost_DescriptionWithLink(channelName, "\(premiumConfiguration.boostsPerGiftCount)").string
let giftPremium = context.component.giftPremium
let description = description.update(
component: MultilineTextComponent(
text: .markdown(text: descriptionString, attributes: markdownAttributes),
horizontalAlignment: .center,
maximumNumberOfLines: 0,
lineSpacing: 0.1
lineSpacing: 0.1,
highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.2),
highlightAction: { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] {
return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)
} else {
return nil
}
},
tapAction: { _, _ in
giftPremium()
}
),
environment: {},
availableSize: CGSize(width: availableSize.width - sideInset * 2.0 - textSideInset, height: availableSize.height),
@ -831,10 +845,13 @@ public class ReplaceBoostScreen: ViewController {
var selectedSlotsUpdatedImpl: (([Int32]) -> Void)?
var presentControllerImpl: ((ViewController) -> Void)?
var giftPremiumImpl: (() -> Void)?
self.init(context: context, component: ReplaceBoostScreenComponent(context: context, peerId: peerId, myBoostStatus: myBoostStatus, initiallySelectedSlot: initiallySelectedSlot, selectedSlotsUpdated: { slots in
selectedSlotsUpdatedImpl?(slots)
}, presentController: { c in
presentControllerImpl?(c)
}, giftPremium: {
giftPremiumImpl?()
}))
self.title = presentationData.strings.ReassignBoost_Title
@ -856,6 +873,17 @@ public class ReplaceBoostScreen: ViewController {
if let initiallySelectedSlot {
self.node.selectedSlots = [initiallySelectedSlot]
}
giftPremiumImpl = { [weak self] in
guard let self else {
return
}
let navigationController = self.navigationController
self.dismiss(animated: true, completion: {
let giftController = context.sharedContext.makePremiumGiftController(context: context)
navigationController?.pushViewController(giftController, animated: true)
})
}
}
private init<C: Component>(context: AccountContext, component: C, theme: PresentationTheme? = nil) where C.EnvironmentType == ViewControllerComponentContainer.Environment {

View File

@ -202,6 +202,12 @@ public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13
}, initialValues: [:], queue: queue)
}
public func combineLatest<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, E>(queue: Queue? = nil, _ s1: Signal<T1, E>, _ s2: Signal<T2, E>, _ s3: Signal<T3, E>, _ s4: Signal<T4, E>, _ s5: Signal<T5, E>, _ s6: Signal<T6, E>, _ s7: Signal<T7, E>, _ s8: Signal<T8, E>, _ s9: Signal<T9, E>, _ s10: Signal<T10, E>, _ s11: Signal<T11, E>, _ s12: Signal<T12, E>, _ s13: Signal<T13, E>, _ s14: Signal<T14, E>, _ s15: Signal<T15, E>, _ s16: Signal<T16, E>, _ s17: Signal<T17, E>, _ s18: Signal<T18, E>, _ s19: Signal<T19, E>, _ s20: Signal<T20, E>) -> Signal<(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20), E> {
return combineLatestAny([signalOfAny(s1), signalOfAny(s2), signalOfAny(s3), signalOfAny(s4), signalOfAny(s5), signalOfAny(s6), signalOfAny(s7), signalOfAny(s8), signalOfAny(s9), signalOfAny(s10), signalOfAny(s11), signalOfAny(s12), signalOfAny(s13), signalOfAny(s14), signalOfAny(s15), signalOfAny(s16), signalOfAny(s17), signalOfAny(s18), signalOfAny(s19), signalOfAny(s20)], combine: { values in
return (values[0] as! T1, values[1] as! T2, values[2] as! T3, values[3] as! T4, values[4] as! T5, values[5] as! T6, values[6] as! T7, values[7] as! T8, values[8] as! T9, values[9] as! T10, values[10] as! T11, values[11] as! T12, values[12] as! T13, values[13] as! T14, values[14] as! T15, values[15] as! T16, values[16] as! T17, values[17] as! T18, values[18] as! T19, values[19] as! T20)
}, initialValues: [:], queue: queue)
}
public func combineLatest<T, E>(queue: Queue? = nil, _ signals: [Signal<T, E>]) -> Signal<[T], E> {
if signals.count == 0 {
return single([T](), E.self)

View File

@ -587,7 +587,13 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.statusBar.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.containerView.layer.animateScale(from: 1.04, to: 1.0, duration: 0.3)
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
self.containerView.layer.allowsGroupOpacity = true
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, completion: { [weak self] _ in
guard let self else {
return
}
self.containerView.layer.allowsGroupOpacity = false
})
}
}

View File

@ -721,6 +721,7 @@ public final class MediaStreamComponent: CombinedComponent {
var infoItem: AnyComponent<Empty>?
if let originInfo = context.state.originInfo {
infoItem = AnyComponent(OriginInfoComponent(
strings: environment.strings,
memberCount: originInfo.memberCount
))
}
@ -933,6 +934,7 @@ public final class MediaStreamComponent: CombinedComponent {
let sheet = sheet.update(
component: StreamSheetComponent(
strings: environment.strings,
topOffset: topOffset,
sheetHeight: sheetHeight,
backgroundColor: (isFullscreen && !state.hasVideo) ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
@ -1691,15 +1693,21 @@ private final class StreamTitleComponent: Component {
private final class OriginInfoComponent: CombinedComponent {
let strings: PresentationStrings
let participantsCount: Int
init(
strings: PresentationStrings,
memberCount: Int
) {
self.strings = strings
self.participantsCount = memberCount
}
static func ==(lhs: OriginInfoComponent, rhs: OriginInfoComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.participantsCount != rhs.participantsCount {
return false
}
@ -1713,6 +1721,7 @@ private final class OriginInfoComponent: CombinedComponent {
return { context in
let viewerCounter = viewerCounter.update(
component: ParticipantsComponent(
strings: context.component.strings,
count: context.component.participantsCount,
showsSubtitle: true,
fontSize: 18.0,

View File

@ -9,12 +9,14 @@ private let purple = UIColor(rgb: 0x3252ef)
private let pink = UIColor(rgb: 0xe4436c)
final class ParticipantsComponent: Component {
private let strings: PresentationStrings
private let count: Int
private let showsSubtitle: Bool
private let fontSize: CGFloat
private let gradientColors: [CGColor]
init(count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48.0, gradientColors: [CGColor] = [pink.cgColor, purple.cgColor, purple.cgColor]) {
init(strings: PresentationStrings, count: Int, showsSubtitle: Bool = true, fontSize: CGFloat = 48.0, gradientColors: [CGColor] = [pink.cgColor, purple.cgColor, purple.cgColor]) {
self.strings = strings
self.count = count
self.showsSubtitle = showsSubtitle
self.fontSize = fontSize
@ -22,6 +24,9 @@ final class ParticipantsComponent: Component {
}
static func == (lhs: ParticipantsComponent, rhs: ParticipantsComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.count != rhs.count {
return false
}
@ -41,8 +46,7 @@ final class ParticipantsComponent: Component {
func update(view: View, availableSize: CGSize, state: ComponentFlow.EmptyComponentState, environment: ComponentFlow.Environment<ComponentFlow.Empty>, transition: ComponentFlow.Transition) -> CGSize {
view.counter.update(
countString: self.count > 0 ? presentationStringsFormattedNumber(Int32(count), ",") : "",
// TODO: localize
subtitle: self.showsSubtitle ? (self.count > 0 ? /*environment.strings.LiveStream_Watching*/"watching" : /*environment.strings.LiveStream_NoViewers.lowercased()*/"no viewers") : "",
subtitle: self.showsSubtitle ? (self.count > 0 ? self.strings.LiveStream_Watching.lowercased() : self.strings.LiveStream_NoViewers.lowercased()) : "",
fontSize: self.fontSize,
gradientColors: self.gradientColors
)

View File

@ -6,8 +6,10 @@ import AccountContext
import AVKit
import MultilineTextComponent
import Display
import TelegramPresentationData
final class StreamSheetComponent: CombinedComponent {
let strings: PresentationStrings
let sheetHeight: CGFloat
let topOffset: CGFloat
let backgroundColor: UIColor
@ -22,6 +24,7 @@ final class StreamSheetComponent: CombinedComponent {
let fullscreenBottomComponent: AnyComponent<Empty>
init(
strings: PresentationStrings,
topOffset: CGFloat,
sheetHeight: CGFloat,
backgroundColor: UIColor,
@ -34,6 +37,7 @@ final class StreamSheetComponent: CombinedComponent {
fullscreenTopComponent: AnyComponent<Empty>,
fullscreenBottomComponent: AnyComponent<Empty>
) {
self.strings = strings
self.topOffset = topOffset
self.sheetHeight = sheetHeight
self.backgroundColor = backgroundColor
@ -49,6 +53,9 @@ final class StreamSheetComponent: CombinedComponent {
}
static func ==(lhs: StreamSheetComponent, rhs: StreamSheetComponent) -> Bool {
if lhs.strings !== rhs.strings {
return false
}
if lhs.topOffset != rhs.topOffset {
return false
}
@ -159,7 +166,7 @@ final class StreamSheetComponent: CombinedComponent {
)
let viewerCounter = viewerCounter.update(
component: ParticipantsComponent(count: context.component.participantsCount, fontSize: 44.0),
component: ParticipantsComponent(strings: context.component.strings, count: context.component.participantsCount, fontSize: 44.0),
availableSize: CGSize(width: context.availableSize.width, height: 70),
transition: context.transition
)

View File

@ -343,10 +343,10 @@ public extension TelegramEngine.EngineData.Item {
return nil
}
switch cachedPeerData {
case let channel as CachedChannelData:
return channel.wallpaper
case let user as CachedUserData:
return user.wallpaper
case let channel as CachedChannelData:
return channel.wallpaper
default:
return nil
}

View File

@ -49,6 +49,15 @@ public extension TelegramWallpaper {
}
}
var isEmoticon: Bool {
switch self {
case .emoticon:
return true
default:
return false
}
}
var dimensions: CGSize? {
if case let .file(file) = self {
return file.file.dimensions?.cgSize

View File

@ -875,26 +875,7 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
animation.animator.updateFrame(layer: self.transformContainer.layer, frame: CGRect(origin: CGPoint(), size: actualSize), completion: nil)
let backgroundFrame = CGRect(origin: CGPoint(x: backgroundInsets.left, y: backgroundInsets.top), size: CGSize(width: actualSize.width - backgroundInsets.left - backgroundInsets.right, height: actualSize.height - backgroundInsets.top - backgroundInsets.bottom))
if displayLine {
let backgroundView: MessageInlineBlockBackgroundView
if let current = self.backgroundView {
backgroundView = current
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: nil, animation: animation)
} else {
backgroundView = MessageInlineBlockBackgroundView()
self.backgroundView = backgroundView
backgroundView.frame = backgroundFrame
self.transformContainer.view.insertSubview(backgroundView, at: 0)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: nil, animation: .None)
}
} else {
if let backgroundView = self.backgroundView {
self.backgroundView = nil
backgroundView.removeFromSuperview()
}
}
var patternTopRightPosition = CGPoint()
if let (inlineMediaValue, inlineMediaSize) = inlineMediaAndSize {
var inlineMediaFrame = CGRect(origin: CGPoint(x: actualSize.width - insets.right - inlineMediaSize.width, y: backgroundInsets.top + inlineMediaEdgeInset), size: inlineMediaSize)
@ -902,6 +883,8 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
inlineMediaFrame.origin.x = insets.left
}
patternTopRightPosition.x = insets.right + inlineMediaSize.width - 6.0
let inlineMedia: TransformImageNode
var updateMedia = false
if let current = self.inlineMedia {
@ -1134,6 +1117,21 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
if let item = contentDisplayOrder.first(where: { $0.item == .media }), let (contentMediaSize, contentMediaApply) = contentMediaSizeAndApply {
let contentMediaFrame = CGRect(origin: CGPoint(x: insets.left, y: item.offsetY), size: contentMediaSize)
var offsetPatternForMedia = false
if let index = contentLayoutOrder.firstIndex(where: { $0 == .media }), index != contentLayoutOrder.count - 1 {
for i in (index + 1) ..< contentLayoutOrder.count {
switch contentLayoutOrder[i] {
case .title, .subtitle, .text:
offsetPatternForMedia = true
default:
break
}
}
}
if offsetPatternForMedia {
patternTopRightPosition.y = contentMediaFrame.maxY + 6.0
}
let contentMedia = contentMediaApply(animation, synchronousLoads)
if self.contentMedia !== contentMedia {
self.contentMedia?.removeFromSupernode()
@ -1303,6 +1301,38 @@ public final class ChatMessageAttachedContentNode: ASDisplayNode {
self.view.removeGestureRecognizer(tapRecognizer)
}
}
if displayLine {
var pattern: MessageInlineBlockBackgroundView.Pattern?
if let backgroundEmojiId = author?.backgroundEmojiId {
pattern = MessageInlineBlockBackgroundView.Pattern(
context: context,
fileId: backgroundEmojiId,
file: message.associatedMedia[MediaId(
namespace: Namespaces.Media.CloudFile,
id: backgroundEmojiId
)] as? TelegramMediaFile
)
}
let backgroundView: MessageInlineBlockBackgroundView
if let current = self.backgroundView {
backgroundView = current
animation.animator.updateFrame(layer: backgroundView.layer, frame: backgroundFrame, completion: nil)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: animation)
} else {
backgroundView = MessageInlineBlockBackgroundView()
self.backgroundView = backgroundView
backgroundView.frame = backgroundFrame
self.transformContainer.view.insertSubview(backgroundView, at: 0)
backgroundView.update(size: backgroundFrame.size, isTransparent: false, primaryColor: mainColor, secondaryColor: secondaryColor, thirdColor: tertiaryColor, backgroundColor: nil, pattern: pattern, patternTopRightPosition: patternTopRightPosition, animation: .None)
}
} else {
if let backgroundView = self.backgroundView {
self.backgroundView = nil
backgroundView.removeFromSuperview()
}
}
})
})
})

View File

@ -5464,4 +5464,13 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
return (image, self.backgroundNode.frame)
}
public func isServiceLikeMessage() -> Bool {
for contentNode in self.contentNodes {
if contentNode is ChatMessageActionBubbleContentNode {
return true
}
}
return false
}
}

View File

@ -791,7 +791,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
}
case .themeSettings, .image:
unboundSize = CGSize(width: 160.0, height: 240.0).fitted(CGSize(width: 240.0, height: 240.0))
case .color, .gradient:
case .color, .gradient, .emoticon:
unboundSize = CGSize(width: 128.0, height: 128.0)
}
} else if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
@ -1339,6 +1339,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
return solidColorImage(color)
case let .gradient(colors, rotation):
return gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0)
case .emoticon:
return solidColorImage(.black)
}
}
@ -1404,7 +1406,7 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr
|> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil)
}
case .themeSettings, .color, .gradient, .image:
case .themeSettings, .color, .gradient, .image, .emoticon:
updatedStatusSignal = .single((.Local, nil))
}
}

View File

@ -332,7 +332,13 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
var imageSize = boundingSize
let updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>
var patternArguments: PatternWallpaperArguments?
switch media.content {
var mediaContent = media.content
if case let .emoticon(emoticon) = mediaContent, let theme = item.associatedData.chatThemes.first(where: { $0.emoticon?.strippedEmoji == emoticon.strippedEmoji }), let themeWallpaper = theme.settings?.first?.wallpaper, let themeWallpaperContent = WallpaperPreviewMedia(wallpaper: themeWallpaper)?.content {
mediaContent = themeWallpaperContent
}
switch mediaContent {
case let .file(file, patternColors, rotation, intensity, _, _):
var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference($0.resource)) })
if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" {
@ -386,6 +392,8 @@ public class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode
updateImageSignal = gradientImage(colors.map(UIColor.init(rgb:)), rotation: rotation ?? 0)
case .themeSettings:
updateImageSignal = .complete()
case .emoticon:
updateImageSignal = .complete()
}
strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads)

View File

@ -459,6 +459,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
var thirdColor: UIColor?
var backgroundColor: UIColor?
var pattern: Pattern?
var patternTopRightPosition: CGPoint?
var displayProgress: Bool
init(
@ -469,6 +470,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
thirdColor: UIColor?,
backgroundColor: UIColor?,
pattern: Pattern?,
patternTopRightPosition: CGPoint?,
displayProgress: Bool
) {
self.size = size
@ -478,6 +480,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
self.thirdColor = thirdColor
self.backgroundColor = backgroundColor
self.pattern = pattern
self.patternTopRightPosition = patternTopRightPosition
self.displayProgress = displayProgress
}
}
@ -608,6 +611,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
thirdColor: UIColor?,
backgroundColor: UIColor?,
pattern: Pattern?,
patternTopRightPosition: CGPoint? = nil,
animation: ListViewItemUpdateAnimation
) {
let params = Params(
@ -618,6 +622,7 @@ public final class MessageInlineBlockBackgroundView: UIView {
thirdColor: thirdColor,
backgroundColor: backgroundColor,
pattern: pattern,
patternTopRightPosition: patternTopRightPosition,
displayProgress: self.displayProgress
)
if self.params == params {
@ -751,8 +756,14 @@ public final class MessageInlineBlockBackgroundView: UIView {
}
patternContentLayer.contents = self.patternContentsTarget?.contents
var patternOrigin = CGPoint(x: size.width, y: 0.0)
if let patternTopRightPosition {
patternOrigin.x -= patternTopRightPosition.x
patternOrigin.y += patternTopRightPosition.y
}
let itemSize = CGSize(width: placement.size / 3.0, height: placement.size / 3.0)
patternContentLayer.frame = CGRect(origin: CGPoint(x: size.width - placement.position.x / 3.0 - itemSize.width * 0.5, y: placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize)
patternContentLayer.frame = CGRect(origin: CGPoint(x: patternOrigin.x - placement.position.x / 3.0 - itemSize.width * 0.5, y: patternOrigin.y + placement.position.y / 3.0 - itemSize.height * 0.5), size: itemSize)
var alphaFraction = abs(placement.position.x / 3.0) / min(500.0, size.width)
alphaFraction = min(1.0, max(0.0, alphaFraction))
patternContentLayer.opacity = 0.3 * Float(1.0 - alphaFraction)

View File

@ -93,13 +93,9 @@ kernel void dustEffectUpdateParticle(
float particleXFraction = float(particleX) / float(size.x);
float particleFraction = particleEaseInValueAt(effectFraction, particleXFraction);
//Loki rng = Loki(gid, uint(phase * timeStep));
//float2 offsetNorm = float2(1.0, 1.0) * 10.0 * timeStep;
Particle particle = particles[gid];
particle.offsetFromBasePosition += (particle.velocity * timeStep) * particleFraction;
//particle.velocity += ((-offsetNorm) * 0.5 + float2(rng.rand(), rng.rand()) * offsetNorm) * particleFraction;
//particle.velocity = particle.velocity * (1.0 - particleFraction) + particle.velocity * 1.001 * particleFraction;
particle.velocity += float2(0.0, timeStep * 120.0) * particleFraction;
particle.lifetime = max(0.0, particle.lifetime - timeStep * particleFraction);
particles[gid] = particle;

View File

@ -162,7 +162,6 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
}
self.lastTimeStep = deltaTimeValue
//print("updateItems: \(deltaTime), localDeltaTime: \(localDeltaTime)")
var didRemoveItems = false
for i in (0 ..< self.items.count).reversed() {
@ -224,19 +223,6 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
if item.particleBuffer == nil {
if let particleBuffer = MetalEngine.shared.sharedBuffer(spec: BufferSpec(length: particleCount * 4 * (4 + 1))) {
item.particleBuffer = particleBuffer
/*let particles = particleBuffer.buffer.contents().assumingMemoryBound(to: Float.self)
for i in 0 ..< particleCount {
particles[i * 5 + 0] = 0.0;
particles[i * 5 + 1] = 0.0;
let direction = Float.random(in: 0.0 ..< Float.pi * 2.0)
let velocity = Float.random(in: 0.1 ... 0.2) * 420.0
particles[i * 5 + 2] = cos(direction) * velocity
particles[i * 5 + 3] = sin(direction) * velocity
particles[i * 5 + 4] = Float.random(in: 0.7 ... 1.5)
}*/
}
}
}
@ -279,6 +265,7 @@ public final class DustEffectLayer: MetalEngineSubjectLayer, MetalEngineSubject
var phase = item.phase
computeEncoder.setBytes(&phase, length: 4, index: 2)
var timeStep: Float = Float(lastTimeStep) / Float(UIView.animationDurationFactor())
timeStep *= 2.0
computeEncoder.setBytes(&timeStep, length: 4, index: 3)
computeEncoder.dispatchThreadgroups(threadgroupCount, threadsPerThreadgroup: threadgroupSize)
}

View File

@ -104,6 +104,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
private var key: PeerInfoHeaderNavigationButtonKey?
private var contentsColor: UIColor = .white
private var canBeExpanded: Bool = false
var action: ((ASDisplayNode, ContextGesture?) -> Void)?
@ -172,6 +173,7 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode {
func updateContentsColor(backgroundColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) {
self.contentsColor = contentsColor
self.canBeExpanded = canBeExpanded
self.backgroundNode.updateColor(color: backgroundColor, transition: transition)

View File

@ -43,6 +43,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
func updateContentsColor(backgroundContentColor: UIColor, contentsColor: UIColor, canBeExpanded: Bool, transition: ContainedViewLayoutTransition) {
self.backgroundContentColor = backgroundContentColor
self.contentsColor = contentsColor
self.canBeExpanded = canBeExpanded
for (_, button) in self.leftButtonNodes {
button.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: canBeExpanded, transition: transition)
@ -110,6 +111,8 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
buttonNode.alpha = 0.0
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
buttonNode.updateContentsColor(backgroundColor: self.backgroundContentColor, contentsColor: self.contentsColor, canBeExpanded: self.canBeExpanded, transition: .immediate)
transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? -8.0 : 0.0, y: 0.0))
} else {
transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
@ -214,6 +217,8 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode {
buttonNode.frame = buttonFrame
buttonNode.alpha = 0.0
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)
transition.updateSublayerTransformOffset(layer: buttonNode.layer, offset: CGPoint(x: canBeExpanded ? 8.0 : 0.0, y: 0.0))
} else {
transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame)
transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor)

View File

@ -156,13 +156,13 @@ final class ChannelAppearanceScreenComponent: Component {
private final class ContentsData {
let peer: EnginePeer?
let wallpaper: TelegramWallpaper?
let peerWallpaper: TelegramWallpaper?
let subscriberCount: Int?
let availableThemes: [TelegramTheme]
init(peer: EnginePeer?, wallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) {
init(peer: EnginePeer?, peerWallpaper: TelegramWallpaper?, subscriberCount: Int?, availableThemes: [TelegramTheme]) {
self.peer = peer
self.wallpaper = wallpaper
self.peerWallpaper = peerWallpaper
self.subscriberCount = subscriberCount
self.availableThemes = availableThemes
}
@ -180,7 +180,7 @@ final class ChannelAppearanceScreenComponent: Component {
let (peer, subscriberCount, wallpaper) = peerData
return ContentsData(
peer: peer,
wallpaper: wallpaper,
peerWallpaper: wallpaper,
subscriberCount: subscriberCount,
availableThemes: cloudThemes
)
@ -195,6 +195,21 @@ final class ChannelAppearanceScreenComponent: Component {
}
private struct ResolvedState {
struct Changes: OptionSet {
var rawValue: Int32
init(rawValue: Int32) {
self.rawValue = rawValue
}
static let nameColor = Changes(rawValue: 1 << 0)
static let profileColor = Changes(rawValue: 1 << 1)
static let replyFileId = Changes(rawValue: 1 << 2)
static let backgroundFileId = Changes(rawValue: 1 << 3)
static let emojiStatus = Changes(rawValue: 1 << 4)
static let wallpaper = Changes(rawValue: 1 << 5)
}
var nameColor: PeerNameColor
var profileColor: PeerNameColor?
var replyFileId: Int64?
@ -202,16 +217,16 @@ final class ChannelAppearanceScreenComponent: Component {
var emojiStatus: PeerEmojiStatus?
var wallpaper: TelegramWallpaper?
var hasChanges: Bool
var changes: Changes
init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, hasChanges: Bool) {
init(nameColor: PeerNameColor, profileColor: PeerNameColor?, replyFileId: Int64?, backgroundFileId: Int64?, emojiStatus: PeerEmojiStatus?, wallpaper: TelegramWallpaper?, changes: Changes) {
self.nameColor = nameColor
self.profileColor = profileColor
self.replyFileId = replyFileId
self.backgroundFileId = backgroundFileId
self.emojiStatus = emojiStatus
self.wallpaper = wallpaper
self.hasChanges = hasChanges
self.changes = changes
}
}
@ -334,7 +349,7 @@ final class ChannelAppearanceScreenComponent: Component {
return nil
}
var hasChanges = false
var changes: ResolvedState.Changes = []
let nameColor: PeerNameColor
if let updatedPeerNameColor = self.updatedPeerNameColor {
@ -345,7 +360,7 @@ final class ChannelAppearanceScreenComponent: Component {
nameColor = .blue
}
if nameColor != peer.nameColor {
hasChanges = true
changes.insert(.nameColor)
}
let profileColor: PeerNameColor?
@ -357,7 +372,7 @@ final class ChannelAppearanceScreenComponent: Component {
profileColor = nil
}
if profileColor != peer.profileColor {
hasChanges = true
changes.insert(.profileColor)
}
let replyFileId: Int64?
@ -367,7 +382,7 @@ final class ChannelAppearanceScreenComponent: Component {
replyFileId = peer.backgroundEmojiId
}
if replyFileId != peer.backgroundEmojiId {
hasChanges = true
changes.insert(.replyFileId)
}
let backgroundFileId: Int64?
@ -377,7 +392,7 @@ final class ChannelAppearanceScreenComponent: Component {
backgroundFileId = peer.profileBackgroundEmojiId
}
if backgroundFileId != peer.profileBackgroundEmojiId {
hasChanges = true
changes.insert(.backgroundFileId)
}
let emojiStatus: PeerEmojiStatus?
@ -387,7 +402,7 @@ final class ChannelAppearanceScreenComponent: Component {
emojiStatus = peer.emojiStatus
}
if emojiStatus != peer.emojiStatus {
hasChanges = true
changes.insert(.emojiStatus)
}
let wallpaper: TelegramWallpaper?
@ -396,13 +411,13 @@ final class ChannelAppearanceScreenComponent: Component {
case .remove:
wallpaper = nil
case let .emoticon(emoticon):
wallpaper = contentsData.availableThemes.first(where: { $0.emoticon == emoticon })?.settings?.first?.wallpaper
wallpaper = .emoticon(emoticon)
case .custom:
wallpaper = self.temporaryPeerWallpaper
}
hasChanges = true
changes.insert(.wallpaper)
} else {
wallpaper = contentsData.wallpaper
wallpaper = contentsData.peerWallpaper
}
return ResolvedState(
@ -412,7 +427,7 @@ final class ChannelAppearanceScreenComponent: Component {
backgroundFileId: backgroundFileId,
emojiStatus: emojiStatus,
wallpaper: wallpaper,
hasChanges: hasChanges
changes: changes
)
}
@ -430,7 +445,7 @@ final class ChannelAppearanceScreenComponent: Component {
return
}
if !resolvedState.hasChanges {
if resolvedState.changes.isEmpty {
self.environment?.controller()?.dismiss()
return
}
@ -446,28 +461,43 @@ final class ChannelAppearanceScreenComponent: Component {
case generic
}
if let updatedPeerWallpaper {
switch updatedPeerWallpaper {
case .remove:
let _ = component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: nil, forBoth: false).start()
case let .emoticon(emoticon):
let _ = component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: .emoticon(emoticon), forBoth: false).start()
case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness):
uploadCustomPeerWallpaper(context: component.context, wallpaper: wallpaperEntry, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: component.peerId, forBoth: false, completion: {})
}
}
self.applyDisposable = (combineLatest([
component.context.engine.peers.updatePeerNameColorAndEmoji(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId)
var signals: [Signal<Never, ApplyError>] = []
if !resolvedState.changes.intersection([.nameColor, .profileColor, .replyFileId, .backgroundFileId]).isEmpty {
signals.append(component.context.engine.peers.updatePeerNameColorAndEmoji(peerId: component.peerId, nameColor: resolvedState.nameColor, backgroundEmojiId: resolvedState.replyFileId, profileColor: resolvedState.profileColor, profileBackgroundEmojiId: resolvedState.backgroundFileId)
|> ignoreValues
|> mapError { _ -> ApplyError in
return .generic
},
component.context.engine.peers.updatePeerEmojiStatus(peerId: component.peerId, fileId: statusFileId, expirationDate: nil)
})
}
if resolvedState.changes.contains(.emojiStatus) {
signals.append(component.context.engine.peers.updatePeerEmojiStatus(peerId: component.peerId, fileId: statusFileId, expirationDate: nil)
|> ignoreValues
|> mapError { _ -> ApplyError in
return .generic
})
}
if resolvedState.changes.contains(.wallpaper) {
if let updatedPeerWallpaper {
switch updatedPeerWallpaper {
case .remove:
signals.append(component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: nil, forBoth: false)
|> ignoreValues
|> mapError { _ -> ApplyError in
return .generic
})
case let .emoticon(emoticon):
signals.append(component.context.engine.themes.setChatWallpaper(peerId: component.peerId, wallpaper: .emoticon(emoticon), forBoth: false)
|> ignoreValues
|> mapError { _ -> ApplyError in
return .generic
})
case let .custom(wallpaperEntry, options, editedImage, cropRect, brightness):
uploadCustomPeerWallpaper(context: component.context, wallpaper: wallpaperEntry, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: component.peerId, forBoth: false, completion: {})
}
}
])
}
self.applyDisposable = (combineLatest(signals)
|> deliverOnMainQueue).start(error: { [weak self] _ in
guard let self, let component = self.component else {
return
@ -550,7 +580,7 @@ final class ChannelAppearanceScreenComponent: Component {
self.updatedPeerWallpaper = result
switch result {
case let .emoticon(emoticon):
if let selectedTheme = contentsData.availableThemes.first(where: { $0.emoticon == emoticon }) {
if let selectedTheme = contentsData.availableThemes.first(where: { $0.emoticon?.strippedEmoji == emoticon.strippedEmoji }) {
self.currentTheme = .cloud(PresentationCloudTheme(theme: selectedTheme, resolvedWallpaper: nil, creatorAccountId: nil))
}
self.temporaryPeerWallpaper = nil
@ -700,13 +730,22 @@ final class ChannelAppearanceScreenComponent: Component {
if self.contentsDataDisposable == nil {
self.contentsDataDisposable = (ContentsData.get(context: component.context, peerId: component.peerId)
|> deliverOnMainQueue).start(next: { [weak self] contentsData in
guard let self else {
guard let self, let component = self.component else {
return
}
if self.contentsData == nil, case let .channel(channel) = contentsData.peer {
self.boostLevel = channel.approximateBoostLevel.flatMap(Int.init)
}
if self.contentsData == nil, let peerWallpaper = contentsData.peerWallpaper {
for cloudTheme in contentsData.availableThemes {
if case let .emoticon(emoticon) = peerWallpaper, cloudTheme.emoticon?.strippedEmoji == emoticon.strippedEmoji {
self.currentTheme = .cloud(PresentationCloudTheme(theme: cloudTheme, resolvedWallpaper: nil, creatorAccountId: cloudTheme.isCreator ? component.context.account.id : nil))
break
}
}
}
self.contentsData = contentsData
if !self.isUpdating {
self.state?.updated(transition: .immediate)
}
@ -733,20 +772,11 @@ final class ChannelAppearanceScreenComponent: Component {
var requiredBoostSubjects: [BoostSubject] = [.nameColors(colors: resolvedState.nameColor)]
let replyIconLevel = 5
var profileIconLevel = 7
var emojiStatusLevel = 8
let themeLevel = 9
let replyIconLevel = Int(BoostSubject.nameIcon.requiredLevel(context: component.context, configuration: premiumConfiguration))
let profileIconLevel = Int(BoostSubject.profileIcon.requiredLevel(context: component.context, configuration: premiumConfiguration))
let emojiStatusLevel = Int(BoostSubject.emojiStatus.requiredLevel(context: component.context, configuration: premiumConfiguration))
let themeLevel = Int(BoostSubject.wallpaper.requiredLevel(context: component.context, configuration: premiumConfiguration))
if let data = component.context.currentAppConfiguration.with({ $0 }).data {
if let value = data["channel_profile_color_level_min"] as? Double {
profileIconLevel = Int(value)
}
if let value = data["channel_emoji_status_level_min"] as? Double {
emojiStatusLevel = Int(value)
}
}
let replyFileId = resolvedState.replyFileId
if replyFileId != nil {
requiredBoostSubjects.append(.nameIcon)
@ -810,13 +840,13 @@ final class ChannelAppearanceScreenComponent: Component {
}))
}
} else if self.currentTheme == nil {
self.resolvedCurrentTheme = nil
self.resolvingCurrentTheme?.disposable.dispose()
self.resolvingCurrentTheme = nil
self.resolvedCurrentTheme = nil
}
if let wallpaper = resolvedState.wallpaper {
if wallpaper.isPattern {
if wallpaper.isEmoticon {
requiredBoostSubjects.append(.wallpaper)
} else {
requiredBoostSubjects.append(.customWallpaper)
@ -901,6 +931,8 @@ final class ChannelAppearanceScreenComponent: Component {
if let wallpaper = resolvedCurrentTheme.wallpaper {
chatPreviewWallpaper = wallpaper
}
} else if let initialWallpaper = contentsData.peerWallpaper, !initialWallpaper.isEmoticon {
chatPreviewWallpaper = initialWallpaper
}
let replySectionSize = self.replySection.update(
@ -1002,7 +1034,7 @@ final class ChannelAppearanceScreenComponent: Component {
var currentTheme = self.currentTheme
var selectedWallpaper: TelegramWallpaper?
if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isPattern {
if currentTheme == nil, let wallpaper = resolvedState.wallpaper, !wallpaper.isEmoticon {
let theme: PresentationThemeReference = .builtin(.day)
currentTheme = theme
selectedWallpaper = wallpaper

View File

@ -306,7 +306,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
self.gridNode.addSubnode(self.colorItemNode)
}
self.gridNode.addSubnode(self.galleryItemNode)
if case let .peer(_, _, wallpaper, _, _) = mode, let wallpaper, !wallpaper.isPattern {
if case let .peer(_, _, wallpaper, _, _) = mode, let wallpaper, !wallpaper.isEmoticon {
self.gridNode.addSubnode(self.removeItemNode)
}
self.gridNode.addSubnode(self.descriptionItemNode)
@ -404,7 +404,7 @@ final class ThemeGridControllerNode: ASDisplayNode {
selectedWallpaper = wallpaper
}
if let selectedWallpaper, !selectedWallpaper.isPattern {
if let selectedWallpaper, !selectedWallpaper.isEmoticon {
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: selectedWallpaper, channelMode: true, isEditable: false, isSelected: true))
} else {
let emojiFile = context.animatedEmojiStickers[""]?.first?.file
@ -414,19 +414,24 @@ final class ThemeGridControllerNode: ASDisplayNode {
index += 1
for theme in themes {
guard let wallpaper = theme.settings?.first?.wallpaper, let emoticon = theme.emoticon else {
guard let wallpaper = theme.settings?.first?.wallpaper, let themeEmoticon = theme.emoticon else {
continue
}
var updatedWallpaper = wallpaper
if let settings = wallpaper.settings {
var updatedSettings = settings
updatedSettings.emoticon = emoticon
updatedSettings.emoticon = themeEmoticon
updatedWallpaper = wallpaper.withUpdatedSettings(updatedSettings)
}
var isSelected = false
if let selectedWallpaper, case let .emoticon(emoticon) = selectedWallpaper, emoticon.strippedEmoji == themeEmoticon.strippedEmoji {
isSelected = true
}
let emoji = context.animatedEmojiStickers[emoticon]
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: selectedWallpaper?.isBasicallyEqual(to: wallpaper) ?? false))
let emoji = context.animatedEmojiStickers[themeEmoticon]
entries.append(ThemeGridControllerEntry(index: index, theme: presentationData.theme, wallpaper: updatedWallpaper, emoji: emoji?.first?.file, channelMode: true, isEditable: false, isSelected: isSelected))
index += 1
}
} else {

View File

@ -407,299 +407,6 @@ public final class StoryFooterPanelComponent: Component {
self.viewStatsButton.isEnabled = displayViewLists
var rightContentOffset: CGFloat = availableSize.width - 12.0
if component.isChannel {
var likeStatsTransition = transition
var forwardStatsTransition = transition
if transition.animation.isImmediate, !isFirstTime, let previousComponent, previousComponent.storyItem.id == component.storyItem.id, previousComponent.expandFraction == component.expandFraction {
likeStatsTransition = .easeInOut(duration: 0.2)
forwardStatsTransition = .easeInOut(duration: 0.2)
}
let likeStatsText: AnimatedCountLabelView
if let current = self.likeStatsText {
likeStatsText = current
} else {
likeStatsTransition = likeStatsTransition.withAnimation(.none)
likeStatsText = AnimatedCountLabelView(frame: CGRect())
likeStatsText.isUserInteractionEnabled = false
self.likeStatsText = likeStatsText
}
let reactionStatsLayout = likeStatsText.update(
size: CGSize(width: availableSize.width, height: size.height),
segments: [
.number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white))
],
transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
)
var likeStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - reactionStatsLayout.size.width, y: floor((size.height - reactionStatsLayout.size.height) * 0.5)), size: reactionStatsLayout.size)
likeStatsFrame.origin.y += component.expandFraction * 45.0
likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center)
likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size))
var likeStatsAlpha: CGFloat = (1.0 - component.expandFraction)
if reactionCount == 0 {
likeStatsAlpha = 0.0
}
likeStatsTransition.setAlpha(view: likeStatsText, alpha: likeStatsAlpha)
likeStatsTransition.setScale(view: likeStatsText, scale: reactionCount == 0 ? 0.001 : 1.0)
if reactionCount != 0 {
rightContentOffset -= reactionStatsLayout.size.width + 1.0
}
let likeButton: ComponentView<Empty>
if let current = self.likeButton {
likeButton = current
} else {
likeButton = ComponentView()
self.likeButton = likeButton
}
let likeButtonSize = likeButton.update(
transition: likeStatsTransition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId),
storyId: component.storyItem.id,
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.likeAction()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let likeButtonView = likeButton.view as? MessageInputActionButtonComponent.View {
if likeButtonView.superview == nil {
self.addSubview(likeButtonView)
}
var likeButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - likeButtonSize.height) * 0.5)), size: likeButtonSize)
likeButtonFrame.origin.y += component.expandFraction * 45.0
if let likeButtonTracingOffsetView = self.likeButtonTracingOffsetView {
let difference = CGPoint(x: likeButtonFrame.midX - likeButtonView.layer.position.x, y: likeButtonFrame.midY - likeButtonView.layer.position.y)
if difference != CGPoint() {
likeStatsTransition.setPosition(view: likeButtonTracingOffsetView, position: likeButtonTracingOffsetView.layer.position.offsetBy(dx: difference.x, dy: difference.y))
}
}
likeStatsTransition.setPosition(view: likeButtonView, position: likeButtonFrame.center)
likeStatsTransition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction)
rightContentOffset -= likeButtonSize.width + 14.0
if likeStatsText.superview == nil {
likeButtonView.button.view.addSubview(likeStatsText)
}
likeStatsFrame.origin.x -= likeButtonFrame.minX
likeStatsFrame.origin.y -= likeButtonFrame.minY
likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center)
likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size))
}
if component.canShare {
let forwardStatsText: AnimatedCountLabelView
if let current = self.forwardStatsText {
forwardStatsText = current
} else {
forwardStatsTransition = forwardStatsTransition.withAnimation(.none)
forwardStatsText = AnimatedCountLabelView(frame: CGRect())
forwardStatsText.isUserInteractionEnabled = false
self.forwardStatsText = forwardStatsText
}
let forwardStatsLayout = forwardStatsText.update(
size: CGSize(width: availableSize.width, height: size.height),
segments: [
.number(forwardCount, NSAttributedString(string: "\(forwardCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white))
],
transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
)
var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size)
forwardStatsFrame.origin.y += component.expandFraction * 45.0
var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction)
if forwardCount == 0 {
forwardStatsAlpha = 0.0
}
forwardStatsTransition.setAlpha(view: forwardStatsText, alpha: forwardStatsAlpha)
forwardStatsTransition.setScale(view: forwardStatsText, scale: forwardCount == 0 ? 0.001 : 1.0)
if forwardCount != 0 {
rightContentOffset -= forwardStatsLayout.size.width + 1.0
}
let repostButton: ComponentView<Empty>
if let current = self.repostButton {
repostButton = current
} else {
repostButton = ComponentView()
self.repostButton = repostButton
}
let forwardButton: ComponentView<Empty>
if let current = self.forwardButton {
forwardButton = current
} else {
forwardButton = ComponentView()
self.forwardButton = forwardButton
}
let repostButtonSize = repostButton.update(
transition: likeStatsTransition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .repost,
storyId: component.storyItem.id,
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.repostAction()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let repostButtonView = repostButton.view as? MessageInputActionButtonComponent.View {
if repostButtonView.superview == nil {
self.addSubview(repostButtonView)
}
var repostButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - repostButtonSize.width, y: floor((size.height - repostButtonSize.height) * 0.5)), size: repostButtonSize)
repostButtonFrame.origin.y += component.expandFraction * 45.0
forwardStatsTransition.setPosition(view: repostButtonView, position: repostButtonFrame.center)
forwardStatsTransition.setBounds(view: repostButtonView, bounds: CGRect(origin: CGPoint(), size: repostButtonFrame.size))
forwardStatsTransition.setAlpha(view: repostButtonView, alpha: 1.0 - component.expandFraction)
rightContentOffset -= repostButtonSize.width + 14.0
if forwardStatsText.superview == nil {
repostButtonView.button.view.addSubview(forwardStatsText)
}
forwardStatsFrame.origin.x -= repostButtonFrame.minX
forwardStatsFrame.origin.y -= repostButtonFrame.minY
forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center)
forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size))
}
let forwardButtonSize = forwardButton.update(
transition: likeStatsTransition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .forward,
storyId: component.storyItem.id,
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.forwardAction()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let forwardButtonView = forwardButton.view {
if forwardButtonView.superview == nil {
self.addSubview(forwardButtonView)
}
var forwardButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - forwardButtonSize.height) * 0.5)), size: forwardButtonSize)
forwardButtonFrame.origin.y += component.expandFraction * 45.0
likeStatsTransition.setPosition(view: forwardButtonView, position: forwardButtonFrame.center)
likeStatsTransition.setBounds(view: forwardButtonView, bounds: CGRect(origin: CGPoint(), size: forwardButtonFrame.size))
likeStatsTransition.setAlpha(view: forwardButtonView, alpha: 1.0 - component.expandFraction)
rightContentOffset -= forwardButtonSize.width + 8.0
}
} else {
if let repostButton = self.repostButton {
self.repostButton = nil
repostButton.view?.removeFromSuperview()
}
if let forwardButton = self.forwardButton {
self.forwardButton = nil
forwardButton.view?.removeFromSuperview()
}
}
} else {
if let likeButton = self.likeButton {
self.likeButton = nil
likeButton.view?.removeFromSuperview()
}
if let repostButton = self.repostButton {
self.repostButton = nil
repostButton.view?.removeFromSuperview()
}
if let forwardButton = self.forwardButton {
self.forwardButton = nil
forwardButton.view?.removeFromSuperview()
}
}
var regularSegments: [AnimatedCountLabelView.Segment] = []
if viewCount != 0 {
regularSegments.append(.number(viewCount, NSAttributedString(string: countString(Int64(viewCount)), font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white)))
@ -986,6 +693,300 @@ public final class StoryFooterPanelComponent: Component {
contentX += repostsTextSize.width
}
var rightContentOffset: CGFloat = availableSize.width - 12.0
if component.isChannel {
var likeStatsTransition = transition
var forwardStatsTransition = transition
if transition.animation.isImmediate, !isFirstTime, let previousComponent, previousComponent.storyItem.id == component.storyItem.id, previousComponent.expandFraction == component.expandFraction {
likeStatsTransition = .easeInOut(duration: 0.2)
forwardStatsTransition = .easeInOut(duration: 0.2)
}
let likeStatsText: AnimatedCountLabelView
if let current = self.likeStatsText {
likeStatsText = current
} else {
likeStatsTransition = likeStatsTransition.withAnimation(.none)
likeStatsText = AnimatedCountLabelView(frame: CGRect())
likeStatsText.isUserInteractionEnabled = false
self.likeStatsText = likeStatsText
}
let reactionStatsLayout = likeStatsText.update(
size: CGSize(width: availableSize.width, height: size.height),
segments: [
.number(reactionCount, NSAttributedString(string: "\(reactionCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white))
],
transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
)
var likeStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - reactionStatsLayout.size.width, y: floor((size.height - reactionStatsLayout.size.height) * 0.5)), size: reactionStatsLayout.size)
likeStatsFrame.origin.y += component.expandFraction * 45.0
//likeStatsFrame.origin.x = (1.0 - component.expandFraction) * likeStatsFrame.origin.x + component.expandFraction * (contentX)
likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center)
likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size))
var likeStatsAlpha: CGFloat = (1.0 - component.expandFraction)
if reactionCount == 0 {
likeStatsAlpha = 0.0
}
likeStatsTransition.setAlpha(view: likeStatsText, alpha: likeStatsAlpha)
likeStatsTransition.setScale(view: likeStatsText, scale: reactionCount == 0 ? 0.001 : 1.0)
if reactionCount != 0 {
rightContentOffset -= reactionStatsLayout.size.width + 1.0
}
let likeButton: ComponentView<Empty>
if let current = self.likeButton {
likeButton = current
} else {
likeButton = ComponentView()
self.likeButton = likeButton
}
let likeButtonSize = likeButton.update(
transition: likeStatsTransition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .like(reaction: component.myReaction?.reaction, file: component.myReaction?.file, animationFileId: component.myReaction?.animationFileId),
storyId: component.storyItem.id,
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.likeAction()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let likeButtonView = likeButton.view as? MessageInputActionButtonComponent.View {
if likeButtonView.superview == nil {
self.addSubview(likeButtonView)
}
var likeButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - likeButtonSize.height) * 0.5)), size: likeButtonSize)
likeButtonFrame.origin.y += component.expandFraction * 45.0
if let likeButtonTracingOffsetView = self.likeButtonTracingOffsetView {
let difference = CGPoint(x: likeButtonFrame.midX - likeButtonView.layer.position.x, y: likeButtonFrame.midY - likeButtonView.layer.position.y)
if difference != CGPoint() {
likeStatsTransition.setPosition(view: likeButtonTracingOffsetView, position: likeButtonTracingOffsetView.layer.position.offsetBy(dx: difference.x, dy: difference.y))
}
}
likeStatsTransition.setPosition(view: likeButtonView, position: likeButtonFrame.center)
likeStatsTransition.setBounds(view: likeButtonView, bounds: CGRect(origin: CGPoint(), size: likeButtonFrame.size))
likeStatsTransition.setAlpha(view: likeButtonView, alpha: 1.0 - component.expandFraction)
rightContentOffset -= likeButtonSize.width + 14.0
if likeStatsText.superview == nil {
likeButtonView.button.view.addSubview(likeStatsText)
}
likeStatsFrame.origin.x -= likeButtonFrame.minX
likeStatsFrame.origin.y -= likeButtonFrame.minY
likeStatsTransition.setPosition(view: likeStatsText, position: likeStatsFrame.center)
likeStatsTransition.setBounds(view: likeStatsText, bounds: CGRect(origin: CGPoint(), size: likeStatsFrame.size))
}
if component.canShare {
let forwardStatsText: AnimatedCountLabelView
if let current = self.forwardStatsText {
forwardStatsText = current
} else {
forwardStatsTransition = forwardStatsTransition.withAnimation(.none)
forwardStatsText = AnimatedCountLabelView(frame: CGRect())
forwardStatsText.isUserInteractionEnabled = false
self.forwardStatsText = forwardStatsText
}
let forwardStatsLayout = forwardStatsText.update(
size: CGSize(width: availableSize.width, height: size.height),
segments: [
.number(forwardCount, NSAttributedString(string: "\(forwardCount)", font: Font.with(size: 15.0, traits: .monospacedNumbers), textColor: .white))
],
transition: (isFirstTime || likeStatsTransition.animation.isImmediate) ? .immediate : ContainedViewLayoutTransition.animated(duration: 0.25, curve: .easeInOut)
)
var forwardStatsFrame = CGRect(origin: CGPoint(x: rightContentOffset - forwardStatsLayout.size.width, y: floor((size.height - forwardStatsLayout.size.height) * 0.5)), size: forwardStatsLayout.size)
forwardStatsFrame.origin.y += component.expandFraction * 45.0
var forwardStatsAlpha: CGFloat = (1.0 - component.expandFraction)
if forwardCount == 0 {
forwardStatsAlpha = 0.0
}
forwardStatsTransition.setAlpha(view: forwardStatsText, alpha: forwardStatsAlpha)
forwardStatsTransition.setScale(view: forwardStatsText, scale: forwardCount == 0 ? 0.001 : 1.0)
if forwardCount != 0 {
rightContentOffset -= forwardStatsLayout.size.width + 1.0
}
let repostButton: ComponentView<Empty>
if let current = self.repostButton {
repostButton = current
} else {
repostButton = ComponentView()
self.repostButton = repostButton
}
let forwardButton: ComponentView<Empty>
if let current = self.forwardButton {
forwardButton = current
} else {
forwardButton = ComponentView()
self.forwardButton = forwardButton
}
let repostButtonSize = repostButton.update(
transition: likeStatsTransition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .repost,
storyId: component.storyItem.id,
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.repostAction()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let repostButtonView = repostButton.view as? MessageInputActionButtonComponent.View {
if repostButtonView.superview == nil {
self.addSubview(repostButtonView)
}
var repostButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - repostButtonSize.width, y: floor((size.height - repostButtonSize.height) * 0.5)), size: repostButtonSize)
repostButtonFrame.origin.y += component.expandFraction * 45.0
forwardStatsTransition.setPosition(view: repostButtonView, position: repostButtonFrame.center)
forwardStatsTransition.setBounds(view: repostButtonView, bounds: CGRect(origin: CGPoint(), size: repostButtonFrame.size))
forwardStatsTransition.setAlpha(view: repostButtonView, alpha: 1.0 - component.expandFraction)
rightContentOffset -= repostButtonSize.width + 14.0
if forwardStatsText.superview == nil {
repostButtonView.button.view.addSubview(forwardStatsText)
}
forwardStatsFrame.origin.x -= repostButtonFrame.minX
forwardStatsFrame.origin.y -= repostButtonFrame.minY
forwardStatsTransition.setPosition(view: forwardStatsText, position: forwardStatsFrame.center)
forwardStatsTransition.setBounds(view: forwardStatsText, bounds: CGRect(origin: CGPoint(), size: forwardStatsFrame.size))
}
let forwardButtonSize = forwardButton.update(
transition: likeStatsTransition,
component: AnyComponent(MessageInputActionButtonComponent(
mode: .forward,
storyId: component.storyItem.id,
action: { [weak self] _, action, _ in
guard let self, let component = self.component else {
return
}
guard case .up = action else {
return
}
component.forwardAction()
},
longPressAction: nil,
switchMediaInputMode: {
},
updateMediaCancelFraction: { _ in
},
lockMediaRecording: {
},
stopAndPreviewMediaRecording: {
},
moreAction: { _, _ in },
context: component.context,
theme: component.theme,
strings: component.strings,
presentController: { _ in },
audioRecorder: nil,
videoRecordingStatus: nil
)),
environment: {},
containerSize: CGSize(width: 33.0, height: 33.0)
)
if let forwardButtonView = forwardButton.view {
if forwardButtonView.superview == nil {
self.addSubview(forwardButtonView)
}
var forwardButtonFrame = CGRect(origin: CGPoint(x: rightContentOffset - likeButtonSize.width, y: floor((size.height - forwardButtonSize.height) * 0.5)), size: forwardButtonSize)
forwardButtonFrame.origin.y += component.expandFraction * 45.0
likeStatsTransition.setPosition(view: forwardButtonView, position: forwardButtonFrame.center)
likeStatsTransition.setBounds(view: forwardButtonView, bounds: CGRect(origin: CGPoint(), size: forwardButtonFrame.size))
likeStatsTransition.setAlpha(view: forwardButtonView, alpha: 1.0 - component.expandFraction)
rightContentOffset -= forwardButtonSize.width + 8.0
}
} else {
if let repostButton = self.repostButton {
self.repostButton = nil
repostButton.view?.removeFromSuperview()
}
if let forwardButton = self.forwardButton {
self.forwardButton = nil
forwardButton.view?.removeFromSuperview()
}
}
} else {
if let likeButton = self.likeButton {
self.likeButton = nil
likeButton.view?.removeFromSuperview()
}
if let repostButton = self.repostButton {
self.repostButton = nil
repostButton.view?.removeFromSuperview()
}
if let forwardButton = self.forwardButton {
self.forwardButton = nil
forwardButton.view?.removeFromSuperview()
}
}
let statsButtonWidth = availableSize.width - 80.0
transition.setFrame(view: self.viewStatsButton, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: statsButtonWidth, height: baseHeight)))

View File

@ -9,6 +9,7 @@ public enum WallpaperPreviewMediaContent: Equatable {
case color(UIColor)
case gradient([UInt32], Int32?)
case themeSettings(TelegramThemeSettings)
case emoticon(String)
}
public final class WallpaperPreviewMedia: Media {
@ -64,6 +65,8 @@ public extension WallpaperPreviewMedia {
self.init(content: .file(file: file.file, colors: file.settings.colors, rotation: file.settings.rotation, intensity: file.settings.intensity, false, false))
case let .image(representations, _):
self.init(content: .image(representations: representations))
case let .emoticon(emoticon):
self.init(content: .emoticon(emoticon))
default:
return nil
}

View File

@ -919,6 +919,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.presentThemeSelection()
return true
}
if peer is TelegramChannel {
return true
}
strongSelf.chatDisplayNode.dismissInput()
var options = WallpaperPresentationOptions()
var intensity: Int32?
@ -3128,9 +3131,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [weak self] controller, f in
if let strongSelf = self {
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds([id])
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: [id], type: .forLocalPeer).startStandalone()
}
f(.dismissWithoutContent)
@ -8744,15 +8744,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
if isAction && (actions.options == .deleteGlobally || actions.options == .deleteLocally) {
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: actions.options == .deleteLocally ? .forLocalPeer : .forEveryone).startStandalone()
completion(.dismissWithoutContent)
} else if (messages.first?.flags.isSending ?? false) {
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone, deleteAllInGroup: true).startStandalone()
completion(.dismissWithoutContent)
} else {
@ -17215,9 +17209,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = strongSelf.context.engine.messages.deleteAllMessagesWithAuthor(peerId: peerId, authorId: author.id, namespace: Namespaces.Message.Cloud).startStandalone()
let _ = strongSelf.context.engine.messages.clearAuthorHistory(peerId: peerId, memberId: author.id).startStandalone()
} else if actions.contains(0) {
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone()
}
if actions.contains(1) {
@ -17256,9 +17247,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone()
}
}))
@ -17283,7 +17271,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} else {
globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone
}
contextItems.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { [weak self] _, f in
contextItems.append(.action(ContextMenuActionItem(text: globalTitle, textColor: .destructive, icon: { _ in nil }, action: { [weak self] c, f in
if let strongSelf = self {
var giveaway: TelegramMediaGiveaway?
for messageId in messageIds {
@ -17296,9 +17284,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
let commit = {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone()
}
if let giveaway {
@ -17311,8 +17296,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
f(.default)
} else {
commit()
f(.dismissWithoutContent)
c.dismiss(completion: {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
commit()
})
})
}
}
})))
@ -17320,9 +17308,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: .forEveryone).startStandalone()
}
}))
@ -17346,29 +17331,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
c.dismiss(completion: { [weak strongSelf] in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
guard let strongSelf else {
return
}
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
})
c.dismiss(completion: { [weak strongSelf] in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.1, execute: {
guard let strongSelf else {
return
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
})
} else {
f(.dismissWithoutContent)
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
}
})
}
})))
items.append(ActionSheetButtonItem(title: localOptionText, color: .destructive, action: { [weak self, weak actionSheet] in
actionSheet?.dismissAnimated()
if let strongSelf = self {
strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { $0.updatedInterfaceState { $0.withoutSelectionState() } })
if strongSelf.context.sharedContext.immediateExperimentalUISettings.dustEffect {
strongSelf.chatDisplayNode.historyNode.setCurrentDeleteAnimationCorrelationIds(messageIds)
}
let _ = strongSelf.context.engine.messages.deleteMessagesInteractively(messageIds: Array(messageIds), type: unsendPersonalMessages ? .forEveryone : .forLocalPeer).startStandalone()
}

View File

@ -29,6 +29,7 @@ import ChatBotInfoItem
import ChatMessageItem
import ChatMessageItemImpl
import ChatMessageItemView
import ChatMessageBubbleItemNode
import ChatMessageTransitionNode
import ChatControllerInteraction
import DustEffect
@ -329,7 +330,8 @@ private func extractAssociatedData(
translateToLanguage: String?,
maxReadStoryId: Int32?,
recommendedChannels: RecommendedChannels?,
audioTranscriptionTrial: AudioTranscription.TrialState
audioTranscriptionTrial: AudioTranscription.TrialState,
chatThemes: [TelegramTheme]
) -> ChatMessageItemAssociatedData {
var automaticDownloadPeerId: EnginePeer.Id?
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
@ -384,7 +386,7 @@ private func extractAssociatedData(
automaticDownloadPeerId = message.messageId.peerId
}
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial)
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes)
}
private extension ChatHistoryLocationInput {
@ -677,6 +679,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
private var toLang: String?
private var allowDustEffect: Bool = true
private var dustEffectLayer: DustEffectLayer?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>), chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, tagMask: MessageTags?, source: ChatHistoryListSource, subject: ChatControllerSubject?, controllerInteraction: ChatControllerInteraction, selectedMessages: Signal<Set<MessageId>?, NoError>, mode: ChatHistoryListMode = .bubbles, messageTransitionNode: @escaping () -> ChatMessageTransitionNodeImpl?) {
@ -694,8 +697,13 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.controllerInteraction = controllerInteraction
self.mode = mode
if let data = context.currentAppConfiguration.with({ $0 }).data, let _ = data["ios_killswitch_disable_unread_alignment"] {
self.enableUnreadAlignment = false
if let data = context.currentAppConfiguration.with({ $0 }).data {
if let _ = data["ios_killswitch_disable_unread_alignment"] {
self.enableUnreadAlignment = false
}
if let _ = data["ios_killswitch_disable_dust_effect"] {
self.allowDustEffect = false
}
}
let presentationData = updatedPresentationData.initial
@ -1367,6 +1375,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
let audioTranscriptionTrial = self.context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.AudioTranscriptionTrial())
let chatThemes = self.context.engine.themes.getChatThemes(accountManager: self.context.sharedContext.accountManager)
let messageViewQueue = Queue.mainQueue()
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
historyViewUpdate,
@ -1387,8 +1397,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
translationState,
maxReadStoryId,
recommendedChannels,
audioTranscriptionTrial
).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial in
audioTranscriptionTrial,
chatThemes
).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes in
let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises
func applyHole() {
@ -1544,7 +1555,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
translateToLanguage = languageCode
}
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial)
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, defaultReaction: defaultReaction, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes)
let filteredEntries = chatHistoryEntriesForView(
location: chatLocation,
@ -2961,22 +2972,16 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.hasActiveTransition = true
let transition = self.enqueuedHistoryViewTransitions.removeFirst()
var expiredMessageIds = Set<MessageId>()
var expiredMessageStableIds = Set<UInt32>()
if let previousHistoryView = self.historyView, transition.options.contains(.AnimateInsertion) {
let demoDustEffect = self.context.sharedContext.immediateExperimentalUISettings.dustEffect
var existingIds = Set<MessageId>()
var existingStableIds = Set<UInt32>()
for entry in transition.historyView.filteredEntries {
switch entry {
case let .MessageEntry(message, _, _, _, _, _):
if message.autoremoveAttribute != nil || demoDustEffect {
existingIds.insert(message.id)
}
existingStableIds.insert(message.stableId)
case let .MessageGroupEntry(_, messages, _):
for message in messages {
if message.0.autoremoveAttribute != nil || demoDustEffect {
existingIds.insert(message.0.id)
}
existingStableIds.insert(message.0.stableId)
}
default:
break
@ -2986,25 +2991,25 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
for entry in previousHistoryView.filteredEntries {
switch entry {
case let .MessageEntry(message, _, _, _, _, _):
if !existingIds.contains(message.id) {
if !existingStableIds.contains(message.stableId) {
if let autoremoveAttribute = message.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime {
let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout
if exipiresAt >= currentTimestamp - 1 {
expiredMessageIds.insert(message.id)
expiredMessageStableIds.insert(message.stableId)
}
} else if demoDustEffect {
expiredMessageIds.insert(message.id)
} else {
expiredMessageStableIds.insert(message.stableId)
}
}
case let .MessageGroupEntry(_, messages, _):
for message in messages {
if !existingIds.contains(message.0.id) {
if !existingStableIds.contains(message.0.stableId) {
if let autoremoveAttribute = message.0.autoremoveAttribute, let countdownBeginTime = autoremoveAttribute.countdownBeginTime {
let exipiresAt = countdownBeginTime + autoremoveAttribute.timeout
if exipiresAt >= currentTimestamp - 1 {
expiredMessageIds.insert(message.0.id)
} else if demoDustEffect {
expiredMessageIds.insert(message.0.id)
expiredMessageStableIds.insert(message.0.stableId)
} else {
expiredMessageStableIds.insert(message.0.stableId)
}
}
}
@ -3014,17 +3019,23 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
}
self.currentDeleteAnimationCorrelationIds.formUnion(expiredMessageIds)
self.currentDeleteAnimationCorrelationIds.formUnion(expiredMessageStableIds)
var appliedDeleteAnimationCorrelationIds = Set<MessageId>()
if !self.currentDeleteAnimationCorrelationIds.isEmpty {
var appliedDeleteAnimationCorrelationIds = Set<UInt32>()
if !self.currentDeleteAnimationCorrelationIds.isEmpty && self.allowDustEffect {
var foundItemNodes: [ChatMessageItemView] = []
self.forEachItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for (message, _) in item.content {
if self.currentDeleteAnimationCorrelationIds.contains(message.id) {
appliedDeleteAnimationCorrelationIds.insert(message.id)
self.currentDeleteAnimationCorrelationIds.remove(message.id)
if let itemNode = itemNode as? ChatMessageBubbleItemNode {
if itemNode.isServiceLikeMessage() {
continue
}
}
if self.currentDeleteAnimationCorrelationIds.contains(message.stableId) {
appliedDeleteAnimationCorrelationIds.insert(message.stableId)
self.currentDeleteAnimationCorrelationIds.remove(message.stableId)
foundItemNodes.append(itemNode)
}
}
@ -4055,11 +4066,11 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.currentSendAnimationCorrelationIds = value
}
private var currentDeleteAnimationCorrelationIds = Set<MessageId>()
func setCurrentDeleteAnimationCorrelationIds(_ value: Set<MessageId>) {
private var currentDeleteAnimationCorrelationIds = Set<UInt32>()
func setCurrentDeleteAnimationCorrelationIds(_ value: Set<UInt32>) {
self.currentDeleteAnimationCorrelationIds = value
}
private var currentAppliedDeleteAnimationCorrelationIds = Set<MessageId>()
private var currentAppliedDeleteAnimationCorrelationIds = Set<UInt32>()
var animationCorrelationMessagesFound: (([Int64: ChatMessageItemView]) -> Void)?
@ -4159,8 +4170,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
if !self.currentAppliedDeleteAnimationCorrelationIds.isEmpty {
if let itemNode = itemNode as? ChatMessageItemView, let item = itemNode.item {
for (message, _) in item.content {
if self.currentAppliedDeleteAnimationCorrelationIds.contains(message.id) {
return 1.5
if self.currentAppliedDeleteAnimationCorrelationIds.contains(message.stableId) {
return 0.8
}
}
}