mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-08 19:10:53 +00:00
Stories
This commit is contained in:
parent
1abb7efc75
commit
84dcde0051
@ -9373,3 +9373,8 @@ Sorry for the inconvenience.";
|
||||
"Conversation.StoryForwardTooltip.TwoChats.One" = "Story forwarded to to **%@** and **%@**";
|
||||
"Conversation.StoryForwardTooltip.ManyChats.One" = "Story forwarded to to **%@** and %@ others";
|
||||
"Conversation.StoryForwardTooltip.SavedMessages.One" = "Story forwarded to to **Saved Messages**";
|
||||
|
||||
"Conversation.StoryMentionTextOutgoing" = "You mentioned %@\nin a story";
|
||||
"Conversation.StoryMentionTextIncoming" = "%@ mentioned you\nin a story";
|
||||
"Conversation.StoryExpiredMentionTextOutgoing" = "The story where you mentioned %@\n is no longer available";
|
||||
"Conversation.StoryExpiredMentionTextIncoming" = "The story you were mentioned in\nis no longer available";
|
||||
|
@ -871,6 +871,7 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, bannedSendMedia: (Int32, Bool)?, presentGallery: @escaping () -> Void, presentFiles: @escaping () -> Void, send: @escaping (AnyMediaReference) -> Void) -> AttachmentFileController
|
||||
func makeGalleryCaptionPanelView(context: AccountContext, chatLocation: ChatLocation, customEmojiAvailable: Bool, present: @escaping (ViewController) -> Void, presentInGlobalOverlay: @escaping (ViewController) -> Void) -> NSObject?
|
||||
func makeHashtagSearchController(context: AccountContext, peer: EnginePeer?, query: String) -> ViewController
|
||||
func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController
|
||||
func navigateToChatController(_ params: NavigateToChatControllerParams)
|
||||
func navigateToForumChannel(context: AccountContext, peerId: EnginePeer.Id, navigationController: NavigationController)
|
||||
func navigateToForumThread(context: AccountContext, peerId: EnginePeer.Id, threadId: Int64, messageId: EngineMessage.Id?, navigationController: NavigationController, activateInput: ChatControllerActivateInput?, keepStack: NavigateToChatKeepStack) -> Signal<Never, NoError>
|
||||
|
@ -49,8 +49,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
public let topicAuthorId: EnginePeer.Id?
|
||||
public let hasBots: Bool
|
||||
public let translateToLanguage: String?
|
||||
public let maxReadStoryId: Int32?
|
||||
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadPeerId: EnginePeer.Id?, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false, alwaysDisplayTranscribeButton: DisplayTranscribeButton = DisplayTranscribeButton(canBeDisplayed: false, displayForNotConsumed: false), topicAuthorId: EnginePeer.Id? = nil, hasBots: Bool = false, translateToLanguage: String? = nil) {
|
||||
public init(automaticDownloadPeerType: MediaAutoDownloadPeerType, automaticDownloadPeerId: EnginePeer.Id?, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, isRecentActions: Bool = false, subject: ChatControllerSubject? = nil, contactsPeerIds: Set<EnginePeer.Id> = Set(), channelDiscussionGroup: ChannelDiscussionGroupStatus = .unknown, animatedEmojiStickers: [String: [StickerPackItem]] = [:], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]] = [:], forcedResourceStatus: FileMediaResourceStatus? = nil, currentlyPlayingMessageId: EngineMessage.Index? = nil, isCopyProtectionEnabled: Bool = false, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, accountPeer: EnginePeer?, forceInlineReactions: Bool = false, alwaysDisplayTranscribeButton: DisplayTranscribeButton = DisplayTranscribeButton(canBeDisplayed: false, displayForNotConsumed: false), topicAuthorId: EnginePeer.Id? = nil, hasBots: Bool = false, translateToLanguage: String? = nil, maxReadStoryId: Int32? = nil) {
|
||||
self.automaticDownloadPeerType = automaticDownloadPeerType
|
||||
self.automaticDownloadPeerId = automaticDownloadPeerId
|
||||
self.automaticDownloadNetworkType = automaticDownloadNetworkType
|
||||
@ -72,6 +73,7 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
self.alwaysDisplayTranscribeButton = alwaysDisplayTranscribeButton
|
||||
self.hasBots = hasBots
|
||||
self.translateToLanguage = translateToLanguage
|
||||
self.maxReadStoryId = maxReadStoryId
|
||||
}
|
||||
|
||||
public static func == (lhs: ChatMessageItemAssociatedData, rhs: ChatMessageItemAssociatedData) -> Bool {
|
||||
@ -135,6 +137,9 @@ public final class ChatMessageItemAssociatedData: Equatable {
|
||||
if lhs.translateToLanguage != rhs.translateToLanguage {
|
||||
return false
|
||||
}
|
||||
if lhs.maxReadStoryId != rhs.maxReadStoryId {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -97,6 +97,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/Stories/StoryPeerListComponent",
|
||||
"//submodules/TelegramUI/Components/FullScreenEffectView",
|
||||
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoStoryGridScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -47,6 +47,7 @@ import InviteLinksUI
|
||||
import ChatFolderLinkPreviewScreen
|
||||
import StoryContainerScreen
|
||||
import FullScreenEffectView
|
||||
import PeerInfoStoryGridScreen
|
||||
|
||||
private final class ContextControllerContentSourceImpl: ContextControllerContentSource {
|
||||
let controller: ViewController
|
||||
@ -2604,6 +2605,30 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
self.openStoryCamera()
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Saved Stories", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Stories"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .saved))
|
||||
})
|
||||
})))
|
||||
|
||||
items.append(.action(ContextMenuActionItem(text: "Archived Stories", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Archive"), color: theme.contextMenu.primaryColor)
|
||||
}, action: { [weak self] c, _ in
|
||||
c.dismiss(completion: {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
self.push(PeerInfoStoryGridScreen(context: self.context, peerId: self.context.account.peerId, scope: .archive))
|
||||
})
|
||||
})))
|
||||
} else {
|
||||
items.append(.action(ContextMenuActionItem(text: "Send Message", icon: { theme in
|
||||
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/MessageBubble"), color: theme.contextMenu.primaryColor)
|
||||
|
@ -295,9 +295,17 @@ public func chatListItemStrings(strings: PresentationStrings, nameDisplayOrder:
|
||||
messageText = "📊 \(poll.text)"
|
||||
case let dice as TelegramMediaDice:
|
||||
messageText = dice.emoji
|
||||
case _ as TelegramMediaStory:
|
||||
//TODO:localize
|
||||
messageText = "Story"
|
||||
case let story as TelegramMediaStory:
|
||||
if story.isMention, let peer {
|
||||
if message.flags.contains(.Incoming) {
|
||||
messageText = strings.Conversation_StoryMentionTextIncoming(peer.compactDisplayTitle).string
|
||||
} else {
|
||||
messageText = strings.Conversation_StoryMentionTextOutgoing(peer.compactDisplayTitle).string
|
||||
}
|
||||
} else {
|
||||
//TODO:localize
|
||||
messageText = "Story"
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ open class TransformImageNode: ASDisplayNode {
|
||||
private var disposable = MetaDisposable()
|
||||
|
||||
private var currentTransform: ((TransformImageArguments) -> DrawingContext?)?
|
||||
private var currentArguments: TransformImageArguments?
|
||||
public private(set) var currentArguments: TransformImageArguments?
|
||||
public private(set) var image: UIImage?
|
||||
private var argumentsPromise = ValuePromise<TransformImageArguments>(ignoreRepeated: true)
|
||||
|
||||
|
@ -952,8 +952,8 @@ public func chatMessagePhotoThumbnail(account: Account, userLocation: MediaResou
|
||||
}
|
||||
}
|
||||
|
||||
public func chatMessageVideoThumbnail(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, blurred: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal = chatMessageVideoDatas(postbox: account.postbox, userLocation: userLocation, fileReference: fileReference, thumbnailSize: true, autoFetchFullSizeThumbnail: true, forceThumbnail: blurred)
|
||||
public func chatMessageVideoThumbnail(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, blurred: Bool = false, synchronousLoads: Bool = false) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> {
|
||||
let signal = chatMessageVideoDatas(postbox: account.postbox, userLocation: userLocation, fileReference: fileReference, thumbnailSize: true, synchronousLoad: synchronousLoads, autoFetchFullSizeThumbnail: true, forceThumbnail: blurred)
|
||||
|
||||
return signal
|
||||
|> map { value in
|
||||
|
@ -2615,6 +2615,13 @@ final class MessageHistoryTable: Table {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
for key in associatedStories.keys {
|
||||
associatedStories[key] = CodableEntry(data: Data())
|
||||
}
|
||||
#endif
|
||||
|
||||
associatedMessageIds.append(contentsOf: attribute.associatedMessageIds)
|
||||
if addAssociatedMessages {
|
||||
for messageId in attribute.associatedMessageIds {
|
||||
|
@ -275,7 +275,7 @@ private func filteredGlobalSound(_ sound: PeerMessageSound) -> PeerMessageSound
|
||||
}
|
||||
}
|
||||
|
||||
private func notificationsPeerCategoryEntries(category: NotificationsPeerCategory, globalSettings: GlobalNotificationSettingsSet, state: NotificationExceptionState, presentationData: PresentationData, notificationSoundList: NotificationSoundList?) -> [NotificationsPeerCategoryEntry] {
|
||||
private func notificationsPeerCategoryEntries(category: NotificationsPeerCategory, globalSettings: GlobalNotificationSettingsSet, state: NotificationExceptionState, presentationData: PresentationData, notificationSoundList: NotificationSoundList?, automaticTopPeers: [EnginePeer], automaticNotificationSettings: [EnginePeer.Id: EnginePeer.NotificationSettings]) -> [NotificationsPeerCategoryEntry] {
|
||||
var entries: [NotificationsPeerCategoryEntry] = []
|
||||
|
||||
let notificationSettings: MessageNotificationSettings
|
||||
@ -333,7 +333,7 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.Notifications_MessageNotificationsExceptions.uppercased()))
|
||||
entries.append(.addException(presentationData.theme, presentationData.strings.Notification_Exceptions_AddException))
|
||||
|
||||
let sortedExceptions = notificationExceptions.settings.sorted(by: { lhs, rhs in
|
||||
var sortedExceptions = notificationExceptions.settings.sorted(by: { lhs, rhs in
|
||||
let lhsName = lhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
let rhsName = rhs.value.peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
|
||||
@ -355,99 +355,114 @@ private func notificationsPeerCategoryEntries(category: NotificationsPeerCategor
|
||||
|
||||
return lhsName < rhsName
|
||||
})
|
||||
var automaticSet = Set<EnginePeer.Id>()
|
||||
if globalSettings.privateChats.storySettings.mute == .default {
|
||||
for peer in automaticTopPeers {
|
||||
if sortedExceptions.contains(where: { $0.key == peer.id }) {
|
||||
continue
|
||||
}
|
||||
sortedExceptions.append((peer.id, NotificationExceptionWrapper(settings: automaticNotificationSettings[peer.id]?._asNotificationSettings() ?? .defaultSettings, peer: peer, date: nil)))
|
||||
automaticSet.insert(peer.id)
|
||||
}
|
||||
}
|
||||
|
||||
var existingPeerIds = Set<EnginePeer.Id>()
|
||||
var index: Int = 0
|
||||
|
||||
for (_, value) in sortedExceptions {
|
||||
if !value.peer.isDeleted {
|
||||
var title: String
|
||||
var title: String = ""
|
||||
|
||||
if case .stories = category {
|
||||
var muted = false
|
||||
if value.settings.storySettings.mute == .muted {
|
||||
muted = true
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOff
|
||||
} else {
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOn
|
||||
}
|
||||
|
||||
if !muted {
|
||||
switch value.settings.storySettings.sound {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title.append(", ")
|
||||
}
|
||||
title.append(presentationData.strings.Notification_Exceptions_SoundCustom)
|
||||
}
|
||||
switch value.settings.storySettings.hideSender {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title += ", "
|
||||
}
|
||||
//TODO:localize
|
||||
if case .show = value.settings.displayPreviews {
|
||||
title += "Show Names"
|
||||
} else {
|
||||
title += "Hide Names"
|
||||
}
|
||||
}
|
||||
}
|
||||
if automaticSet.contains(value.peer.id) {
|
||||
//TODO:localize
|
||||
title = "\(presentationData.strings.Notification_Exceptions_AlwaysOn) (automatic)"
|
||||
} else {
|
||||
var muted = false
|
||||
switch value.settings.muteState {
|
||||
case let .muted(until):
|
||||
if until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||
if until < Int32.max - 1 {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: presentationData.strings.baseLanguageCode)
|
||||
|
||||
if Calendar.current.isDateInToday(Date(timeIntervalSince1970: Double(until))) {
|
||||
formatter.dateFormat = "HH:mm"
|
||||
} else {
|
||||
formatter.dateFormat = "E, d MMM HH:mm"
|
||||
}
|
||||
|
||||
let dateString = formatter.string(from: Date(timeIntervalSince1970: Double(until)))
|
||||
|
||||
title = presentationData.strings.Notification_Exceptions_MutedUntil(dateString).string
|
||||
} else {
|
||||
muted = true
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOff
|
||||
}
|
||||
if case .stories = category {
|
||||
var muted = false
|
||||
if value.settings.storySettings.mute == .muted {
|
||||
muted = true
|
||||
title.append(presentationData.strings.Notification_Exceptions_AlwaysOff)
|
||||
} else {
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOn
|
||||
title.append(presentationData.strings.Notification_Exceptions_AlwaysOn)
|
||||
}
|
||||
case .unmuted:
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOn
|
||||
default:
|
||||
title = ""
|
||||
}
|
||||
if !muted {
|
||||
switch value.settings.messageSound {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title.append(", ")
|
||||
|
||||
if !muted {
|
||||
switch value.settings.storySettings.sound {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title.append(", ")
|
||||
}
|
||||
title.append(presentationData.strings.Notification_Exceptions_SoundCustom)
|
||||
}
|
||||
switch value.settings.storySettings.hideSender {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title += ", "
|
||||
}
|
||||
//TODO:localize
|
||||
if case .show = value.settings.displayPreviews {
|
||||
title += "Show Names"
|
||||
} else {
|
||||
title += "Hide Names"
|
||||
}
|
||||
}
|
||||
title.append(presentationData.strings.Notification_Exceptions_SoundCustom)
|
||||
}
|
||||
switch value.settings.displayPreviews {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title += ", "
|
||||
}
|
||||
if case .show = value.settings.displayPreviews {
|
||||
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOn
|
||||
} else {
|
||||
var muted = false
|
||||
switch value.settings.muteState {
|
||||
case let .muted(until):
|
||||
if until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
|
||||
if until < Int32.max - 1 {
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: presentationData.strings.baseLanguageCode)
|
||||
|
||||
if Calendar.current.isDateInToday(Date(timeIntervalSince1970: Double(until))) {
|
||||
formatter.dateFormat = "HH:mm"
|
||||
} else {
|
||||
formatter.dateFormat = "E, d MMM HH:mm"
|
||||
}
|
||||
|
||||
let dateString = formatter.string(from: Date(timeIntervalSince1970: Double(until)))
|
||||
|
||||
title = presentationData.strings.Notification_Exceptions_MutedUntil(dateString).string
|
||||
} else {
|
||||
muted = true
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOff
|
||||
}
|
||||
} else {
|
||||
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOff
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOn
|
||||
}
|
||||
case .unmuted:
|
||||
title = presentationData.strings.Notification_Exceptions_AlwaysOn
|
||||
default:
|
||||
title = ""
|
||||
}
|
||||
if !muted {
|
||||
switch value.settings.messageSound {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title.append(", ")
|
||||
}
|
||||
title.append(presentationData.strings.Notification_Exceptions_SoundCustom)
|
||||
}
|
||||
switch value.settings.displayPreviews {
|
||||
case .default:
|
||||
break
|
||||
default:
|
||||
if !title.isEmpty {
|
||||
title += ", "
|
||||
}
|
||||
if case .show = value.settings.displayPreviews {
|
||||
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOn
|
||||
} else {
|
||||
title += presentationData.strings.Notification_Exceptions_PreviewAlwaysOff
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -923,8 +938,35 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
||||
let sharedData = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
|
||||
let preferences = context.account.postbox.preferencesView(keys: [PreferencesKeys.globalNotifications])
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, context.engine.peers.notificationSoundList(), sharedData, preferences, statePromise.get())
|
||||
|> map { presentationData, notificationSoundList, sharedData, view, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
var automaticData: Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.NotificationSettings]), NoError> = .single(([], [:]))
|
||||
if case .stories = category {
|
||||
automaticData = context.engine.peers.recentPeers()
|
||||
|> mapToSignal { recentPeers -> Signal<([EnginePeer], [EnginePeer.Id: EnginePeer.NotificationSettings]), NoError> in
|
||||
guard case let .peers(peersValue) = recentPeers else {
|
||||
return .single(([], [:]))
|
||||
}
|
||||
let peers = peersValue.prefix(5).map(EnginePeer.init)
|
||||
return context.engine.data.subscribe(
|
||||
EngineDataMap(peers.map { peer in
|
||||
return TelegramEngine.EngineData.Item.Peer.NotificationSettings(id: peer.id)
|
||||
})
|
||||
)
|
||||
|> map { settings -> ([EnginePeer], [EnginePeer.Id: EnginePeer.NotificationSettings]) in
|
||||
var settingsMap: [EnginePeer.Id: EnginePeer.NotificationSettings] = [:]
|
||||
for peer in peers {
|
||||
if let value = settings[peer.id] {
|
||||
settingsMap[peer.id] = value
|
||||
} else {
|
||||
settingsMap[peer.id] = EnginePeer.NotificationSettings(TelegramPeerNotificationSettings.defaultSettings)
|
||||
}
|
||||
}
|
||||
return (peers, settingsMap)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let signal = combineLatest(context.sharedContext.presentationData, context.engine.peers.notificationSoundList(), sharedData, preferences, statePromise.get(), automaticData)
|
||||
|> map { presentationData, notificationSoundList, sharedData, view, state, automaticData -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let viewSettings: GlobalNotificationSettingsSet
|
||||
if let settings = view.values[PreferencesKeys.globalNotifications]?.get(GlobalNotificationSettings.self) {
|
||||
viewSettings = settings.effective
|
||||
@ -932,7 +974,7 @@ public func notificationsPeerCategoryController(context: AccountContext, categor
|
||||
viewSettings = GlobalNotificationSettingsSet.defaultSettings
|
||||
}
|
||||
|
||||
let entries = notificationsPeerCategoryEntries(category: category, globalSettings: viewSettings, state: state, presentationData: presentationData, notificationSoundList: notificationSoundList)
|
||||
let entries = notificationsPeerCategoryEntries(category: category, globalSettings: viewSettings, state: state, presentationData: presentationData, notificationSoundList: notificationSoundList, automaticTopPeers: automaticData.0, automaticNotificationSettings: automaticData.1)
|
||||
|
||||
var index = 0
|
||||
var scrollToItem: ListViewScrollToItem?
|
||||
|
@ -23,6 +23,7 @@ public protocol SparseItemGridView: UIView {
|
||||
public protocol SparseItemGridDisplayItem: AnyObject {
|
||||
var layer: SparseItemGridLayer? { get }
|
||||
var view: SparseItemGridView? { get }
|
||||
var blurLayer: SimpleLayer? { get }
|
||||
}
|
||||
|
||||
public protocol SparseItemGridShimmerLayer: CALayer {
|
||||
@ -342,6 +343,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
let layer: SparseItemGridLayer?
|
||||
let view: SparseItemGridView?
|
||||
var shimmerLayer: SparseItemGridShimmerLayer?
|
||||
var blurLayer: SimpleLayer?
|
||||
|
||||
init(layer: SparseItemGridLayer?, view: SparseItemGridView?) {
|
||||
self.layer = layer
|
||||
@ -391,8 +393,9 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
let itemSpacing: CGFloat
|
||||
let lastItemSize: CGFloat
|
||||
let itemsPerRow: Int
|
||||
let centerItems: Bool
|
||||
|
||||
init(containerLayout: ContainerLayout, zoomLevel: ZoomLevel) {
|
||||
init(containerLayout: ContainerLayout, zoomLevel: ZoomLevel, itemCount: Int) {
|
||||
self.containerLayout = containerLayout
|
||||
let width: CGFloat
|
||||
if containerLayout.useSideInsets {
|
||||
@ -400,15 +403,23 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
} else {
|
||||
width = containerLayout.size.width
|
||||
}
|
||||
var centerItems = false
|
||||
if let fixedItemHeight = containerLayout.fixedItemHeight {
|
||||
self.itemsPerRow = 1
|
||||
self.itemSize = CGSize(width: width, height: fixedItemHeight)
|
||||
self.lastItemSize = width
|
||||
self.itemSpacing = 0.0
|
||||
self.centerItems = false
|
||||
} else {
|
||||
self.itemSpacing = 1.0
|
||||
|
||||
let itemsPerRow = CGFloat(zoomLevel.rawValue)
|
||||
let itemsPerRow: CGFloat
|
||||
if containerLayout.fixedItemAspect != nil && itemCount <= 2 {
|
||||
itemsPerRow = 2.0
|
||||
centerItems = itemCount == 1
|
||||
} else {
|
||||
itemsPerRow = CGFloat(zoomLevel.rawValue)
|
||||
}
|
||||
self.itemsPerRow = Int(itemsPerRow)
|
||||
let itemSize = floorToScreenPixels((width - (self.itemSpacing * CGFloat(self.itemsPerRow - 1))) / itemsPerRow)
|
||||
if let fixedItemAspect = containerLayout.fixedItemAspect {
|
||||
@ -417,16 +428,24 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
self.itemSize = CGSize(width: itemSize, height: itemSize)
|
||||
}
|
||||
|
||||
self.lastItemSize = width - (self.itemSize.width + self.itemSpacing) * CGFloat(self.itemsPerRow - 1)
|
||||
if centerItems {
|
||||
self.lastItemSize = self.itemSize.width
|
||||
} else {
|
||||
self.lastItemSize = width - (self.itemSize.width + self.itemSpacing) * CGFloat(self.itemsPerRow - 1)
|
||||
}
|
||||
self.centerItems = centerItems
|
||||
}
|
||||
}
|
||||
|
||||
func frame(at index: Int) -> CGRect {
|
||||
let row = index / self.itemsPerRow
|
||||
let column = index % self.itemsPerRow
|
||||
|
||||
|
||||
return CGRect(origin: CGPoint(x: (self.containerLayout.useSideInsets ? self.containerLayout.insets.left : 0.0) + CGFloat(column) * (self.itemSize.width + self.itemSpacing), y: self.containerLayout.insets.top + CGFloat(row) * (self.itemSize.height + self.itemSpacing)), size: CGSize(width: column == (self.itemsPerRow - 1) ? self.lastItemSize : itemSize.width, height: itemSize.height))
|
||||
var frame = CGRect(origin: CGPoint(x: (self.containerLayout.useSideInsets ? self.containerLayout.insets.left : 0.0) + CGFloat(column) * (self.itemSize.width + self.itemSpacing), y: self.containerLayout.insets.top + CGFloat(row) * (self.itemSize.height + self.itemSpacing)), size: CGSize(width: column == (self.itemsPerRow - 1) ? self.lastItemSize : itemSize.width, height: itemSize.height))
|
||||
if self.centerItems {
|
||||
frame.origin.x = floor((self.containerLayout.size.width - frame.width) * 0.5)
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
func contentHeight(count: Int) -> CGFloat {
|
||||
@ -514,7 +533,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
|
||||
func update(containerLayout: ContainerLayout, items: Items, restoreScrollPosition: (y: CGFloat, index: Int)?, synchronous: SparseItemGrid.Synchronous) {
|
||||
if self.layout?.containerLayout != containerLayout || self.items !== items {
|
||||
self.layout = Layout(containerLayout: containerLayout, zoomLevel: self.zoomLevel)
|
||||
self.layout = Layout(containerLayout: containerLayout, zoomLevel: self.zoomLevel, itemCount: items.count)
|
||||
self.items = items
|
||||
|
||||
self.updateVisibleItems(resetScrolling: true, synchronous: synchronous, restoreScrollPosition: restoreScrollPosition)
|
||||
@ -949,6 +968,8 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
var bindItems: [Item] = []
|
||||
var bindLayers: [SparseItemGridDisplayItem] = []
|
||||
var updateLayers: [SparseItemGridDisplayItem] = []
|
||||
|
||||
let addBlur = layout.centerItems
|
||||
|
||||
let visibleRange = layout.visibleItemRange(for: visibleBounds, count: items.count)
|
||||
if visibleRange.maxIndex >= visibleRange.minIndex {
|
||||
@ -974,6 +995,22 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
if addBlur {
|
||||
let blurLayer: SimpleLayer
|
||||
if let current = itemLayer.blurLayer {
|
||||
blurLayer = current
|
||||
} else {
|
||||
blurLayer = SimpleLayer()
|
||||
blurLayer.masksToBounds = true
|
||||
blurLayer.zPosition = -1.0
|
||||
self.scrollView.layer.addSublayer(blurLayer)
|
||||
itemLayer.blurLayer = blurLayer
|
||||
}
|
||||
} else if let blurLayer = itemLayer.blurLayer {
|
||||
itemLayer.blurLayer = nil
|
||||
blurLayer.removeFromSuperlayer()
|
||||
}
|
||||
|
||||
if itemLayer.needsShimmer {
|
||||
let placeholderLayer: SparseItemGridShimmerLayer
|
||||
if let current = itemLayer.shimmerLayer {
|
||||
@ -995,6 +1032,9 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
validIds.insert(item.id)
|
||||
|
||||
itemLayer.frame = itemFrame
|
||||
if let blurLayer = itemLayer.blurLayer {
|
||||
blurLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: itemFrame.minY), size: CGSize(width: layout.containerLayout.size.width, height: itemFrame.height))
|
||||
}
|
||||
} else {
|
||||
let placeholderLayer: SparseItemGridShimmerLayer
|
||||
if self.visiblePlaceholders.count > usedPlaceholderCount {
|
||||
@ -1022,7 +1062,7 @@ public final class SparseItemGrid: ASDisplayNode {
|
||||
if let layer = item.layer {
|
||||
layer.update(size: layer.frame.size)
|
||||
} else if let view = item.view {
|
||||
view.update(size: layer.frame.size, insets: layout.containerLayout.insets)
|
||||
view.update(size: view.layer.frame.size, insets: layout.containerLayout.insets)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -935,7 +935,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1886646706] = { return Api.UrlAuthResult.parse_urlAuthResultAccepted($0) }
|
||||
dict[-1445536993] = { return Api.UrlAuthResult.parse_urlAuthResultDefault($0) }
|
||||
dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) }
|
||||
dict[-1885878744] = { return Api.User.parse_user($0) }
|
||||
dict[-1414139616] = { return Api.User.parse_user($0) }
|
||||
dict[-742634630] = { return Api.User.parse_userEmpty($0) }
|
||||
dict[1340198022] = { return Api.UserFull.parse_userFull($0) }
|
||||
dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) }
|
||||
|
@ -452,14 +452,14 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum User: TypeConstructorDescription {
|
||||
case user(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?, emojiStatus: Api.EmojiStatus?, usernames: [Api.Username]?)
|
||||
case user(flags: Int32, flags2: Int32, id: Int64, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: Api.UserProfilePhoto?, status: Api.UserStatus?, botInfoVersion: Int32?, restrictionReason: [Api.RestrictionReason]?, botInlinePlaceholder: String?, langCode: String?, emojiStatus: Api.EmojiStatus?, usernames: [Api.Username]?, storiesMaxId: Int32?)
|
||||
case userEmpty(id: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames):
|
||||
case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames, let storiesMaxId):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1885878744)
|
||||
buffer.appendInt32(-1414139616)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(flags2, buffer: buffer, boxed: false)
|
||||
@ -485,6 +485,7 @@ public extension Api {
|
||||
for item in usernames! {
|
||||
item.serialize(buffer, true)
|
||||
}}
|
||||
if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(storiesMaxId!, buffer: buffer, boxed: false)}
|
||||
break
|
||||
case .userEmpty(let id):
|
||||
if boxed {
|
||||
@ -497,8 +498,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames):
|
||||
return ("user", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("username", username as Any), ("phone", phone as Any), ("photo", photo as Any), ("status", status as Any), ("botInfoVersion", botInfoVersion as Any), ("restrictionReason", restrictionReason as Any), ("botInlinePlaceholder", botInlinePlaceholder as Any), ("langCode", langCode as Any), ("emojiStatus", emojiStatus as Any), ("usernames", usernames as Any)])
|
||||
case .user(let flags, let flags2, let id, let accessHash, let firstName, let lastName, let username, let phone, let photo, let status, let botInfoVersion, let restrictionReason, let botInlinePlaceholder, let langCode, let emojiStatus, let usernames, let storiesMaxId):
|
||||
return ("user", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("firstName", firstName as Any), ("lastName", lastName as Any), ("username", username as Any), ("phone", phone as Any), ("photo", photo as Any), ("status", status as Any), ("botInfoVersion", botInfoVersion as Any), ("restrictionReason", restrictionReason as Any), ("botInlinePlaceholder", botInlinePlaceholder as Any), ("langCode", langCode as Any), ("emojiStatus", emojiStatus as Any), ("usernames", usernames as Any), ("storiesMaxId", storiesMaxId as Any)])
|
||||
case .userEmpty(let id):
|
||||
return ("userEmpty", [("id", id as Any)])
|
||||
}
|
||||
@ -547,6 +548,8 @@ public extension Api {
|
||||
if Int(_2!) & Int(1 << 0) != 0 {if let _ = reader.readInt32() {
|
||||
_16 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Username.self)
|
||||
} }
|
||||
var _17: Int32?
|
||||
if Int(_2!) & Int(1 << 5) != 0 {_17 = reader.readInt32() }
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = _3 != nil
|
||||
@ -563,8 +566,9 @@ public extension Api {
|
||||
let _c14 = (Int(_1!) & Int(1 << 22) == 0) || _14 != nil
|
||||
let _c15 = (Int(_1!) & Int(1 << 30) == 0) || _15 != nil
|
||||
let _c16 = (Int(_2!) & Int(1 << 0) == 0) || _16 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 {
|
||||
return Api.User.user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16)
|
||||
let _c17 = (Int(_2!) & Int(1 << 5) == 0) || _17 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 {
|
||||
return Api.User.user(flags: _1!, flags2: _2!, id: _3!, accessHash: _4, firstName: _5, lastName: _6, username: _7, phone: _8, photo: _9, status: _10, botInfoVersion: _11, restrictionReason: _12, botInlinePlaceholder: _13, langCode: _14, emojiStatus: _15, usernames: _16, storiesMaxId: _17)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
@ -8510,6 +8510,21 @@ public extension Api.functions.stories {
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.stories {
|
||||
static func getAllReadUserStories() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(1922848300)
|
||||
|
||||
return (FunctionDescription(name: "stories.getAllReadUserStories", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.Updates?
|
||||
if let signature = reader.readInt32() {
|
||||
result = Api.parse(reader, signature: signature) as? Api.Updates
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
public extension Api.functions.stories {
|
||||
static func getAllStories(flags: Int32, state: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.stories.AllStories>) {
|
||||
let buffer = Buffer()
|
||||
|
@ -525,7 +525,7 @@ struct AccountMutableState {
|
||||
var presences: [PeerId: Api.UserStatus] = [:]
|
||||
for user in users {
|
||||
switch user {
|
||||
case let .user(_, _, id, _, _, _, _, _, _, status, _, _, _, _, _, _):
|
||||
case let .user(_, _, id, _, _, _, _, _, _, status, _, _, _, _, _, _, _):
|
||||
if let status = status {
|
||||
presences[PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))] = status
|
||||
}
|
||||
|
@ -380,8 +380,9 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
||||
}
|
||||
case let .messageMediaDice(value, emoticon):
|
||||
return (TelegramMediaDice(emoji: emoticon, value: value), nil, nil, nil)
|
||||
case let .messageMediaStory(_, userId, id, _):
|
||||
return (TelegramMediaStory(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id)), nil, nil, nil)
|
||||
case let .messageMediaStory(flags, userId, id, _):
|
||||
let isMention = (flags & (1 << 1)) != 0
|
||||
return (TelegramMediaStory(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id), isMention: isMention), nil, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) ->
|
||||
webpageAttributes = attributes.compactMap(telegramMediaWebpageAttributeFromApiWebpageAttribute)
|
||||
for attribute in attributes {
|
||||
if case let .webPageAttributeStory(_, userId, id, _) = attribute {
|
||||
story = TelegramMediaStory(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id))
|
||||
story = TelegramMediaStory(storyId: StoryId(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(userId)), id: id), isMention: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ extension TelegramPeerUsername {
|
||||
extension TelegramUser {
|
||||
convenience init(user: Api.User) {
|
||||
switch user {
|
||||
case let .user(flags, flags2, id, accessHash, firstName, lastName, username, phone, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, usernames):
|
||||
case let .user(flags, flags2, id, accessHash, firstName, lastName, username, phone, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, usernames, _):
|
||||
let representations: [TelegramMediaImageRepresentation] = photo.flatMap(parsedTelegramProfilePhoto) ?? []
|
||||
|
||||
let isMin = (flags & (1 << 20)) != 0
|
||||
@ -104,7 +104,7 @@ extension TelegramUser {
|
||||
|
||||
static func merge(_ lhs: TelegramUser?, rhs: Api.User) -> TelegramUser? {
|
||||
switch rhs {
|
||||
case let .user(flags, flags2, _, rhsAccessHash, _, _, _, _, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, _):
|
||||
case let .user(flags, flags2, _, rhsAccessHash, _, _, _, _, photo, _, _, restrictionReason, botInlinePlaceholder, _, emojiStatus, _, _):
|
||||
let isMin = (flags & (1 << 20)) != 0
|
||||
if !isMin {
|
||||
return TelegramUser(user: rhs)
|
||||
|
@ -23,7 +23,7 @@ extension TelegramUserPresence {
|
||||
|
||||
convenience init?(apiUser: Api.User) {
|
||||
switch apiUser {
|
||||
case let .user(_, _, _, _, _, _, _, _, _, status, _, _, _, _, _, _):
|
||||
case let .user(_, _, _, _, _, _, _, _, _, status, _, _, _, _, _, _, _):
|
||||
if let status = status {
|
||||
self.init(apiStatus: status)
|
||||
} else {
|
||||
|
@ -192,7 +192,7 @@ extension Api.Chat {
|
||||
extension Api.User {
|
||||
var peerId: PeerId {
|
||||
switch self {
|
||||
case let .user(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .user(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))
|
||||
case let .userEmpty(id):
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(id))
|
||||
|
@ -7,19 +7,22 @@ public final class TelegramMediaStory: Media, Equatable {
|
||||
public let peerIds: [PeerId]
|
||||
|
||||
public let storyId: StoryId
|
||||
public let isMention: Bool
|
||||
|
||||
public var storyIds: [StoryId] {
|
||||
return [self.storyId]
|
||||
}
|
||||
|
||||
public init(storyId: StoryId) {
|
||||
public init(storyId: StoryId, isMention: Bool) {
|
||||
self.storyId = storyId
|
||||
self.isMention = isMention
|
||||
|
||||
self.peerIds = [self.storyId.peerId]
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.storyId = StoryId(peerId: PeerId(decoder.decodeInt64ForKey("pid", orElse: 0)), id: decoder.decodeInt32ForKey("sid", orElse: 0))
|
||||
self.isMention = decoder.decodeBoolForKey("mns", orElse: false)
|
||||
|
||||
self.peerIds = [self.storyId.peerId]
|
||||
}
|
||||
@ -27,6 +30,7 @@ public final class TelegramMediaStory: Media, Equatable {
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.storyId.peerId.toInt64(), forKey: "pid")
|
||||
encoder.encodeInt32(self.storyId.id, forKey: "sid")
|
||||
encoder.encodeBool(self.isMention, forKey: "mns")
|
||||
}
|
||||
|
||||
public func isLikelyToBeUpdated() -> Bool {
|
||||
@ -48,6 +52,9 @@ public final class TelegramMediaStory: Media, Equatable {
|
||||
if lhs.storyId != rhs.storyId {
|
||||
return false
|
||||
}
|
||||
if lhs.isMention != rhs.isMention {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ public enum Stories {
|
||||
case isPublic
|
||||
case isCloseFriends
|
||||
case isForwardingDisabled
|
||||
case isEdited
|
||||
}
|
||||
|
||||
public let id: Int32
|
||||
@ -141,6 +142,7 @@ public enum Stories {
|
||||
public let isPublic: Bool
|
||||
public let isCloseFriends: Bool
|
||||
public let isForwardingDisabled: Bool
|
||||
public let isEdited: Bool
|
||||
|
||||
public init(
|
||||
id: Int32,
|
||||
@ -155,7 +157,8 @@ public enum Stories {
|
||||
isExpired: Bool,
|
||||
isPublic: Bool,
|
||||
isCloseFriends: Bool,
|
||||
isForwardingDisabled: Bool
|
||||
isForwardingDisabled: Bool,
|
||||
isEdited: Bool
|
||||
) {
|
||||
self.id = id
|
||||
self.timestamp = timestamp
|
||||
@ -170,6 +173,7 @@ public enum Stories {
|
||||
self.isPublic = isPublic
|
||||
self.isCloseFriends = isCloseFriends
|
||||
self.isForwardingDisabled = isForwardingDisabled
|
||||
self.isEdited = isEdited
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
@ -194,6 +198,7 @@ public enum Stories {
|
||||
self.isPublic = try container.decodeIfPresent(Bool.self, forKey: .isPublic) ?? false
|
||||
self.isCloseFriends = try container.decodeIfPresent(Bool.self, forKey: .isCloseFriends) ?? false
|
||||
self.isForwardingDisabled = try container.decodeIfPresent(Bool.self, forKey: .isForwardingDisabled) ?? false
|
||||
self.isEdited = try container.decodeIfPresent(Bool.self, forKey: .isEdited) ?? false
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@ -219,6 +224,7 @@ public enum Stories {
|
||||
try container.encode(self.isPublic, forKey: .isPublic)
|
||||
try container.encode(self.isCloseFriends, forKey: .isCloseFriends)
|
||||
try container.encode(self.isForwardingDisabled, forKey: .isForwardingDisabled)
|
||||
try container.encode(self.isEdited, forKey: .isEdited)
|
||||
}
|
||||
|
||||
public static func ==(lhs: Item, rhs: Item) -> Bool {
|
||||
@ -269,6 +275,9 @@ public enum Stories {
|
||||
if lhs.isForwardingDisabled != rhs.isForwardingDisabled {
|
||||
return false
|
||||
}
|
||||
if lhs.isEdited != rhs.isEdited {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -850,7 +859,8 @@ func _internal_uploadStoryImpl(postbox: Postbox, network: Network, accountPeerId
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
items.append(StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp))
|
||||
@ -999,7 +1009,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
transaction.setStory(id: storyId, value: entry)
|
||||
@ -1022,7 +1033,8 @@ func _internal_editStoryPrivacy(account: Account, id: Int32, privacy: EngineStor
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp)
|
||||
@ -1154,7 +1166,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
if let entry = CodableEntry(Stories.StoredItem.item(updatedItem)) {
|
||||
items[index] = StoryItemsTableEntry(value: entry, id: item.id, expirationTimestamp: updatedItem.expirationTimestamp)
|
||||
@ -1176,7 +1189,8 @@ func _internal_updateStoriesArePinned(account: Account, ids: [Int32: EngineStory
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
updatedItems.append(updatedItem)
|
||||
}
|
||||
@ -1273,6 +1287,7 @@ extension Stories.StoredItem {
|
||||
let isPublic = (flags & (1 << 7)) != 0
|
||||
let isCloseFriends = (flags & (1 << 8)) != 0
|
||||
let isForwardingDisabled = (flags & (1 << 10)) != 0
|
||||
let isEdited = (flags & (1 << 11)) != 0
|
||||
|
||||
let item = Stories.Item(
|
||||
id: id,
|
||||
@ -1287,7 +1302,8 @@ extension Stories.StoredItem {
|
||||
isExpired: isExpired,
|
||||
isPublic: isPublic,
|
||||
isCloseFriends: isCloseFriends,
|
||||
isForwardingDisabled: isForwardingDisabled
|
||||
isForwardingDisabled: isForwardingDisabled,
|
||||
isEdited: isEdited
|
||||
)
|
||||
self = .item(item)
|
||||
} else {
|
||||
|
@ -45,8 +45,9 @@ public final class EngineStoryItem: Equatable {
|
||||
public let isPending: Bool
|
||||
public let isCloseFriends: Bool
|
||||
public let isForwardingDisabled: Bool
|
||||
public let isEdited: Bool
|
||||
|
||||
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isForwardingDisabled: Bool) {
|
||||
public init(id: Int32, timestamp: Int32, expirationTimestamp: Int32, media: EngineMedia, text: String, entities: [MessageTextEntity], views: Views?, privacy: EngineStoryPrivacy?, isPinned: Bool, isExpired: Bool, isPublic: Bool, isPending: Bool, isCloseFriends: Bool, isForwardingDisabled: Bool, isEdited: Bool) {
|
||||
self.id = id
|
||||
self.timestamp = timestamp
|
||||
self.expirationTimestamp = expirationTimestamp
|
||||
@ -61,6 +62,7 @@ public final class EngineStoryItem: Equatable {
|
||||
self.isPending = isPending
|
||||
self.isCloseFriends = isCloseFriends
|
||||
self.isForwardingDisabled = isForwardingDisabled
|
||||
self.isEdited = isEdited
|
||||
}
|
||||
|
||||
public static func ==(lhs: EngineStoryItem, rhs: EngineStoryItem) -> Bool {
|
||||
@ -106,6 +108,9 @@ public final class EngineStoryItem: Equatable {
|
||||
if lhs.isForwardingDisabled != rhs.isForwardingDisabled {
|
||||
return false
|
||||
}
|
||||
if lhs.isEdited != rhs.isEdited {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -135,7 +140,8 @@ extension EngineStoryItem {
|
||||
isExpired: self.isExpired,
|
||||
isPublic: self.isPublic,
|
||||
isCloseFriends: self.isCloseFriends,
|
||||
isForwardingDisabled: self.isForwardingDisabled
|
||||
isForwardingDisabled: self.isForwardingDisabled,
|
||||
isEdited: self.isEdited
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -494,7 +500,8 @@ public final class PeerStoryListContext {
|
||||
isPublic: item.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
items.append(mappedItem)
|
||||
}
|
||||
@ -601,7 +608,8 @@ public final class PeerStoryListContext {
|
||||
isPublic: item.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
storyItems.append(mappedItem)
|
||||
}
|
||||
@ -735,7 +743,8 @@ public final class PeerStoryListContext {
|
||||
isPublic: item.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
finalUpdatedState = updatedState
|
||||
}
|
||||
@ -774,7 +783,8 @@ public final class PeerStoryListContext {
|
||||
isPublic: item.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
))
|
||||
updatedState.items.sort(by: { lhs, rhs in
|
||||
return lhs.timestamp > rhs.timestamp
|
||||
@ -922,7 +932,8 @@ public final class PeerExpiringStoryListContext {
|
||||
isPublic: item.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
items.append(.item(mappedItem))
|
||||
}
|
||||
|
@ -965,7 +965,8 @@ public extension TelegramEngine {
|
||||
isExpired: item.isExpired,
|
||||
isPublic: item.isPublic,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
))
|
||||
if let entry = CodableEntry(updatedItem) {
|
||||
currentItems[i] = StoryItemsTableEntry(value: entry, id: updatedItem.id, expirationTimestamp: updatedItem.expirationTimestamp)
|
||||
|
@ -147,7 +147,7 @@ func updatePeerPresences(transaction: Transaction, accountPeerId: PeerId, peerPr
|
||||
parsedPresences[peerId] = presence
|
||||
default:
|
||||
switch user {
|
||||
case let .user(flags, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .user(flags, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
let isMin = (flags & (1 << 20)) != 0
|
||||
if isMin, let _ = transaction.getPeerPresence(peerId: peerId) {
|
||||
} else {
|
||||
@ -215,7 +215,7 @@ func updateContacts(transaction: Transaction, apiUsers: [Api.User]) {
|
||||
for user in apiUsers {
|
||||
var isContact: Bool?
|
||||
switch user {
|
||||
case let .user(flags, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
case let .user(flags, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _):
|
||||
if (flags & (1 << 20)) == 0 {
|
||||
isContact = (flags & (1 << 11)) != 0
|
||||
}
|
||||
|
@ -1249,33 +1249,23 @@ public struct PresentationResourcesChat {
|
||||
|
||||
public static func chatExpiredStoryIndicatorIcon(_ theme: PresentationTheme, type: ChatExpiredStoryIndicatorType) -> UIImage? {
|
||||
return theme.image(PresentationResourceParameterKey.chatExpiredStoryIndicatorIcon(type: type), { theme in
|
||||
return generateImage(CGSize(width: 34.0, height: 34.0), rotatedContext: { size, context in
|
||||
return generateImage(CGSize(width: 16.0, height: 16.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.addPath(UIBezierPath(roundedRect: CGRect(origin: CGPoint(), size: size), cornerRadius: 6.0).cgPath)
|
||||
context.clip()
|
||||
|
||||
let color: UIColor
|
||||
let foregroundColor: UIColor
|
||||
switch type {
|
||||
case .incoming:
|
||||
color = theme.chat.message.incoming.mediaActiveControlColor.withMultipliedAlpha(0.1)
|
||||
foregroundColor = theme.chat.message.incoming.mediaActiveControlColor
|
||||
case .outgoing:
|
||||
color = theme.chat.message.outgoing.mediaActiveControlColor.withMultipliedAlpha(0.1)
|
||||
foregroundColor = theme.chat.message.outgoing.mediaActiveControlColor
|
||||
case .free:
|
||||
color = theme.chat.message.freeform.withWallpaper.reactionActiveMediaPlaceholder
|
||||
foregroundColor = theme.chat.message.freeform.withWallpaper.reactionActiveBackground
|
||||
foregroundColor = theme.chat.serviceMessage.components.withDefaultWallpaper.primaryText
|
||||
}
|
||||
|
||||
context.setFillColor(color.cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/ExpiredStoryIcon"), color: foregroundColor) {
|
||||
UIGraphicsPushContext(context)
|
||||
|
||||
image.draw(at: CGPoint(x: floor((size.width - image.size.width) * 0.5), y: floor((size.height - image.size.height) * 0.5)), blendMode: .normal, alpha: 1.0)
|
||||
let fittedSize = image.size
|
||||
image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - fittedSize.width) * 0.5), y: floor((size.height - fittedSize.height) * 0.5)), size: fittedSize), blendMode: .normal, alpha: 1.0)
|
||||
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
|
@ -900,6 +900,16 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
|
||||
case .file:
|
||||
attributedString = NSAttributedString(string: strings.Message_VideoExpired, font: titleFont, textColor: primaryTextColor)
|
||||
}
|
||||
} else if let _ = media as? TelegramMediaStory {
|
||||
let compactPeerName = message.peers[message.id.peerId].flatMap(EnginePeer.init)?.compactDisplayTitle ?? ""
|
||||
|
||||
let resultTitleString: PresentationStrings.FormattedString
|
||||
if message.flags.contains(.Incoming) {
|
||||
resultTitleString = PresentationStrings.FormattedString(string: strings.Conversation_StoryExpiredMentionTextIncoming, ranges: [])
|
||||
} else {
|
||||
resultTitleString = strings.Conversation_StoryExpiredMentionTextOutgoing(compactPeerName)
|
||||
}
|
||||
attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -247,13 +247,15 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
if hasPsaInfo {
|
||||
infoWidth += 32.0
|
||||
}
|
||||
var leftOffset: CGFloat = 0.0
|
||||
if let storyData, storyData.isExpired {
|
||||
leftOffset += 34.0 + 6.0
|
||||
}
|
||||
let leftOffset: CGFloat = 0.0
|
||||
infoWidth += leftOffset
|
||||
|
||||
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
var cutout: TextNodeCutout?
|
||||
if let storyData, storyData.isExpired {
|
||||
cutout = TextNodeCutout(topLeft: CGSize(width: 16.0, height: 10.0))
|
||||
}
|
||||
|
||||
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: string, backgroundColor: nil, maximumNumberOfLines: 2, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - credibilityIconWidth - infoWidth, height: constrainedSize.height), alignment: .natural, cutout: cutout, insets: UIEdgeInsets()))
|
||||
|
||||
return (CGSize(width: textLayout.size.width + credibilityIconWidth + infoWidth, height: textLayout.size.height), { width in
|
||||
let node: ChatMessageForwardInfoNode
|
||||
@ -290,8 +292,9 @@ public class ChatMessageForwardInfoNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(presentationData.theme.theme, type: imageType)
|
||||
if let image = expiredStoryIconView.image {
|
||||
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: image.size)
|
||||
if let _ = expiredStoryIconView.image {
|
||||
let imageSize = CGSize(width: 18.0, height: 18.0)
|
||||
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: -1.0, y: -2.0), size: imageSize)
|
||||
}
|
||||
} else if let expiredStoryIconView = node.expiredStoryIconView {
|
||||
expiredStoryIconView.removeFromSuperview()
|
||||
|
@ -14,7 +14,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
public let animationName: String
|
||||
public let title: String
|
||||
public let text: String
|
||||
public let actionTitle: String
|
||||
public let actionTitle: String?
|
||||
public let action: () -> Void
|
||||
|
||||
public init(
|
||||
@ -23,7 +23,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
animationName: String,
|
||||
title: String,
|
||||
text: String,
|
||||
actionTitle: String,
|
||||
actionTitle: String?,
|
||||
action: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
@ -64,7 +64,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
private let animation = ComponentView<Empty>()
|
||||
private let title = ComponentView<Empty>()
|
||||
private let text = ComponentView<Empty>()
|
||||
private let button = ComponentView<Empty>()
|
||||
private var button: ComponentView<Empty>?
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
@ -108,35 +108,54 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: min(300.0, availableSize.width - 16.0 * 2.0), height: 1000.0)
|
||||
)
|
||||
let buttonSize = self.button.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||
Text(text: component.actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
var buttonSize: CGSize?
|
||||
if let actionTitle = component.actionTitle {
|
||||
let button: ComponentView<Empty>
|
||||
if let current = self.button {
|
||||
button = current
|
||||
} else {
|
||||
button = ComponentView()
|
||||
self.button = button
|
||||
}
|
||||
|
||||
buttonSize = button.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: component.theme.list.itemCheckColors.fillColor,
|
||||
foreground: component.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: component.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(id: 0, component: AnyComponent(
|
||||
Text(text: actionTitle, font: Font.semibold(17.0), color: component.theme.list.itemCheckColors.foregroundColor)
|
||||
)),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
component.action()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 240.0, height: 50.0)
|
||||
)
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 240.0, height: 50.0)
|
||||
)
|
||||
} else {
|
||||
if let button = self.button {
|
||||
self.button = nil
|
||||
button.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let animationSpacing: CGFloat = 11.0
|
||||
let titleSpacing: CGFloat = 17.0
|
||||
let buttonSpacing: CGFloat = 17.0
|
||||
|
||||
let totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height + buttonSpacing + buttonSize.height
|
||||
var totalHeight: CGFloat = animationSize.height + animationSpacing + titleSize.height + titleSpacing + textSize.height
|
||||
if let buttonSize {
|
||||
totalHeight += buttonSpacing + buttonSize.height
|
||||
}
|
||||
|
||||
var contentY = floor((availableSize.height - totalHeight) * 0.5)
|
||||
|
||||
@ -161,7 +180,7 @@ public final class EmptyStateIndicatorComponent: Component {
|
||||
transition.setFrame(view: textView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: contentY), size: textSize))
|
||||
contentY += textSize.height + buttonSpacing
|
||||
}
|
||||
if let buttonView = self.button.view {
|
||||
if let buttonSize, let buttonView = self.button?.view {
|
||||
if buttonView.superview == nil {
|
||||
self.addSubview(buttonView)
|
||||
}
|
||||
|
@ -716,7 +716,7 @@ private func notificationPeerExceptionEntries(presentationData: PresentationData
|
||||
index += 1
|
||||
|
||||
if state.storiesMuted != .alwaysOff {
|
||||
entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: "Display Author Name"))
|
||||
entries.append(.displayPreviewsHeader(index: index, theme: presentationData.theme, title: "DISPLAY AUTHOR NAME"))
|
||||
index += 1
|
||||
entries.append(.showSender(index: index, theme: presentationData.theme, strings: presentationData.strings, value: .alwaysOn, selected: state.storiesHideSender == .alwaysOn))
|
||||
index += 1
|
||||
|
@ -631,7 +631,7 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
}
|
||||
|
||||
if let selectedMedia = selectedMedia {
|
||||
if let result = directMediaImageCache.getImage(peer: item.peer, story: story, media: selectedMedia, width: imageWidthSpec, aspectRatio: 0.56, possibleWidths: SparseItemGridBindingImpl.widthSpecs.1, includeBlurred: hasSpoiler, synchronous: synchronous == .full) {
|
||||
if let result = directMediaImageCache.getImage(peer: item.peer, story: story, media: selectedMedia, width: imageWidthSpec, aspectRatio: 0.72, possibleWidths: SparseItemGridBindingImpl.widthSpecs.1, includeBlurred: hasSpoiler || displayItem.blurLayer != nil, synchronous: synchronous == .full) {
|
||||
if let image = result.image {
|
||||
layer.setContents(image)
|
||||
switch synchronous {
|
||||
@ -648,6 +648,11 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
}
|
||||
if let image = result.blurredImage {
|
||||
layer.setSpoilerContents(image)
|
||||
|
||||
if let blurLayer = displayItem.blurLayer {
|
||||
blurLayer.contentsGravity = .resizeAspectFill
|
||||
blurLayer.contents = result.blurredImage?.cgImage
|
||||
}
|
||||
}
|
||||
if let loadSignal = result.loadSignal {
|
||||
layer.disposable?.dispose()
|
||||
@ -702,6 +707,11 @@ private final class SparseItemGridBindingImpl: SparseItemGridBinding {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let displayItem, let blurLayer = displayItem.blurLayer {
|
||||
blurLayer.contentsGravity = .resizeAspectFill
|
||||
blurLayer.contents = result.blurredImage?.cgImage
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1588,7 +1598,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
}
|
||||
|
||||
var headerText: String?
|
||||
if strongSelf.isArchive {
|
||||
if strongSelf.isArchive && !mappedItems.isEmpty {
|
||||
//TODO:localize
|
||||
headerText = "Only you can see archived stories unless you choose to save them to your profile."
|
||||
}
|
||||
@ -1876,7 +1886,7 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
|
||||
transition.updateFrame(node: self.contextGestureContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)))
|
||||
|
||||
if let items = self.items, items.items.isEmpty, items.count == 0, !self.isArchive {
|
||||
if let items = self.items, items.items.isEmpty, items.count == 0 {
|
||||
let emptyStateView: ComponentView<Empty>
|
||||
var emptyStateTransition = Transition(transition)
|
||||
if let current = self.emptyStateView {
|
||||
@ -1893,9 +1903,9 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
context: self.context,
|
||||
theme: presentationData.theme,
|
||||
animationName: "StoryListEmpty",
|
||||
title: "No saved stories",
|
||||
text: "Open the Archive to select stories you\nwant to be displayed in your profile.",
|
||||
actionTitle: "Open Archive",
|
||||
title: self.isArchive ? "No Archived Stories" : "No saved stories",
|
||||
text: self.isArchive ? "Upload a new story to view it here" : "Open the Archive to select stories you\nwant to be displayed in your profile.",
|
||||
actionTitle: self.isArchive ? nil : "Open Archive",
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
@ -1948,10 +1958,11 @@ public final class PeerInfoStoryPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
|
||||
fixedItemHeight = nil
|
||||
}
|
||||
|
||||
let fixedItemAspect: CGFloat? = 9.0 / 16.0
|
||||
let fixedItemAspect: CGFloat? = 0.72
|
||||
|
||||
let gridTopInset = topInset
|
||||
|
||||
self.itemGrid.pinchEnabled = items.count > 2
|
||||
self.itemGrid.update(size: size, insets: UIEdgeInsets(top: gridTopInset, left: sideInset, bottom: bottomInset, right: sideInset), useSideInsets: !isList, scrollIndicatorInsets: UIEdgeInsets(top: 0.0, left: sideInset, bottom: bottomInset, right: sideInset), lockScrollingAtTop: isScrollingLockedAtTop, fixedItemHeight: fixedItemHeight, fixedItemAspect: fixedItemAspect, items: items, theme: self.itemGridBinding.chatPresentationData.theme.theme, synchronous: wasFirstTime ? .full : .none)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
public let theme: PresentationTheme
|
||||
public let activeLineWidth: CGFloat
|
||||
public let inactiveLineWidth: CGFloat
|
||||
public let isGlassBackground: Bool
|
||||
public let counters: Counters?
|
||||
|
||||
public init(
|
||||
@ -28,6 +29,7 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
theme: PresentationTheme,
|
||||
activeLineWidth: CGFloat,
|
||||
inactiveLineWidth: CGFloat,
|
||||
isGlassBackground: Bool = false,
|
||||
counters: Counters?
|
||||
) {
|
||||
self.hasUnseen = hasUnseen
|
||||
@ -35,6 +37,7 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
self.theme = theme
|
||||
self.activeLineWidth = activeLineWidth
|
||||
self.inactiveLineWidth = inactiveLineWidth
|
||||
self.isGlassBackground = isGlassBackground
|
||||
self.counters = counters
|
||||
}
|
||||
|
||||
@ -54,6 +57,9 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
if lhs.inactiveLineWidth != rhs.inactiveLineWidth {
|
||||
return false
|
||||
}
|
||||
if lhs.isGlassBackground != rhs.isGlassBackground {
|
||||
return false
|
||||
}
|
||||
if lhs.counters != rhs.counters {
|
||||
return false
|
||||
}
|
||||
@ -90,7 +96,7 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
} else {
|
||||
lineWidth = component.inactiveLineWidth
|
||||
}
|
||||
let maxOuterInset = component.activeLineWidth + component.activeLineWidth
|
||||
let maxOuterInset = component.activeLineWidth + lineWidth
|
||||
diameter = availableSize.width + maxOuterInset * 2.0
|
||||
let imageDiameter = availableSize.width + ceilToScreenPixels(maxOuterInset) * 2.0
|
||||
|
||||
@ -112,10 +118,14 @@ public final class AvatarStoryIndicatorComponent: Component {
|
||||
]
|
||||
}
|
||||
|
||||
if component.theme.overallDarkAppearance {
|
||||
inactiveColors = [component.theme.rootController.tabBar.textColor.cgColor, component.theme.rootController.tabBar.textColor.cgColor]
|
||||
if component.isGlassBackground {
|
||||
inactiveColors = [UIColor(white: 1.0, alpha: 0.2).cgColor, UIColor(white: 1.0, alpha: 0.2).cgColor]
|
||||
} else {
|
||||
inactiveColors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||
if component.theme.overallDarkAppearance {
|
||||
inactiveColors = [component.theme.rootController.tabBar.textColor.cgColor, component.theme.rootController.tabBar.textColor.cgColor]
|
||||
} else {
|
||||
inactiveColors = [UIColor(rgb: 0xD8D8E1).cgColor, UIColor(rgb: 0xD8D8E1).cgColor]
|
||||
}
|
||||
}
|
||||
|
||||
var locations: [CGFloat] = [0.0, 1.0]
|
||||
|
@ -95,12 +95,12 @@ final class MediaNavigationStripComponent: Component {
|
||||
|
||||
let spacing: CGFloat = 3.0
|
||||
let itemHeight: CGFloat = 2.0
|
||||
let minItemWidth: CGFloat = 10.0
|
||||
let minItemWidth: CGFloat = 2.0
|
||||
|
||||
var validIndices: [Int] = []
|
||||
if component.count != 0 {
|
||||
var idealItemWidth: CGFloat = (availableSize.width - CGFloat(component.count - 1) * spacing) / CGFloat(component.count)
|
||||
idealItemWidth = round(idealItemWidth)
|
||||
idealItemWidth = floor(idealItemWidth)
|
||||
|
||||
let itemWidth: CGFloat
|
||||
if idealItemWidth < minItemWidth {
|
||||
|
@ -10,17 +10,15 @@ final class StoryAuthorInfoComponent: Component {
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer?
|
||||
let timestamp: Int32
|
||||
let isEdited: Bool
|
||||
|
||||
init(context: AccountContext, peer: EnginePeer?, timestamp: Int32) {
|
||||
init(context: AccountContext, peer: EnginePeer?, timestamp: Int32, isEdited: Bool) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.timestamp = timestamp
|
||||
self.isEdited = isEdited
|
||||
}
|
||||
|
||||
convenience init(context: AccountContext, message: EngineMessage) {
|
||||
self.init(context: context, peer: message.author, timestamp: message.timestamp)
|
||||
}
|
||||
|
||||
static func ==(lhs: StoryAuthorInfoComponent, rhs: StoryAuthorInfoComponent) -> Bool {
|
||||
if lhs.context !== rhs.context {
|
||||
return false
|
||||
@ -30,6 +28,9 @@ final class StoryAuthorInfoComponent: Component {
|
||||
}
|
||||
if lhs.timestamp != rhs.timestamp {
|
||||
return false
|
||||
}
|
||||
if lhs.isEdited != rhs.isEdited {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@ -69,7 +70,12 @@ final class StoryAuthorInfoComponent: Component {
|
||||
}
|
||||
|
||||
let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)
|
||||
let subtitle = stringForRelativeActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, relativeTimestamp: component.timestamp, relativeTo: timestamp)
|
||||
var subtitle = stringForRelativeActivityTimestamp(strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, relativeTimestamp: component.timestamp, relativeTo: timestamp)
|
||||
|
||||
if component.isEdited {
|
||||
subtitle.append(" • ")
|
||||
subtitle.append("edited")
|
||||
}
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
|
@ -139,9 +139,11 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
isPublic: item.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: item.isCloseFriends,
|
||||
isForwardingDisabled: item.isForwardingDisabled
|
||||
isForwardingDisabled: item.isForwardingDisabled,
|
||||
isEdited: item.isEdited
|
||||
)
|
||||
}
|
||||
var totalCount = peerStoryItemsView.items.count
|
||||
if peerId == context.account.peerId, let stateView = views.views[PostboxViewKey.storiesState(key: .local)] as? StoryStatesView, let localState = stateView.value?.get(Stories.LocalState.self) {
|
||||
for item in localState.items {
|
||||
mappedItems.append(EngineStoryItem(
|
||||
@ -158,8 +160,10 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
isPublic: false,
|
||||
isPending: true,
|
||||
isCloseFriends: false,
|
||||
isForwardingDisabled: false
|
||||
isForwardingDisabled: false,
|
||||
isEdited: false
|
||||
))
|
||||
totalCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -263,7 +267,7 @@ public final class StoryContentContextImpl: StoryContentContext {
|
||||
peerId: peer.id,
|
||||
storyItem: mappedItem
|
||||
),
|
||||
totalCount: mappedItems.count,
|
||||
totalCount: totalCount,
|
||||
previousItemId: previousItemId,
|
||||
nextItemId: nextItemId,
|
||||
allItems: allItems
|
||||
@ -982,7 +986,8 @@ public final class SingleStoryContentContextImpl: StoryContentContext {
|
||||
isPublic: itemValue.isPublic,
|
||||
isPending: false,
|
||||
isCloseFriends: itemValue.isCloseFriends,
|
||||
isForwardingDisabled: itemValue.isForwardingDisabled
|
||||
isForwardingDisabled: itemValue.isForwardingDisabled,
|
||||
isEdited: itemValue.isEdited
|
||||
)
|
||||
|
||||
let mainItem = StoryContentItem(
|
||||
|
@ -516,8 +516,10 @@ private final class StoryContainerScreenComponent: Component {
|
||||
}
|
||||
|
||||
if subview is ItemSetView {
|
||||
if let result = subview.hitTest(self.convert(point, to: subview), with: event) {
|
||||
return result
|
||||
if let component = self.component, let stateValue = component.content.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], itemSetView === subview {
|
||||
if let result = subview.hitTest(self.convert(point, to: subview), with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let result = subview.hitTest(self.convert(self.convert(point, to: subview), to: subview), with: event) {
|
||||
@ -846,7 +848,7 @@ private final class StoryContainerScreenComponent: Component {
|
||||
}
|
||||
|
||||
if let stateValue = component.content.stateValue, let slice = stateValue.slice {
|
||||
if case .next = direction, slice.nextItemId == nil {
|
||||
if case .next = direction, slice.nextItemId == nil, (slice.item.position == nil || slice.item.position == slice.totalCount - 1) {
|
||||
if stateValue.nextSlice == nil {
|
||||
environment.controller()?.dismiss()
|
||||
} else {
|
||||
|
@ -217,7 +217,7 @@ final class StoryContentCaptionComponent: Component {
|
||||
let edgeDistanceFraction = edgeDistance / 7.0
|
||||
transition.setAlpha(view: self.scrollFullMaskView, alpha: 1.0 - edgeDistanceFraction)
|
||||
|
||||
let shadowOverflow: CGFloat = 26.0
|
||||
let shadowOverflow: CGFloat = 36.0
|
||||
let shadowFrame = CGRect(origin: CGPoint(x: 0.0, y: -self.scrollView.contentOffset.y + itemLayout.containerSize.height - itemLayout.visibleTextHeight - itemLayout.verticalInset - shadowOverflow), size: CGSize(width: itemLayout.containerSize.width, height: itemLayout.visibleTextHeight + itemLayout.verticalInset + shadowOverflow))
|
||||
transition.setFrame(layer: self.shadowGradientLayer, frame: shadowFrame)
|
||||
transition.setFrame(layer: self.shadowPlainLayer, frame: CGRect(origin: CGPoint(x: shadowFrame.minX, y: shadowFrame.maxY), size: CGSize(width: shadowFrame.width, height: self.scrollView.contentSize.height + 1000.0)))
|
||||
@ -364,7 +364,9 @@ final class StoryContentCaptionComponent: Component {
|
||||
attributedString: attributedText,
|
||||
maximumNumberOfLines: 0,
|
||||
truncationType: .end,
|
||||
constrainedSize: textContainerSize
|
||||
constrainedSize: textContainerSize,
|
||||
textShadowColor: UIColor(white: 0.0, alpha: 0.25),
|
||||
textShadowBlur: 4.0
|
||||
))
|
||||
|
||||
let maxHeight: CGFloat = 50.0
|
||||
|
@ -139,7 +139,7 @@ final class StoryItemContentComponent: Component {
|
||||
enableSound: true,
|
||||
beginWithAmbientSound: environment.sharedState.useAmbientMode,
|
||||
useLargeThumbnail: false,
|
||||
autoFetchFullSizeThumbnail: true,
|
||||
autoFetchFullSizeThumbnail: false,
|
||||
tempFilePath: nil,
|
||||
captureProtected: component.item.isForwardingDisabled,
|
||||
hintDimensions: file.dimensions?.cgSize,
|
||||
@ -431,7 +431,7 @@ final class StoryItemContentComponent: Component {
|
||||
onlyFullSize: false,
|
||||
useLargeThumbnail: false,
|
||||
synchronousLoad: synchronousLoad,
|
||||
autoFetchFullSizeThumbnail: true,
|
||||
autoFetchFullSizeThumbnail: false,
|
||||
overlayColor: nil,
|
||||
nilForEmptyResult: false,
|
||||
useMiniThumbnailIfAvailable: false,
|
||||
|
@ -1772,7 +1772,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.navigateToPeer(peer: peer)
|
||||
self.navigateToPeer(peer: peer, chat: false)
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
@ -2082,7 +2082,7 @@ 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))
|
||||
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 centerInfoItem = self.centerInfoItem, centerInfoItem.component == centerInfoComponent {
|
||||
currentCenterInfoItem = centerInfoItem
|
||||
} else {
|
||||
@ -2106,7 +2106,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.navigateToPeer(peer: component.slice.peer)
|
||||
if component.slice.peer.id == component.context.account.peerId {
|
||||
self.navigateToMyStories()
|
||||
} else {
|
||||
self.navigateToPeer(peer: component.slice.peer, chat: false)
|
||||
}
|
||||
})),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: contentFrame.width, height: 44.0)
|
||||
@ -2136,7 +2140,11 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
self.navigateToPeer(peer: component.slice.peer)
|
||||
if component.slice.peer.id == component.context.account.peerId {
|
||||
self.navigateToMyStories()
|
||||
} else {
|
||||
self.navigateToPeer(peer: component.slice.peer, chat: false)
|
||||
}
|
||||
})),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 32.0, height: 32.0)
|
||||
@ -2433,7 +2441,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
presentationData: presentationData,
|
||||
content: .sticker(context: context, file: animation, loop: false, title: nil, text: "Reaction Sent.", undoText: "View in Chat", customAction: { [weak self] in
|
||||
if let messageId = messageIds.first, let self {
|
||||
self.navigateToPeer(peer: peer, messageId: messageId)
|
||||
self.navigateToPeer(peer: peer, chat: true, messageId: messageId)
|
||||
}
|
||||
}),
|
||||
elevatedLayout: false,
|
||||
@ -2708,7 +2716,7 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
})
|
||||
}
|
||||
|
||||
func navigateToPeer(peer: EnginePeer, messageId: EngineMessage.Id? = nil) {
|
||||
func navigateToMyStories() {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
@ -2718,8 +2726,34 @@ public final class StoryItemSetContainerComponent: Component {
|
||||
guard let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
if let messageId {
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: false, timecode: nil), keepStack: .always, animated: true, pushController: { [weak controller, weak navigationController] chatController, animated, completion in
|
||||
|
||||
let targetController = component.context.sharedContext.makeMyStoriesController(context: component.context, isArchive: false)
|
||||
|
||||
var viewControllers = navigationController.viewControllers
|
||||
if let index = viewControllers.firstIndex(where: { $0 === controller }) {
|
||||
viewControllers.insert(targetController, at: index)
|
||||
} else {
|
||||
viewControllers.append(targetController)
|
||||
}
|
||||
navigationController.setViewControllers(viewControllers, animated: true)
|
||||
}
|
||||
|
||||
func navigateToPeer(peer: EnginePeer, chat: Bool, messageId: EngineMessage.Id? = nil) {
|
||||
guard let component = self.component else {
|
||||
return
|
||||
}
|
||||
guard let controller = component.controller() as? StoryContainerScreen else {
|
||||
return
|
||||
}
|
||||
guard let navigationController = controller.navigationController as? NavigationController else {
|
||||
return
|
||||
}
|
||||
if messageId != nil || chat {
|
||||
var subject: ChatControllerSubject?
|
||||
if let messageId {
|
||||
subject = .message(id: .id(messageId), highlight: false, timecode: nil)
|
||||
}
|
||||
component.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: component.context, chatLocation: .peer(peer), subject: subject, keepStack: .always, animated: true, pushController: { [weak controller, weak navigationController] chatController, animated, completion in
|
||||
guard let controller, let navigationController else {
|
||||
return
|
||||
}
|
||||
|
@ -344,7 +344,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
animateInAsReplacement: false,
|
||||
action: { [weak view] action in
|
||||
if case .undo = action, let messageId = messageIds.first {
|
||||
view?.navigateToPeer(peer: peer, messageId: messageId)
|
||||
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -386,7 +386,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
animateInAsReplacement: false,
|
||||
action: { [weak view] action in
|
||||
if case .undo = action, let messageId = messageIds.first {
|
||||
view?.navigateToPeer(peer: peer, messageId: messageId)
|
||||
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -448,7 +448,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
animateInAsReplacement: false,
|
||||
action: { [weak view] action in
|
||||
if case .undo = action, let messageId = messageIds.first {
|
||||
view?.navigateToPeer(peer: peer, messageId: messageId)
|
||||
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -698,7 +698,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
|
||||
let shareController = ShareController(
|
||||
context: component.context,
|
||||
subject: .media(AnyMediaReference.standalone(media: TelegramMediaStory(storyId: StoryId(peerId: peerId, id: focusedItem.storyItem.id)))),
|
||||
subject: .media(AnyMediaReference.standalone(media: TelegramMediaStory(storyId: StoryId(peerId: peerId, id: focusedItem.storyItem.id), isMention: false))),
|
||||
preferredAction: preferredAction ?? .default,
|
||||
externalShare: false,
|
||||
immediateExternalShare: false,
|
||||
@ -2134,7 +2134,7 @@ final class StoryItemSetContainerSendMessage {
|
||||
animateInAsReplacement: false,
|
||||
action: { [weak view] action in
|
||||
if case .undo = action, let messageId = messageIds.first {
|
||||
view?.navigateToPeer(peer: peer, messageId: messageId)
|
||||
view?.navigateToPeer(peer: peer, chat: true, messageId: messageId)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -447,8 +447,9 @@ public final class StoryPeerListComponent: Component {
|
||||
itemView.updateIsPreviewing(isPreviewing: peerId == itemId)
|
||||
|
||||
if component.unlocked && peerId == itemId {
|
||||
if !self.scrollView.bounds.intersects(itemView.frame.insetBy(dx: 20.0, dy: 0.0)) {
|
||||
self.scrollView.scrollRectToVisible(itemView.frame.insetBy(dx: -40.0, dy: 0.0), animated: false)
|
||||
let itemFrame = itemView.frame.offsetBy(dx: self.scrollView.bounds.minX, dy: 0.0)
|
||||
if !self.scrollView.bounds.intersects(itemFrame.insetBy(dx: 20.0, dy: 0.0)) {
|
||||
self.scrollView.scrollRectToVisible(itemFrame.insetBy(dx: -40.0, dy: 0.0), animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -465,7 +466,7 @@ public final class StoryPeerListComponent: Component {
|
||||
return nil
|
||||
}
|
||||
if let visibleItem = self.visibleItems[peerId], let itemView = visibleItem.view.view as? StoryPeerListItemComponent.View {
|
||||
if !self.scrollView.bounds.intersects(itemView.frame) {
|
||||
if !self.bounds.intersects(itemView.frame) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ContextStories.svg",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
3
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/ContextStories.svg
vendored
Normal file
3
submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/ContextStories.svg
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.7635 4.66876C11.842 4.66627 11.9208 4.66502 12 4.66502C12.2275 4.66502 12.4524 4.67536 12.6744 4.69557C13.0401 4.72887 13.3636 4.45936 13.3969 4.09361C13.4302 3.72785 13.1607 3.40435 12.795 3.37105C12.533 3.3472 12.2678 3.33502 12 3.33502C11.9012 3.33502 11.8028 3.33667 11.7048 3.33995C9.50354 3.41363 7.51029 4.30838 6.02163 5.7277C5.90873 5.83534 5.79874 5.94599 5.69177 6.05953C4.28989 7.54763 3.4087 9.53174 3.33938 11.7207C3.33644 11.8134 3.33496 11.9066 3.33496 12C3.33496 12.0982 3.33659 12.196 3.33983 12.2934C3.41063 14.4212 4.2486 16.3547 5.58597 17.8262C5.78988 18.0505 6.00541 18.2641 6.23159 18.4661C7.6731 19.7529 9.54763 20.5649 11.6095 20.6564C11.7389 20.6621 11.8691 20.665 12 20.665C12.2678 20.665 12.533 20.6528 12.795 20.629C13.1607 20.5957 13.4302 20.2722 13.3969 19.9064C13.3636 19.5407 13.0401 19.2712 12.6744 19.3045C12.4524 19.3247 12.2275 19.335 12 19.335C11.8813 19.335 11.7633 19.3322 11.646 19.3266C9.88642 19.243 8.28926 18.5394 7.06807 17.4295C6.87365 17.2528 6.68876 17.0658 6.51426 16.8694C5.41246 15.629 4.72495 14.0124 4.6687 12.2365C4.66621 12.158 4.66496 12.0791 4.66496 12C4.66496 11.9159 4.66638 11.8322 4.66918 11.7488C4.73083 9.91731 5.4639 8.25606 6.63004 7.00336C6.76152 6.86212 6.89851 6.72607 7.04066 6.59556C8.29233 5.44639 9.94435 4.72638 11.7635 4.66876ZM17.5392 5.33648C17.2569 5.10156 16.8376 5.13998 16.6027 5.4223C16.3677 5.70461 16.4062 6.12391 16.6885 6.35882C17.0343 6.64663 17.3534 6.96564 17.6412 7.31151C17.8761 7.59382 18.2954 7.63225 18.5777 7.39733C18.86 7.16241 18.8984 6.74311 18.6635 6.4608C18.3238 6.05261 17.9474 5.67614 17.5392 5.33648ZM20.6289 11.205C20.5956 10.8393 20.2721 10.5698 19.9064 10.6031C19.5406 10.6364 19.2711 10.9599 19.3044 11.3256C19.3246 11.5475 19.335 11.7725 19.335 12C19.335 12.2276 19.3246 12.4525 19.3044 12.6744C19.2711 13.0402 19.5406 13.3637 19.9064 13.397C20.2721 13.4303 20.5956 13.1608 20.6289 12.795C20.6528 12.5331 20.665 12.2679 20.665 12C20.665 11.7322 20.6528 11.467 20.6289 11.205ZM18.6635 17.5392C18.8984 17.2569 18.86 16.8376 18.5777 16.6027C18.2954 16.3678 17.8761 16.4062 17.6412 16.6885C17.3534 17.0344 17.0343 17.3534 16.6885 17.6412C16.4062 17.8761 16.3677 18.2954 16.6027 18.5777C16.8376 18.8601 17.2569 18.8985 17.5392 18.6636C17.9474 18.3239 18.3238 17.9474 18.6635 17.5392Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "timer.pdf",
|
||||
"filename" : "ic_exipred.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
120
submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/ic_exipred.pdf
vendored
Normal file
120
submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/ic_exipred.pdf
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 1.899414 2.555176 cm
|
||||
1.000000 1.000000 1.000000 scn
|
||||
0.629534 10.881271 m
|
||||
0.362801 10.952742 0.088632 10.794451 0.017161 10.527718 c
|
||||
-0.054310 10.260984 0.103982 9.986816 0.370715 9.915345 c
|
||||
1.336640 9.656527 l
|
||||
1.603373 9.585055 1.877542 9.743346 1.949013 10.010079 c
|
||||
2.020484 10.276813 1.862192 10.550982 1.595459 10.622452 c
|
||||
0.629534 10.881271 l
|
||||
h
|
||||
4.493724 9.846077 m
|
||||
4.226991 9.917548 3.952822 9.759256 3.881351 9.492523 c
|
||||
3.809880 9.225790 3.968172 8.951622 4.234905 8.880151 c
|
||||
5.200830 8.621332 l
|
||||
5.467563 8.549862 5.741732 8.708153 5.813203 8.974886 c
|
||||
5.884674 9.241618 5.726382 9.515787 5.459650 9.587257 c
|
||||
4.493724 9.846077 l
|
||||
h
|
||||
2.139100 12.649343 m
|
||||
2.405833 12.720814 2.680002 12.562522 2.751473 12.295790 c
|
||||
3.010292 11.329864 l
|
||||
3.081763 11.063130 2.923471 10.788962 2.656739 10.717491 c
|
||||
2.390005 10.646021 2.115837 10.804312 2.044366 11.071045 c
|
||||
1.785547 12.036970 l
|
||||
1.714076 12.303703 1.872367 12.577872 2.139100 12.649343 c
|
||||
h
|
||||
3.622073 11.165578 m
|
||||
3.426811 10.970316 3.426811 10.653732 3.622073 10.458471 c
|
||||
3.817335 10.263208 4.133917 10.263208 4.329180 10.458471 c
|
||||
5.036287 11.165578 l
|
||||
5.231549 11.360840 5.231549 11.677423 5.036287 11.872684 c
|
||||
4.841024 12.067946 4.524442 12.067946 4.329180 11.872684 c
|
||||
3.622073 11.165578 l
|
||||
h
|
||||
3.015071 8.419374 m
|
||||
2.921972 8.755319 3.007470 9.130381 3.271566 9.394478 c
|
||||
3.466828 9.589739 3.466828 9.906322 3.271566 10.101583 c
|
||||
3.076304 10.296846 2.759721 10.296846 2.564459 10.101583 c
|
||||
1.905519 9.442644 1.802501 8.438299 2.255404 7.671359 c
|
||||
2.210943 7.626899 l
|
||||
1.820419 7.236375 1.820419 6.603209 2.210943 6.212686 c
|
||||
2.416687 6.006942 l
|
||||
1.799674 4.550779 2.084684 2.803301 3.271717 1.616267 c
|
||||
4.833814 0.054171 7.366474 0.054171 8.928571 1.616267 c
|
||||
10.490668 3.178365 10.490668 5.711025 8.928571 7.273122 c
|
||||
7.741580 8.460113 5.994184 8.745144 4.538052 8.128218 c
|
||||
4.332264 8.334005 l
|
||||
3.972641 8.693628 3.407257 8.722085 3.015071 8.419374 c
|
||||
h
|
||||
4.686750 3.030611 m
|
||||
3.905701 3.811659 3.905701 5.077989 4.686750 5.859038 c
|
||||
4.882012 6.054300 4.882012 6.370883 4.686750 6.566144 c
|
||||
4.491488 6.761407 4.174905 6.761406 3.979643 6.566144 c
|
||||
2.808070 5.394572 2.808070 3.495077 3.979643 2.323503 c
|
||||
4.174905 2.128242 4.491488 2.128242 4.686750 2.323503 c
|
||||
4.882012 2.518766 4.882012 2.835348 4.686750 3.030611 c
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
2317
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 16.000000 16.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000002407 00000 n
|
||||
0000002430 00000 n
|
||||
0000002603 00000 n
|
||||
0000002677 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
2736
|
||||
%%EOF
|
@ -1,95 +0,0 @@
|
||||
%PDF-1.7
|
||||
|
||||
1 0 obj
|
||||
<< >>
|
||||
endobj
|
||||
|
||||
2 0 obj
|
||||
<< /Length 3 0 R >>
|
||||
stream
|
||||
/DeviceRGB CS
|
||||
/DeviceRGB cs
|
||||
q
|
||||
1.000000 0.000000 -0.000000 1.000000 11.386475 7.000000 cm
|
||||
0.000000 0.000000 0.000000 scn
|
||||
3.109776 20.000000 m
|
||||
1.179763 20.000000 -0.283886 18.259834 0.046805 16.358360 c
|
||||
0.156231 15.729159 0.419039 15.136623 0.811980 14.633168 c
|
||||
3.413476 11.300000 l
|
||||
3.439425 11.257834 l
|
||||
3.914118 10.486457 3.914118 9.513542 3.439425 8.742166 c
|
||||
3.413476 8.700000 l
|
||||
0.811979 5.366831 l
|
||||
0.419039 4.863377 0.156231 4.270840 0.046805 3.641638 c
|
||||
-0.283886 1.740166 1.179765 0.000000 3.109779 0.000000 c
|
||||
8.117176 0.000000 l
|
||||
10.047190 0.000000 11.510839 1.740166 11.180147 3.641638 c
|
||||
11.070721 4.270841 10.807913 4.863377 10.414972 5.366832 c
|
||||
7.813476 8.700000 l
|
||||
7.787528 8.742166 l
|
||||
7.312835 9.513542 7.312835 10.486458 7.787528 11.257834 c
|
||||
7.813476 11.300000 l
|
||||
10.414973 14.633169 l
|
||||
10.807914 15.136623 11.070721 15.729160 11.180147 16.358362 c
|
||||
11.510839 18.259834 10.047188 20.000000 8.117173 20.000000 c
|
||||
3.109776 20.000000 l
|
||||
h
|
||||
7.428961 3.624302 m
|
||||
6.183511 4.350814 4.643443 4.350814 3.397992 3.624302 c
|
||||
1.891497 2.745512 l
|
||||
1.538821 2.539783 1.684751 2.000000 2.093044 2.000000 c
|
||||
8.733909 2.000000 l
|
||||
9.142202 2.000000 9.288133 2.539783 8.935456 2.745512 c
|
||||
7.428961 3.624302 l
|
||||
h
|
||||
f*
|
||||
n
|
||||
Q
|
||||
|
||||
endstream
|
||||
endobj
|
||||
|
||||
3 0 obj
|
||||
1194
|
||||
endobj
|
||||
|
||||
4 0 obj
|
||||
<< /Annots []
|
||||
/Type /Page
|
||||
/MediaBox [ 0.000000 0.000000 34.000000 34.000000 ]
|
||||
/Resources 1 0 R
|
||||
/Contents 2 0 R
|
||||
/Parent 5 0 R
|
||||
>>
|
||||
endobj
|
||||
|
||||
5 0 obj
|
||||
<< /Kids [ 4 0 R ]
|
||||
/Count 1
|
||||
/Type /Pages
|
||||
>>
|
||||
endobj
|
||||
|
||||
6 0 obj
|
||||
<< /Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj
|
||||
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
0000000034 00000 n
|
||||
0000001284 00000 n
|
||||
0000001307 00000 n
|
||||
0000001480 00000 n
|
||||
0000001554 00000 n
|
||||
trailer
|
||||
<< /ID [ (some) (id) ]
|
||||
/Root 6 0 R
|
||||
/Size 7
|
||||
>>
|
||||
startxref
|
||||
1613
|
||||
%%EOF
|
@ -318,7 +318,7 @@ private final class ChatHistoryTransactionOpaqueState {
|
||||
}
|
||||
}
|
||||
|
||||
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, alwaysDisplayTranscribeButton: ChatMessageItemAssociatedData.DisplayTranscribeButton, accountPeer: EnginePeer?, topicAuthorId: EnginePeer.Id?, hasBots: Bool, translateToLanguage: String?) -> ChatMessageItemAssociatedData {
|
||||
private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHistoryView, automaticDownloadNetworkType: MediaAutoDownloadNetworkType, animatedEmojiStickers: [String: [StickerPackItem]], additionalAnimatedEmojiStickers: [String: [Int: StickerPackItem]], subject: ChatControllerSubject?, currentlyPlayingMessageId: MessageIndex?, isCopyProtectionEnabled: Bool, availableReactions: AvailableReactions?, defaultReaction: MessageReaction.Reaction?, isPremium: Bool, alwaysDisplayTranscribeButton: ChatMessageItemAssociatedData.DisplayTranscribeButton, accountPeer: EnginePeer?, topicAuthorId: EnginePeer.Id?, hasBots: Bool, translateToLanguage: String?, maxReadStoryId: Int32?) -> ChatMessageItemAssociatedData {
|
||||
var automaticDownloadPeerId: EnginePeer.Id?
|
||||
var automaticMediaDownloadPeerType: MediaAutoDownloadPeerType = .channel
|
||||
var contactsPeerIds: Set<PeerId> = Set()
|
||||
@ -372,7 +372,7 @@ private func extractAssociatedData(chatLocation: ChatLocation, view: MessageHist
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
private extension ChatHistoryLocationInput {
|
||||
@ -1086,6 +1086,24 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
self.chatHasBotsPromise.get()
|
||||
)
|
||||
|
||||
let maxReadStoryId: Signal<Int32?, NoError>
|
||||
if let peerId = chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser {
|
||||
maxReadStoryId = context.account.postbox.combinedView(keys: [PostboxViewKey.storiesState(key: .peer(peerId))])
|
||||
|> map { views -> Int32? in
|
||||
guard let view = views.views[PostboxViewKey.storiesState(key: .peer(peerId))] as? StoryStatesView else {
|
||||
return nil
|
||||
}
|
||||
if let state = view.value?.get(Stories.PeerState.self) {
|
||||
return state.maxReadId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
maxReadStoryId = .single(nil)
|
||||
}
|
||||
|
||||
let historyViewTransitionDisposable = combineLatest(queue: messageViewQueue,
|
||||
historyViewUpdate,
|
||||
self.chatPresentationDataPromise.get(),
|
||||
@ -1103,8 +1121,9 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
promises,
|
||||
topicAuthorId,
|
||||
self.allAdMessagesPromise.get(),
|
||||
translationState
|
||||
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, allAdMessages, translationState in
|
||||
translationState,
|
||||
maxReadStoryId
|
||||
).start(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, allAdMessages, translationState, maxReadStoryId in
|
||||
let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots) = promises
|
||||
|
||||
func applyHole() {
|
||||
@ -1260,7 +1279,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
|
||||
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)
|
||||
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)
|
||||
|
||||
let filteredEntries = chatHistoryEntriesForView(
|
||||
location: chatLocation,
|
||||
|
@ -38,6 +38,8 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
private var videoContent: NativeVideoContent?
|
||||
private var videoStartTimestamp: Double?
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
private var leadingIconView: UIImageView?
|
||||
|
||||
private var cachedMaskBackgroundImage: (CGPoint, UIImage, [CGRect])?
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
@ -167,6 +169,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
let attributedString = attributedServiceMessageString(theme: item.presentationData.theme, strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, dateTimeFormat: item.presentationData.dateTimeFormat, message: item.message, accountPeerId: item.context.account.peerId, forForumOverview: forForumOverview)
|
||||
|
||||
var image: TelegramMediaImage?
|
||||
var story: TelegramMediaStory?
|
||||
for media in item.message.media {
|
||||
if let action = media as? TelegramMediaAction {
|
||||
switch action.action {
|
||||
@ -175,12 +178,21 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
default:
|
||||
break
|
||||
}
|
||||
} else if let media = media as? TelegramMediaStory {
|
||||
story = media
|
||||
}
|
||||
}
|
||||
|
||||
let imageSize = CGSize(width: 212.0, height: 212.0)
|
||||
|
||||
let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
var updatedAttributedString = attributedString
|
||||
if story != nil, let attributedString {
|
||||
let mutableString = NSMutableAttributedString(attributedString: attributedString)
|
||||
mutableString.insert(NSAttributedString(string: " ", font: Font.regular(13.0), textColor: .clear), at: 0)
|
||||
updatedAttributedString = mutableString
|
||||
}
|
||||
|
||||
let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: updatedAttributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var labelRects = labelLayout.linesRects()
|
||||
if labelRects.count > 1 {
|
||||
@ -306,6 +318,28 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode {
|
||||
))
|
||||
|
||||
let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: image != nil ? 2 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size)
|
||||
|
||||
if story != nil {
|
||||
let leadingIconView: UIImageView
|
||||
if let current = strongSelf.leadingIconView {
|
||||
leadingIconView = current
|
||||
} else {
|
||||
leadingIconView = UIImageView()
|
||||
strongSelf.leadingIconView = leadingIconView
|
||||
strongSelf.view.addSubview(leadingIconView)
|
||||
}
|
||||
|
||||
leadingIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(item.presentationData.theme.theme, type: .free)
|
||||
|
||||
if let lineRect = labelLayout.linesRects().first, let iconImage = leadingIconView.image {
|
||||
let iconSize = iconImage.size
|
||||
leadingIconView.frame = CGRect(origin: CGPoint(x: lineRect.minX + labelFrame.minX - 1.0, y: labelFrame.minY), size: iconSize)
|
||||
}
|
||||
} else if let leadingIconView = strongSelf.leadingIconView {
|
||||
strongSelf.leadingIconView = nil
|
||||
leadingIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
strongSelf.labelNode.textNode.frame = labelFrame
|
||||
strongSelf.backgroundColorNode.backgroundColor = selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
|
||||
|
||||
|
@ -129,12 +129,17 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
}
|
||||
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||
} else if let story = media as? TelegramMediaStory {
|
||||
if let forwardInfo = message.forwardInfo, forwardInfo.flags.contains(.isImported), message.text.isEmpty {
|
||||
messageWithCaptionToAdd = (message, itemAttributes)
|
||||
}
|
||||
if let storyItem = message.associatedStories[story.storyId], storyItem.data.isEmpty {
|
||||
if story.isMention {
|
||||
if let storyItem = message.associatedStories[story.storyId], storyItem.data.isEmpty {
|
||||
result.append((message, ChatMessageActionBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
} else {
|
||||
result.append((message, ChatMessageStoryMentionContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
}
|
||||
} else {
|
||||
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||
if let storyItem = message.associatedStories[story.storyId], storyItem.data.isEmpty {
|
||||
} else {
|
||||
result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default)))
|
||||
}
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let isVideo = file.isVideo || (file.isAnimated && file.dimensions != nil)
|
||||
@ -229,7 +234,14 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([
|
||||
|
||||
inner: for media in message.media {
|
||||
if let webpage = media as? TelegramMediaWebpage {
|
||||
if case .Loaded = webpage.content {
|
||||
if case let .Loaded(content) = webpage.content {
|
||||
if let story = content.story {
|
||||
if let storyItem = message.associatedStories[story.storyId], !storyItem.data.isEmpty {
|
||||
} else {
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
result.append((message, ChatMessageWebpageBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .freeform, neighborSpacing: .default)))
|
||||
needReactions = false
|
||||
}
|
||||
|
@ -81,6 +81,9 @@ public enum ChatMessageItemContent: Sequence {
|
||||
}
|
||||
|
||||
private func mediaMergeableStyle(_ media: Media) -> ChatMessageMerge {
|
||||
if let story = media as? TelegramMediaStory, story.isMention {
|
||||
return .none
|
||||
}
|
||||
if let file = media as? TelegramMediaFile {
|
||||
for attribute in file.attributes {
|
||||
switch attribute {
|
||||
|
@ -137,13 +137,14 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
titleString = arguments.strings.User_DeletedAccount
|
||||
}
|
||||
//TODO:localize
|
||||
isMedia = true
|
||||
isText = false
|
||||
if let storyItem = arguments.parentMessage.associatedStories[story], storyItem.data.isEmpty {
|
||||
isExpiredStory = true
|
||||
textString = NSAttributedString(string: "Expired story")
|
||||
isMedia = false
|
||||
} else {
|
||||
textString = NSAttributedString(string: "Story")
|
||||
isMedia = true
|
||||
}
|
||||
} else {
|
||||
titleString = " "
|
||||
@ -187,7 +188,7 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
case let .bubble(incoming):
|
||||
titleColor = incoming ? (authorNameColor ?? arguments.presentationData.theme.theme.chat.message.incoming.accentTextColor) : arguments.presentationData.theme.theme.chat.message.outgoing.accentTextColor
|
||||
lineImage = incoming ? (authorNameColor.flatMap({ PresentationResourcesChat.chatBubbleVerticalLineImage(color: $0) }) ?? PresentationResourcesChat.chatBubbleVerticalLineIncomingImage(arguments.presentationData.theme.theme)) : PresentationResourcesChat.chatBubbleVerticalLineOutgoingImage(arguments.presentationData.theme.theme)
|
||||
if isMedia {
|
||||
if isMedia || isExpiredStory {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.secondaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.secondaryTextColor
|
||||
} else {
|
||||
textColor = incoming ? arguments.presentationData.theme.theme.chat.message.incoming.primaryTextColor : arguments.presentationData.theme.theme.chat.message.outgoing.primaryTextColor
|
||||
@ -283,8 +284,6 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
imageDimensions = representation.dimensions.cgSize
|
||||
}
|
||||
}
|
||||
} else if storyItem.data.isEmpty {
|
||||
imageDimensions = CGSize(width: 34.0, height: 34.0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,19 +294,18 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
|
||||
let maximumTextWidth = max(0.0, arguments.constrainedSize.width - imageTextInset)
|
||||
|
||||
let contrainedTextSize = CGSize(width: maximumTextWidth, height: arguments.constrainedSize.height)
|
||||
var contrainedTextSize = CGSize(width: maximumTextWidth, height: arguments.constrainedSize.height)
|
||||
|
||||
let textInsets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0)
|
||||
|
||||
let (titleLayout, titleApply) = titleNodeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: titleString, font: titleFont, textColor: titleColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||
if isExpiredStory {
|
||||
contrainedTextSize.width -= 24.0
|
||||
}
|
||||
let (textLayout, textApply) = textNodeLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: contrainedTextSize, alignment: .natural, cutout: nil, insets: textInsets))
|
||||
|
||||
let imageSide: CGFloat
|
||||
if isExpiredStory {
|
||||
imageSide = 38.0
|
||||
} else {
|
||||
imageSide = titleLayout.size.height + textLayout.size.height - 16.0
|
||||
}
|
||||
imageSide = titleLayout.size.height + textLayout.size.height - 16.0
|
||||
|
||||
var applyImage: (() -> TransformImageNode)?
|
||||
if let imageDimensions = imageDimensions {
|
||||
@ -357,9 +355,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = isExpiredStory
|
||||
|
||||
let size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right) + leftInset, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing)
|
||||
var size = CGSize(width: max(titleLayout.size.width - textInsets.left - textInsets.right, textLayout.size.width - textInsets.left - textInsets.right) + leftInset, height: titleLayout.size.height + textLayout.size.height - 2 * (textInsets.top + textInsets.bottom) + 2 * spacing)
|
||||
if isExpiredStory {
|
||||
size.width += 14.0
|
||||
}
|
||||
|
||||
return (size, { attemptSynchronous in
|
||||
let node: ChatMessageReplyInfoNode
|
||||
@ -414,6 +414,11 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
node.imageNode?.captureProtected = message.isCopyProtected()
|
||||
}
|
||||
|
||||
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size)
|
||||
textNode.textNode.frame = textFrame.offsetBy(dx: isExpiredStory ? 16.0 : 0.0, dy: 0.0)
|
||||
|
||||
if isExpiredStory {
|
||||
let expiredStoryIconView: UIImageView
|
||||
if let current = node.expiredStoryIconView {
|
||||
@ -434,17 +439,13 @@ public class ChatMessageReplyInfoNode: ASDisplayNode {
|
||||
|
||||
expiredStoryIconView.image = PresentationResourcesChat.chatExpiredStoryIndicatorIcon(arguments.presentationData.theme.theme, type: imageType)
|
||||
if let image = expiredStoryIconView.image {
|
||||
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: 8.0, y: 3.0), size: image.size)
|
||||
let imageSize = CGSize(width: floor(image.size.width * 1.22), height: floor(image.size.height * 1.22))
|
||||
expiredStoryIconView.frame = CGRect(origin: CGPoint(x: textFrame.minX - 2.0, y: textFrame.minY + 2.0), size: imageSize)
|
||||
}
|
||||
} else if let expiredStoryIconView = node.expiredStoryIconView {
|
||||
expiredStoryIconView.removeFromSuperview()
|
||||
}
|
||||
|
||||
titleNode.frame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: spacing - textInsets.top + 1.0), size: titleLayout.size)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: leftInset - textInsets.left - 2.0, y: titleNode.frame.maxY - textInsets.bottom + spacing - textInsets.top - 2.0), size: textLayout.size)
|
||||
textNode.textNode.frame = textFrame
|
||||
|
||||
if !textLayout.spoilers.isEmpty {
|
||||
let dustNode: InvisibleInkDustNode
|
||||
if let current = node.dustNode {
|
||||
|
@ -0,0 +1,368 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TextFormat
|
||||
import LocalizedPeerData
|
||||
import TelegramStringFormatting
|
||||
import WallpaperBackgroundNode
|
||||
import ReactionSelectionNode
|
||||
import PhotoResources
|
||||
import UniversalMediaPlayer
|
||||
import TelegramUniversalVideoContent
|
||||
import GalleryUI
|
||||
import Markdown
|
||||
import ComponentFlow
|
||||
import AvatarStoryIndicatorComponent
|
||||
import AvatarNode
|
||||
|
||||
class ChatMessageStoryMentionContentNode: ChatMessageBubbleContentNode {
|
||||
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
|
||||
private let mediaBackgroundNode: NavigationBackgroundNode
|
||||
private let subtitleNode: TextNode
|
||||
private let imageNode: TransformImageNode
|
||||
private let storyIndicator = ComponentView<Empty>()
|
||||
|
||||
private let buttonNode: HighlightTrackingButtonNode
|
||||
private let buttonTitleNode: TextNode
|
||||
|
||||
private var absoluteRect: (CGRect, CGSize)?
|
||||
|
||||
private let fetchDisposable = MetaDisposable()
|
||||
|
||||
required init() {
|
||||
self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear)
|
||||
self.mediaBackgroundNode.clipsToBounds = true
|
||||
self.mediaBackgroundNode.cornerRadius = 24.0
|
||||
|
||||
self.subtitleNode = TextNode()
|
||||
self.subtitleNode.isUserInteractionEnabled = false
|
||||
self.subtitleNode.displaysAsynchronously = false
|
||||
|
||||
self.imageNode = TransformImageNode()
|
||||
self.imageNode.contentAnimations = [.subsequentUpdates]
|
||||
|
||||
self.buttonNode = HighlightTrackingButtonNode()
|
||||
self.buttonNode.clipsToBounds = true
|
||||
self.buttonNode.cornerRadius = 17.0
|
||||
|
||||
self.buttonTitleNode = TextNode()
|
||||
self.buttonTitleNode.isUserInteractionEnabled = false
|
||||
self.buttonTitleNode.displaysAsynchronously = false
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.mediaBackgroundNode)
|
||||
self.addSubnode(self.subtitleNode)
|
||||
self.addSubnode(self.imageNode)
|
||||
|
||||
self.addSubnode(self.buttonNode)
|
||||
self.addSubnode(self.buttonTitleNode)
|
||||
|
||||
self.buttonNode.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.buttonNode.alpha = 0.4
|
||||
strongSelf.buttonTitleNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.buttonTitleNode.alpha = 0.4
|
||||
} else {
|
||||
strongSelf.buttonNode.alpha = 1.0
|
||||
strongSelf.buttonNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
strongSelf.buttonTitleNode.alpha = 1.0
|
||||
strongSelf.buttonTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.fetchDisposable.dispose()
|
||||
}
|
||||
|
||||
override func transitionNode(messageId: MessageId, media: Media, adjustRect: Bool) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
|
||||
if self.item?.message.id == messageId {
|
||||
return (self.imageNode, self.imageNode.bounds, { [weak self] in
|
||||
guard let strongSelf = self else {
|
||||
return (nil, nil)
|
||||
}
|
||||
|
||||
let resultView = strongSelf.imageNode.view.snapshotContentTree(unhide: true)
|
||||
return (resultView, nil)
|
||||
})
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func updateHiddenMedia(_ media: [Media]?) -> Bool {
|
||||
var mediaHidden = false
|
||||
var currentMedia: Media?
|
||||
if let item = item {
|
||||
mediaLoop: for media in item.message.media {
|
||||
if let media = media as? TelegramMediaStory {
|
||||
currentMedia = media
|
||||
}
|
||||
}
|
||||
}
|
||||
if let currentMedia = currentMedia, let media = media {
|
||||
for item in media {
|
||||
if item.isSemanticallyEqual(to: currentMedia) {
|
||||
mediaHidden = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.imageNode.isHidden = mediaHidden
|
||||
return mediaHidden
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
guard let item = self.item else {
|
||||
return
|
||||
}
|
||||
let _ = item.controllerInteraction.openMessage(item.message, .default)
|
||||
}
|
||||
|
||||
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
|
||||
let makeImageLayout = self.imageNode.asyncLayout()
|
||||
let makeSubtitleLayout = TextNode.asyncLayout(self.subtitleNode)
|
||||
let makeButtonTitleLayout = TextNode.asyncLayout(self.buttonTitleNode)
|
||||
|
||||
let currentItem = self.item
|
||||
|
||||
return { item, layoutConstants, _, _, _, _ in
|
||||
let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: true, headerSpacing: 0.0, hidesBackground: .always, forceFullCorners: false, forceAlignment: .center)
|
||||
|
||||
return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in
|
||||
let width: CGFloat = 180.0
|
||||
let imageSize = CGSize(width: 100.0, height: 100.0)
|
||||
|
||||
let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText
|
||||
|
||||
var story: Stories.Item?
|
||||
|
||||
let storyMedia: TelegramMediaStory? = item.message.media.first(where: { $0 is TelegramMediaStory }) as? TelegramMediaStory
|
||||
var selectedMedia: Media?
|
||||
|
||||
if let storyMedia, let storyItem = item.message.associatedStories[storyMedia.storyId], !storyItem.data.isEmpty, case let .item(storyValue) = storyItem.get(Stories.StoredItem.self) {
|
||||
selectedMedia = storyValue.media
|
||||
story = storyValue
|
||||
}
|
||||
|
||||
var mediaUpdated = false
|
||||
if let selectedMedia, let currentItem, let storyMedia = currentItem.message.media.first(where: { $0 is TelegramMediaStory }) as? TelegramMediaStory, let storyItem = currentItem.message.associatedStories[storyMedia.storyId], !storyItem.data.isEmpty, case let .item(storyValue) = storyItem.get(Stories.StoredItem.self) {
|
||||
if let currentMedia = storyValue.media {
|
||||
mediaUpdated = !selectedMedia.isSemanticallyEqual(to: currentMedia)
|
||||
} else {
|
||||
mediaUpdated = true
|
||||
}
|
||||
} else {
|
||||
mediaUpdated = true
|
||||
}
|
||||
|
||||
let fromYou = item.message.author?.id == item.context.account.peerId
|
||||
|
||||
let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? ""
|
||||
let textWithRanges: PresentationStrings.FormattedString
|
||||
if fromYou {
|
||||
textWithRanges = item.presentationData.strings.Conversation_StoryMentionTextOutgoing(peerName)
|
||||
} else {
|
||||
textWithRanges = item.presentationData.strings.Conversation_StoryMentionTextIncoming(peerName)
|
||||
}
|
||||
|
||||
let text = NSMutableAttributedString()
|
||||
text.append(NSAttributedString(string: textWithRanges.string, font: Font.regular(13.0), textColor: primaryTextColor))
|
||||
for range in textWithRanges.ranges {
|
||||
if range.index == 0 {
|
||||
text.addAttribute(.font, value: Font.semibold(13.0), range: range.range)
|
||||
}
|
||||
}
|
||||
|
||||
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: text, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
//TODO:localize
|
||||
|
||||
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "View Story", font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 186.0)
|
||||
|
||||
return (backgroundSize.width, { boundingWidth in
|
||||
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.item = item
|
||||
|
||||
let imageFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: 15.0), size: imageSize).insetBy(dx: 6.0, dy: 6.0)
|
||||
if let story, let selectedMedia {
|
||||
if mediaUpdated {
|
||||
if story.isForwardingDisabled {
|
||||
let maxImageSize = CGSize(width: 180.0, height: 180.0)
|
||||
let boundingImageSize = maxImageSize
|
||||
|
||||
var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
|
||||
if let author = item.message.author {
|
||||
updateImageSignal = peerAvatarCompleteImage(account: item.context.account, peer: EnginePeer(author), size: imageSize)
|
||||
|> map { image in
|
||||
return { arguments in
|
||||
let context = DrawingContext(size: arguments.drawingSize, scale: arguments.scale ?? 0.0, clear: true)
|
||||
context?.withContext { c in
|
||||
UIGraphicsPushContext(c)
|
||||
c.addEllipse(in: CGRect(origin: CGPoint(), size: arguments.drawingSize))
|
||||
c.clip()
|
||||
if let image {
|
||||
image.draw(in: arguments.imageRect)
|
||||
}
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
return context
|
||||
}
|
||||
}
|
||||
}
|
||||
if let updateImageSignal {
|
||||
strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads)
|
||||
}
|
||||
|
||||
let arguments = TransformImageArguments(corners: ImageCorners(radius: imageFrame.height / 2.0), imageSize: boundingImageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets())
|
||||
let apply = makeImageLayout(arguments)
|
||||
apply()
|
||||
|
||||
strongSelf.imageNode.frame = imageFrame
|
||||
} else if let photo = selectedMedia as? TelegramMediaImage {
|
||||
let maxImageSize = photo.representations.last?.dimensions.cgSize ?? imageFrame.size
|
||||
let boundingImageSize = maxImageSize.aspectFilled(imageFrame.size)
|
||||
|
||||
strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(context: item.context, userLocation: .peer(item.message.id.peerId), photoReference: .message(message: MessageReference(item.message), media: photo), displayAtSize: nil, storeToDownloadsPeerId: nil).start())
|
||||
|
||||
let updateImageSignal = chatMessagePhoto(postbox: item.context.account.postbox, userLocation: .peer(item.message.id.peerId), photoReference: .message(message: MessageReference(item.message), media: photo), synchronousLoad: synchronousLoads)
|
||||
strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads)
|
||||
|
||||
let arguments = TransformImageArguments(corners: ImageCorners(radius: imageFrame.height / 2.0), imageSize: boundingImageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets())
|
||||
let apply = makeImageLayout(arguments)
|
||||
apply()
|
||||
|
||||
strongSelf.imageNode.frame = imageFrame
|
||||
} else if let file = selectedMedia as? TelegramMediaFile {
|
||||
let maxImageSize = file.dimensions?.cgSize ?? imageFrame.size
|
||||
let boundingImageSize = maxImageSize.aspectFilled(imageFrame.size)
|
||||
|
||||
let updateImageSignal = chatMessageVideoThumbnail(account: item.context.account, userLocation: .peer(item.message.id.peerId), fileReference: .message(message: MessageReference(item.message), media: file), blurred: false, synchronousLoads: synchronousLoads)
|
||||
|
||||
strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads)
|
||||
|
||||
let arguments = TransformImageArguments(corners: ImageCorners(radius: imageFrame.width / 2.0), imageSize: boundingImageSize, boundingSize: imageFrame.size, intrinsicInsets: UIEdgeInsets())
|
||||
let apply = makeImageLayout(arguments)
|
||||
apply()
|
||||
|
||||
strongSelf.imageNode.frame = imageFrame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let storyMedia {
|
||||
var hasUnseen = false
|
||||
if !fromYou {
|
||||
hasUnseen = item.associatedData.maxReadStoryId.flatMap({ $0 < storyMedia.storyId.id }) ?? false
|
||||
}
|
||||
|
||||
let indicatorFrame = imageFrame
|
||||
let _ = strongSelf.storyIndicator.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(AvatarStoryIndicatorComponent(
|
||||
hasUnseen: hasUnseen,
|
||||
hasUnseenCloseFriendsItems: hasUnseen && (story?.isCloseFriends ?? false),
|
||||
theme: item.presentationData.theme.theme,
|
||||
activeLineWidth: 3.0,
|
||||
inactiveLineWidth: 1.0 + UIScreenPixel,
|
||||
isGlassBackground: true,
|
||||
counters: nil
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: indicatorFrame.size
|
||||
)
|
||||
if let storyIndicatorView = strongSelf.storyIndicator.view {
|
||||
if storyIndicatorView.superview == nil {
|
||||
strongSelf.view.addSubview(storyIndicatorView)
|
||||
}
|
||||
storyIndicatorView.frame = indicatorFrame
|
||||
}
|
||||
}
|
||||
|
||||
let mediaBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - width) / 2.0), y: 0.0), size: backgroundSize)
|
||||
strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame
|
||||
|
||||
strongSelf.mediaBackgroundNode.updateColor(color: selectDateFillStaticColor(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), enableBlur: item.controllerInteraction.enableFullTranslucency && dateFillNeedsBlur(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper), transition: .immediate)
|
||||
strongSelf.mediaBackgroundNode.update(size: mediaBackgroundFrame.size, transition: .immediate)
|
||||
strongSelf.buttonNode.backgroundColor = item.presentationData.theme.theme.overallDarkAppearance ? UIColor(rgb: 0xffffff, alpha: 0.12) : UIColor(rgb: 0x000000, alpha: 0.12)
|
||||
|
||||
let _ = subtitleApply()
|
||||
let _ = buttonTitleApply()
|
||||
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 128.0), size: subtitleLayout.size)
|
||||
strongSelf.subtitleNode.frame = subtitleFrame
|
||||
|
||||
let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 19.0), size: buttonTitleLayout.size)
|
||||
strongSelf.buttonTitleNode.frame = buttonTitleFrame
|
||||
|
||||
let buttonSize = CGSize(width: buttonTitleLayout.size.width + 38.0, height: 34.0)
|
||||
strongSelf.buttonNode.frame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonSize.width) / 2.0), y: subtitleFrame.maxY + 11.0), size: buttonSize)
|
||||
|
||||
if item.controllerInteraction.presentationContext.backgroundNode?.hasExtraBubbleBackground() == true {
|
||||
if strongSelf.mediaBackgroundContent == nil, let backgroundContent = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
strongSelf.mediaBackgroundNode.isHidden = true
|
||||
backgroundContent.clipsToBounds = true
|
||||
backgroundContent.allowsGroupOpacity = true
|
||||
backgroundContent.cornerRadius = 24.0
|
||||
|
||||
strongSelf.mediaBackgroundContent = backgroundContent
|
||||
strongSelf.insertSubnode(backgroundContent, at: 0)
|
||||
}
|
||||
|
||||
strongSelf.mediaBackgroundContent?.frame = mediaBackgroundFrame
|
||||
} else {
|
||||
strongSelf.mediaBackgroundNode.isHidden = false
|
||||
strongSelf.mediaBackgroundContent?.removeFromSupernode()
|
||||
strongSelf.mediaBackgroundContent = nil
|
||||
}
|
||||
|
||||
if let (rect, size) = strongSelf.absoluteRect {
|
||||
strongSelf.updateAbsoluteRect(rect, within: size)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override func updateAbsoluteRect(_ rect: CGRect, within containerSize: CGSize) {
|
||||
self.absoluteRect = (rect, containerSize)
|
||||
|
||||
if let mediaBackgroundContent = self.mediaBackgroundContent {
|
||||
var backgroundFrame = mediaBackgroundContent.frame
|
||||
backgroundFrame.origin.x += rect.minX
|
||||
backgroundFrame.origin.y += rect.minY
|
||||
mediaBackgroundContent.update(rect: backgroundFrame, within: containerSize, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
|
||||
if self.mediaBackgroundNode.frame.contains(point) {
|
||||
return .openMessage
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
@ -47,10 +47,14 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
selectedTransitionNode = params.transitionNode(params.message.id, story, true)
|
||||
|
||||
if let selectedTransitionNode {
|
||||
var cornerRadius: CGFloat = 0.0
|
||||
if let imageNode = selectedTransitionNode.0 as? TransformImageNode, let currentArguments = imageNode.currentArguments {
|
||||
cornerRadius = currentArguments.corners.topLeft.radius
|
||||
}
|
||||
transitionIn = StoryContainerScreen.TransitionIn(
|
||||
sourceView: selectedTransitionNode.0.view,
|
||||
sourceRect: selectedTransitionNode.1,
|
||||
sourceCornerRadius: 0.0,
|
||||
sourceCornerRadius: cornerRadius,
|
||||
sourceIsAvatar: false
|
||||
)
|
||||
}
|
||||
@ -67,6 +71,11 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
var selectedTransitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||
selectedTransitionNode = params.transitionNode(params.message.id, story, true)
|
||||
if let selectedTransitionNode {
|
||||
var cornerRadius: CGFloat = 0.0
|
||||
if let imageNode = selectedTransitionNode.0 as? TransformImageNode, let currentArguments = imageNode.currentArguments {
|
||||
cornerRadius = currentArguments.corners.topLeft.radius
|
||||
}
|
||||
|
||||
transitionOut = StoryContainerScreen.TransitionOut(
|
||||
destinationView: selectedTransitionNode.0.view,
|
||||
transitionView: StoryContainerScreen.TransitionView(
|
||||
@ -83,20 +92,23 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool {
|
||||
return
|
||||
}
|
||||
if state.progress == 0.0 {
|
||||
view.frame = CGRect(origin: CGPoint(), size: state.sourceSize)
|
||||
view.frame = CGRect(origin: CGPoint(), size: state.destinationSize)
|
||||
}
|
||||
|
||||
let toScale = state.sourceSize.width / state.destinationSize.width
|
||||
let fromScale: CGFloat = 1.0
|
||||
let scale = toScale.interpolate(to: fromScale, amount: state.progress)
|
||||
transition.setTransform(view: view, transform: CATransform3DMakeScale(scale, scale, 1.0))
|
||||
let toScaleX = state.sourceSize.width / state.destinationSize.width
|
||||
let toScaleY = state.sourceSize.height / state.destinationSize.height
|
||||
let fromScaleX: CGFloat = 1.0
|
||||
let fromScaleY: CGFloat = 1.0
|
||||
let scaleX = toScaleX.interpolate(to: fromScaleX, amount: state.progress)
|
||||
let scaleY = toScaleY.interpolate(to: fromScaleY, amount: state.progress)
|
||||
transition.setTransform(view: view, transform: CATransform3DMakeScale(scaleX, scaleY, 1.0))
|
||||
},
|
||||
insertCloneTransitionView: { view in
|
||||
params.addToTransitionSurface(view)
|
||||
}
|
||||
),
|
||||
destinationRect: selectedTransitionNode.1,
|
||||
destinationCornerRadius: 0.0,
|
||||
destinationCornerRadius: cornerRadius,
|
||||
destinationIsAvatar: false,
|
||||
completed: {
|
||||
params.context.sharedContext.mediaManager.galleryHiddenMediaManager.removeSource(hiddenMediaSource)
|
||||
|
@ -722,7 +722,7 @@ func peerInfoScreenData(context: AccountContext, peerId: PeerId, strings: Presen
|
||||
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories -> PeerInfoScreenData in
|
||||
var availablePanes = availablePanes
|
||||
|
||||
if hasStories, peerView.peers[peerView.peerId] is TelegramUser {
|
||||
if hasStories, peerView.peers[peerView.peerId] is TelegramUser, peerView.peerId != context.account.peerId {
|
||||
availablePanes?.insert(.stories, at: 0)
|
||||
}
|
||||
|
||||
|
@ -38,6 +38,7 @@ import ChatTextLinkEditUI
|
||||
import AttachmentTextInputPanelNode
|
||||
import ChatEntityKeyboardInputNode
|
||||
import HashtagSearchUI
|
||||
import PeerInfoStoryGridScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -1723,6 +1724,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return HashtagSearchController(context: context, peer: peer, query: query)
|
||||
}
|
||||
|
||||
public func makeMyStoriesController(context: AccountContext, isArchive: Bool) -> ViewController {
|
||||
return PeerInfoStoryGridScreen(context: context, peerId: context.account.peerId, scope: isArchive ? .archive : .saved)
|
||||
}
|
||||
|
||||
public func makePremiumIntroController(context: AccountContext, source: PremiumIntroSource) -> ViewController {
|
||||
let mappedSource: PremiumSource
|
||||
switch source {
|
||||
|
Loading…
x
Reference in New Issue
Block a user