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"