Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2026-02-10 12:59:12 +04:00
commit 72ed83c99a
40 changed files with 411 additions and 161 deletions

View file

@ -1980,7 +1980,15 @@ private final class NotificationServiceHandler {
}
if enableInlineEmoji, let textEntitiesAttribute = message.textEntitiesAttribute, let author = message.author {
let authorTitle = author.debugDisplayTitle
let messagePrefix = "\(authorTitle): "
var needsPrefix = false
if message.id.peerId.namespace == Namespaces.Peer.CloudGroup {
needsPrefix = true
} else if let channel = message.peers[message.id.peerId] as? TelegramChannel {
if case .group = channel.info {
needsPrefix = true
}
}
let messagePrefix = needsPrefix ? "\(authorTitle): " : ""
let messagePrefixLength = (messagePrefix as NSString).length
for entity in textEntitiesAttribute.entities {
if case let .CustomEmoji(_, fileId) = entity.type {

View file

@ -203,56 +203,87 @@ static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, U
/// Collect all accessibliity elements for a given view and view node
static void CollectAccessibilityElementsForView(UIView *view, NSMutableArray *elements)
{
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
ASDisplayNode *node = view.asyncdisplaykit_node;
static Class displayListViewClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
displayListViewClass = NSClassFromString(@"Display.ListView");
});
BOOL anySubNodeIsCollection = (nil != ASDisplayNodeFindFirstNode(node,
^BOOL(ASDisplayNode *nodeToCheck) {
ASDisplayNodeCAssertNotNil(elements, @"Should pass in a NSMutableArray");
ASDisplayNode *node = view.asyncdisplaykit_node;
static Class displayListViewClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
displayListViewClass = NSClassFromString(@"Display.ListView");
});
BOOL anySubNodeIsCollection = (nil != ASDisplayNodeFindFirstNode(node,
^BOOL(ASDisplayNode *nodeToCheck) {
if (displayListViewClass != nil && [nodeToCheck isKindOfClass:displayListViewClass]) {
return true;
return true;
}
return false;
/*return ASDynamicCast(nodeToCheck, ASCollectionNode) != nil ||
ASDynamicCast(nodeToCheck, ASTableNode) != nil;*/
}));
if (node.isAccessibilityContainer && !anySubNodeIsCollection) {
CollectAccessibilityElementsForContainer(node, view, elements);
return;
}
// Handle rasterize case
if (node.rasterizesSubtree) {
CollectUIAccessibilityElementsForNode(node, node, view, elements);
return;
}
if (!node.isLayerBacked) {
for (UIView *subview in node.view.subviews) {
ASDisplayNode *subnode = subview.asyncdisplaykit_node;
if (subnode) {
if (subnode.isAccessibilityElement) {
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
if (subnode.isLayerBacked) {
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node];
[elements addObject:accessiblityElement];
} else {
// Accessiblity element is not layer backed just add the view as accessibility element
[elements addObject:subnode.view];
}
} else if (subnode.isLayerBacked) {
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
CollectUIAccessibilityElementsForNode(subnode, node, view, elements);
} else if ([subnode accessibilityElementCount] > 0) {
// UIView is itself a UIAccessibilityContainer just add it
[elements addObject:subnode.view];
}
} else {
// Plain UIView without an associated ASDisplayNode
if (subview.isAccessibilityElement) {
[elements addObject:subview];
} else if ([subview accessibilityElementCount] > 0) {
[elements addObject:subview];
}
}
}
} else {
for (ASDisplayNode *subnode in node.subnodes) {
if (subnode.isAccessibilityElement) {
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
if (subnode.isLayerBacked) {
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node];
[elements addObject:accessiblityElement];
} else {
// Accessiblity element is not layer backed just add the view as accessibility element
[elements addObject:subnode.view];
}
} else if (subnode.isLayerBacked) {
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
CollectUIAccessibilityElementsForNode(subnode, node, view, elements);
} else if ([subnode accessibilityElementCount] > 0) {
// UIView is itself a UIAccessibilityContainer just add it
[elements addObject:subnode.view];
}
}
return false;
/*return ASDynamicCast(nodeToCheck, ASCollectionNode) != nil ||
ASDynamicCast(nodeToCheck, ASTableNode) != nil;*/
}));
if (node.isAccessibilityContainer && !anySubNodeIsCollection) {
CollectAccessibilityElementsForContainer(node, view, elements);
return;
}
// Handle rasterize case
if (node.rasterizesSubtree) {
CollectUIAccessibilityElementsForNode(node, node, view, elements);
return;
}
for (ASDisplayNode *subnode in node.subnodes) {
if (subnode.isAccessibilityElement) {
// An accessiblityElement can either be a UIView or a UIAccessibilityElement
if (subnode.isLayerBacked) {
// No view for layer backed nodes exist. It's necessary to create a UIAccessibilityElement that represents this node
UIAccessibilityElement *accessiblityElement = [ASAccessibilityElement accessibilityElementWithContainer:view node:subnode containerNode:node];
[elements addObject:accessiblityElement];
} else {
// Accessiblity element is not layer backed just add the view as accessibility element
[elements addObject:subnode.view];
}
} else if (subnode.isLayerBacked) {
// Go down the hierarchy of the layer backed subnode and collect all of the UIAccessibilityElement
CollectUIAccessibilityElementsForNode(subnode, node, view, elements);
} else if ([subnode accessibilityElementCount] > 0) {
// UIView is itself a UIAccessibilityContainer just add it
[elements addObject:subnode.view];
}
}
}
@interface _ASDisplayView () {

View file

@ -55,6 +55,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
private(set) var isAnimating = false
public var hitTestSize: CGFloat?
public typealias BlobRange = (min: CGFloat, max: CGFloat)
public init(
@ -224,6 +226,15 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
self.updateBlobsState()
}
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let hitTestSize = self.hitTestSize {
if !CGSize(width: hitTestSize, height: hitTestSize).centered(in: self.bounds).contains(point) {
return nil
}
}
return super.hitTest(point, with: event)
}
}
final class BlobNode: ASDisplayNode {

View file

@ -896,7 +896,7 @@ public final class ReactionButtonAsyncNode: ContextControllerSourceView {
)
}
counterLayout = counterValue
if spec.component.count != 0 {
if spec.component.count != 0 || hasTitle {
size.width += spacing + counterValue.size.width
} else {
size.width -= 1.0

View file

@ -96,6 +96,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case experimentalCompatibility(Bool)
case enableDebugDataDisplay(Bool)
case fakeGlass(Bool)
case forceClearGlass(Bool)
case browserExperiment(Bool)
case allForumsHaveTabs(Bool)
case enableReactionOverrides(Bool)
@ -135,7 +136,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .alwaysDisplayTyping, .debugRatingLayout, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .fakeGlass, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .enableUpdates, .pwa, .enableLocalTranslation:
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .fakeGlass, .forceClearGlass, .browserExperiment, .allForumsHaveTabs, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .enableUpdates, .pwa, .enableLocalTranslation:
return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue
@ -228,24 +229,26 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 38
case .fakeGlass:
return 39
case .browserExperiment:
case .forceClearGlass:
return 40
case .allForumsHaveTabs:
case .browserExperiment:
return 41
case .enableReactionOverrides:
case .allForumsHaveTabs:
return 42
case .restorePurchases:
case .enableReactionOverrides:
return 43
case .logTranslationRecognition:
case .restorePurchases:
return 44
case .resetTranslationStates:
case .logTranslationRecognition:
return 45
case .compressedEmojiCache:
case .resetTranslationStates:
return 46
case .storiesJpegExperiment:
case .compressedEmojiCache:
return 47
case .disableReloginTokens:
case .storiesJpegExperiment:
return 48
case .disableReloginTokens:
return 49
case .checkSerializedData:
return 50
case .enableQuickReactionSwitch:
@ -1279,6 +1282,16 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .forceClearGlass(value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Force clear glass", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
settings.forceClearGlass = value
return PreferencesEntry(settings)
})
}).start()
})
case let .browserExperiment(value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Inline UI", value: value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -1555,6 +1568,7 @@ private func debugControllerEntries(context: AccountContext?, sharedContext: Sha
entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility))
entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay))
entries.append(.fakeGlass(experimentalSettings.fakeGlass))
entries.append(.forceClearGlass(experimentalSettings.forceClearGlass))
#if DEBUG
entries.append(.browserExperiment(experimentalSettings.browserExperiment))
#else

