mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
0d2bdf5132
@ -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.";
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)))
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user