Various fixes

This commit is contained in:
Ilya Laktyushin 2026-03-26 13:58:50 +01:00
parent e3eac999c4
commit 77cc01f0d9
14 changed files with 157 additions and 91 deletions

View file

@ -3580,8 +3580,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
interaction.messageContextAction(EngineMessage(message), node, rect, gesture, key, fetchResourceId)
}, toggleMediaPlayback: { _ in
}, toggleMessagesSelection: { messageId, selected in
}, toggleMediaPlayback: nil, toggleMessagesSelection: { messageId, selected in
if let messageId = messageId.first {
interaction.toggleMessageSelection(messageId, selected)
}

View file

@ -155,6 +155,8 @@ class ChatImageGalleryItem: GalleryItem {
for media in self.message.media {
if let poll = media as? TelegramMediaPoll, let image = poll.attachedMedia as? TelegramMediaImage {
node.setImage(userLocation: .peer(self.message.id.peerId), imageReference: .message(message: MessageReference(self.message), media: image))
} else if let poll = media as? TelegramMediaPoll, let file = poll.attachedMedia as? TelegramMediaFile, file.mimeType.hasPrefix("image/") {
node.setFile(context: self.context, userLocation: .peer(self.message.id.peerId), fileReference: .message(message: MessageReference(self.message), media: file))
} else if let paidContent = media as? TelegramMediaPaidContent {
let mediaIndex = self.mediaIndex ?? 0
if case let .full(fullMedia) = paidContent.extendedMedia[Int(mediaIndex)], let image = fullMedia as? TelegramMediaImage {

View file

@ -976,9 +976,11 @@
if (adjustments.paintingData.hasAnimation) {
grouping = false;
}
// if ([editingContext livePhotoModeForItem:asset] != TGMediaLivePhotoModeOff) {
// grouping = false;
// }
TGMediaLivePhotoMode livePhotoMode = [editingContext livePhotoModeForItem:asset];
if (livePhotoMode == TGMediaLivePhotoModeLoop || livePhotoMode == TGMediaLivePhotoModeBounce) {
grouping = false;
}
}
}
@ -1544,9 +1546,10 @@
if (adjustments.paintingData.hasAnimation) {
grouping = false;
}
// if ([editingContext livePhotoModeForItem:asset] != TGMediaLivePhotoModeOff) {
// grouping = false;
// }
TGMediaLivePhotoMode livePhotoMode = [editingContext livePhotoModeForItem:asset];
if (livePhotoMode == TGMediaLivePhotoModeLoop || livePhotoMode == TGMediaLivePhotoModeBounce) {
grouping = false;
}
}
}
@ -1771,21 +1774,6 @@
- (UIBarButtonItem *)rightBarButtonItem
{
return nil;
// if (_intent == TGMediaAssetsControllerSendFileIntent)
// return nil;
// if (self.requestSearchController == nil) {
// return nil;
// }
//
// if (iosMajorVersion() < 7)
// {
// TGModernBarButton *searchButton = [[TGModernBarButton alloc] initWithImage:TGComponentsImageNamed(@"NavigationSearchIcon.png")];
// searchButton.portraitAdjustment = CGPointMake(-7, -5);
// [searchButton addTarget:self action:@selector(searchButtonPressed) forControlEvents:UIControlEventTouchUpInside];
// return [[UIBarButtonItem alloc] initWithCustomView:searchButton];
// }
//
// return [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(searchButtonPressed)];
}
- (void)cancelButtonPressed

View file

@ -754,6 +754,14 @@
hasLivePhotoButton = true;
}
}
} else if ([item isKindOfClass:[TGMediaPickerGalleryPhotoItem class]]) {
TGMediaPickerGalleryPhotoItem *photoGalleryItem = (TGMediaPickerGalleryPhotoItem *)item;
if ([photoGalleryItem.asset isKindOfClass:[TGMediaAsset class]]) {
TGMediaAsset *asset = (TGMediaAsset *)photoGalleryItem.asset;
if (asset.subtypes & TGMediaAssetSubtypePhotoLive) {
hasLivePhotoButton = true;
}
}
}
[_muteButton setImage:muteIcon forState:UIControlStateNormal];
[_muteButton setImage:muteActiveIcon forState:UIControlStateSelected];

View file

