WinterGram/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/StringForMessageTimestampStatus.swift
isaac 69bfc65da7 Postbox refactor waves 138–276: 168-commit squash
Consolidates 137 wave commits + 31 supporting commits (CLAUDE.md bump,
typealias additions, AnyObject→EngineMedia restoration) into one squashed
commit. Migrates dozens of consumer-side public APIs, struct fields,
protocol methods, and enum payloads from Postbox protocols/structs to
TelegramEngine engine wrappers and typealiases. Drops `import Postbox`
from many files. Adds new TelegramCore typealiases and one
TelegramEngineUnauthorized facade.

Notable changes by category:

**TelegramCore typealias additions** (rule 2 — narrow utility typealiases):
- EngineChatListIndex, EngineTempBoxFile, EngineItemCollectionItemIndex,
  EngineItemCollectionViewEntryIndex, EngineValueBoxEncryptionParameters,
  EngineMessageAndThreadId, EnginePeerStoryStats, EngineMessageHistoryAnchorIndex,
  EngineChatListTotalUnreadStateCategory, EngineChatListTotalUnreadStateStats,
  EnginePeerSummaryCounterTags, EngineChatListTotalUnreadState,
  EngineItemCacheEntryId, EngineHashFunctions,
  EngineCachedMediaResourceRepresentationResult,
  EngineMediaResourceDataFetchResult, EngineMediaResourceDataFetchError,
  EngineMediaResourceStatus, EngineCachedPeerData

**TelegramCore engine extensions/forwarders**:
- EngineMessage.engineMedia, EngineMessage.enginePeers,
  EngineMessage.adAttribute, EngineMessage.effectivelyIncoming
- engineFileSize forwarder
- TelegramEngine.Resources.clearCachedMediaResources(mediaResourceIds: Set<EngineMediaResource.Id>)
- TelegramEngine.Resources.fetchStatus(id:resourceSize:)
- TelegramEngineUnauthorized.UnauthorizedResources facade with storeResourceData

**Public API/struct migrations to engine types**:
- ChatAvailableMessageActions.banAuthor/banAuthors → EnginePeer?/[EnginePeer]
- WebSessionsContextState.peers → [EnginePeer.Id: EnginePeer]
- CacheUsageStats.peers → [EnginePeer.Id: EnginePeer]
- PeerCommand.peer → EnginePeer
- PeerInfoControllerMode.calls(messages:) → [EngineMessage]
- CallControllerNodeProtocol.updatePeer → EnginePeer params
- ChatHistoryListNode.messageInCurrentHistoryView (and 4 variants) → EngineMessage?
- ChatHistorySearchContainerNode.messageForGallery → EngineMessage?
- PeerInfoPaneNode.findLoadedMessage / ensureMessageIsVisible /
  transitionNodeForGallery → engine-typed
- GalleryHiddenMediaTarget.getTransitionInfo /
  GalleryHiddenMediaManager.findTarget → engine-typed
- ChatPanelInterfaceInteraction.presentReactionDeletionOptions /
  presentBan*MessageOptions → EnginePeer
- DrawingMessageRenderer.messages → [EngineMessage]
- ChatVideoGalleryItemScrubberView.setFetchStatusSignal →
  EngineMediaResource.FetchStatus
- ChannelDiscussionGroupActionSheetItem.peer, VoiceChatPeerEntry.peer,
  VoiceChatFullscreenParticipantItem.peer, MediaStreamComponent.chatPeer,
  MediaStreamVideoComponent.callPeer, ChatMessageContactBubbleContentNode.contactPeer,
  ChatMessageForwardInfoNode.peer, ChatMessageCommentFooterContentNode.replyPeers,
  ChatReportPeerTitlePanelNode.peer, ChatMessageActionUrlAuthController.bot,
  PeerMediaCollectionInterfaceState.peer, ChatMessageCallBubbleContentNode.peopleAvatars,
  ChatLoadingNode.renderedPeer (→ EngineRenderedPeer) — all to engine types

