diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 8974dd8a44..4f2b1ffa1c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -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"; diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 3430837b4d..75d79ef028 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -871,6 +871,7 @@ public protocol SharedAccountContext: AnyObject { func makeAttachmentFileController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, 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 diff --git a/submodules/AccountContext/Sources/ChatController.swift b/submodules/AccountContext/Sources/ChatController.swift index 4767446896..f6c445ac6c 100644 --- a/submodules/AccountContext/Sources/ChatController.swift +++ b/submodules/AccountContext/Sources/ChatController.swift @@ -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 = 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 = 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 } } diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD index 490ff5d855..43a8681831 100644 --- a/submodules/ChatListUI/BUILD +++ b/submodules/ChatListUI/BUILD @@ -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", diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 8353bf128e..77a4b483c7 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -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) diff --git a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift index 8ab977b34c..e6a64e39cb 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItemStrings.swift @@ -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 } diff --git a/submodules/Display/Source/TransformImageNode.swift b/submodules/Display/Source/TransformImageNode.swift index cc4b58db0b..cb2d23ca73 100644 --- a/submodules/Display/Source/TransformImageNode.swift +++ b/submodules/Display/Source/TransformImageNode.swift @@ -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(ignoreRepeated: true) diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 4f4d4025aa..fccefeffe6 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -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 diff --git a/submodules/Postbox/Sources/MessageHistoryTable.swift b/submodules/Postbox/Sources/MessageHistoryTable.swift index 47e00a3f75..4f800747b7 100644 --- a/submodules/Postbox/Sources/MessageHistoryTable.swift +++ b/submodules/Postbox/Sources/MessageHistoryTable.swift @@ -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 { diff --git a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift index 9930d3b485..4823ec1f34 100644 --- a/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift +++ b/submodules/SettingsUI/Sources/NotificationsPeerCategoryController.swift @@ -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() + 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() 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? diff --git a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift index 0af9264400..eff8bfd439 100644 --- a/submodules/SparseItemGrid/Sources/SparseItemGrid.swift +++ b/submodules/SparseItemGrid/Sources/SparseItemGrid.swift @@ -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) } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 3eabf4b700..e28d138652 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -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) } diff --git a/submodules/TelegramApi/Sources/Api22.swift b/submodules/TelegramApi/Sources/Api22.swift index 48f1ed706d..6679f4cbc9 100644 --- a/submodules/TelegramApi/Sources/Api22.swift +++ b/submodules/TelegramApi/Sources/Api22.swift @@ -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 diff --git a/submodules/TelegramApi/Sources/Api31.swift b/submodules/TelegramApi/Sources/Api31.swift index 705c981590..9c6b810d6a 100644 --- a/submodules/TelegramApi/Sources/Api31.swift +++ b/submodules/TelegramApi/Sources/Api31.swift @@ -8510,6 +8510,21 @@ public extension Api.functions.stories { }) } } +public extension Api.functions.stories { + static func getAllReadUserStories() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + 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) { let buffer = Buffer() diff --git a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift index f7f1caf57f..741fa2a1fc 100644 --- a/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/Account/AccountIntermediateState.swift @@ -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 } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index f1e148fb82..39c0624492 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -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) } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift index 8686b470a9..710d34657b 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaWebpage.swift @@ -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) } } } diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift index f6893936d2..f0086070d8 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramUser.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramUserPresence.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramUserPresence.swift index 4e5614b081..9a29052a97 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramUserPresence.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramUserPresence.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index 7222906a27..8e828bccf0 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -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)) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaStory.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaStory.swift index efe709bb70..5b62980b40 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaStory.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaStory.swift @@ -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 } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift index fd85989db6..f303ec28ce 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Stories.swift @@ -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 { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift index 26c0ce6330..4a1ec258b8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/StoryListContext.swift @@ -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)) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index e038cf2b29..b6212cfba0 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -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) diff --git a/submodules/TelegramCore/Sources/UpdatePeers.swift b/submodules/TelegramCore/Sources/UpdatePeers.swift index f766710a1a..d18cb74f57 100644 --- a/submodules/TelegramCore/Sources/UpdatePeers.swift +++ b/submodules/TelegramCore/Sources/UpdatePeers.swift @@ -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 } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 53ffe7dbb7..5a4fb91dd3 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -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() } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index f514957521..45e7bbe12f 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -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]) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift index d41d823934..f43155bce9 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageForwardInfoNode/Sources/ChatMessageForwardInfoNode.swift @@ -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() diff --git a/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/Sources/EmptyStateIndicatorComponent.swift b/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/Sources/EmptyStateIndicatorComponent.swift index 6b4afe2853..4cb104ad66 100644 --- a/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/Sources/EmptyStateIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/EmptyStateIndicatorComponent/Sources/EmptyStateIndicatorComponent.swift @@ -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() private let title = ComponentView() private let text = ComponentView() - private let button = ComponentView() + private var button: ComponentView? 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 + 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) } diff --git a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift index 1a9968dfd6..4c7298fb38 100644 --- a/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift +++ b/submodules/TelegramUI/Components/NotificationPeerExceptionController/Sources/NotificationPeerExceptionController.swift @@ -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 diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift index 6e310c5bf6..1549ac9d4b 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoStoryPaneNode.swift @@ -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 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) } } diff --git a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift index 9f526295f0..e6174a3c66 100644 --- a/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift +++ b/submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent/Sources/AvatarStoryIndicatorComponent.swift @@ -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] diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift index 4bfab16ef8..695d126d29 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/MediaNavigationStripComponent.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift index 83a761618f..73908bf8dd 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryAuthorInfoComponent.swift @@ -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, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift index 3a6b83158c..3f574d0588 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryChatContent.swift @@ -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( diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift index ee826080c4..e8fee6339e 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContainerScreen.swift @@ -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 { diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift index ea0aefb6bd..21d87548e7 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryContentCaptionComponent.swift @@ -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 diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift index 204668ed82..98b5aa2bfa 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemContentComponent.swift @@ -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, diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift index e31aadb307..33d39ac87d 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerComponent.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift index e9412c9aef..ba7e463143 100644 --- a/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift +++ b/submodules/TelegramUI/Components/Stories/StoryContainerScreen/Sources/StoryItemSetContainerViewSendMessage.swift @@ -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 } diff --git a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift index cb9f7273d6..b330480fb4 100644 --- a/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift +++ b/submodules/TelegramUI/Components/Stories/StoryPeerListComponent/Sources/StoryPeerListComponent.swift @@ -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 } diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/Contents.json new file mode 100644 index 0000000000..f3467818f9 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ContextStories.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/ContextStories.svg b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/ContextStories.svg new file mode 100644 index 0000000000..b6f6f6da47 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Stories.imageset/ContextStories.svg @@ -0,0 +1,3 @@ + + + diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json index a5c36ef619..e1961f0136 100644 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "timer.pdf", + "filename" : "ic_exipred.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/ic_exipred.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/ic_exipred.pdf new file mode 100644 index 0000000000..7c8bdb9045 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/ic_exipred.pdf @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf deleted file mode 100644 index 4767c66cdd..0000000000 --- a/submodules/TelegramUI/Images.xcassets/Chat/Message/ExpiredStoryIcon.imageset/timer.pdf +++ /dev/null @@ -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 \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift index 5c9c5808ab..1e5fd337af 100644 --- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift +++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift @@ -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 = 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 + 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, diff --git a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift index 6d06e24a80..62e9404cfa 100644 --- a/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageActionItemNode.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index 3a9790a1de..89893bbeab 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -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 } diff --git a/submodules/TelegramUI/Sources/ChatMessageItem.swift b/submodules/TelegramUI/Sources/ChatMessageItem.swift index dace06728c..0a4aab7c8c 100644 --- a/submodules/TelegramUI/Sources/ChatMessageItem.swift +++ b/submodules/TelegramUI/Sources/ChatMessageItem.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 4ee421da57..7e9318070a 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -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 { diff --git a/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift new file mode 100644 index 0000000000..0c08bfc796 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatMessageStoryMentionContentNode.swift @@ -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() + + 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 + } + } +} diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index f29ad9dd53..303fae7c12 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -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) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index ee95fecc88..ff334bb932 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -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) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c6932c415e..4ac887145d 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -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 {