@ -338,7 +338,13 @@
backgroundHeight += _keyboardHeight - edgeInsets.bottom;
}
CGRect livePhotoButtonFrame = CGRectMake(edgeInsets.left + 16.0, edgeInsets.top + 145.0 - _keyboardHeight, 160.0, 18.0);
CGFloat livePhotoY = 0.0;
if (frame.size.width > frame.size.height && !TGIsPad()) {
livePhotoY = 48.0;
} else {
livePhotoY = edgeInsets.top + 145.0 - _keyboardHeight;
}
CGRect livePhotoButtonFrame = CGRectMake(_safeAreaInset.left + edgeInsets.left + 16.0, livePhotoY, 160.0, 18.0);
_livePhotoButtonView.frame = livePhotoButtonFrame;
CGRect panelFrame = CGRectMake(edgeInsets.left, panelY, frame.size.width, panelHeight);

View file

@ -13,7 +13,16 @@ import LegacyMediaPickerUI
import Photos
import MediaAssetsContext
private func galleryFetchResultItems(fetchResult: PHFetchResult<PHAsset>, index: Int, reversed: Bool, selectionContext: TGMediaSelectionContext?, editingContext: TGMediaEditingContext, stickersContext: TGPhotoPaintStickersContext, immediateThumbnail: UIImage?) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
private func galleryFetchResultItems(
fetchResult: PHFetchResult<PHAsset>,
index: Int,
reversed: Bool,
selectionContext: TGMediaSelectionContext?,
editingContext: TGMediaEditingContext,
stickersContext: TGPhotoPaintStickersContext,
immediateThumbnail: UIImage?,
asFile: Bool
) -> ([TGModernGalleryItem], TGModernGalleryItem?) {
var focusItem: TGModernGalleryItem?
var galleryItems: [TGModernGalleryItem] = []
@ -24,6 +33,7 @@ private func galleryFetchResultItems(fetchResult: PHFetchResult<PHAsset>, index:
galleryItem.selectionContext = selectionContext
galleryItem.editingContext = editingContext
galleryItem.stickersContext = stickersContext
galleryItem.asFile = asFile
galleryItems.append(galleryItem)
if i == index {
@ -112,6 +122,7 @@ func presentLegacyMediaPickerGallery(
immediateThumbnail: UIImage?,
selectionContext: TGMediaSelectionContext?,
editingContext: TGMediaEditingContext,
asFile: Bool,
hasSilentPosting: Bool,
hasSchedule: Bool,
hasTimer: Bool,
@ -166,10 +177,10 @@ func presentLegacyMediaPickerGallery(
let (items, focusItem): ([TGModernGalleryItem], TGModernGalleryItem?)
switch source {
case let .fetchResult(fetchResult, index, reversed):
(items, focusItem) = galleryFetchResultItems(fetchResult: fetchResult, index: index, reversed: reversed, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail)
case let .selection(item):
(items, focusItem) = gallerySelectionItems(item: item, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail)
case let .fetchResult(fetchResult, index, reversed):
(items, focusItem) = galleryFetchResultItems(fetchResult: fetchResult, index: index, reversed: reversed, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail, asFile: asFile)
case let .selection(item):
(items, focusItem) = gallerySelectionItems(item: item, selectionContext: selectionContext, editingContext: editingContext, stickersContext: paintStickersContext, immediateThumbnail: immediateThumbnail)
}
let recipientName: String?
@ -183,7 +194,23 @@ func presentLegacyMediaPickerGallery(
}
}
let model = TGMediaPickerGalleryModel(context: legacyController.context, items: items, focus: focusItem, selectionContext: selectionContext, editingContext: editingContext, hasCaptions: true, allowCaptionEntities: true, hasTimer: hasTimer, onlyCrop: false, inhibitDocumentCaptions: false, hasSelectionPanel: true, hasCamera: false, recipientName: recipientName, isScheduledMessages: isScheduledMessages, hasCoverButton: hasCoverButton)!
let model = TGMediaPickerGalleryModel(
context: legacyController.context,
items: items,
focus: focusItem,
selectionContext: selectionContext,
editingContext: editingContext,
hasCaptions: true,
allowCaptionEntities: true,
hasTimer: hasTimer,
onlyCrop: false,
inhibitDocumentCaptions: false,
hasSelectionPanel: true,
hasCamera: false,
recipientName: recipientName,
isScheduledMessages: isScheduledMessages,
hasCoverButton: hasCoverButton
)!
model.stickersContext = paintStickersContext
controller.model = model
model.controller = controller

View file

@ -603,7 +603,7 @@ final class MediaPickerGridItemNode: GridItemNode {
}
duration = stringForDuration(Int32(asset.duration))
} else {
if asset.mediaSubtypes.contains(.photoLive) {
if asset.mediaSubtypes.contains(.photoLive) && interaction.displayLivePhotoIcon {
typeIcon = UIImage(bundleImageName: "Chat/Message/LivePhoto")
}
}

View file

@ -47,8 +47,20 @@ final class MediaPickerInteraction {
let selectionState: TGMediaSelectionContext?
let editingState: TGMediaEditingContext
var hiddenMediaId: String?
var displayLivePhotoIcon: Bool = false
init(downloadManager: AssetDownloadManager, openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void, openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void, openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void, toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool, sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void, schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void, dismissInput: @escaping () -> Void, selectionState: TGMediaSelectionContext?, editingState: TGMediaEditingContext) {
init(
downloadManager: AssetDownloadManager,
openMedia: @escaping (PHFetchResult<PHAsset>, Int, UIImage?) -> Void,
openSelectedMedia: @escaping (TGMediaSelectableItem, UIImage?) -> Void,
openDraft: @escaping (MediaEditorDraft, UIImage?) -> Void,
toggleSelection: @escaping (TGMediaSelectableItem, Bool, Bool) -> Bool,
sendSelected: @escaping (TGMediaSelectableItem?, Bool, Int32?, Bool, ChatSendMessageActionSheetController.SendParameters?, @escaping () -> Void) -> Void,
schedule: @escaping (ChatSendMessageActionSheetController.SendParameters?) -> Void,
dismissInput: @escaping () -> Void,
selectionState: TGMediaSelectionContext?,
editingState: TGMediaEditingContext
) {
self.downloadManager = downloadManager
self.openMedia = openMedia
self.openSelectedMedia = openSelectedMedia
@ -185,6 +197,18 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
case assets(PHAssetCollection?, AssetsMode)
case media([Media])
var asFile: Bool {
switch self {
case let .assets(_, mode):
if case let .poll(_, asFile) = mode {
return asFile
}
return false
default:
return false
}
}
}
private let context: AccountContext
@ -607,11 +631,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.gridNode.scrollView.alwaysBounceVertical = true
self.gridNode.scrollView.showsVerticalScrollIndicator = false
if case let .assets(_, mode) = controller.subject, [.wallpaper, .story, .addImage, .cover, .createSticker, .createAvatar].contains(mode) {
} else {
self.setupSelectionGesture()
}
self.setupSelectionGesture()
if let controller = self.controller, case let .assets(collection, _) = controller.subject, collection != nil {
self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in
@ -808,10 +828,20 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
}
func setupSelectionGesture() {
guard self.selectionGesture == nil else {
func setupSelectionGesture(force: Bool = false) {
guard self.selectionGesture == nil, let controller = self.controller else {
return
}
if !force, case let .assets(_, mode) = controller.subject {
switch mode {
case .default:
break
default:
return
}
}
let selectionGesture = MediaPickerGridSelectionGesture<TGMediaSelectableItem>()
selectionGesture.delegate = self.wrappedGestureRecognizerDelegate
selectionGesture.began = { [weak self] in
@ -1296,7 +1326,6 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.openingMedia = true
let asset = fetchResult[index]
let _ = (checkIfAssetIsLocal(asset)
|> deliverOnMainQueue).start(next: { [weak self] isLocallyAvailable in
guard let self else {
@ -1333,7 +1362,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.openingMedia = true
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState?.selectionLimit == 1 ? nil : interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: hasSchedule, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .fetchResult(fetchResult: fetchResult, index: index, reversed: reversed), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState?.selectionLimit == 1 ? nil : interaction.selectionState, editingContext: interaction.editingState, asFile: controller.subject.asFile, hasSilentPosting: true, hasSchedule: hasSchedule, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, initialLayout: layout, transitionHostView: { [weak self] in
return self?.gridNode.view
@ -1346,7 +1375,11 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}, presentSchedulePicker: controller.presentSchedulePicker, presentTimerPicker: controller.presentTimerPicker, getCaptionPanelView: controller.getCaptionPanelView, present: { [weak self] c, a in
self?.currentGalleryParentController = c
c.navigationPresentation = .flatModal
self?.controller?.parentController()?.push(c)
if let parentController = self?.controller?.parentController() {
parentController.push(c)
} else {
self?.controller?.push(c)
}
//self?.controller?.present(c, in: .window(.root), with: a)
}, finishedTransitionIn: { [weak self] in
self?.openingMedia = false
@ -1376,7 +1409,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
self.openingMedia = true
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self.currentGalleryController = presentLegacyMediaPickerGallery(context: controller.context, peer: controller.peer, threadTitle: controller.threadTitle, chatLocation: controller.chatLocation, isScheduledMessages: controller.isScheduledMessages, presentationData: self.presentationData, source: .selection(item: item), immediateThumbnail: immediateThumbnail, selectionContext: interaction.selectionState, editingContext: interaction.editingState, asFile: controller.subject.asFile, hasSilentPosting: true, hasSchedule: true, hasTimer: hasTimer, updateHiddenMedia: { [weak self] id in
self?.hiddenMediaId.set(.single(id))
}, initialLayout: layout, transitionHostView: { [weak self] in
return self?.selectionNode?.view
@ -2021,6 +2054,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.secondaryButtonAction = secondaryButtonAction
let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)!
let editingContext = editingContext ?? (subject.asFile ? TGMediaEditingContext.forCaptionsOnly() : TGMediaEditingContext())!
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, glass: style == .glass, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
@ -2260,7 +2294,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if let currentItem = currentItem {
selectionState.setItem(currentItem, selected: true)
}
strongSelf.controllerNode.send(fromGallery: currentItem != nil, silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
strongSelf.controllerNode.send(fromGallery: currentItem != nil, asFile: strongSelf.subject.asFile, silently: silently, scheduleTime: scheduleTime, animated: animated, parameters: parameters, completion: completion)
}
}, schedule: { [weak self] parameters in
if let strongSelf = self {
@ -2272,7 +2306,16 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
if let strongSelf = self {
strongSelf.controllerNode.dismissInput()
}
}, selectionState: selectionContext, editingState: editingContext ?? TGMediaEditingContext())
}, selectionState: selectionContext, editingState: editingContext)
var displayLivePhotoIcon = false
if case let .assets(_, mode) = subject, case .default = mode {
if let peer = self.peer, case .secretChat = peer {
} else {
displayLivePhotoIcon = true
}
}
self.interaction?.displayLivePhotoIcon = displayLivePhotoIcon
let highQualityPhoto = UserDefaults.standard.bool(forKey: "TG_photoHighQuality_v0")
if highQualityPhoto {
@ -2575,13 +2618,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
let selectedSize = self.selectedButtonNode.update(count: count)
var safeInset: CGFloat = 0.0
if layout.safeInsets.right > 0.0 {
safeInset += layout.safeInsets.right + 16.0
}
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
let navigationHeight = navigationLayout(layout: layout).navigationFrame.height
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - selectedSize.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize)
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: 16.0 + 44.0 + 12.0, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - selectedSize.height) / 2.0) + 1.0), size: selectedSize)
let isSelectionButtonVisible = count > 0 && self.controllerNode.currentDisplayMode == .all
transition.updateAlpha(node: self.selectedButtonNode, alpha: isSelectionButtonVisible ? 1.0 : 0.0)
@ -2973,7 +3011,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
}
self.explicitMultipleSelection = true
self.controllerNode.setupSelectionGesture()
self.controllerNode.setupSelectionGesture(force: true)
self.requestAttachmentMenuExpansion()
if let state = self.controllerNode.state {
@ -3202,14 +3240,8 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att
self.validLayout = layout
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
var safeInset: CGFloat = 0.0
if layout.safeInsets.right > 0.0 {
safeInset += layout.safeInsets.right + 16.0
}
let navigationHeight = navigationLayout(layout: layout).navigationFrame.height
let selectedButtonInset: CGFloat = self._hasGlassStyle ? 68.0 : 54.0
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: self.view.bounds.width - selectedButtonInset - self.selectedButtonNode.frame.width - safeInset, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size)
self.selectedButtonNode.frame = CGRect(origin: CGPoint(x: 16.0 + 44.0 + 12.0, y: self._hasGlassStyle ? 16.0 : floorToScreenPixels((navigationHeight - self.selectedButtonNode.frame.height) / 2.0) + 1.0), size: self.selectedButtonNode.frame.size)
}
public func dismissAnimated() {

View file

@ -501,7 +501,7 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
public func descriptionStringForMessage(contentSettings: ContentSettings, message: EngineMessage, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: EnginePeer.Id) -> (NSAttributedString, Bool, Bool) {
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
if !message.text.isEmpty && ![.expiredImage, .expiredVideo, .poll].contains(contentKind.key) {
return (foldLineBreaks(messageTextWithAttributes(message: message)), false, true)
}
let result = stringForMediaKind(contentKind, strings: strings)

View file

@ -20,8 +20,7 @@ private extension ListMessageItemInteraction {
return controllerInteraction.openMessage(message, OpenMessageParams(mode: mode))
}, openMessageContextMenu: { message, bool, node, rect, gesture in
controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture, nil)
}, toggleMediaPlayback: { _ in
}, toggleMessagesSelection: { messageId, selected in
}, toggleMediaPlayback: nil, toggleMessagesSelection: { messageId, selected in
controllerInteraction.toggleMessagesSelection(messageId, selected)
}, openUrl: { url, param1, param2, message in
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message))

View file

@ -11,6 +11,7 @@ swift_library(
],
deps = [
"//submodules/AsyncDisplayKit",
"//submodules/Crc32",
"//submodules/Display",
"//submodules/TelegramCore",
"//submodules/Postbox",

View file

@ -1,6 +1,7 @@
import Foundation
import UIKit
import AsyncDisplayKit
import Crc32
import Display
import TelegramCore
import Postbox
@ -3654,11 +3655,6 @@ public class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode {
}
}
private struct ArbitraryRandomNumberGenerator : RandomNumberGenerator {
init(seed: Int) { srand48(seed) }
func next() -> UInt64 { return UInt64(drand48() * Double(UInt64.max)) }
}
private func stringForRemainingTime(_ duration: Int32, strings: PresentationStrings, results: Bool) -> String {
let days = duration / (3600 * 24)
let hours = duration / 3600
@ -3739,26 +3735,31 @@ private func resolvedOptionOrder(for item: ChatMessageBubbleContentItem) -> [(In
return defaultOrderedOptions
}
let currentOpaqueIdentifiers = poll.options.map(\.opaqueIdentifier)
let messageSeed = UInt64(bitPattern: Int64(item.message.stableId))
let peerSeed = UInt64(bitPattern: item.context.account.peerId.toInt64())
let seed = Int(truncatingIfNeeded: (messageSeed &* 6364136223846793005) ^ peerSeed)
let userId = item.context.account.peerId.id._internalGetInt64Value()
let pollId = poll.pollId.id
var resolvedOpaqueIdentifiers = currentOpaqueIdentifiers
var randomNumberGenerator = ArbitraryRandomNumberGenerator(seed: seed)
resolvedOpaqueIdentifiers.shuffle(using: &randomNumberGenerator)
return defaultOrderedOptions
.map { index, option in
var hashValue = Data()
hashValue.append(contentsOf: String(userId).utf8)
hashValue.append(option.opaqueIdentifier)
hashValue.append(contentsOf: String(pollId).utf8)
let indicesByOpaqueIdentifier = Dictionary(uniqueKeysWithValues: poll.options.enumerated().map { ($0.element.opaqueIdentifier, $0.offset) })
let orderedOptions = resolvedOpaqueIdentifiers.compactMap { opaqueIdentifier -> (Int, TelegramMediaPollOption)? in
guard let index = indicesByOpaqueIdentifier[opaqueIdentifier] else {
return nil
let sortValue: UInt32 = hashValue.withUnsafeBytes { bytes in
guard let baseAddress = bytes.baseAddress else {
return 0
}
return Crc32(baseAddress, Int32(bytes.count))
}
return (index, option, sortValue)
}
return (index, poll.options[index])
}
if orderedOptions.count == poll.options.count {
return orderedOptions
} else {
return defaultOrderedOptions
}
.sorted(by: { lhs, rhs in
if lhs.2 != rhs.2 {
return lhs.2 < rhs.2
} else {
return lhs.0 < rhs.0
}
})
.map { ($0.0, $0.1) }
}

View file

@ -1103,9 +1103,13 @@ public final class ListComposePollOptionComponent: Component {
let cornerRadius: CGFloat = 10.0
let makeLayout = imageNode.asyncLayout()
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))
apply()
Queue.concurrentDefaultQueue().async {
let apply = makeLayout(TransformImageArguments(corners: ImageCorners(radius: cornerRadius), imageSize: imageSize, boundingSize: imageNodeSize, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))
Queue.mainQueue().async {
apply()
}
}
if self.imageButton.superview == nil {
self.imageButton.addTarget(self, action: #selector(self.imageButtonPressed), for: .touchUpInside)
self.addSubview(self.imageButton)

View file

@ -202,8 +202,7 @@ extension ListMessageItemInteraction {
return controllerInteraction.openMessage(message, OpenMessageParams(mode: mode))
}, openMessageContextMenu: { message, bool, node, rect, gesture in
controllerInteraction.openMessageContextMenu(message, bool, node, rect, gesture, nil)
}, toggleMediaPlayback: { _ in
}, toggleMessagesSelection: { messageId, selected in
}, toggleMediaPlayback: nil, toggleMessagesSelection: { messageId, selected in
controllerInteraction.toggleMessagesSelection(messageId, selected)
}, openUrl: { url, param1, param2, message in
controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: param1, external: param2, message: message, progress: Promise()))