**Wave-71-shadow stored-field migrations** (Postbox Peer/Message → Engine wrapper):
- LegacyCallControllerNode.peer
- CallStatusBarNode.currentPeer

**Dead-code / dead-field removals**:
- CallController.peer, CallControllerNodeV2.account,
  ContactMultiselectionController PeerNameIndex fields,
  preparedChatListNodeViewTransition account: Account param,
  FetchResource.swift entirely (unused function)

**Module-level Postbox import drops**: 30+ files including TelegramRootController,
EditStories, GiftViewScreen, AnimatedStickerUtils, FetchPhotoLibraryImageResource,
PeerInfoGiftsPaneNode, PeerInfoPaneContainerNode, PresentAddMembers,
PeerInfoProfileItems, ChatControllerAdminBanUsers, PresentationData typealiases,
DefaultDayPresentationTheme, ChatListViewTransition, GalleryHiddenMediaManager,
RecentSessionsController, GifContext, AuthorizationSequenceController,
PeerInfoHeaderEditingContentNode, PeerInfoHeaderNode,
PeerAllowedReactionListController, CallControllerNodeV2, and 6 PeerInfo pane files.

**AnyObject restoration**: rule 8 added (never substitute Postbox protocols
with Any/AnyObject) — undid previous AnyObject substitutions in waves 141/143
back to EngineMedia.

Doc maintenance: CLAUDE.md updated to reflect new typealiases and forwarders.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 00:44:27 +02:00

223 lines
8.6 KiB
Swift

