mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Stories
This commit is contained in:
parent
fbea3fccc0
commit
9672c77070
10 changed files with 159 additions and 13 deletions
|
|
@ -7090,6 +7090,7 @@ Sorry for the inconvenience.";
|
|||
"Time.HoursAgo_many" = "%@ hours ago";
|
||||
"Time.HoursAgo_0" = "%@ hours ago";
|
||||
"Time.AtDate" = "%@";
|
||||
"Time.AtPreciseDate" = "%@ at %@";
|
||||
|
||||
"Stickers.ShowMore" = "Show More";
|
||||
|
||||
|
|
|
|||
|
|
@ -2051,7 +2051,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||
return
|
||||
}
|
||||
|
||||
if let orderedStorySubscriptions = self.orderedStorySubscriptions, !orderedStorySubscriptions.items.isEmpty {
|
||||
if case .chatList(groupId: .root) = self.location, let orderedStorySubscriptions = self.orderedStorySubscriptions, !orderedStorySubscriptions.items.isEmpty {
|
||||
let _ = (ApplicationSpecificNotice.displayChatListStoriesTooltip(accountManager: self.context.sharedContext.accountManager)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] didDisplay in
|
||||
guard let self else {
|
||||
|
|
|
|||
|
|
@ -434,7 +434,11 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
|||
if case .forum = location {
|
||||
subject = .forum(hasGeneral: hasOnlyGeneralThread)
|
||||
} else {
|
||||
subject = .chats(hasArchive: hasOnlyArchive)
|
||||
if case .chatList(groupId: .archive) = location {
|
||||
subject = .archive
|
||||
} else {
|
||||
subject = .chats(hasArchive: hasOnlyArchive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import AccountContext
|
|||
final class ChatListEmptyNode: ASDisplayNode {
|
||||
enum Subject {
|
||||
case chats(hasArchive: Bool)
|
||||
case archive
|
||||
case filter(showEdit: Bool)
|
||||
case forum(hasGeneral: Bool)
|
||||
}
|
||||
|
|
@ -132,11 +133,14 @@ final class ChatListEmptyNode: ASDisplayNode {
|
|||
func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) {
|
||||
let text: String
|
||||
var descriptionText = ""
|
||||
let buttonText: String
|
||||
let buttonText: String?
|
||||
switch self.subject {
|
||||
case let .chats(hasArchive):
|
||||
text = hasArchive ? strings.ChatList_EmptyChatListWithArchive : strings.ChatList_EmptyChatList
|
||||
buttonText = strings.ChatList_EmptyChatListNewMessage
|
||||
case .archive:
|
||||
text = strings.ChatList_EmptyChatList
|
||||
buttonText = nil
|
||||
case .filter:
|
||||
text = strings.ChatList_EmptyChatListFilterTitle
|
||||
descriptionText = strings.ChatList_EmptyChatListFilterText
|
||||
|
|
@ -152,7 +156,12 @@ final class ChatListEmptyNode: ASDisplayNode {
|
|||
self.textNode.attributedText = string
|
||||
self.descriptionNode.attributedText = descriptionString
|
||||
|
||||
self.buttonNode.title = buttonText
|
||||
if let buttonText {
|
||||
self.buttonNode.title = buttonText
|
||||
self.buttonNode.isHidden = false
|
||||
} else {
|
||||
self.buttonNode.isHidden = true
|
||||
}
|
||||
|
||||
self.activityIndicator.type = .custom(theme.list.itemAccentColor, 22.0, 1.0, false)
|
||||
|
||||
|
|
|
|||
|
|
@ -569,6 +569,16 @@ struct ChatListContactPeer {
|
|||
}
|
||||
|
||||
func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer], accountPeerId: EnginePeer.Id) -> (entries: [ChatListNodeEntry], loading: Bool) {
|
||||
var groupItems = view.groupItems
|
||||
if state.archiveStoryState != nil && groupItems.isEmpty {
|
||||
groupItems.append(EngineChatList.GroupItem(
|
||||
id: .archive,
|
||||
topMessage: nil,
|
||||
items: [],
|
||||
unreadCount: 0
|
||||
))
|
||||
}
|
||||
|
||||
var result: [ChatListNodeEntry] = []
|
||||
|
||||
if !view.hasEarlier {
|
||||
|
|
@ -588,7 +598,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
|||
|
||||
if !view.hasLater, case .chatList = mode {
|
||||
var groupEntryCount = 0
|
||||
for _ in view.groupItems {
|
||||
for _ in groupItems {
|
||||
groupEntryCount += 1
|
||||
}
|
||||
pinnedIndexOffset += UInt16(groupEntryCount)
|
||||
|
|
@ -831,7 +841,7 @@ func chatListNodeEntriesForView(view: EngineChatList, state: ChatListNodeState,
|
|||
}
|
||||
|
||||
if !view.hasLater, case .chatList = mode {
|
||||
for groupReference in view.groupItems {
|
||||
for groupReference in groupItems {
|
||||
let messageIndex = EngineMessage.Index(id: EngineMessage.Id(peerId: EnginePeer.Id(0), namespace: 0, id: 0), timestamp: 1)
|
||||
var mappedStoryState: ChatListNodeState.StoryState?
|
||||
if let archiveStoryState = state.archiveStoryState {
|
||||
|
|
|
|||
|
|
@ -362,7 +362,7 @@ public func stringForRelativeLiveLocationUpdateTimestamp(strings: PresentationSt
|
|||
}
|
||||
}
|
||||
|
||||
public func stringForRelativeActivityTimestamp(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, relativeTimestamp: Int32, relativeTo timestamp: Int32) -> String {
|
||||
public func stringForRelativeActivityTimestamp(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, preciseTime: Bool = false, relativeTimestamp: Int32, relativeTo timestamp: Int32) -> String {
|
||||
let difference = timestamp - relativeTimestamp
|
||||
if difference < 60 {
|
||||
return strings.Time_JustNow
|
||||
|
|
@ -392,6 +392,8 @@ public func stringForRelativeActivityTimestamp(strings: PresentationStrings, dat
|
|||
day = .yesterday
|
||||
}
|
||||
return humanReadableStringForTimestamp(strings: strings, day: day, dateTimeFormat: dateTimeFormat, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min).string
|
||||
} else if preciseTime {
|
||||
return strings.Time_AtPreciseDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat), stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat)).string
|
||||
} else {
|
||||
return strings.Time_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,15 +8,22 @@ import TelegramStringFormatting
|
|||
import MultilineTextComponent
|
||||
|
||||
final class StoryAuthorInfoComponent: Component {
|
||||
struct Counters: Equatable {
|
||||
var position: Int
|
||||
var totalCount: Int
|
||||
}
|
||||
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer?
|
||||
let timestamp: Int32
|
||||
let counters: Counters?
|
||||
let isEdited: Bool
|
||||
|
||||
init(context: AccountContext, peer: EnginePeer?, timestamp: Int32, isEdited: Bool) {
|
||||
init(context: AccountContext, peer: EnginePeer?, timestamp: Int32, counters: Counters?, isEdited: Bool) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.timestamp = timestamp
|
||||
self.counters = counters
|
||||
self.isEdited = isEdited
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +37,9 @@ final class StoryAuthorInfoComponent: Component {
|
|||
if lhs.timestamp != rhs.timestamp {
|
||||
return false
|
||||
}
|
||||
if lhs.counters != rhs.counters {
|
||||
return false
|
||||
}
|
||||
if lhs.isEdited != rhs.isEdited {
|
||||
return false
|
||||
}
|
||||
|
|
@ -39,6 +49,7 @@ final class StoryAuthorInfoComponent: Component {
|
|||
final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private var counterLabel: ComponentView<Empty>?
|
||||
|
||||
private var component: StoryAuthorInfoComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
|
|
@ -71,7 +82,7 @@ final class StoryAuthorInfoComponent: Component {
|
|||
}
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
var subtitle = stringForRelativeActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, relativeTimestamp: component.timestamp, relativeTo: timestamp)
|
||||
var subtitle = stringForRelativeActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, preciseTime: true, relativeTimestamp: component.timestamp, relativeTo: timestamp)
|
||||
|
||||
if component.isEdited {
|
||||
subtitle.append(" • ")
|
||||
|
|
@ -117,6 +128,36 @@ final class StoryAuthorInfoComponent: Component {
|
|||
}
|
||||
transition.setFrame(view: subtitleView, frame: subtitleFrame)
|
||||
}
|
||||
|
||||
let countersSpacing: CGFloat = 5.0
|
||||
if let counters = component.counters {
|
||||
let counterLabel: ComponentView<Empty>
|
||||
if let current = self.counterLabel {
|
||||
counterLabel = current
|
||||
} else {
|
||||
counterLabel = ComponentView()
|
||||
self.counterLabel = counterLabel
|
||||
}
|
||||
let counterSize = counterLabel.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "\(counters.position + 1)/\(counters.totalCount)", font: Font.regular(11.0), textColor: UIColor(white: 1.0, alpha: 0.43))),
|
||||
truncationType: .end,
|
||||
maximumNumberOfLines: 1
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: max(1.0, availableSize.width - titleSize.width - countersSpacing), height: 100.0)
|
||||
)
|
||||
if let counterLabelView = counterLabel.view {
|
||||
if counterLabelView.superview == nil {
|
||||
self.addSubview(counterLabelView)
|
||||
}
|
||||
counterLabelView.frame = CGRect(origin: CGPoint(x: titleFrame.maxX + countersSpacing, y: titleFrame.minY + 1.0 + floorToScreenPixels((titleFrame.height - counterSize.height) * 0.5)), size: counterSize)
|
||||
}
|
||||
} else if let counterLabel = self.counterLabel {
|
||||
self.counterLabel = nil
|
||||
counterLabel.view?.removeFromSuperview()
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||
let allItems = mappedItems.map { item in
|
||||
return StoryContentItem(
|
||||
position: nil,
|
||||
dayCounters: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: item,
|
||||
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: allEntityFiles)
|
||||
|
|
@ -281,6 +282,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
|||
additionalPeerData: additionalPeerData,
|
||||
item: StoryContentItem(
|
||||
position: mappedFocusedIndex ?? focusedIndex,
|
||||
dayCounters: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: mappedItem,
|
||||
entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
|
||||
|
|
@ -1011,6 +1013,7 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
|||
|
||||
let mainItem = StoryContentItem(
|
||||
position: 0,
|
||||
dayCounters: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: mappedItem,
|
||||
entityFiles: extractItemEntityFiles(item: mappedItem, allEntityFiles: allEntityFiles)
|
||||
|
|
@ -1153,20 +1156,58 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
|||
}
|
||||
}
|
||||
|
||||
struct DayIndex: Hashable {
|
||||
var year: Int32
|
||||
var day: Int32
|
||||
|
||||
init(timestamp: Int32) {
|
||||
var time: time_t = time_t(timestamp)
|
||||
var timeinfo: tm = tm()
|
||||
localtime_r(&time, &timeinfo)
|
||||
|
||||
self.year = timeinfo.tm_year
|
||||
self.day = timeinfo.tm_yday
|
||||
}
|
||||
}
|
||||
|
||||
let stateValue: StoryContentContextState
|
||||
if let focusedIndex = focusedIndex {
|
||||
let item = state.items[focusedIndex]
|
||||
self.focusedId = item.id
|
||||
|
||||
var allItems: [StoryContentItem] = []
|
||||
|
||||
var dayCounts: [DayIndex: Int] = [:]
|
||||
var itemDayIndices: [Int32: (Int, DayIndex)] = [:]
|
||||
|
||||
for i in 0 ..< state.items.count {
|
||||
let stateItem = state.items[i]
|
||||
allItems.append(StoryContentItem(
|
||||
position: i,
|
||||
dayCounters: nil,
|
||||
peerId: peer.id,
|
||||
storyItem: stateItem,
|
||||
entityFiles: extractItemEntityFiles(item: stateItem, allEntityFiles: state.allEntityFiles)
|
||||
))
|
||||
|
||||
let day = DayIndex(timestamp: stateItem.timestamp)
|
||||
let dayCount: Int
|
||||
if let current = dayCounts[day] {
|
||||
dayCount = current + 1
|
||||
dayCounts[day] = dayCount
|
||||
} else {
|
||||
dayCount = 1
|
||||
dayCounts[day] = dayCount
|
||||
}
|
||||
itemDayIndices[stateItem.id] = (dayCount - 1, day)
|
||||
}
|
||||
|
||||
var dayCounters: StoryContentItem.DayCounters?
|
||||
if let (offset, day) = itemDayIndices[item.id], let dayCount = dayCounts[day] {
|
||||
dayCounters = StoryContentItem.DayCounters(
|
||||
position: offset,
|
||||
totalCount: dayCount
|
||||
)
|
||||
}
|
||||
|
||||
stateValue = StoryContentContextState(
|
||||
|
|
@ -1175,6 +1216,7 @@ public final class PeerStoryListContentContextImpl: StoryContentContext {
|
|||
additionalPeerData: additionalPeerData,
|
||||
item: StoryContentItem(
|
||||
position: focusedIndex,
|
||||
dayCounters: dayCounters,
|
||||
peerId: peer.id,
|
||||
storyItem: item,
|
||||
entityFiles: extractItemEntityFiles(item: item, allEntityFiles: state.allEntityFiles)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,16 @@ public final class StoryContentItem: Equatable {
|
|||
case off
|
||||
}
|
||||
|
||||
public struct DayCounters: Equatable {
|
||||
public var position: Int
|
||||
public var totalCount: Int
|
||||
|
||||
public init(position: Int, totalCount: Int) {
|
||||
self.position = position
|
||||
self.totalCount = totalCount
|
||||
}
|
||||
}
|
||||
|
||||
public final class SharedState {
|
||||
public init() {
|
||||
}
|
||||
|
|
@ -78,17 +88,20 @@ public final class StoryContentItem: Equatable {
|
|||
}
|
||||
|
||||
public let position: Int?
|
||||
public let dayCounters: DayCounters?
|
||||
public let peerId: EnginePeer.Id?
|
||||
public let storyItem: EngineStoryItem
|
||||
public let entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
|
||||
public init(
|
||||
position: Int?,
|
||||
dayCounters: DayCounters?,
|
||||
peerId: EnginePeer.Id?,
|
||||
storyItem: EngineStoryItem,
|
||||
entityFiles: [EngineMedia.Id: TelegramMediaFile]
|
||||
) {
|
||||
self.position = position
|
||||
self.dayCounters = dayCounters
|
||||
self.peerId = peerId
|
||||
self.storyItem = storyItem
|
||||
self.entityFiles = entityFiles
|
||||
|
|
@ -98,6 +111,9 @@ public final class StoryContentItem: Equatable {
|
|||
if lhs.position != rhs.position {
|
||||
return false
|
||||
}
|
||||
if lhs.dayCounters != rhs.dayCounters {
|
||||
return false
|
||||
}
|
||||
if lhs.peerId != rhs.peerId {
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2445,8 +2445,22 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
}
|
||||
|
||||
var currentCenterInfoItem: InfoItem?
|
||||
if focusedItem != nil {
|
||||
let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(context: component.context, peer: component.slice.peer, timestamp: component.slice.item.storyItem.timestamp, isEdited: component.slice.item.storyItem.isEdited))
|
||||
if let focusedItem {
|
||||
var counters: StoryAuthorInfoComponent.Counters?
|
||||
if focusedItem.dayCounters != nil, let position = focusedItem.position {
|
||||
counters = StoryAuthorInfoComponent.Counters(
|
||||
position: position,
|
||||
totalCount: component.slice.totalCount
|
||||
)
|
||||
}
|
||||
|
||||
let centerInfoComponent = AnyComponent(StoryAuthorInfoComponent(
|
||||
context: component.context,
|
||||
peer: component.slice.peer,
|
||||
timestamp: component.slice.item.storyItem.timestamp,
|
||||
counters: counters,
|
||||
isEdited: component.slice.item.storyItem.isEdited
|
||||
))
|
||||
if let centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent {
|
||||
currentCenterInfoItem = centerInfoItem
|
||||
} else {
|
||||
|
|
@ -2987,11 +3001,18 @@ public final class StoryItemSetContainerComponent: Component {
|
|||
let navigationStripSideInset: CGFloat = 8.0
|
||||
let navigationStripTopInset: CGFloat = 8.0
|
||||
|
||||
var index = max(0, min(index, component.slice.totalCount - 1))
|
||||
var count = component.slice.totalCount
|
||||
if let dayCounters = focusedItem.dayCounters {
|
||||
index = dayCounters.position
|
||||
count = dayCounters.totalCount
|
||||
}
|
||||
|
||||
let _ = self.navigationStrip.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MediaNavigationStripComponent(
|
||||
index: max(0, min(index, component.slice.totalCount - 1)),
|
||||
count: component.slice.totalCount
|
||||
index: index,
|
||||
count: count
|
||||
)),
|
||||
environment: {
|
||||
MediaNavigationStripComponent.EnvironmentType(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue