diff --git a/.gitignore b/.gitignore
index 704dfd16..f2976dc2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -84,3 +84,5 @@ xcode-files
/codesigning/
/build-system/real-codesigning/
/build-system/local-codesigning/
+build-output/
+build-output/
diff --git a/Telegram/BUILD b/Telegram/BUILD
index 4b84ab9c..4247f058 100644
--- a/Telegram/BUILD
+++ b/Telegram/BUILD
@@ -36,6 +36,7 @@ load(
"telegram_bazel_path",
"telegram_use_xcode_managed_codesigning",
"telegram_bundle_id",
+ "telegram_is_appstore_build",
"telegram_aps_environment",
"telegram_team_id",
"telegram_enable_icloud",
@@ -509,6 +510,16 @@ aps_fragment = "" if telegram_aps_environment == "" else """
{telegram_aps_environment}
""".format(telegram_aps_environment=telegram_aps_environment)
+beta_reports_active_fragment = "" if telegram_is_appstore_build != "true" else """
+beta-reports-active
+
+"""
+
+get_task_allow_fragment = """
+get-task-allow
+<{value}/>
+""".format(value = "false" if telegram_is_appstore_build == "true" else "true")
+
app_groups_fragment = """
com.apple.security.application-groups
@@ -546,6 +557,8 @@ plist_fragment(
extension = "entitlements",
template = "".join([
aps_fragment,
+ beta_reports_active_fragment,
+ get_task_allow_fragment,
app_groups_fragment,
siri_fragment,
associated_domains_fragment,
@@ -1718,7 +1731,7 @@ ios_application(
":RequiredDeviceCapabilitiesPlist",
":UrlTypesInfoPlist",
],
- app_icons = [ ":{}_icon".format(name) for name in composer_icon_folders ],
+ app_icons = [":DefaultAppIcon"],
alternate_icons = [
":{}".format(name) for name in alternate_icon_folders
],
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png
deleted file mode 100644
index dd360d8f..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x-1.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png
deleted file mode 100644
index 2e502e7d..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@2x.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png
deleted file mode 100644
index c47aeed4..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIcon@3x.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png
deleted file mode 100644
index 6d9e7ab9..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconIpad@2x.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png
deleted file mode 100644
index 9bf36374..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueIconLargeIpad@2x.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png
deleted file mode 100644
index dc591628..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png
deleted file mode 100644
index 0898af42..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x-1.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png
deleted file mode 100644
index 0898af42..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@2x.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png
deleted file mode 100644
index f7725e99..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/BlueNotificationIcon@3x.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json
index 221e2b44..ae5c27e8 100644
--- a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json
+++ b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Contents.json
@@ -1,116 +1,92 @@
{
- "images": [
+ "images" : [
{
- "filename": "GhostIcon@40x40.png",
- "idiom": "iphone",
- "scale": "2x",
- "size": "20x20"
+ "filename" : "GhostIcon@40x40.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
},
{
- "filename": "GhostIcon@60x60.png",
- "idiom": "iphone",
- "scale": "3x",
- "size": "20x20"
+ "filename" : "GhostIcon@60x60.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
},
{
- "filename": "GhostIcon@58x58.png",
- "idiom": "iphone",
- "scale": "2x",
- "size": "29x29"
+ "filename" : "GhostIcon@58x58.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
},
{
- "filename": "GhostIcon@87x87.png",
- "idiom": "iphone",
- "scale": "3x",
- "size": "29x29"
+ "filename" : "GhostIcon@87x87.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
},
{
- "filename": "GhostIcon@80x80.png",
- "idiom": "iphone",
- "scale": "2x",
- "size": "40x40"
+ "filename" : "GhostIcon@80x80.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
},
{
- "filename": "GhostIcon@120x120.png",
- "idiom": "iphone",
- "scale": "3x",
- "size": "40x40"
+ "filename" : "GhostIcon@120x120.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
},
{
- "filename": "GhostIcon@120x120.png",
- "idiom": "iphone",
- "scale": "2x",
- "size": "60x60"
+ "filename" : "GhostIcon@120x120.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
},
{
- "filename": "GhostIcon@180x180.png",
- "idiom": "iphone",
- "scale": "3x",
- "size": "60x60"
+ "filename" : "GhostIcon@180x180.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
},
{
- "filename": "GhostIcon@20x20.png",
- "idiom": "ipad",
- "scale": "1x",
- "size": "20x20"
+ "filename" : "GhostIcon@40x40.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
},
{
- "filename": "GhostIcon@40x40.png",
- "idiom": "ipad",
- "scale": "2x",
- "size": "20x20"
+ "filename" : "GhostIcon@58x58.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
},
{
- "filename": "GhostIcon@29x29.png",
- "idiom": "ipad",
- "scale": "1x",
- "size": "29x29"
+ "filename" : "GhostIcon@80x80.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
},
{
- "filename": "GhostIcon@58x58.png",
- "idiom": "ipad",
- "scale": "2x",
- "size": "29x29"
+ "filename" : "GhostIcon@152x152.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
},
{
- "filename": "GhostIcon@40x40.png",
- "idiom": "ipad",
- "scale": "1x",
- "size": "40x40"
+ "filename" : "GhostIcon@167x167.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
},
{
- "filename": "GhostIcon@80x80.png",
- "idiom": "ipad",
- "scale": "2x",
- "size": "40x40"
- },
- {
- "filename": "GhostIcon@76x76.png",
- "idiom": "ipad",
- "scale": "1x",
- "size": "76x76"
- },
- {
- "filename": "GhostIcon@152x152.png",
- "idiom": "ipad",
- "scale": "2x",
- "size": "76x76"
- },
- {
- "filename": "GhostIcon@167x167.png",
- "idiom": "ipad",
- "scale": "2x",
- "size": "83.5x83.5"
- },
- {
- "filename": "GhostIcon@1024x1024.png",
- "idiom": "ios-marketing",
- "scale": "1x",
- "size": "1024x1024"
+ "filename" : "GhostIcon@1024x1024.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
}
],
- "info": {
- "author": "xcode",
- "version": 1
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
}
-}
\ No newline at end of file
+}
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@20x20.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@20x20.png
deleted file mode 100644
index 3ad57dfa..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@20x20.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@29x29.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@29x29.png
deleted file mode 100644
index 840c5956..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@29x29.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@76x76.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@76x76.png
deleted file mode 100644
index 88823054..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/GhostIcon@76x76.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png
deleted file mode 100644
index f00a2857..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple-iTunesArtwork.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@29x29.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@29x29.png
deleted file mode 100644
index 90d7b67b..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@29x29.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png
deleted file mode 100644
index a79cb5dc..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@40x40-1.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png
deleted file mode 100644
index aa6a4a44..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58-1.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58.png
deleted file mode 100644
index aa6a4a44..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@58x58.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png
deleted file mode 100644
index 385bc474..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80-1.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80.png
deleted file mode 100644
index 385bc474..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@80x80.png and /dev/null differ
diff --git a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@87x87.png b/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@87x87.png
deleted file mode 100644
index c0a9ce93..00000000
Binary files a/Telegram/Telegram-iOS/DefaultAppIcon.xcassets/AppIconLLC.appiconset/Simple@87x87.png and /dev/null differ
diff --git a/build-system/GenerateStrings/GenerateStrings.py b/build-system/GenerateStrings/GenerateStrings.py
index e10ff2b8..5fa5da03 100644
--- a/build-system/GenerateStrings/GenerateStrings.py
+++ b/build-system/GenerateStrings/GenerateStrings.py
@@ -241,7 +241,7 @@ def generate(header_path: str, implementation_path: str, data_path: str, entries
arguments_array += ', '
arguments_array += '[[NSString alloc] initWithFormat:@"%@", arg{}]'.format(i)
formatted_accessors += '''
-static _FormattedString * _Nonnull getFormatted{num_arguments}(_PresentationStrings * _Nonnull strings,
+static __attribute__((unused)) _FormattedString * _Nonnull getFormatted{num_arguments}(_PresentationStrings * _Nonnull strings,
uint32_t keyId{arguments_string}) {{
NSString *formatString = getSingle(strings, strings->_idToKey[@(keyId)], nil);
NSArray<_FormattedStringRange *> *argumentRanges = extractArgumentRanges(formatString);
diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py
index 93b6a56d..3a8c15b0 100644
--- a/build-system/Make/Make.py
+++ b/build-system/Make/Make.py
@@ -232,6 +232,9 @@ class BazelCommandLine:
combined_arguments += self.common_debug_args
combined_arguments += self.get_define_arguments()
+ if self.disable_provisioning_profiles:
+ combined_arguments += ['--//Telegram:disableProvisioningProfiles']
+
if self.remote_cache is not None:
combined_arguments += [
'--remote_cache={}'.format(self.remote_cache),
@@ -559,6 +562,8 @@ def generate_project(bazel, arguments):
disable_extensions = arguments.disableExtensions
if arguments.disableProvisioningProfiles is not None:
disable_provisioning_profiles = arguments.disableProvisioningProfiles
+ if disable_provisioning_profiles:
+ bazel_command_line.set_disable_provisioning_profiles()
if arguments.projectIncludeRelease is not None:
project_include_release = arguments.projectIncludeRelease
if arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True:
diff --git a/build-system/Make/ProjectGeneration.py b/build-system/Make/ProjectGeneration.py
index 8de46931..610542da 100644
--- a/build-system/Make/ProjectGeneration.py
+++ b/build-system/Make/ProjectGeneration.py
@@ -26,6 +26,8 @@ def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions,
if target_name == 'Telegram':
if disable_extensions:
bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)]
+ if disable_provisioning_profiles:
+ bazel_generate_arguments += ['--//{}:disableProvisioningProfiles'.format(app_target)]
bazel_generate_arguments += ['--//{}:disableStripping'.format(app_target)]
project_bazel_arguments = []
@@ -35,6 +37,8 @@ def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions,
if target_name == 'Telegram':
if disable_extensions:
project_bazel_arguments += ['--//{}:disableExtensions'.format(app_target)]
+ if disable_provisioning_profiles:
+ project_bazel_arguments += ['--//{}:disableProvisioningProfiles'.format(app_target)]
project_bazel_arguments += ['--//{}:disableStripping'.format(app_target)]
project_bazel_arguments += ['--features=-swift.debug_prefix_map']
diff --git a/build-system/config.json b/build-system/config.json
index 262a5fdb..6ab48191 100755
--- a/build-system/config.json
+++ b/build-system/config.json
@@ -4,7 +4,7 @@
"api_hash": "",
"team_id": "",
"app_center_id": "0",
- "is_internal_build": "false",
+ "is_internal_build": "true",
"is_appstore_build": "true",
"appstore_id": "0",
"app_specific_url_scheme": "ghostgram",
diff --git a/submodules/AsyncDisplayKit/BUILD b/submodules/AsyncDisplayKit/BUILD
index f713d118..8c405fc1 100644
--- a/submodules/AsyncDisplayKit/BUILD
+++ b/submodules/AsyncDisplayKit/BUILD
@@ -16,9 +16,11 @@ objc_library(
], allow_empty=True) + private_headers,
copts = [
"-Werror",
+ "-Wno-deprecated-declarations",
],
cxxopts = [
"-Werror",
+ "-Wno-deprecated-declarations",
"-std=c++17",
],
hdrs = public_headers,
diff --git a/submodules/ChatListSearchItemNode/Sources/ChatListSearchItem.swift b/submodules/ChatListSearchItemNode/Sources/ChatListSearchItem.swift
index d2959cd0..82f722c1 100644
--- a/submodules/ChatListSearchItemNode/Sources/ChatListSearchItem.swift
+++ b/submodules/ChatListSearchItemNode/Sources/ChatListSearchItem.swift
@@ -89,7 +89,7 @@ public class ChatListSearchItemNode: ListViewItemNode {
required public init() {
self.searchBarNode = SearchBarPlaceholderNode(fieldStyle: .modern)
- super.init(layerBacked: false, dynamicBounce: false)
+ super.init(layerBacked: false)
self.addSubnode(self.searchBarNode)
}
@@ -107,7 +107,6 @@ public class ChatListSearchItemNode: ListViewItemNode {
}
public func asyncLayout() -> (_ item: ChatListSearchItem, _ params: ListViewItemLayoutParams, _ nextIsPinned: Bool, _ isEnabled: Bool) -> (ListViewItemNodeLayout, (Bool) -> Void) {
- let searchBarNodeLayout = self.searchBarNode.asyncLayout()
let placeholder = self.placeholder
return { [weak self] item, params, nextIsPinned, isEnabled in
@@ -115,9 +114,9 @@ public class ChatListSearchItemNode: ListViewItemNode {
let backgroundColor = nextIsPinned ? item.theme.chatList.pinnedItemBackgroundColor : item.theme.chatList.itemBackgroundColor
let placeholderColor = item.theme.list.itemSecondaryTextColor
+ let controlColor = item.theme.chat.inputPanel.panelControlColor
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: placeholderColor)
- let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 20.0, height: 36.0), 1.0, placeholderColor, nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 54.0), insets: UIEdgeInsets())
@@ -132,9 +131,7 @@ public class ChatListSearchItemNode: ListViewItemNode {
let searchBarFrame = CGRect(origin: CGPoint(x: params.leftInset + 10.0, y: 8.0), size: CGSize(width: baseWidth - 20.0, height: 36.0))
strongSelf.searchBarNode.frame = searchBarFrame
- searchBarApply()
-
- strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - 20.0, height: 36.0))
+ _ = strongSelf.searchBarNode.updateLayout(placeholderString: placeholderString, compactPlaceholderString: placeholderString, constrainedSize: searchBarFrame.size, expansionProgress: 1.0, iconColor: placeholderColor, foregroundColor: nextIsPinned ? item.theme.chatList.pinnedSearchBarColor : item.theme.chatList.regularSearchBarColor, backgroundColor: backgroundColor, controlColor: controlColor, transition: transition)
if !item.isEnabled {
if strongSelf.disabledOverlay == nil {
diff --git a/submodules/ChatListUI/BUILD b/submodules/ChatListUI/BUILD
index 87652ea3..2da4a2e0 100644
--- a/submodules/ChatListUI/BUILD
+++ b/submodules/ChatListUI/BUILD
@@ -3,9 +3,14 @@ load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ChatListUI",
module_name = "ChatListUI",
- srcs = glob([
- "Sources/**/*.swift",
- ]),
+ srcs = glob(
+ [
+ "Sources/**/*.swift",
+ ],
+ exclude = [
+ "Sources/ChatListFilterTabContainerNode.swift",
+ ],
+ ),
copts = [
"-warnings-as-errors",
],
diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift
index c020faa7..8de604b6 100644
--- a/submodules/ChatListUI/Sources/ChatListController.swift
+++ b/submodules/ChatListUI/Sources/ChatListController.swift
@@ -6595,8 +6595,17 @@ private final class ChatListLocationContext {
var proxyButton: AnyComponentWithIdentity?
var storyButton: AnyComponentWithIdentity?
+ // GHOSTGRAM: Account switcher — liquid glass avatar button for the next account
+ var accountSwitcherButton: AnyComponentWithIdentity?
+ private var accountSwitcherDisposable: Disposable?
+ private var accountSwitcherAvatarDisposable: Disposable?
+
var rightButtons: [AnyComponentWithIdentity] {
var result: [AnyComponentWithIdentity] = []
+ // Account switcher is first — leftmost of the right-side buttons
+ if let accountSwitcherButton = self.accountSwitcherButton {
+ result.append(accountSwitcherButton)
+ }
if let rightButton = self.rightButton {
result.append(rightButton)
}
@@ -6631,6 +6640,90 @@ private final class ChatListLocationContext {
self.location = location
self.parentController = parentController
+ // GHOSTGRAM: Subscribe to account list and maintain the switcher button
+ if case .chatList(.root) = location {
+ self.accountSwitcherDisposable = (context.sharedContext.activeAccountsWithInfo
+ |> deliverOnMainQueue)
+ .start(next: { [weak self] (info: (primary: AccountRecordId?, accounts: [AccountWithInfo])) in
+ guard let self else { return }
+
+ let primaryId = info.primary
+ let accounts = info.accounts
+
+ // Only show when there is more than one account
+ guard accounts.count > 1, let primaryId = primaryId else {
+ if self.accountSwitcherButton != nil {
+ self.accountSwitcherButton = nil
+ let _ = self.parentController?.updateHeaderContent()
+ self.parentController?.requestLayout(transition: .immediate)
+ }
+ return
+ }
+
+ // Find next account cyclically
+ let currentIndex = accounts.firstIndex(where: { $0.account.id == primaryId }) ?? 0
+ let nextIndex = (currentIndex + 1) % accounts.count
+ let nextAccount = accounts[nextIndex]
+ let nextPeer = nextAccount.peer
+ let nextPeerId = "\(nextAccount.account.id)"
+
+ // Build button placeholder immediately (image loads async)
+ let buildButton: (UIImage?) -> Void = { [weak self] image in
+ guard let self else { return }
+ guard case .chatList(.root) = self.location else { return }
+
+ let sharedContext = self.context.sharedContext
+ let nextAccountId = nextAccount.account.id
+
+ self.accountSwitcherButton = AnyComponentWithIdentity(
+ id: "accountSwitcher",
+ component: AnyComponent(NavigationButtonComponent(
+ content: .avatar(peerId: nextPeerId, avatarImage: image),
+ pressed: { [weak sharedContext] _ in
+ sharedContext?.switchToAccount(id: nextAccountId, fromSettingsController: nil, withChatListController: nil)
+ }
+ ))
+ )
+ // Trigger header rebuild
+ let _ = self.parentController?.updateHeaderContent()
+ self.parentController?.requestLayout(transition: .immediate)
+ }
+
+ // Attempt to load the peer's avatar from mediaBox
+ if let representation = nextPeer.smallProfileImage {
+ self.accountSwitcherAvatarDisposable?.dispose()
+ let resource = representation.resource
+ let account = nextAccount.account
+
+ // Try to read cached data first; if not ready, trigger a fetch then watch for completion
+ self.accountSwitcherAvatarDisposable = (account.postbox.mediaBox
+ .resourceData(resource)
+ |> deliverOnMainQueue)
+ .start(next: { data in
+ if data.complete, let uiImage = UIImage(contentsOfFile: data.path) {
+ buildButton(uiImage)
+ }
+ }, completed: {
+ // If resource was never complete after signal ended, show placeholder
+ buildButton(nil)
+ })
+
+ // Trigger the actual network fetch so mediaBox populates the resource
+ if let peerReference = PeerReference(nextPeer) {
+ let _ = fetchedMediaResource(
+ mediaBox: account.postbox.mediaBox,
+ userLocation: .peer(nextPeer.id),
+ userContentType: .avatar,
+ reference: .avatar(peer: peerReference, resource: resource)
+ ).start()
+ }
+ } else {
+ // No photo — show placeholder
+ buildButton(nil)
+ }
+ })
+ }
+
let hasProxy = context.sharedContext.accountManager.sharedData(keys: [SharedDataKeys.proxySettings])
|> map { sharedData -> (Bool, Bool) in
if let settings = sharedData.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) {
@@ -6949,6 +7042,8 @@ private final class ChatListLocationContext {
deinit {
self.titleDisposable?.dispose()
self.stateDisposable?.dispose()
+ self.accountSwitcherDisposable?.dispose()
+ self.accountSwitcherAvatarDisposable?.dispose()
}
private func updateChatList(
@@ -6982,6 +7077,7 @@ private final class ChatListLocationContext {
if case .chatList(.root) = self.location {
self.rightButton = nil
self.storyButton = nil
+ self.accountSwitcherButton = nil
}
let title = !stateAndFilterId.state.selectedPeerIds.isEmpty ? presentationData.strings.ChatList_SelectedChats(Int32(stateAndFilterId.state.selectedPeerIds.count)) : defaultTitle
@@ -6997,6 +7093,7 @@ private final class ChatListLocationContext {
if case .chatList(.root) = self.location {
self.rightButton = nil
self.storyButton = nil
+ self.accountSwitcherButton = nil
}
self.leftButton = AnyComponentWithIdentity(id: "done", component: AnyComponent(NavigationButtonComponent(
content: .text(title: presentationData.strings.Common_Done, isBold: true),
diff --git a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift
index eae620ae..4833aefa 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListNoticeItem.swift
@@ -14,6 +14,7 @@ import MergedAvatarsNode
import TextNodeWithEntities
import TextFormat
import AvatarNode
+import GlobalControlPanelsContext
class ChatListNoticeItem: ListViewItem {
enum Action {
@@ -25,12 +26,12 @@ class ChatListNoticeItem: ListViewItem {
let context: AccountContext
let theme: PresentationTheme
let strings: PresentationStrings
- let notice: ChatListNotice
+ let notice: GlobalControlPanelsContext.ChatListNotice
let action: (Action) -> Void
let selectable: Bool = true
- init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: ChatListNotice, action: @escaping (Action) -> Void) {
+ init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, notice: GlobalControlPanelsContext.ChatListNotice, action: @escaping (Action) -> Void) {
self.context = context
self.theme = theme
self.strings = strings
@@ -130,7 +131,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
self.arrowNode = ASImageNode()
self.separatorNode = ASDisplayNode()
- super.init(layerBacked: false, dynamicBounce: false, rotated: false, seeThrough: false)
+ super.init(layerBacked: false, rotated: false, seeThrough: false)
self.contentContainer.clipsToBounds = true
self.clipsToBounds = true
diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift
index 67ad9cb6..35e9ee7f 100644
--- a/submodules/Display/Source/NavigationBar.swift
+++ b/submodules/Display/Source/NavigationBar.swift
@@ -122,10 +122,6 @@ public func navigationBarBackArrowImage(color: UIColor) -> UIImage? {
}
}
-public protocol NavigationButtonCustomDisplayNode {
- var isHighlightable: Bool { get }
-}
-
public protocol NavigationButtonNode: ASDisplayNode {
func updateManualAlpha(alpha: CGFloat, transition: ContainedViewLayoutTransition)
var mainContentNode: ASDisplayNode? { get }
diff --git a/submodules/Display/Source/NavigationButtonNode.swift b/submodules/Display/Source/NavigationButtonNode.swift
index 1ebc26af..d71443aa 100644
--- a/submodules/Display/Source/NavigationButtonNode.swift
+++ b/submodules/Display/Source/NavigationButtonNode.swift
@@ -328,7 +328,7 @@ private final class NavigationButtonItemNode: ImmediateTextNode {
}
-public final class NavigationButtonNode: ContextControllerSourceNode {
+public final class NavigationButtonNodeImpl: ContextControllerSourceNode, NavigationButtonNode {
private var nodes: [NavigationButtonItemNode] = []
private var disappearingNodes: [(frame: CGRect, size: CGSize, node: NavigationButtonItemNode)] = []
diff --git a/submodules/SettingsUI/Sources/DeletedMessagesController.swift b/submodules/SettingsUI/Sources/DeletedMessagesController.swift
index 92154df0..20fc8e6a 100644
--- a/submodules/SettingsUI/Sources/DeletedMessagesController.swift
+++ b/submodules/SettingsUI/Sources/DeletedMessagesController.swift
@@ -1,12 +1,22 @@
import Foundation
import UIKit
import Display
+import AsyncDisplayKit
import SwiftSignalKit
import TelegramCore
import Postbox
import TelegramPresentationData
import ItemListUI
import AccountContext
+import ComponentFlow
+import SliderComponent
+
+private let minDeletedMessageTransparencyPercent: Int32 = Int32(AntiDeleteManager.minDeletedMessageTransparency * 100.0)
+private let maxDeletedMessageTransparencyPercent: Int32 = Int32(AntiDeleteManager.maxDeletedMessageTransparency * 100.0)
+
+private func clampDeletedMessageTransparencyPercent(_ value: Int32) -> Int32 {
+ return max(minDeletedMessageTransparencyPercent, min(maxDeletedMessageTransparencyPercent, value))
+}
// MARK: - Entry Definition
@@ -17,6 +27,7 @@ private enum DeletedMessagesSection: Int32 {
private enum DeletedMessagesEntry: ItemListNodeEntry {
case enableToggle(PresentationTheme, String, Bool)
case archiveMediaToggle(PresentationTheme, String, Bool)
+ case transparencySlider(PresentationTheme, Int32, Bool)
case settingsInfo(PresentationTheme, String)
var section: ItemListSectionId {
@@ -29,8 +40,10 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
return 0
case .archiveMediaToggle:
return 1
- case .settingsInfo:
+ case .transparencySlider:
return 2
+ case .settingsInfo:
+ return 3
}
}
@@ -48,6 +61,12 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
return true
}
return false
+ case let .transparencySlider(lhsTheme, lhsValue, lhsIsEnabled):
+ if case let .transparencySlider(rhsTheme, rhsValue, rhsIsEnabled) = rhs,
+ lhsTheme === rhsTheme, lhsValue == rhsValue, lhsIsEnabled == rhsIsEnabled {
+ return true
+ }
+ return false
case let .settingsInfo(lhsTheme, lhsText):
if case let .settingsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@@ -85,6 +104,16 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
arguments.toggleArchiveMedia(value)
}
)
+ case let .transparencySlider(theme, value, isEnabled):
+ return DeletedMessagesTransparencySliderItem(
+ theme: theme,
+ value: value,
+ isEnabled: isEnabled,
+ sectionId: self.section,
+ updated: { value in
+ arguments.updateTransparency(value)
+ }
+ )
case let .settingsInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
@@ -96,13 +125,16 @@ private enum DeletedMessagesEntry: ItemListNodeEntry {
private final class DeletedMessagesControllerArguments {
let toggleEnabled: (Bool) -> Void
let toggleArchiveMedia: (Bool) -> Void
+ let updateTransparency: (Int32) -> Void
init(
toggleEnabled: @escaping (Bool) -> Void,
- toggleArchiveMedia: @escaping (Bool) -> Void
+ toggleArchiveMedia: @escaping (Bool) -> Void,
+ updateTransparency: @escaping (Int32) -> Void
) {
self.toggleEnabled = toggleEnabled
self.toggleArchiveMedia = toggleArchiveMedia
+ self.updateTransparency = updateTransparency
}
}
@@ -111,10 +143,12 @@ private final class DeletedMessagesControllerArguments {
private struct DeletedMessagesControllerState: Equatable {
var isEnabled: Bool
var archiveMedia: Bool
+ var transparencyPercent: Int32
static func ==(lhs: DeletedMessagesControllerState, rhs: DeletedMessagesControllerState) -> Bool {
return lhs.isEnabled == rhs.isEnabled &&
- lhs.archiveMedia == rhs.archiveMedia
+ lhs.archiveMedia == rhs.archiveMedia &&
+ lhs.transparencyPercent == rhs.transparencyPercent
}
}
@@ -128,7 +162,8 @@ private func deletedMessagesControllerEntries(
entries.append(.enableToggle(presentationData.theme, "Сохранять удалённые сообщения", state.isEnabled))
entries.append(.archiveMediaToggle(presentationData.theme, "Архивировать медиа", state.archiveMedia))
- entries.append(.settingsInfo(presentationData.theme, "Когда включено, сообщения, удалённые другими пользователями, будут сохраняться локально. Рядом со временем сообщения появится иконка корзины."))
+ entries.append(.transparencySlider(presentationData.theme, state.transparencyPercent, state.isEnabled))
+ entries.append(.settingsInfo(presentationData.theme, "Когда включено, сообщения, удалённые другими пользователями, будут сохраняться локально. Прозрачность влияет только на сообщения, которые уже помечены как удалённые."))
return entries
}
@@ -138,7 +173,8 @@ private func deletedMessagesControllerEntries(
public func deletedMessagesController(context: AccountContext) -> ViewController {
let initialState = DeletedMessagesControllerState(
isEnabled: AntiDeleteManager.shared.isEnabled,
- archiveMedia: AntiDeleteManager.shared.archiveMedia
+ archiveMedia: AntiDeleteManager.shared.archiveMedia,
+ transparencyPercent: clampDeletedMessageTransparencyPercent(Int32(round(AntiDeleteManager.shared.deletedMessageTransparency * 100.0)))
)
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
@@ -163,6 +199,15 @@ public func deletedMessagesController(context: AccountContext) -> ViewController
state.archiveMedia = value
return state
}
+ },
+ updateTransparency: { value in
+ let clampedValue = clampDeletedMessageTransparencyPercent(value)
+ AntiDeleteManager.shared.deletedMessageTransparency = Double(clampedValue) / 100.0
+ updateState { state in
+ var state = state
+ state.transparencyPercent = clampedValue
+ return state
+ }
}
)
@@ -195,3 +240,225 @@ public func deletedMessagesController(context: AccountContext) -> ViewController
let controller = ItemListController(context: context, state: signal)
return controller
}
+
+private final class DeletedMessagesTransparencySliderItem: ListViewItem, ItemListItem {
+ let theme: PresentationTheme
+ let value: Int32
+ let isEnabled: Bool
+ let sectionId: ItemListSectionId
+ let updated: (Int32) -> Void
+
+ init(theme: PresentationTheme, value: Int32, isEnabled: Bool, sectionId: ItemListSectionId, updated: @escaping (Int32) -> Void) {
+ self.theme = theme
+ self.value = clampDeletedMessageTransparencyPercent(value)
+ self.isEnabled = isEnabled
+ self.sectionId = sectionId
+ self.updated = updated
+ }
+
+ func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) {
+ async {
+ let node = DeletedMessagesTransparencySliderItemNode()
+ let (layout, apply) = node.asyncLayout()(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
+
+ node.contentSize = layout.contentSize
+ node.insets = layout.insets
+
+ Queue.mainQueue().async {
+ completion(node, {
+ return (nil, { _ in apply() })
+ })
+ }
+ }
+ }
+
+ func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) {
+ Queue.mainQueue().async {
+ if let nodeValue = node() as? DeletedMessagesTransparencySliderItemNode {
+ let makeLayout = nodeValue.asyncLayout()
+
+ async {
+ let (layout, apply) = makeLayout(self, params, itemListNeighbors(item: self, topItem: previousItem as? ItemListItem, bottomItem: nextItem as? ItemListItem))
+ Queue.mainQueue().async {
+ completion(layout, { _ in
+ apply()
+ })
+ }
+ }
+ }
+ }
+ }
+}
+
+private final class DeletedMessagesTransparencySliderItemNode: ListViewItemNode {
+ private let backgroundNode: ASDisplayNode
+ private let topStripeNode: ASDisplayNode
+ private let bottomStripeNode: ASDisplayNode
+ private let maskNode: ASImageNode
+
+ private let leftTextNode: ImmediateTextNode
+ private let rightTextNode: ImmediateTextNode
+ private let centerTextNode: ImmediateTextNode
+ private let slider = ComponentView()
+
+ private var item: DeletedMessagesTransparencySliderItem?
+
+ init() {
+ self.backgroundNode = ASDisplayNode()
+ self.backgroundNode.isLayerBacked = true
+
+ self.topStripeNode = ASDisplayNode()
+ self.topStripeNode.isLayerBacked = true
+
+ self.bottomStripeNode = ASDisplayNode()
+ self.bottomStripeNode.isLayerBacked = true
+
+ self.maskNode = ASImageNode()
+
+ self.leftTextNode = ImmediateTextNode()
+ self.rightTextNode = ImmediateTextNode()
+ self.centerTextNode = ImmediateTextNode()
+
+ super.init(layerBacked: false)
+
+ self.addSubnode(self.leftTextNode)
+ self.addSubnode(self.rightTextNode)
+ self.addSubnode(self.centerTextNode)
+ }
+
+ func asyncLayout() -> (_ item: DeletedMessagesTransparencySliderItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
+ return { item, params, neighbors in
+ let separatorHeight = UIScreenPixel
+ let contentSize = CGSize(width: params.width, height: 88.0)
+ let insets = itemListNeighborsGroupedInsets(neighbors, params)
+
+ let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
+ let layoutSize = layout.size
+
+ return (layout, { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+
+ strongSelf.item = item
+
+ strongSelf.backgroundNode.backgroundColor = item.theme.list.itemBlocksBackgroundColor
+ strongSelf.topStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
+ strongSelf.bottomStripeNode.backgroundColor = item.theme.list.itemBlocksSeparatorColor
+
+ if strongSelf.backgroundNode.supernode == nil {
+ strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
+ }
+ if strongSelf.topStripeNode.supernode == nil {
+ strongSelf.insertSubnode(strongSelf.topStripeNode, at: 1)
+ }
+ if strongSelf.bottomStripeNode.supernode == nil {
+ strongSelf.insertSubnode(strongSelf.bottomStripeNode, at: 2)
+ }
+ if strongSelf.maskNode.supernode == nil {
+ strongSelf.insertSubnode(strongSelf.maskNode, at: 3)
+ }
+
+ let hasCorners = itemListHasRoundedBlockLayout(params)
+ var hasTopCorners = false
+ var hasBottomCorners = false
+
+ switch neighbors.top {
+ case .sameSection(false):
+ strongSelf.topStripeNode.isHidden = true
+ default:
+ hasTopCorners = true
+ strongSelf.topStripeNode.isHidden = hasCorners
+ }
+
+ let bottomStripeInset: CGFloat
+ let bottomStripeOffset: CGFloat
+ switch neighbors.bottom {
+ case .sameSection(false):
+ bottomStripeInset = 0.0
+ bottomStripeOffset = -separatorHeight
+ strongSelf.bottomStripeNode.isHidden = false
+ default:
+ bottomStripeInset = 0.0
+ bottomStripeOffset = 0.0
+ hasBottomCorners = true
+ strongSelf.bottomStripeNode.isHidden = hasCorners
+ }
+
+ strongSelf.maskNode.image = hasCorners ? PresentationResourcesItemList.cornersImage(item.theme, top: hasTopCorners, bottom: hasBottomCorners) : nil
+
+ strongSelf.backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: params.width, height: contentSize.height + min(insets.top, separatorHeight) + min(insets.bottom, separatorHeight)))
+ strongSelf.maskNode.frame = strongSelf.backgroundNode.frame.insetBy(dx: params.leftInset, dy: 0.0)
+ strongSelf.topStripeNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -min(insets.top, separatorHeight)), size: CGSize(width: layoutSize.width, height: separatorHeight))
+ strongSelf.bottomStripeNode.frame = CGRect(origin: CGPoint(x: bottomStripeInset, y: contentSize.height + bottomStripeOffset), size: CGSize(width: layoutSize.width - bottomStripeInset, height: separatorHeight))
+
+ let sideTextColor = item.theme.list.itemSecondaryTextColor.withAlphaComponent(item.isEnabled ? 1.0 : 0.6)
+ let centerTextColor = item.isEnabled ? item.theme.list.itemPrimaryTextColor : item.theme.list.itemDisabledTextColor
+
+ strongSelf.leftTextNode.attributedText = NSAttributedString(string: "Меньше", font: Font.regular(13.0), textColor: sideTextColor)
+ strongSelf.rightTextNode.attributedText = NSAttributedString(string: "Больше", font: Font.regular(13.0), textColor: sideTextColor)
+ strongSelf.centerTextNode.attributedText = NSAttributedString(string: "Прозрачность \(item.value)%", font: Font.regular(16.0), textColor: centerTextColor)
+
+ let leftTextSize = strongSelf.leftTextNode.updateLayout(CGSize(width: 120.0, height: 100.0))
+ let rightTextSize = strongSelf.rightTextNode.updateLayout(CGSize(width: 120.0, height: 100.0))
+ let centerTextSize = strongSelf.centerTextNode.updateLayout(CGSize(width: params.width - params.leftInset - params.rightInset - 60.0, height: 100.0))
+
+ let sideInset: CGFloat = 18.0
+ strongSelf.leftTextNode.frame = CGRect(origin: CGPoint(x: params.leftInset + sideInset, y: 15.0), size: leftTextSize)
+ strongSelf.rightTextNode.frame = CGRect(origin: CGPoint(x: params.width - params.leftInset - sideInset - rightTextSize.width, y: 15.0), size: rightTextSize)
+ strongSelf.centerTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - centerTextSize.width) / 2.0), y: 11.0), size: centerTextSize)
+
+ let maxRange = CGFloat(maxDeletedMessageTransparencyPercent - minDeletedMessageTransparencyPercent)
+ let normalizedValue: CGFloat
+ if maxRange.isZero {
+ normalizedValue = 0.0
+ } else {
+ normalizedValue = CGFloat(item.value - minDeletedMessageTransparencyPercent) / maxRange
+ }
+
+ let sliderSize = strongSelf.slider.update(
+ transition: .immediate,
+ component: AnyComponent(
+ SliderComponent(
+ content: .continuous(.init(
+ value: normalizedValue,
+ minValue: nil,
+ valueUpdated: { [weak self] value in
+ guard let self, let item = self.item, item.isEnabled else {
+ return
+ }
+
+ let transparencyValue = Int32((CGFloat(minDeletedMessageTransparencyPercent) + maxRange * value).rounded())
+ item.updated(clampDeletedMessageTransparencyPercent(transparencyValue))
+ }
+ )),
+ useNative: true,
+ trackBackgroundColor: item.theme.list.itemSwitchColors.frameColor,
+ trackForegroundColor: item.isEnabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor
+ )
+ ),
+ environment: {},
+ containerSize: CGSize(width: params.width - params.leftInset - params.rightInset - 15.0 * 2.0, height: 44.0)
+ )
+
+ if let sliderView = strongSelf.slider.view {
+ if sliderView.superview == nil {
+ strongSelf.view.addSubview(sliderView)
+ }
+
+ sliderView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((params.width - sliderSize.width) / 2.0), y: 36.0), size: sliderSize)
+ sliderView.isUserInteractionEnabled = item.isEnabled
+ sliderView.alpha = item.isEnabled ? 1.0 : 0.55
+ }
+ })
+ }
+ }
+
+ override func animateInsertion(_ currentTimestamp: Double, duration: Double, options: ListViewItemAnimationOptions) {
+ self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
+ }
+
+ override func animateRemoved(_ currentTimestamp: Double, duration: Double) {
+ self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false)
+ }
+}
diff --git a/submodules/SettingsUI/Sources/GhostgramSettingsController.swift b/submodules/SettingsUI/Sources/GhostgramSettingsController.swift
index a045e524..0c7d5a40 100644
--- a/submodules/SettingsUI/Sources/GhostgramSettingsController.swift
+++ b/submodules/SettingsUI/Sources/GhostgramSettingsController.swift
@@ -19,6 +19,7 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
case misc(PresentationTheme, String, String)
case deviceSpoof(PresentationTheme, String, String)
case voiceMorpher(PresentationTheme, String, String)
+ case sendDelay(PresentationTheme, String, String)
case info(PresentationTheme, String)
var section: ItemListSectionId {
@@ -37,8 +38,10 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
return 3
case .voiceMorpher:
return 4
- case .info:
+ case .sendDelay:
return 5
+ case .info:
+ return 6
}
}
@@ -74,6 +77,12 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
return true
}
return false
+ case let .sendDelay(lhsTheme, lhsText, lhsValue):
+ if case let .sendDelay(rhsTheme, rhsText, rhsValue) = rhs,
+ lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
+ return true
+ }
+ return false
case let .info(lhsTheme, lhsText):
if case let .info(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
@@ -144,6 +153,17 @@ private enum GhostgramSettingsEntry: ItemListNodeEntry {
arguments.openVoiceMorpher()
}
)
+ case let .sendDelay(_, text, value):
+ return ItemListDisclosureItem(
+ presentationData: presentationData,
+ title: text,
+ label: value,
+ sectionId: self.section,
+ style: .blocks,
+ action: {
+ arguments.openSendDelay()
+ }
+ )
case let .info(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
@@ -158,19 +178,22 @@ private final class GhostgramSettingsControllerArguments {
let openMisc: () -> Void
let openDeviceSpoof: () -> Void
let openVoiceMorpher: () -> Void
+ let openSendDelay: () -> Void
init(
openDeletedMessages: @escaping () -> Void,
openGhostMode: @escaping () -> Void,
openMisc: @escaping () -> Void,
openDeviceSpoof: @escaping () -> Void,
- openVoiceMorpher: @escaping () -> Void
+ openVoiceMorpher: @escaping () -> Void,
+ openSendDelay: @escaping () -> Void
) {
self.openDeletedMessages = openDeletedMessages
self.openGhostMode = openGhostMode
self.openMisc = openMisc
self.openDeviceSpoof = openDeviceSpoof
self.openVoiceMorpher = openVoiceMorpher
+ self.openSendDelay = openSendDelay
}
}
@@ -184,6 +207,8 @@ private struct GhostgramSettingsState: Equatable {
var miscActiveCount: Int
var deviceSpoofEnabled: Bool
var voiceMorpherEnabled: Bool
+ var voiceMorpherPresetName: String
+ var sendDelayEnabled: Bool
static func current() -> GhostgramSettingsState {
return GhostgramSettingsState(
@@ -193,7 +218,9 @@ private struct GhostgramSettingsState: Equatable {
miscEnabled: MiscSettingsManager.shared.isEnabled,
miscActiveCount: MiscSettingsManager.shared.activeFeatureCount,
deviceSpoofEnabled: DeviceSpoofManager.shared.isEnabled,
- voiceMorpherEnabled: VoiceMorpherManager.shared.isEnabled
+ voiceMorpherEnabled: VoiceMorpherManager.shared.isEnabled,
+ voiceMorpherPresetName: VoiceMorpherManager.shared.selectedPreset.name,
+ sendDelayEnabled: SendDelayManager.shared.isEnabled
)
}
}
@@ -223,9 +250,13 @@ private func ghostgramSettingsControllerEntries(
entries.append(.deviceSpoof(presentationData.theme, "Подмена устройства", deviceSpoofStatus))
// Voice Morpher
- let voiceMorpherStatus = state.voiceMorpherEnabled ? VoiceMorpherManager.shared.selectedPreset.name : "Выкл"
+ let voiceMorpherStatus = state.voiceMorpherEnabled ? state.voiceMorpherPresetName : "Выкл"
entries.append(.voiceMorpher(presentationData.theme, "Голосовой двойник", voiceMorpherStatus))
+ // Send Delay
+ let sendDelayStatus = state.sendDelayEnabled ? "Вкл" : "Выкл"
+ entries.append(.sendDelay(presentationData.theme, "Отложка сообщений", sendDelayStatus))
+
// Info
entries.append(.info(presentationData.theme, "Функции конфиденциальности Ghostgram. Скрытые отметки о прочтении, обход исчезающих сообщений, обход защиты от пересылки и другое."))
@@ -255,6 +286,9 @@ public func ghostgramSettingsController(context: AccountContext) -> ViewControll
},
openVoiceMorpher: {
pushControllerImpl?(voiceMorpherController(context: context), true)
+ },
+ openSendDelay: {
+ pushControllerImpl?(sendDelayController(context: context), true)
}
)
diff --git a/submodules/SettingsUI/Sources/MiscController.swift b/submodules/SettingsUI/Sources/MiscController.swift
index eb8f4997..35659dce 100644
--- a/submodules/SettingsUI/Sources/MiscController.swift
+++ b/submodules/SettingsUI/Sources/MiscController.swift
@@ -328,7 +328,7 @@ public func miscController(context: AccountContext) -> ViewController {
let controllerState = ItemListControllerState(
presentationData: ItemListPresentationData(presentationData),
- title: .text("Misc"),
+ title: .text("Прочее"),
leftNavigationButton: nil,
rightNavigationButton: nil,
backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
diff --git a/submodules/SettingsUI/Sources/Notifications/NotificationSearchItem.swift b/submodules/SettingsUI/Sources/Notifications/NotificationSearchItem.swift
index 49fd2c6d..393f2bac 100644
--- a/submodules/SettingsUI/Sources/Notifications/NotificationSearchItem.swift
+++ b/submodules/SettingsUI/Sources/Notifications/NotificationSearchItem.swift
@@ -90,7 +90,7 @@ class NotificationSearchItemNode: ListViewItemNode {
required init() {
self.searchBarNode = SearchBarPlaceholderNode()
- super.init(layerBacked: false, dynamicBounce: false)
+ super.init(layerBacked: false)
self.addSubnode(self.searchBarNode)
}
@@ -104,16 +104,16 @@ class NotificationSearchItemNode: ListViewItemNode {
}
func asyncLayout() -> (_ item: NotificationSearchItem, _ params: ListViewItemLayoutParams) -> (ListViewItemNodeLayout, (Bool) -> Void) {
- let searchBarNodeLayout = self.searchBarNode.asyncLayout()
let placeholder = self.placeholder
return { item, params in
let baseWidth = params.width - params.leftInset - params.rightInset
let backgroundColor = item.theme.chatList.itemBackgroundColor
+ let iconColor = UIColor(rgb: 0x8e8e93)
+ let controlColor = item.theme.chat.inputPanel.panelControlColor
let placeholderString = NSAttributedString(string: placeholder ?? "", font: searchBarFont, textColor: UIColor(rgb: 0x8e8e93))
- let (_, searchBarApply) = searchBarNodeLayout(placeholderString, placeholderString, CGSize(width: baseWidth - 16.0, height: 28.0), 1.0, UIColor(rgb: 0x8e8e93), item.theme.chatList.regularSearchBarColor, backgroundColor, .immediate)
let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 44.0), insets: UIEdgeInsets())
@@ -126,10 +126,9 @@ class NotificationSearchItemNode: ListViewItemNode {
transition = .immediate
}
- strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: CGSize(width: baseWidth - 16.0, height: 28.0))
- searchBarApply()
-
- strongSelf.searchBarNode.bounds = CGRect(origin: CGPoint(), size: CGSize(width: baseWidth - 16.0, height: 28.0))
+ let searchBarSize = CGSize(width: baseWidth - 16.0, height: 28.0)
+ strongSelf.searchBarNode.frame = CGRect(origin: CGPoint(x: params.leftInset + 8.0, y: 8.0), size: searchBarSize)
+ _ = strongSelf.searchBarNode.updateLayout(placeholderString: placeholderString, compactPlaceholderString: placeholderString, constrainedSize: searchBarSize, expansionProgress: 1.0, iconColor: iconColor, foregroundColor: item.theme.chatList.regularSearchBarColor, backgroundColor: backgroundColor, controlColor: controlColor, transition: transition)
transition.updateBackgroundColor(node: strongSelf, color: backgroundColor)
}
diff --git a/submodules/SettingsUI/Sources/SendDelayController.swift b/submodules/SettingsUI/Sources/SendDelayController.swift
new file mode 100644
index 00000000..34a68215
--- /dev/null
+++ b/submodules/SettingsUI/Sources/SendDelayController.swift
@@ -0,0 +1,148 @@
+import Foundation
+import UIKit
+import Display
+import SwiftSignalKit
+import TelegramCore
+import TelegramPresentationData
+import ItemListUI
+import AccountContext
+
+// MARK: - Section / Entry definitions
+
+private enum SendDelaySection: Int32 {
+ case main
+}
+
+private enum SendDelayEntry: ItemListNodeEntry {
+ case toggle(PresentationTheme, String, Bool)
+ case info(PresentationTheme, String)
+
+ var section: ItemListSectionId {
+ return SendDelaySection.main.rawValue
+ }
+
+ var stableId: Int32 {
+ switch self {
+ case .toggle: return 0
+ case .info: return 1
+ }
+ }
+
+ static func ==(lhs: SendDelayEntry, rhs: SendDelayEntry) -> Bool {
+ switch lhs {
+ case let .toggle(lhsTheme, lhsText, lhsValue):
+ if case let .toggle(rhsTheme, rhsText, rhsValue) = rhs,
+ lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
+ return true
+ }
+ return false
+ case let .info(lhsTheme, lhsText):
+ if case let .info(rhsTheme, rhsText) = rhs,
+ lhsTheme === rhsTheme, lhsText == rhsText {
+ return true
+ }
+ return false
+ }
+ }
+
+ static func <(lhs: SendDelayEntry, rhs: SendDelayEntry) -> Bool {
+ return lhs.stableId < rhs.stableId
+ }
+
+ func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
+ let arguments = arguments as! SendDelayControllerArguments
+ switch self {
+ case let .toggle(_, text, value):
+ return ItemListSwitchItem(
+ presentationData: presentationData,
+ title: text,
+ value: value,
+ sectionId: self.section,
+ style: .blocks,
+ updated: { newValue in
+ arguments.toggleEnabled(newValue)
+ }
+ )
+ case let .info(_, text):
+ return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
+ }
+ }
+}
+
+// MARK: - Arguments
+
+private final class SendDelayControllerArguments {
+ let toggleEnabled: (Bool) -> Void
+ init(toggleEnabled: @escaping (Bool) -> Void) {
+ self.toggleEnabled = toggleEnabled
+ }
+}
+
+// MARK: - State
+
+private struct SendDelayControllerState: Equatable {
+ var isEnabled: Bool
+}
+
+// MARK: - Entries builder
+
+private func sendDelayControllerEntries(
+ presentationData: PresentationData,
+ state: SendDelayControllerState
+) -> [SendDelayEntry] {
+ let theme = presentationData.theme
+ return [
+ .toggle(theme, "Использовать отложку", state.isEnabled),
+ .info(theme, "Автоматически ставит задержку в ~12 секунд (дольше для сообщений с вложениями) при отправке сообщений. При использовании этой функции вы не будете появляться в сети.")
+ ]
+}
+
+// MARK: - Controller
+
+public func sendDelayController(context: AccountContext) -> ViewController {
+ let statePromise = ValuePromise(
+ SendDelayControllerState(isEnabled: SendDelayManager.shared.isEnabled),
+ ignoreRepeated: true
+ )
+ let stateValue = Atomic(value: SendDelayControllerState(isEnabled: SendDelayManager.shared.isEnabled))
+
+ let updateState: ((inout SendDelayControllerState) -> Void) -> Void = { f in
+ let result = stateValue.modify { state in
+ var s = state; f(&s); return s
+ }
+ statePromise.set(result)
+ }
+
+ let arguments = SendDelayControllerArguments(
+ toggleEnabled: { value in
+ SendDelayManager.shared.isEnabled = value
+ updateState { $0.isEnabled = value }
+ }
+ )
+
+ let signal = combineLatest(
+ context.sharedContext.presentationData,
+ statePromise.get()
+ )
+ |> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
+ let entries = sendDelayControllerEntries(presentationData: presentationData, state: state)
+
+ let controllerState = ItemListControllerState(
+ presentationData: ItemListPresentationData(presentationData),
+ title: .text("Отложка сообщений"),
+ leftNavigationButton: nil,
+ rightNavigationButton: nil,
+ backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back),
+ animateChanges: false
+ )
+ let listState = ItemListNodeState(
+ presentationData: ItemListPresentationData(presentationData),
+ entries: entries,
+ style: .blocks,
+ animateChanges: true
+ )
+ return (controllerState, (listState, arguments))
+ }
+
+ return ItemListController(context: context, state: signal)
+}
diff --git a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift
index 04529dbe..34a6a8cd 100644
--- a/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift
+++ b/submodules/TelegramCallsUI/Sources/CallStatusBarNode.swift
@@ -22,7 +22,7 @@ private let pink = UIColor(rgb: 0xef436c)
private let latePurple = UIColor(rgb: 0xaa56a6)
private let latePink = UIColor(rgb: 0xef476f)
-private func textForTimeout(value: Int32) -> String {
+private func callStatusBarTextForTimeout(value: Int32) -> String {
if value < 3600 {
let minutes = value / 60
let seconds = value % 60
@@ -498,9 +498,9 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
timerText = presentationData.strings.VoiceChat_StatusStartsIn(scheduledTimeIntervalString(strings: presentationData.strings, value: elapsedTime)).string
} else if elapsedTime < 0 {
isLate = true
- timerText = presentationData.strings.VoiceChat_StatusLateBy(textForTimeout(value: abs(elapsedTime))).string
+ timerText = presentationData.strings.VoiceChat_StatusLateBy(callStatusBarTextForTimeout(value: abs(elapsedTime))).string
} else {
- timerText = presentationData.strings.VoiceChat_StatusStartsIn(textForTimeout(value: elapsedTime)).string
+ timerText = presentationData.strings.VoiceChat_StatusStartsIn(callStatusBarTextForTimeout(value: elapsedTime)).string
}
segments.append(.text(0, NSAttributedString(string: timerText, font: textFont, textColor: textColor)))
} else if let membersCount = membersCount {
diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift b/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift
index 1143f2ea..42f21425 100644
--- a/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift
+++ b/submodules/TelegramCallsUI/Sources/VideoChatScheduledInfoComponent.swift
@@ -13,7 +13,7 @@ private let pink = UIColor(rgb: 0xef436c)
private let latePurple = UIColor(rgb: 0x974aa9)
private let latePink = UIColor(rgb: 0xf0436c)
-private func textForTimeout(value: Int32) -> String {
+private func scheduledInfoTextForTimeout(value: Int32) -> String {
if value < 3600 {
let minutes = value / 60
let seconds = value % 60
@@ -150,7 +150,7 @@ final class VideoChatScheduledInfoComponent: Component {
if remainingSeconds >= 86400 {
countdownText = scheduledTimeIntervalString(strings: component.strings, value: remainingSeconds)
} else {
- countdownText = textForTimeout(value: abs(remainingSeconds))
+ countdownText = scheduledInfoTextForTimeout(value: abs(remainingSeconds))
/*if remainingSeconds < 0 && !self.isLate {
self.isLate = true
self.foregroundGradientLayer.colors = [latePink.cgColor, latePurple.cgColor, latePurple.cgColor]
diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift
index 6f4abcd3..ad9ae24b 100644
--- a/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift
+++ b/submodules/TelegramCallsUI/Sources/VoiceChatTimerNode.swift
@@ -12,7 +12,7 @@ private let pink = UIColor(rgb: 0xef436c)
private let latePurple = UIColor(rgb: 0x974aa9)
private let latePink = UIColor(rgb: 0xf0436c)
-private func textForTimeout(value: Int32) -> String {
+private func voiceChatTimerTextForTimeout(value: Int32) -> String {
if value < 3600 {
let minutes = value / 60
let seconds = value % 60
@@ -189,7 +189,7 @@ final class VoiceChatTimerNode: ASDisplayNode {
if elapsedTime >= 86400 {
timerText = scheduledTimeIntervalString(strings: self.strings, value: elapsedTime)
} else {
- timerText = textForTimeout(value: abs(elapsedTime))
+ timerText = voiceChatTimerTextForTimeout(value: abs(elapsedTime))
if elapsedTime < 0 && !self.isLate {
self.isLate = true
self.foregroundGradientLayer.colors = [latePink.cgColor, latePurple.cgColor, latePurple.cgColor]
diff --git a/submodules/TelegramCore/Sources/AntiDelete/AntiDeleteManager.swift b/submodules/TelegramCore/Sources/AntiDelete/AntiDeleteManager.swift
index 30614454..ca2f73f8 100644
--- a/submodules/TelegramCore/Sources/AntiDelete/AntiDeleteManager.swift
+++ b/submodules/TelegramCore/Sources/AntiDelete/AntiDeleteManager.swift
@@ -11,6 +11,7 @@ public final class AntiDeleteManager {
private let defaults = UserDefaults.standard
private let enabledKey = "antiDelete.enabled"
private let archiveMediaKey = "antiDelete.archiveMedia"
+ private let deletedMessageTransparencyKey = "antiDelete.deletedMessageTransparency"
private let archiveKey = "antiDelete.archive"
private let deletedIdsKey = "antiDelete.deletedIds"
@@ -26,6 +27,33 @@ public final class AntiDeleteManager {
set { defaults.set(newValue, forKey: archiveMediaKey) }
}
+ /// Минимальное значение прозрачности удалённого сообщения
+ public static let minDeletedMessageTransparency: Double = 0.0
+
+ /// Максимальное значение прозрачности удалённого сообщения
+ public static let maxDeletedMessageTransparency: Double = 0.8
+
+ /// Значение прозрачности удалённого сообщения по умолчанию
+ public static let defaultDeletedMessageTransparency: Double = 0.45
+
+ /// Прозрачность удалённых сообщений (0.0 = непрозрачно, 0.8 = максимально прозрачно)
+ public var deletedMessageTransparency: Double {
+ get {
+ let value = defaults.object(forKey: deletedMessageTransparencyKey) as? NSNumber
+ let resolvedValue = value?.doubleValue ?? Self.defaultDeletedMessageTransparency
+ return max(Self.minDeletedMessageTransparency, min(Self.maxDeletedMessageTransparency, resolvedValue))
+ }
+ set {
+ let clampedValue = max(Self.minDeletedMessageTransparency, min(Self.maxDeletedMessageTransparency, newValue))
+ defaults.set(clampedValue, forKey: deletedMessageTransparencyKey)
+ }
+ }
+
+ /// Альфа для отображения удалённых сообщений
+ public var deletedMessageDisplayAlpha: Double {
+ return 1.0 - self.deletedMessageTransparency
+ }
+
// MARK: - Deleted Message IDs Storage
private var deletedMessageIds: Set = []
@@ -120,6 +148,9 @@ public final class AntiDeleteManager {
if defaults.object(forKey: archiveMediaKey) == nil {
defaults.set(true, forKey: archiveMediaKey)
}
+ if defaults.object(forKey: deletedMessageTransparencyKey) == nil {
+ defaults.set(Self.defaultDeletedMessageTransparency, forKey: deletedMessageTransparencyKey)
+ }
loadArchive()
loadDeletedIds()
}
diff --git a/submodules/TelegramCore/Sources/GhostMode/GhostModeManager.swift b/submodules/TelegramCore/Sources/GhostMode/GhostModeManager.swift
index ba06e6d5..ccca924c 100644
--- a/submodules/TelegramCore/Sources/GhostMode/GhostModeManager.swift
+++ b/submodules/TelegramCore/Sources/GhostMode/GhostModeManager.swift
@@ -23,9 +23,6 @@ public final class GhostModeManager {
private let defaults = UserDefaults.standard
- // Prevents recursive mutual-exclusion calls
- private var isApplyingMutualExclusion = false
-
// MARK: - Properties
/// Master toggle for Ghost Mode.
@@ -34,11 +31,9 @@ public final class GhostModeManager {
get { defaults.bool(forKey: Keys.isEnabled) }
set {
defaults.set(newValue, forKey: Keys.isEnabled)
- if newValue && !isApplyingMutualExclusion {
- // Ghost Mode ON → disable Always Online
- isApplyingMutualExclusion = true
+ if newValue {
+ // Ghost Mode ON → disable Always Online so they don't coexist in UI
MiscSettingsManager.shared.disableAlwaysOnlineForMutualExclusion()
- isApplyingMutualExclusion = false
}
notifySettingsChanged()
}
@@ -102,9 +97,11 @@ public final class GhostModeManager {
}
/// Online status is hidden only when Ghost Mode is on AND Always Online is NOT active.
+ /// Checks alwaysOnline raw value (not shouldAlwaysBeOnline) so ghost mode works
+ /// even when the Misc master toggle is off.
public var shouldHideOnlineStatus: Bool {
guard isEnabled && hideOnlineStatus else { return false }
- return !MiscSettingsManager.shared.shouldAlwaysBeOnline
+ return !MiscSettingsManager.shared.alwaysOnline
}
public var shouldHideTypingIndicator: Bool {
@@ -114,7 +111,7 @@ public final class GhostModeManager {
/// Force offline only when Ghost Mode is on AND Always Online is NOT active.
public var shouldForceOffline: Bool {
guard isEnabled && forceOffline else { return false }
- return !MiscSettingsManager.shared.shouldAlwaysBeOnline
+ return !MiscSettingsManager.shared.alwaysOnline
}
/// Count of active features (e.g., "5/5")
@@ -131,17 +128,6 @@ public final class GhostModeManager {
/// Total number of features
public static let totalFeatureCount = 5
- // MARK: - Internal mutual exclusion (called by MiscSettingsManager)
-
- /// Called by MiscSettingsManager when Always Online is turned on.
- /// Disables Ghost Mode without triggering mutual exclusion back.
- public func disableForMutualExclusion() {
- isApplyingMutualExclusion = true
- defaults.set(false, forKey: Keys.isEnabled)
- notifySettingsChanged()
- isApplyingMutualExclusion = false
- }
-
// MARK: - Initialization
private init() {
diff --git a/submodules/TelegramCore/Sources/MiscSettings/MiscSettingsManager.swift b/submodules/TelegramCore/Sources/MiscSettings/MiscSettingsManager.swift
index 819c9e42..58d7161f 100644
--- a/submodules/TelegramCore/Sources/MiscSettings/MiscSettingsManager.swift
+++ b/submodules/TelegramCore/Sources/MiscSettings/MiscSettingsManager.swift
@@ -16,9 +16,6 @@ public final class MiscSettingsManager {
private let defaults = UserDefaults.standard
- // Prevents recursive mutual-exclusion calls
- private var isApplyingMutualExclusion = false
-
// MARK: - Main Toggle
public var isEnabled: Bool {
@@ -68,17 +65,12 @@ public final class MiscSettingsManager {
}
/// Always appear as online.
- /// Enabling this automatically disables Ghost Mode (mutual exclusion).
+ /// NOTE: Ghost Mode features dynamically yield to Always Online via their
+ /// `shouldAlwaysBeOnline` check, so no permanent disabling is needed here.
public var alwaysOnline: Bool {
get { defaults.bool(forKey: Keys.alwaysOnline) }
set {
defaults.set(newValue, forKey: Keys.alwaysOnline)
- if newValue && !isApplyingMutualExclusion {
- // Always Online ON → disable Ghost Mode
- isApplyingMutualExclusion = true
- GhostModeManager.shared.disableForMutualExclusion()
- isApplyingMutualExclusion = false
- }
notifySettingsChanged()
}
}
@@ -122,7 +114,7 @@ public final class MiscSettingsManager {
disableViewOnceAutoDelete = true
bypassScreenshotProtection = true
blockAds = true
- alwaysOnline = true // setter handles mutual exclusion
+ alwaysOnline = true
}
public func disableAll() {
@@ -136,12 +128,10 @@ public final class MiscSettingsManager {
// MARK: - Internal mutual exclusion (called by GhostModeManager)
/// Called by GhostModeManager when Ghost Mode is turned on.
- /// Disables Always Online without triggering mutual exclusion back.
+ /// Disables Always Online so the two modes don't coexist in the UI.
public func disableAlwaysOnlineForMutualExclusion() {
- isApplyingMutualExclusion = true
defaults.set(false, forKey: Keys.alwaysOnline)
notifySettingsChanged()
- isApplyingMutualExclusion = false
}
// MARK: - Notification
diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift
index 3395dae4..3b1f7165 100644
--- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift
+++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift
@@ -365,14 +365,46 @@ public func enqueueMessages(account: Account, peerId: PeerId, messages: [Enqueue
} else {
signal = .single(messages.map { (false, $0) })
}
+
+ let hasMedia = messages.contains { message in
+ if case let .message(_, _, _, mediaReference, _, _, _, _, _, _) = message {
+ return mediaReference != nil
+ }
+ return false
+ }
+
+ // GHOSTGRAM: Send delay — write to Postbox immediately so the UI
+ // clears the input field, then delay _only_ the return signal.
+ // The actual network send delay is handled by scheduling: we add
+ // OutgoingScheduleInfoMessageAttribute inside the transaction so
+ // the message is stored as "scheduled" and Telegram server sends it
+ // after the delay elapses. The message appears in Scheduled Messages
+ // section for the duration of the delay.
return signal
|> mapToSignal { messages -> Signal<[MessageId?], NoError> in
return account.postbox.transaction { transaction -> [MessageId?] in
- return enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: messages)
+ var finalMessages = messages
+ if SendDelayManager.shared.isEnabled {
+ let delayInterval = hasMedia
+ ? SendDelayManager.mediaDelaySeconds
+ : SendDelayManager.textDelaySeconds
+ let scheduleTime = Int32(Date().timeIntervalSince1970) + Int32(delayInterval)
+ finalMessages = messages.map { (transformed, msg) in
+ let updatedMsg = msg.withUpdatedAttributes { attrs in
+ var attrs = attrs
+ attrs.removeAll(where: { $0 is OutgoingScheduleInfoMessageAttribute })
+ attrs.append(OutgoingScheduleInfoMessageAttribute(scheduleTime: scheduleTime, repeatPeriod: nil))
+ return attrs
+ }
+ return (transformed, updatedMsg)
+ }
+ }
+ return enqueueMessages(transaction: transaction, account: account, peerId: peerId, messages: finalMessages)
}
}
}
+
public func enqueueMessagesToMultiplePeers(account: Account, peerIds: [PeerId], threadIds: [PeerId: Int64], messages: [EnqueueMessage]) -> Signal<[MessageId], NoError> {
let signal: Signal<[(Bool, EnqueueMessage)], NoError>
if let transformOutgoingMessageMedia = account.transformOutgoingMessageMedia {
diff --git a/submodules/TelegramCore/Sources/SendDelay/SendDelayManager.swift b/submodules/TelegramCore/Sources/SendDelay/SendDelayManager.swift
new file mode 100644
index 00000000..57e80e7b
--- /dev/null
+++ b/submodules/TelegramCore/Sources/SendDelay/SendDelayManager.swift
@@ -0,0 +1,55 @@
+import Foundation
+
+/// SendDelayManager - delays outgoing messages by ~12 seconds to prevent
+/// online status from appearing after sending.
+///
+/// Delays are applied per-message at the enqueueMessages level.
+/// Media messages receive a slightly longer delay (~20 s) because upload
+/// time would otherwise reveal the send moment anyway.
+public final class SendDelayManager {
+
+ // MARK: - Singleton
+
+ public static let shared = SendDelayManager()
+
+ // MARK: - UserDefaults Keys
+
+ private enum Keys {
+ static let isEnabled = "SendDelay.isEnabled"
+ }
+
+ // MARK: - Storage
+
+ private let defaults = UserDefaults.standard
+
+ // MARK: - Properties
+
+ /// When true, all outgoing messages are delayed before being enqueued.
+ public var isEnabled: Bool {
+ get { defaults.bool(forKey: Keys.isEnabled) }
+ set {
+ defaults.set(newValue, forKey: Keys.isEnabled)
+ notifySettingsChanged()
+ }
+ }
+
+ // MARK: - Delay constants
+
+ /// Base delay for text-only messages.
+ public static let textDelaySeconds: Double = 12.0
+
+ /// Delay for messages that contain media attachments.
+ public static let mediaDelaySeconds: Double = 20.0
+
+ // MARK: - Init
+
+ private init() {}
+
+ // MARK: - Notifications
+
+ public static let settingsChangedNotification = Notification.Name("SendDelaySettingsChanged")
+
+ private func notifySettingsChanged() {
+ NotificationCenter.default.post(name: SendDelayManager.settingsChangedNotification, object: nil)
+ }
+}
diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift
index 0f6e050e..d252bc6b 100644
--- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift
+++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift
@@ -4233,6 +4233,9 @@ func replayFinalState(
if AntiDeleteManager.shared.isEnabled {
let messageIds = transaction.messageIdsForGlobalIds(ids)
for (index, messageId) in messageIds.enumerated() {
+ // Skip scheduled/local/quick-reply messages — they get deleted when sent, not by the remote peer
+ guard messageId.namespace == Namespaces.Message.Cloud else { continue }
+
if let message = transaction.getMessage(messageId) {
let globalId = index < ids.count ? ids[index] : 0
@@ -4289,6 +4292,9 @@ func replayFinalState(
if AntiDeleteManager.shared.isEnabled {
let messageIds = transaction.messageIdsForGlobalIds(ids)
for messageId in messageIds {
+ // Skip scheduled/local/quick-reply messages — they get deleted when sent, not by the remote peer
+ guard messageId.namespace == Namespaces.Message.Cloud else { continue }
+
// Mark as deleted for icon display
AntiDeleteManager.shared.markAsDeleted(peerId: messageId.peerId.toInt64(), messageId: messageId.id)
@@ -4317,6 +4323,9 @@ func replayFinalState(
// ANTI-DELETE: Archive channel messages with full content before deletion
if AntiDeleteManager.shared.isEnabled {
for messageId in ids {
+ // Skip scheduled/local/quick-reply messages — they get deleted when sent, not by the remote peer
+ guard messageId.namespace == Namespaces.Message.Cloud else { continue }
+
if let message = transaction.getMessage(messageId) {
// Extract text content
let textContent = message.text
@@ -4370,6 +4379,9 @@ func replayFinalState(
// ANTI-DELETE: Mark messages as deleted instead of removing them
if AntiDeleteManager.shared.isEnabled {
for messageId in ids {
+ // Skip scheduled/local/quick-reply messages — they get deleted when sent, not by the remote peer
+ guard messageId.namespace == Namespaces.Message.Cloud else { continue }
+
// Mark as deleted for icon display
AntiDeleteManager.shared.markAsDeleted(peerId: messageId.peerId.toInt64(), messageId: messageId.id)
diff --git a/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift b/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift
index a5c70ae8..cae3b698 100644
--- a/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift
+++ b/submodules/TelegramCore/Sources/State/ManagedAccountPresence.swift
@@ -82,16 +82,19 @@ private final class AccountPresenceManagerImpl {
/// 2. Ghost Mode hide online status → skip update entirely (freeze last-seen)
/// 3. Default app behaviour (wasOnline)
private func refreshPresence() {
- let alwaysOnline = MiscSettingsManager.shared.shouldAlwaysBeOnline
+ // Use raw alwaysOnline flag (not shouldAlwaysBeOnline) so it works independently
+ // of the Misc master toggle. Ghost Mode's shouldHideOnlineStatus already checks
+ // !MiscSettingsManager.shared.alwaysOnline internally.
+ let alwaysOnline = MiscSettingsManager.shared.alwaysOnline
let ghostHideOnline = GhostModeManager.shared.shouldHideOnlineStatus
if alwaysOnline {
// Always Online wins — push online regardless of Ghost Mode
sendPresenceUpdate(online: true)
} else if ghostHideOnline {
- // Ghost Mode active, no Always Online — freeze presence (don't send anything)
- self.onlineTimer?.invalidate()
- self.onlineTimer = nil
+ // Ghost Mode active: actively send offline so the server immediately
+ // hides our last-seen instead of keeping the stale "online" status.
+ sendPresenceUpdate(online: false)
} else {
// Normal mode — follow the app-level state
sendPresenceUpdate(online: wasOnline)
diff --git a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift
index a43f3615..67727795 100644
--- a/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift
+++ b/submodules/TelegramUI/Components/AvatarEditorScreen/Sources/AvatarEditorScreen.swift
@@ -27,7 +27,6 @@ import MediaEditor
import AvatarBackground
import LottieComponent
import UndoUI
-import PremiumAlertController
public struct AvatarKeyboardInputData: Equatable {
var emoji: EmojiPagerContentComponent
diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
index 1d378b4f..2fa5cd2f 100644
--- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
+++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift
@@ -481,6 +481,10 @@ private func mapVisibility(_ visibility: ListViewItemNodeVisibility, boundsSize:
}
}
+private func isDeletedBubbleMessage(_ message: Message) -> Bool {
+ return AntiDeleteManager.shared.isMessageDeleted(peerId: message.id.peerId.toInt64(), messageId: message.id.id) || AntiDeleteManager.shared.isMessageDeleted(text: message.text)
+}
+
public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode {
public class ContentContainer {
public let contentMessageStableId: UInt32
@@ -4368,6 +4372,14 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
}
+ let deletedMessageAlpha = CGFloat(AntiDeleteManager.shared.deletedMessageDisplayAlpha)
+ var deletedMessageStableIds = Set()
+ for (message, _) in item.content {
+ if isDeletedBubbleMessage(message) {
+ deletedMessageStableIds.insert(message.stableId)
+ }
+ }
+
var incomingOffset: CGFloat = 0.0
switch backgroundType {
case .incoming:
@@ -4512,10 +4524,31 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
}
contentContainer?.update(size: relativeFrame.size, contentOrigin: contentOrigin, selectionInsets: selectionInsets, index: index, presentationData: item.presentationData, graphics: graphics, backgroundType: backgroundType, presentationContext: item.controllerInteraction.presentationContext, mediaBox: item.context.account.postbox.mediaBox, messageSelection: itemSelection)
+
+ if let contentContainer = contentContainer {
+ let containerAlpha: CGFloat = deletedMessageStableIds.contains(stableId) ? deletedMessageAlpha : 1.0
+ if case .System = animation {
+ animation.animator.updateAlpha(layer: contentContainer.sourceNode.contentNode.layer, alpha: containerAlpha, completion: nil)
+ } else {
+ contentContainer.sourceNode.contentNode.alpha = containerAlpha
+ }
+ }
index += 1
}
+ let mainContainerAlpha: CGFloat
+ if contentContainerNodeFrames.isEmpty, !deletedMessageStableIds.isEmpty {
+ mainContainerAlpha = deletedMessageAlpha
+ } else {
+ mainContainerAlpha = 1.0
+ }
+ if case .System = animation {
+ animation.animator.updateAlpha(layer: strongSelf.mainContextSourceNode.contentNode.layer, alpha: mainContainerAlpha, completion: nil)
+ } else {
+ strongSelf.mainContextSourceNode.contentNode.alpha = mainContainerAlpha
+ }
+
if hasSelection {
var currentMaskView: UIImageView?
if let maskView = strongSelf.contentContainersWrapperNode.view.mask as? UIImageView {
diff --git a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift
index 5ebc8200..09358a7a 100644
--- a/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift
+++ b/submodules/TelegramUI/Components/ChatListHeaderComponent/Sources/NavigationButtonComponent.swift
@@ -29,6 +29,27 @@ public final class NavigationButtonComponent: Component {
case more
case icon(imageName: String)
case proxy(status: ChatTitleProxyStatus)
+ /// Liquid glass avatar button for account switching.
+ /// peerId is used as a diff key; avatarImage is the rendered avatar.
+ case avatar(peerId: String, avatarImage: UIImage?)
+
+ public static func ==(lhs: Content, rhs: Content) -> Bool {
+ switch (lhs, rhs) {
+ case let (.text(lt, lb), .text(rt, rb)):
+ return lt == rt && lb == rb
+ case (.more, .more):
+ return true
+ case let (.icon(l), .icon(r)):
+ return l == r
+ case let (.proxy(l), .proxy(r)):
+ return l == r
+ case let (.avatar(lId, _), .avatar(rId, _)):
+ // Re-render when peerId changes; image updates are handled by the view itself
+ return lId == rId
+ default:
+ return false
+ }
+ }
}
public let content: Content
@@ -62,6 +83,12 @@ public final class NavigationButtonComponent: Component {
private var moreButton: MoreHeaderButton?
+ // MARK: - Liquid Glass Avatar
+ private var avatarContainerView: UIView?
+ private var avatarBlurView: UIVisualEffectView?
+ private var avatarImageView: UIImageView?
+ private var avatarBorderLayer: CAShapeLayer?
+
private var component: NavigationButtonComponent?
private var theme: PresentationTheme?
@@ -74,19 +101,23 @@ public final class NavigationButtonComponent: Component {
guard let self else {
return
}
- if highlighted {
- self.textView?.alpha = 0.6
- self.proxyNode?.alpha = 0.6
- self.iconView?.alpha = 0.6
- } else {
- self.textView?.alpha = 1.0
- self.textView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
-
- self.proxyNode?.alpha = 1.0
- self.proxyNode?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
-
- self.iconView?.alpha = 1.0
- self.iconView?.layer.animateAlpha(from: 0.6, to: 1.0, duration: 0.2)
+ let alpha: CGFloat = highlighted ? 0.55 : 1.0
+ self.textView?.alpha = alpha
+ self.proxyNode?.alpha = alpha
+ self.iconView?.alpha = alpha
+ self.avatarContainerView?.alpha = alpha
+ if !highlighted {
+ let animateAlpha = { (layer: CALayer?) in
+ let anim = CABasicAnimation(keyPath: "opacity")
+ anim.fromValue = 0.55
+ anim.toValue = 1.0
+ anim.duration = 0.2
+ layer?.add(anim, forKey: "opacity")
+ }
+ animateAlpha(self.textView?.layer)
+ animateAlpha(self.proxyNode?.layer)
+ animateAlpha(self.iconView?.layer)
+ animateAlpha(self.avatarContainerView?.layer)
}
}
}
@@ -99,6 +130,51 @@ public final class NavigationButtonComponent: Component {
self.component?.pressed(self)
}
+ // MARK: - Liquid glass avatar setup
+
+ private func setupAvatarViewsIfNeeded() {
+ guard avatarContainerView == nil else { return }
+
+ // Container holds blur + image
+ let container = UIView()
+ container.isUserInteractionEnabled = false
+ container.clipsToBounds = true
+
+ // Blur background — liquid glass effect
+ let blurEffect = UIBlurEffect(style: .systemUltraThinMaterial)
+ let blurView = UIVisualEffectView(effect: blurEffect)
+ blurView.isUserInteractionEnabled = false
+ container.addSubview(blurView)
+
+ // Avatar image on top of blur
+ let imageView = UIImageView()
+ imageView.contentMode = .scaleAspectFill
+ imageView.isUserInteractionEnabled = false
+ imageView.clipsToBounds = true
+ container.addSubview(imageView)
+
+ self.addSubview(container)
+ self.avatarContainerView = container
+ self.avatarBlurView = blurView
+ self.avatarImageView = imageView
+
+ // Subtle glass ring border
+ let borderLayer = CAShapeLayer()
+ borderLayer.fillColor = UIColor.clear.cgColor
+ borderLayer.strokeColor = UIColor.white.withAlphaComponent(0.22).cgColor
+ borderLayer.lineWidth = 1.5
+ container.layer.addSublayer(borderLayer)
+ self.avatarBorderLayer = borderLayer
+ }
+
+ private func removeAvatarViews() {
+ avatarContainerView?.removeFromSuperview()
+ avatarContainerView = nil
+ avatarBlurView = nil
+ avatarImageView = nil
+ avatarBorderLayer = nil
+ }
+
func update(component: NavigationButtonComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize {
self.component = component
@@ -113,6 +189,7 @@ public final class NavigationButtonComponent: Component {
var imageName: String?
var proxyStatus: ChatTitleProxyStatus?
var isMore: Bool = false
+ var avatarContent: (peerId: String, image: UIImage?)? = nil
switch component.content {
case let .text(title, isBold):
@@ -123,10 +200,13 @@ public final class NavigationButtonComponent: Component {
imageName = imageNameValue
case let .proxy(status):
proxyStatus = status
+ case let .avatar(peerId, image):
+ avatarContent = (peerId, image)
}
var size = CGSize(width: 0.0, height: availableSize.height)
+ // MARK: Text
if let textString = textString {
let textView: ImmediateTextView
if let current = self.textView {
@@ -144,11 +224,13 @@ public final class NavigationButtonComponent: Component {
size.width = max(44.0, textSize.width + textInset * 2.0)
textView.frame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((availableSize.height - textSize.height) / 2.0)), size: textSize)
+ removeAvatarViews()
} else if let textView = self.textView {
self.textView = nil
textView.removeFromSuperview()
}
+ // MARK: Icon
if let imageName = imageName {
let iconView: UIImageView
if let current = self.iconView {
@@ -166,15 +248,16 @@ public final class NavigationButtonComponent: Component {
if let iconSize = iconView.image?.size {
size.width = 44.0
-
iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((availableSize.height - iconSize.height) / 2.0)), size: iconSize)
}
+ removeAvatarViews()
} else if let iconView = self.iconView {
self.iconView = nil
iconView.removeFromSuperview()
self.iconImageName = nil
}
+ // MARK: Proxy
if let proxyStatus = proxyStatus {
let proxyNode: ChatTitleProxyNode
if let current = self.proxyNode {
@@ -191,13 +274,14 @@ public final class NavigationButtonComponent: Component {
proxyNode.theme = theme
proxyNode.status = proxyStatus
-
proxyNode.frame = CGRect(origin: CGPoint(x: floor((size.width - proxySize.width) / 2.0), y: floor((availableSize.height - proxySize.height) / 2.0)), size: proxySize)
+ removeAvatarViews()
} else if let proxyNode = self.proxyNode {
self.proxyNode = nil
proxyNode.removeFromSupernode()
}
+ // MARK: More
if isMore {
let moreButton: MoreHeaderButton
if let current = self.moreButton, !themeUpdated {
@@ -233,13 +317,52 @@ public final class NavigationButtonComponent: Component {
size.width = 44.0
moreButton.setContent(.more(MoreHeaderButton.optionsCircleImage(color: theme.rootController.navigationBar.buttonColor)))
-
moreButton.frame = CGRect(origin: CGPoint(x: floor((size.width - buttonSize.width) / 2.0), y: floor((size.height - buttonSize.height) / 2.0)), size: buttonSize)
+ removeAvatarViews()
} else if let moreButton = self.moreButton {
self.moreButton = nil
moreButton.removeFromSupernode()
}
+ // MARK: Liquid Glass Avatar
+ if let (_, image) = avatarContent {
+ setupAvatarViewsIfNeeded()
+
+ let avatarDiameter: CGFloat = 28.0
+ size.width = 44.0
+
+ let containerRect = CGRect(
+ x: floor((size.width - avatarDiameter) / 2.0),
+ y: floor((availableSize.height - avatarDiameter) / 2.0),
+ width: avatarDiameter,
+ height: avatarDiameter
+ )
+
+ avatarContainerView?.frame = containerRect
+ avatarContainerView?.layer.cornerRadius = avatarDiameter / 2.0
+
+ avatarBlurView?.frame = CGRect(origin: .zero, size: containerRect.size)
+
+ if let image = image {
+ avatarImageView?.image = image
+ avatarImageView?.frame = CGRect(origin: .zero, size: containerRect.size)
+ avatarImageView?.backgroundColor = nil
+ } else {
+ avatarImageView?.image = nil
+ // Fallback: solid tinted background when no photo
+ avatarImageView?.frame = CGRect(origin: .zero, size: containerRect.size)
+ avatarImageView?.backgroundColor = theme.list.itemAccentColor.withAlphaComponent(0.35)
+ }
+
+ // Update border ring path
+ let borderPath = UIBezierPath(roundedRect: CGRect(origin: .zero, size: containerRect.size).insetBy(dx: 0.75, dy: 0.75), cornerRadius: avatarDiameter / 2.0)
+ avatarBorderLayer?.path = borderPath.cgPath
+ avatarBorderLayer?.frame = CGRect(origin: .zero, size: containerRect.size)
+
+ } else if avatarContent == nil && avatarContainerView != nil {
+ removeAvatarViews()
+ }
+
return size
}
}
diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD
index 5d86b40c..bebd8c96 100644
--- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD
+++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD
@@ -5,6 +5,8 @@ swift_library(
module_name = "GiftViewScreen",
srcs = glob([
"Sources/**/*.swift",
+ ], exclude = [
+ "Sources/TableComponent.swift",
]),
copts = [
"-warnings-as-errors",
diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift
index 7b7409f4..ee3580ce 100644
--- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift
+++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftOfferAlertController.swift
@@ -133,7 +133,7 @@ public func giftOfferAlertController(
HStack(items, spacing: 4.0)
)
- tableItems.append(.init(
+ tableItems.append(TableComponent.Item(
id: id,
title: title,
hasBackground: false,
@@ -180,12 +180,12 @@ public func giftOfferAlertController(
AlertTextComponent(content: .plain(text))
)
))
- content.append(AnyComponentWithIdentity(
+ let tableComponent = AnyComponent(AlertTableComponent(items: tableItems))
+ let tableEntry = AnyComponentWithIdentity(
id: "table",
- component: AnyComponent(
- AlertTableComponent(items: tableItems)
- )
- ))
+ component: tableComponent
+ )
+ content.append(tableEntry)
if let valueAmount = gift.valueUsdAmount {
let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 })
diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift
index f625cc31..511e008a 100644
--- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift
+++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift
@@ -118,7 +118,7 @@ public func giftTransferAlertController(
HStack(items, spacing: 4.0)
)
- tableItems.append(.init(
+ tableItems.append(TableComponent.Item(
id: id,
title: title,
hasBackground: false,
@@ -165,12 +165,12 @@ public func giftTransferAlertController(
AlertTextComponent(content: .plain(text))
)
))
- content.append(AnyComponentWithIdentity(
+ let tableComponent = AnyComponent(AlertTableComponent(items: tableItems))
+ let tableEntry = AnyComponentWithIdentity(
id: "table",
- component: AnyComponent(
- AlertTableComponent(items: tableItems)
- )
- ))
+ component: tableComponent
+ )
+ content.append(tableEntry)
let alertController = ChatMessagePaymentAlertController(
context: context,
diff --git a/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift b/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift
index 941ae252..31a4ee6b 100644
--- a/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift
+++ b/submodules/TelegramUI/Components/LegacyChatHeaderPanelComponent/Sources/LegacyChatHeaderPanelComponent.swift
@@ -8,7 +8,7 @@ import ChatPresentationInterfaceState
import AsyncDisplayKit
import AccountContext
-open class ChatTitleAccessoryPanelNode: ASDisplayNode {
+open class LegacyChatTitleAccessoryPanelNode: ASDisplayNode {
public typealias LayoutResult = ChatControllerCustomNavigationPanelNodeLayoutResult
open var interfaceInteraction: ChatPanelInterfaceInteraction?
@@ -19,11 +19,11 @@ open class ChatTitleAccessoryPanelNode: ASDisplayNode {
}
public final class LegacyChatHeaderPanelComponent: Component {
- public let panelNode: ChatTitleAccessoryPanelNode
+ public let panelNode: LegacyChatTitleAccessoryPanelNode
public let interfaceState: ChatPresentationInterfaceState
public init(
- panelNode: ChatTitleAccessoryPanelNode,
+ panelNode: LegacyChatTitleAccessoryPanelNode,
interfaceState: ChatPresentationInterfaceState
) {
self.panelNode = panelNode
diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift
index 73d83595..fa88325a 100644
--- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift
+++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/AffiliateProgramSetupScreen.swift
@@ -232,20 +232,34 @@ final class AffiliateProgramSetupScreenComponent: Component {
)
))
- let tableItems: [TableComponent.Item] = [
- TableComponent.Item(id: 0, title: environment.strings.AffiliateSetup_AlertApply_SectionCommission, component: AnyComponent(MultilineTextComponent(
- text: .plain(NSAttributedString(string: commissionTitle, font: Font.regular(15.0), textColor: environment.theme.actionSheet.primaryTextColor))
- ))),
- TableComponent.Item(id: 1, title: environment.strings.AffiliateSetup_AlertApply_SectionDuration, component: AnyComponent(MultilineTextComponent(
- text: .plain(NSAttributedString(string: durationTitle, font: Font.regular(15.0), textColor: environment.theme.actionSheet.primaryTextColor))
- )))
- ]
- content.append(AnyComponentWithIdentity(
+ let textColor = environment.theme.actionSheet.primaryTextColor
+ let commissionItemText = NSAttributedString(
+ string: commissionTitle,
+ font: Font.regular(15.0),
+ textColor: textColor
+ )
+ let durationItemText = NSAttributedString(
+ string: durationTitle,
+ font: Font.regular(15.0),
+ textColor: textColor
+ )
+ let commissionItem = TableComponent.Item(
+ id: 0,
+ title: environment.strings.AffiliateSetup_AlertApply_SectionCommission,
+ component: AnyComponent(MultilineTextComponent(text: .plain(commissionItemText)))
+ )
+ let durationItem = TableComponent.Item(
+ id: 1,
+ title: environment.strings.AffiliateSetup_AlertApply_SectionDuration,
+ component: AnyComponent(MultilineTextComponent(text: .plain(durationItemText)))
+ )
+ let tableItems: [TableComponent.Item] = [commissionItem, durationItem]
+ let tableComponent = AnyComponent(AlertTableComponent(items: tableItems))
+ let tableEntry = AnyComponentWithIdentity(
id: "table",
- component: AnyComponent(
- AlertTableComponent(items: tableItems)
- )
- ))
+ component: tableComponent
+ )
+ content.append(tableEntry)
let alertController = AlertScreen(
context: component.context,
diff --git a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/TableComponent.swift b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/TableComponent.swift
index 762cf5c9..162ab621 100644
--- a/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/TableComponent.swift
+++ b/submodules/TelegramUI/Components/PeerInfo/AffiliateProgramSetupScreen/Sources/TableComponent.swift
@@ -6,7 +6,7 @@ import TelegramPresentationData
import MultilineTextComponent
import AlertComponent
-final class TableComponent: CombinedComponent {
+final class AffiliateTableComponent: CombinedComponent {
class Item: Equatable {
public let id: AnyHashable
public let title: String
@@ -45,7 +45,7 @@ final class TableComponent: CombinedComponent {
self.items = items
}
- public static func ==(lhs: TableComponent, rhs: TableComponent) -> Bool {
+ public static func ==(lhs: AffiliateTableComponent, rhs: AffiliateTableComponent) -> Bool {
if lhs.theme !== rhs.theme {
return false
}
@@ -228,9 +228,9 @@ private final class TableAlertContentComponent: CombinedComponent {
let theme: PresentationTheme
let title: String
let text: String
- let table: TableComponent
+ let table: AffiliateTableComponent
- init(theme: PresentationTheme, title: String, text: String, table: TableComponent) {
+ init(theme: PresentationTheme, title: String, text: String, table: AffiliateTableComponent) {
self.theme = theme
self.title = title
self.text = text
@@ -256,7 +256,7 @@ private final class TableAlertContentComponent: CombinedComponent {
public static var body: Body {
let title = Child(MultilineTextComponent.self)
let text = Child(MultilineTextComponent.self)
- let table = Child(TableComponent.self)
+ let table = Child(AffiliateTableComponent.self)
return { context in
let title = title.update(
@@ -318,17 +318,3 @@ private final class TableAlertContentComponent: CombinedComponent {
}
}
}
-
-func tableAlert(theme: PresentationTheme, title: String, text: String, table: TableComponent, actions: [ComponentAlertAction]) -> ViewController {
- return componentAlertController(
- theme: AlertControllerTheme(presentationTheme: theme, fontSize: .regular),
- content: AnyComponent(TableAlertContentComponent(
- theme: theme,
- title: title,
- text: text,
- table: table
- )),
- actions: actions,
- actionLayout: .horizontal
- )
-}
diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift
index d5361bc5..689ea8ca 100644
--- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift
+++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionController.swift
@@ -8,7 +8,8 @@ import TelegramPresentationData
import ProgressNavigationButtonNode
import AccountContext
import SearchUI
-import ChatListUI
+import func ChatListUI.chatListFilterItems
+import enum ChatListUI.ChatListContainerNodeFilter
import CounterControllerTitleView
import ChatListFilterTabContainerNode
diff --git a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift
index b6d17cf8..fe7c818a 100644
--- a/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift
+++ b/submodules/TelegramUI/Components/Settings/BirthdayPickerScreen/Sources/BirthdayPickerComponent.swift
@@ -69,7 +69,7 @@ public final class BirthdayPickerComponent: Component {
private let calendar = Calendar(identifier: .gregorian)
private var value = TelegramBirthday(day: 1, month: 1, year: nil)
- private var minYear: Int32 = 1900
+ private var minYear: Int32 = 1
private let maxYear: Int32
override init(frame: CGRect) {
diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/backspace_24.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/backspace_24.svg
deleted file mode 100644
index 3907ddf6..00000000
--- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputClearIcon.imageset/backspace_24.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_24.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_24.svg
deleted file mode 100644
index 6134e3e7..00000000
--- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputGlobeIcon.imageset/keyboard_24.svg
+++ /dev/null
@@ -1,8 +0,0 @@
-
diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/keyboard_2444.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/keyboard_2444.svg
deleted file mode 100644
index 74f7d2fe..00000000
--- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/EntityInputSettingsIcon.imageset/keyboard_2444.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Group 1.svg b/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Group 1.svg
deleted file mode 100644
index d93a88b5..00000000
--- a/submodules/TelegramUI/Images.xcassets/Chat/Input/Media/PanelFeaturedIcon.imageset/Group 1.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf
deleted file mode 100644
index e77136af..00000000
Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Back.imageset/ic_left.pdf and /dev/null differ
diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf
deleted file mode 100644
index d413418c..00000000
Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Bookmark.imageset/Bookmark.pdf and /dev/null differ
diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf
deleted file mode 100644
index 81f7ac31..00000000
Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Browser.imageset/Browser.pdf and /dev/null differ
diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf
deleted file mode 100644
index c0246a08..00000000
Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/Forward.imageset/ic_right.pdf and /dev/null differ
diff --git a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf b/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf
deleted file mode 100644
index 18bc4e97..00000000
Binary files a/submodules/TelegramUI/Images.xcassets/Instant View/OpenDocument.imageset/docviewer_24.pdf and /dev/null differ
diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift
index 99bdd97f..042a3c4f 100644
--- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift
+++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift
@@ -692,6 +692,44 @@ extension ChatControllerImpl {
self.displayNode = ChatControllerNode(context: self.context, chatLocation: self.chatLocation, chatLocationContextHolder: self.chatLocationContextHolder, subject: self.subject, controllerInteraction: self.controllerInteraction!, chatPresentationInterfaceState: self.presentationInterfaceState, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings, navigationBar: self.navigationBar, statusBar: self.statusBar, backgroundNode: self.chatBackgroundNode, controller: self)
+ // Seed the title pill synchronously from whatever data is available at this point,
+ // so the header shows the peer name immediately instead of staying blank until
+ // the first async contentDataUpdated() fires.
+ let initialTitleContent: ChatTitleContent? = self.contentData?.state.chatTitleContent
+ ?? self.presentationInterfaceState.renderedPeer.flatMap { renderedPeer -> ChatTitleContent? in
+ guard let peer = renderedPeer.peer else { return nil }
+ let peerData = ChatTitleContent.PeerData(
+ peerId: renderedPeer.peerId,
+ peer: peer,
+ isContact: false,
+ isSavedMessages: peer.id == self.context.account.peerId,
+ notificationSettings: nil,
+ peerPresences: [:],
+ cachedData: nil
+ )
+ return .peer(
+ peerView: peerData,
+ customTitle: nil,
+ customSubtitle: nil,
+ onlineMemberCount: (nil, nil),
+ isScheduledMessages: false,
+ isMuted: nil,
+ customMessageCount: nil,
+ isEnabled: true
+ )
+ }
+ if let initialTitleContent {
+ self.chatTitleView?.update(
+ context: self.context,
+ theme: self.presentationData.theme,
+ strings: self.presentationData.strings,
+ dateTimeFormat: self.presentationData.dateTimeFormat,
+ nameDisplayOrder: self.presentationData.nameDisplayOrder,
+ content: initialTitleContent,
+ transition: .immediate
+ )
+ }
+
if let currentItem = self.globalControlPanelsContext?.tempVoicePlaylistCurrentItem {
self.chatDisplayNode.historyNode.voicePlaylistItemChanged(nil, currentItem)
}
diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift
index 6ba6d498..02d4268a 100644
--- a/submodules/TelegramUI/Sources/ChatController.swift
+++ b/submodules/TelegramUI/Sources/ChatController.swift
@@ -146,7 +146,6 @@ import ChatTextInputPanelNode
import ChatInputAccessoryPanel
import GlobalControlPanelsContext
import ChatSearchNavigationContentNode
-import ChatAgeRestrictionAlertController
public final class ChatControllerOverlayPresentationData {
public let expandData: (ASDisplayNode?, () -> Void)
@@ -830,7 +829,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return true
}
- let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message, params in
+ let openMessage: (Message, OpenMessageParams) -> Bool = { [weak self] message, params in
guard let self, self.isNodeLoaded, let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) else {
return false
}
@@ -1566,7 +1565,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.controllerInteraction?.isOpeningMediaSignal = openChatMessageParams.blockInteraction.get()
return context.sharedContext.openChatMessage(openChatMessageParams)
- }, openPeer: { [weak self] peer, navigation, fromMessage, source in
+ }
+
+ let openPeer: (EnginePeer, ChatControllerInteractionNavigateToPeer, MessageReference?, ChatControllerInteraction.OpenPeerSource) -> Void = { [weak self] peer, navigation, fromMessage, source in
var expandAvatar = false
if case let .groupParticipant(storyStats, avatarHeaderNode) = source {
if let storyStats, storyStats.totalCount != 0, let avatarHeaderNode = avatarHeaderNode as? ChatMessageAvatarHeaderNodeImpl {
@@ -1581,20 +1582,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
fromReactionMessageId = fromMessage?.id
}
self?.openPeer(peer: peer, navigation: navigation, fromMessage: fromMessage, fromReactionMessageId: fromReactionMessageId, expandAvatar: expandAvatar)
- }, openPeerMention: { [weak self] name, progress in
+ }
+
+ let openPeerMention: (String, Promise?) -> Void = { [weak self] name, progress in
self?.openPeerMention(name, progress: progress)
- }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, anyRecognizer, location in
+ }
+
+ let openMessageContextMenu: (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?, CGPoint?) -> Void = { [weak self] message, selectAll, node, frame, anyRecognizer, location in
guard let self, self.isNodeLoaded else {
return
}
self.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame, anyRecognizer: anyRecognizer, location: location)
- }, openMessageReactionContextMenu: { [weak self] message, sourceView, gesture, value in
+ }
+
+ let openMessageReactionContextMenu: (Message, ContextExtractedContentContainingView, ContextGesture?, MessageReaction.Reaction) -> Void = { [weak self] message, sourceView, gesture, value in
guard let self else {
return
}
self.openMessageReactionContextMenu(message: message, sourceView: sourceView, gesture: gesture, value: value)
- }, updateMessageReaction: { [weak self] initialMessage, reaction, force, sourceView in
+ }
+
+ let updateMessageReaction: (Message, ChatControllerInteractionReaction, Bool, ContextExtractedContentContainingView?) -> Void = { [weak self] initialMessage, reaction, force, sourceView in
guard let strongSelf = self else {
return
}
@@ -2062,7 +2071,16 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
})
- }, activateMessagePinch: { [weak self] sourceNode in
+ }
+
+ let controllerInteraction = ChatControllerInteraction(
+ openMessage: openMessage,
+ openPeer: openPeer,
+ openPeerMention: openPeerMention,
+ openMessageContextMenu: openMessageContextMenu,
+ openMessageReactionContextMenu: openMessageReactionContextMenu,
+ updateMessageReaction: updateMessageReaction,
+ activateMessagePinch: { [weak self] sourceNode in
guard let strongSelf = self else {
return
}
diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift
index 3caadb68..9d815c5b 100644
--- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift
+++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift
@@ -893,7 +893,8 @@ extension ChatControllerImpl {
peerVerification = cachedChannelData.verification
}
}
- copyProtectionEnabled = peer.isCopyProtectionEnabled
+ // GHOSTGRAM: Bypass copy protection if enabled in Misc settings
+ copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
@@ -1371,7 +1372,8 @@ extension ChatControllerImpl {
var alwaysShowGiftButton = false
var disallowedGifts: TelegramDisallowedGifts?
if let peer = peerView.peers[peerView.peerId] {
- copyProtectionEnabled = peer.isCopyProtectionEnabled
+ // GHOSTGRAM: Bypass copy protection if enabled in Misc settings
+ copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
if case let .known(value) = cachedData.businessIntro {
diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift
index a1e76201..b50efb0b 100644
--- a/submodules/TelegramUI/Sources/ChatControllerNode.swift
+++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift
@@ -1129,7 +1129,9 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
- let isSecret = self.chatPresentationInterfaceState.copyProtectionEnabled || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
+ // GHOSTGRAM: Bypass screenshot protection if enabled in Misc settings
+ let effectiveCopyProtection = MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : self.chatPresentationInterfaceState.copyProtectionEnabled
+ let isSecret = effectiveCopyProtection || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
if self.historyNodeContainer.isSecret != isSecret {
self.historyNodeContainer.isSecret = isSecret
setLayerDisableScreenshots(self.titleAccessoryPanelContainer.layer, isSecret)
@@ -4600,12 +4602,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
- self.setupSendActionOnViewUpdate({ [weak self] in
- guard let self, let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
- return
- }
+ // GHOSTGRAM: When send delay is active, scheduled messages
+ // don't trigger history view update, so clear input immediately.
+ if SendDelayManager.shared.isEnabled {
self.collapseInput()
-
self.ignoreUpdateHeight = true
textInputPanelNode.text = ""
self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
@@ -4624,7 +4624,33 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
return state
})
self.ignoreUpdateHeight = false
- }, usedCorrelationId)
+ } else {
+ self.setupSendActionOnViewUpdate({ [weak self] in
+ guard let self, let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode else {
+ return
+ }
+ self.collapseInput()
+
+ self.ignoreUpdateHeight = true
+ textInputPanelNode.text = ""
+ self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
+ var state = state
+ state = state.withUpdatedReplyMessageSubject(nil)
+ state = state.withUpdatedSendMessageEffect(nil)
+
+ if state.postSuggestionState != nil {
+ state = state.withUpdatedPostSuggestionState(nil)
+ state = state.withUpdatedEditMessage(nil)
+ }
+
+ state = state.withUpdatedForwardMessageIds(nil)
+ state = state.withUpdatedForwardOptionsState(nil)
+ state = state.withUpdatedComposeDisableUrlPreviews([])
+ return state
+ })
+ self.ignoreUpdateHeight = false
+ }, usedCorrelationId)
+ }
completion()
self.sendMessages(messages, silentPosting, scheduleTime, repeatPeriod, messages.count > 1, postpone)
diff --git a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift
index eec79b5e..95b575d2 100644
--- a/submodules/TelegramUI/Sources/ChatHistoryListNode.swift
+++ b/submodules/TelegramUI/Sources/ChatHistoryListNode.swift
@@ -2056,6 +2056,10 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
}
+ // GHOSTGRAM: Bypass copy protection if enabled in Misc settings
+ if MiscSettingsManager.shared.shouldBypassCopyProtection {
+ isCopyProtectionEnabled = false
+ }
let alwaysDisplayTranscribeButton = ChatMessageItemAssociatedData.DisplayTranscribeButton(
canBeDisplayed: suggestAudioTranscription.0 < 2,
displayForNotConsumed: suggestAudioTranscription.1,
@@ -2102,7 +2106,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
selectedMessages: selectedMessages,
presentationData: chatPresentationData,
historyAppearsCleared: historyAppearsCleared,
- skipViewOnceMedia: mode != .bubbles,
+ // GHOSTGRAM: Keep view-once media visible if bypass is enabled
+ skipViewOnceMedia: MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete ? false : (mode != .bubbles),
pendingUnpinnedAllMessages: pendingUnpinnedAllMessages,
pendingRemovedMessages: pendingRemovedMessages,
associatedData: associatedData,
@@ -2110,8 +2115,9 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
customChannelDiscussionReadState: customChannelDiscussionReadState,
customThreadOutgoingReadState: customThreadOutgoingReadState,
cachedData: data.cachedData,
- adMessage: allAdMessages.fixed,
- dynamicAdMessages: allAdMessages.opportunistic
+ // GHOSTGRAM: Block ads if enabled in Misc settings
+ adMessage: MiscSettingsManager.shared.shouldBlockAds ? nil : allAdMessages.fixed,
+ dynamicAdMessages: MiscSettingsManager.shared.shouldBlockAds ? [] : allAdMessages.opportunistic
)
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2, ignoreMessagesInTimestampRange: update.3, ignoreMessageIds: update.4)
diff --git a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift
index 73423965..632c0072 100644
--- a/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift
+++ b/submodules/TelegramUI/Sources/ChatSearchNavigationContentNode.swift
@@ -31,7 +31,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
self.chatLocation = chatLocation
self.interaction = interaction
- self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasBackground: false, hasSeparator: false), strings: strings, fieldStyle: .modern)
+ self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: theme, hasBackground: false, hasSeparator: false), presentationTheme: theme, strings: strings, fieldStyle: .modern)
let placeholderText: String
switch chatLocation {
case .peer, .replyThread, .customChatContents:
@@ -90,10 +90,11 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
return 54.0
}
- override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
+ override func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGSize {
let searchBarFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - self.nominalHeight), size: CGSize(width: size.width, height: 54.0))
self.searchBar.frame = searchBarFrame
self.searchBar.updateLayout(boundingSize: searchBarFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition)
+ return size
}
func activate() {
@@ -106,7 +107,7 @@ final class ChatSearchNavigationContentNode: NavigationBarContentNode {
func update(presentationInterfaceState: ChatPresentationInterfaceState) {
if let search = presentationInterfaceState.search {
- self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), strings: presentationInterfaceState.strings)
+ self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: presentationInterfaceState.theme, hasBackground: false, hasSeparator: false), presentationTheme: presentationInterfaceState.theme, strings: presentationInterfaceState.strings)
switch search.domain {
case .everything, .tag:
diff --git a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift
index 8fd917a6..95c36d69 100644
--- a/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift
+++ b/submodules/TelegramUI/Sources/ChatTitleAccessoryPanelNode.swift
@@ -1,16 +1,4 @@
-import Foundation
-import UIKit
-import Display
-import AsyncDisplayKit
-import ChatPresentationInterfaceState
-import AccountContext
+import class LegacyChatHeaderPanelComponent.LegacyChatTitleAccessoryPanelNode
-class ChatTitleAccessoryPanelNode: ASDisplayNode {
- typealias LayoutResult = ChatControllerCustomNavigationPanelNode.LayoutResult
-
- var interfaceInteraction: ChatPanelInterfaceInteraction?
-
- func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
- preconditionFailure()
- }
+class ChatTitleAccessoryPanelNode: LegacyChatTitleAccessoryPanelNode {
}
diff --git a/third-party/dav1d/BUILD b/third-party/dav1d/BUILD
index ba022d04..05947ebf 100644
--- a/third-party/dav1d/BUILD
+++ b/third-party/dav1d/BUILD
@@ -49,6 +49,8 @@ genrule(
BUILD_ARCH="arm64"
elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then
BUILD_ARCH="sim_arm64"
+ elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then
+ BUILD_ARCH="sim_x86_64"
else
echo "Unsupported architecture $(TARGET_CPU)"
fi
@@ -119,4 +121,3 @@ objc_library(
"//visibility:public",
],
)
-
diff --git a/third-party/dav1d/build-dav1d-bazel.sh b/third-party/dav1d/build-dav1d-bazel.sh
index 6df4bd50..b54a38ea 100644
--- a/third-party/dav1d/build-dav1d-bazel.sh
+++ b/third-party/dav1d/build-dav1d-bazel.sh
@@ -18,6 +18,13 @@ elif [ "$ARCH" = "sim_arm64" ]; then
custom_xcode_path="$(xcode-select -p)/"
sed -i '' "s|/Applications/Xcode.app/Contents/Developer/|$custom_xcode_path|g" "$TARGET_CROSSFILE"
CROSSFILE="../package/crossfiles/arm64-iPhoneSimulator-custom.meson"
+elif [ "$ARCH" = "sim_x86_64" ]; then
+ TARGET_CROSSFILE="$BUILD_DIR/dav1d/package/crossfiles/x86_64-iPhoneSimulator-custom.meson"
+ cp "$BUILD_DIR/dav1d/package/crossfiles/x86_64-iPhoneSimulator.meson" "$TARGET_CROSSFILE"
+ custom_xcode_path="$(xcode-select -p)/"
+ sed -i '' "s|/Applications/Xcode.app/Contents/Developer/|$custom_xcode_path|g" "$TARGET_CROSSFILE"
+ CROSSFILE="../package/crossfiles/x86_64-iPhoneSimulator-custom.meson"
+ MESON_OPTIONS="$MESON_OPTIONS -Denable_asm=false"
else
echo "Unsupported architecture $ARCH"
exit 1
@@ -33,4 +40,3 @@ ninja
popd
popd
-
diff --git a/third-party/mozjpeg/build-mozjpeg-bazel.sh b/third-party/mozjpeg/build-mozjpeg-bazel.sh
index c115e26c..b4e5148c 100755
--- a/third-party/mozjpeg/build-mozjpeg-bazel.sh
+++ b/third-party/mozjpeg/build-mozjpeg-bazel.sh
@@ -37,6 +37,22 @@ elif [ "$ARCH" = "sim_arm64" ]; then
echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake
echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
+ cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} -DPNG_SUPPORTED=FALSE -DENABLE_SHARED=FALSE -DWITH_JPEG8=1 -DBUILD=10000 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../mozjpeg
+ make
+elif [ "$ARCH" = "x86_64" ]; then
+ IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
+ IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
+ export CFLAGS="-Wall -arch x86_64 --target=x86_64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0 -funwind-tables"
+
+ cd "$BUILD_DIR"
+ mkdir build
+ cd build
+
+ touch toolchain.cmake
+ echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake
+ echo "set(CMAKE_SYSTEM_PROCESSOR x86_64)" >> toolchain.cmake
+ echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
+
cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} -DPNG_SUPPORTED=FALSE -DENABLE_SHARED=FALSE -DWITH_JPEG8=1 -DBUILD=10000 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../mozjpeg
make
else
diff --git a/third-party/td/BUILD b/third-party/td/BUILD
index 365f56ee..82204ac8 100644
--- a/third-party/td/BUILD
+++ b/third-party/td/BUILD
@@ -38,8 +38,15 @@ genrule(
BUILD_ARCH="arm64"
elif [ "$(TARGET_CPU)" == "ios_sim_arm64" ]; then
BUILD_ARCH="sim_arm64"
+ elif [ "$(TARGET_CPU)" == "ios_x86_64" ]; then
+ BUILD_ARCH="sim_x86_64"
+ elif [ "$(TARGET_CPU)" == "darwin_arm64" ]; then
+ BUILD_ARCH="sim_arm64"
+ elif [ "$(TARGET_CPU)" == "darwin_x86_64" ]; then
+ BUILD_ARCH="sim_x86_64"
else
- echo "Unsupported architecture $(TARGET_CPU)"
+ echo "Unsupported architecture $(TARGET_CPU)" >&2
+ exit 1
fi
BUILD_DIR="$(RULEDIR)/build_$${BUILD_ARCH}"
diff --git a/third-party/td/build-td-bazel.sh b/third-party/td/build-td-bazel.sh
index a977bbb1..9d910cb4 100755
--- a/third-party/td/build-td-bazel.sh
+++ b/third-party/td/build-td-bazel.sh
@@ -17,36 +17,91 @@ options="$options -DOPENSSL_INCLUDE_DIR=${OPENSSL_DIR}/src/include"
options="$options -DCMAKE_BUILD_TYPE=Release"
options="$options -DIOS_DEPLOYMENT_TARGET=13.0"
+# Bazel genrule runs with PATH=/bin:/usr/bin, so resolve CPU count without
+# relying on /usr/sbin being in PATH.
+if [ -n "${TD_BUILD_JOBS:-}" ]; then
+ BUILD_JOBS="$TD_BUILD_JOBS"
+elif [ -x /usr/sbin/sysctl ]; then
+ BUILD_JOBS="$(/usr/sbin/sysctl -n hw.ncpu)"
+elif command -v getconf >/dev/null 2>&1; then
+ BUILD_JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || true)"
+fi
+case "$BUILD_JOBS" in
+ ''|*[!0-9]*)
+ BUILD_JOBS=8
+ ;;
+esac
+if [ "$BUILD_JOBS" -lt 1 ]; then
+ BUILD_JOBS=1
+fi
+
+MAX_BUILD_JOBS="${TD_MAX_BUILD_JOBS:-8}"
+case "$MAX_BUILD_JOBS" in
+ ''|*[!0-9]*)
+ MAX_BUILD_JOBS=8
+ ;;
+esac
+if [ "$MAX_BUILD_JOBS" -lt 1 ]; then
+ MAX_BUILD_JOBS=8
+fi
+if [ "$BUILD_JOBS" -gt "$MAX_BUILD_JOBS" ]; then
+ BUILD_JOBS="$MAX_BUILD_JOBS"
+fi
+if [ -z "$BUILD_JOBS" ]; then
+ BUILD_JOBS=8
+fi
+
cd "$BUILD_DIR"
# Generate source files
mkdir native-build
cd native-build
cmake -DTD_GENERATE_SOURCE_FILES=ON ../td
-cmake --build . -- -j$(sysctl -n hw.ncpu)
+cmake --build . -- -j"$BUILD_JOBS"
cd ..
if [ "$ARCH" = "arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneOS.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneOS*.sdk)
- export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0 -miphoneos-version-min=13.0"
+ cmake_arch="arm64"
+ clang_target="arm64-apple-ios13.0"
+ minimum_target_flag="-miphoneos-version-min=13.0"
+ cmake_processor="aarch64"
elif [ "$ARCH" = "sim_arm64" ]; then
IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
- export CFLAGS="-arch arm64 --target=arm64-apple-ios13.0-simulator -miphonesimulator-version-min=13.0"
+ cmake_arch="arm64"
+ clang_target="arm64-apple-ios13.0-simulator"
+ minimum_target_flag="-miphonesimulator-version-min=13.0"
+ cmake_processor="aarch64"
+elif [ "$ARCH" = "sim_x86_64" ]; then
+ IOS_PLATFORMDIR="$(xcode-select -p)/Platforms/iPhoneSimulator.platform"
+ IOS_SYSROOT=($IOS_PLATFORMDIR/Developer/SDKs/iPhoneSimulator*.sdk)
+ cmake_arch="x86_64"
+ clang_target="x86_64-apple-ios13.0-simulator"
+ minimum_target_flag="-miphonesimulator-version-min=13.0"
+ cmake_processor="x86_64"
else
echo "Unsupported architecture $ARCH"
exit 1
fi
+export CFLAGS="-arch ${cmake_arch} --target=${clang_target} ${minimum_target_flag}"
+export CXXFLAGS="$CFLAGS"
+export LDFLAGS="$CFLAGS"
+
# Common build steps
mkdir build
cd build
touch toolchain.cmake
echo "set(CMAKE_SYSTEM_NAME Darwin)" >> toolchain.cmake
-echo "set(CMAKE_SYSTEM_PROCESSOR aarch64)" >> toolchain.cmake
+echo "set(CMAKE_SYSTEM_PROCESSOR ${cmake_processor})" >> toolchain.cmake
+echo "set(CMAKE_OSX_ARCHITECTURES ${cmake_arch})" >> toolchain.cmake
echo "set(CMAKE_C_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang)" >> toolchain.cmake
+echo "set(CMAKE_CXX_COMPILER $(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++)" >> toolchain.cmake
+echo "set(CMAKE_C_COMPILER_TARGET ${clang_target})" >> toolchain.cmake
+echo "set(CMAKE_CXX_COMPILER_TARGET ${clang_target})" >> toolchain.cmake
cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake -DCMAKE_OSX_SYSROOT=${IOS_SYSROOT[0]} ../td $options
-make tde2e -j$(sysctl -n hw.ncpu)
+make tde2e -j"$BUILD_JOBS"