Merge commit 'a329f55dce' into formatted-date-api

# Conflicts:
#	submodules/TelegramCore/Sources/State/Serialization.swift
This commit is contained in:
Mikhail Filimonov 2026-02-10 11:48:39 +04:00
commit e294569f5d
81 changed files with 927 additions and 332 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

@ -117,7 +117,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Login_Announce_Notify, icon: { theme in
if !announceSignUp {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { [weak self] _, a in
@ -128,7 +128,7 @@ final class AuthorizationSequenceSignUpController: ViewController {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Login_Announce_DontNotify, icon: { theme in
if announceSignUp {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, iconPosition: .left, action: { [weak self] _, a in

View file

@ -57,6 +57,7 @@ swift_library(
"//submodules/TelegramUI/Components/GlassBarButtonComponent",
"//submodules/TelegramUI/Components/SearchInputPanelComponent",
"//submodules/TelegramUI/Components/GlassControls",
"//submodules/UIKitRuntimeUtils",
],
visibility = [
"//visibility:public",

View file

@ -24,6 +24,7 @@ import DeviceModel
import LegacyMediaPickerUI
import PassKit
import AlertComponent
import UIKitRuntimeUtils
private final class TonSchemeHandler: NSObject, WKURLSchemeHandler {
private final class PendingTask {
@ -217,6 +218,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
var cancelInteractiveTransitionGestures: () -> Void = {}
private var tempFile: TempBoxFile?
private var disposeTrustedDomain: (() -> Void)?
init(context: AccountContext, presentationData: PresentationData, url: String, preferredConfiguration: WKWebViewConfiguration? = nil) {
self.context = context
@ -264,6 +266,30 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
configuration.userContentController = contentController
configuration.applicationNameForUserAgent = computedUserAgent()
}
if context.sharedContext.immediateExperimentalUISettings.enablePWA {
if #available(iOS 17.0, *) {
if let parsedUrl = URL(string: url), let host = parsedUrl.host {
let rootPath = context.sharedContext.applicationBindings.containerPath + "/telegram-data"
let pwaPath = rootPath + "/pwa"
let uuidPath = pwaPath + "/uuid_\(host)"
let uuid: UUID
if let value = try? String(contentsOf: URL(fileURLWithPath: uuidPath), encoding: .utf8) {
uuid = UUID(uuidString: value)!
} else {
uuid = UUID()
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: pwaPath), withIntermediateDirectories: true)
let _ = try? uuid.uuidString.write(to: URL(fileURLWithPath: uuidPath), atomically: true, encoding: .utf8)
}
configuration.websiteDataStore = WKWebsiteDataStore(forIdentifier: uuid)
configuration.limitsNavigationsToAppBoundDomains = true
disposeTrustedDomain = WebHelpers.addTrustedDomain(host)
WebHelpers.forceRefreshTrustedDomains(configuration.websiteDataStore)
}
}
}
self.webView = WebView(frame: CGRect(), configuration: configuration)
self.webView.allowsLinkPreview = true
@ -273,18 +299,16 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
}
var title: String = ""
var request: URLRequest?
if url.hasPrefix("file://") {
var updatedPath = url
let tempFile = TempBox.shared.file(path: url.replacingOccurrences(of: "file://", with: ""), fileName: "file.xlsx")
updatedPath = tempFile.path
self.tempFile = tempFile
let request = URLRequest(url: URL(fileURLWithPath: updatedPath))
self.webView.load(request)
request = URLRequest(url: URL(fileURLWithPath: updatedPath))
} else if let parsedUrl = URL(string: url) {
let request = URLRequest(url: parsedUrl)
self.webView.load(request)
request = URLRequest(url: parsedUrl)
title = getDisplayUrl(url, hostOnly: true)
}
@ -299,6 +323,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.webView.backgroundColor = presentationData.theme.list.plainBackgroundColor
self.webView.alpha = 0.0
if let request {
self.webView.load(request)
}
self.webView.allowsBackForwardNavigationGestures = true
self.webView.scrollView.delegate = self
self.webView.scrollView.clipsToBounds = false
@ -360,6 +388,8 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU
self.faviconDisposable.dispose()
self.instantPageDisposable.dispose()
self.disposeTrustedDomain?()
}
private func handleScriptMessage(_ message: WKScriptMessage) {

View file

@ -3742,7 +3742,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
items.append(.action(ContextMenuActionItem(text: strings.Chat_ContextViewAsTopics, icon: { theme in
if !isViewingAsTopics {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak sourceController] _, a in
@ -3778,7 +3778,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
items.append(.action(ContextMenuActionItem(text: strings.Chat_ContextViewAsMessages, icon: { theme in
if isViewingAsTopics {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak sourceController] _, a in
@ -5439,14 +5439,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})
}
if let self, case let .channel(channel) = peer.peer, channel.flags.contains(.isCreator) {
let _ = (self.context.engine.peers.getFutureCreatorAfterLeave(peerId: channel.id)
let shouldCheckFutureCreator: Bool
if case let .channel(channel) = peer.peer, channel.flags.contains(.isCreator) {
shouldCheckFutureCreator = true
} else if case let .legacyGroup(group) = peer.peer, case .creator = group.role {
shouldCheckFutureCreator = true
} else {
shouldCheckFutureCreator = false
}
if let self, shouldCheckFutureCreator {
let _ = (self.context.engine.peers.getFutureCreatorAfterLeave(peerId: peer.peerId)
|> deliverOnMainQueue).start(next: { [weak self] nextCreator in
guard let self else {
return
}
if let nextCreator, let peer = peer.peer {
self.presentLeaveChannelConfirmation(peer: peer, nextCreator: nextCreator, completion: { commit in
self.presentLeaveChatConfirmation(peer: peer, nextCreator: nextCreator, completion: { commit in
if commit {
proceed()
}
@ -5913,14 +5922,24 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
removed()
})
}
let shouldCheckFutureCreator: Bool
if case let .channel(channel) = peer.peer, channel.flags.contains(.isCreator) {
let _ = (self.context.engine.peers.getFutureCreatorAfterLeave(peerId: channel.id)
shouldCheckFutureCreator = true
} else if case let .legacyGroup(group) = peer.peer, case .creator = group.role {
shouldCheckFutureCreator = true
} else {
shouldCheckFutureCreator = false
}
if shouldCheckFutureCreator {
let _ = (self.context.engine.peers.getFutureCreatorAfterLeave(peerId: peer.peerId)
|> deliverOnMainQueue).start(next: { [weak self] nextCreator in
guard let self else {
return
}
if let nextCreator, let peer = peer.peer {
self.presentLeaveChannelConfirmation(peer: peer, nextCreator: nextCreator, completion: { commit in
self.presentLeaveChatConfirmation(peer: peer, nextCreator: nextCreator, completion: { commit in
if commit {
proceed()
} else {

View file

@ -12,7 +12,7 @@ import PeerInfoUI
import OwnershipTransferController
extension ChatListControllerImpl {
func presentLeaveChannelConfirmation(peer: EnginePeer, nextCreator: EnginePeer, completion: @escaping (Bool) -> Void) {
func presentLeaveChatConfirmation(peer: EnginePeer, nextCreator: EnginePeer, completion: @escaping (Bool) -> Void) {
Task { @MainActor in
let accountPeer = await (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: self.context.account.peerId))).get()

View file

@ -5587,7 +5587,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
}
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_Search_Messages_Menu_AllChats, icon: { theme in
return scope == .everywhere ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return scope == .everywhere ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { [weak self] _, f in
guard let self else {
return
@ -5596,7 +5596,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.searchScopePromise.set(.everywhere)
})))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_Search_Messages_Menu_PrivateChats, icon: { theme in
return scope == .privateChats ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return scope == .privateChats ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { [weak self] _, f in
guard let self else {
return
@ -5605,7 +5605,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.searchScopePromise.set(.privateChats)
})))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_Search_Messages_Menu_GroupChats, icon: { theme in
return scope == .groups ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return scope == .groups ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { [weak self] _, f in
guard let self else {
return
@ -5614,7 +5614,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.searchScopePromise.set(.groups)
})))
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.ChatList_Search_Messages_Menu_Channels, icon: { theme in
return scope == .channels ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return scope == .channels ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { [weak self] _, f in
guard let self else {
return

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)
@ -109,7 +110,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case playerV2(Bool)
case devRequests(Bool)
case enableUpdates(Bool)
case fakeAds(Bool)
case pwa(Bool)
case enableLocalTranslation(Bool)
case preferredVideoCodec(Int, String, String?, Bool)
case disableVideoAspectScaling(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, .fakeAds, .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:
@ -258,7 +261,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 54
case .devRequests:
return 55
case .fakeAds:
case .pwa:
return 56
case .enableLocalTranslation:
return 57
@ -1200,7 +1203,18 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
case .resetWebViewCache:
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Clear Web View Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
WKWebsiteDataStore.default().removeData(ofTypes: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ })
WKWebsiteDataStore.default().removeData(ofTypes: [
WKWebsiteDataTypeDiskCache,
WKWebsiteDataTypeOfflineWebApplicationCache,
WKWebsiteDataTypeMemoryCache,
WKWebsiteDataTypeLocalStorage,
WKWebsiteDataTypeCookies,
WKWebsiteDataTypeSessionStorage,
WKWebsiteDataTypeIndexedDBDatabases,
WKWebsiteDataTypeWebSQLDatabases,
WKWebsiteDataTypeFetchCache,
WKWebsiteDataTypeServiceWorkerRegistrations
], modifiedSince: Date(timeIntervalSince1970: 0), completionHandler:{ })
})
case .optimizeDatabase:
return ItemListActionItem(presentationData: presentationData, systemStyle: .glass, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
@ -1268,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
@ -1392,12 +1416,12 @@ private enum DebugControllerEntry: ItemListNodeEntry {
})
}).start()
})
case let .fakeAds(value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Fake Ads", value: value, sectionId: self.section, style: .blocks, updated: { value in
case let .pwa(value):
return ItemListSwitchItem(presentationData: presentationData, systemStyle: .glass, title: "Test1", 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.fakeAds = value
settings.enablePWA = value
return PreferencesEntry(settings)
})
}).start()
@ -1488,7 +1512,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}
}
private func debugControllerEntries(sharedContext: SharedAccountContext, presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, networkSettings: NetworkSettings?, hasLegacyAppData: Bool, useBetaFeatures: Bool) -> [DebugControllerEntry] {
private func debugControllerEntries(context: AccountContext?, sharedContext: SharedAccountContext, presentationData: PresentationData, loggingSettings: LoggingSettings, mediaInputSettings: MediaInputSettings, experimentalSettings: ExperimentalUISettings, networkSettings: NetworkSettings?, hasLegacyAppData: Bool, useBetaFeatures: Bool) -> [DebugControllerEntry] {
var entries: [DebugControllerEntry] = []
let isMainApp = sharedContext.applicationBindings.isMainApp
@ -1544,6 +1568,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
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
@ -1572,7 +1597,18 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.playerV2(experimentalSettings.playerV2))
entries.append(.devRequests(experimentalSettings.devRequests))
entries.append(.fakeAds(experimentalSettings.fakeAds))
if let data = context?.currentAppConfiguration.with({ $0 }).data {
var displayPwa = false
if let _ = data["ios_display_pwa"] {
displayPwa = true
} else if let isDev = data["dev"] as? Double, isDev == 1.0 {
displayPwa = true
}
if displayPwa {
entries.append(.pwa(experimentalSettings.enablePWA))
}
}
entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation))
entries.append(.enableUpdates(experimentalSettings.enableUpdates))
}
@ -1658,7 +1694,7 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
}
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text("Debug"), leftNavigationButton: leftNavigationButton, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: debugControllerEntries(sharedContext: sharedContext, presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, networkSettings: networkSettings, hasLegacyAppData: hasLegacyAppData, useBetaFeatures: useBetaFeatures), style: .blocks)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: debugControllerEntries(context: context, sharedContext: sharedContext, presentationData: presentationData, loggingSettings: loggingSettings, mediaInputSettings: mediaInputSettings, experimentalSettings: experimentalSettings, networkSettings: networkSettings, hasLegacyAppData: hasLegacyAppData, useBetaFeatures: useBetaFeatures), style: .blocks)
return (controllerState, (listState, arguments))
}

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

@ -166,13 +166,16 @@ class LegacyBufferReader {
if count == 0 {
return 0
}
else if count > 0 && count <= 4 || self.offset + UInt(count) <= self.buffer._size {
var value: Int32 = 0
memcpy(&value, self.buffer.data?.advanced(by: Int(self.offset)), count)
self.offset += UInt(count)
return value
guard count > 0, count <= 4, self.offset + UInt(count) <= self.buffer._size else {
return nil
}
return nil
guard let bufferData = self.buffer.data else {
return nil
}
var value: Int32 = 0
memcpy(&value, bufferData.advanced(by: Int(self.offset)), count)
self.offset += UInt(count)
return value
}
func readBuffer(_ count: Int) -> LegacyBuffer? {

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

@ -401,7 +401,7 @@ public func storageUsageExceptionsScreen(
if currentValue == value {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}, action: { _, f in
applyValue(value)

View file

@ -1173,6 +1173,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1094555409] = { return Api.Update.parse_updateNotifySettings($0) }
dict[-1955438642] = { return Api.Update.parse_updatePaidReactionPrivacy($0) }
dict[-337610926] = { return Api.Update.parse_updatePeerBlocked($0) }
dict[1115654513] = { return Api.Update.parse_updatePeerHistoryNoForward($0) }
dict[-1147422299] = { return Api.Update.parse_updatePeerHistoryTTL($0) }
dict[-1263546448] = { return Api.Update.parse_updatePeerLocated($0) }
dict[1786671974] = { return Api.Update.parse_updatePeerSettings($0) }

View file

@ -969,6 +969,14 @@ public extension Api {
self.peerId = peerId
}
}
public class Cons_updatePeerHistoryNoForward {
public var flags: Int32
public var peer: Api.Peer
public init(flags: Int32, peer: Api.Peer) {
self.flags = flags
self.peer = peer
}
}
public class Cons_updatePeerHistoryTTL {
public var flags: Int32
public var peer: Api.Peer
@ -1531,6 +1539,7 @@ public extension Api {
case updateNotifySettings(Cons_updateNotifySettings)
case updatePaidReactionPrivacy(Cons_updatePaidReactionPrivacy)
case updatePeerBlocked(Cons_updatePeerBlocked)
case updatePeerHistoryNoForward(Cons_updatePeerHistoryNoForward)
case updatePeerHistoryTTL(Cons_updatePeerHistoryTTL)
case updatePeerLocated(Cons_updatePeerLocated)
case updatePeerSettings(Cons_updatePeerSettings)
@ -2488,6 +2497,13 @@ public extension Api {
serializeInt32(_data.flags, buffer: buffer, boxed: false)
_data.peerId.serialize(buffer, true)
break
case .updatePeerHistoryNoForward(let _data):
if boxed {
buffer.appendInt32(1115654513)
}
serializeInt32(_data.flags, buffer: buffer, boxed: false)
_data.peer.serialize(buffer, true)
break
case .updatePeerHistoryTTL(let _data):
if boxed {
buffer.appendInt32(-1147422299)
@ -3192,6 +3208,8 @@ public extension Api {
return ("updatePaidReactionPrivacy", [("`private`", _data.`private` as Any)])
case .updatePeerBlocked(let _data):
return ("updatePeerBlocked", [("flags", _data.flags as Any), ("peerId", _data.peerId as Any)])
case .updatePeerHistoryNoForward(let _data):
return ("updatePeerHistoryNoForward", [("flags", _data.flags as Any), ("peer", _data.peer as Any)])
case .updatePeerHistoryTTL(let _data):
return ("updatePeerHistoryTTL", [("flags", _data.flags as Any), ("peer", _data.peer as Any), ("ttlPeriod", _data.ttlPeriod as Any)])
case .updatePeerLocated(let _data):
@ -5158,6 +5176,22 @@ public extension Api {
return nil
}
}
public static func parse_updatePeerHistoryNoForward(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Api.Peer?
if let signature = reader.readInt32() {
_2 = Api.parse(reader, signature: signature) as? Api.Peer
}
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updatePeerHistoryNoForward(Cons_updatePeerHistoryNoForward(flags: _1!, peer: _2!))
}
else {
return nil
}
}
public static func parse_updatePeerHistoryTTL(_ reader: BufferReader) -> Update? {
var _1: Int32?
_1 = reader.readInt32()

View file

@ -3191,23 +3191,6 @@ public extension Api.functions.channels {
})
}
}
public extension Api.functions.channels {
static func editCreator(channel: Api.InputChannel, userId: Api.InputUser, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-1892102881)
channel.serialize(buffer, true)
userId.serialize(buffer, true)
password.serialize(buffer, true)
return (FunctionDescription(name: "channels.editCreator", parameters: [("channel", String(describing: channel)), ("userId", String(describing: userId)), ("password", String(describing: password))]), 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.channels {
static func editLocation(channel: Api.InputChannel, geoPoint: Api.InputGeoPoint, address: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer()
@ -3371,21 +3354,6 @@ public extension Api.functions.channels {
})
}
}
public extension Api.functions.channels {
static func getFutureCreatorAfterLeave(channel: Api.InputChannel) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.User>) {
let buffer = Buffer()
buffer.appendInt32(-1610016593)
channel.serialize(buffer, true)
return (FunctionDescription(name: "channels.getFutureCreatorAfterLeave", parameters: [("channel", String(describing: channel))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in
let reader = BufferReader(buffer)
var result: Api.User?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.User
}
return result
})
}
}
public extension Api.functions.channels {
static func getGroupsForDiscussion() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.Chats>) {
let buffer = Buffer()
@ -5742,6 +5710,23 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func editChatCreator(peer: Api.InputPeer, userId: Api.InputUser, password: Api.InputCheckPasswordSRP) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(-146556841)
peer.serialize(buffer, true)
userId.serialize(buffer, true)
password.serialize(buffer, true)
return (FunctionDescription(name: "messages.editChatCreator", parameters: [("peer", String(describing: peer)), ("userId", String(describing: userId)), ("password", String(describing: password))]), 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.messages {
static func editChatDefaultBannedRights(peer: Api.InputPeer, bannedRights: Api.ChatBannedRights) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
@ -6781,6 +6766,21 @@ public extension Api.functions.messages {
})
}
}
public extension Api.functions.messages {
static func getFutureChatCreatorAfterLeave(peer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.User>) {
let buffer = Buffer()
buffer.appendInt32(998051494)
peer.serialize(buffer, true)
return (FunctionDescription(name: "messages.getFutureChatCreatorAfterLeave", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.User? in
let reader = BufferReader(buffer)
var result: Api.User?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.User
}
return result
})
}
}
public extension Api.functions.messages {
static func getGameHighScores(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.messages.HighScores>) {
let buffer = Buffer()

View file

@ -571,7 +571,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
if isSelected && value == nil {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}), action: { [weak self] _, f in
scheduleTooltip(nil)

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

@ -2010,6 +2010,26 @@ private func finalStateWithUpdatesAndServerTime(accountPeerId: PeerId, postbox:
updatedState.updateStarGiftAuctionMyState(giftId: giftId, state: GiftAuctionContext.State.MyState(apiAuctionUserState: userState))
case let .updateEmojiGameInfo(updateEmojiGameInfoData):
updatedState.updateEmojiGameInfo(info: EmojiGameInfo(apiEmojiGameInfo: updateEmojiGameInfoData.info))
case let .updatePeerHistoryNoForward(updatePeerHistoryNoForwardData):
let (flags, peer) = (updatePeerHistoryNoForwardData.flags, updatePeerHistoryNoForwardData.peer)
let peerId = peer.peerId
updatedState.updateCachedPeerData(peerId, { current in
if let previous = current as? CachedUserData {
var updatedFlags = previous.flags
if (flags & (1 << 0)) != 0 {
updatedFlags.insert(.myCopyProtectionEnabled)
} else {
updatedFlags.remove(.myCopyProtectionEnabled)
}
if (flags & (1 << 1)) != 0 {
updatedFlags.insert(.copyProtectionEnabled)
} else {
updatedFlags.remove(.copyProtectionEnabled)
}
return previous.withUpdatedFlags(updatedFlags)
}
return current
})
default:
break
}

View file

@ -210,7 +210,7 @@ public class BoxedMessage: NSObject {
public class Serialization: NSObject, MTSerialization {
public func currentLayer() -> UInt {
return 225
return 223
}
public func parseMessage(_ data: Data!) -> Any! {

View file

@ -627,6 +627,8 @@ public struct CachedUserFlags: OptionSet {
public static let canViewRevenue = CachedUserFlags(rawValue: 1 << 5)
public static let botCanManageEmojiStatus = CachedUserFlags(rawValue: 1 << 6)
public static let displayGiftButton = CachedUserFlags(rawValue: 1 << 7)
public static let myCopyProtectionEnabled = CachedUserFlags(rawValue: 1 << 8)
public static let copyProtectionEnabled = CachedUserFlags(rawValue: 1 << 9)
}
public final class EditableBotInfo: PostboxCoding, Equatable {

View file

@ -4,7 +4,7 @@ import Postbox
import TelegramApi
public enum ChannelOwnershipTransferError {
public enum ChatOwnershipTransferError {
case generic
case twoStepAuthMissing
case twoStepAuthTooFresh(Int32)
@ -20,12 +20,12 @@ public enum ChannelOwnershipTransferError {
case userBlocked
}
func _internal_checkOwnershipTranfserAvailability(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, memberId: PeerId) -> Signal<Never, ChannelOwnershipTransferError> {
func _internal_checkOwnershipTranfserAvailability(postbox: Postbox, network: Network, accountStateManager: AccountStateManager, memberId: PeerId) -> Signal<Never, ChatOwnershipTransferError> {
return postbox.transaction { transaction -> Peer? in
return transaction.getPeer(memberId)
}
|> castError(ChannelOwnershipTransferError.self)
|> mapToSignal { user -> Signal<Never, ChannelOwnershipTransferError> in
|> castError(ChatOwnershipTransferError.self)
|> mapToSignal { user -> Signal<Never, ChatOwnershipTransferError> in
guard let user = user else {
return .fail(.generic)
}
@ -33,8 +33,8 @@ func _internal_checkOwnershipTranfserAvailability(postbox: Postbox, network: Net
return .fail(.generic)
}
return network.request(Api.functions.channels.editCreator(channel: .inputChannelEmpty, userId: apiUser, password: .inputCheckPasswordEmpty))
|> mapError { error -> ChannelOwnershipTransferError in
return network.request(Api.functions.messages.editChatCreator(peer: .inputPeerEmpty, userId: apiUser, password: .inputCheckPasswordEmpty))
|> mapError { error -> ChatOwnershipTransferError in
if error.errorDescription == "PASSWORD_HASH_INVALID" {
return .requestPassword
} else if error.errorDescription == "PASSWORD_MISSING" {
@ -64,39 +64,39 @@ func _internal_checkOwnershipTranfserAvailability(postbox: Postbox, network: Net
}
return .generic
}
|> mapToSignal { updates -> Signal<Never, ChannelOwnershipTransferError> in
|> mapToSignal { updates -> Signal<Never, ChatOwnershipTransferError> in
accountStateManager.addUpdates(updates)
return .complete()
}
}
}
func _internal_updateChannelOwnership(account: Account, channelId: PeerId, memberId: PeerId, password: String) -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> {
func _internal_updateChatOwnership(account: Account, peerId: PeerId, memberId: PeerId, password: String) -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChatOwnershipTransferError> {
guard !password.isEmpty else {
return .fail(.invalidPassword)
}
return combineLatest(_internal_fetchChannelParticipant(account: account, peerId: channelId, participantId: account.peerId), _internal_fetchChannelParticipant(account: account, peerId: channelId, participantId: memberId))
|> mapError { _ -> ChannelOwnershipTransferError in
return combineLatest(_internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: account.peerId), _internal_fetchChannelParticipant(account: account, peerId: peerId, participantId: memberId))
|> mapError { _ -> ChatOwnershipTransferError in
}
|> mapToSignal { currentCreator, currentParticipant -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
return account.postbox.transaction { transaction -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
if let channel = transaction.getPeer(channelId) as? TelegramChannel, let inputChannel = apiInputChannel(channel), let accountUser = transaction.getPeer(account.peerId), let user = transaction.getPeer(memberId), let inputUser = apiInputUser(user) {
|> mapToSignal { currentCreator, currentParticipant -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChatOwnershipTransferError> in
return account.postbox.transaction { transaction -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChatOwnershipTransferError> in
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer), let accountUser = transaction.getPeer(account.peerId), let user = transaction.getPeer(memberId), let inputUser = apiInputUser(user) {
let flags: TelegramChatAdminRightsFlags = TelegramChatAdminRightsFlags.peerSpecific(peer: .channel(channel))
let flags: TelegramChatAdminRightsFlags = TelegramChatAdminRightsFlags.peerSpecific(peer: EnginePeer(peer))
let updatedParticipant = ChannelParticipant.creator(id: user.id, adminInfo: nil, rank: currentParticipant?.rank)
let updatedPreviousCreator = ChannelParticipant.member(id: accountUser.id, invitedAt: Int32(Date().timeIntervalSince1970), adminInfo: ChannelParticipantAdminInfo(rights: TelegramChatAdminRights(rights: flags), promotedBy: accountUser.id, canBeEditedByAccountPeer: false), banInfo: nil, rank: currentCreator?.rank, subscriptionUntilDate: nil)
let checkPassword = _internal_twoStepAuthData(account.network)
|> mapError { error -> ChannelOwnershipTransferError in
|> mapError { error -> ChatOwnershipTransferError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else {
return .generic
}
}
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChannelOwnershipTransferError> in
|> mapToSignal { authData -> Signal<Api.InputCheckPasswordSRP, ChatOwnershipTransferError> in
if let currentPasswordDerivation = authData.currentPasswordDerivation, let srpSessionData = authData.srpSessionData {
guard let kdfResult = passwordKDF(encryptionProvider: account.network.encryptionProvider, password: password, derivation: currentPasswordDerivation, srpSessionData: srpSessionData) else {
return .fail(.generic)
@ -108,9 +108,9 @@ func _internal_updateChannelOwnership(account: Account, channelId: PeerId, membe
}
return checkPassword
|> mapToSignal { password -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
return account.network.request(Api.functions.channels.editCreator(channel: inputChannel, userId: inputUser, password: password), automaticFloodWait: false)
|> mapError { error -> ChannelOwnershipTransferError in
|> mapToSignal { password -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChatOwnershipTransferError> in
return account.network.request(Api.functions.messages.editChatCreator(peer: inputPeer, userId: inputUser, password: password), automaticFloodWait: false)
|> mapError { error -> ChatOwnershipTransferError in
if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .limitExceeded
} else if error.errorDescription == "PASSWORD_HASH_INVALID" {
@ -140,11 +140,11 @@ func _internal_updateChannelOwnership(account: Account, channelId: PeerId, membe
}
return .generic
}
|> mapToSignal { updates -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> in
|> mapToSignal { updates -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChatOwnershipTransferError> in
account.stateManager.addUpdates(updates)
return account.postbox.transaction { transaction -> [(ChannelParticipant?, RenderedChannelParticipant)] in
transaction.updatePeerCachedData(peerIds: Set([channelId]), update: { _, cachedData -> CachedPeerData? in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
if let cachedData = cachedData as? CachedChannelData, let adminCount = cachedData.participantsSummary.adminCount {
var updatedAdminCount = adminCount
var wasAdmin = false
@ -179,14 +179,14 @@ func _internal_updateChannelOwnership(account: Account, channelId: PeerId, membe
}
return [(currentCreator, RenderedChannelParticipant(participant: updatedPreviousCreator, peer: accountUser, peers: peers, presences: presences)), (currentParticipant, RenderedChannelParticipant(participant: updatedParticipant, peer: user, peers: peers, presences: presences))]
}
|> mapError { _ -> ChannelOwnershipTransferError in }
|> mapError { _ -> ChatOwnershipTransferError in }
}
}
} else {
return .fail(.generic)
}
}
|> mapError { _ -> ChannelOwnershipTransferError in }
|> mapError { _ -> ChatOwnershipTransferError in }
|> switchToLatest
}
}

View file

@ -83,13 +83,13 @@ func _internal_removePeerChat(account: Account, transaction: Transaction, mediaB
func _internal_getFutureCreatorAfterLeave(account: Account, peerId: EnginePeer.Id) -> Signal<EnginePeer?, NoError> {
return account.postbox.transaction { transaction in
return transaction.getPeer(peerId).flatMap(apiInputChannel)
return transaction.getPeer(peerId).flatMap(apiInputPeer)
}
|> mapToSignal { channel in
guard let channel else {
|> mapToSignal { peer in
guard let peer else {
return .single(nil)
}
return account.network.request(Api.functions.channels.getFutureCreatorAfterLeave(channel: channel))
return account.network.request(Api.functions.messages.getFutureChatCreatorAfterLeave(peer: peer))
|> map(Optional.init)
|> `catch` { _ in
return .single(nil)

View file

@ -486,12 +486,12 @@ public extension TelegramEngine {
return _internal_channelMembers(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId, peerId: peerId, category: category, offset: offset, limit: limit, hash: hash)
}
public func checkOwnershipTranfserAvailability(memberId: PeerId) -> Signal<Never, ChannelOwnershipTransferError> {
public func checkOwnershipTranfserAvailability(memberId: PeerId) -> Signal<Never, ChatOwnershipTransferError> {
return _internal_checkOwnershipTranfserAvailability(postbox: self.account.postbox, network: self.account.network, accountStateManager: self.account.stateManager, memberId: memberId)
}
public func updateChannelOwnership(channelId: PeerId, memberId: PeerId, password: String) -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChannelOwnershipTransferError> {
return _internal_updateChannelOwnership(account: self.account, channelId: channelId, memberId: memberId, password: password)
public func updateChatOwnership(peerId: PeerId, memberId: PeerId, password: String) -> Signal<[(ChannelParticipant?, RenderedChannelParticipant)], ChatOwnershipTransferError> {
return _internal_updateChatOwnership(account: self.account, peerId: peerId, memberId: memberId, password: password)
}
public func searchGroupMembers(peerId: PeerId, query: String) -> Signal<[EnginePeer], NoError> {

View file

@ -297,6 +297,8 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
let canViewRevenue = (userFullFlags2 & (1 << 9)) != 0
let botCanManageEmojiStatus = (userFullFlags2 & (1 << 10)) != 0
let displayGiftButton = (userFullFlags2 & (1 << 16)) != 0
let myCopyProtectionEnabled = (userFullFlags2 & (1 << 23)) != 0
let copyProtectionEnabled = (userFullFlags2 & (1 << 24)) != 0
var flags: CachedUserFlags = previous.flags
if premiumRequired {
@ -334,6 +336,16 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
} else {
flags.remove(.displayGiftButton)
}
if myCopyProtectionEnabled {
flags.insert(.myCopyProtectionEnabled)
} else {
flags.remove(.myCopyProtectionEnabled)
}
if copyProtectionEnabled {
flags.insert(.copyProtectionEnabled)
} else {
flags.remove(.copyProtectionEnabled)
}
let callsPrivate = (userFullFlags & (1 << 5)) != 0
let canPinMessages = (userFullFlags & (1 << 7)) != 0

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

@ -3522,9 +3522,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
},
mosaicStatusOrigin: mosaicStatusOrigin,
mosaicStatusSizeAndApply: mosaicStatusSizeAndApply,
unlockButtonPosition: unlockButtonPosition?.offsetBy(dx: 0.0, dy: layoutInsets.top),
unlockButtonPosition: unlockButtonPosition,
unlockButtonSizeAndApply: unlockButtonSizeApply,
mediaInfoOrigin: mediaInfoOrigin?.offsetBy(dx: 0.0, dy: layoutInsets.top),
mediaInfoOrigin: mediaInfoOrigin,
mediaInfoSizeAndApply: mediaInfoSizeApply,
needsSummarizeButton: needsSummarizeButton,
needsShareButton: needsShareButton,

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 textRightInset: CGFloat
let textRightInset: CGFloat = 8.0
var textLeftInset: CGFloat = horizontalInset
if let _ = self.iconNode.image {
textRightInset = iconSize.width - 2.0
} else {
textRightInset = 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 - textRightInset, 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, 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

@ -69,6 +69,7 @@ private final class CraftGiftPageContent: Component {
let starsTopUpOptionsPromise: Promise<[StarsTopUpOption]?>
let selectGift: (Int32, GiftItem) -> Void
let removeGift: (Int32) -> Void
let craftAnotherGift: () -> Void
let dismiss: () -> Void
init(
@ -86,6 +87,7 @@ private final class CraftGiftPageContent: Component {
starsTopUpOptionsPromise: Promise<[StarsTopUpOption]?>,
selectGift: @escaping (Int32, GiftItem) -> Void,
removeGift: @escaping (Int32) -> Void,
craftAnotherGift: @escaping () -> Void,
dismiss: @escaping () -> Void
) {
self.context = context
@ -102,6 +104,7 @@ private final class CraftGiftPageContent: Component {
self.starsTopUpOptionsPromise = starsTopUpOptionsPromise
self.selectGift = selectGift
self.removeGift = removeGift
self.craftAnotherGift = craftAnotherGift
self.dismiss = dismiss
}
@ -1203,7 +1206,13 @@ private final class CraftGiftPageContent: Component {
}
if let _ = view {
if case let .gift(gift) = component.result {
let giftController = GiftViewScreen(context: component.context, subject: .profileGift(component.context.account.peerId, gift))
let giftController = GiftViewScreen(
context: component.context,
subject: .profileGift(component.context.account.peerId, gift),
customAction: .init(title: environment.strings.Gift_Craft_CraftingFailed_CraftAnotherGift, action: {
component.craftAnotherGift()
})
)
if let navigationController = controller.navigationController {
navigationController.pushViewController(giftController, animated: true)
@ -1548,6 +1557,36 @@ private final class SheetContainerComponent: CombinedComponent {
}
}
let navigationController = environment.controller()?.navigationController as? NavigationController
let profileGiftsContext = (environment.controller() as? GiftCraftScreen)?.profileGiftsContext
let resaleContext = state.resaleContext
let starsTopUpOptionsPromise = state.starsTopUpOptionsPromise
let craftAnotherGift = { [weak navigationController] in
guard let navigationController else {
return
}
if let genericGift = externalState.starGiftsMap[component.gift.giftId] {
HapticFeedback().impact(.light)
let selectController = SelectCraftGiftScreen(
context: component.context,
craftContext: component.craftContext,
resaleContext: resaleContext,
gift: component.gift,
genericGift: genericGift,
selectedGiftIds: Set(),
starsTopUpOptions: starsTopUpOptionsPromise.get(),
selectGift: { [weak navigationController] item in
if let navigationController{
let craftController = GiftCraftScreen(context: component.context, gift: item.gift, profileGiftsContext: profileGiftsContext)
navigationController.pushViewController(craftController)
}
}
)
navigationController.pushViewController(selectController)
}
}
let theme = environment.theme
var colors: (UIColor, UIColor, UIColor, UIColor, UIColor) = (
@ -1720,6 +1759,7 @@ private final class SheetContainerComponent: CombinedComponent {
state.selectedGiftIds[index] = nil
state.updated(transition: .spring(duration: 0.4))
},
craftAnotherGift: craftAnotherGift,
dismiss: {
dismiss(true)
}
@ -1791,26 +1831,9 @@ private final class SheetContainerComponent: CombinedComponent {
if state.displayInfo {
state.displayInfo = false
state.updated(transition: .spring(duration: 0.3))
} else if state.displayFailure, let genericGift = externalState.starGiftsMap[component.gift.giftId] {
HapticFeedback().impact(.light)
let selectController = SelectCraftGiftScreen(
context: component.context,
craftContext: component.craftContext,
resaleContext: state.resaleContext,
gift: component.gift,
genericGift: genericGift,
selectedGiftIds: Set(),
starsTopUpOptions: state.starsTopUpOptionsPromise.get(),
selectGift: { item in
if let controller = controller() as? GiftCraftScreen, let navigationController = controller.navigationController as? NavigationController {
let craftController = GiftCraftScreen(context: component.context, gift: item.gift, profileGiftsContext: controller.profileGiftsContext)
controller.dismissAnimated()
navigationController.pushViewController(craftController)
}
}
)
environment.controller()?.push(selectController)
} else if state.displayFailure {
craftAnotherGift()
dismiss(true)
} else {
HapticFeedback().impact(.medium)
@ -1861,6 +1884,10 @@ private final class SheetContainerComponent: CombinedComponent {
}
}
}
Queue.mainQueue().after(1.0) {
craftContext.reload()
}
}, error: { error in
switch error {
case .craftFailed:

View file

@ -153,6 +153,8 @@ final class SelectGiftPageContent: Component {
}
transition.setFrame(view: self.loadingView, frame: CGRect(origin: CGPoint(x: 0.0, y: contentHeight - 170.0), size: loadingSize))
let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
var itemFrame = CGRect(origin: CGPoint(x: itemSideInset, y: contentHeight), size: itemSize)
var itemsHeight: CGFloat = 0.0
var validIds: [AnyHashable] = []
@ -245,6 +247,12 @@ final class SelectGiftPageContent: Component {
}
}
itemTransition.setFrame(view: itemView, frame: itemFrame)
var canCraft = true
if let profileGift = self.giftMap[gift.gift.id], let canCraftDate = profileGift.canCraftAt, currentTime < canCraftDate {
canCraft = false
}
transition.setAlpha(view: itemView, alpha: canCraft ? 1.0 : 0.5)
}
}
@ -414,6 +422,7 @@ final class SelectGiftPageContent: Component {
contentHeight += 32.0
contentHeight = self.updateScrolling(interactive: false, transition: transition)
let originalContentHeight = contentHeight
let resaleCount = component.genericGift.availability?.resale ?? 0
let saleTitle = environment.strings.Gift_Craft_Select_SaleGiftsCount(Int32(clamping: resaleCount)).uppercased()
@ -483,7 +492,11 @@ final class SelectGiftPageContent: Component {
}
transition.setFrame(view: storeGiftsView, frame: storeGiftsFrame)
storeGiftsView.updateScrolling(bounds: CGRect(origin: .zero, size: availableSize), transition: .immediate)
var effectiveBounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: 1000.0))
if let bounds = self.currentBounds {
effectiveBounds = bounds.offsetBy(dx: 0.0, dy: -originalContentHeight)
}
storeGiftsView.updateScrolling(bounds: effectiveBounds, transition: .immediate)
}
contentHeight += storeGiftsSize.height
contentHeight += 90.0

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

@ -183,7 +183,7 @@ public final class GiftStoreContentComponent: Component {
if let initialCount = self.initialCount, initialCount < minimumCountToDisplayFilters {
topInset = component.navigationHeight
}
let visibleBounds = bounds.insetBy(dx: 0.0, dy: -10.0)
if let starGifts = self.effectiveGifts {
let sideInset: CGFloat = 16.0 + component.safeInsets.left
@ -1269,8 +1269,10 @@ final class GiftStoreScreenComponent: Component {
guard let self, let component = self.component, let environment = self.environment else {
return
}
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
environment.controller()?.push(controller)
Queue.mainQueue().after(0.3) {
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: starsContext)
environment.controller()?.push(controller)
}
}
)))
@ -1283,8 +1285,10 @@ final class GiftStoreScreenComponent: Component {
guard let self, let component = self.component, let environment = self.environment else {
return
}
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
environment.controller()?.push(controller)
Queue.mainQueue().after(0.3) {
let controller = component.context.sharedContext.makeStarsTransactionsScreen(context: component.context, starsContext: tonContext)
environment.controller()?.push(controller)
}
}
)))

View file

@ -4677,7 +4677,26 @@ private final class GiftViewSheetContent: CombinedComponent {
)
let buttonChild: _UpdatedChildComponent
if state.canSkip {
if let controller = controller() as? GiftViewScreen, let customAction = controller.customAction {
buttonChild = button.update(
component: ButtonComponent(
background: buttonBackground,
content: AnyComponentWithIdentity(
id: AnyHashable("custom"),
component: AnyComponent(MultilineTextComponent(text: .plain(NSAttributedString(string: customAction.title, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
isEnabled: true,
displaysProgress: state.inProgress,
action: { [weak state] in
if let state {
customAction.action()
state.dismiss(animated: true)
}
}),
availableSize: buttonSize,
transition: context.transition
)
} else if state.canSkip {
buttonChild = button.update(
component: ButtonComponent(
background: buttonBackground,
@ -5630,6 +5649,16 @@ public class GiftViewScreen: ViewControllerComponentContainer {
}
}
public struct CustomAction {
public let title: String
public let action: () -> Void
public init(title: String, action: @escaping () -> Void) {
self.title = title
self.action = action
}
}
private let context: AccountContext
private let subject: GiftViewScreen.Subject
@ -5671,6 +5700,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
fileprivate let togglePinnedToTop: ((StarGiftReference, Bool) -> Bool)?
fileprivate let shareStory: ((StarGift.UniqueGift) -> Void)?
fileprivate let openChatTheme: (() -> Void)?
fileprivate let customAction: CustomAction?
public var disposed: () -> Void = {}
@ -5690,7 +5720,8 @@ public class GiftViewScreen: ViewControllerComponentContainer {
updateResellStars: ((StarGiftReference, CurrencyAmount?) -> Signal<Never, UpdateStarGiftPriceError>)? = nil,
togglePinnedToTop: ((StarGiftReference, Bool) -> Bool)? = nil,
shareStory: ((StarGift.UniqueGift) -> Void)? = nil,
openChatTheme: (() -> Void)? = nil
openChatTheme: (() -> Void)? = nil,
customAction: CustomAction? = nil
) {
self.context = context
self.subject = subject
@ -5706,6 +5737,7 @@ public class GiftViewScreen: ViewControllerComponentContainer {
self.togglePinnedToTop = togglePinnedToTop
self.shareStory = shareStory
self.openChatTheme = openChatTheme
self.customAction = customAction
if case let .unique(gift) = subject.arguments?.gift, gift.resellForTonOnly {
self.balanceCurrency = .ton

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

@ -408,7 +408,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
items.append(.action(ContextMenuActionItem(text: title, textLayout: .multiline, textFont: .small, icon: { _ in return nil }, action: emptyAction)))
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_ViewOnce, icon: { theme in
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return currentValue == viewOnceTimeout ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, a in
a(.default)
@ -419,7 +419,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
for value in values {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_Seconds(value), icon: { theme in
return currentValue == value ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return currentValue == value ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, a in
a(.default)
@ -428,7 +428,7 @@ public class LegacyMessageInputPanelNode: ASDisplayNode, TGCaptionPanelView {
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.MediaPicker_Timer_DoNotDelete, icon: { theme in
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return currentValue == nil ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, a in
a(.default)

View file

@ -7286,7 +7286,7 @@ public final class MediaEditorScreenImpl: ViewController, MediaEditorScreen, UID
color: theme.contextMenu.primaryColor
)
} else {
return nil
return UIImage()
}
},
action: { [weak self] _, a in

View file

@ -561,7 +561,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, ASScrollVi
if isSelected && value == nil {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}), action: { [weak self] _, f in
scheduleTooltip(nil)

View file

@ -476,7 +476,7 @@ final class AffiliateProgramSetupScreenComponent: Component {
if isSelected {
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)

View file

@ -172,28 +172,28 @@ extension PeerInfoScreenNode {
}
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unlimited, icon: { theme in
return filter.contains(.unlimited) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.unlimited) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.unlimited)
}, longPressAction: { _, f in
switchToFilter(.unlimited)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Limited, icon: { theme in
return filter.contains(.limitedNonUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.limitedNonUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.limitedNonUpgradable)
}, longPressAction: { _, f in
switchToFilter(.limitedNonUpgradable)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Upgradable, icon: { theme in
return filter.contains(.limitedUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.limitedUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.limitedUpgradable)
}, longPressAction: { _, f in
switchToFilter(.limitedUpgradable)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unique, icon: { theme in
return filter.contains(.unique) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.unique) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.unique)
}, longPressAction: { _, f in
@ -204,14 +204,14 @@ extension PeerInfoScreenNode {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Displayed, icon: { theme in
return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.displayed)
}, longPressAction: { _, f in
switchToVisiblityFilter(.displayed)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Hidden, icon: { theme in
return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.hidden)
}, longPressAction: { _, f in

View file

@ -381,28 +381,28 @@ public final class AddGiftsScreen: ViewControllerComponentContainer {
}
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unlimited, icon: { theme in
return filter.contains(.unlimited) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.unlimited) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.unlimited)
}, longPressAction: { _, f in
switchToFilter(.unlimited)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Limited, icon: { theme in
return filter.contains(.limitedNonUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.limitedNonUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.limitedNonUpgradable)
}, longPressAction: { _, f in
switchToFilter(.limitedNonUpgradable)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Upgradable, icon: { theme in
return filter.contains(.limitedUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.limitedUpgradable) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.limitedUpgradable)
}, longPressAction: { _, f in
switchToFilter(.limitedUpgradable)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Unique, icon: { theme in
return filter.contains(.unique) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.unique) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.unique)
}, longPressAction: { _, f in
@ -412,14 +412,14 @@ public final class AddGiftsScreen: ViewControllerComponentContainer {
items.append(.separator)
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Displayed, icon: { theme in
return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.displayed) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.displayed)
}, longPressAction: { _, f in
switchToVisiblityFilter(.displayed)
})))
items.append(.action(ContextMenuActionItem(text: strings.PeerInfo_Gifts_Hidden, icon: { theme in
return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil
return filter.contains(.hidden) ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage()
}, action: { _, f in
toggleFilter(.hidden)
}, longPressAction: { _, f in

View file

@ -1033,7 +1033,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr
let isAdded = gift.collectionIds?.contains(collection.id) ?? false
subItems.append(.action(ContextMenuActionItem(text: title, entities: entities, entityFiles: entityFiles, enableEntityAnimations: false, icon: { theme in
return entities.isEmpty ? generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/Collection"), color: theme.contextMenu.primaryColor) : (isAdded ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : nil)
return entities.isEmpty ? generateTintedImage(image: UIImage(bundleImageName: "Peer Info/Gifts/Collection"), color: theme.contextMenu.primaryColor) : (isAdded ? generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor) : UIImage())
}, iconPosition: collection.icon == nil ? .left : .right, action: { [weak self] _, f in
f(.default)

View file

@ -88,7 +88,7 @@ private func commitChannelOwnershipTransferController(
applyImpl = {
doneInProgressPromise.set(true)
let signal: Signal<EnginePeer.Id?, ChannelOwnershipTransferError>
let signal: Signal<EnginePeer.Id?, ChatOwnershipTransferError>
if case let .channel(peer) = peer {
signal = context.peerChannelMemberCategoriesContextsManager.transferOwnership(engine: context.engine, peerId: peer.id, memberId: member.id, password: inputState.value) |> mapToSignal { _ in
return .complete()
@ -97,7 +97,7 @@ private func commitChannelOwnershipTransferController(
} else if case let .legacyGroup(peer) = peer {
signal = context.engine.peers.convertGroupToSupergroup(peerId: peer.id)
|> map(Optional.init)
|> mapError { error -> ChannelOwnershipTransferError in
|> mapError { error -> ChatOwnershipTransferError in
switch error {
case .tooManyChannels:
return .tooMuchJoined
@ -106,7 +106,7 @@ private func commitChannelOwnershipTransferController(
}
}
|> deliverOnMainQueue
|> mapToSignal { upgradedPeerId -> Signal<EnginePeer.Id?, ChannelOwnershipTransferError> in
|> mapToSignal { upgradedPeerId -> Signal<EnginePeer.Id?, ChatOwnershipTransferError> in
guard let upgradedPeerId = upgradedPeerId else {
return .fail(.generic)
}
@ -213,7 +213,7 @@ public func channelOwnershipTransferController(
peer: EnginePeer,
member: TelegramUser,
onLeave: Bool,
initialError: ChannelOwnershipTransferError,
initialError: ChatOwnershipTransferError,
present: @escaping (ViewController, Any?) -> Void,
push: @escaping (ViewController) -> Void,
completion: @escaping (EnginePeer.Id?) -> Void

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)
@ -488,7 +493,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
if canHideNames {
items.append(.action(ContextMenuActionItem(text: uniquePeerIds.count == 1 ? presentationData.strings.Conversation_ForwardOptions_ShowSendersName : presentationData.strings.Conversation_ForwardOptions_ShowSendersNames, icon: { theme in
if hideNames {
return nil
return UIImage()
} else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}
@ -506,7 +511,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
if hideNames {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}, action: { _, f in
self?.interfaceInteraction?.updateForwardOptionsState({ current in
@ -523,7 +528,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
if hasCaptions {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_ForwardOptions_ShowCaption, icon: { theme in
if hideCaptions {
return nil
return UIImage()
} else {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}
@ -543,7 +548,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
if hideCaptions {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}, action: { _, f in
self?.interfaceInteraction?.updateForwardOptionsState({ current in

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

@ -958,7 +958,7 @@ final class ShareWithPeersScreenComponent: Component {
let isSelected = self.shareToFolders.contains(where: { $0.id == folderPreview.folder.id })
items.append(.action(ContextMenuActionItem(text: folderPreview.folder.title, icon: icon, additionalLeftIcon: { theme in
if !isSelected {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, iconSource: iconSource, iconPosition: .left, action: { [weak self] c, f in

View file

@ -3241,7 +3241,7 @@ final class StorageUsageScreenComponent: Component {
if currentValue == value {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}, action: { _, f in
applyValue(value)

View file

@ -6388,7 +6388,7 @@ public final class StoryItemSetContainerComponent: Component {
if isSelected && value == nil {
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

@ -1397,7 +1397,7 @@ public final class StoryItemSetViewListComponent: Component {
return generateTintedImage(image: UIImage(bundleImageName: "Stories/Context Menu/Repost"), color: theme.contextMenu.primaryColor)
}, additionalLeftIcon: { theme in
if sortMode != .repostsFirst {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
@ -1416,7 +1416,7 @@ public final class StoryItemSetViewListComponent: Component {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Reactions"), color: theme.contextMenu.primaryColor)
}, additionalLeftIcon: { theme in
if sortMode != .reactionsFirst {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in
@ -1435,7 +1435,7 @@ public final class StoryItemSetViewListComponent: Component {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Time"), color: theme.contextMenu.primaryColor)
}, additionalLeftIcon: { theme in
if sortMode != .recentFirst {
return nil
return UIImage()
}
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] _, a in

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

@ -1185,7 +1185,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
if currentValue == 0 {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}, action: { _, f in
applyValue(0)
@ -1208,7 +1208,7 @@ public func createGroupControllerImpl(context: AccountContext, peerIds: [PeerId]
if currentValue == value {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}, action: { _, f in
applyValue(value)

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

@ -1310,7 +1310,7 @@ final class OverlayPlayerControlsNode: ASDisplayNode {
if isSelected && value == nil {
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Check"), color: theme.contextMenu.primaryColor)
} else {
return nil
return UIImage()
}
}), action: { [weak self] _, f in
scheduleTooltip(nil)

View file

@ -69,6 +69,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
public var allForumsHaveTabs: Bool
public var debugRatingLayout: Bool
public var enableUpdates: Bool
public var enablePWA: Bool
public var forceClearGlass: Bool
public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings(
@ -115,7 +117,9 @@ public struct ExperimentalUISettings: Codable, Equatable {
checkSerializedData: false,
allForumsHaveTabs: false,
debugRatingLayout: false,
enableUpdates: false
enableUpdates: false,
enablePWA: false,
forceClearGlass: false
)
}
@ -163,7 +167,9 @@ public struct ExperimentalUISettings: Codable, Equatable {
checkSerializedData: Bool,
allForumsHaveTabs: Bool,
debugRatingLayout: Bool,
enableUpdates: Bool
enableUpdates: Bool,
enablePWA: Bool,
forceClearGlass: Bool
) {
self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory
@ -209,6 +215,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.allForumsHaveTabs = allForumsHaveTabs
self.debugRatingLayout = debugRatingLayout
self.enableUpdates = enableUpdates
self.enablePWA = enablePWA
self.forceClearGlass = forceClearGlass
}
public init(from decoder: Decoder) throws {
@ -258,6 +266,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
self.allForumsHaveTabs = try container.decodeIfPresent(Bool.self, forKey: "allForumsHaveTabs") ?? false
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 {
@ -307,6 +317,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
try container.encodeIfPresent(self.allForumsHaveTabs, forKey: "allForumsHaveTabs")
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

@ -408,8 +408,8 @@ public final class PeerChannelMemberCategoriesContextsManager {
}
}
public func transferOwnership(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, password: String) -> Signal<Void, ChannelOwnershipTransferError> {
return engine.peers.updateChannelOwnership(channelId: peerId, memberId: memberId, password: password)
public func transferOwnership(engine: TelegramEngine, peerId: PeerId, memberId: PeerId, password: String) -> Signal<Void, ChatOwnershipTransferError> {
return engine.peers.updateChatOwnership(peerId: peerId, memberId: memberId, password: password)
|> map(Optional.init)
|> deliverOnMainQueue
|> beforeNext { [weak self] results in
@ -423,7 +423,7 @@ public final class PeerChannelMemberCategoriesContextsManager {
}
}
}
|> mapToSignal { _ -> Signal<Void, ChannelOwnershipTransferError> in
|> mapToSignal { _ -> Signal<Void, ChatOwnershipTransferError> in
return .complete()
}
}

View file

@ -16,12 +16,13 @@ objc_library(
"Source",
],
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/ObjCRuntimeUtils:ObjCRuntimeUtils",
"//submodules/AsyncDisplayKit",
"//submodules/ObjCRuntimeUtils",
],
sdk_frameworks = [
"Foundation",
"UIKit",
"WebKit",
],
visibility = [
"//visibility:public",

View file

@ -1,4 +1,5 @@
#import <UIKit/UIKit.h>
#import <WebKit/WebKit.h>
typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
UIResponderDisableAutomaticKeyboardHandlingForward = 1 << 0,
@ -104,3 +105,10 @@ void snapshotViewByDrawingInContext(UIView * _Nonnull view);
@property (nonatomic) double lumaMax;
@end
@interface WebHelpers : NSObject
+ (dispatch_block_t _Nonnull)addTrustedDomain:(NSString * _Nonnull)domain;
+ (void)forceRefreshTrustedDomains:(WKWebsiteDataStore * _Nonnull)websiteDataStore;
@end

View file

@ -5,6 +5,7 @@
#import "NSWeakReference.h"
#import <UIKitRuntimeUtils/UIKitUtils.h>
#import <dlfcn.h>
@interface UIViewControllerPresentingProxy : UIViewController
@ -405,6 +406,131 @@ static void registerEffectViewOverrides(void) {
}
}
static NSLock *webHelpersLock() {
static NSLock *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSLock alloc] init];
});
return value;
}
@interface TrustedWebRecord : NSObject
@property (nonatomic) NSInteger nextReference;
@property (nonatomic, strong, readonly) NSMutableSet<NSNumber *> *references;
@end
@implementation TrustedWebRecord
- (instancetype)init {
self = [super init];
if (self != nil) {
_references = [[NSMutableSet alloc] init];
}
return self;
}
- (NSInteger)addReference {
NSInteger reference = _nextReference;
_nextReference += 1;
[_references addObject:@(reference)];
return reference;
}
- (void)removeReference:(NSInteger)reference {
[_references removeObject:@(reference)];
}
- (bool)isEmpty {
return _references.count == 0;
}
@end
static NSMutableDictionary<NSString *, TrustedWebRecord *> *trustedWebRecords() {
static NSMutableDictionary *value = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
value = [[NSMutableDictionary alloc] init];
});
return value;
}
@implementation WebHelpers
+ (NSArray<NSString *> * _Nonnull)threadSafeTrustedDomains {
[webHelpersLock() lock];
NSMutableArray<NSString *> *result = [[NSMutableArray alloc] init];
for (NSString *domain in trustedWebRecords()) {
[result addObject:domain];
}
[result sortUsingSelector:@selector(compare:)];
[webHelpersLock() unlock];
return result;
}
+ (dispatch_block_t _Nonnull)addTrustedDomain:(NSString * _Nonnull)domain {
[webHelpersLock() lock];
TrustedWebRecord *record = trustedWebRecords()[domain];
if (record == nil) {
record = [[TrustedWebRecord alloc] init];
trustedWebRecords()[domain] = record;
}
NSInteger reference = [record addReference];
__block __weak TrustedWebRecord *weakRecord = record;
[webHelpersLock() unlock];
return ^{
[webHelpersLock() lock];
TrustedWebRecord *strongRecord = weakRecord;
if (strongRecord && trustedWebRecords()[domain] == strongRecord) {
[strongRecord removeReference:reference];
if ([strongRecord isEmpty]) {
[trustedWebRecords() removeObjectForKey:domain];
}
}
[webHelpersLock() unlock];
};
}
+ (void)forceRefreshTrustedDomains:(WKWebsiteDataStore * _Nonnull)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
@implementation NSBundle (Telegram)
- (id)_65087dc8_objectForInfoDictionaryKey:(NSString *)key {
if ([key isEqualToString:@"WKAppBoundDomains"]) {
NSArray *result = [WebHelpers threadSafeTrustedDomains];
if (result.count != 0) {
return result;
}
}
return [self _65087dc8_objectForInfoDictionaryKey:key];
}
@end
@implementation UIViewController (Navigation)
+ (void)load
@ -432,6 +558,8 @@ static void registerEffectViewOverrides(void) {
[RuntimeUtils swizzleInstanceMethodOfClass:[UIFocusSystem class] currentSelector:@selector(updateFocusIfNeeded) newSelector:@selector(_65087dc8_updateFocusIfNeeded)];
[RuntimeUtils swizzleInstanceMethodOfClass:[NSBundle class] currentSelector:@selector(objectForInfoDictionaryKey:) newSelector:@selector(_65087dc8_objectForInfoDictionaryKey:)];
if (@available(iOS 26.0, *)) {
registerEffectViewOverrides();
}

View file

@ -162,12 +162,8 @@ final class WebAppWebView: WKWebView {
self.isOpaque = false
self.backgroundColor = .clear
if #available(iOS 9.0, *) {
self.allowsLinkPreview = false
}
if #available(iOS 11.0, *) {
self.scrollView.contentInsetAdjustmentBehavior = .never
}
self.allowsLinkPreview = false
self.scrollView.contentInsetAdjustmentBehavior = .never
self.interactiveTransitionGestureRecognizerTest = { point -> Bool in
return point.x > 30.0
}

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"