View file

@ -1340,13 +1340,14 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll
isOpen: false
))),
action: { [weak self] in
guard let self, let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View, let centerItemView = buttonPanelView.centerItemView else {
guard let self, let buttonPanelView = self.buttonPanel.view as? GlassControlPanelComponent.View else {
return
}
guard let itemView = centerItemView.itemView(id: AnyHashable("settings")) else {
return
if let centerItemView = buttonPanelView.centerItemView, let itemView = centerItemView.itemView(id: AnyHashable("settings")) {
self.settingsButtonPressed(sourceView: itemView)
} else if let rightItemView = buttonPanelView.rightItemView, let itemView = rightItemView.itemView(id: AnyHashable("settings")) {
self.settingsButtonPressed(sourceView: itemView)
}
self.settingsButtonPressed(sourceView: itemView)
}
))
}

View file

@ -1469,8 +1469,14 @@ public class GalleryController: ViewController, StandalonePresentableController,
}
self.galleryNode.controlsVisibilityChanged = { [weak self] visible in
self?.prefersOnScreenNavigationHidden = !visible
self?.galleryNode.pager.centralItemNode()?.controlsVisibilityUpdated(isVisible: visible)
guard let self else {
return
}
self.prefersOnScreenNavigationHidden = !visible
self.galleryNode.pager.forEachItemNode { itemNode in
itemNode.controlsVisibilityUpdated(isVisible: visible)
}
}
self.galleryNode.updateOrientation = { [weak self] orientation in

View file

@ -3350,7 +3350,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if isSelected && value == nil {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
} else {
return nil
return UIImage()
}
}), action: { _, f in
f(.default)
@ -3383,7 +3383,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if isSelected {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
} else {
return nil
return UIImage()
}
}, action: { [weak strongSelf] _, f in
f(.default)
@ -3437,7 +3437,7 @@ final class UniversalVideoGalleryItemNode: ZoomableContentGalleryItemNode {
if isSelected {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
} else {
return nil
return UIImage()
}
}, action: { [weak self] _, f in
f(.default)

View file

@ -83,12 +83,14 @@ final class PeerChatTopTaggedMessageIdsTable: Table {
let currentTopMessageId = self.get(peerId: message.id.peerId, threadId: message.threadId, namespace: message.id.namespace)
if currentTopMessageId == nil || currentTopMessageId! < message.id {
self.set(peerId: message.id.peerId, threadId: message.threadId, namespace: message.id.namespace, id: message.id)
self.set(peerId: message.id.peerId, threadId: 0, namespace: message.id.namespace, id: message.id)
}
}
case let .Remove(indices):
for (index, _, threadId) in indices {
if let messageId = self.get(peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace), index.id == messageId {
self.set(peerId: index.id.peerId, threadId: threadId, namespace: index.id.namespace, id: nil)
self.set(peerId: index.id.peerId, threadId: 0, namespace: index.id.namespace, id: nil)
}
}
default:
@ -110,8 +112,12 @@ final class PeerChatTopTaggedMessageIdsTable: Table {
if let maybeMessageId = maybeMessageId {
var messageIdId: Int32 = maybeMessageId.id
self.valueBox.set(self.table, key: self.key(combinedId: record.peerAndThreadId, namespace: record.namespace), value: MemoryBuffer(memory: &messageIdId, capacity: 4, length: 4, freeWhenDone: false))
if record.peerAndThreadId.threadId != nil {
self.valueBox.set(self.table, key: self.key(combinedId: PeerAndThreadId(peerId: record.peerAndThreadId.peerId, threadId: 0), namespace: record.namespace), value: MemoryBuffer(memory: &messageIdId, capacity: 4, length: 4, freeWhenDone: false))
}
} else {
self.valueBox.remove(self.table, key: self.key(combinedId: record.peerAndThreadId, namespace: record.namespace), secure: false)
self.valueBox.remove(self.table, key: self.key(combinedId: PeerAndThreadId(peerId: record.peerAndThreadId.peerId, threadId: 0), namespace: record.namespace), secure: false)
}
}
}

View file

@ -3507,7 +3507,13 @@ final class PostboxImpl {
}
if let peerId = mainPeerIdForTopTaggedMessages {
for namespace in topTaggedMessageIdNamespaces {
if let messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId.peerId, threadId: peerId.threadId, namespace: namespace) {
var messageId: MessageId?
messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId.peerId, threadId: peerId.threadId, namespace: namespace)
if messageId == nil && peerId.threadId == nil {
messageId = self.peerChatTopTaggedMessageIdsTable.get(peerId: peerId.peerId, threadId: 0, namespace: namespace)
}
if let messageId {
if let index = self.messageHistoryIndexTable.getIndex(messageId) {
if let message = self.messageHistoryTable.getMessage(index) {
topTaggedMessages[namespace] = MessageHistoryTopTaggedMessage.intermediate(message)

View file

@ -338,7 +338,7 @@ extension VideoChatScreenComponent.View {
if isSelected {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: .white)
} else {
return nil
return UIImage()
}
}, action: { [weak self] _, f in
f(.default)
@ -730,7 +730,7 @@ extension VideoChatScreenComponent.View {
if output == currentOutput {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
} else {
return nil
return UIImage()
}
}, action: { [weak self] _, f in
f(.default)
@ -763,7 +763,7 @@ extension VideoChatScreenComponent.View {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_SpeakPermissionEveryone, icon: { theme in
if isMuted {
return nil
return UIImage()
} else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}
@ -777,7 +777,7 @@ extension VideoChatScreenComponent.View {
})))
items.append(.action(ContextMenuActionItem(text: environment.strings.VoiceChat_SpeakPermissionAdmin, icon: { theme in
if !isMuted {
return nil
return UIImage()
} else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.actionSheet.primaryTextColor)
}

View file

@ -161,7 +161,7 @@ private final class VoiceChatShareScreenContextItemNode: ASDisplayNode, ContextM
}
func canBeHighlighted() -> Bool {
return true
return false
}
func updateIsHighlighted(isHighlighted: Bool) {

View file

@ -12,7 +12,7 @@ public func stringForFullAuthorName(message: EngineMessage, strings: Presentatio
} else if author.isDeleted {
authorName = strings.User_DeletedAccount
} else {
authorName = author.compactDisplayTitle
authorName = author.displayTitle(strings: strings, displayOrder: nameDisplayOrder)
}
if let peer = message.peers[message.id.peerId].flatMap(EnginePeer.init), author.id != peer.id {
authorString = [authorName, peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)]

View file

@ -92,6 +92,8 @@ final class BoostSlowModeButton: HighlightTrackingButtonNode {
self?.requestUpdate()
}, queue: .mainQueue())
self.updateTimer?.start()
} else {
text = stringForDuration(0)
}
} else {
self.updateTimer?.invalidate()
@ -115,7 +117,7 @@ final class BoostSlowModeButton: HighlightTrackingButtonNode {
self.textNode.segments = segments
let textSize = self.textNode.updateLayout(size: CGSize(width: 200.0, height: 100.0), animated: true)
let totalSize = CGSize(width: textSize.width > 0.0 ? textSize.width + 38.0 : 33.0, height: 33.0)
let totalSize = CGSize(width: textSize.width > 0.0 ? textSize.width + 38.0 : 40.0, height: 40.0)
self.containerNode.bounds = CGRect(origin: .zero, size: totalSize)
self.containerNode.position = CGPoint(x: totalSize.width / 2.0, y: totalSize.height / 2.0)

View file

@ -1172,9 +1172,6 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.touchDownGestureRecognizer = recognizer
textInputNode.textView.accessibilityHint = self.textPlaceholderNode.attributedText?.string
self.isAccessibilityContainer = true
self.accessibilityElements = [textInputNode.textView]
}
private func textFieldMaxHeight(_ maxHeight: CGFloat, metrics: LayoutMetrics, bottomInset: CGFloat) -> CGFloat {
@ -2206,9 +2203,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
var rightSlowModeInset: CGFloat = 0.0
var slowModeButtonSize: CGSize = .zero
if let presentationInterfaceState = self.presentationInterfaceState, (presentationInterfaceState.boostsToUnrestrict ?? 0) > 0 {
var hasSlowmodeButton = false
if let presentationInterfaceState = self.presentationInterfaceState, (presentationInterfaceState.boostsToUnrestrict ?? 0) > 0, presentationInterfaceState.slowmodeState != nil {
slowModeButtonSize = self.slowModeButton.update(size: CGSize(width: width, height: 40.0), interfaceState: presentationInterfaceState)
rightSlowModeInset = max(0.0, slowModeButtonSize.width - 33.0)
rightSlowModeInset = max(0.0, slowModeButtonSize.width + 4.0)
hasSlowmodeButton = true
}
self.rightSlowModeInset = rightSlowModeInset
@ -2395,7 +2394,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if additionalSideInsets.right > 0.0 {
textFieldInsets.right += additionalSideInsets.right / 3.0
}
if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward {
if inputHasText || self.extendedSearchLayout || hasMediaDraft || hasForward || hasSlowmodeButton {
} else {
if let customRightAction = self.customRightAction, case .empty = customRightAction {
textFieldInsets.right = 8.0
@ -3023,6 +3022,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
var nextButtonTopRight = CGPoint(x: textInputContainerBackgroundFrame.width - accessoryButtonInset, y: textInputContainerBackgroundFrame.height - minimalInputHeight)
if self.extendedSearchLayout {
nextButtonTopRight.x -= 46.0
} else if hasSlowmodeButton {
} else if inputHasText || hasMediaDraft || hasForward {
nextButtonTopRight.x -= sendActionButtonsSize.width
}
@ -3172,7 +3172,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
var mediaActionButtonsFrame = CGRect(origin: CGPoint(x: textInputContainerBackgroundFrame.maxX + 6.0, y: textInputContainerBackgroundFrame.maxY - mediaActionButtonsSize.height), size: mediaActionButtonsSize)
if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil {
if inputHasText || self.extendedSearchLayout || hasMediaDraft || interfaceState.interfaceState.forwardMessageIds != nil || hasSlowmodeButton {
mediaActionButtonsFrame.origin.x = width + 8.0
}
transition.updateFrame(node: self.mediaActionButtons, frame: mediaActionButtonsFrame)
@ -3235,7 +3235,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
self.sendActionButtons.updateAbsoluteRect(CGRect(x: rect.origin.x + sendActionButtonsFrame.origin.x, y: rect.origin.y + sendActionButtonsFrame.origin.y, width: sendActionButtonsFrame.width, height: sendActionButtonsFrame.height), within: containerSize, transition: transition)
}
let slowModeButtonFrame = CGRect(origin: CGPoint(x: hideOffset.x + width - rightInset - 5.0 - slowModeButtonSize.width + composeButtonsOffset, y: hideOffset.y + panelHeight - minimalHeight + 6.0), size: slowModeButtonSize)
let slowModeButtonFrame = CGRect(origin: CGPoint(x: hideOffset.x + width - rightInset - 5.0 - slowModeButtonSize.width + composeButtonsOffset, y: hideOffset.y + panelHeight - minimalHeight), size: slowModeButtonSize)
transition.updateFrame(node: self.slowModeButton, frame: slowModeButtonFrame)
if let _ = interfaceState.inputTextPanelState.mediaRecordingState {
@ -4381,7 +4381,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
}
})
blurTransitionOut.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: 0.0, toRadius: sendButtonBlurOut)
blurTransitionOut.setBlur(layer: self.sendActionButtons.sendContainerNode.layer, radius: sendButtonBlurOut)
if let sendButtonRadialStatusNode = self.sendActionButtons.sendButtonRadialStatusNode {
alphaTransition.updateAlpha(node: sendButtonRadialStatusNode, alpha: 0.0)
@ -4412,7 +4412,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
if (hasText || keepSendButtonEnabled && !mediaInputIsActive && !hasSlowModeButton) {
if self.sendActionButtons.sendContainerNode.alpha.isZero && self.rightSlowModeInset.isZero {
alphaTransition.updateAlpha(node: self.sendActionButtons.sendContainerNode, alpha: 1.0)
blurTransitionIn.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: sendButtonBlurOut, toRadius: 0.0)
blurTransitionIn.setBlur(layer: self.sendActionButtons.sendContainerNode.layer, radius: 0.0)
transition.animatePositionAdditive(layer: self.sendActionButtons.sendButton.imageNode.layer, offset: CGPoint(x: -22.0, y: 18.0))
if let sendButtonRadialStatusNode = self.sendActionButtons.sendButtonRadialStatusNode {
alphaTransition.updateAlpha(node: sendButtonRadialStatusNode, alpha: 1.0)
@ -4427,7 +4427,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
strongSelf.applyUpdateSendButtonIcon()
}
})
blurTransitionOut.animateBlur(layer: self.sendActionButtons.sendContainerNode.layer, fromRadius: 0.0, toRadius: sendButtonBlurOut)
blurTransitionOut.setBlur(layer: self.sendActionButtons.sendContainerNode.layer, radius: sendButtonBlurOut)
if let sendButtonRadialStatusNode = self.sendActionButtons.sendButtonRadialStatusNode {
alphaTransition.updateAlpha(node: sendButtonRadialStatusNode, alpha: 0.0)
}

View file

@ -322,6 +322,7 @@ public final class ChatTextInputMediaRecordingButton: TGModernConversationInputM
)
let theme = self.hidesOnLock ? defaultDarkColorPresentationTheme : self.theme
blobView.setColor(theme.chat.inputPanel.actionControlFillColor)
blobView.hitTestSize = 110.0
self.micDecorationValue = blobView
return blobView
}

View file

@ -549,18 +549,18 @@ final class InnerTextSelectionTipContainerNode: ASDisplayNode {
shimmeringForegroundColor = presentationData.theme.contextMenu.primaryColor.withMultipliedAlpha(0.07)
}
let textLeftInset: CGFloat
let textRightInset: CGFloat = 8.0
var textLeftInset: CGFloat = horizontalInset
if let _ = self.iconNode.image {
textLeftInset = iconSize.width + 18.0
} else {
textLeftInset = 0.0
textLeftInset = 60.0
}
let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, minimumNumberOfLines: 0, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - horizontalInset * 2.0 - textLeftInset, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.12, cutout: nil, insets: UIEdgeInsets(), lineColor: nil, textShadowColor: nil, textStroke: nil))
let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: attributedText, backgroundColor: nil, minimumNumberOfLines: 0, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - textLeftInset - textRightInset, height: .greatestFiniteMagnitude), alignment: .left, lineSpacing: 0.12, cutout: nil, insets: UIEdgeInsets(), lineColor: nil, textShadowColor: nil, textStroke: nil))
let _ = textApply(self.arguments?.withUpdatedPlaceholderColor(shimmeringForegroundColor))
let textFrame = CGRect(origin: CGPoint(x: horizontalInset + textLeftInset, y: topInset), size: textLayout.size)
let textFrame = CGRect(origin: CGPoint(x: textLeftInset, y: topInset), size: textLayout.size)
transition.updateFrame(node: self.textNode.textNode, frame: textFrame)
if textFrame.size.height.isZero {
self.textNode.textNode.alpha = 0.0

View file

@ -827,6 +827,17 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
self.addSubnode(item.node)
}
if let tipSignal = tipSignal {
self.tipDisposable = (tipSignal
|> deliverOnMainQueue).start(next: { [weak self] tip in
guard let strongSelf = self else {
return
}
strongSelf.tip = tip
requestUpdate(.immediate)
}).strict()
}
requestUpdateAction = { [weak self] id, action in
guard let self else {
return
@ -981,17 +992,26 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
if let tip = self.tip {
let tipNode: InnerTextSelectionTipContainerNode
var tipTransition = transition
if let current = self.tipNode {
if let current = self.tipNode, current.tip == tip {
tipNode = current
} else {
tipTransition = .immediate
let previousTipNode = self.tipNode
tipNode = InnerTextSelectionTipContainerNode(presentationData: presentationData, tip: tip, isInline: true)
self.addSubnode(tipNode)
self.tipNode = tipNode
let getController = self.getController
tipNode.requestDismiss = { completion in
getController()?.dismiss(completion: completion)
}
self.addSubnode(tipNode)
self.tipNode = tipNode
if let previousTipNode = previousTipNode {
previousTipNode.animateTransitionInside(other: tipNode)
previousTipNode.removeFromSupernode()
tipNode.animateContentIn()
}
}
let tipSeparatorNode: ContextControllerActionsListSeparatorItemNode
@ -1004,12 +1024,15 @@ public final class ContextControllerActionsListStackItem: ContextControllerActio
}
let (tipSeparatorMinSize, tipSeparatorApply) = tipSeparatorNode.update(presentationData: presentationData, constrainedSize: CGSize(width: combinedSize.width, height: 10.0))
tipSeparatorNode.isHidden = self.itemNodes.isEmpty
let tipSeparatorSize = CGSize(width: combinedSize.width, height: tipSeparatorMinSize.height)
tipSeparatorApply(tipSeparatorSize, tipTransition)
let tipSeparatorFrame = CGRect(origin: nextItemOrigin, size: tipSeparatorSize)
tipTransition.updateFrame(node: tipSeparatorNode, frame: tipSeparatorFrame)
nextItemOrigin.y += tipSeparatorSize.height
combinedSize.height += tipSeparatorSize.height
if !tipSeparatorNode.isHidden {
nextItemOrigin.y += tipSeparatorSize.height
combinedSize.height += tipSeparatorSize.height
}
let tipSize = tipNode.updateLayout(widthClass: .compact, presentation: .inline, width: combinedSize.width, transition: tipTransition)
let tipFrame = CGRect(origin: nextItemOrigin, size: tipSize)
@ -1488,7 +1511,6 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
}
}
self.sourceExtractableContainer = nil
self.contentContainer.frame = CGRect(origin: CGPoint(), size: sourceSize)
self.contentContainer.layer.cornerRadius = normalCornerRadius
@ -1553,7 +1575,7 @@ public final class ContextControllerActionsStackNodeImpl: ASDisplayNode, Context
self.backgroundView.update(size: size, cornerRadius: min(30.0, size.height * 0.5), isDark: presentationData.theme.overallDarkAppearance, tintColor: .init(kind: .panel), isInteractive: true, transition: transition)
if let sourceExtractableContainer = self.sourceExtractableContainer {
transition.setFrame(view: sourceExtractableContainer.extractableContentView, frame: CGRect(origin: CGPoint(), size: size))
transition.setFrame(view: sourceExtractableContainer.extractableContentView, frame: CGRect(origin: CGPoint(x: self.backgroundContainerInset, y: self.backgroundContainerInset), size: size))
sourceExtractableContainer.updateState(state: .extracted(size: size, cornerRadius: min(30.0, size.height * 0.5), state: .animatedIn), transition: .transition(transition.containedViewLayoutTransition), completion: nil)
}
}