import Foundation
import TelegramCore
import TelegramPresentationData
import TelegramUIPreferences
import TelegramStringFormatting
import TextFormat
import LocalizedPeerData
import AccountContext
public enum MessageTimestampStatusFormat {
case full
case regular
case minimal
}
private func dateStringForDay(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, timestamp: Int32) -> String {
var t: time_t = time_t(timestamp)
var timeinfo: tm = tm()
localtime_r(&t, &timeinfo)
let timestampNow = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var now: time_t = time_t(timestampNow)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
if timeinfo.tm_year != timeinfoNow.tm_year {
return "\(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat))"
} else {
return "\(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, dateTimeFormat: dateTimeFormat))"
}
}
private func monthAtIndex(_ index: Int, strings: PresentationStrings) -> String {
switch index {
case 0:
return strings.Month_ShortJanuary
case 1:
return strings.Month_ShortFebruary
case 2:
return strings.Month_ShortMarch
case 3:
return strings.Month_ShortApril
case 4:
return strings.Month_ShortMay
case 5:
return strings.Month_ShortJune
case 6:
return strings.Month_ShortJuly
case 7:
return strings.Month_ShortAugust
case 8:
return strings.Month_ShortSeptember
case 9:
return strings.Month_ShortOctober
case 10:
return strings.Month_ShortNovember
case 11:
return strings.Month_ShortDecember
default:
return ""
}
}
public func stringForMessageTimestampStatus(accountPeerId: EnginePeer.Id, message: EngineMessage, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, strings: PresentationStrings, format: MessageTimestampStatusFormat = .regular, associatedData: ChatMessageItemAssociatedData, ignoreAuthor: Bool = false) -> String {
if let adAttribute = message.adAttribute {
switch adAttribute.messageType {
case .sponsored:
return strings.Message_SponsoredLabel
case .recommended:
return strings.Message_RecommendedLabel
}
}
var timestamp: Int32
if let scheduleTime = message.scheduleTime {
timestamp = scheduleTime
} else {
timestamp = message.timestamp
}
var displayFullDate = false
if case .full = format, timestamp > 100000 {
displayFullDate = true
} else if let forwardInfo = message.forwardInfo, message.id.peerId == accountPeerId {
displayFullDate = true
timestamp = forwardInfo.date
}
if let sourceAuthorInfo = message.sourceAuthorInfo, let orignalDate = sourceAuthorInfo.orignalDate {
timestamp = orignalDate
}
var dateText = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)
if timestamp == scheduleWhenOnlineTimestamp {
dateText = " "
}
if let repeatPeriod = message.scheduleRepeatPeriod {
let repeatString: String
switch repeatPeriod {
case 60:
repeatString = "1 min"
case 300:
repeatString = "5 min"
case 86400:
repeatString = strings.Message_RepeatPeriod_Daily
case 7 * 86400:
repeatString = strings.Message_RepeatPeriod_Weekly
case 14 * 86400:
repeatString = strings.Message_RepeatPeriod_Biweekly
case 30 * 86400:
repeatString = strings.Message_RepeatPeriod_Monthly
case 91 * 86400:
repeatString = strings.Message_RepeatPeriod_3Months
case 182 * 86400:
repeatString = strings.Message_RepeatPeriod_6Months
case 365 * 86400:
repeatString = strings.Message_RepeatPeriod_Yearly
default:
repeatString = "\(repeatPeriod)s"
}
dateText = strings.Message_RepeatAt(repeatString, dateText).string
}
if message.id.namespace == Namespaces.Message.ScheduledCloud, let _ = message.pendingProcessingAttribute {
return strings.Message_Approximate(dateText).string
}
if displayFullDate {
let dayText: String
let nowTimestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
var t: time_t = time_t(timestamp)
var timeinfo: tm = tm()
localtime_r(&t, &timeinfo)
var now: time_t = time_t(nowTimestamp)
var timeinfoNow: tm = tm()
localtime_r(&now, &timeinfoNow)
if timeinfo.tm_year == timeinfoNow.tm_year {
if format != .full, timeinfo.tm_yday == timeinfoNow.tm_yday {
dayText = strings.Weekday_Today
} else {
dayText = strings.Date_ChatDateHeader(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)").string
}
} else {
dayText = strings.Date_ChatDateHeaderYear(monthAtIndex(Int(timeinfo.tm_mon), strings: strings), "\(timeinfo.tm_mday)", "\(1900 + timeinfo.tm_year)").string
}
dateText = strings.Message_FullDateFormat(dayText, stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)).string
} else if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported) {
dateText = strings.Message_ImportedDateFormat(dateStringForDay(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: forwardInfo.date), stringForMessageTimestamp(timestamp: forwardInfo.date, dateTimeFormat: dateTimeFormat), dateText).string
}
var authorTitle: String?
if let author = message.author, case .user = author {
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id, info.flags.contains(.messagesShouldHaveProfiles) {
} else {
authorTitle = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
} else if let forwardInfo = message.forwardInfo, forwardInfo.sourceMessageId?.peerId.namespace == Namespaces.Peer.CloudChannel {
authorTitle = forwardInfo.authorSignature
}
} else {
if let peer = message.peers[message.id.peerId] as? TelegramChannel, case .broadcast = peer.info {
if let channel = message.peers[message.id.peerId] as? TelegramChannel, case let .broadcast(info) = channel.info, message.author?.id != channel.id, info.flags.contains(.messagesShouldHaveProfiles) {
} else {
for attribute in message.attributes {
if let attribute = attribute as? AuthorSignatureMessageAttribute {
authorTitle = attribute.signature
break
}
}
}
}
if message.id.peerId != accountPeerId {
for attribute in message.attributes {
if let attribute = attribute as? SourceReferenceMessageAttribute {
if let forwardInfo = message.forwardInfo {
if forwardInfo.author?.id == attribute.messageId.peerId {
if authorTitle == nil {
authorTitle = forwardInfo.authorSignature
}
}
}
break
}
}
}
}
if authorTitle == nil {
for attribute in message.attributes {
if let attribute = attribute as? InlineBusinessBotMessageAttribute {
if let title = attribute.title {
authorTitle = title
} else if let peerId = attribute.peerId, let peer = message.peers[peerId] {
authorTitle = peer.debugDisplayTitle
}
}
}
}
if let subject = associatedData.subject, case let .messageOptions(_, _, info) = subject, case .forward = info {
authorTitle = nil
}
if ignoreAuthor {
authorTitle = nil
}
if case .minimal = format {
} else {
if let authorTitle = authorTitle, !authorTitle.isEmpty {
dateText = "\(authorTitle), \(dateText)"
}
}
return dateText
}