View file

@ -1206,7 +1206,6 @@ private final class CraftGiftPageContent: Component {
}
if let _ = view {
if case let .gift(gift) = component.result {
//TODO:localize
let giftController = GiftViewScreen(
context: component.context,
subject: .profileGift(component.context.account.peerId, gift),
@ -1834,6 +1833,7 @@ private final class SheetContainerComponent: CombinedComponent {
state.updated(transition: .spring(duration: 0.3))
} else if state.displayFailure {
craftAnotherGift()
dismiss(true)
} else {
HapticFeedback().impact(.medium)
@ -1884,6 +1884,10 @@ private final class SheetContainerComponent: CombinedComponent {
}
}
}
Queue.mainQueue().after(1.0) {
craftContext.reload()
}
}, error: { error in
switch error {
case .craftFailed:

View file

@ -120,7 +120,7 @@ private func actionForAttribute(attribute: StarGift.UniqueGift.Attribute, presen
return ContextMenuActionItem(text: title, entities: entities, entityFiles: entityFiles, enableEntityAnimations: false, customTextInsets: UIEdgeInsets(top: 0.0, left: 18.0 + 5.0, bottom: 0.0, right: 0.0), parseMarkdown: true, icon: { _ in
return nil
}, additionalLeftIcon: { theme in
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)
@ -181,7 +181,7 @@ private func actionForAttribute(attribute: StarGift.UniqueGift.Attribute, presen
return ContextMenuActionItem(text: title, entities: entities, icon: { _ in
return generateGradientFilledCircleImage(diameter: 24.0, colors: [UIColor(rgb: UInt32(bitPattern: innerColor)).cgColor, UIColor(rgb: UInt32(bitPattern: outerColor)).cgColor])
}, additionalLeftIcon: { theme in
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return isSelected ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
getController()?.dismiss(result: .dismissWithoutContent, completion: nil)

View file

@ -437,7 +437,21 @@ public class GlassBackgroundView: UIView {
if let legacyView = self.legacyView {
switch shape {
case let .roundedRect(cornerRadius):
legacyView.update(size: size, cornerRadius: cornerRadius, transition: transition)
let style: LegacyGlassView.Style
switch tintColor.kind {
case .panel:
style = .normal
case .clear:
style = .clear
case let .custom(styleValue, _):
switch styleValue {
case .clear:
style = .clear
case .default:
style = .normal
}
}
legacyView.update(size: size, cornerRadius: cornerRadius, style: style, transition: transition)
}
transition.setFrame(view: legacyView, frame: CGRect(origin: CGPoint(), size: size))
transition.setAlpha(view: legacyView, alpha: isVisible ? 1.0 : 0.0)
@ -512,19 +526,28 @@ public class GlassBackgroundView: UIView {
if let foregroundView = self.foregroundView {
let fillColor: UIColor
let borderWidthFactor: CGFloat
switch tintColor.kind {
case .panel:
borderWidthFactor = 1.0
if isDark {
fillColor = UIColor(white: 1.0, alpha: 1.0).mixedWith(.black, alpha: 1.0 - 0.11).withAlphaComponent(0.85)
} else {
fillColor = UIColor(white: 1.0, alpha: 0.7)
}
case .clear:
borderWidthFactor = 2.0
fillColor = UIColor(white: 1.0, alpha: 0.0)
case let .custom(_, color):
case let .custom(style, color):
fillColor = color
switch style {
case .clear:
borderWidthFactor = 2.0
case .default:
borderWidthFactor = 1.0
}
}
foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: outerCornerRadius * 2.0, height: outerCornerRadius * 2.0), inset: shadowInset, isDark: isDark, fillColor: fillColor)
foregroundView.image = GlassBackgroundView.generateLegacyGlassImage(size: CGSize(width: outerCornerRadius * 2.0, height: outerCornerRadius * 2.0), inset: shadowInset, borderWidthFactor: borderWidthFactor, isDark: isDark, fillColor: fillColor)
transition.setAlpha(view: foregroundView, alpha: isVisible ? 1.0 : 0.0)
} else {
if let nativeParamsView = self.nativeParamsView, let nativeView = self.nativeView {
@ -565,11 +588,21 @@ public class GlassBackgroundView: UIView {
if glassEffect == nil {
if nativeView.effect is UIGlassEffect {
if transition.animation.isImmediate {
nativeView.effect = nil
} else {
transition.animateView {
if #available(iOS 26.1, *) {
if transition.animation.isImmediate {
nativeView.effect = nil
} else {
transition.animateView {
nativeView.effect = nil
}
}
} else {
if transition.animation.isImmediate {
nativeView.effect = UIVisualEffect()
} else {
transition.animateView {
nativeView.effect = UIVisualEffect()
}
}
}
}
@ -598,6 +631,9 @@ public class GlassBackgroundView: UIView {
}
}
if let nativeParamsView = self.nativeParamsView {
transition.setFrame(view: nativeParamsView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
}
transition.setFrame(view: self.maskContainerView, frame: CGRect(origin: CGPoint(), size: CGSize(width: size.width + shadowInset * 2.0, height: size.height + shadowInset * 2.0)))
transition.setFrame(view: self.maskContentView, frame: CGRect(origin: CGPoint(x: shadowInset, y: shadowInset), size: size))
if let foregroundView = self.foregroundView {
@ -631,7 +667,7 @@ public final class GlassBackgroundContainerView: UIView {
}
public override init(frame: CGRect) {
if #available(iOS 26.0, *) {
if #available(iOS 26.0, *), !GlassBackgroundView.useCustomGlassImpl {
let effect = UIGlassContainerEffect()
effect.spacing = 7.0
let nativeView = UIVisualEffectView(effect: effect)
@ -707,6 +743,9 @@ public final class GlassBackgroundContainerView: UIView {
nativeParamsView.lumaMin = 0.8
nativeParamsView.lumaMax = 0.801
}
if let nativeParamsView = self.nativeParamsView {
transition.setFrame(view: nativeParamsView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: size))
}
transition.animateView {
nativeView.frame = CGRect(origin: CGPoint(), size: size)
@ -768,7 +807,7 @@ private extension CGContext {
}
public extension GlassBackgroundView {
static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, isDark: Bool, fillColor: UIColor) -> UIImage {
static func generateLegacyGlassImage(size: CGSize, inset: CGFloat, borderWidthFactor: CGFloat = 1.0, isDark: Bool, fillColor: UIColor) -> UIImage {
var size = size
if size == .zero {
size = CGSize(width: 2.0, height: 2.0)
@ -908,7 +947,7 @@ public extension GlassBackgroundView {
var ellipseRect = CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset)
context.fillEllipse(in: ellipseRect)
let lineWidth: CGFloat = isDark ? 0.8 : 0.8
let lineWidth: CGFloat = (isDark ? 0.8 : 0.8) * borderWidthFactor
let strokeColor: UIColor
let blendMode: CGBlendMode
let baseAlpha: CGFloat = isDark ? 0.3 : 0.6
@ -956,20 +995,6 @@ public extension GlassBackgroundView {
context.resetClip()
context.setBlendMode(.normal)
//let image = makeInnerShadowPillImageExact(size: CGSize(width: size.width - inset * 2.0, height: size.height - inset * 2.0), scale: UIScreenScale, glossColor: UIColor(white: 1.0, alpha: 1.0), borderWidth: 1.33)
/*let image = generateCircleImage(diameter: size.width - inset * 2.0, lineWidth: 0.5, color: UIColor(white: 1.0, alpha: 1.0))!
if s == 0.0 && abs(a - 0.7) < 0.1 && !isDark {
image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .normal, alpha: 1.0)
} else if s <= 0.3 && !isDark {
image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .normal, alpha: 0.7)
} else if b >= 0.2 {
let maxAlpha: CGFloat = isDark ? 0.7 : 0.8
image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .overlay, alpha: max(0.5, min(1.0, maxAlpha * s)))
} else {
image.draw(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: inset, dy: inset), blendMode: .normal, alpha: 0.5)
}*/
}
innerImage.draw(in: CGRect(origin: CGPoint(), size: size))
}.stretchableImage(withLeftCapWidth: Int(size.width * 0.5), topCapHeight: Int(size.height * 0.5))

View file

@ -81,13 +81,20 @@ private final class BackdropLayerDelegate: NSObject, CALayerDelegate {
}
final class LegacyGlassView: UIView {
enum Style {
case normal
case clear
}
private struct Params: Equatable {
let size: CGSize
let cornerRadius: CGFloat
let style: Style
init(size: CGSize, cornerRadius: CGFloat) {
init(size: CGSize, cornerRadius: CGFloat, style: Style) {
self.size = size
self.cornerRadius = cornerRadius
self.style = style
}
}
@ -109,19 +116,8 @@ final class LegacyGlassView: UIView {
self.layer.addSublayer(backdropLayer)
backdropLayer.delegate = self.backdropLayerDelegate
let blur: CGFloat
let scale: CGFloat
blur = 2.0
scale = 1.0
invokeBackdropLayerSetScaleMethod(object: backdropLayer, scale: scale)
backdropLayer.rasterizationScale = scale
if let blurFilter = CALayer.blur() {
blurFilter.setValue(blur as NSNumber, forKey: "inputRadius")
backdropLayer.filters = [blurFilter]
}
invokeBackdropLayerSetScaleMethod(object: backdropLayer, scale: 1.0)
backdropLayer.rasterizationScale = 1.0
}
}
@ -129,8 +125,9 @@ final class LegacyGlassView: UIView {
fatalError("init(coder:) has not been implemented")
}
func update(size: CGSize, cornerRadius: CGFloat, transition: ComponentTransition) {
let params = Params(size: size, cornerRadius: cornerRadius)
func update(size: CGSize, cornerRadius: CGFloat, style: Style, transition: ComponentTransition) {
let params = Params(size: size, cornerRadius: cornerRadius, style: style)
let previousParams = self.params
if self.params == params {
return
}
@ -140,6 +137,18 @@ final class LegacyGlassView: UIView {
return
}
if previousParams?.style != style {
if let blurFilter = CALayer.blur() {
switch style {
case .clear:
blurFilter.setValue(6.0 as NSNumber, forKey: "inputRadius")
case .normal:
blurFilter.setValue(2.0 as NSNumber, forKey: "inputRadius")
}
backdropLayer.filters = [blurFilter]
}
}
transition.setCornerRadius(layer: self.layer, cornerRadius: cornerRadius)
transition.setFrame(layer: backdropLayer, frame: CGRect(origin: CGPoint(), size: size))

View file

@ -228,6 +228,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.peerSelectionNode.pushedController = controller
strongSelf.push(controller)
} else {
strongSelf.selectTab(id: id)
@ -306,7 +307,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
return
}
if mainChannel.hasPermission(.manageDirect) {
if !mainChannel.isMonoForum || mainChannel.hasPermission(.manageDirect) {
let displayPeer = EnginePeer(mainChannel)
let controller = PeerSelectionControllerImpl(
@ -330,6 +331,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
)
)
controller.peerSelected = self.peerSelected
self.peerSelectionNode.pushedController = controller
self.push(controller)
} else {
peerSelected(.channel(peer), threadId)
@ -373,6 +375,7 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
)
)
controller.peerSelected = strongSelf.peerSelected
strongSelf.peerSelectionNode.pushedController = controller
strongSelf.push(controller)
} else {
peerSelected(peer, threadId)
@ -626,4 +629,16 @@ public final class PeerSelectionControllerImpl: ViewController, PeerSelectionCon
}
})
}
override public func dismiss(completion: (() -> Void)? = nil) {
guard let navigationController = self.navigationController as? NavigationController else {
return
}
var viewControllers = navigationController.viewControllers
viewControllers.removeAll(where: { $0 === self })
if let pushedController = self.peerSelectionNode.pushedController {
viewControllers.removeAll(where: { $0 === pushedController })
}
navigationController.setViewControllers(viewControllers, animated: true)
}
}

View file

@ -102,6 +102,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
private var countPanelNode: PeersCountPanelNode?
weak var pushedController: ViewController?
private var readyValue = Promise<Bool>()
var ready: Signal<Bool, NoError> {
return self.readyValue.get()
@ -286,10 +288,12 @@ final class PeerSelectionControllerNode: ASDisplayNode {
return
}
self.chatListNode?.clearHighlightAnimated(true)
self.mainContainerNode?.currentItemNode.clearHighlightAnimated(true)
self.requestOpenPeer?(mainPeer, peer.id.toInt64())
})
} else {
self.chatListNode?.clearHighlightAnimated(true)
self.mainContainerNode?.currentItemNode.clearHighlightAnimated(true)
self.requestOpenPeer?(peer, threadId)
}
}
@ -348,6 +352,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
replaceImpl = { [weak controller] c in
controller?.replace(with: c)
}
strongSelf.pushedController = controller
strongSelf.controller?.push(controller)
}
self.addSubnode(mainContainerNode)

View file

@ -39,6 +39,7 @@ swift_library(
"//submodules/TelegramUI/Components/TelegramAccountAuxiliaryMethods",
"//submodules/TelegramUI/Components/PeerSelectionController",
"//submodules/TelegramUI/Components/ContextMenuScreen",
"//submodules/TelegramUI/Components/NavigationBarImpl",
],
visibility = [
"//visibility:public",

View file

@ -31,6 +31,7 @@ import TelegramUIDeclareEncodables
import TelegramAccountAuxiliaryMethods
import PeerSelectionController
import ContextMenuScreen
import NavigationBarImpl
private var installedSharedLogger = false
@ -194,6 +195,10 @@ public class ShareRootControllerImpl {
public init(initializationData: ShareRootControllerInitializationData, getExtensionContext: @escaping () -> NSExtensionContext?) {
self.initializationData = initializationData
self.getExtensionContext = getExtensionContext
defaultNavigationBarImpl = { presentationData in
return NavigationBarImpl(presentationData: presentationData)
}
}
deinit {

View file

@ -167,6 +167,7 @@ public final class VideoPlaybackControlsComponent: Component {
self.leftIconView.update(size: leftButtonFrame.size)
transition.setAlpha(view: self.leftIconView, alpha: areSideButtonsVisible ? 1.0 : 0.0)
transition.setBlur(layer: self.leftIconView.layer, radius: areSideButtonsVisible ? 0.0 : 10.0)
self.leftButtonBackgroundView.isUserInteractionEnabled = areSideButtonsVisible
transition.setFrame(view: self.rightButtonBackgroundView, frame: rightButtonFrame)
self.rightButtonBackgroundView.update(size: rightButtonFrame.size, cornerRadius: rightButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: areSideButtonsVisible, transition: transition)
@ -174,6 +175,7 @@ public final class VideoPlaybackControlsComponent: Component {
self.rightIconView.update(size: rightButtonFrame.size)
transition.setAlpha(view: self.rightIconView, alpha: areSideButtonsVisible ? 1.0 : 0.0)
transition.setBlur(layer: self.rightIconView.layer, radius: areSideButtonsVisible ? 0.0 : 10.0)
self.rightButtonBackgroundView.isUserInteractionEnabled = areSideButtonsVisible
transition.setFrame(view: self.centerButtonBackgroundView, frame: centerButtonFrame)
self.centerButtonBackgroundView.update(size: centerButtonFrame.size, cornerRadius: centerButtonFrame.height * 0.5, isDark: true, tintColor: buttonsTintColor, isInteractive: true, isVisible: component.isVisible, transition: transition)

View file

@ -379,6 +379,9 @@ extension ChatControllerImpl {
if previousState.chatTitleContent != contentData.state.chatTitleContent {
animated = true
}
if previousState.slowmodeState != contentData.state.slowmodeState || previousState.boostsToUnrestrict != contentData.state.boostsToUnrestrict {
animated = true
}
var transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.4, curve: .spring) : .immediate
if let forceAnimationTransition {

View file

@ -743,6 +743,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !self.presentationData.theme.overallDarkAppearance {
preferredGlassType = .default
}
if self.context.sharedContext.immediateExperimentalUISettings.forceClearGlass {
preferredGlassType = .clear
}
if self.presentationInterfaceState.preferredGlassType != preferredGlassType {
self.updateChatPresentationInterfaceState(transition: .immediate, interactive: false, { state in
return state.updatedPreferredGlassType(preferredGlassType)
@ -6411,6 +6414,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if !self.presentationData.theme.overallDarkAppearance {
preferredGlassType = .default
}
if self.context.sharedContext.immediateExperimentalUISettings.forceClearGlass {
preferredGlassType = .clear
}
self.updateChatPresentationInterfaceState(animated: false, interactive: false, { state in
var state = state
state = state.updatedPresentationReady(self.didSetPresentationData)

View file

@ -2324,8 +2324,12 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
var keyboardButtonsMessage = view.topTaggedMessages.first
if keyboardButtonsMessage != nil && keyboardButtonsMessage?.threadId != chatLocation.threadId {
keyboardButtonsMessage = nil
if let keyboardButtonsMessageValue = keyboardButtonsMessage {
if keyboardButtonsMessageValue.threadId != chatLocation.threadId {
if chatLocation.threadId != nil {
keyboardButtonsMessage = nil
}
}
}
if let keyboardButtonsMessageValue = keyboardButtonsMessage, keyboardButtonsMessageValue.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with({ $0 })) {
keyboardButtonsMessage = nil
@ -3903,7 +3907,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
loadState = .messages
} else if let historyView = strongSelf.historyView {
if historyView.filteredEntries.isEmpty {
if let firstEntry = historyView.originalView.entries.first {
if historyView.originalView.isLoading {
loadState = .loading(false)
} else if let firstEntry = historyView.originalView.entries.first {
var emptyType = ChatHistoryNodeLoadState.EmptyType.generic
for media in firstEntry.message.media {
if let action = media as? TelegramMediaAction {

View file

@ -97,7 +97,7 @@ final class NotificationItemContainerNode: ASDisplayNode {
if let contentNode = self.contentNode {
let inset: CGFloat = 8.0
var contentInsets = UIEdgeInsets(top: inset, left: inset + layout.safeInsets.left, bottom: inset, right: inset + layout.safeInsets.right)
var contentInsets = UIEdgeInsets(top: inset + layout.safeInsets.left, left: inset, bottom: inset, right: inset + layout.safeInsets.right)
if let statusBarHeight = layout.statusBarHeight, statusBarHeight >= 39.0 {
if layout.deviceMetrics.hasDynamicIsland {
@ -109,7 +109,7 @@ final class NotificationItemContainerNode: ASDisplayNode {
}
}
let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left)
let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
let contentWidth = containerWidth - contentInsets.left - contentInsets.right
let contentHeight = contentNode.updateLayout(width: contentWidth, transition: transition)

View file

@ -70,6 +70,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var debugRatingLayout: Bool
public var enableUpdates: Bool
public var enablePWA: Bool
public var forceClearGlass: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -117,7 +118,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
allForumsHaveTabs: false,
debugRatingLayout: false,
enableUpdates: false,
enablePWA: false
enablePWA: false,
forceClearGlass: false
)
}
@ -166,7 +168,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
allForumsHaveTabs: Bool,
debugRatingLayout: Bool,
enableUpdates: Bool,
enablePWA: Bool
enablePWA: Bool,
forceClearGlass: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -213,6 +216,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.debugRatingLayout = debugRatingLayout
self.enableUpdates = enableUpdates
self.enablePWA = enablePWA
self.forceClearGlass = forceClearGlass
}
public init(from decoder: Decoder) throws {
@ -263,6 +267,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.debugRatingLayout = try container.decodeIfPresent(Bool.self, forKey: "debugRatingLayout") ?? false
self.enableUpdates = try container.decodeIfPresent(Bool.self, forKey: "enableUpdates") ?? false
self.enablePWA = try container.decodeIfPresent(Bool.self, forKey: "enablePWA") ?? false
self.forceClearGlass = try container.decodeIfPresent(Bool.self, forKey: "forceClearGlass") ?? false
}
public func encode(to encoder: Encoder) throws {
@ -313,6 +318,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encodeIfPresent(self.debugRatingLayout, forKey: "debugRatingLayout")
try container.encodeIfPresent(self.enableUpdates, forKey: "enableUpdates")
try container.encodeIfPresent(self.enablePWA, forKey: "enablePWA")
try container.encodeIfPresent(self.forceClearGlass, forKey: "forceClearGlass")
}
}

View file

@ -5,6 +5,7 @@
#import "NSWeakReference.h"
#import <UIKitRuntimeUtils/UIKitUtils.h>
#import <dlfcn.h>
@interface UIViewControllerPresentingProxy : UIViewController
@ -457,8 +458,6 @@ static NSMutableDictionary<NSString *, TrustedWebRecord *> *trustedWebRecords()
return value;
}
void WKWebsiteDataStoreReinitializeAppBoundDomains(CFTypeRef dataStoreRef);
@implementation WebHelpers
+ (NSArray<NSString *> * _Nonnull)threadSafeTrustedDomains {
@ -504,7 +503,16 @@ void WKWebsiteDataStoreReinitializeAppBoundDomains(CFTypeRef dataStoreRef);
}
+ (void)forceRefreshTrustedDomains:(WKWebsiteDataStore * _Nonnull)websiteDataStore {
WKWebsiteDataStoreReinitializeAppBoundDomains((__bridge CFTypeRef)(websiteDataStore));
static void (*reinitializeFunction)(CFTypeRef dataStoreRef) = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *nameString = [NSString stringWithFormat:@"WK%@", @"WebsiteDataStoreReinitializeAppBoundDomains"];
reinitializeFunction = dlsym(RTLD_DEFAULT, [nameString UTF8String]);
});
if (reinitializeFunction) {
reinitializeFunction((__bridge CFTypeRef)(websiteDataStore));
}
}
@end
@ -513,8 +521,10 @@ void WKWebsiteDataStoreReinitializeAppBoundDomains(CFTypeRef dataStoreRef);
- (id)_65087dc8_objectForInfoDictionaryKey:(NSString *)key {
if ([key isEqualToString:@"WKAppBoundDomains"]) {
//NSLog(@"Returning trusted domains: %@", [WebHelpers threadSafeTrustedDomains]);
return [WebHelpers threadSafeTrustedDomains];
NSArray *result = [WebHelpers threadSafeTrustedDomains];
if (result.count != 0) {
return result;
}
}
return [self _65087dc8_objectForInfoDictionaryKey:key];
}

View file

@ -25,6 +25,7 @@ arch_specific_cflags = select({
"@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags,
"//build-system:ios_sim_arm64": common_flags + arm64_specific_flags,
"@build_bazel_rules_apple//apple:ios_x86_64": common_flags + x86_64_specific_flags,
"//conditions:default": common_flags,
})
cc_library(

View file

@ -29,6 +29,10 @@ genrule(
BUILD_ARCH="sim_arm64"
elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then
BUILD_ARCH="x86_64"
elif [ "$(TARGET_CPU)" == "k8" ] || [ "$(TARGET_CPU)" == "x86_64" ]; then
BUILD_ARCH="linux_x86_64"
elif [ "$(TARGET_CPU)" == "aarch64" ] || [ "$(TARGET_CPU)" == "arm64" ]; then
BUILD_ARCH="linux_arm64"
else
echo "Unsupported architecture $(TARGET_CPU)"
fi
@ -63,6 +67,20 @@ cc_library(
srcs = [":Public/opus/lib/lib" + x + ".a" for x in libs],
)
cc_library(
name = "opus_cc",
hdrs = [":Public/opus/" + x for x in headers],
includes = [
"Public",
],
deps = [
":opus_lib",
],
visibility = [
"//visibility:public",
],
)
objc_library(
name = "opus",
module_name = "opus",

View file

@ -12,8 +12,6 @@ OPT_CFLAGS="-Os -g"
OPT_LDFLAGS=""
OPT_CONFIG_ARGS=""
DEVELOPER=`xcode-select -print-path`
OUTPUTDIR="$BUILD_DIR/Public"
# where we will keep our sources and build from.
@ -28,7 +26,27 @@ mkdir -p $INTERDIR
tar zxf "$BUILD_DIR/$SOURCE_CODE_ARCHIVE" -C $SRCDIR
cd "${SRCDIR}/opus-"*
if [ "${ARCH}" == "x86_64" ]; then
if [ "${ARCH}" = "linux_x86_64" ] || [ "${ARCH}" = "linux_arm64" ]; then
mkdir -p "${INTERDIR}"
# Keep it simple and portable for container builds.
./configure \
--disable-shared \
--enable-static \
--with-pic \
--disable-extra-programs \
--disable-doc \
--disable-asm \
--enable-intrinsics \
${OPT_CONFIG_ARGS} \
--prefix="${INTERDIR}" \
CFLAGS="$CFLAGS ${OPT_CFLAGS} -fPIC" \
LDFLAGS="$LDFLAGS ${OPT_LDFLAGS}"
make -j"$(getconf _NPROCESSORS_ONLN || echo 4)"
make install
exit 0
elif [ "${ARCH}" == "x86_64" ]; then
PLATFORM="iphonesimulator"
EXTRA_CFLAGS="-arch ${ARCH}"
EXTRA_CONFIG="--host=x86_64-apple-darwin"

View file

@ -355,9 +355,15 @@ absl_sources = [
"absl/types/bad_variant_access.cc",
]
absl_inc_files = [
f
for f in glob(["absl/**/*.inc"])
if f not in absl_headers
]
cc_library(
name = "absl",
hdrs = absl_headers,
hdrs = absl_headers + absl_inc_files,
srcs = absl_sources,
cxxopts = [
"-std=c++17",

View file

@ -6,6 +6,7 @@ arch_specific_crc32c_sources = select({
"//build-system:ios_sim_arm64": [
"third_party/crc32c/src/crc32c_arm64.cc",
],
"//conditions:default": [],
})
crc32c_sources = ["third_party/crc32c/src/" + x for x in [

View file

@ -37,6 +37,7 @@ arm64_specific_flags = [
arch_specific_cflags = select({
"@build_bazel_rules_apple//apple:ios_arm64": common_flags + arm64_specific_flags,
"//build-system:ios_sim_arm64": common_flags + arm64_specific_flags,
"//conditions:default": [],
})
optimization_flags = select({

View file

@ -1,5 +1,5 @@
{
"app": "12.4",
"app": "12.4.1",
"xcode": "26.2",
"bazel": "8.4.2:45e9388abf21d1107e146ea366ad080eb93cb6a5f3a4a3b048f78de0bc3faffa",
"macos": "26"