diff --git a/README.md b/README.md
index eceb040fe9..2728bbd0fd 100644
--- a/README.md
+++ b/README.md
@@ -11,86 +11,85 @@ There are several things we require from **all developers** for the moment.
3. Please study our [**security guidelines**](https://core.telegram.org/mtproto/security_guidelines) and take good care of your users' data and privacy.
4. Please remember to publish **your** code too in order to comply with the licences.
-# Compilation Guide
+# Quick Compilation Guide
-1. Install Xcode (directly from https://developer.apple.com/download/more or using the App Store).
-2. Clone the project from GitHub:
+## Get the Code
```
git clone --recursive -j8 https://github.com/TelegramMessenger/Telegram-iOS.git
```
-3. Adjust configuration parameters
+## Setup Xcode
+
+Install Xcode (directly from https://developer.apple.com/download/applications or using the App Store).
+
+## Adjust Configuration
+
+1. Generate a random identifier:
+```
+openssl rand -hex 8
+```
+2. Create a new Xcode project. Use `Telegram` as the Product Name. Use `org.{identifier from step 1}` as the Organization Identifier.
+3. Open `Keychain Access` and navigate to `Certificates`. Locate `Apple Development: your@email.address (XXXXXXXXXX)` and double tap the certificate. Under `Details`, locate `Organizational Unit`. This is the Team ID.
+4. Edit `build-system/template_minimal_development_configuration.json`. Use data from the previous steps.
+
+## Generate an Xcode project
```
-mkdir -p $HOME/telegram-configuration
-mkdir -p $HOME/telegram-provisioning
-cp build-system/appstore-configuration.json $HOME/telegram-configuration/configuration.json
-cp -R build-system/fake-codesigning $HOME/telegram-provisioning/
+python3 build-system/Make/Make.py \
+ --cacheDir="$HOME/telegram-bazel-cache" \
+ generateProject \
+ --configurationPath=build-system/template_minimal_development_configuration.json \
+ --xcodeManagedCodesigning
```
-- Modify the values in `configuration.json`
-- Replace the provisioning profiles in `profiles` with valid files
+# Advanced Compilation Guide
-4. (Optional) Create a build cache directory to speed up rebuilds
+## Xcode
+1. Copy and edit `build-system/appstore-configuration.json`.
+2. Copy `build-system/fake-codesigning`. Create and download provisioning profiles, using the `profiles` folder as a reference for the entitlements.
+3. Generate an Xcode project:
```
-mkdir -p "$HOME/telegram-bazel-cache"
+python3 build-system/Make/Make.py \
+ --cacheDir="$HOME/telegram-bazel-cache" \
+ generateProject \
+ --configurationPath=configuration_from_step_1.json \
+ --codesigningInformationPath=directory_from_step_2
```
-5. Build the app
+## IPA
+1. Repeat the steps from the previous section. Use distribution provisioning profiles.
+2. Run:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
build \
- --configurationPath=path-to-configuration.json \
- --codesigningInformationPath=path-to-provisioning-data \
+ --configurationPath=...see previous section... \
+ --codesigningInformationPath=...see previous section... \
--buildNumber=100001 \
- --configuration=release_universal
+ --configuration=release_arm64
```
-6. (Optional) Generate an Xcode project
+## Tips
+## Codesigning is not required for simulator-only builds
+
+Add `--disableProvisioningProfiles`:
```
python3 build-system/Make/Make.py \
--cacheDir="$HOME/telegram-bazel-cache" \
generateProject \
--configurationPath=path-to-configuration.json \
--codesigningInformationPath=path-to-provisioning-data \
- --disableExtensions
-```
-
-It is possible to generate a project that does not require any codesigning certificates to be installed: add `--disableProvisioningProfiles` flag:
-```
-python3 build-system/Make/Make.py \
- --cacheDir="$HOME/telegram-bazel-cache" \
- generateProject \
- --configurationPath=path-to-configuration.json \
- --codesigningInformationPath=path-to-provisioning-data \
- --disableExtensions \
--disableProvisioningProfiles
```
+## Versions
-Tip: use `--disableExtensions` when developing to speed up development by not building application extensions and the WatchOS app.
-
-
-# Tips
-
-Bazel is used to build the app. To simplify the development setup a helper script is provided (`build-system/Make/Make.py`). See help:
+Each release is built using a specific Xcode version (see `versions.json`). The helper script checks the versions of the installed software and reports an error if they don't match the ones specified in `versions.json`. It is possible to bypass these checks:
```
-python3 build-system/Make/Make.py --help
-python3 build-system/Make/Make.py build --help
-python3 build-system/Make/Make.py generateProject --help
-```
-
-Bazel is automatically downloaded when running Make.py for the first time. If you wish to use your own build of Bazel, pass `--bazel=path-to-bazel`. If your Bazel version differs from that in `versions.json`, you may use `--overrideBazelVersion` to skip the version check.
-
-Each release is built using specific Xcode and Bazel versions (see `versions.json`). The helper script checks the versions of installed software and reports an error if they don't match the ones specified in `versions.json`. There are flags that allow to bypass these checks:
-
-```
-python3 build-system/Make/Make.py --overrideBazelVersion build ... # Don't check the version of Bazel
python3 build-system/Make/Make.py --overrideXcodeVersion build ... # Don't check the version of Xcode
```
diff --git a/Telegram/BUILD b/Telegram/BUILD
index ac96cc11c6..00a75be7dd 100644
--- a/Telegram/BUILD
+++ b/Telegram/BUILD
@@ -28,9 +28,13 @@ load("@build_bazel_rules_swift//swift:swift.bzl",
load(
"@rules_xcodeproj//xcodeproj:defs.bzl",
"top_level_target",
+ "top_level_targets",
"xcodeproj",
+ "xcode_provisioning_profile",
)
+load("@build_bazel_rules_apple//apple:apple.bzl", "local_provisioning_profile")
+
load("//build-system/bazel-utils:plist_fragment.bzl",
"plist_fragment",
)
@@ -38,6 +42,7 @@ load("//build-system/bazel-utils:plist_fragment.bzl",
load(
"@build_configuration//:variables.bzl",
"telegram_bazel_path",
+ "telegram_use_xcode_managed_codesigning",
"telegram_bundle_id",
"telegram_aps_environment",
"telegram_team_id",
@@ -509,10 +514,11 @@ app_groups_fragment = """
telegram_bundle_id=telegram_bundle_id
)
-communication_notifications_fragment = """
+official_communication_notifications_fragment = """
com.apple.developer.usernotifications.communication
"""
+communication_notifications_fragment = official_communication_notifications_fragment if telegram_bundle_id in official_bundle_ids else ""
store_signin_fragment = """
com.apple.developer.applesignin
@@ -1919,6 +1925,18 @@ plist_fragment(
)
)
+local_provisioning_profile(
+ name = "Telegram_local_profile",
+ profile_name = "iOS Team Provisioning Profile: {}".format(telegram_bundle_id),
+ team_id = telegram_team_id,
+)
+
+xcode_provisioning_profile(
+ name = "Telegram_xcode_profile",
+ managed_by_xcode = True,
+ provisioning_profile = ":Telegram_local_profile",
+)
+
ios_application(
name = "Telegram",
bundle_id = "{telegram_bundle_id}".format(
@@ -1928,7 +1946,7 @@ ios_application(
minimum_os_version = minimum_os_version,
provisioning_profile = select({
":disableProvisioningProfilesSetting": None,
- "//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision",
+ "//conditions:default": ":Telegram_xcode_profile" if telegram_use_xcode_managed_codesigning else "@build_configuration//provisioning:Telegram.mobileprovision",
}),
entitlements = ":TelegramEntitlements.entitlements",
infoplists = [
@@ -1985,9 +2003,22 @@ xcodeproj(
bazel_path = telegram_bazel_path,
project_name = "Telegram",
tags = ["manual"],
- top_level_targets = [
- ":Telegram",
- ],
+ top_level_targets = top_level_targets(
+ labels = [
+ ":Telegram",
+ ],
+ target_environments = ["device", "simulator"],
+ ),
+ xcode_configurations = {
+ "Debug": {
+ "//command_line_option:compilation_mode": "dbg",
+ },
+ "Release": {
+ "//command_line_option:compilation_mode": "opt",
+ },
+ },
+ default_xcode_configuration = "Debug"
+
)
# Temporary targets used to simplify webrtc build tests
diff --git a/Telegram/NotificationService/BUILD b/Telegram/NotificationService/BUILD
index edac695af6..5564908392 100644
--- a/Telegram/NotificationService/BUILD
+++ b/Telegram/NotificationService/BUILD
@@ -21,6 +21,7 @@ swift_library(
"//submodules/rlottie:RLottieBinding",
"//submodules/GZip:GZip",
"//submodules/PersistentStringHash:PersistentStringHash",
+ "//submodules/Utils/RangeSet",
],
visibility = [
diff --git a/Telegram/NotificationService/Sources/NotificationService.swift b/Telegram/NotificationService/Sources/NotificationService.swift
index f3a1f0d7f5..0a067efc1c 100644
--- a/Telegram/NotificationService/Sources/NotificationService.swift
+++ b/Telegram/NotificationService/Sources/NotificationService.swift
@@ -15,6 +15,7 @@ import PersistentStringHash
import CallKit
import AppLockState
import NotificationsPresentationData
+import RangeSet
private let queue = Queue()
@@ -680,7 +681,7 @@ private final class NotificationServiceHandler {
Logger.shared.logToConsole = loggingSettings.logToConsole
Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData
- let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild)
+ let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false)
let isLockedMessage: String?
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
@@ -1187,7 +1188,7 @@ private final class NotificationServiceHandler {
fetchMediaSignal = Signal { subscriber in
final class DataValue {
var data = Data()
- var totalSize: Int64?
+ var missingRanges = RangeSet(0 ..< Int64.max)
}
let collectedData = Atomic(value: DataValue())
@@ -1217,12 +1218,22 @@ private final class NotificationServiceHandler {
useMainConnection: true
).start(next: { result in
switch result {
- case let .dataPart(_, data, _, _):
+ case let .dataPart(offset, data, dataRange, _):
var isCompleted = false
let _ = collectedData.modify { current in
let current = current
- current.data.append(data)
- if let totalSize = current.totalSize, Int64(current.data.count) >= totalSize {
+
+ let fillRange = Int(offset) ..< (Int(offset) + data.count)
+ if current.data.count < fillRange.upperBound {
+ current.data.count = fillRange.upperBound
+ }
+ current.data.withUnsafeMutableBytes { buffer -> Void in
+ let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
+ data.copyBytes(to: bytes.advanced(by: Int(offset)), from: Int(dataRange.lowerBound) ..< Int(dataRange.upperBound))
+ }
+ current.missingRanges.remove(contentsOf: Int64(fillRange.lowerBound) ..< Int64(fillRange.upperBound))
+
+ if current.missingRanges.isEmpty {
isCompleted = true
}
return current
@@ -1235,8 +1246,8 @@ private final class NotificationServiceHandler {
var isCompleted = false
let _ = collectedData.modify { current in
let current = current
- current.totalSize = size
- if Int64(current.data.count) >= size {
+ current.missingRanges.remove(contentsOf: size ..< Int64.max)
+ if current.missingRanges.isEmpty {
isCompleted = true
}
return current
diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift
index 17641dfdfd..09fab9493b 100644
--- a/Telegram/SiriIntents/IntentHandler.swift
+++ b/Telegram/SiriIntents/IntentHandler.swift
@@ -174,7 +174,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
if let accountCache = accountCache {
account = .single(accountCache)
} else {
- account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
+ account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: !buildConfig.isAppStoreBuild, isICloudEnabled: false), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|> mapToSignal { account -> Signal in
if let account = account {
switch account {
diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings
index 4f21a5b6a0..4a7d0d41da 100644
--- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings
+++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings
@@ -255,8 +255,8 @@
"PUSH_MESSAGE_SUGGEST_USERPIC" = "%1$@|suggested you new profile photo";
-"PUSH_MESSAGE_WALLPAPER" = "%1$@ set a new background for the chat with you";
-"PUSH_MESSAGE_SAME_WALLPAPER" = "%1$@ set the same background for the chat with you";
+"PUSH_MESSAGE_WALLPAPER" = "%1$@ set a new wallpaper for the chat with you";
+"PUSH_MESSAGE_SAME_WALLPAPER" = "%1$@ set the same wallpaper for the chat with you";
"PUSH_REMINDER_TITLE" = "🗓 Reminder";
"PUSH_SENDER_YOU" = "📅 You";
@@ -9121,12 +9121,13 @@ Sorry for the inconvenience.";
"Premium.GiftedTitle.Someone" = "Someone";
-"Notification.ChangedWallpaper" = "%1$@ set a new background for this chat";
-"Notification.YouChangedWallpaper" = "You set a new background for this chat";
-"Notification.Wallpaper.View" = "View Background";
+"Notification.ChangedWallpaper" = "%1$@ set a new wallpaper for this chat";
+"Notification.YouChangedWallpaper" = "You set a new wallpaper for this chat";
+"Notification.YouChangingWallpaper" = "Setting new wallpaper";
+"Notification.Wallpaper.View" = "View Wallpaper";
-"Notification.ChangedToSameWallpaper" = "%1$@ set the same background for this chat";
-"Notification.YouChangedToSameWallpaper" = "You set the same background for this chat";
+"Notification.ChangedToSameWallpaper" = "%1$@ set the same wallpaper for this chat";
+"Notification.YouChangedToSameWallpaper" = "You set the same wallpaper for this chat";
"Channel.AdminLog.JoinedViaFolderInviteLink" = "%1$@ joined via invite link %2$@ (community)";
@@ -9144,10 +9145,185 @@ Sorry for the inconvenience.";
"PeerInfo.Bot.ChangeSettings" = "Change Bot Settings";
"PeerInfo.Bot.BotFatherInfo" = "Use [@BotFather]() to manage this bot.";
-"WallpaperPreview.NotAppliedInfo" = "Background will not be applied for **%@**";
-"WallpaperPreview.ChatTopText" = "Apply the background in this chat.";
+"WallpaperPreview.NotAppliedInfo" = "**%@** will be able to apply this wallpaper";
+"WallpaperPreview.ChatTopText" = "Apply the wallpaper in this chat.";
"WallpaperPreview.ChatBottomText" = "Enjoy the view.";
-"Conversation.Theme.SetPhotoWallpaper" = "Choose Background from Photos";
-"Conversation.Theme.SetColorWallpaper" = "Choose Color as a Background";
-"Conversation.Theme.OtherOptions" = "Other Options...";
+"Conversation.Theme.SetPhotoWallpaper" = "Choose Wallpaper from Photos";
+"Conversation.Theme.SetColorWallpaper" = "Set a Color as Wallpaper";
+
+"Conversation.Theme.ChooseWallpaperTitle" = "Choose Wallpaper";
+"Conversation.Theme.ResetWallpaper" = "Remove Wallpaper";
+"Conversation.Theme.ChooseColorTitle" = "Set a Color";
+"Conversation.Theme.SetCustomColor" = "Set Custom";
+
+"Appearance.ShowNextMediaOnTap" = "Show Next Media on Tap";
+"Appearance.ShowNextMediaOnTapInfo" = "Tap near the edge of the screen while viewing media to navigate between photos.";
+
+"WebApp.LaunchMoreInfo" = "More about this bot";
+"WebApp.LaunchConfirmation" = "To launch this web app, you will connect to its website.";
+
+"WallpaperPreview.PreviewInNightMode" = "Preview this wallpaper in night mode.";
+"WallpaperPreview.PreviewInDayMode" = "Preview this wallpaper in day mode.";
+
+"PeerInfo.BotBlockedTitle" = "Bot Blocked";
+"PeerInfo.BotBlockedText" = "This bot will not be able to message you.";
+
+"ChatList.ContextMuteAll" = "Mute All";
+"ChatList.ContextUnmuteAll" = "Unmute All";
+"ChatList.ToastFolderMuted" = "All chats in **%@** are now muted";
+"ChatList.ToastFolderUnmuted" = "All chats in **%@** are now unmuted";
+"ChatList.ContextMenuShare" = "Share";
+"ChatList.ContextMenuBadgeNew" = "NEW";
+"ChatList.AlertDeleteFolderTitle" = "Delete Folder";
+"ChatList.AlertDeleteFolderText" = "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.";
+"ChatList.PanelNewChatsAvailable_1" = "1 New Chat Available";
+"ChatList.PanelNewChatsAvailable_any" = "%d New Chats Available";
+
+"ChatListFilter.SectionShare" = "SHARE FOLDER";
+"ChatListFilter.CreateLink" = "Create a new Link";
+"ChatListFilter.CreateLinkNew" = "Create an Invite Link";
+"ChatListFilter.ExcludeChatsAction" = "Add Chats to Exclude";
+"ChatListFilter.LinkListInfo" = "Create more links to set up different access levels for different people.";
+"ChatListFilter.LinkListInfoNew" = "Share access to some of this folder's groups and channels with others.";
+"ChatListFilter.ToastChatsAddedTitle_1" = "Сhat added to folder";
+"ChatListFilter.ToastChatsAddedTitle_any" = "%d chats added to folder";
+"ChatListFilter.ToastChatsAddedText" = "It will not affect chatlist of the links of this folder";
+"ChatListFilter.ToastChatsRemovedTitle_1" = "Сhat removed from folder";
+"ChatListFilter.ToastChatsRemovedTitle_any" = "%d chats added to folder";
+"ChatListFilter.ToastChatsRemovedText" = "It will not affect chatlist of the links of this folder";
+"ChatListFilter.AlertCreateFolderBeforeSharingText" = "Please finish creating this folder to share it.";
+"ChatListFilter.ErrorShareInvalidFolder" = "You can’t share folders which have chat types or excluded chats.";
+"ChatListFilter.SaveAlertActionSave" = "Save";
+"ChatListFilter.CreateLinkUnknownError" = "Error creating a share link";
+"ChatListFilter.CreateLinkErrorSomeoneHasChannelLimit" = "One of the groups in this folder can’t be added because one of its admins has too many groups and channels.";
+
+"ChatListFilterList.CreateFolder" = "Create a Folder";
+
+"FolderLinkScreen.LabelCanInvite" = "you can invite others here";
+
+"FolderLinkScreen.LabelUnavailableBot" = "you can't share chats with bots";
+"FolderLinkScreen.AlertTextUnavailableBot" = "You can't share chats with bots";
+
+"FolderLinkScreen.LabelUnavailableUser" = "you can't share private chats";
+"FolderLinkScreen.AlertTextUnavailableUser" = "You can't share private chats";
+
+"FolderLinkScreen.LabelUnavailableGeneric" = "you can't invite others here";
+"FolderLinkScreen.AlertTextUnavailablePrivateGroup" = "You don't have the admin rights to share invite links to this private group.";
+"FolderLinkScreen.AlertTextUnavailablePublicGroup" = "You don't have the admin rights to share invite links to this group chat.";
+"FolderLinkScreen.AlertTextUnavailablePrivateChannel" = "You don't have the admin rights to share invite links to this private channel.";
+"FolderLinkScreen.AlertTextUnavailablePublicChannel" = "You don't have the admin rights to share invite links to this channel.";
+
+"FolderLinkScreen.TitleDescriptionUnavailable" = "You can only share groups and channels in which you are allowed to create invite links.";
+"FolderLinkScreen.ChatCountHeaderUnavailable" = "There are no chats in this folder that you can share with others.";
+"FolderLinkScreen.ChatsSectionHeaderUnavailable" = "THESE CHATS CANNOT BE SHARED";
+
+"FolderLinkScreen.TitleDescriptionDeselected" = "Anyone with this link can add **%@** folder and the chats selected below.";
+"FolderLinkScreen.ChatsSectionHeader" = "CHATS";
+"FolderLinkScreen.ChatsSectionHeaderActionSelectAll" = "SELECT ALL";
+"FolderLinkScreen.ChatsSectionHeaderActionDeselectAll" = "DESELECT ALL";
+
+"FolderLinkScreen.TitleDescriptionSelectedCount_1" = "the 1 chat";
+"FolderLinkScreen.TitleDescriptionSelectedCount_any" = "the %d chats";
+"FolderLinkScreen.TitleDescriptionSelected" = "Anyone with this link can add **%1$@** folder and %2$@ selected below.";
+
+"FolderLinkScreen.ChatsSectionHeaderSelected_1" = "1 CHAT SELECTED";
+"FolderLinkScreen.ChatsSectionHeaderSelected_any" = "%d CHATS SELECTED";
+
+"FolderLinkScreen.LinkSectionHeader" = "INVITE LINK";
+
+"FolderLinkScreen.ContextActionNameLink" = "Name Link";
+"FolderLinkScreen.NameLink.Title" = "Name This Link";
+
+"FolderLinkScreen.ToastNewChatAdded" = "People who already used the invite link will be able to join newly added chats.";
+"FolderLinkScreen.SaveUnknownError" = "An error occurred while updating the link";
+"FolderLinkScreen.ToastLinkUpdated" = "Link updated";
+
+"FolderLinkScreen.Title" = "Share Folder";
+
+"FolderLinkScreen.SaveAlertTitle" = "Unsaved Changes";
+"FolderLinkScreen.SaveAlertText" = "You have changed the settings of this folder. Apply changes?";
+"FolderLinkScreen.SaveAlertActionDiscard" = "Discard";
+"FolderLinkScreen.SaveAlertActionApply" = "Apply";
+"FolderLinkScreen.SaveAlertActionContinue" = "Cancel";
+
+"FolderLinkScreen.LinkActionCopy" = "Copy";
+"FolderLinkScreen.LinkActionShare" = "Share";
+
+"InviteLink.LabelJoinedViaFolder" = "joined via a folder invite link";
+
+"ChatListFilter.LinkLabelChatCount_1" = "includes 1 chat";
+"ChatListFilter.LinkLabelChatCount_any" = "includes %d chats";
+"ChatListFilter.LinkActionDelete" = "Delete";
+
+"InviteLink.QRCodeFolder.Title" = "Invite by QR Code";
+"InviteLink.QRCodeFolder.Text" = "Everyone on Telegram can scan this code to add this folder and join the chats included in this invite link.";
+
+"FolderLinkPreview.IconTabLeft" = "All Chats";
+"FolderLinkPreview.IconTabRight" = "Personal";
+"FolderLinkPreview.TitleShare" = "Share Folder";
+"FolderLinkPreview.TitleRemove" = "Remove Folder";
+"FolderLinkPreview.TitleAddFolder" = "Add Folder";
+"FolderLinkPreview.TitleAddChats_1" = "Add %d Chat";
+"FolderLinkPreview.TitleAddChats_any" = "Add %d Chats";
+"FolderLinkPreview.LinkSectionHeader" = "INVITE LINKS";
+"FolderLinkPreview.RemoveSectionSelectedHeader_1" = "%d CHAT TO QUIT";
+"FolderLinkPreview.RemoveSectionSelectedHeader_any" = "%d CHATS TO QUIT";
+"FolderLinkPreview.ChatSectionHeader_1" = "1 CHAT IN THIS FOLDER";
+"FolderLinkPreview.ChatSectionHeader_any" = "%d CHATS IN THIS FOLDER";
+"FolderLinkPreview.ChatSectionJoinHeader_1" = "1 CHAT IN THIS FOLDER TO JOIN";
+"FolderLinkPreview.ChatSectionJoinHeader_any" = "%d CHATS IN THIS FOLDER TO JOIN";
+
+"FolderLinkPreview.ToastChatsAddedTitle" = "Folder %@ Updated";
+"FolderLinkPreview.ToastChatsAddedText_1" = "You have joined %d new chat";
+"FolderLinkPreview.ToastChatsAddedText_any" = "You have joined %d new chats";
+
+"FolderLinkPreview.ToastFolderAddedTitle" = "Folder %@ Added";
+"FolderLinkPreview.ToastFolderAddedText_1" = "You also joined %d chat";
+"FolderLinkPreview.ToastFolderAddedText_any" = "You also joined %d chats";
+
+"FolderLinkPreview.TextLinkList" = "Create more links to set up different access\nlevels for different people.";
+"FolderLinkPreview.TextRemoveFolder" = "Do you also want to quit the chats included in this folder?";
+"FolderLinkPreview.TextAllAdded" = "You have already added this\nfolder and its chats.";
+"FolderLinkPreview.TextAddFolder" = "Do you want to add a new chat folder\nand join its groups and channels?";
+
+"FolderLinkPreview.TextAddChatsCount_1" = "%d chat";
+"FolderLinkPreview.TextAddChatsCount_any" = "%d chats";
+"FolderLinkPreview.TextAddChats" = "Do you want to add **%1$@** to the\nfolder **%2$@**?";
+
+"FolderLinkPreview.LabelPeerSubscriber" = "You are already a subscriber";
+"FolderLinkPreview.LabelPeerSubscribers_1" = "%d subscriber";
+"FolderLinkPreview.LabelPeerSubscribers_any" = "%d subscribers";
+
+"FolderLinkPreview.LabelPeerMember" = "You are already a member";
+"FolderLinkPreview.LabelPeerMembers_1" = "%d member";
+"FolderLinkPreview.LabelPeerMembers_any" = "%d members";
+
+"FolderLinkPreview.ToastAlreadyMemberChannel" = "You are already subscribed to this channel.";
+"FolderLinkPreview.ToastAlreadyMemberGroup" = "You are already a member of this group.";
+
+"FolderLinkPreview.ButtonRemoveFolder" = "Remove Folder";
+"FolderLinkPreview.ButtonRemoveFolderAndChats" = "Remove Folder and Chats";
+
+"FolderLinkPreview.ButtonDoNotJoinChats" = "Do Not Join Any Chats";
+"FolderLinkPreview.ButtonJoinChats" = "Join Chats";
+"FolderLinkPreview.ButtonAddFolder" = "Add Folder";
+
+"FolderLinkPreview.ToastLeftTitle" = "Folder %@ deleted";
+"FolderLinkPreview.ToastLeftChatsText_1" = "You also left **%d** chat";
+"FolderLinkPreview.ToastLeftChatsText_any" = "You also left **%d** chats";
+
+"FolderLinkPreview.ListSelectionSelectAllDynamicPartSelect" = "SELECT";
+"FolderLinkPreview.ListSelectionSelectAllDynamicPartDeselect" = "DESELECT";
+"FolderLinkPreview.ListSelectionSelectAllStaticPartSelect" = "ALL";
+"FolderLinkPreview.ListSelectionSelectAllStaticPartDeselect" = "ALL";
+"FolderLinkPreview.ListSelectionSelectAllFormat" = "{dynamic}{static}";
+
+"UserInfo.ChangeWallpaper" = "Change Wallpaper";
+
+"Conversation.Theme.PreviewDarkShort" = "Tap to view this theme in the night mode.";
+"Conversation.Theme.PreviewLightShort" = "Tap to view this theme in the day mode.";
+
+"ChatList.EmptyListContactsHeader" = "YOUR CONTACTS ON TELEGRAM";
+"ChatList.EmptyListContactsHeaderHide" = "hide";
+"ChatList.EmptyListTooltip" = "Send a message or\nstart a group here.";
diff --git a/WORKSPACE b/WORKSPACE
index fa23cf7b1a..330d06cb04 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -75,3 +75,9 @@ http_archive(
sha256 = "032907801dc7784744a1ca8fd40d3eecc34a2e27a93a4b3993f617cca204a9f3",
build_file = "@//third-party/AppCenter:AppCenter.BUILD",
)
+
+load("@build_bazel_rules_apple//apple:apple.bzl", "provisioning_profile_repository")
+
+provisioning_profile_repository(
+ name = "local_provisioning_profiles",
+)
diff --git a/build-system/Make/BuildConfiguration.py b/build-system/Make/BuildConfiguration.py
index 92f85d8f2b..8407dad9c5 100644
--- a/build-system/Make/BuildConfiguration.py
+++ b/build-system/Make/BuildConfiguration.py
@@ -35,9 +35,10 @@ class BuildConfiguration:
self.enable_siri = enable_siri
self.enable_icloud = enable_icloud
- def write_to_variables_file(self, bazel_path, aps_environment, path):
+ def write_to_variables_file(self, bazel_path, use_xcode_managed_codesigning, aps_environment, path):
string = ''
string += 'telegram_bazel_path = "{}"\n'.format(bazel_path)
+ string += 'telegram_use_xcode_managed_codesigning = {}\n'.format('True' if use_xcode_managed_codesigning else 'False')
string += 'telegram_bundle_id = "{}"\n'.format(self.bundle_id)
string += 'telegram_api_id = "{}"\n'.format(self.api_id)
string += 'telegram_api_hash = "{}"\n'.format(self.api_hash)
@@ -240,6 +241,9 @@ class CodesigningSource:
raise Exception('Not implemented')
def resolve_aps_environment(self):
+ raise Exception('Not implemented')
+
+ def use_xcode_managed_codesigning(self):
raise Exception('Not implemented')
def copy_certificates_to_destination(self, destination_path):
@@ -280,6 +284,9 @@ class GitCodesigningSource(CodesigningSource):
source_path = self.working_dir + '/decrypted/profiles/{}'.format(self.codesigning_type)
return resolve_aps_environment_from_directory(source_path=source_path, team_id=self.team_id, bundle_id=self.bundle_id)
+ def use_xcode_managed_codesigning(self):
+ return False
+
def copy_certificates_to_destination(self, destination_path):
source_path = None
if self.codesigning_type in ['adhoc', 'appstore']:
@@ -308,5 +315,28 @@ class DirectoryCodesigningSource(CodesigningSource):
def resolve_aps_environment(self):
return resolve_aps_environment_from_directory(source_path=self.directory_path + '/profiles', team_id=self.team_id, bundle_id=self.bundle_id)
+ def use_xcode_managed_codesigning(self):
+ return False
+
def copy_certificates_to_destination(self, destination_path):
copy_certificates_from_directory(source_path=self.directory_path + '/certs', destination_path=destination_path)
+
+
+class XcodeManagedCodesigningSource(CodesigningSource):
+ def __init__(self):
+ pass
+
+ def load_data(self, working_dir):
+ pass
+
+ def copy_profiles_to_destination(self, destination_path):
+ pass
+
+ def resolve_aps_environment(self):
+ return ""
+
+ def use_xcode_managed_codesigning(self):
+ return True
+
+ def copy_certificates_to_destination(self, destination_path):
+ pass
diff --git a/build-system/Make/Make.py b/build-system/Make/Make.py
index 6506859acb..708145fd2c 100644
--- a/build-system/Make/Make.py
+++ b/build-system/Make/Make.py
@@ -12,14 +12,15 @@ import glob
from BuildEnvironment import resolve_executable, call_executable, run_executable_with_output, BuildEnvironment
from ProjectGeneration import generate
from BazelLocation import locate_bazel
-from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, BuildConfiguration, build_configuration_from_json
+from BuildConfiguration import CodesigningSource, GitCodesigningSource, DirectoryCodesigningSource, XcodeManagedCodesigningSource, BuildConfiguration, build_configuration_from_json
import RemoteBuild
import GenerateProfiles
class ResolvedCodesigningData:
- def __init__(self, aps_environment):
+ def __init__(self, aps_environment, use_xcode_managed_codesigning):
self.aps_environment = aps_environment
+ self.use_xcode_managed_codesigning = use_xcode_managed_codesigning
class BazelCommandLine:
@@ -451,8 +452,8 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_
team_id=build_configuration.team_id,
bundle_id=build_configuration.bundle_id
)
- elif arguments.noCodesigning is not None:
- return ResolvedCodesigningData(aps_environment='production')
+ elif arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True:
+ profile_source = XcodeManagedCodesigningSource()
else:
raise Exception('Neither gitCodesigningRepository nor codesigningInformationPath are set')
@@ -467,7 +468,10 @@ def resolve_codesigning(arguments, base_path, build_configuration, provisioning_
profile_source.copy_profiles_to_destination(destination_path=additional_codesigning_output_path + '/profiles')
profile_source.copy_certificates_to_destination(destination_path=additional_codesigning_output_path + '/certs')
- return ResolvedCodesigningData(aps_environment=profile_source.resolve_aps_environment())
+ return ResolvedCodesigningData(
+ aps_environment=profile_source.resolve_aps_environment(),
+ use_xcode_managed_codesigning=profile_source.use_xcode_managed_codesigning()
+ )
def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, arguments, additional_codesigning_output_path):
@@ -499,7 +503,7 @@ def resolve_configuration(base_path, bazel_command_line: BazelCommandLine, argum
sys.exit(1)
if bazel_command_line is not None:
- build_configuration.write_to_variables_file(bazel_path=bazel_command_line.bazel, aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl')
+ build_configuration.write_to_variables_file(bazel_path=bazel_command_line.bazel, use_xcode_managed_codesigning=codesigning_data.use_xcode_managed_codesigning, aps_environment=codesigning_data.aps_environment, path=configuration_repository_path + '/variables.bzl')
provisioning_profile_files = []
for file_name in os.listdir(provisioning_path):
@@ -549,6 +553,8 @@ def generate_project(bazel, arguments):
disable_extensions = arguments.disableExtensions
if arguments.disableProvisioningProfiles is not None:
disable_provisioning_profiles = arguments.disableProvisioningProfiles
+ if arguments.xcodeManagedCodesigning is not None and arguments.xcodeManagedCodesigning == True:
+ disable_extensions = True
if arguments.generateDsym is not None:
generate_dsym = arguments.generateDsym
if arguments.target is not None:
@@ -594,9 +600,6 @@ def build(bazel, arguments):
bazel_command_line.set_show_actions(arguments.showActions)
bazel_command_line.set_enable_sandbox(arguments.sandbox)
- if arguments.noCodesigning is not None:
- bazel_command_line.set_disable_provisioning_profiles()
-
bazel_command_line.set_split_swiftmodules(arguments.enableParallelSwiftmoduleGeneration)
bazel_command_line.invoke_build()
@@ -688,12 +691,11 @@ def add_codesigning_common_arguments(current_parser: argparse.ArgumentParser):
metavar='command'
)
codesigning_group.add_argument(
- '--noCodesigning',
- type=bool,
+ '--xcodeManagedCodesigning',
+ action='store_true',
help='''
- Use signing certificates and provisioning profiles from a local directory.
+ Let Xcode manage your certificates and provisioning profiles.
''',
- metavar='command'
)
current_parser.add_argument(
diff --git a/build-system/Make/ProjectGeneration.py b/build-system/Make/ProjectGeneration.py
index 8d021208cd..05a326f441 100644
--- a/build-system/Make/ProjectGeneration.py
+++ b/build-system/Make/ProjectGeneration.py
@@ -10,21 +10,29 @@ def remove_directory(path):
shutil.rmtree(path)
def generate_xcodeproj(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments, target_name):
+ if '/' in target_name:
+ app_target_spec = target_name.split('/')[0] + '/' + target_name.split('/')[1] + ':' + target_name.split('/')[1]
+ app_target = target_name
+ app_target_clean = app_target.replace('/', '_')
+ else:
+ app_target_spec = '{target}:{target}'.format(target=target_name)
+ app_target = target_name
+ app_target_clean = app_target.replace('/', '_')
+
bazel_generate_arguments = [build_environment.bazel_path]
bazel_generate_arguments += ['run', '//Telegram:Telegram_xcodeproj']
bazel_generate_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)]
- #if disable_extensions:
- # bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)]
- #if disable_provisioning_profiles:
- # bazel_generate_arguments += ['--//{}:disableProvisioningProfiles'.format(app_target)]
- #if generate_dsym:
- # bazel_generate_arguments += ['--apple_generate_dsym']
- #bazel_generate_arguments += ['--//{}:disableStripping'.format('Telegram')]
+ if disable_extensions:
+ bazel_generate_arguments += ['--//{}:disableExtensions'.format(app_target)]
+ bazel_generate_arguments += ['--//{}:disableStripping'.format('Telegram')]
project_bazel_arguments = []
for argument in bazel_app_arguments:
project_bazel_arguments.append(argument)
project_bazel_arguments += ['--override_repository=build_configuration={}'.format(configuration_path)]
+ if disable_extensions:
+ project_bazel_arguments += ['--//{}:disableExtensions'.format(app_target)]
+ project_bazel_arguments += ['--//{}:disableStripping'.format('Telegram')]
xcodeproj_bazelrc = os.path.join(build_environment.base_path, 'xcodeproj.bazelrc')
if os.path.isfile(xcodeproj_bazelrc):
diff --git a/build-system/bazel-rules/rules_xcodeproj b/build-system/bazel-rules/rules_xcodeproj
index dc226d129a..7f600ddd7c 160000
--- a/build-system/bazel-rules/rules_xcodeproj
+++ b/build-system/bazel-rules/rules_xcodeproj
@@ -1 +1 @@
-Subproject commit dc226d129aca2237982b98a95c80ed1ccc74f0c5
+Subproject commit 7f600ddd7cb3ebc59c696a4639b84b93267c7b6e
diff --git a/build-system/template_minimal_development_configuration.json b/build-system/template_minimal_development_configuration.json
new file mode 100755
index 0000000000..1aad0aed95
--- /dev/null
+++ b/build-system/template_minimal_development_configuration.json
@@ -0,0 +1,14 @@
+{
+ "bundle_id": "org.{! a random string !}.Telegram",
+ "api_id": "{! get one at https://my.telegram.org/apps !}",
+ "api_hash": "{! get one at https://my.telegram.org/apps !}",
+ "team_id": "{! check README.md !}",
+ "app_center_id": "0",
+ "is_internal_build": "true",
+ "is_appstore_build": "false",
+ "appstore_id": "0",
+ "app_specific_url_scheme": "tg",
+ "premium_iap_product_id": "",
+ "enable_siri": false,
+ "enable_icloud": false
+}
\ No newline at end of file
diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift
index 3173913004..0869ada5a6 100644
--- a/submodules/AccountContext/Sources/AccountContext.swift
+++ b/submodules/AccountContext/Sources/AccountContext.swift
@@ -756,6 +756,7 @@ public protocol SharedAccountContext: AnyObject {
var currentInAppNotificationSettings: Atomic { get }
var currentMediaInputSettings: Atomic { get }
var currentStickerSettings: Atomic { get }
+ var currentMediaDisplaySettings: Atomic { get }
var energyUsageSettings: EnergyUsageSettings { get }
diff --git a/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift b/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift
index 75c01e9917..9d815f9921 100644
--- a/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift
+++ b/submodules/AnimatedStickerNode/Sources/AnimatedStickerFrameSource.swift
@@ -330,7 +330,7 @@ private final class AnimatedStickerDirectFrameSourceCache {
return .notFound
}
- self.file.seek(position: Int64(index * 4 * 2))
+ let _ = self.file.seek(position: Int64(index * 4 * 2))
var offset: Int32 = 0
var length: Int32 = 0
if self.file.read(&offset, 4) != 4 {
@@ -384,12 +384,12 @@ private final class AnimatedStickerDirectFrameSourceCache {
return
}
- strongSelf.file.seek(position: Int64(index * 4 * 2))
+ let _ = strongSelf.file.seek(position: Int64(index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.data.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
- strongSelf.file.seek(position: Int64(currentSize))
+ let _ = strongSelf.file.seek(position: Int64(currentSize))
compressedData.data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
@@ -427,12 +427,12 @@ private final class AnimatedStickerDirectFrameSourceCache {
return
}
- strongSelf.file.seek(position: Int64(index * 4 * 2))
+ let _ = strongSelf.file.seek(position: Int64(index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
- strongSelf.file.seek(position: Int64(currentSize))
+ let _ = strongSelf.file.seek(position: Int64(currentSize))
compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
@@ -502,7 +502,7 @@ private final class AnimatedStickerDirectFrameSourceCache {
switch rangeResult {
case let .range(range):
- self.file.seek(position: Int64(range.lowerBound))
+ let _ = self.file.seek(position: Int64(range.lowerBound))
let length = range.upperBound - range.lowerBound
let compressedData = self.file.readData(count: length)
if compressedData.count != length {
diff --git a/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift b/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift
index 3331868289..d599a9bb3a 100644
--- a/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift
+++ b/submodules/AnimatedStickerNode/Sources/VideoStickerFrameSource.swift
@@ -100,7 +100,7 @@ private final class VideoStickerFrameSourceCache {
return true
}
- self.file.seek(position: 0)
+ let _ = self.file.seek(position: 0)
var frameRate: Int32 = 0
if self.file.read(&frameRate, 4) != 4 {
return false
@@ -113,7 +113,7 @@ private final class VideoStickerFrameSourceCache {
}
self.frameRate = frameRate
- self.file.seek(position: 4)
+ let _ = self.file.seek(position: 4)
var frameCount: Int32 = 0
if self.file.read(&frameCount, 4) != 4 {
@@ -144,7 +144,7 @@ private final class VideoStickerFrameSourceCache {
return .notFound
}
- self.file.seek(position: Int64(8 + index * 4 * 2))
+ let _ = self.file.seek(position: Int64(8 + index * 4 * 2))
var offset: Int32 = 0
var length: Int32 = 0
if self.file.read(&offset, 4) != 4 {
@@ -167,11 +167,11 @@ private final class VideoStickerFrameSourceCache {
}
func storeFrameRateAndCount(frameRate: Int, frameCount: Int) {
- self.file.seek(position: 0)
+ let _ = self.file.seek(position: 0)
var frameRate = Int32(frameRate)
let _ = self.file.write(&frameRate, count: 4)
- self.file.seek(position: 4)
+ let _ = self.file.seek(position: 4)
var frameCount = Int32(frameCount)
let _ = self.file.write(&frameCount, count: 4)
}
@@ -203,12 +203,12 @@ private final class VideoStickerFrameSourceCache {
return
}
- strongSelf.file.seek(position: Int64(8 + index * 4 * 2))
+ let _ = strongSelf.file.seek(position: Int64(8 + index * 4 * 2))
var offset = Int32(currentSize)
var length = Int32(compressedData.count)
let _ = strongSelf.file.write(&offset, count: 4)
let _ = strongSelf.file.write(&length, count: 4)
- strongSelf.file.seek(position: Int64(currentSize))
+ let _ = strongSelf.file.seek(position: Int64(currentSize))
compressedData.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in
if let baseAddress = buffer.baseAddress {
let _ = strongSelf.file.write(baseAddress, count: Int(length))
@@ -226,7 +226,7 @@ private final class VideoStickerFrameSourceCache {
switch rangeResult {
case let .range(range):
- self.file.seek(position: Int64(range.lowerBound))
+ let _ = self.file.seek(position: Int64(range.lowerBound))
let length = range.upperBound - range.lowerBound
let compressedData = self.file.readData(count: length)
if compressedData.count != length {
diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift
index 552aefab1c..8edb125876 100644
--- a/submodules/AttachmentUI/Sources/AttachmentController.swift
+++ b/submodules/AttachmentUI/Sources/AttachmentController.swift
@@ -177,12 +177,13 @@ private func generateMaskImage() -> UIImage? {
public class AttachmentController: ViewController {
private let context: AccountContext
private let updatedPresentationData: (initial: PresentationData, signal: Signal)?
- private let chatLocation: ChatLocation
+ private let chatLocation: ChatLocation?
private let buttons: [AttachmentButtonType]
private let initialButton: AttachmentButtonType
private let fromMenu: Bool
private let hasTextInput: Bool
private let makeEntityInputView: () -> AttachmentTextInputPanelInputView?
+ public var animateAppearance: Bool = false
public var willDismiss: () -> Void = {}
public var didDismiss: () -> Void = {}
@@ -619,14 +620,26 @@ public class AttachmentController: ViewController {
private var animating = false
func animateIn() {
- guard let layout = self.validLayout else {
+ guard let layout = self.validLayout, let controller = self.controller else {
return
}
self.animating = true
if case .regular = layout.metrics.widthClass {
- self.animating = false
-
+ if controller.animateAppearance {
+ let targetPosition = self.position
+ let startPosition = targetPosition.offsetBy(dx: 0.0, dy: layout.size.height)
+
+ self.position = startPosition
+ let transition = ContainedViewLayoutTransition.animated(duration: 0.4, curve: .spring)
+ transition.animateView(allowUserInteraction: true, {
+ self.position = targetPosition
+ }, completion: { _ in
+ self.animating = false
+ })
+ } else {
+ self.animating = false
+ }
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 0.1)
} else {
ContainedViewLayoutTransition.animated(duration: 0.3, curve: .linear).updateAlpha(node: self.dim, alpha: 1.0)
@@ -888,7 +901,7 @@ public class AttachmentController: ViewController {
public var getSourceRect: (() -> CGRect?)?
- public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil}) {
+ public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, chatLocation: ChatLocation?, buttons: [AttachmentButtonType], initialButton: AttachmentButtonType = .gallery, fromMenu: Bool = false, hasTextInput: Bool = true, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView? = { return nil}) {
self.context = context
self.updatedPresentationData = updatedPresentationData
self.chatLocation = chatLocation
diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift
index 5e7cf29c0e..257d60b6d0 100644
--- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift
+++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift
@@ -387,7 +387,13 @@ public struct AttachmentMainButtonState {
case center
}
+ public enum Font: Equatable {
+ case regular
+ case bold
+ }
+
public let text: String?
+ public let font: Font
public let background: Background
public let textColor: UIColor
public let isVisible: Bool
@@ -396,6 +402,7 @@ public struct AttachmentMainButtonState {
public init(
text: String?,
+ font: Font,
background: Background,
textColor: UIColor,
isVisible: Bool,
@@ -403,6 +410,7 @@ public struct AttachmentMainButtonState {
isEnabled: Bool
) {
self.text = text
+ self.font = font
self.background = background
self.textColor = textColor
self.isVisible = isVisible
@@ -411,7 +419,7 @@ public struct AttachmentMainButtonState {
}
static var initial: AttachmentMainButtonState {
- return AttachmentMainButtonState(text: nil, background: .color(.clear), textColor: .clear, isVisible: false, progress: .none, isEnabled: false)
+ return AttachmentMainButtonState(text: nil, font: .bold, background: .color(.clear), textColor: .clear, isVisible: false, progress: .none, isEnabled: false)
}
}
@@ -443,6 +451,7 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
super.init(pointerStyle: pointerStyle)
+ self.isExclusiveTouch = true
self.clipsToBounds = true
self.addSubnode(self.backgroundAnimationNode)
@@ -643,7 +652,14 @@ private final class MainButtonNode: HighlightTrackingButtonNode {
self.setupShimmering()
if let text = state.text {
- self.textNode.attributedText = NSAttributedString(string: text, font: Font.semibold(17.0), textColor: state.textColor)
+ let font: UIFont
+ switch state.font {
+ case .regular:
+ font = Font.regular(17.0)
+ case .bold:
+ font = Font.semibold(17.0)
+ }
+ self.textNode.attributedText = NSAttributedString(string: text, font: font, textColor: state.textColor)
let textSize = self.textNode.updateLayout(size)
self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize)
@@ -758,13 +774,13 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
var mainButtonPressed: () -> Void = { }
- init(context: AccountContext, chatLocation: ChatLocation, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
+ init(context: AccountContext, chatLocation: ChatLocation?, updatedPresentationData: (initial: PresentationData, signal: Signal)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
self.context = context
self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
self.makeEntityInputView = makeEntityInputView
- self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil)
+ self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: .builtin(WallpaperSettings()), theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: chatLocation ?? .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, importState: nil, threadData: nil, isGeneralThreadClosed: nil)
self.containerNode = ASDisplayNode()
self.containerNode.clipsToBounds = true
@@ -936,7 +952,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
return
}
textInputPanelNode.loadTextInputNodeIfNeeded()
- guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation.peerId else {
+ guard let textInputNode = textInputPanelNode.textInputNode, let peerId = chatLocation?.peerId else {
return
}
@@ -1267,7 +1283,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
func updateMainButtonState(_ mainButtonState: AttachmentMainButtonState?) {
var currentButtonState = self.mainButtonState
if mainButtonState == nil {
- currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, background: currentButtonState.background, textColor: currentButtonState.textColor, isVisible: false, progress: .none, isEnabled: currentButtonState.isEnabled)
+ currentButtonState = AttachmentMainButtonState(text: currentButtonState.text, font: currentButtonState.font, background: currentButtonState.background, textColor: currentButtonState.textColor, isVisible: false, progress: .none, isEnabled: currentButtonState.isEnabled)
}
self.mainButtonState = mainButtonState ?? currentButtonState
}
@@ -1417,6 +1433,7 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
self.scrollNode.isUserInteractionEnabled = !isSelecting
let isButtonVisible = self.mainButtonState.isVisible
+ let isNarrowButton = isButtonVisible && self.mainButtonState.font == .regular
var insets = layout.insets(options: [])
if let inputHeight = layout.inputHeight, inputHeight > 0.0 && (isSelecting || isButtonVisible) {
@@ -1452,12 +1469,12 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
}
let bounds = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: buttonSize.height + insets.bottom))
- let containerTransition: ContainedViewLayoutTransition
+ var containerTransition: ContainedViewLayoutTransition
let containerFrame: CGRect
if isButtonVisible {
var height: CGFloat
if layout.intrinsicInsets.bottom > 0.0 && (layout.inputHeight ?? 0.0).isZero {
- height = bounds.height + 9.0
+ height = bounds.height
if case .regular = layout.metrics.widthClass {
if self.isStandalone {
height -= 3.0
@@ -1466,7 +1483,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
}
}
} else {
- height = bounds.height + 9.0 + 8.0
+ height = bounds.height + 8.0
+ }
+ if !isNarrowButton {
+ height += 9.0
}
containerFrame = CGRect(origin: CGPoint(), size: CGSize(width: bounds.width, height: height))
} else if isSelecting {
@@ -1500,6 +1520,10 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
}
}
+ if self.containerNode.frame.size.width.isZero {
+ containerTransition = .immediate
+ }
+
containerTransition.updateFrame(node: self.containerNode, frame: containerFrame)
containerTransition.updateFrame(node: self.backgroundNode, frame: containerBounds)
self.backgroundNode.update(size: containerBounds.size, transition: transition)
@@ -1532,11 +1556,13 @@ final class AttachmentPanel: ASDisplayNode, UIScrollViewDelegate {
let sideInset: CGFloat = 16.0
let buttonSize = CGSize(width: layout.size.width - (sideInset + layout.safeInsets.left) * 2.0, height: 50.0)
+ let buttonTopInset: CGFloat = isNarrowButton ? 2.0 : 8.0
+
if !self.dismissed {
self.mainButtonNode.updateLayout(size: buttonSize, state: self.mainButtonState, transition: transition)
}
if !self.animatingTransition {
- transition.updateFrame(node: self.mainButtonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + sideInset, y: isButtonVisible || self.fromMenu ? 8.0 : containerFrame.height), size: buttonSize))
+ transition.updateFrame(node: self.mainButtonNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left + sideInset, y: isButtonVisible || self.fromMenu ? buttonTopInset : containerFrame.height), size: buttonSize))
}
return containerFrame.height
diff --git a/submodules/BuildConfig/BUILD b/submodules/BuildConfig/BUILD
index 10c5c92dbb..7ac35f1be8 100644
--- a/submodules/BuildConfig/BUILD
+++ b/submodules/BuildConfig/BUILD
@@ -7,6 +7,8 @@ load(
"telegram_is_appstore_build",
"telegram_appstore_id",
"telegram_app_specific_url_scheme",
+ "telegram_enable_icloud",
+ "telegram_enable_siri",
)
objc_library(
@@ -25,6 +27,8 @@ objc_library(
"-DAPP_CONFIG_IS_APPSTORE_BUILD={}".format(telegram_is_appstore_build),
"-DAPP_CONFIG_APPSTORE_ID={}".format(telegram_appstore_id),
"-DAPP_SPECIFIC_URL_SCHEME=\\\"{}\\\"".format(telegram_app_specific_url_scheme),
+ "-DAPP_CONFIG_IS_ICLOUD_ENABLED={}".format("true" if telegram_enable_icloud else "false"),
+ "-DAPP_CONFIG_IS_SIRI_ENABLED={}".format("true" if telegram_enable_siri else "false"),
],
hdrs = glob([
"PublicHeaders/**/*.h",
diff --git a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h
index d735dd74bc..f37dd5ebce 100644
--- a/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h
+++ b/submodules/BuildConfig/PublicHeaders/BuildConfig/BuildConfig.h
@@ -18,6 +18,8 @@
@property (nonatomic, readonly) bool isAppStoreBuild;
@property (nonatomic, readonly) int64_t appStoreId;
@property (nonatomic, strong, readonly) NSString * _Nonnull appSpecificUrlScheme;
+@property (nonatomic, readonly) bool isICloudEnabled;
+@property (nonatomic, readonly) bool isSiriEnabled;
+ (DeviceSpecificEncryptionParameters * _Nonnull)deviceSpecificEncryptionParameters:(NSString * _Nonnull)rootPath baseAppBundleId:(NSString * _Nonnull)baseAppBundleId;
- (NSData * _Nullable)bundleDataWithAppToken:(NSData * _Nullable)appToken signatureDict:(NSDictionary * _Nullable)signatureDict;
diff --git a/submodules/BuildConfig/Sources/BuildConfig.m b/submodules/BuildConfig/Sources/BuildConfig.m
index 3dde1749ae..ba2637e202 100644
--- a/submodules/BuildConfig/Sources/BuildConfig.m
+++ b/submodules/BuildConfig/Sources/BuildConfig.m
@@ -185,6 +185,14 @@ API_AVAILABLE(ios(10))
return @(APP_SPECIFIC_URL_SCHEME);
}
+- (bool)isICloudEnabled {
+ return APP_CONFIG_IS_ICLOUD_ENABLED;
+}
+
+- (bool)isSiriEnabled {
+ return APP_CONFIG_IS_SIRI_ENABLED;
+}
+
+ (NSString * _Nullable)bundleSeedId {
NSDictionary *query = [NSDictionary dictionaryWithObjectsAndKeys:
(__bridge NSString *)kSecClassGenericPassword, (__bridge NSString *)kSecClass,
diff --git a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
index a4f1b421d6..c293f8802c 100644
--- a/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
+++ b/submodules/CalendarMessageScreen/Sources/CalendarMessageScreen.swift
@@ -1370,7 +1370,7 @@ public final class CalendarMessageScreen: ViewController {
if self.selectionState?.dayRange == nil {
if let selectionToolbarNode = self.selectionToolbarNode {
let toolbarFrame = selectionToolbarNode.view.convert(selectionToolbarNode.bounds, to: self.view)
- self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in
+ self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.MessageCalendar_EmptySelectionTooltip, style: .default, icon: .none, location: .point(toolbarFrame.insetBy(dx: 0.0, dy: 10.0), .bottom), shouldDismissOnTouch: { point in
return .dismiss(consume: false)
}), in: .current)
}
diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift
index 33c098db5c..087002b3b6 100644
--- a/submodules/ChatListUI/Sources/ChatListController.swift
+++ b/submodules/ChatListUI/Sources/ChatListController.swift
@@ -1599,12 +1599,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
})))
}
- //TODO:localize
-
for filter in filters {
if filter.id == filterId, case let .filter(_, title, _, data) = filter {
if let filterPeersAreMuted {
- items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? "Unmute All" : "Mute All", textColor: .primary, badge: nil, icon: { theme in
+ items.append(.action(ContextMenuActionItem(text: filterPeersAreMuted.areMuted ? strongSelf.presentationData.strings.ChatList_ContextUnmuteAll : strongSelf.presentationData.strings.ChatList_ContextMuteAll, textColor: .primary, badge: nil, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: filterPeersAreMuted.areMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
@@ -1623,21 +1621,23 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let iconColor: UIColor = .white
let overlayController: UndoOverlayController
if !filterPeersAreMuted.areMuted {
+ let text = strongSelf.presentationData.strings.ChatList_ToastFolderMuted(title).string
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
- ], title: nil, text: "All chats in **\(title)** are now muted", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
+ ], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
} else {
+ let text = strongSelf.presentationData.strings.ChatList_ToastFolderUnmuted(title).string
overlayController = UndoOverlayController(presentationData: strongSelf.presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [
"Middle.Group 1.Fill 1": iconColor,
"Top.Group 1.Fill 1": iconColor,
"Bottom.Group 1.Fill 1": iconColor,
"EXAMPLE.Group 1.Fill 1": iconColor,
"Line.Group 1.Stroke 1": iconColor
- ], title: nil, text: "All chats in **\(title)** are now unmuted", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
+ ], title: nil, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false })
}
strongSelf.present(overlayController, in: .current)
})
@@ -1645,7 +1645,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
if !data.includePeers.peers.isEmpty && data.categories.isEmpty && !data.excludeRead && !data.excludeMuted && !data.excludeArchived && data.excludePeers.isEmpty {
- items.append(.action(ContextMenuActionItem(text: "Share", textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: "NEW", color: .accent, style: .label), icon: { theme in
+ items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.ChatList_ContextMenuShare, textColor: .primary, badge: data.hasSharedLinks ? nil : ContextMenuActionBadge(value: strongSelf.presentationData.strings.ChatList_ContextMenuBadgeNew, color: .accent, style: .label), icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor)
}, action: { c, f in
c.dismiss(completion: {
@@ -2000,6 +2000,35 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let _ = value
})
})
+
+ Queue.mainQueue().after(2.0, { [weak self] in
+ guard let self else {
+ return
+ }
+ //TODO:generalize
+ var hasEmptyMark = false
+ self.chatListDisplayNode.mainContainerNode.currentItemNode.forEachItemNode { itemNode in
+ if itemNode is ChatListSectionHeaderNode {
+ hasEmptyMark = true
+ }
+ }
+ if hasEmptyMark {
+ if let componentView = self.headerContentView.view as? ChatListHeaderComponent.View {
+ if let rightButtonView = componentView.rightButtonView {
+ let absoluteFrame = rightButtonView.convert(rightButtonView.bounds, to: self.view)
+ let text: String = self.presentationData.strings.ChatList_EmptyListTooltip
+
+ let tooltipController = TooltipController(content: .text(text), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 30.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 6.0, innerPadding: UIEdgeInsets(top: 2.0, left: 3.0, bottom: 2.0, right: 3.0))
+ self.present(tooltipController, in: .current, with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in
+ guard let self else {
+ return nil
+ }
+ return (self.displayNode, absoluteFrame.insetBy(dx: 4.0, dy: 8.0).offsetBy(dx: 4.0, dy: -1.0))
+ }))
+ }
+ }
+ }
+ })
}
self.chatListDisplayNode.mainContainerNode.addedVisibleChatsWithPeerIds = { [weak self] peerIds in
@@ -2079,7 +2108,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize())
- parentController.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in
+ parentController.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: .chatListPress, location: .point(location, .bottom), shouldDismissOnTouch: { point in
guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else {
return .dismiss(consume: false)
}
@@ -2997,8 +3026,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
if hasLinks {
- //TODO:localize
- self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
+ self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
confirmDeleteFolder()
}),
diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift
index 261e946cf4..e05badfeb4 100644
--- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift
+++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift
@@ -571,13 +571,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
self.topPanel = topPanel
}
- //TODO:localize
- let title: String
- if chatFolderUpdates.availableChatsToJoin == 1 {
- title = "1 New Chat Available"
- } else {
- title = "\(chatFolderUpdates.availableChatsToJoin) New Chats Available"
- }
+ let title: String = self.presentationData.strings.ChatList_PanelNewChatsAvailable(Int32(chatFolderUpdates.availableChatsToJoin))
let topPanelHeight: CGFloat = 44.0
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift
index db327fd667..0b888606a1 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetController.swift
@@ -547,11 +547,9 @@ private enum ChatListFilterPresetEntry: ItemListNodeEntry {
arguments.expandSection(.exclude)
})
case let .inviteLinkHeader(hasLinks):
- //TODO:localize
- return ItemListSectionHeaderItem(presentationData: presentationData, text: "SHARE FOLDER", badge: hasLinks ? nil : "NEW", sectionId: self.section)
+ return ItemListSectionHeaderItem(presentationData: presentationData, text: presentationData.strings.ChatListFilter_SectionShare, badge: hasLinks ? nil : presentationData.strings.ChatList_ContextMenuBadgeNew, sectionId: self.section)
case let .inviteLinkCreate(hasLinks):
- //TODO:localize
- return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? "Create a new Link" : "Create an Invite Link", sectionId: self.section, editing: false, action: {
+ return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.linkIcon(presentationData.theme), title: hasLinks ? presentationData.strings.ChatListFilter_CreateLink : presentationData.strings.ChatListFilter_CreateLinkNew, sectionId: self.section, editing: false, action: {
arguments.createLink()
})
case let .inviteLink(_, link):
@@ -648,8 +646,7 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
if let currentPreset, let data = currentPreset.data, data.isShared {
} else {
entries.append(.excludePeersHeader(presentationData.strings.ChatListFolder_ExcludedSectionHeader))
- //TODO:localize
- entries.append(.addExcludePeer(title: "Add Chats to Exclude"))
+ entries.append(.addExcludePeer(title: presentationData.strings.ChatListFilter_ExcludeChatsAction))
var excludeCategoryIndex = 0
for category in ChatListFilterExcludeCategory.allCases {
@@ -705,8 +702,7 @@ private func chatListFilterPresetControllerEntries(presentationData: Presentatio
}
}
- //TODO:localize
- entries.append(.inviteLinkInfo(text: hasLinks ? "Create more links to set up different access levels for different people." : "Share access to some of this folder's groups and channels with others."))
+ entries.append(.inviteLinkInfo(text: hasLinks ? presentationData.strings.ChatListFilter_LinkListInfo : presentationData.strings.ChatListFilter_LinkListInfoNew))
return entries
}
@@ -840,37 +836,23 @@ private func internalChatListFilterAddChatsController(context: AccountContext, f
}
includePeers.sort()
- let newPeers = includePeers.filter({ !(filter.data?.includePeers.peers.contains($0) ?? false) })
- var removedPeers: [PeerId] = []
- if let data = filter.data {
- removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) })
- }
- if newPeers.count != 0 {
- let title: String
- let text: String
-
- if newPeers.count == 1 {
- title = "Сhat added to folder"
- text = "It will not affect chatlist of the links of this folder"
- } else {
- title = "\(newPeers.count) chats added to folder"
- text = "It will not affect chatlist of the links of this folder"
+ if filter.id > 1, case let .filter(_, _, _, data) = filter, data.hasSharedLinks {
+ let newPeers = includePeers.filter({ !(filter.data?.includePeers.peers.contains($0) ?? false) })
+ var removedPeers: [PeerId] = []
+ if let data = filter.data {
+ removedPeers = data.includePeers.peers.filter({ !includePeers.contains($0) })
}
-
- presentUndo(.info(title: title, text: text, timeout: nil))
- } else if removedPeers.count != 0 {
- let title: String
- let text: String
-
- if newPeers.count == 1 {
- title = "Сhat removed from folder"
- text = "It will not affect chatlist of the links of this folder"
- } else {
- title = "\(newPeers.count) chats removed from folder"
- text = "It will not affect chatlist of the links of this folder"
+ if newPeers.count != 0 {
+ let title: String = presentationData.strings.ChatListFilter_ToastChatsAddedTitle(Int32(newPeers.count))
+ let text: String = presentationData.strings.ChatListFilter_ToastChatsAddedText
+
+ presentUndo(.universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil))
+ } else if removedPeers.count != 0 {
+ let title: String = presentationData.strings.ChatListFilter_ToastChatsRemovedTitle(Int32(newPeers.count))
+ let text: String = presentationData.strings.ChatListFilter_ToastChatsRemovedText
+
+ presentUndo(.universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil))
}
-
- presentUndo(.info(title: title, text: text, timeout: nil))
}
var categories: ChatListFilterPeerCategories = []
@@ -1321,6 +1303,20 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
}
return state
}
+
+ let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in
+ if let currentPreset, let data = currentPreset.data, data.hasSharedLinks {
+ let title: String
+ let text: String
+
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+
+ title = presentationData.strings.ChatListFilter_ToastChatsRemovedTitle(1)
+ text = presentationData.strings.ChatListFilter_ToastChatsRemovedText
+
+ presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
+ }
+ })
},
deleteExcludePeer: { peerId in
updateState { state in
@@ -1376,17 +1372,15 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
},
createLink: {
if initialPreset == nil {
- //TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- let text = "Please finish creating this folder to share it."
+ let text = presentationData.strings.ChatListFilter_AlertCreateFolderBeforeSharingText
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
} else {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let state = stateValue.with({ $0 })
if state.additionallyIncludePeers.isEmpty {
- //TODO:localize
- let text = "You can’t share folders which have chat types or excluded chats."
+ let text = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
return
@@ -1400,14 +1394,13 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in
if let currentPreset, let data = currentPreset.data {
- //TODO:localize
var unavailableText: String?
if !data.categories.isEmpty {
- unavailableText = "You can’t share folders which have chat types or excluded chats."
+ unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
} else if data.excludeArchived || data.excludeRead || data.excludeMuted {
- unavailableText = "You can’t share folders which have chat types or excluded chats."
+ unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
} else if !data.excludePeers.isEmpty {
- unavailableText = "You can’t share folders which have chat types or excluded chats."
+ unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
}
if let unavailableText {
statusController?.dismiss()
@@ -1581,6 +1574,20 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
}
return state
}
+
+ let _ = (updatedCurrentPreset |> take(1) |> deliverOnMainQueue).start(next: { currentPreset in
+ if let currentPreset, let data = currentPreset.data, data.hasSharedLinks {
+ let title: String
+ let text: String
+
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+
+ title = presentationData.strings.ChatListFilter_ToastChatsRemovedTitle(1)
+ text = presentationData.strings.ChatListFilter_ToastChatsRemovedText
+
+ presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_remove_from_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: title, text: text, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
+ }
+ })
})))
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
@@ -1757,8 +1764,7 @@ func chatListFilterPresetController(context: AccountContext, currentPreset initi
TextAlertAction(type: .genericAction, title: presentationData.strings.ChatListFolder_DiscardDiscard, action: {
dismissImpl?()
}),
- //TODO:localize
- TextAlertAction(type: .defaultAction, title: "Save", action: {
+ TextAlertAction(type: .defaultAction, title: presentationData.strings.ChatListFilter_SaveAlertActionSave, action: {
applyImpl?(false, {
dismissImpl?()
})
@@ -1899,11 +1905,13 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
}))
}, error: { error in
completed()
- //TODO:localize
+
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+
let text: String
switch error {
case .generic:
- text = "An error occurred"
+ text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
case let .sharedFolderLimitExceeded(limit, _):
let limitController = context.sharedContext.makePremiumLimitController(context: context, subject: .membershipInSharedFolders, count: limit, action: {
pushPremiumController(PremiumIntroScreen(context: context, source: .membershipInSharedFolders))
@@ -1933,10 +1941,8 @@ func openCreateChatListFolderLink(context: AccountContext, folderId: Int32, chec
return
case .someUserTooManyChannels:
- //TODO:localize
- text = "One of the groups in this folder can’t be added because one of its admins has too many groups and channels."
+ text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit
}
- let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentController(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]))
})
}
diff --git a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift
index 460e2b3722..6bcb8e8954 100644
--- a/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift
+++ b/submodules/ChatListUI/Sources/ChatListFilterPresetListController.swift
@@ -223,8 +223,7 @@ private func chatListFilterPresetListControllerEntries(presentationData: Present
entries.append(.listHeader(presentationData.strings.ChatListFolderSettings_FoldersSection))
- //TODO:localize
- entries.append(.addItem(text: "Create a Folder", isEditing: state.isEditing))
+ entries.append(.addItem(text: presentationData.strings.ChatListFilterList_CreateFolder, isEditing: state.isEditing))
if !filters.isEmpty || suggestedFilters.isEmpty {
var folderCount = 0
@@ -450,8 +449,7 @@ public func chatListFilterPresetListController(context: AccountContext, mode: Ch
}
if hasLinks {
- //TODO:localize
- presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Delete Folder", text: "Are you sure you want to delete this folder? This will also deactivate all the invite links used to share this folder.", actions: [
+ presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.ChatList_AlertDeleteFolderTitle, text: presentationData.strings.ChatList_AlertDeleteFolderText, actions: [
TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Delete, action: {
confirmDeleteFolder()
}),
diff --git a/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift
new file mode 100644
index 0000000000..e7068be412
--- /dev/null
+++ b/submodules/ChatListUI/Sources/Node/ChatListEmptyInfoItem.swift
@@ -0,0 +1,277 @@
+import Foundation
+import UIKit
+import AsyncDisplayKit
+import Postbox
+import Display
+import SwiftSignalKit
+import TelegramPresentationData
+import ListSectionHeaderNode
+import AppBundle
+import AnimatedStickerNode
+import TelegramAnimatedStickerNode
+
+class ChatListEmptyInfoItem: ListViewItem {
+ let theme: PresentationTheme
+ let strings: PresentationStrings
+
+ let selectable: Bool = false
+
+ init(theme: PresentationTheme, strings: PresentationStrings) {
+ self.theme = theme
+ self.strings = strings
+ }
+
+ 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 = ChatListEmptyInfoItemNode()
+
+ let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
+
+ node.insets = nodeLayout.insets
+ node.contentSize = nodeLayout.contentSize
+
+ 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 {
+ assert(node() is ChatListEmptyInfoItemNode)
+ if let nodeValue = node() as? ChatListEmptyInfoItemNode {
+
+ let layout = nodeValue.asyncLayout()
+ async {
+ let (nodeLayout, apply) = layout(self, params, nextItem == nil)
+ Queue.mainQueue().async {
+ completion(nodeLayout, { _ in
+ apply()
+ })
+ }
+ }
+ }
+ }
+ }
+}
+
+class ChatListEmptyInfoItemNode: ListViewItemNode {
+ private var item: ChatListEmptyInfoItem?
+
+ private let animationNode: AnimatedStickerNode
+ private let textNode: TextNode
+
+ override var visibility: ListViewItemNodeVisibility {
+ didSet {
+ let wasVisible = self.visibilityStatus
+ let isVisible: Bool
+ switch self.visibility {
+ case let .visible(fraction, _):
+ isVisible = fraction > 0.2
+ case .none:
+ isVisible = false
+ }
+ if wasVisible != isVisible {
+ self.visibilityStatus = isVisible
+ }
+ }
+ }
+
+ private var visibilityStatus: Bool = false {
+ didSet {
+ if self.visibilityStatus != oldValue {
+ self.animationNode.visibility = self.visibilityStatus
+ }
+ }
+ }
+
+ required init() {
+ self.animationNode = DefaultAnimatedStickerNodeImpl()
+ self.textNode = TextNode()
+
+ super.init(layerBacked: false, dynamicBounce: false)
+
+ self.addSubnode(self.animationNode)
+ self.addSubnode(self.textNode)
+ }
+
+ override func didLoad() {
+ super.didLoad()
+ }
+
+ override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
+ let layout = self.asyncLayout()
+ let (_, apply) = layout(item as! ChatListEmptyInfoItem, params, nextItem == nil)
+ apply()
+ }
+
+ func asyncLayout() -> (_ item: ChatListEmptyInfoItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
+ let makeTextLayout = TextNode.asyncLayout(self.textNode)
+
+ return { item, params, last in
+ let baseWidth = params.width - params.leftInset - params.rightInset
+
+ let topInset: CGFloat = 8.0
+ let textSpacing: CGFloat = 27.0
+ let bottomInset: CGFloat = 24.0
+ let animationHeight: CGFloat = 140.0
+
+ let string = NSMutableAttributedString(string: item.strings.ChatList_EmptyChatList, font: Font.semibold(17.0), textColor: item.theme.list.itemPrimaryTextColor)
+
+ let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: string, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: baseWidth, height: .greatestFiniteMagnitude), alignment: .center))
+
+ let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: topInset + animationHeight + textSpacing + textLayout.0.size.height + bottomInset), insets: UIEdgeInsets())
+
+ return (layout, { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+ strongSelf.item = item
+
+ var topOffset: CGFloat = topInset
+
+ let animationFrame = CGRect(origin: CGPoint(x: floor((params.width - animationHeight) * 0.5), y: topOffset), size: CGSize(width: animationHeight, height: animationHeight))
+ if strongSelf.animationNode.bounds.isEmpty {
+ strongSelf.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "ChatListEmpty"), width: 248, height: 248, playbackMode: .once, mode: .direct(cachePathPrefix: nil))
+ }
+ strongSelf.animationNode.frame = animationFrame
+ topOffset += animationHeight + textSpacing
+
+ let _ = textLayout.1()
+ strongSelf.textNode.frame = CGRect(origin: CGPoint(x: floor((params.width - textLayout.0.size.width) * 0.5), y: topOffset), size: textLayout.0.size)
+
+ strongSelf.contentSize = layout.contentSize
+ strongSelf.insets = layout.insets
+ })
+ }
+ }
+}
+
+class ChatListSectionHeaderItem: ListViewItem {
+ let theme: PresentationTheme
+ let strings: PresentationStrings
+ let hide: (() -> Void)?
+
+ let selectable: Bool = false
+
+ init(theme: PresentationTheme, strings: PresentationStrings, hide: (() -> Void)?) {
+ self.theme = theme
+ self.strings = strings
+ self.hide = hide
+ }
+
+ 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 = ChatListSectionHeaderNode()
+
+ let (nodeLayout, apply) = node.asyncLayout()(self, params, false)
+
+ node.insets = nodeLayout.insets
+ node.contentSize = nodeLayout.contentSize
+
+ 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 {
+ assert(node() is ChatListSectionHeaderNode)
+ if let nodeValue = node() as? ChatListSectionHeaderNode {
+
+ let layout = nodeValue.asyncLayout()
+ async {
+ let (nodeLayout, apply) = layout(self, params, nextItem == nil)
+ Queue.mainQueue().async {
+ completion(nodeLayout, { _ in
+ apply()
+ })
+ }
+ }
+ }
+ }
+ }
+}
+
+class ChatListSectionHeaderNode: ListViewItemNode {
+ private var item: ChatListSectionHeaderItem?
+
+ private var headerNode: ListSectionHeaderNode?
+
+ required init() {
+ super.init(layerBacked: false, dynamicBounce: false)
+
+ self.zPosition = 1.0
+ }
+
+ override func didLoad() {
+ super.didLoad()
+ }
+
+ override func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) {
+ let layout = self.asyncLayout()
+ let (_, apply) = layout(item as! ChatListSectionHeaderItem, params, nextItem == nil)
+ apply()
+ }
+
+ override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ if let headerNode = self.headerNode {
+ if let result = headerNode.view.hitTest(self.view.convert(point, to: headerNode.view), with: event) {
+ return result
+ }
+ }
+ return nil
+ }
+
+ func asyncLayout() -> (_ item: ChatListSectionHeaderItem, _ params: ListViewItemLayoutParams, _ isLast: Bool) -> (ListViewItemNodeLayout, () -> Void) {
+ return { item, params, last in
+ let layout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 28.0), insets: UIEdgeInsets())
+
+ return (layout, { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+ strongSelf.item = item
+
+ let headerNode: ListSectionHeaderNode
+ if let current = strongSelf.headerNode {
+ headerNode = current
+ } else {
+ headerNode = ListSectionHeaderNode(theme: item.theme)
+ strongSelf.headerNode = headerNode
+ strongSelf.addSubnode(headerNode)
+ }
+
+ headerNode.title = item.strings.ChatList_EmptyListContactsHeader
+ if item.hide != nil {
+ headerNode.action = item.strings.ChatList_EmptyListContactsHeaderHide
+ headerNode.actionType = .generic
+ headerNode.activateAction = {
+ guard let self else {
+ return
+ }
+ self.item?.hide?()
+ }
+ } else {
+ headerNode.action = nil
+ }
+
+ headerNode.updateTheme(theme: item.theme)
+ headerNode.updateLayout(size: CGSize(width: params.width, height: layout.contentSize.height), leftInset: params.leftInset, rightInset: params.rightInset)
+
+ strongSelf.contentSize = layout.contentSize
+ strongSelf.insets = layout.insets
+ })
+ }
+ }
+}
+
diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift
index d9d620a424..89de56c420 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift
@@ -3312,6 +3312,12 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleLayout.trailingLineWidth + 3.0 + titleOffset
+ let lastLineRect: CGRect
+ if let rect = titleLayout.linesRects().last {
+ lastLineRect = CGRect(origin: CGPoint(x: 0.0, y: titleLayout.size.height - rect.height - 2.0), size: CGSize(width: rect.width, height: rect.height + 2.0))
+ } else {
+ lastLineRect = CGRect(origin: CGPoint(), size: titleLayout.size)
+ }
if let currentCredibilityIconContent = currentCredibilityIconContent {
let credibilityIconView: ComponentHostView
@@ -3339,7 +3345,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
environment: {},
containerSize: CGSize(width: 20.0, height: 20.0)
)
- transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.midY - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
+ transition.updateFrame(view: credibilityIconView, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - iconSize.height / 2.0) - UIScreenPixel), size: iconSize))
nextTitleIconOrigin += credibilityIconView.bounds.width + 4.0
} else if let credibilityIconView = strongSelf.credibilityIconView {
strongSelf.credibilityIconView = nil
@@ -3349,7 +3355,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let currentMutedIconImage = currentMutedIconImage {
strongSelf.mutedIconNode.image = currentMutedIconImage
strongSelf.mutedIconNode.isHidden = false
- transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.midY - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size))
+ transition.updateFrame(node: strongSelf.mutedIconNode, frame: CGRect(origin: CGPoint(x: nextTitleIconOrigin - 5.0, y: floorToScreenPixels(titleFrame.maxY - lastLineRect.height * 0.5 - currentMutedIconImage.size.height / 2.0)), size: currentMutedIconImage.size))
nextTitleIconOrigin += currentMutedIconImage.size.width + 1.0
} else {
strongSelf.mutedIconNode.image = nil
diff --git a/submodules/ChatListUI/Sources/Node/ChatListNode.swift b/submodules/ChatListUI/Sources/Node/ChatListNode.swift
index 24506af387..6cdfc5f62c 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListNode.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListNode.swift
@@ -616,8 +616,44 @@ private func mappedInsertEntries(context: AccountContext, nodeInteraction: ChatL
hiddenOffset: hiddenByDefault && !revealed,
interaction: nodeInteraction
), directionHint: entry.directionHint)
+ case let .ContactEntry(contactEntry):
+ let header: ChatListSearchItemHeader? = nil
+
+ var status: ContactsPeerItemStatus = .none
+ status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat)
+
+ let presentationData = contactEntry.presentationData
+
+ let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
+
+ return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
+ presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
+ sortOrder: presentationData.nameSortOrder,
+ displayOrder: presentationData.nameDisplayOrder,
+ context: context,
+ peerMode: .generalSearch,
+ peer: peerContent,
+ status: status,
+ enabled: true,
+ selection: .none,
+ editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
+ index: nil,
+ header: header,
+ action: { _ in
+ nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil)
+ },
+ disabledAction: nil,
+ animationCache: nodeInteraction.animationCache,
+ animationRenderer: nodeInteraction.animationRenderer
+ ), directionHint: entry.directionHint)
case let .ArchiveIntro(presentationData):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
+ case let .EmptyIntro(presentationData):
+ return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
+ case let .SectionHeader(presentationData, displayHide):
+ return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
+ hideChatListContacts(context: context)
+ } : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
@@ -881,8 +917,44 @@ private func mappedUpdateEntries(context: AccountContext, nodeInteraction: ChatL
hiddenOffset: hiddenByDefault && !revealed,
interaction: nodeInteraction
), directionHint: entry.directionHint)
+ case let .ContactEntry(contactEntry):
+ let header: ChatListSearchItemHeader? = nil
+
+ var status: ContactsPeerItemStatus = .none
+ status = .presence(contactEntry.presence, contactEntry.presentationData.dateTimeFormat)
+
+ let presentationData = contactEntry.presentationData
+
+ let peerContent: ContactsPeerItemPeer = .peer(peer: contactEntry.peer, chatPeer: contactEntry.peer)
+
+ return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ContactsPeerItem(
+ presentationData: ItemListPresentationData(theme: presentationData.theme, fontSize: presentationData.fontSize, strings: presentationData.strings, nameDisplayOrder: presentationData.nameDisplayOrder),
+ sortOrder: presentationData.nameSortOrder,
+ displayOrder: presentationData.nameDisplayOrder,
+ context: context,
+ peerMode: .generalSearch,
+ peer: peerContent,
+ status: status,
+ enabled: true,
+ selection: .none,
+ editing: ContactsPeerItemEditing(editable: false, editing: false, revealed: false),
+ index: nil,
+ header: header,
+ action: { _ in
+ nodeInteraction.peerSelected(contactEntry.peer, nil, nil, nil)
+ },
+ disabledAction: nil,
+ animationCache: nodeInteraction.animationCache,
+ animationRenderer: nodeInteraction.animationRenderer
+ ), directionHint: entry.directionHint)
case let .ArchiveIntro(presentationData):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListArchiveInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
+ case let .EmptyIntro(presentationData):
+ return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListEmptyInfoItem(theme: presentationData.theme, strings: presentationData.strings), directionHint: entry.directionHint)
+ case let .SectionHeader(presentationData, displayHide):
+ return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListSectionHeaderItem(theme: presentationData.theme, strings: presentationData.strings, hide: displayHide ? {
+ hideChatListContacts(context: context)
+ } : nil), directionHint: entry.directionHint)
case let .Notice(presentationData, notice):
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: ChatListStorageInfoItem(theme: presentationData.theme, strings: presentationData.strings, notice: notice, action: { [weak nodeInteraction] action in
switch action {
@@ -1702,6 +1774,65 @@ public final class ChatListNode: ListView {
let _ = self.enqueueTransition(value).start()
})*/
+ let contacts: Signal<[ChatListContactPeer], NoError>
+ if case .chatList(groupId: .root) = location, chatListFilter == nil {
+ contacts = ApplicationSpecificNotice.displayChatListContacts(accountManager: context.sharedContext.accountManager)
+ |> distinctUntilChanged
+ |> mapToSignal { value -> Signal<[ChatListContactPeer], NoError> in
+ if value {
+ return .single([])
+ }
+
+ return context.engine.messages.chatList(group: .root, count: 10)
+ |> map { chatList -> Bool in
+ if chatList.items.count >= 5 {
+ return true
+ } else {
+ return false
+ }
+ }
+ |> distinctUntilChanged
+ |> mapToSignal { hasChats -> Signal<[ChatListContactPeer], NoError> in
+ if hasChats {
+ return .single([])
+ }
+
+ return context.engine.data.subscribe(
+ TelegramEngine.EngineData.Item.Contacts.List(includePresences: true)
+ )
+ |> mapToThrottled { next -> Signal in
+ return .single(next)
+ |> then(
+ .complete()
+ |> delay(5.0, queue: Queue.concurrentDefaultQueue())
+ )
+ }
+ |> map { contactList -> [ChatListContactPeer] in
+ var result: [ChatListContactPeer] = []
+ for peer in contactList.peers {
+ if peer.id == context.account.peerId {
+ continue
+ }
+ result.append(ChatListContactPeer(
+ peer: peer,
+ presence: contactList.presences[peer.id] ?? EnginePeer.Presence(status: .longTimeAgo, lastActivity: 0)
+ ))
+ }
+ result.sort(by: { lhs, rhs in
+ if lhs.presence.status != rhs.presence.status {
+ return lhs.presence.status < rhs.presence.status
+ } else {
+ return lhs.peer.id < rhs.peer.id
+ }
+ })
+ return result
+ }
+ }
+ }
+ } else {
+ contacts = .single([])
+ }
+
let chatListNodeViewTransition = combineLatest(
queue: viewProcessingQueue,
hideArchivedFolderByDefault,
@@ -1711,9 +1842,10 @@ public final class ChatListNode: ListView {
savedMessagesPeer,
chatListViewUpdate,
self.chatFolderUpdates.get() |> distinctUntilChanged,
- self.statePromise.get()
+ self.statePromise.get(),
+ contacts
)
- |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state) -> Signal in
+ |> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state, contacts) -> Signal in
let (update, filter) = updateAndFilter
let previousHideArchivedFolderByDefaultValue = previousHideArchivedFolderByDefault.swap(hideArchivedFolderByDefault)
@@ -1729,7 +1861,7 @@ public final class ChatListNode: ListView {
notice = nil
}
- let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location)
+ let (rawEntries, isLoading) = chatListNodeEntriesForView(update.list, state: state, savedMessagesPeer: savedMessagesPeer, foundPeers: state.foundPeers, hideArchivedFolderByDefault: hideArchivedFolderByDefault, displayArchiveIntro: displayArchiveIntro, notice: notice, mode: mode, chatListLocation: location, contacts: contacts)
var isEmpty = true
var entries = rawEntries.filter { entry in
switch entry {
@@ -1974,6 +2106,9 @@ public final class ChatListNode: ListView {
return false
}
}
+ case .ContactEntry:
+ isEmpty = false
+ return true
case .GroupReferenceEntry:
isEmpty = false
return true
@@ -2910,7 +3045,7 @@ public final class ChatListNode: ListView {
var hasArchive = false
loop: for entry in transition.chatListView.filteredEntries {
switch entry {
- case .GroupReferenceEntry, .HoleEntry, .PeerEntry:
+ case .GroupReferenceEntry, .HoleEntry, .PeerEntry, .ContactEntry:
if case .GroupReferenceEntry = entry {
hasArchive = true
} else {
@@ -2929,7 +3064,7 @@ public final class ChatListNode: ListView {
} else {
break loop
}
- case .ArchiveIntro, .Notice, .HeaderEntry, .AdditionalCategory:
+ case .ArchiveIntro, .EmptyIntro, .SectionHeader, .Notice, .HeaderEntry, .AdditionalCategory:
break
}
}
@@ -3660,3 +3795,7 @@ public class ChatHistoryListSelectionRecognizer: UIPanGestureRecognizer {
}
}
}
+
+func hideChatListContacts(context: AccountContext) {
+ let _ = ApplicationSpecificNotice.setDisplayChatListContacts(accountManager: context.sharedContext.accountManager).start()
+}
diff --git a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift
index 8d9b8d8300..43d34c20ad 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListNodeEntries.swift
@@ -12,7 +12,10 @@ enum ChatListNodeEntryId: Hashable {
case PeerId(Int64)
case ThreadId(Int64)
case GroupId(EngineChatList.Group)
+ case ContactId(EnginePeer.Id)
case ArchiveIntro
+ case EmptyIntro
+ case SectionHeader
case Notice
case additionalCategory(Int)
}
@@ -20,6 +23,8 @@ enum ChatListNodeEntryId: Hashable {
enum ChatListNodeEntrySortIndex: Comparable {
case index(EngineChatList.Item.Index)
case additionalCategory(Int)
+ case sectionHeader
+ case contact(id: EnginePeer.Id, presence: EnginePeer.Presence)
static func <(lhs: ChatListNodeEntrySortIndex, rhs: ChatListNodeEntrySortIndex) -> Bool {
switch lhs {
@@ -29,6 +34,10 @@ enum ChatListNodeEntrySortIndex: Comparable {
return lhsIndex < rhsIndex
case .additionalCategory:
return false
+ case .sectionHeader:
+ return true
+ case .contact:
+ return true
}
case let .additionalCategory(lhsIndex):
switch rhs {
@@ -36,6 +45,30 @@ enum ChatListNodeEntrySortIndex: Comparable {
return lhsIndex < rhsIndex
case .index:
return true
+ case .sectionHeader:
+ return true
+ case .contact:
+ return true
+ }
+ case .sectionHeader:
+ switch rhs {
+ case .additionalCategory, .index, .sectionHeader:
+ return false
+ case .contact:
+ return true
+ }
+ case let .contact(lhsId, lhsPresense):
+ switch rhs {
+ case .sectionHeader:
+ return false
+ case let .contact(rhsId, rhsPresense):
+ if lhsPresense != rhsPresense {
+ return rhsPresense.status > rhsPresense.status
+ } else {
+ return lhsId < rhsId
+ }
+ default:
+ return false
}
}
}
@@ -238,11 +271,39 @@ enum ChatListNodeEntry: Comparable, Identifiable {
}
}
+ struct ContactEntryData: Equatable {
+ var presentationData: ChatListPresentationData
+ var peer: EnginePeer
+ var presence: EnginePeer.Presence
+
+ init(presentationData: ChatListPresentationData, peer: EnginePeer, presence: EnginePeer.Presence) {
+ self.presentationData = presentationData
+ self.peer = peer
+ self.presence = presence
+ }
+
+ static func ==(lhs: ContactEntryData, rhs: ContactEntryData) -> Bool {
+ if lhs.presentationData !== rhs.presentationData {
+ return false
+ }
+ if lhs.peer != rhs.peer {
+ return false
+ }
+ if lhs.presence != rhs.presence {
+ return false
+ }
+ return true
+ }
+ }
+
case HeaderEntry
case PeerEntry(PeerEntryData)
case HoleEntry(EngineMessage.Index, theme: PresentationTheme)
case GroupReferenceEntry(index: EngineChatList.Item.Index, presentationData: ChatListPresentationData, groupId: EngineChatList.Group, peers: [EngineChatList.GroupItem.Item], message: EngineMessage?, editing: Bool, unreadCount: Int, revealed: Bool, hiddenByDefault: Bool)
+ case ContactEntry(ContactEntryData)
case ArchiveIntro(presentationData: ChatListPresentationData)
+ case EmptyIntro(presentationData: ChatListPresentationData)
+ case SectionHeader(presentationData: ChatListPresentationData, displayHide: Bool)
case Notice(presentationData: ChatListPresentationData, notice: ChatListNotice)
case AdditionalCategory(index: Int, id: Int, title: String, image: UIImage?, appearance: ChatListNodeAdditionalCategory.Appearance, selected: Bool, presentationData: ChatListPresentationData)
@@ -256,8 +317,14 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .index(.chatList(EngineChatList.Item.Index.ChatList(pinningIndex: nil, messageIndex: holeIndex)))
case let .GroupReferenceEntry(index, _, _, _, _, _, _, _, _):
return .index(index)
+ case let .ContactEntry(contactEntry):
+ return .contact(id: contactEntry.peer.id, presence: contactEntry.presence)
case .ArchiveIntro:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
+ case .EmptyIntro:
+ return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor))
+ case .SectionHeader:
+ return .sectionHeader
case .Notice:
return .index(.chatList(EngineChatList.Item.Index.ChatList.absoluteUpperBound.successor.successor))
case let .AdditionalCategory(index, _, _, _, _, _, _):
@@ -280,8 +347,14 @@ enum ChatListNodeEntry: Comparable, Identifiable {
return .Hole(Int64(holeIndex.id.id))
case let .GroupReferenceEntry(_, _, groupId, _, _, _, _, _, _):
return .GroupId(groupId)
+ case let .ContactEntry(contactEntry):
+ return .ContactId(contactEntry.peer.id)
case .ArchiveIntro:
return .ArchiveIntro
+ case .EmptyIntro:
+ return .EmptyIntro
+ case .SectionHeader:
+ return .SectionHeader
case .Notice:
return .Notice
case let .AdditionalCategory(_, id, _, _, _, _, _):
@@ -347,6 +420,12 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
+ case let .ContactEntry(contactEntry):
+ if case .ContactEntry(contactEntry) = rhs {
+ return true
+ } else {
+ return false
+ }
case let .ArchiveIntro(lhsPresentationData):
if case let .ArchiveIntro(rhsPresentationData) = rhs {
if lhsPresentationData !== rhsPresentationData {
@@ -356,6 +435,27 @@ enum ChatListNodeEntry: Comparable, Identifiable {
} else {
return false
}
+ case let .EmptyIntro(lhsPresentationData):
+ if case let .EmptyIntro(rhsPresentationData) = rhs {
+ if lhsPresentationData !== rhsPresentationData {
+ return false
+ }
+ return true
+ } else {
+ return false
+ }
+ case let .SectionHeader(lhsPresentationData, lhsDisplayHide):
+ if case let .SectionHeader(rhsPresentationData, rhsDisplayHide) = rhs {
+ if lhsPresentationData !== rhsPresentationData {
+ return false
+ }
+ if lhsDisplayHide != rhsDisplayHide {
+ return false
+ }
+ return true
+ } else {
+ return false
+ }
case let .Notice(lhsPresentationData, lhsInfo):
if case let .Notice(rhsPresentationData, rhsInfo) = rhs {
if lhsPresentationData !== rhsPresentationData {
@@ -407,9 +507,32 @@ private func offsetPinnedIndex(_ index: EngineChatList.Item.Index, offset: UInt1
}
}
-func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation) -> (entries: [ChatListNodeEntry], loading: Bool) {
+struct ChatListContactPeer {
+ var peer: EnginePeer
+ var presence: EnginePeer.Presence
+
+ init(peer: EnginePeer, presence: EnginePeer.Presence) {
+ self.peer = peer
+ self.presence = presence
+ }
+}
+
+func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState, savedMessagesPeer: EnginePeer?, foundPeers: [(EnginePeer, EnginePeer?)], hideArchivedFolderByDefault: Bool, displayArchiveIntro: Bool, notice: ChatListNotice?, mode: ChatListNodeMode, chatListLocation: ChatListControllerLocation, contacts: [ChatListContactPeer]) -> (entries: [ChatListNodeEntry], loading: Bool) {
var result: [ChatListNodeEntry] = []
+ if !view.hasEarlier {
+ for contact in contacts {
+ result.append(.ContactEntry(ChatListNodeEntry.ContactEntryData(
+ presentationData: state.presentationData,
+ peer: contact.peer,
+ presence: contact.presence
+ )))
+ }
+ if !contacts.isEmpty {
+ result.append(.SectionHeader(presentationData: state.presentationData, displayHide: !view.items.isEmpty))
+ }
+ }
+
var pinnedIndexOffset: UInt16 = 0
if !view.hasLater, case .chatList = mode {
@@ -668,6 +791,14 @@ func chatListNodeEntriesForView(_ view: EngineChatList, state: ChatListNodeState
if displayArchiveIntro {
result.append(.ArchiveIntro(presentationData: state.presentationData))
+ } else if !contacts.isEmpty && !result.contains(where: { entry in
+ if case .PeerEntry = entry {
+ return true
+ } else {
+ return false
+ }
+ }) {
+ result.append(.EmptyIntro(presentationData: state.presentationData))
}
if let notice {
diff --git a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift
index be5aef5480..7e188fd8ca 100644
--- a/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift
+++ b/submodules/ChatListUI/Sources/Node/ChatListStorageInfoItem.swift
@@ -221,8 +221,7 @@ class ChatListStorageInfoItemNode: ItemListRevealOptionsItemNode {
switch item.notice {
case .chatFolderUpdates:
- //TODO:locallize
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Hide", icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.ChatList_HideAction, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
default:
strongSelf.setRevealOptions((left: [], right: []))
}
diff --git a/submodules/Display/Source/NavigationBar.swift b/submodules/Display/Source/NavigationBar.swift
index 234eb1e768..d033d54219 100644
--- a/submodules/Display/Source/NavigationBar.swift
+++ b/submodules/Display/Source/NavigationBar.swift
@@ -138,6 +138,7 @@ public final class NavigationBackgroundNode: ASDisplayNode {
private var _color: UIColor
private var enableBlur: Bool
+ private var enableSaturation: Bool
public var effectView: UIVisualEffectView?
private let backgroundNode: ASDisplayNode
@@ -152,9 +153,10 @@ public final class NavigationBackgroundNode: ASDisplayNode {
}
}
- public init(color: UIColor, enableBlur: Bool = true) {
+ public init(color: UIColor, enableBlur: Bool = true, enableSaturation: Bool = true) {
self._color = .clear
self.enableBlur = enableBlur
+ self.enableSaturation = enableSaturation
self.backgroundNode = ASDisplayNode()
@@ -195,10 +197,12 @@ public final class NavigationBackgroundNode: ASDisplayNode {
if let sublayer = effectView.layer.sublayers?[0], let filters = sublayer.filters {
sublayer.backgroundColor = nil
sublayer.isOpaque = false
- let allowedKeys: [String] = [
- "colorSaturate",
+ var allowedKeys: [String] = [
"gaussianBlur"
]
+ if self.enableSaturation {
+ allowedKeys.append("colorSaturate")
+ }
sublayer.filters = filters.filter { filter in
guard let filter = filter as? NSObject else {
return true
@@ -225,14 +229,16 @@ public final class NavigationBackgroundNode: ASDisplayNode {
}
}
- public func updateColor(color: UIColor, enableBlur: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) {
+ public func updateColor(color: UIColor, enableBlur: Bool? = nil, enableSaturation: Bool? = nil, forceKeepBlur: Bool = false, transition: ContainedViewLayoutTransition) {
let effectiveEnableBlur = enableBlur ?? self.enableBlur
-
- if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur {
+ let effectiveEnableSaturation = enableSaturation ?? self.enableSaturation
+
+ if self._color.isEqual(color) && self.enableBlur == effectiveEnableBlur && self.enableSaturation == effectiveEnableSaturation {
return
}
self._color = color
self.enableBlur = effectiveEnableBlur
+ self.enableSaturation = effectiveEnableSaturation
if sharedIsReduceTransparencyEnabled {
transition.updateBackgroundColor(node: self.backgroundNode, color: self._color.withAlphaComponent(1.0))
diff --git a/submodules/Display/Source/TooltipController.swift b/submodules/Display/Source/TooltipController.swift
index 1ab86d59ef..be5d52bff7 100644
--- a/submodules/Display/Source/TooltipController.swift
+++ b/submodules/Display/Source/TooltipController.swift
@@ -123,13 +123,14 @@ open class TooltipController: ViewController, StandalonePresentableController {
private var timeoutTimer: SwiftSignalKit.Timer?
private var padding: CGFloat
+ private var innerPadding: UIEdgeInsets
private var layout: ContainerViewLayout?
private var initialArrowOnBottom: Bool
public var dismissed: ((Bool) -> Void)?
- public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0) {
+ public init(content: TooltipControllerContent, baseFontSize: CGFloat, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissByTapOutsideSource: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false, arrowOnBottom: Bool = true, padding: CGFloat = 8.0, innerPadding: UIEdgeInsets = UIEdgeInsets()) {
self.content = content
self.baseFontSize = baseFontSize
self.timeout = timeout
@@ -138,6 +139,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate
self.initialArrowOnBottom = arrowOnBottom
self.padding = padding
+ self.innerPadding = innerPadding
super.init(navigationBarPresentationData: nil)
@@ -157,6 +159,7 @@ open class TooltipController: ViewController, StandalonePresentableController {
self?.dismiss(tappedInside: tappedInside)
}, dismissByTapOutside: self.dismissByTapOutside, dismissByTapOutsideSource: self.dismissByTapOutsideSource)
self.controllerNode.padding = self.padding
+ self.controllerNode.innerPadding = self.innerPadding
self.controllerNode.arrowOnBottom = self.initialArrowOnBottom
self.displayNodeDidLoad()
}
diff --git a/submodules/Display/Source/TooltipControllerNode.swift b/submodules/Display/Source/TooltipControllerNode.swift
index d044cbc5f6..7122720407 100644
--- a/submodules/Display/Source/TooltipControllerNode.swift
+++ b/submodules/Display/Source/TooltipControllerNode.swift
@@ -20,6 +20,7 @@ final class TooltipControllerNode: ASDisplayNode {
var arrowOnBottom: Bool = true
var padding: CGFloat = 8.0
+ var innerPadding: UIEdgeInsets = UIEdgeInsets()
private var dismissedByTouchOutside = false
private var dismissByTapOutsideSource = false
@@ -98,14 +99,14 @@ final class TooltipControllerNode: ASDisplayNode {
textSize.width = ceil(textSize.width / 2.0) * 2.0
textSize.height = ceil(textSize.height / 2.0) * 2.0
- contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0, height: textSize.height + 34.0)
+ contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0 + self.innerPadding.left + self.innerPadding.right, height: textSize.height + 34.0 + self.innerPadding.top + self.innerPadding.bottom)
- let textFrame = CGRect(origin: CGPoint(x: 6.0 + imageSizeWithInset.width, y: 17.0), size: textSize)
+ let textFrame = CGRect(origin: CGPoint(x: 6.0 + self.innerPadding.left + imageSizeWithInset.width, y: 17.0 + self.innerPadding.top), size: textSize)
if transition.isAnimated, textFrame.size != self.textNode.frame.size {
transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textFrame.minX - self.textNode.frame.minX, y: 0.0))
}
- let imageFrame = CGRect(origin: CGPoint(x: 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize)
+ let imageFrame = CGRect(origin: CGPoint(x: self.innerPadding.left + 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize)
self.imageNode.frame = imageFrame
self.textNode.frame = textFrame
}
diff --git a/submodules/GalleryUI/Sources/GalleryController.swift b/submodules/GalleryUI/Sources/GalleryController.swift
index b1fa7a8a98..5823fa9537 100644
--- a/submodules/GalleryUI/Sources/GalleryController.swift
+++ b/submodules/GalleryUI/Sources/GalleryController.swift
@@ -1211,7 +1211,9 @@ public class GalleryController: ViewController, StandalonePresentableController,
})
}
})
- self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction)
+
+ let disableTapNavigation = !(self.context.sharedContext.currentMediaDisplaySettings.with { $0 }.showNextMediaOnTap)
+ self.displayNode = GalleryControllerNode(controllerInteraction: controllerInteraction, disableTapNavigation: disableTapNavigation)
self.displayNodeDidLoad()
self.galleryNode.statusBar = self.statusBar
diff --git a/submodules/GalleryUI/Sources/GalleryPagerNode.swift b/submodules/GalleryUI/Sources/GalleryPagerNode.swift
index 07af78c66f..fbb87ce7c9 100644
--- a/submodules/GalleryUI/Sources/GalleryPagerNode.swift
+++ b/submodules/GalleryUI/Sources/GalleryPagerNode.swift
@@ -585,7 +585,7 @@ public final class GalleryPagerNode: ASDisplayNode, UIScrollViewDelegate, UIGest
let node = self.itemNodes[i]
transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: CGFloat(i) * self.scrollView.bounds.size.width + self.pageGap, y: 0.0), size: CGSize(width: self.scrollView.bounds.size.width - self.pageGap * 2.0, height: self.scrollView.bounds.size.height)))
- let screenFrame = node.convert(node.bounds, to: self.supernode)
+ let screenFrame = node.view.convert(node.view.bounds, to: self.view.superview)
node.screenFrameUpdated(screenFrame)
}
diff --git a/submodules/ImageBlur/Sources/ImageBlur.swift b/submodules/ImageBlur/Sources/ImageBlur.swift
index 05fb8da1be..1340bddc0b 100644
--- a/submodules/ImageBlur/Sources/ImageBlur.swift
+++ b/submodules/ImageBlur/Sources/ImageBlur.swift
@@ -39,6 +39,7 @@ public func blurredImage(_ image: UIImage, radius: CGFloat, iterations: Int = 3)
let source = CFDataGetBytePtr(providerData)
memcpy(inBuffer.data, source, bytes)
+
for _ in 0 ..< iterations {
vImageBoxConvolve_ARGB8888(&inBuffer, &outBuffer, tempData, 0, 0, boxSize, boxSize, nil, vImage_Flags(kvImageEdgeExtend))
diff --git a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift
index 4d4a17c61b..ad43b752f7 100644
--- a/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift
+++ b/submodules/InviteLinksUI/Sources/FolderInviteLinkListController.swift
@@ -170,7 +170,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case let .mainLinkHeader(text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .mainLink(link, isGenerating):
- return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: link != nil ? "Copy" : "Generate Invite Link", secondaryButtonTitle: link != nil ? "Share" : nil, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
+ return ItemListFolderInviteLinkItem(context: arguments.context, presentationData: presentationData, invite: link, count: 0, peers: [], displayButton: true, enableButton: !isGenerating, buttonTitle: presentationData.strings.FolderLinkScreen_LinkActionCopy, secondaryButtonTitle: link != nil ? presentationData.strings.FolderLinkScreen_LinkActionShare : nil, displayImporters: false, buttonColor: nil, sectionId: self.section, style: .blocks, copyAction: {
if let link {
arguments.copyLink(link.link)
}
@@ -196,7 +196,6 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
case let .peersInfo(text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section)
case let .peer(_, peer, isSelected, disabledReasonText):
- //TODO:localize
return ItemListPeerItem(
presentationData: presentationData,
dateTimeFormat: PresentationDateTimeFormat(),
@@ -204,7 +203,7 @@ private enum InviteLinksListEntry: ItemListNodeEntry {
context: arguments.context,
peer: peer,
presence: nil,
- text: .text(disabledReasonText ?? "you can invite others here", .secondary),
+ text: .text(disabledReasonText ?? presentationData.strings.FolderLinkScreen_LabelCanInvite, .secondary),
label: .none,
editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false),
switchValue: ItemListPeerItemSwitch(value: isSelected, style: .leftCheck, isEnabled: disabledReasonText == nil),
@@ -232,8 +231,6 @@ private func folderInviteLinkListControllerEntries(
) -> [InviteLinksListEntry] {
var entries: [InviteLinksListEntry] = []
- //TODO:localize
-
var infoString: String?
let chatCountString: String
let peersHeaderString: String
@@ -244,34 +241,26 @@ private func folderInviteLinkListControllerEntries(
var selectAllString: String?
if !canShareChats {
- infoString = "You can only share groups and channels in which you are allowed to create invite links."
- chatCountString = "There are no chats in this folder that you can share with others."
- peersHeaderString = "THESE CHATS CANNOT BE SHARED"
+ infoString = presentationData.strings.FolderLinkScreen_TitleDescriptionUnavailable
+ chatCountString = presentationData.strings.FolderLinkScreen_ChatCountHeaderUnavailable
+ peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderUnavailable
} else if state.selectedPeerIds.isEmpty {
- chatCountString = "Anyone with this link can add **\(title)** folder and the chats selected below."
- peersHeaderString = "CHATS"
+ chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionDeselected(title).string
+ peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeader
if allPeers.count > 1 {
- selectAllString = allSelected ? "DESELECT ALL" : "SELECT ALL"
- }
- } else if state.selectedPeerIds.count == 1 {
- chatCountString = "Anyone with this link can add **\(title)** folder and the 1 chat selected below."
- peersHeaderString = "1 CHAT SELECTED"
- if allPeers.count > 1 {
- selectAllString = allSelected ? "DESELECT ALL" : "SELECT ALL"
+ selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
}
} else {
- chatCountString = "Anyone with this link can add **\(title)** folder and the \(state.selectedPeerIds.count) chats selected below."
- peersHeaderString = "\(state.selectedPeerIds.count) CHATS SELECTED"
+ chatCountString = presentationData.strings.FolderLinkScreen_TitleDescriptionSelected(title, presentationData.strings.FolderLinkScreen_TitleDescriptionSelectedCount(Int32(state.selectedPeerIds.count))).string
+ peersHeaderString = presentationData.strings.FolderLinkScreen_ChatsSectionHeaderSelected(Int32(state.selectedPeerIds.count))
if allPeers.count > 1 {
- selectAllString = allSelected ? "DESELECT ALL" : "SELECT ALL"
+ selectAllString = allSelected ? presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionDeselectAll : presentationData.strings.FolderLinkScreen_ChatsSectionHeaderActionSelectAll
}
}
entries.append(.header(chatCountString))
- //TODO:localize
-
if canShareChats {
- entries.append(.mainLinkHeader("INVITE LINK"))
+ entries.append(.mainLinkHeader(presentationData.strings.FolderLinkScreen_LinkSectionHeader))
entries.append(.mainLink(link: state.currentLink, isGenerating: state.generatingLink))
}
@@ -290,12 +279,12 @@ private func folderInviteLinkListControllerEntries(
if !canShareLinkToPeer(peer: peer) {
if case let .user(user) = peer {
if user.botInfo != nil {
- disabledReasonText = "you can't share chats with bots"
+ disabledReasonText = presentationData.strings.FolderLinkScreen_LabelUnavailableBot
} else {
- disabledReasonText = "you can't share private chats"
+ disabledReasonText = presentationData.strings.FolderLinkScreen_LabelUnavailableUser
}
} else {
- disabledReasonText = "you can't invite others here"
+ disabledReasonText = presentationData.strings.FolderLinkScreen_LabelUnavailableGeneric
}
}
entries.append(.peer(index: entries.count, peer: peer, isSelected: state.selectedPeerIds.contains(peer.id), disabledReasonText: disabledReasonText))
@@ -420,15 +409,14 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = []
- //TODO:localize
- items.append(.action(ContextMenuActionItem(text: "Name Link", icon: { theme in
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.FolderLinkScreen_ContextActionNameLink, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Pencil"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
let state = stateValue.with({ $0 })
- let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: "Name This Link", titleFont: .bold, value: state.title ?? "", characterLimit: 32, apply: { value in
+ let promptController = promptController(sharedContext: context.sharedContext, updatedPresentationData: updatedPresentationData, text: presentationData.strings.FolderLinkScreen_NameLink_Title, titleFont: .bold, value: state.title ?? "", characterLimit: 32, apply: { value in
if let value {
updateState { state in
var state = state
@@ -481,6 +469,8 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
let contextController = ContextController(account: context.account, presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture)
presentInGlobalOverlayImpl?(contextController)
}, peerAction: { peer, isEnabled in
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+
if isEnabled {
var added = false
updateState { state in
@@ -503,17 +493,15 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
didDisplayAddPeerNotice = true
dismissTooltipsImpl?()
- //TODO:localize
- displayTooltipImpl?(.info(title: nil, text: "People who already used the invite link will be able to join newly added chats.", timeout: 8), true)
+ displayTooltipImpl?(.info(title: nil, text: presentationData.strings.FolderLinkScreen_ToastNewChatAdded, timeout: 8), true)
}
} else {
- //TODO:localize
let text: String
if case let .user(user) = peer {
if user.botInfo != nil {
- text = "You can't share chats with bots"
+ text = presentationData.strings.FolderLinkScreen_AlertTextUnavailableBot
} else {
- text = "You can't share private chats"
+ text = presentationData.strings.FolderLinkScreen_AlertTextUnavailableUser
}
} else {
var isGroup = true
@@ -523,15 +511,15 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
}
if isGroup {
if isPrivate {
- text = "You don't have the admin rights to share invite links to this private group."
+ text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePrivateGroup
} else {
- text = "You don't have the admin rights to share invite links to this group chat."
+ text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePublicGroup
}
} else {
if isPrivate {
- text = "You don't have the admin rights to share invite links to this private channel."
+ text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePrivateChannel
} else {
- text = "You don't have the admin rights to share invite links to this channel."
+ text = presentationData.strings.FolderLinkScreen_AlertTextUnavailablePublicChannel
}
}
}
@@ -605,12 +593,11 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
dismissTooltipsImpl?()
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- //TODO:localize
- presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: "An error occurred.", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
+ presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .info(title: nil, text: presentationData.strings.FolderLinkScreen_SaveUnknownError, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}, completed: {
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
linkUpdated(ExportedChatFolderLink(title: state.title ?? "", link: currentLink.link, peerIds: Array(state.selectedPeerIds), isRevoked: false))
- //TODO:localize
- displayTooltipImpl?(.info(title: nil, text: "Link updated", timeout: 3), false)
+ displayTooltipImpl?(.info(title: nil, text: presentationData.strings.FolderLinkScreen_ToastLinkUpdated, timeout: 3), false)
dismissImpl?()
}))
@@ -666,10 +653,9 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
animateChanges = true
}
- //TODO:localize
let title: ItemListControllerTitle
- var folderTitle = "Share Folder"
+ var folderTitle = presentationData.strings.FolderLinkScreen_Title
if let title = state.title, !title.isEmpty {
folderTitle = title
}
@@ -742,13 +728,12 @@ public func folderInviteLinkListController(context: AccountContext, updatedPrese
if hasChanges {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
- //TODO:localize
- presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: "Unsaved Changes", text: "You have changed the settings of this folder. Apply changes?", actions: [
- TextAlertAction(type: .genericAction, title: "Discard", action: {
+ presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.FolderLinkScreen_SaveAlertTitle, text: presentationData.strings.FolderLinkScreen_SaveAlertText, actions: [
+ TextAlertAction(type: .genericAction, title: presentationData.strings.FolderLinkScreen_SaveAlertActionDiscard, action: {
f()
dismissImpl?()
}),
- TextAlertAction(type: .defaultAction, title: state.selectedPeerIds.isEmpty ? "Continue" : "Apply", action: {
+ TextAlertAction(type: .defaultAction, title: state.selectedPeerIds.isEmpty ? presentationData.strings.FolderLinkScreen_SaveAlertActionApply : presentationData.strings.FolderLinkScreen_SaveAlertActionContinue, action: {
applyChangesImpl?()
})
]), nil)
diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift
index 794cb8df90..5596edb62b 100644
--- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift
+++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift
@@ -227,8 +227,7 @@ private enum InviteLinkViewEntry: Comparable, Identifiable {
case let .importer(_, _, dateTimeFormat, peer, date, joinedViaFolderLink, loading):
let dateString: String
if joinedViaFolderLink {
- //TODO:localize
- dateString = "joined via a folder invite link"
+ dateString = presentationData.strings.InviteLink_LabelJoinedViaFolder
} else {
dateString = stringForFullDate(timestamp: date, strings: presentationData.strings, dateTimeFormat: dateTimeFormat)
}
diff --git a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift
index 8173bb57a5..7e75b08bbe 100644
--- a/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift
+++ b/submodules/InviteLinksUI/Sources/ItemListFolderInviteLinkListItem.swift
@@ -308,12 +308,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
titleText = invite.title
}
- //TODO:localize
- if invite.peerIds.count == 1 {
- subtitleText = "includes 1 chat"
- } else {
- subtitleText = "includes \(invite.peerIds.count) chats"
- }
+ subtitleText = item.presentationData.strings.ChatListFilter_LinkLabelChatCount(Int32(invite.peerIds.count))
} else {
titleText = " "
subtitleText = " "
@@ -519,8 +514,7 @@ public class ItemListFolderInviteLinkListItemNode: ItemListRevealOptionsItemNode
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
if item.removeAction != nil {
- //TODO:localize
- strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: "Delete", icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
+ strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.presentationData.strings.ChatListFilter_LinkActionDelete, icon: .none, color: item.presentationData.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.presentationData.theme.list.itemDisclosureActions.destructive.foregroundColor)]))
} else {
strongSelf.setRevealOptions((left: [], right: []))
}
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h
index ab241977cb..ee08d0cb83 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/PGCameraCaptureSession.h
@@ -4,6 +4,7 @@
@class PGCameraMovieWriter;
@class PGRectangleDetector;
+@class SQueue;
@interface PGCameraCaptureSession : AVCaptureSession
@@ -65,4 +66,6 @@
+ (bool)_isZoomAvailableForDevice:(AVCaptureDevice *)device;
++ (SQueue *)cameraQueue;
+
@end
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h
index 8d2175553f..4d4030a97d 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGMediaPickerGalleryModel.h
@@ -49,6 +49,6 @@
- (instancetype)initWithContext:(id)context items:(NSArray *)items focusItem:(id)focusItem selectionContext:(TGMediaSelectionContext *)selectionContext editingContext:(TGMediaEditingContext *)editingContext hasCaptions:(bool)hasCaptions allowCaptionEntities:(bool)allowCaptionEntities hasTimer:(bool)hasTimer onlyCrop:(bool)onlyCrop inhibitDocumentCaptions:(bool)inhibitDocumentCaptions hasSelectionPanel:(bool)hasSelectionPanel hasCamera:(bool)hasCamera recipientName:(NSString *)recipientName;
- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab;
-- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots;
+- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots fromRect:(CGRect)fromRect;
@end
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h
index 533f54948b..c65a9552f9 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGModernGalleryController.h
@@ -48,6 +48,8 @@ typedef NS_ENUM(NSUInteger, TGModernGalleryScrollAnimationDirection) {
- (void)dismissWhenReady;
- (void)dismissWhenReadyAnimated:(bool)animated;
+- (void)setScrollViewHidden:(bool)hidden;
+
- (bool)isFullyOpaque;
@end
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h
index e82f3b5c6d..7e480079f4 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorController.h
@@ -23,7 +23,8 @@ typedef enum {
TGPhotoEditorControllerVideoIntent = (1 << 4),
TGPhotoEditorControllerForumAvatarIntent = (1 << 5),
TGPhotoEditorControllerSuggestedAvatarIntent = (1 << 6),
- TGPhotoEditorControllerSuggestingAvatarIntent = (1 << 7)
+ TGPhotoEditorControllerSuggestingAvatarIntent = (1 << 7),
+ TGPhotoEditorControllerWallpaperIntent = (1 << 8)
} TGPhotoEditorControllerIntent;
@interface TGPhotoEditorController : TGOverlayController
@@ -67,6 +68,8 @@ typedef enum {
@property (nonatomic, strong) UIView *entitiesView;
+@property (nonatomic, assign) bool ignoreCropForResult;
+
- (instancetype)initWithContext:(id)context item:(id)item intent:(TGPhotoEditorControllerIntent)intent adjustments:(id)adjustments caption:(NSAttributedString *)caption screenImage:(UIImage *)screenImage availableTabs:(TGPhotoEditorTab)availableTabs selectedTab:(TGPhotoEditorTab)selectedTab;
- (void)dismissEditor;
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h
index 948ccab2ff..86a85c0067 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoEditorUtils.h
@@ -46,6 +46,7 @@ CGSize TGPhotoThumbnailSizeForCurrentScreen();
CGSize TGPhotoEditorScreenImageMaxSize();
extern const CGSize TGPhotoEditorResultImageMaxSize;
+extern const CGSize TGPhotoEditorResultImageWallpaperMaxSize;
#ifdef __cplusplus
}
diff --git a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h
index 737380548d..6e5c165ac0 100644
--- a/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h
+++ b/submodules/LegacyComponents/PublicHeaders/LegacyComponents/TGPhotoVideoEditor.h
@@ -4,6 +4,8 @@
+ (void)presentWithContext:(id)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id)stickersContext transitionView:(UIView *)transitionView senderName:(NSString *)senderName didFinishWithImage:(void (^)(UIImage *image))didFinishWithImage didFinishWithVideo:(void (^)(UIImage *image, NSURL *url, TGVideoEditAdjustments *adjustments))didFinishWithVideo dismissed:(void (^)(void))dismissed;
-+ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
++ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
+
++ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item cropRect:(CGRect)cropRect adjustments:(id)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)())beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut;
@end
diff --git a/submodules/LegacyComponents/Sources/PGBlurTool.m b/submodules/LegacyComponents/Sources/PGBlurTool.m
index eedcb75f81..21cd6392b9 100644
--- a/submodules/LegacyComponents/Sources/PGBlurTool.m
+++ b/submodules/LegacyComponents/Sources/PGBlurTool.m
@@ -218,6 +218,11 @@
return false;
}
+- (bool)isRegional
+{
+ return true;
+}
+
- (bool)isAvialableForVideo
{
return false;
diff --git a/submodules/LegacyComponents/Sources/PGCamera.m b/submodules/LegacyComponents/Sources/PGCamera.m
index af30b63446..446282eb11 100644
--- a/submodules/LegacyComponents/Sources/PGCamera.m
+++ b/submodules/LegacyComponents/Sources/PGCamera.m
@@ -120,7 +120,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
TGLegacyLog(@"ERROR: Camera runtime error: %@", notification.userInfo[AVCaptureSessionErrorKey]);
__weak PGCamera *weakSelf = self;
- TGDispatchAfter(1.5f, [PGCamera cameraQueue]._dispatch_queue, ^
+ TGDispatchAfter(1.5f, [PGCameraCaptureSession cameraQueue]._dispatch_queue, ^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_invalidated)
@@ -198,7 +198,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
[previewView setupWithCamera:self];
__weak PGCamera *weakSelf = self;
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
__strong PGCamera *strongSelf = weakSelf;
if (strongSelf == nil || strongSelf->_invalidated)
@@ -225,7 +225,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
if (_invalidated)
return;
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.captureSession.isRunning)
return;
@@ -261,10 +261,11 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
TGLegacyLog(@"Camera: stop capture");
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (_invalidated)
{
+#if !TARGET_IPHONE_SIMULATOR
[self.captureSession beginConfiguration];
[self.captureSession resetFlashMode];
@@ -279,16 +280,21 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
for (AVCaptureOutput *output in self.captureSession.outputs)
[self.captureSession removeOutput:output];
-#if !TARGET_IPHONE_SIMULATOR
[self.captureSession commitConfiguration];
#endif
}
TGLegacyLog(@"Camera: stop running");
#if !TARGET_IPHONE_SIMULATOR
- [self.captureSession stopRunning];
+ @try {
+ [self.captureSession stopRunning];
+ } @catch (NSException *exception) {
+ TGLegacyLog(@"Camera: caught exception – %@", exception.description);
+ [self.captureSession commitConfiguration];
+ [self.captureSession stopRunning];
+ TGLegacyLog(@"Camera: seems to be successfully resolved");
+ }
#endif
-
_capturing = false;
TGDispatchOnMainThread(^
@@ -328,9 +334,9 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
};
if (synchronous)
- [[PGCamera cameraQueue] dispatchSync:block];
+ [[PGCameraCaptureSession cameraQueue] dispatchSync:block];
else
- [[PGCamera cameraQueue] dispatch:block];
+ [[PGCameraCaptureSession cameraQueue] dispatch:block];
}
#pragma mark -
@@ -361,7 +367,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
{
bool videoMirrored = !self.disableResultMirroring ? _previewView.captureConnection.videoMirrored : false;
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (!self.captureSession.isRunning || self.captureSession.imageOutput.isCapturingStillImage || _invalidated)
return;
@@ -410,13 +416,13 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
if (CFAbsoluteTimeGetCurrent() - _captureStartTime > 0.4)
takePhoto();
else
- TGDispatchAfter(0.4 - delta, [[PGCamera cameraQueue] _dispatch_queue], takePhoto);
+ TGDispatchAfter(0.4 - delta, [[PGCameraCaptureSession cameraQueue] _dispatch_queue], takePhoto);
}];
}
- (void)startVideoRecordingForMoment:(bool)moment completion:(void (^)(NSURL *, CGAffineTransform transform, CGSize dimensions, NSTimeInterval duration, bool success))completion
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (!self.captureSession.isRunning || _invalidated)
return;
@@ -443,7 +449,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
if (CFAbsoluteTimeGetCurrent() - _captureStartTime > 1.5)
startRecording();
else
- TGDispatchAfter(1.5, [[PGCamera cameraQueue] _dispatch_queue], startRecording);
+ TGDispatchAfter(1.5, [[PGCameraCaptureSession cameraQueue] _dispatch_queue], startRecording);
TGDispatchOnMainThread(^
{
@@ -455,7 +461,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
- (void)stopVideoRecording
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
[self.captureSession stopVideoRecording];
@@ -496,7 +502,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
if (strongSelf == nil)
return;
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
strongSelf.captureSession.currentMode = cameraMode;
@@ -584,7 +590,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
- (void)_setFocusPoint:(CGPoint)point focusMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode monitorSubjectAreaChange:(bool)monitorSubjectAreaChange
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
@@ -600,7 +606,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
- (void)beginExposureTargetBiasChange
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
@@ -611,7 +617,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
- (void)setExposureTargetBias:(CGFloat)bias
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
@@ -622,7 +628,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
- (void)endExposureTargetBiasChange
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
@@ -661,7 +667,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
- (void)setFlashMode:(PGCameraFlashMode)flashMode
{
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
self.captureSession.currentFlashMode = flashMode;
}];
@@ -689,7 +695,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
if (strongSelf == nil)
return;
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
[strongSelf.captureSession setCurrentCameraPosition:targetCameraPosition];
@@ -744,7 +750,7 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
if (self.cameraMode == PGCameraModeVideo) {
animated = false;
}
- [[PGCamera cameraQueue] dispatch:^
+ [[PGCameraCaptureSession cameraQueue] dispatch:^
{
if (self.disabled)
return;
@@ -786,18 +792,6 @@ NSString *const PGCameraAdjustingFocusKey = @"adjustingFocus";
return ([PGCameraCaptureSession _deviceWithCameraPosition:PGCameraPositionFront] != nil);
}
-+ (SQueue *)cameraQueue
-{
- static dispatch_once_t onceToken;
- static SQueue *queue = nil;
- dispatch_once(&onceToken, ^
- {
- queue = [[SQueue alloc] init];
- });
-
- return queue;
-}
-
+ (AVCaptureVideoOrientation)_videoOrientationForInterfaceOrientation:(UIInterfaceOrientation)deviceOrientation mirrored:(bool)mirrored
{
switch (deviceOrientation)
diff --git a/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m b/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m
index 7d8814b37b..e071d81116 100644
--- a/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m
+++ b/submodules/LegacyComponents/Sources/PGCameraCaptureSession.m
@@ -208,6 +208,7 @@ const NSInteger PGCameraFrameRate = 30;
- (void)reset
{
+ NSAssert([[PGCameraCaptureSession cameraQueue] isCurrentQueue], @"[[PGCameraCaptureSession cameraQueue] isCurrentQueue]");
[self beginConfiguration];
[self _removeAudioInputEndAudioSession:true];
@@ -259,6 +260,8 @@ const NSInteger PGCameraFrameRate = 30;
- (void)setCurrentMode:(PGCameraMode)mode
{
+ NSAssert([[PGCameraCaptureSession cameraQueue] isCurrentQueue], @"[[PGCameraCaptureSession cameraQueue] isCurrentQueue]");
+
_currentMode = mode;
[self beginConfiguration];
@@ -804,6 +807,7 @@ const NSInteger PGCameraFrameRate = 30;
- (void)setCurrentCameraPosition:(PGCameraPosition)position
{
+ NSAssert([[PGCameraCaptureSession cameraQueue] isCurrentQueue], @"[[PGCameraCaptureSession cameraQueue] isCurrentQueue]");
AVCaptureDevice *deviceForTargetPosition = [PGCameraCaptureSession _deviceWithCameraPosition:position];
if ([_videoDevice isEqual:deviceForTargetPosition])
return;
@@ -1123,4 +1127,18 @@ static UIImageOrientation TGSnapshotOrientationForVideoOrientation(bool mirrored
}
}
+#pragma mark -
+
++ (SQueue *)cameraQueue
+{
+ static dispatch_once_t onceToken;
+ static SQueue *queue = nil;
+ dispatch_once(&onceToken, ^
+ {
+ queue = [[SQueue alloc] init];
+ });
+
+ return queue;
+}
+
@end
diff --git a/submodules/LegacyComponents/Sources/PGPhotoTool.h b/submodules/LegacyComponents/Sources/PGPhotoTool.h
index 7e2ec58156..474a17a514 100644
--- a/submodules/LegacyComponents/Sources/PGPhotoTool.h
+++ b/submodules/LegacyComponents/Sources/PGPhotoTool.h
@@ -35,6 +35,7 @@ typedef enum
@property (nonatomic, readonly) NSInteger order;
@property (nonatomic, readonly) bool isHidden;
+@property (nonatomic, readonly) bool isRegional;
@property (nonatomic, readonly) NSString *shaderString;
@property (nonatomic, readonly) NSString *ancillaryShaderString;
diff --git a/submodules/LegacyComponents/Sources/PGPhotoTool.m b/submodules/LegacyComponents/Sources/PGPhotoTool.m
index 1c8a0263be..d8dba44ec7 100644
--- a/submodules/LegacyComponents/Sources/PGPhotoTool.m
+++ b/submodules/LegacyComponents/Sources/PGPhotoTool.m
@@ -38,6 +38,11 @@
return true;
}
+- (bool)isRegional
+{
+ return false;
+}
+
- (bool)isAvialableForVideo
{
return true;
diff --git a/submodules/LegacyComponents/Sources/PGVignetteTool.m b/submodules/LegacyComponents/Sources/PGVignetteTool.m
index 3e64b56e5b..dc307df873 100644
--- a/submodules/LegacyComponents/Sources/PGVignetteTool.m
+++ b/submodules/LegacyComponents/Sources/PGVignetteTool.m
@@ -38,6 +38,11 @@
return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON);
}
+- (bool)isRegional
+{
+ return true;
+}
+
- (NSArray *)parameters
{
if (!_parameters)
diff --git a/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m b/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m
index 445a794977..dc6440e395 100644
--- a/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m
+++ b/submodules/LegacyComponents/Sources/TGCameraCapturedPhoto.m
@@ -81,9 +81,6 @@
[[NSFileManager defaultManager] removeItemAtPath:[self filePath] error:nil];
}
-#define PGTick NSDate *startTime = [NSDate date]
-#define PGTock NSLog(@"!=========== %s Time: %f", __func__, -[startTime timeIntervalSinceNow])
-
- (void)_saveToDisk:(UIImage *)image
{
if (image == nil)
diff --git a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m
index 53ebd775e0..c786c20090 100644
--- a/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m
+++ b/submodules/LegacyComponents/Sources/TGMediaPickerGalleryModel.m
@@ -338,10 +338,10 @@
- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab
{
- [self presentPhotoEditorForItem:item tab:tab snapshots:@[]];
+ [self presentPhotoEditorForItem:item tab:tab snapshots:@[] fromRect:CGRectZero];
}
-- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots
+- (void)presentPhotoEditorForItem:(id)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots fromRect:(CGRect)fromRect
{
__weak TGMediaPickerGalleryModel *weakSelf = self;
@@ -356,12 +356,15 @@
CGRect refFrame = CGRectZero;
UIView *editorReferenceView = [self referenceViewForItem:item frame:&refFrame];
+ if (!CGRectEqualToRect(fromRect, CGRectZero)) {
+ refFrame = fromRect;
+ }
UIView *referenceView = nil;
UIImage *screenImage = nil;
UIView *referenceParentView = nil;
UIImage *image = nil;
- UIView *entitiesView = nil;
+ UIView *entitiesView = nil;
id editableMediaItem = item.editableMediaItem;
diff --git a/submodules/LegacyComponents/Sources/TGModernGalleryController.m b/submodules/LegacyComponents/Sources/TGModernGalleryController.m
index 6596d5cf3f..e38b44e82d 100644
--- a/submodules/LegacyComponents/Sources/TGModernGalleryController.m
+++ b/submodules/LegacyComponents/Sources/TGModernGalleryController.m
@@ -166,6 +166,12 @@ static void adjustFrameRate(CAAnimation *animation) {
[self dismissWhenReadyAnimated:animated force:false];
}
+- (void)setScrollViewHidden:(bool)hidden {
+ TGDispatchAfter(0.01, dispatch_get_main_queue(), ^{
+ _view.scrollView.hidden = hidden;
+ });
+}
+
- (void)dismissWhenReadyAnimated:(bool)animated force:(bool)force
{
if (animated) {
diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
index b9b5dc374d..7b296a5ff6 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoEditorController.m
@@ -982,14 +982,23 @@
bool hasImageAdjustments = editorValues.toolsApplied || saveOnly;
bool hasPainting = editorValues.hasPainting;
bool hasAnimation = editorValues.paintingData.hasAnimation;
+ bool ignoreCropForResult = self.ignoreCropForResult;
SSignal *(^imageCropSignal)(UIImage *, bool) = ^(UIImage *image, bool resize)
{
return [[SSignal alloc] initWithGenerator:^id(SSubscriber *subscriber)
{
- UIImage *paintingImage = !hasImageAdjustments ? editorValues.paintingData.image : nil;
- UIImage *croppedImage = TGPhotoEditorCrop(image, paintingImage, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropRect, photoEditor.cropMirrored, TGPhotoEditorResultImageMaxSize, photoEditor.originalSize, resize);
- [subscriber putNext:croppedImage];
+ if (ignoreCropForResult) {
+ if (image.size.width > TGPhotoEditorResultImageWallpaperMaxSize.width || image.size.height > TGPhotoEditorResultImageWallpaperMaxSize.width) {
+ [subscriber putNext:TGPhotoEditorFitImage(image, TGPhotoEditorResultImageWallpaperMaxSize)];
+ } else {
+ [subscriber putNext:image];
+ }
+ } else {
+ UIImage *paintingImage = !hasImageAdjustments ? editorValues.paintingData.image : nil;
+ UIImage *croppedImage = TGPhotoEditorCrop(image, paintingImage, photoEditor.cropOrientation, photoEditor.cropRotation, photoEditor.cropRect, photoEditor.cropMirrored, TGPhotoEditorResultImageMaxSize, photoEditor.originalSize, resize);
+ [subscriber putNext:croppedImage];
+ }
[subscriber putCompletion];
return nil;
@@ -1045,12 +1054,18 @@
void (^didFinishRenderingFullSizeImage)(UIImage *) = self.didFinishRenderingFullSizeImage;
void (^didFinishEditing)(id, UIImage *, UIImage *, bool , void(^)(void)) = self.didFinishEditing;
+ TGPhotoEditorControllerIntent intent = _intent;
[[[[renderedImageSignal map:^id(UIImage *image)
{
if (!hasImageAdjustments)
{
- if (hasPainting && !hasAnimation && didFinishRenderingFullSizeImage != nil)
- didFinishRenderingFullSizeImage(image);
+ if (didFinishRenderingFullSizeImage != nil) {
+ if (hasPainting && !hasAnimation) {
+ didFinishRenderingFullSizeImage(image);
+ } else if (intent == TGPhotoEditorControllerWallpaperIntent) {
+ didFinishRenderingFullSizeImage(nil);
+ }
+ }
return image;
}
@@ -1155,6 +1170,13 @@
_portraitToolbarView.alpha = 1.0f;
_landscapeToolbarView.alpha = 1.0f;
} completion:nil];
+
+ if (_intent == TGPhotoEditorControllerWallpaperIntent) {
+ [UIView animateWithDuration:0.25f delay:0.15 options:UIViewAnimationOptionCurveLinear animations:^
+ {
+ _backgroundView.alpha = 1.0f;
+ } completion:nil];
+ }
}
- (void)transitionOutSaving:(bool)saving completion:(void (^)(void))completion
@@ -1170,6 +1192,13 @@
_landscapeToolbarView.alpha = 0.0f;
}];
+ if (_intent == TGPhotoEditorControllerWallpaperIntent) {
+ [UIView animateWithDuration:0.1f animations:^
+ {
+ _backgroundView.alpha = 0.0f;
+ }];
+ }
+
_currentTabController.beginTransitionOut = self.beginTransitionOut;
[self setToolbarHidden:false animated:true];
diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m b/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m
index 81a052f13d..9410ea9a1c 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoEditorTabController.m
@@ -28,6 +28,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
UIView *_transitionInReferenceView;
UIView *_transitionInParentView;
CGRect _transitionTargetFrame;
+
+ UIView *_upperTransitionView;
+ CGRect _upperTransitionTargetFrame;
}
@end
@@ -114,6 +117,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
_dismissing = false;
CGRect targetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame];
+ if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ targetFrame = [self.view convertRect:targetFrame toView: parentView];
+ }
if (_CGRectEqualToRectWithEpsilon(targetFrame, referenceFrame, FLT_EPSILON))
{
@@ -157,12 +163,20 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
_transitionView = referenceView;
}
transitionViewSuperview = parentView;
+
+ if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ _upperTransitionView = [referenceView snapshotViewAfterScreenUpdates:false];
+ _upperTransitionView.alpha = 0.0;
+ _upperTransitionView.frame = [parentView convertRect:referenceFrame toView:self.view];
+ _upperTransitionTargetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame];
+ [self.view insertSubview:_upperTransitionView atIndex:0];
+ }
}
_transitionView.hidden = false;
_transitionView.frame = referenceFrame;
- _transitionTargetFrame = [self _targetFrameForTransitionInFromFrame:referenceFrame];
+ _transitionTargetFrame = targetFrame;
[transitionViewSuperview addSubview:_transitionView];
}
@@ -174,6 +188,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
_transitionInProgress = true;
CGAffineTransform initialTransform = _transitionView.transform;
+ [UIView animateWithDuration:0.25 animations:^{
+ _upperTransitionView.alpha = 1.0;
+ }];
[UIView animateWithDuration:0.3f delay:0.0f options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionLayoutSubviews animations:^
{
if (_animateScale) {
@@ -182,6 +199,7 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
_transitionView.transform = CGAffineTransformScale(initialTransform, scale, scale);
} else {
_transitionView.frame = _transitionTargetFrame;
+ _upperTransitionView.frame = _upperTransitionTargetFrame;
}
} completion:^(BOOL finished) {
_transitionInProgress = false;
@@ -201,6 +219,8 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
}
[self _finishedTransitionInWithView:transitionView];
+ [_upperTransitionView removeFromSuperview];
+ _upperTransitionView = nil;
}];
}
@@ -275,7 +295,11 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
if (saving)
{
- [self _animatePreviewViewTransitionOutToFrame:CGRectNull saving:saving parentView:parentView completion:^
+ CGRect targetFrame = CGRectNull;
+ if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ targetFrame = referenceFrame;
+ }
+ [self _animatePreviewViewTransitionOutToFrame:targetFrame saving:saving parentView:parentView completion:^
{
if (completion != nil)
completion();
@@ -316,6 +340,9 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
orientation = UIInterfaceOrientationPortrait;
CGRect sourceFrame = [self transitionOutSourceFrameForReferenceFrame:referenceView.frame orientation:orientation];
+ if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ sourceFrame = [self.view convertRect:sourceFrame toView: parentView];
+ }
CGRect targetFrame = referenceFrame;
toTransitionView.frame = sourceFrame;
@@ -334,7 +361,12 @@ const CGFloat TGPhotoEditorToolbarSize = 49.0f;
};
[animations addObject:@1];
- [self _animatePreviewViewTransitionOutToFrame:targetFrame saving:saving parentView:nil completion:^
+
+ CGRect previewTargetFrame = targetFrame;
+ if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ previewTargetFrame = [referenceView convertRect:referenceView.bounds toView:self.view];
+ }
+ [self _animatePreviewViewTransitionOutToFrame:previewTargetFrame saving:saving parentView:nil completion:^
{
onAnimationCompletion(@1);
}];
diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m
index c2dfab89a4..949bb0b510 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoEditorUtils.m
@@ -7,6 +7,7 @@
#import
const CGSize TGPhotoEditorResultImageMaxSize = { 1280, 1280 };
+const CGSize TGPhotoEditorResultImageWallpaperMaxSize = { 2048, 2048 };
const CGSize TGPhotoEditorScreenImageHardLimitSize = { 1280, 1280 };
const CGSize TGPhotoEditorScreenImageHardLimitLegacySize = { 750, 750 };
diff --git a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m
index f66777e0ae..1dfec01a9e 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoToolsController.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoToolsController.m
@@ -150,6 +150,9 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
}
if (!tool.isHidden)
{
+ if (tool.isRegional && self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ continue;
+ }
[tools addObject:tool];
if (tool.isSimple)
[simpleTools addObject:tool];
@@ -451,24 +454,40 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
UIView *snapshotView = nil;
POPSpringAnimation *snapshotAnimation = nil;
- if (saving && CGRectIsNull(targetFrame) && parentView != nil)
+ if (saving && parentView != nil)
{
- snapshotView = [previewView snapshotViewAfterScreenUpdates:false];
- snapshotView.frame = previewView.frame;
-
- CGSize fittedSize = TGScaleToSize(previewView.frame.size, self.view.frame.size);
- targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2, (self.view.frame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);
-
- [parentView addSubview:snapshotView];
-
- snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
- snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame];
- snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame];
+ if (CGRectIsNull(targetFrame)) {
+ snapshotView = [previewView snapshotViewAfterScreenUpdates:false];
+ snapshotView.frame = previewView.frame;
+
+ CGSize fittedSize = TGScaleToSize(previewView.frame.size, self.view.frame.size);
+ targetFrame = CGRectMake((self.view.frame.size.width - fittedSize.width) / 2, (self.view.frame.size.height - fittedSize.height) / 2, fittedSize.width, fittedSize.height);
+
+ [parentView addSubview:snapshotView];
+
+ snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
+ snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame];
+ snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame];
+ } else if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ snapshotView = [previewView snapshotViewAfterScreenUpdates:false];
+ snapshotView.frame = [self.view convertRect:previewView.frame toView:parentView];
+
+ [parentView addSubview:snapshotView];
+
+ snapshotAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
+ snapshotAnimation.fromValue = [NSValue valueWithCGRect:snapshotView.frame];
+ snapshotAnimation.toValue = [NSValue valueWithCGRect:targetFrame];
+ }
+ }
+
+ CGRect previewTargetFrame = targetFrame;
+ if (self.intent == TGPhotoEditorControllerWallpaperIntent && saving) {
+ previewTargetFrame = [parentView convertRect:targetFrame toView:self.view];
}
POPSpringAnimation *previewAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewFrame];
previewAnimation.fromValue = [NSValue valueWithCGRect:previewView.frame];
- previewAnimation.toValue = [NSValue valueWithCGRect:targetFrame];
+ previewAnimation.toValue = [NSValue valueWithCGRect:previewTargetFrame];
POPSpringAnimation *previewAlphaAnimation = [TGPhotoEditorAnimation prepareTransitionAnimationForPropertyNamed:kPOPViewAlpha];
previewAlphaAnimation.fromValue = @(previewView.alpha);
@@ -1020,6 +1039,8 @@ const CGFloat TGPhotoEditorToolsLandscapePanelSize = TGPhotoEditorToolsPanelSize
{
if (self.photoEditor.forVideo) {
return TGPhotoEditorToolsTab | TGPhotoEditorTintTab | TGPhotoEditorCurvesTab;
+ } else if (self.intent == TGPhotoEditorControllerWallpaperIntent) {
+ return TGPhotoEditorToolsTab | TGPhotoEditorTintTab | TGPhotoEditorCurvesTab;
} else {
return TGPhotoEditorToolsTab | TGPhotoEditorTintTab | TGPhotoEditorBlurTab | TGPhotoEditorCurvesTab;
}
diff --git a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m
index 38e1c6ebdc..537beec6c3 100644
--- a/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m
+++ b/submodules/LegacyComponents/Sources/TGPhotoVideoEditor.m
@@ -154,7 +154,7 @@
}
}
-+ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
++ (void)presentWithContext:(id)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
{
id windowManager = [context makeOverlayWindowManager];
id windowContext = [windowManager context];
@@ -201,6 +201,11 @@
[editingContext setCaption:caption forItem:editableItem];
};
+ model.didFinishRenderingFullSizeImage = ^(id editableItem, UIImage *resultImage)
+ {
+ [editingContext setFullSizeImage:resultImage forItem:editableItem];
+ };
+
model.interfaceView.hasSwipeGesture = false;
galleryController.model = model;
@@ -255,10 +260,10 @@
}
};
- if (paint) {
+ if (paint || adjustments) {
[model.interfaceView immediateEditorTransitionIn];
}
-
+
for (UIView *view in snapshots) {
[galleryController.view addSubview:view];
}
@@ -269,9 +274,235 @@
if (paint) {
TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{
- [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots];
+ [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots fromRect:fromRect];
+ });
+ } else if (adjustments) {
+ TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{
+ [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorToolsTab snapshots:snapshots fromRect:fromRect];
});
}
}
++ (void)presentEditorWithContext:(id)context controller:(TGViewController *)controller withItem:(id)item cropRect:(CGRect)cropRect adjustments:(id)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)())beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut;
+{
+ id windowManager = [context makeOverlayWindowManager];
+
+ TGMediaEditingContext *editingContext = [[TGMediaEditingContext alloc] init];
+
+ UIImage *thumbnailImage;
+
+ NSDictionary *toolValues;
+ if (adjustments != nil) {
+ toolValues = adjustments.toolValues;
+ } else {
+ toolValues = @{};
+ }
+ PGPhotoEditorValues *editorValues = [PGPhotoEditorValues editorValuesWithOriginalSize:item.originalSize cropRect:cropRect cropRotation:0.0f cropOrientation:UIImageOrientationUp cropLockedAspectRatio:0.0 cropMirrored:false toolValues:toolValues paintingData:nil sendAsGif:false];
+
+ TGPhotoEditorController *editorController = [[TGPhotoEditorController alloc] initWithContext:[windowManager context] item:item intent:TGPhotoEditorControllerWallpaperIntent adjustments:editorValues caption:nil screenImage:thumbnailImage availableTabs:TGPhotoEditorToolsTab selectedTab:TGPhotoEditorToolsTab];
+ editorController.editingContext = editingContext;
+ editorController.dontHideStatusBar = true;
+ editorController.ignoreCropForResult = true;
+
+ CGRect fromRect = referenceView.frame;// [referenceView convertRect:referenceView.bounds toView:nil];
+ editorController.beginTransitionIn = ^UIView *(CGRect *referenceFrame, UIView **parentView)
+ {
+ *referenceFrame = fromRect;
+ *parentView = referenceView.superview;
+ //UIImageView *imageView = [[UIImageView alloc] initWithFrame:fromRect];
+ //imageView.image = image;
+
+ return referenceView;
+ };
+
+ editorController.beginTransitionOut = ^UIView *(CGRect *referenceFrame, UIView **parentView)
+ {
+ CGRect startFrame = CGRectZero;
+ if (referenceFrame != NULL)
+ {
+ startFrame = *referenceFrame;
+ *referenceFrame = fromRect;
+ *parentView = referenceView.superview;
+ }
+
+ if (beginTransitionOut) {
+ beginTransitionOut();
+ }
+
+ return referenceView;
+ };
+
+ __weak TGPhotoEditorController *weakController = editorController;
+ editorController.finishedTransitionOut = ^(bool saved) {
+ TGPhotoEditorController *strongGalleryController = weakController;
+ if (strongGalleryController != nil && strongGalleryController.overlayWindow == nil)
+ {
+ TGNavigationController *navigationController = (TGNavigationController *)strongGalleryController.navigationController;
+ TGOverlayControllerWindow *window = (TGOverlayControllerWindow *)navigationController.view.window;
+ if ([window isKindOfClass:[TGOverlayControllerWindow class]])
+ [window dismiss];
+ }
+ if (finishTransitionOut) {
+ finishTransitionOut();
+ }
+ };
+
+ editorController.didFinishRenderingFullSizeImage = ^(UIImage *resultImage)
+ {
+ fullSizeCompletion(resultImage);
+ };
+
+ editorController.didFinishEditing = ^(id adjustments, UIImage *resultImage, __unused UIImage *thumbnailImage, __unused bool hasChanges, void(^commit)(void))
+ {
+ if (!hasChanges)
+ return;
+
+ __strong TGPhotoEditorController *strongController = weakController;
+ if (strongController == nil)
+ return;
+
+ completion(resultImage, adjustments);
+
+ };
+ editorController.requestThumbnailImage = ^(id editableItem)
+ {
+ return [editableItem thumbnailImageSignal];
+ };
+
+ editorController.requestOriginalScreenSizeImage = ^(id editableItem, NSTimeInterval position)
+ {
+ return [editableItem screenImageSignal:position];
+ };
+
+ editorController.requestOriginalFullSizeImage = ^(id editableItem, NSTimeInterval position)
+ {
+ return [editableItem originalImageSignal:position];
+ };
+
+ TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:editorController];
+ controllerWindow.hidden = false;
+ controller.view.clipsToBounds = true;
+
+// TGModernGalleryController *galleryController = [[TGModernGalleryController alloc] initWithContext:windowContext];
+// galleryController.adjustsStatusBarVisibility = true;
+// galleryController.animateTransition = false;
+// galleryController.finishedTransitionIn = ^(id item, TGModernGalleryItemView *itemView) {
+// appeared();
+// };
+// //galleryController.hasFadeOutTransition = true;
+//
+// id galleryItem = nil;
+// if (item.isVideo)
+// galleryItem = [[TGMediaPickerGalleryVideoItem alloc] initWithAsset:item];
+// else
+// galleryItem = [[TGMediaPickerGalleryPhotoItem alloc] initWithAsset:item];
+// galleryItem.editingContext = editingContext;
+// galleryItem.stickersContext = stickersContext;
+//
+// TGMediaPickerGalleryModel *model = [[TGMediaPickerGalleryModel alloc] initWithContext:windowContext items:@[galleryItem] focusItem:galleryItem selectionContext:nil editingContext:editingContext hasCaptions:true allowCaptionEntities:true hasTimer:false onlyCrop:false inhibitDocumentCaptions:false hasSelectionPanel:false hasCamera:false recipientName:recipientName];
+// model.controller = galleryController;
+// model.stickersContext = stickersContext;
+//
+// model.willFinishEditingItem = ^(id editableItem, id adjustments, id representation, bool hasChanges)
+// {
+// if (hasChanges)
+// {
+// [editingContext setAdjustments:adjustments forItem:editableItem];
+// [editingContext setTemporaryRep:representation forItem:editableItem];
+// }
+// };
+//
+// model.didFinishEditingItem = ^(id editableItem, __unused id adjustments, UIImage *resultImage, UIImage *thumbnailImage)
+// {
+// [editingContext setImage:resultImage thumbnailImage:thumbnailImage forItem:editableItem synchronous:false];
+// };
+//
+// model.saveItemCaption = ^(id editableItem, NSAttributedString *caption)
+// {
+// [editingContext setCaption:caption forItem:editableItem];
+// };
+//
+// model.didFinishRenderingFullSizeImage = ^(id editableItem, UIImage *resultImage)
+// {
+// [editingContext setFullSizeImage:resultImage forItem:editableItem];
+// };
+//
+// model.interfaceView.hasSwipeGesture = false;
+// galleryController.model = model;
+//
+// __weak TGModernGalleryController *weakGalleryController = galleryController;
+//
+// [model.interfaceView updateSelectionInterface:1 counterVisible:false animated:false];
+// model.interfaceView.thumbnailSignalForItem = ^SSignal *(id item)
+// {
+// return nil;
+// };
+// model.interfaceView.donePressed = ^(TGMediaPickerGalleryItem *item)
+// {
+// __strong TGModernGalleryController *strongController = weakGalleryController;
+// if (strongController == nil)
+// return;
+//
+// if ([item isKindOfClass:[TGMediaPickerGalleryVideoItem class]])
+// {
+// TGMediaPickerGalleryVideoItemView *itemView = (TGMediaPickerGalleryVideoItemView *)[strongController itemViewForItem:item];
+// [itemView stop];
+// [itemView setPlayButtonHidden:true animated:true];
+// }
+//
+// if (completion != nil)
+// completion(item.asset, editingContext);
+//
+// [strongController dismissWhenReadyAnimated:true];
+// };
+//
+// galleryController.beginTransitionIn = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
+// {
+// return nil;
+// };
+//
+// galleryController.beginTransitionOut = ^UIView *(__unused TGMediaPickerGalleryItem *item, __unused TGModernGalleryItemView *itemView)
+// {
+// return nil;
+// };
+//
+// galleryController.completedTransitionOut = ^
+// {
+// TGModernGalleryController *strongGalleryController = weakGalleryController;
+// if (strongGalleryController != nil && strongGalleryController.overlayWindow == nil)
+// {
+// TGNavigationController *navigationController = (TGNavigationController *)strongGalleryController.navigationController;
+// TGOverlayControllerWindow *window = (TGOverlayControllerWindow *)navigationController.view.window;
+// if ([window isKindOfClass:[TGOverlayControllerWindow class]])
+// [window dismiss];
+// }
+// if (dismissed) {
+// dismissed();
+// }
+// };
+//
+// if (paint || adjustments) {
+// [model.interfaceView immediateEditorTransitionIn];
+// }
+//
+// for (UIView *view in snapshots) {
+// [galleryController.view addSubview:view];
+// }
+//
+// TGOverlayControllerWindow *controllerWindow = [[TGOverlayControllerWindow alloc] initWithManager:windowManager parentController:controller contentController:galleryController];
+// controllerWindow.hidden = false;
+// galleryController.view.clipsToBounds = true;
+//
+// if (paint) {
+// TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{
+// [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorPaintTab snapshots:snapshots fromRect:fromRect];
+// });
+// } else if (adjustments) {
+// TGDispatchAfter(0.05, dispatch_get_main_queue(), ^{
+// [model presentPhotoEditorForItem:galleryItem tab:TGPhotoEditorToolsTab snapshots:snapshots fromRect:fromRect];
+// });
+// }
+}
+
+
@end
diff --git a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift
index 8ea2a99219..9c5a507cd7 100644
--- a/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift
+++ b/submodules/LegacyMediaPickerUI/Sources/LegacyAttachmentMenu.swift
@@ -58,7 +58,47 @@ public enum LegacyAttachmentMenuMediaEditing {
case file
}
-public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) {
+public enum LegacyMediaEditorMode {
+ case draw
+ case adjustments
+}
+
+
+public func legacyWallpaperEditor(context: AccountContext, item: TGMediaEditableItem, cropRect: CGRect, adjustments: TGMediaEditAdjustments?, referenceView: UIView, beginTransitionOut: (() -> Void)?, finishTransitionOut: (() -> Void)?, completion: @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, fullSizeCompletion: @escaping (UIImage?) -> Void, present: @escaping (ViewController, Any?) -> Void) {
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ let legacyController = LegacyController(presentation: .custom, theme: presentationData.theme, initialLayout: nil)
+ legacyController.blocksBackgroundWhenInOverlay = true
+ legacyController.acceptsFocusWhenInOverlay = true
+ legacyController.statusBar.statusBarStyle = .Ignore
+ legacyController.controllerLoaded = { [weak legacyController] in
+ legacyController?.view.disablesInteractiveTransitionGestureRecognizer = true
+ }
+
+ let emptyController = LegacyEmptyController(context: legacyController.context)!
+ emptyController.navigationBarShouldBeHidden = true
+ let navigationController = makeLegacyNavigationController(rootController: emptyController)
+ navigationController.setNavigationBarHidden(true, animated: false)
+ legacyController.bind(controller: navigationController)
+
+ legacyController.enableSizeClassSignal = true
+
+ present(legacyController, nil)
+
+ TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, with: item, cropRect: cropRect, adjustments: adjustments, referenceView: referenceView, completion: { image, adjustments in
+ completion(image, adjustments)
+ }, fullSizeCompletion: { image in
+ Queue.mainQueue().async {
+ fullSizeCompletion(image)
+ }
+ }, beginTransitionOut: {
+ beginTransitionOut?()
+ }, finishTransitionOut: { [weak legacyController] in
+ legacyController?.dismiss()
+ finishTransitionOut?()
+ })
+}
+
+public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle: String?, media: AnyMediaReference, mode: LegacyMediaEditorMode, initialCaption: NSAttributedString, snapshots: [UIView], transitionCompletion: (() -> Void)?, getCaptionPanelView: @escaping () -> TGCaptionPanelView?, sendMessagesWithSignals: @escaping ([Any]?, Bool, Int32) -> Void, present: @escaping (ViewController, Any?) -> Void) {
let _ = (fetchMediaData(context: context, postbox: context.account.postbox, userLocation: .other, mediaReference: media)
|> deliverOnMainQueue).start(next: { (value, isImage) in
guard case let .data(data) = value, data.complete else {
@@ -107,7 +147,7 @@ public func legacyMediaEditor(context: AccountContext, peer: Peer, threadTitle:
present(legacyController, nil)
- TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: true, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, appeared: {
+ TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: initialCaption, withItem: item, paint: mode == .draw, adjustments: mode == .adjustments, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: snapshots as [Any], immediate: transitionCompletion != nil, appeared: {
transitionCompletion?()
}, completion: { result, editingContext in
let nativeGenerator = legacyAssetPickerItemGenerator()
@@ -365,7 +405,7 @@ public func legacyAttachmentMenu(context: AccountContext, peer: Peer, threadTitl
present(legacyController, nil)
- TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, recipientName: recipientName, stickersContext: paintStickersContext, snapshots: [], immediate: false, appeared: {
+ TGPhotoVideoEditor.present(with: legacyController.context, controller: emptyController, caption: NSAttributedString(), withItem: item, paint: false, adjustments: false, recipientName: recipientName, stickersContext: paintStickersContext, from: .zero, mainSnapshot: nil, snapshots: [], immediate: false, appeared: {
}, completion: { result, editingContext in
let nativeGenerator = legacyAssetPickerItemGenerator()
var selectableResult: TGMediaSelectableItem?
diff --git a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift
index d737a6a77f..bbd1942090 100644
--- a/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift
+++ b/submodules/ListSectionHeaderNode/Sources/ListSectionHeaderNode.swift
@@ -5,7 +5,7 @@ import Display
import TelegramPresentationData
private let titleFont = Font.bold(13.0)
-private let actionFont = Font.medium(13.0)
+private let actionFont = Font.regular(13.0)
public enum ListSectionHeaderActionType {
case generic
@@ -13,6 +13,7 @@ public enum ListSectionHeaderActionType {
}
public final class ListSectionHeaderNode: ASDisplayNode {
+ private let backgroundLayer: SimpleLayer
private let label: ImmediateTextNode
private var actionButtonLabel: ImmediateTextNode?
private var actionButton: HighlightableButtonNode?
@@ -87,16 +88,29 @@ public final class ListSectionHeaderNode: ASDisplayNode {
public init(theme: PresentationTheme) {
self.theme = theme
+ self.backgroundLayer = SimpleLayer()
+
self.label = ImmediateTextNode()
self.label.isUserInteractionEnabled = false
self.label.isAccessibilityElement = true
super.init()
-
+ self.layer.addSublayer(self.backgroundLayer)
+
self.addSubnode(self.label)
- self.backgroundColor = theme.chatList.sectionHeaderFillColor
+ self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor
+ }
+
+ override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ if let actionButton = self.actionButton {
+ if actionButton.frame.contains(point) {
+ return actionButton.view
+ }
+ }
+
+ return super.hitTest(point, with: event)
}
public func updateTheme(theme: PresentationTheme) {
@@ -105,7 +119,7 @@ public final class ListSectionHeaderNode: ASDisplayNode {
self.label.attributedText = NSAttributedString(string: self.title ?? "", font: titleFont, textColor: self.theme.chatList.sectionHeaderTextColor)
- self.backgroundColor = theme.chatList.sectionHeaderFillColor
+ self.backgroundLayer.backgroundColor = theme.chatList.sectionHeaderFillColor.cgColor
if let action = self.action {
self.actionButtonLabel?.attributedText = NSAttributedString(string: action, font: actionFont, textColor: self.theme.chatList.sectionHeaderTextColor)
}
@@ -126,6 +140,8 @@ public final class ListSectionHeaderNode: ASDisplayNode {
actionButtonLabel.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
actionButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - 16.0 - buttonSize.width, y: 6.0 + UIScreenPixel), size: buttonSize)
}
+
+ self.backgroundLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel), size: CGSize(width: size.width, height: size.height + UIScreenPixel))
}
@objc private func actionButtonPressed() {
diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift
index 879371f8bc..2a70da2a89 100644
--- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift
+++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift
@@ -829,7 +829,7 @@ final class LocationViewControllerNode: ViewControllerTracingNode, CLLocationMan
text = strongSelf.presentationData.strings.Location_ProximityTip(EnginePeer(peer).compactDisplayTitle).string
}
- strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
+ strongSelf.interaction.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: -9.0, dy: 0.0), .right), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}))
})
diff --git a/submodules/LottieMeshSwift/Sources/Buffer.swift b/submodules/LottieMeshSwift/Sources/Buffer.swift
index cff168947f..3601d4f89a 100644
--- a/submodules/LottieMeshSwift/Sources/Buffer.swift
+++ b/submodules/LottieMeshSwift/Sources/Buffer.swift
@@ -75,7 +75,7 @@ public final class MeshWriteBuffer {
}
public func seek(offset: Int) {
- self.file.seek(position: Int64(offset))
+ let _ = self.file.seek(position: Int64(offset))
self.offset = offset
}
}
diff --git a/submodules/ManagedFile/Sources/ManagedFile.swift b/submodules/ManagedFile/Sources/ManagedFile.swift
index 34ddd4eadc..66ad61cb81 100644
--- a/submodules/ManagedFile/Sources/ManagedFile.swift
+++ b/submodules/ManagedFile/Sources/ManagedFile.swift
@@ -99,12 +99,14 @@ public final class ManagedFile {
return result
}
- public func seek(position: Int64) {
+ @discardableResult
+ public func seek(position: Int64) -> Bool {
if let queue = self.queue {
assert(queue.isCurrent())
}
assert(!self.isClosed)
- lseek(self.fd, position, SEEK_SET)
+ let result = lseek(self.fd, position, SEEK_SET)
+ return result == position
}
public func truncate(count: Int64) {
diff --git a/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift b/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift
index 113bf267e6..aebaf1f84d 100644
--- a/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift
+++ b/submodules/MediaPickerUI/Sources/MediaAssetsContext.swift
@@ -5,12 +5,16 @@ import Photos
import AVFoundation
class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
+ private let assetType: PHAssetMediaType?
+
private var registeredChangeObserver = false
private let changeSink = ValuePipe()
private let mediaAccessSink = ValuePipe()
private let cameraAccessSink = ValuePipe()
- override init() {
+ init(assetType: PHAssetMediaType?) {
+ self.assetType = assetType
+
super.init()
if PHPhotoLibrary.authorizationStatus() == .authorized {
@@ -30,7 +34,12 @@ class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
}
func fetchAssets(_ collection: PHAssetCollection) -> Signal, NoError> {
- let initialFetchResult = PHAsset.fetchAssets(in: collection, options: nil)
+ let options = PHFetchOptions()
+ if let assetType = self.assetType {
+ options.predicate = NSPredicate(format: "mediaType = %d", assetType.rawValue)
+ }
+
+ let initialFetchResult = PHAsset.fetchAssets(in: collection, options: options)
let fetchResult = Atomic>(value: initialFetchResult)
return .single(initialFetchResult)
|> then(
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift
index dac85ccde4..6d65780560 100644
--- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift
+++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift
@@ -323,7 +323,14 @@ final class MediaPickerGridItemNode: GridItemNode {
strongSelf.updateHasSpoiler(hasSpoiler)
}))
- if asset.mediaType == .video {
+ if asset.isFavorite {
+ self.typeIconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Media Grid/Favorite"), color: .white)
+ if self.typeIconNode.supernode == nil {
+ self.addSubnode(self.gradientNode)
+ self.addSubnode(self.typeIconNode)
+ self.setNeedsLayout()
+ }
+ } else if asset.mediaType == .video {
if asset.mediaSubtypes.contains(.videoHighFrameRate) {
self.typeIconNode.image = UIImage(bundleImageName: "Media Editor/MediaSlomo")
} else if asset.mediaSubtypes.contains(.videoTimelapse) {
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift
index eb9d6215d5..f13bd2e941 100644
--- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift
+++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift
@@ -128,7 +128,12 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
- case assets(PHAssetCollection?, Bool)
+ public enum AssetsMode: Equatable {
+ case `default`
+ case wallpaper
+ }
+
+ case assets(PHAssetCollection?, AssetsMode)
case media([Media])
}
@@ -238,7 +243,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.controller = controller
self.presentationData = controller.presentationData
- let mediaAssetsContext = MediaAssetsContext()
+ var assetType: PHAssetMediaType?
+ if case let .assets(_, mode) = controller.subject, case .wallpaper = mode {
+ assetType = .image
+ }
+ let mediaAssetsContext = MediaAssetsContext(assetType: assetType)
self.mediaAssetsContext = mediaAssetsContext
self.containerNode = ASDisplayNode()
@@ -253,7 +262,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
super.init()
- if case .assets(nil, false) = controller.subject {
+ if case .assets(nil, .default) = controller.subject {
} else {
self.preloadPromise.set(false)
}
@@ -376,32 +385,40 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
override func didLoad() {
super.didLoad()
+ guard let controller = self.controller else {
+ return
+ }
+
self.gridNode.scrollView.alwaysBounceVertical = true
self.gridNode.scrollView.showsVerticalScrollIndicator = false
- let selectionGesture = MediaPickerGridSelectionGesture()
- selectionGesture.delegate = self
- selectionGesture.began = { [weak self] in
- self?.controller?.cancelPanGesture()
- }
- selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in
- self?.gridNode.scrollView.isScrollEnabled = isEnabled
- }
- selectionGesture.itemAt = { [weak self] point in
- if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem {
- return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false)
- } else {
- return nil
+ if case let .assets(_, mode) = controller.subject, case .wallpaper = mode {
+
+ } else {
+ let selectionGesture = MediaPickerGridSelectionGesture()
+ selectionGesture.delegate = self
+ selectionGesture.began = { [weak self] in
+ self?.controller?.cancelPanGesture()
}
- }
- selectionGesture.updateSelection = { [weak self] asset, selected in
- if let strongSelf = self {
- strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil)
+ selectionGesture.updateIsScrollEnabled = { [weak self] isEnabled in
+ self?.gridNode.scrollView.isScrollEnabled = isEnabled
}
+ selectionGesture.itemAt = { [weak self] point in
+ if let strongSelf = self, let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? MediaPickerGridItemNode, let selectableItem = itemNode.selectableItem {
+ return (selectableItem, strongSelf.controller?.interaction?.selectionState?.isIdentifierSelected(selectableItem.uniqueIdentifier) ?? false)
+ } else {
+ return nil
+ }
+ }
+ selectionGesture.updateSelection = { [weak self] asset, selected in
+ if let strongSelf = self {
+ strongSelf.controller?.interaction?.selectionState?.setItem(asset, selected: selected, animated: true, sender: nil)
+ }
+ }
+ selectionGesture.sideInset = 44.0
+ self.gridNode.view.addGestureRecognizer(selectionGesture)
+ self.selectionGesture = selectionGesture
}
- selectionGesture.sideInset = 44.0
- self.gridNode.view.addGestureRecognizer(selectionGesture)
- self.selectionGesture = selectionGesture
if let controller = self.controller, case let .assets(collection, _) = controller.subject, collection != nil {
self.gridNode.view.interactiveTransitionGestureRecognizerTest = { point -> Bool in
@@ -454,7 +471,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
})
- if let controller = self.controller, case .assets(nil, false) = controller.subject {
+ if let controller = self.controller, case .assets(nil, .default) = controller.subject {
let enableAnimations = self.controller?.context.sharedContext.energyUsageSettings.fullTranslucency ?? true
let cameraView = TGAttachmentCameraView(forSelfPortrait: false, videoModeByDefault: controller.bannedSendPhotos != nil && controller.bannedSendVideos == nil)!
@@ -556,7 +573,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
var updateLayout = false
var selectable = true
- if case let .assets(_, isStandalone) = controller.subject, isStandalone {
+ if case let .assets(_, mode) = controller.subject, mode != .default {
selectable = false
}
@@ -732,7 +749,11 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
if let customSelection = controller.customSelection {
+ self.openingMedia = true
customSelection(fetchResult[index])
+ Queue.mainQueue().after(0.3) {
+ self.openingMedia = false
+ }
return
}
@@ -1229,7 +1250,24 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
private var isDismissing = false
- public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, peer: EnginePeer?, threadTitle: String?, chatLocation: ChatLocation?, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, subject: Subject, editingContext: TGMediaEditingContext? = nil, selectionContext: TGMediaSelectionContext? = nil, saveEditedPhotos: Bool = false) {
+ fileprivate let mainButtonState: AttachmentMainButtonState?
+ private let mainButtonAction: (() -> Void)?
+
+ public init(
+ context: AccountContext,
+ updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
+ peer: EnginePeer?,
+ threadTitle: String?,
+ chatLocation: ChatLocation?,
+ bannedSendPhotos: (Int32, Bool)?,
+ bannedSendVideos: (Int32, Bool)?,
+ subject: Subject,
+ editingContext: TGMediaEditingContext? = nil,
+ selectionContext: TGMediaSelectionContext? = nil,
+ saveEditedPhotos: Bool = false,
+ mainButtonState: AttachmentMainButtonState? = nil,
+ mainButtonAction: (() -> Void)? = nil
+ ) {
self.context = context
let presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 }
@@ -1242,13 +1280,24 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.bannedSendVideos = bannedSendVideos
self.subject = subject
self.saveEditedPhotos = saveEditedPhotos
+ self.mainButtonState = mainButtonState
+ self.mainButtonAction = mainButtonAction
let selectionContext = selectionContext ?? TGMediaSelectionContext()
self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0)
- if case let .assets(collection, _) = subject, let collection = collection {
- self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery
+ if case let .assets(collection, mode) = subject {
+ if let collection = collection {
+ self.titleView.title = collection.localizedTitle ?? presentationData.strings.Attachment_Gallery
+ } else {
+ switch mode {
+ case .default:
+ self.titleView.title = presentationData.strings.Attachment_Gallery
+ case .wallpaper:
+ self.titleView.title = presentationData.strings.Conversation_Theme_ChooseWallpaperTitle
+ }
+ }
} else {
self.titleView.title = presentationData.strings.Attachment_Gallery
}
@@ -1316,7 +1365,7 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
self.navigationItem.titleView = self.titleView
- if case let .assets(_, isStandalone) = self.subject, isStandalone {
+ if case let .assets(_, mode) = self.subject, mode != .default {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
} else {
if case let .assets(collection, _) = self.subject, collection != nil {
@@ -1609,6 +1658,10 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
}
+ func mainButtonPressed() {
+ self.mainButtonAction?()
+ }
+
func dismissAllTooltips() {
self.undoOverlayController?.dismissWithCommitAction()
}
@@ -1674,13 +1727,13 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
private func presentSearch(activateOnDisplay: Bool) {
- guard self.moreButtonNode.iconNode.iconState == .search else {
+ guard self.moreButtonNode.iconNode.iconState == .search, case let .assets(_, mode) = self.subject else {
return
}
self.requestAttachmentMenuExpansion()
self.presentWebSearch(MediaGroupsScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mediaAssetsContext: self.controllerNode.mediaAssetsContext, openGroup: { [weak self] collection in
if let strongSelf = self {
- let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection, false), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState)
+ let mediaPicker = MediaPickerScreen(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: strongSelf.peer, threadTitle: strongSelf.threadTitle, chatLocation: strongSelf.chatLocation, bannedSendPhotos: strongSelf.bannedSendPhotos, bannedSendVideos: strongSelf.bannedSendVideos, subject: .assets(collection, mode), editingContext: strongSelf.interaction?.editingState, selectionContext: strongSelf.interaction?.selectionState)
mediaPicker.presentSchedulePicker = strongSelf.presentSchedulePicker
mediaPicker.presentTimerPicker = strongSelf.presentTimerPicker
@@ -1793,21 +1846,17 @@ public final class MediaPickerScreen: ViewController, AttachmentContainable {
}
public var mediaPickerContext: AttachmentMediaPickerContext? {
- if let interaction = self.interaction {
- return MediaPickerContext(interaction: interaction)
- } else {
- return nil
- }
+ return MediaPickerContext(controller: self)
}
}
final class MediaPickerContext: AttachmentMediaPickerContext {
- private weak var interaction: MediaPickerInteraction?
+ private weak var controller: MediaPickerScreen?
var selectionCount: Signal {
return Signal { [weak self] subscriber in
- let disposable = self?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in
- subscriber.putNext(Int(self?.interaction?.selectionState?.count() ?? 0))
+ let disposable = self?.controller?.interaction?.selectionState?.selectionChangedSignal().start(next: { [weak self] value in
+ subscriber.putNext(Int(self?.controller?.interaction?.selectionState?.count() ?? 0))
}, error: { _ in }, completed: { })
return ActionDisposable {
disposable?.dispose()
@@ -1817,7 +1866,7 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
var caption: Signal {
return Signal { [weak self] subscriber in
- let disposable = self?.interaction?.editingState.forcedCaption().start(next: { caption in
+ let disposable = self?.controller?.interaction?.editingState.forcedCaption().start(next: { caption in
if let caption = caption as? NSAttributedString {
subscriber.putNext(caption)
} else {
@@ -1835,27 +1884,27 @@ final class MediaPickerContext: AttachmentMediaPickerContext {
}
public var mainButtonState: Signal {
- return .single(nil)
+ return .single(self.controller?.mainButtonState)
}
- init(interaction: MediaPickerInteraction) {
- self.interaction = interaction
+ init(controller: MediaPickerScreen) {
+ self.controller = controller
}
func setCaption(_ caption: NSAttributedString) {
- self.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
+ self.controller?.interaction?.editingState.setForcedCaption(caption, skipUpdate: true)
}
func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
- self.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {})
+ self.controller?.interaction?.sendSelected(nil, mode == .silently, mode == .whenOnline ? scheduleWhenOnlineTimestamp : nil, true, {})
}
func schedule() {
- self.interaction?.schedule()
+ self.controller?.interaction?.schedule()
}
func mainButtonAction() {
-
+ self.controller?.mainButtonPressed()
}
}
@@ -1968,3 +2017,28 @@ public class MediaPickerGridSelectionGesture : UIPanGestureRecognizer {
self.updateIsScrollEnabled(true)
}
}
+
+public func wallpaperMediaPickerController(
+ context: AccountContext,
+ updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
+ peer: EnginePeer,
+ animateAppearance: Bool,
+ completion: @escaping (PHAsset) -> Void = { _ in },
+ openColors: @escaping () -> Void
+) -> ViewController {
+ let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
+ return nil
+ })
+ controller.animateAppearance = animateAppearance
+ //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
+ controller.requestController = { [weak controller] _, present in
+ let presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ let mediaPickerController = MediaPickerScreen(context: context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper), mainButtonState: AttachmentMainButtonState(text: presentationData.strings.Conversation_Theme_SetColorWallpaper, font: .regular, background: .color(.clear), textColor: presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true), mainButtonAction: {
+ controller?.dismiss(animated: true)
+ openColors()
+ })
+ mediaPickerController.customSelection = completion
+ present(mediaPickerController, mediaPickerController.mediaPickerContext)
+ }
+ return controller
+}
diff --git a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift
index 81dd23cf02..7fe0210eeb 100644
--- a/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift
+++ b/submodules/MediaPickerUI/Sources/MediaPickerTitleView.swift
@@ -88,7 +88,7 @@ final class MediaPickerTitleView: UIView {
let controlSize = self.segmentedControlNode.updateLayout(.stretchToFill(width: min(300.0, size.width - 36.0)), transition: .immediate)
self.segmentedControlNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - controlSize.width) / 2.0), y: floorToScreenPixels((size.height - controlSize.height) / 2.0)), size: controlSize)
- let titleSize = self.titleNode.updateLayout(CGSize(width: 160.0, height: 44.0))
+ let titleSize = self.titleNode.updateLayout(CGSize(width: 210.0, height: 44.0))
self.titleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
}
}
diff --git a/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift b/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift
index 134a758464..58c940fb9a 100644
--- a/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift
+++ b/submodules/MosaicLayout/Sources/ChatMessageBubbleMosaicLayout.swift
@@ -302,7 +302,7 @@ public func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize],
innerPositionFlags.insert(.right)
}
- if positionFlags == .none {
+ if positionFlags == [] {
innerPositionFlags = .inside
}
diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h
index 03c3202c86..fa26508ecb 100644
--- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h
+++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTContext.h
@@ -21,7 +21,7 @@
@protocol MTTcpConnectionInterfaceDelegate
- (void)connectionInterfaceDidReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag;
-- (void)connectionInterfaceDidReadData:(NSData * _Nonnull)rawData withTag:(long)tag;
+- (void)connectionInterfaceDidReadData:(NSData * _Nonnull)rawData withTag:(long)tag networkType:(int32_t)networkType;
- (void)connectionInterfaceDidConnect;
- (void)connectionInterfaceDidDisconnectWithError:(NSError * _Nullable)error;
diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h
index 7f3d013a62..1dd9fdd4f8 100644
--- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h
+++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTMessageService.h
@@ -21,7 +21,7 @@
- (MTMessageTransaction *)mtProtoMessageTransaction:(MTProto *)mtProto authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector sessionInfo:(MTSessionInfo *)sessionInfo scheme:(MTTransportScheme *)scheme;
- (void)mtProtoDidChangeSession:(MTProto *)mtProto;
- (void)mtProtoServerDidChangeSession:(MTProto *)mtProto firstValidMessageId:(int64_t)firstValidMessageId otherValidMessageIds:(NSArray *)otherValidMessageIds;
-- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector;
+- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType;
- (void)mtProto:(MTProto *)mtProto receivedQuickAck:(int32_t)quickAckId;
- (void)mtProto:(MTProto *)mtProto transactionsMayHaveFailed:(NSArray *)transactionIds;
- (void)mtProtoAllTransactionsMayHaveFailed:(MTProto *)mtProto;
diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h
index 5f5d3d1248..28dba10b2d 100644
--- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h
+++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequest.h
@@ -6,6 +6,16 @@
@class MTRequestErrorContext;
@class MTRpcError;
+@interface MTRequestResponseInfo : NSObject
+
+@property (nonatomic, readonly) int32_t networkType;
+@property (nonatomic, readonly) double timestamp;
+@property (nonatomic, readonly) double duration;
+
+- (instancetype)initWithNetworkType:(int32_t)networkType timestamp:(double)timestamp duration:(double)duration;
+
+@end
+
@interface MTRequest : NSObject
@property (nonatomic, strong, readonly) id internalId;
@@ -24,7 +34,7 @@
@property (nonatomic) bool passthroughPasswordEntryError;
@property (nonatomic) bool needsTimeoutTimer;
-@property (nonatomic, copy) void (^completed)(id result, NSTimeInterval completionTimestamp, MTRpcError *error);
+@property (nonatomic, copy) void (^completed)(id result, MTRequestResponseInfo *info, MTRpcError *error);
@property (nonatomic, copy) void (^progressUpdated)(float progress, NSUInteger packetLength);
@property (nonatomic, copy) void (^acknowledgementReceived)();
diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h
index 46b17abcee..9c63163811 100644
--- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h
+++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTRequestContext.h
@@ -12,6 +12,7 @@
@property (nonatomic) bool delivered;
@property (nonatomic) int64_t responseMessageId;
@property (nonatomic) bool willInitializeApi;
+@property (nonatomic) double sentTimestamp;
- (instancetype)initWithMessageId:(int64_t)messageId messageSeqNo:(int32_t)messageSeqNo transactionId:(id)transactionId quickAckId:(int32_t)quickAckId;
diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h
index 156fe128c3..9237e0d24d 100644
--- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h
+++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h
@@ -26,7 +26,7 @@
- (void)transportConnectionProblemsStatusChanged:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme hasConnectionProblems:(bool)hasConnectionProblems isProbablyHttp:(bool)isProbablyHttp;
- (void)transportReadyForTransaction:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme transportSpecificTransaction:(MTMessageTransaction * _Nonnull)transportSpecificTransaction forceConfirmations:(bool)forceConfirmations transactionReady:(void (^ _Nonnull)(NSArray * _Nonnull))transactionReady;
-- (void)transportHasIncomingData:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme data:(NSData * _Nonnull)data transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult;
+- (void)transportHasIncomingData:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme networkType:(int32_t)networkType data:(NSData * _Nonnull)data transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult;
- (void)transportTransactionsMayHaveFailed:(MTTransport * _Nonnull)transport transactionIds:(NSArray * _Nonnull)transactionIds;
- (void)transportReceivedQuickAck:(MTTransport * _Nonnull)transport quickAckId:(int32_t)quickAckId;
- (void)transportDecodeProgressToken:(MTTransport * _Nonnull)transport scheme:(MTTransportScheme * _Nonnull)scheme data:(NSData * _Nonnull)data token:(int64_t)token completion:(void (^ _Nonnull)(int64_t token, id _Nonnull progressToken))completion;
@@ -57,7 +57,7 @@
- (void)stop;
- (void)updateConnectionState;
- (void)setDelegateNeedsTransaction;
-- (void)_processIncomingData:(NSData * _Nonnull)data scheme:(MTTransportScheme * _Nonnull)scheme transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult;
+- (void)_processIncomingData:(NSData * _Nonnull)data scheme:(MTTransportScheme * _Nonnull)scheme networkType:(int32_t)networkType transactionId:(id _Nonnull)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^ _Nonnull)(id _Nonnull transactionId, bool success))decodeResult;
- (void)_networkAvailabilityChanged:(bool)networkAvailable;
- (void)activeTransactionIds:(void (^ _Nonnull)(NSArray * _Nonnull activeTransactionId))completion;
diff --git a/submodules/MtProtoKit/Sources/GCDAsyncSocket.h b/submodules/MtProtoKit/Sources/GCDAsyncSocket.h
index 1f7f6e6378..c329cd4b7d 100644
--- a/submodules/MtProtoKit/Sources/GCDAsyncSocket.h
+++ b/submodules/MtProtoKit/Sources/GCDAsyncSocket.h
@@ -975,7 +975,7 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError;
* Called when a socket has completed reading the requested data into memory.
* Not called if there is an error.
**/
-- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag;
+- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag networkType:(int32_t)networkType;
/**
* Called when a socket has read in data, but has not yet completed the read.
diff --git a/submodules/MtProtoKit/Sources/GCDAsyncSocket.m b/submodules/MtProtoKit/Sources/GCDAsyncSocket.m
index 470658d86e..fd54f234a0 100755
--- a/submodules/MtProtoKit/Sources/GCDAsyncSocket.m
+++ b/submodules/MtProtoKit/Sources/GCDAsyncSocket.m
@@ -5119,14 +5119,15 @@ enum GCDAsyncSocketConfig
result = [NSData dataWithBytesNoCopy:buffer length:currentRead->bytesDone freeWhenDone:NO];
}
- if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:)])
+ if (delegateQueue && [delegate respondsToSelector:@selector(socket:didReadData:withTag:networkType:)])
{
__strong id theDelegate = delegate;
GCDAsyncReadPacket *theRead = currentRead; // Ensure currentRead retained since result may not own buffer
+ int32_t networkType = _interface == MTNetworkUsageManagerInterfaceOther ? 0 : 1;
dispatch_async(delegateQueue, ^{ @autoreleasepool {
- [theDelegate socket:self didReadData:result withTag:theRead->tag];
+ [theDelegate socket:self didReadData:result withTag:theRead->tag networkType:networkType];
}});
}
diff --git a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m
index be7b1f7ab2..5b79068a55 100644
--- a/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m
+++ b/submodules/MtProtoKit/Sources/MTBackupAddressSignals.m
@@ -313,7 +313,7 @@ static NSString *makeRandomPadding() {
__weak MTContext *weakCurrentContext = currentContext;
return [[MTSignal alloc] initWithGenerator:^id(MTSubscriber *subscriber) {
- [request setCompleted:^(MTDatacenterAddressListData *result, __unused NSTimeInterval completionTimestamp, id error)
+ [request setCompleted:^(MTDatacenterAddressListData *result, __unused MTRequestResponseInfo *info, id error)
{
if (error == nil) {
__strong MTContext *strongCurrentContext = weakCurrentContext;
diff --git a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m
index 417818f43c..7b339d497d 100644
--- a/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m
+++ b/submodules/MtProtoKit/Sources/MTBindKeyMessageService.m
@@ -136,7 +136,7 @@
}
}
-- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector {
+- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType {
if ([message.body isKindOfClass:[MTRpcResultMessage class]]) {
MTRpcResultMessage *rpcResultMessage = message.body;
if (rpcResultMessage.requestMessageId == _currentMessageId) {
diff --git a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m
index 06c4f4264f..882d6c6118 100644
--- a/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m
+++ b/submodules/MtProtoKit/Sources/MTDatacenterAuthMessageService.m
@@ -410,7 +410,7 @@ static NSData *encryptRSAModernPadding(id encryptionProvider
}
}
-- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
+- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType
{
if (_stage == MTDatacenterAuthStagePQ && [message.body isKindOfClass:[MTResPqMessage class]])
{
diff --git a/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m b/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m
index 54e2de78ad..0e38f9c20c 100644
--- a/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m
+++ b/submodules/MtProtoKit/Sources/MTDatacenterTransferAuthAction.m
@@ -101,7 +101,7 @@
[request setPayload:exportAuthRequestData metadata:@"exportAuthorization" shortMetadata:@"exportAuthorization" responseParser:responseParser];
__weak MTDatacenterTransferAuthAction *weakSelf = self;
- [request setCompleted:^(MTExportedAuthorizationData *result, __unused NSTimeInterval timestamp, id error)
+ [request setCompleted:^(MTExportedAuthorizationData *result, __unused MTRequestResponseInfo *info, id error)
{
__strong MTDatacenterTransferAuthAction *strongSelf = weakSelf;
if (strongSelf == nil)
@@ -147,7 +147,7 @@
id authToken = _authToken;
__weak MTDatacenterTransferAuthAction *weakSelf = self;
- [request setCompleted:^(__unused id result, __unused NSTimeInterval timestamp, id error)
+ [request setCompleted:^(__unused id result, __unused MTRequestResponseInfo *info, id error)
{
__strong MTDatacenterTransferAuthAction *strongSelf = weakSelf;
if (strongSelf == nil)
diff --git a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m
index 8cd1ed0a6f..2cac360ee8 100644
--- a/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m
+++ b/submodules/MtProtoKit/Sources/MTDiscoverDatacenterAddressAction.m
@@ -106,7 +106,7 @@
[request setPayload:getConfigData metadata:@"getConfig" shortMetadata:@"getConfig" responseParser:responseParser];
__weak MTDiscoverDatacenterAddressAction *weakSelf = self;
- [request setCompleted:^(MTDatacenterAddressListData *result, __unused NSTimeInterval completionTimestamp, id error)
+ [request setCompleted:^(MTDatacenterAddressListData *result, __unused MTRequestResponseInfo *info, id error)
{
__strong MTDiscoverDatacenterAddressAction *strongSelf = weakSelf;
if (strongSelf != nil) {
diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m
index 4b02c06b50..7e71be1b65 100644
--- a/submodules/MtProtoKit/Sources/MTProto.m
+++ b/submodules/MtProtoKit/Sources/MTProto.m
@@ -1966,7 +1966,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
return hexString;
}
-- (void)transportHasIncomingData:(MTTransport *)transport scheme:(MTTransportScheme *)scheme data:(NSData *)data transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult
+- (void)transportHasIncomingData:(MTTransport *)transport scheme:(MTTransportScheme *)scheme networkType:(int32_t)networkType data:(NSData *)data transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult
{
/*__block bool simulateError = false;
static dispatch_once_t onceToken;
@@ -2097,7 +2097,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) {
for (MTIncomingMessage *incomingMessage in parsedMessages)
{
- [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address authInfoSelector:authInfoSelector];
+ [self _processIncomingMessage:incomingMessage totalSize:(int)data.length withTransactionId:transactionId address:scheme.address authInfoSelector:authInfoSelector networkType:networkType];
}
if (requestTransactionAfterProcessing)
@@ -2422,7 +2422,7 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) {
return messages;
}
-- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
+- (void)_processIncomingMessage:(MTIncomingMessage *)incomingMessage totalSize:(int)totalSize withTransactionId:(id)transactionId address:(MTDatacenterAddress *)address authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType
{
if ([_sessionInfo messageProcessed:incomingMessage.messageId])
{
@@ -2605,8 +2605,8 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) {
{
id messageService = _messageServices[(NSUInteger)i];
- if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:authInfoSelector:)])
- [messageService mtProto:self receivedMessage:incomingMessage authInfoSelector:authInfoSelector];
+ if ([messageService respondsToSelector:@selector(mtProto:receivedMessage:authInfoSelector:networkType:)])
+ [messageService mtProto:self receivedMessage:incomingMessage authInfoSelector:authInfoSelector networkType:networkType];
}
if (_timeFixContext != nil && [incomingMessage.body isKindOfClass:[MTPongMessage class]] && ((MTPongMessage *)incomingMessage.body).messageId == _timeFixContext.messageId)
diff --git a/submodules/MtProtoKit/Sources/MTRequest.m b/submodules/MtProtoKit/Sources/MTRequest.m
index e348537a8c..b154934fd3 100644
--- a/submodules/MtProtoKit/Sources/MTRequest.m
+++ b/submodules/MtProtoKit/Sources/MTRequest.m
@@ -5,6 +5,20 @@
#import
#import
+@implementation MTRequestResponseInfo
+
+- (instancetype)initWithNetworkType:(int32_t)networkType timestamp:(double)timestamp duration:(double)duration {
+ self = [super init];
+ if (self != nil) {
+ _networkType = networkType;
+ _timestamp = timestamp;
+ _duration = duration;
+ }
+ return self;
+}
+
+@end
+
@interface MTRequestInternalId : NSObject
{
NSUInteger _value;
diff --git a/submodules/MtProtoKit/Sources/MTRequestMessageService.m b/submodules/MtProtoKit/Sources/MTRequestMessageService.m
index 9b89b6a545..fed278d302 100644
--- a/submodules/MtProtoKit/Sources/MTRequestMessageService.m
+++ b/submodules/MtProtoKit/Sources/MTRequestMessageService.m
@@ -369,7 +369,7 @@
MTRequestNoopParser responseParser = [[_context serialization] requestNoop:&noopData];
[request setPayload:noopData metadata:@"noop" shortMetadata:@"noop" responseParser:responseParser];
- [request setCompleted:^(__unused id result, __unused NSTimeInterval timestamp, __unused id error) {
+ [request setCompleted:^(__unused id result, __unused MTRequestResponseInfo *info, __unused id error) {
}];
[self addRequest:request];
@@ -614,6 +614,7 @@
}
MTRequestContext *requestContext = [[MTRequestContext alloc] initWithMessageId:preparedMessage.messageId messageSeqNo:preparedMessage.seqNo transactionId:nil quickAckId:0];
+ requestContext.sentTimestamp = CFAbsoluteTimeGetCurrent();
requestContext.willInitializeApi = requestsWillInitializeApi;
requestContext.waitingForMessageId = true;
request.requestContext = requestContext;
@@ -646,6 +647,7 @@
continue;
}
MTRequestContext *requestContext = [[MTRequestContext alloc] initWithMessageId:preparedMessage.messageId messageSeqNo:preparedMessage.seqNo transactionId:messageInternalIdToTransactionId[messageInternalId] quickAckId:(int32_t)[messageInternalIdToQuickAckId[messageInternalId] intValue]];
+ requestContext.sentTimestamp = CFAbsoluteTimeGetCurrent();
requestContext.willInitializeApi = requestsWillInitializeApi;
request.requestContext = requestContext;
}
@@ -667,7 +669,7 @@
return nil;
}
-- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
+- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType
{
if ([message.body isKindOfClass:[MTRpcResultMessage class]])
{
@@ -871,6 +873,8 @@
}
}
+ double sentTimestamp = request.requestContext.sentTimestamp;
+
request.requestContext = nil;
if (restartRequest)
@@ -879,11 +883,17 @@
}
else
{
- void (^completed)(id result, NSTimeInterval completionTimestamp, id error) = [request.completed copy];
+ void (^completed)(id result, MTRequestResponseInfo *info, id error) = [request.completed copy];
[_requests removeObjectAtIndex:(NSUInteger)index];
- if (completed)
- completed(rpcResult, message.timestamp, rpcError);
+ if (completed) {
+ double duration = 0.0;
+ if (sentTimestamp != 0.0) {
+ duration = CFAbsoluteTimeGetCurrent() - sentTimestamp;
+ }
+ MTRequestResponseInfo *info = [[MTRequestResponseInfo alloc] initWithNetworkType:networkType timestamp:message.timestamp duration:duration];
+ completed(rpcResult, info, rpcError);
+ }
}
break;
diff --git a/submodules/MtProtoKit/Sources/MTResendMessageService.m b/submodules/MtProtoKit/Sources/MTResendMessageService.m
index 468e30eec6..c72d1f7f81 100644
--- a/submodules/MtProtoKit/Sources/MTResendMessageService.m
+++ b/submodules/MtProtoKit/Sources/MTResendMessageService.m
@@ -121,7 +121,7 @@
}
}
-- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
+- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType
{
if (message.messageId == _messageId)
{
diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.h b/submodules/MtProtoKit/Sources/MTTcpConnection.h
index 872eea7981..b186b83745 100644
--- a/submodules/MtProtoKit/Sources/MTTcpConnection.h
+++ b/submodules/MtProtoKit/Sources/MTTcpConnection.h
@@ -13,7 +13,7 @@
- (void)tcpConnectionOpened:(MTTcpConnection *)connection;
- (void)tcpConnectionClosed:(MTTcpConnection *)connection error:(bool)error;
-- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection data:(NSData *)data;
+- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection networkType:(int32_t)networkType data:(NSData *)data;
- (void)tcpConnectionReceivedQuickAck:(MTTcpConnection *)connection quickAck:(int32_t)quickAck;
- (void)tcpConnectionDecodePacketProgressToken:(MTTcpConnection *)connection data:(NSData *)data token:(int64_t)token completion:(void (^)(int64_t token, id packetProgressToken))completion;
- (void)tcpConnectionProgressUpdated:(MTTcpConnection *)connection packetProgressToken:(id)packetProgressToken packetLength:(NSUInteger)packetLength progress:(float)progress;
diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.m b/submodules/MtProtoKit/Sources/MTTcpConnection.m
index 77819c28ec..dd2755e68d 100644
--- a/submodules/MtProtoKit/Sources/MTTcpConnection.m
+++ b/submodules/MtProtoKit/Sources/MTTcpConnection.m
@@ -664,10 +664,10 @@ struct ctr_state {
}
}
-- (void)socket:(GCDAsyncSocket *)socket didReadData:(NSData *)rawData withTag:(long)tag {
+- (void)socket:(GCDAsyncSocket *)socket didReadData:(NSData *)rawData withTag:(long)tag networkType:(int32_t)networkType {
id delegate = _delegate;
if (delegate) {
- [delegate connectionInterfaceDidReadData:rawData withTag:tag];
+ [delegate connectionInterfaceDidReadData:rawData withTag:tag networkType:networkType];
}
}
@@ -717,6 +717,7 @@ struct ctr_state {
id _socket;
bool _closed;
+ int32_t _lastNetworkType;
bool _useIntermediateFormat;
@@ -1459,7 +1460,7 @@ struct ctr_state {
[_socket readDataToLength:4 withTimeout:-1 tag:MTTcpSocksRequest];
}
-- (void)connectionInterfaceDidReadData:(NSData *)rawData withTag:(long)tag
+- (void)connectionInterfaceDidReadData:(NSData *)rawData withTag:(long)tag networkType:(int32_t)networkType
{
if (_closed)
return;
@@ -1754,12 +1755,12 @@ struct ctr_state {
[_socket readDataToLength:(int)nextLength withTimeout:-1 tag:MTTcpSocksReceiveComplexPacketPart];
return;
} else if (tag == MTTcpSocksReceiveComplexPacketPart) {
- [self addReadData:rawData];
+ [self addReadData:rawData networkType:networkType];
[_socket readDataToLength:5 withTimeout:-1 tag:MTTcpSocksReceiveComplexLength];
return;
} else {
- [self addReadData:rawData];
+ [self addReadData:rawData networkType:networkType];
}
}
@@ -1775,15 +1776,15 @@ struct ctr_state {
[_receivedDataBuffer replaceBytesInRange:NSMakeRange(0, _pendingReceiveData.length) withBytes:nil length:0];
int tag = _pendingReceiveData.tag;
_pendingReceiveData = nil;
- [self processReceivedData:rawData tag:tag];
+ [self processReceivedData:rawData tag:tag networkType:_lastNetworkType];
}
}
-- (void)addReadData:(NSData *)data {
+- (void)addReadData:(NSData *)data networkType:(int32_t)networkType {
if (_pendingReceiveData != nil && _pendingReceiveData.length == data.length) {
int tag = _pendingReceiveData.tag;
_pendingReceiveData = nil;
- [self processReceivedData:data tag:tag];
+ [self processReceivedData:data tag:tag networkType:networkType];
} else {
[_receivedDataBuffer appendData:data];
if (_pendingReceiveData != nil) {
@@ -1792,13 +1793,15 @@ struct ctr_state {
[_receivedDataBuffer replaceBytesInRange:NSMakeRange(0, _pendingReceiveData.length) withBytes:nil length:0];
int tag = _pendingReceiveData.tag;
_pendingReceiveData = nil;
- [self processReceivedData:rawData tag:tag];
+ [self processReceivedData:rawData tag:tag networkType:networkType];
}
}
}
}
-- (void)processReceivedData:(NSData *)rawData tag:(int)tag {
+- (void)processReceivedData:(NSData *)rawData tag:(int)tag networkType:(int32_t)networkType {
+ _lastNetworkType = networkType;
+
NSMutableData *decryptedData = [[NSMutableData alloc] initWithLength:rawData.length];
[_incomingAesCtr encryptIn:rawData.bytes out:decryptedData.mutableBytes len:rawData.length];
@@ -1968,8 +1971,8 @@ struct ctr_state {
if (_connectionReceivedData)
_connectionReceivedData(packetData);
id delegate = _delegate;
- if ([delegate respondsToSelector:@selector(tcpConnectionReceivedData:data:)])
- [delegate tcpConnectionReceivedData:self data:packetData];
+ if ([delegate respondsToSelector:@selector(tcpConnectionReceivedData:networkType:data:)])
+ [delegate tcpConnectionReceivedData:self networkType:networkType data:packetData];
}
if (_useIntermediateFormat) {
diff --git a/submodules/MtProtoKit/Sources/MTTcpTransport.m b/submodules/MtProtoKit/Sources/MTTcpTransport.m
index 5dc5154d52..7c5b0fab46 100644
--- a/submodules/MtProtoKit/Sources/MTTcpTransport.m
+++ b/submodules/MtProtoKit/Sources/MTTcpTransport.m
@@ -452,7 +452,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
}];
}
-- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection data:(NSData *)data
+- (void)tcpConnectionReceivedData:(MTTcpConnection *)connection networkType:(int32_t)networkType data:(NSData *)data
{
MTTcpTransportContext *transportContext = _transportContext;
[[MTTcpTransport tcpTransportQueue] dispatchOnQueue:^
@@ -464,7 +464,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
[self startActualizationPingResendTimer];
__weak MTTcpTransport *weakSelf = self;
- [self _processIncomingData:data scheme:connection.scheme transactionId:connection.internalId requestTransactionAfterProcessing:false decodeResult:^(id transactionId, bool success)
+ [self _processIncomingData:data scheme:connection.scheme networkType:networkType transactionId:connection.internalId requestTransactionAfterProcessing:false decodeResult:^(id transactionId, bool success)
{
if (success)
{
@@ -760,7 +760,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0;
}];
}
-- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
+- (void)mtProto:(MTProto *)__unused mtProto receivedMessage:(MTIncomingMessage *)incomingMessage authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType
{
if ([incomingMessage.body isKindOfClass:[MTPongMessage class]])
{
diff --git a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m
index 30f1fe2f15..deaa634fd2 100644
--- a/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m
+++ b/submodules/MtProtoKit/Sources/MTTimeSyncMessageService.m
@@ -127,7 +127,7 @@
}
}
-- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector
+- (void)mtProto:(MTProto *)mtProto receivedMessage:(MTIncomingMessage *)message authInfoSelector:(MTDatacenterAuthInfoSelector)authInfoSelector networkType:(int32_t)networkType
{
if ([message.body isKindOfClass:[MTFutureSaltsMessage class]] && ((MTFutureSaltsMessage *)message.body).requestMessageId == _currentMessageId)
{
diff --git a/submodules/MtProtoKit/Sources/MTTransport.m b/submodules/MtProtoKit/Sources/MTTransport.m
index ab2511226f..6d23ff4897 100644
--- a/submodules/MtProtoKit/Sources/MTTransport.m
+++ b/submodules/MtProtoKit/Sources/MTTransport.m
@@ -58,12 +58,12 @@
{
}
-- (void)_processIncomingData:(NSData *)data scheme:(MTTransportScheme *)scheme transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult
+- (void)_processIncomingData:(NSData *)data scheme:(MTTransportScheme *)scheme networkType:(int32_t)networkType transactionId:(id)transactionId requestTransactionAfterProcessing:(bool)requestTransactionAfterProcessing decodeResult:(void (^)(id transactionId, bool success))decodeResult
{
id delegate = _delegate;
- if ([delegate respondsToSelector:@selector(transportHasIncomingData:scheme:data:transactionId:requestTransactionAfterProcessing:decodeResult:)])
+ if ([delegate respondsToSelector:@selector(transportHasIncomingData:scheme:networkType:data:transactionId:requestTransactionAfterProcessing:decodeResult:)])
{
- [delegate transportHasIncomingData:self scheme:scheme data:data transactionId:transactionId requestTransactionAfterProcessing:requestTransactionAfterProcessing decodeResult:decodeResult];
+ [delegate transportHasIncomingData:self scheme:scheme networkType:networkType data:data transactionId:transactionId requestTransactionAfterProcessing:requestTransactionAfterProcessing decodeResult:decodeResult];
}
}
diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift
index 8cba2107f6..99364741a6 100644
--- a/submodules/Postbox/Sources/MediaBox.swift
+++ b/submodules/Postbox/Sources/MediaBox.swift
@@ -139,7 +139,7 @@ public final class MediaBox {
private let statusQueue = Queue()
private let concurrentQueue = Queue.concurrentDefaultQueue()
- private let dataQueue = Queue()
+ private let dataQueue = Queue(name: "MediaBox-Data")
private let dataFileManager: MediaBoxFileManager
private let cacheQueue = Queue()
private let timeBasedCleanup: TimeBasedCleanup
@@ -209,6 +209,58 @@ public final class MediaBox {
let _ = self.ensureDirectoryCreated
//self.updateResourceIndex()
+
+ /*#if DEBUG
+ self.dataQueue.async {
+ for _ in 0 ..< 5 {
+ let tempFile = TempBox.shared.tempFile(fileName: "file")
+ print("MediaBox test: file \(tempFile.path)")
+ let queue2 = Queue.concurrentDefaultQueue()
+ if let fileContext = MediaBoxFileContextV2Impl(queue: self.dataQueue, manager: self.dataFileManager, storageBox: self.storageBox, resourceId: tempFile.path.data(using: .utf8)!, path: tempFile.path + "_complete", partialPath: tempFile.path + "_partial", metaPath: tempFile.path + "_partial" + ".meta") {
+ let _ = fileContext.fetched(
+ range: 0 ..< Int64.max,
+ priority: .default,
+ fetch: { ranges in
+ return ranges
+ |> filter { !$0.isEmpty }
+ |> take(1)
+ |> castError(MediaResourceDataFetchError.self)
+ |> mapToSignal { _ in
+ return Signal { subscriber in
+ queue2.async {
+ subscriber.putNext(.resourceSizeUpdated(524288))
+ }
+ queue2.async {
+ subscriber.putNext(.resourceSizeUpdated(393216))
+ }
+ queue2.async {
+ subscriber.putNext(.resourceSizeUpdated(655360))
+ }
+ queue2.async {
+ subscriber.putNext(.resourceSizeUpdated(169608))
+ }
+ queue2.async {
+ subscriber.putNext(.dataPart(resourceOffset: 131072, data: Data(repeating: 0xbb, count: 38536), range: 0 ..< 38536, complete: true))
+ }
+ queue2.async {
+ subscriber.putNext(.dataPart(resourceOffset: 0, data: Data(repeating: 0xaa, count: 131072), range: 0 ..< 131072, complete: false))
+ }
+
+ return EmptyDisposable
+ }
+ }
+ },
+ error: { _ in
+ },
+ completed: {
+ assert(try! Data(contentsOf: URL(fileURLWithPath: tempFile.path + "_complete")) == Data(repeating: 0xaa, count: 131072) + Data(repeating: 0xbb, count: 38536))
+ let _ = fileContext.addReference()
+ }
+ )
+ }
+ }
+ }
+ #endif*/
}
public func setMaxStoreTimes(general: Int32, shortLived: Int32, gigabytesLimit: Int32) {
@@ -688,7 +740,7 @@ public final class MediaBox {
let clippedLowerBound = min(completeSize, max(0, range.lowerBound))
let clippedUpperBound = min(completeSize, max(0, range.upperBound))
if clippedLowerBound < clippedUpperBound && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 {
- file.seek(position: clippedLowerBound)
+ let _ = file.seek(position: clippedLowerBound)
let data = file.readData(count: Int(clippedUpperBound - clippedLowerBound))
subscriber.putNext((data, true))
} else {
@@ -725,7 +777,7 @@ public final class MediaBox {
subscriber.putNext((Data(), true))
subscriber.putCompletion()
} else if clippedUpperBound <= fileSize && (clippedUpperBound - clippedLowerBound) <= 64 * 1024 * 1024 {
- file.seek(position: Int64(clippedLowerBound))
+ let _ = file.seek(position: Int64(clippedLowerBound))
let resultData = file.readData(count: Int(clippedUpperBound - clippedLowerBound))
subscriber.putNext((resultData, true))
subscriber.putCompletion()
diff --git a/submodules/Postbox/Sources/MediaBoxFile.swift b/submodules/Postbox/Sources/MediaBoxFile.swift
index ba0d687a3b..44472bd1ce 100644
--- a/submodules/Postbox/Sources/MediaBoxFile.swift
+++ b/submodules/Postbox/Sources/MediaBoxFile.swift
@@ -88,7 +88,7 @@ final class MediaBoxPartialFile {
guard let clippedRange = fileMap.contains(range) else {
return nil
}
- fd.seek(position: Int64(clippedRange.lowerBound))
+ let _ = fd.seek(position: Int64(clippedRange.lowerBound))
return fd.readData(count: Int(clippedRange.upperBound - clippedRange.lowerBound))
}
@@ -227,7 +227,7 @@ final class MediaBoxPartialFile {
do {
try self.fd.access { fd in
- fd.seek(position: offset)
+ let _ = fd.seek(position: offset)
let written = data.withUnsafeBytes { rawBytes -> Int in
let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
@@ -330,7 +330,7 @@ final class MediaBoxPartialFile {
do {
var result: Data?
try self.fd.access { fd in
- fd.seek(position: Int64(actualRange.lowerBound))
+ let _ = fd.seek(position: Int64(actualRange.lowerBound))
var data = Data(count: actualRange.count)
let dataCount = data.count
let readBytes = data.withUnsafeMutableBytes { rawBytes -> Int in
diff --git a/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift b/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift
index 560920279c..fb866c9146 100644
--- a/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift
+++ b/submodules/Postbox/Sources/MediaBoxFileContextV2Impl.swift
@@ -421,21 +421,28 @@ final class MediaBoxFileContextV2Impl: MediaBoxFileContext {
private func processWrite(resourceOffset: Int64, data: Data, dataRange: Range) {
if let destinationFile = self.destinationFile {
do {
+ var success = true
try destinationFile.access { fd in
- fd.seek(position: resourceOffset)
- let written = data.withUnsafeBytes { rawBytes -> Int in
- let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
-
- return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count)
+ if fd.seek(position: resourceOffset) {
+ let written = data.withUnsafeBytes { rawBytes -> Int in
+ let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
+
+ return fd.write(bytes.advanced(by: Int(dataRange.lowerBound)), count: dataRange.count)
+ }
+ assert(written == dataRange.count)
+ } else {
+ success = false
}
- assert(written == dataRange.count)
}
-
- let range: Range = resourceOffset ..< (resourceOffset + Int64(dataRange.count))
- self.fileMap.fill(range)
- self.fileMap.serialize(manager: self.manager, to: self.metaPath)
-
- self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
+ if success {
+ let range: Range = resourceOffset ..< (resourceOffset + Int64(dataRange.count))
+ self.fileMap.fill(range)
+ self.fileMap.serialize(manager: self.manager, to: self.metaPath)
+
+ self.storageBox.update(id: self.resourceId, size: self.fileMap.sum)
+ } else {
+ postboxLog("MediaBoxFileContextV2Impl: error seeking file to \(resourceOffset) at \(self.partialPath)")
+ }
} catch let e {
postboxLog("MediaBoxFileContextV2Impl: error writing file at \(self.partialPath): \(e)")
}
diff --git a/submodules/Postbox/Sources/MediaBoxFileManager.swift b/submodules/Postbox/Sources/MediaBoxFileManager.swift
index a98a2ae0a1..bc963b8e4c 100644
--- a/submodules/Postbox/Sources/MediaBoxFileManager.swift
+++ b/submodules/Postbox/Sources/MediaBoxFileManager.swift
@@ -32,8 +32,8 @@ final class MediaBoxFileManager {
return self.file.readData(count: count)
}
- func seek(position: Int64) {
- self.file.seek(position: position)
+ func seek(position: Int64) -> Bool {
+ return self.file.seek(position: position)
}
}
diff --git a/submodules/Postbox/Sources/MediaBoxFileMap.swift b/submodules/Postbox/Sources/MediaBoxFileMap.swift
index d353eb61de..e81872da84 100644
--- a/submodules/Postbox/Sources/MediaBoxFileMap.swift
+++ b/submodules/Postbox/Sources/MediaBoxFileMap.swift
@@ -203,7 +203,7 @@ final class MediaBoxFileMap {
}
let _ = try? fileItem.access { file in
- file.seek(position: 0)
+ let _ = file.seek(position: 0)
let buffer = WriteBuffer()
var magic: UInt32 = 0x7bac1487
buffer.write(&magic, offset: 0, length: 4)
diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
index adf80c4f64..bfc61df365 100644
--- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift
@@ -645,7 +645,7 @@ private final class PremiumGiftScreenComponent: CombinedComponent {
price = nil
}
let buttonText = presentationData.strings.Premium_Gift_GiftSubscription(price ?? "—").string
- self.buttonStatePromise.set(.single(AttachmentMainButtonState(text: buttonText, background: .premium, textColor: .white, isVisible: true, progress: self.inProgress ? .center : .none, isEnabled: true)))
+ self.buttonStatePromise.set(.single(AttachmentMainButtonState(text: buttonText, font: .bold, background: .premium, textColor: .white, isVisible: true, progress: self.inProgress ? .center : .none, isEnabled: true)))
}
func buy() {
diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
index 7dd8b4ac1d..25f2ab09a1 100644
--- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
+++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift
@@ -606,14 +606,19 @@ public final class PremiumLimitDisplayComponent: CombinedComponent {
let activityPosition = floor(context.availableSize.width * component.badgeGraphPosition)
+ var inactiveTitleOpacity: CGFloat = 1.0
var inactiveValueOpacity: CGFloat = 1.0
- if inactiveValue.size.width + inactiveTitle.size.width >= activityPosition - 8.0 {
- inactiveValueOpacity = 0.0
+
+ if 12.0 + inactiveValue.size.width + 4.0 + inactiveTitle.size.width + 12.0 >= activityPosition - 8.0 {
+ inactiveTitleOpacity = 0.0
+ if 12.0 + inactiveValue.size.width + 12.0 >= activityPosition - 8.0 {
+ inactiveValueOpacity = 0.0
+ }
}
context.add(inactiveTitle
.position(CGPoint(x: inactiveTitle.size.width / 2.0 + 12.0, y: height - lineHeight / 2.0))
- .opacity(inactiveValueOpacity)
+ .opacity(inactiveTitleOpacity)
)
context.add(inactiveValue
diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift
index 86330a046b..08dff93dac 100644
--- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift
+++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift
@@ -239,9 +239,8 @@ public final class QrCodeScreen: ViewController {
title = self.presentationData.strings.InviteLink_QRCode_Title
text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel
case .chatFolder:
- //TODO:localize
- title = "Invite by QR Code"
- text = "Everyone on Telegram can scan this code to add this folder and join the chats included in this invite link."
+ title = self.presentationData.strings.InviteLink_QRCodeFolder_Title
+ text = self.presentationData.strings.InviteLink_QRCodeFolder_Text
default:
title = ""
text = ""
diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD
index 678a8fd26f..d7c52ee05c 100644
--- a/submodules/SettingsUI/BUILD
+++ b/submodules/SettingsUI/BUILD
@@ -112,6 +112,7 @@ swift_library(
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
"//submodules/MediaPickerUI:MediaPickerUI",
"//submodules/ImageBlur:ImageBlur",
+ "//submodules/AttachmentUI:AttachmentUI",
],
visibility = [
"//visibility:public",
diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift
index 1e06aaf916..b5a667f6c0 100644
--- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift
+++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift
@@ -669,12 +669,8 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
entries.append(.everybody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenEverybody, state.setting == .everybody))
entries.append(.contacts(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenContacts, state.setting == .contacts))
- switch kind {
- case .presence, .voiceCalls, .forwards, .phoneNumber, .voiceMessages, .profilePhoto:
- entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody))
- case .groupInvitations:
- break
- }
+ entries.append(.nobody(presentationData.theme, presentationData.strings.PrivacySettings_LastSeenNobody, state.setting == .nobody))
+
let phoneLink = "https://t.me/+\(phoneNumber)"
if let settingInfoText = settingInfoText {
entries.append(.settingInfo(presentationData.theme, settingInfoText, phoneLink))
diff --git a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift
index 74293fd2ba..205059cf86 100644
--- a/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift
+++ b/submodules/SettingsUI/Sources/Reactions/ReactionChatPreviewItem.swift
@@ -309,6 +309,9 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
if let node = node {
contentSize.height += node.frame.size.height
}
+ if item.reaction == nil {
+ contentSize.height += 34.0
+ }
insets = itemListNeighborsGroupedInsets(neighbors, params)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@@ -333,7 +336,7 @@ class ReactionChatPreviewItemNode: ListViewItemNode {
strongSelf.containerNode.frame = CGRect(origin: CGPoint(), size: contentSize)
- var topOffset: CGFloat = 16.0
+ var topOffset: CGFloat = 16.0 + 17.0
if let node = node {
strongSelf.messageNode = node
if node.supernode == nil {
diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift
index 0dcb6dfb53..d938dd0358 100644
--- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift
+++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift
@@ -25,9 +25,9 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V
controller.selectionBlock = { [weak legacyController] asset, _ in
if let asset = asset {
let controller = WallpaperGalleryController(context: context, source: .asset(asset.backingAsset))
- controller.apply = { [weak legacyController, weak controller] wallpaper, mode, cropRect in
+ controller.apply = { [weak legacyController, weak controller] wallpaper, mode, editedImage, cropRect, brightness in
if let legacyController = legacyController, let controller = controller {
- uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, cropRect: cropRect, completion: { [weak legacyController, weak controller] in
+ uploadCustomWallpaper(context: context, wallpaper: wallpaper, mode: mode, editedImage: nil, cropRect: cropRect, brightness: brightness, completion: { [weak legacyController, weak controller] in
if let legacyController = legacyController, let controller = controller {
legacyController.dismiss()
controller.dismiss(forceAway: true)
@@ -47,8 +47,8 @@ func presentCustomWallpaperPicker(context: AccountContext, present: @escaping (V
})
}
-func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, completion: @escaping () -> Void) {
- let imageSignal: Signal
+func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, completion: @escaping () -> Void) {
+ var imageSignal: Signal
switch wallpaper {
case let .wallpaper(wallpaper, _):
switch wallpaper {
@@ -112,6 +112,10 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
}
}
+ if let editedImage {
+ imageSignal = .single(editedImage)
+ }
+
let _ = (imageSignal
|> map { image -> UIImage in
var croppedImage = UIImage()
@@ -131,12 +135,12 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
if let data = croppedImage.jpegData(compressionQuality: 0.8), let thumbnailImage = thumbnailImage, let thumbnailData = thumbnailImage.jpegData(compressionQuality: 0.4) {
let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
- context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData)
- context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData)
+ context.sharedContext.accountManager.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true)
+ context.account.postbox.mediaBox.storeResourceData(thumbnailResource.id, data: thumbnailData, synchronous: true)
let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
- context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
- context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
+ context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
+ context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
let autoNightModeTriggered = context.sharedContext.currentPresentationData.with {$0 }.autoNightModeTriggered
let accountManager = context.sharedContext.accountManager
@@ -196,29 +200,31 @@ func uploadCustomWallpaper(context: AccountContext, wallpaper: WallpaperGalleryE
}).start()
}
-public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, cropRect: CGRect?, peerId: PeerId, completion: @escaping () -> Void) {
- let imageSignal: Signal
+public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: WallpaperGalleryEntry, mode: WallpaperPresentationOptions, editedImage: UIImage?, cropRect: CGRect?, brightness: CGFloat?, peerId: PeerId, completion: @escaping () -> Void) {
+ var imageSignal: Signal
switch wallpaper {
case let .wallpaper(wallpaper, _):
+ imageSignal = .complete()
switch wallpaper {
case let .file(file):
- if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
- context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data)
+ if let path = context.account.postbox.mediaBox.completedResourcePath(file.file.resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead), let image = UIImage(data: data) {
+ context.sharedContext.accountManager.mediaBox.storeResourceData(file.file.resource.id, data: data, synchronous: true)
let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(file.file.resource, representation: CachedBlurredWallpaperRepresentation(), complete: true, fetch: true).start()
+
+ imageSignal = .single(image)
}
case let .image(representations, _):
for representation in representations {
let resource = representation.resource
if let path = context.account.postbox.mediaBox.completedResourcePath(resource), let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .mappedRead) {
- context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data)
+ context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
let _ = context.sharedContext.accountManager.mediaBox.cachedResourceRepresentation(resource, representation: CachedScaledImageRepresentation(size: CGSize(width: 720.0, height: 720.0), mode: .aspectFit), complete: true, fetch: true).start()
}
}
default:
break
}
- imageSignal = .complete()
completion()
case let .asset(asset):
imageSignal = fetchPhotoLibraryImage(localIdentifier: asset.localIdentifier, thumbnail: false)
@@ -261,6 +267,10 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa
}
}
+ if let editedImage {
+ imageSignal = .single(editedImage)
+ }
+
let _ = (imageSignal
|> map { image -> UIImage in
var croppedImage = UIImage()
@@ -276,7 +286,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa
croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true)
if mode.contains(.blur) {
- croppedImage = blurredImage(croppedImage, radius: 20.0)!
+ croppedImage = blurredImage(croppedImage, radius: 30.0)!
}
let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0))
@@ -291,35 +301,94 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa
context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
context.account.postbox.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
- let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: nil)
- let temporaryWallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], settings)
+ var intensity: Int32?
+ if let brightness {
+ intensity = max(0, min(100, Int32(brightness * 100.0)))
+ }
- let _ = context.account.postbox.transaction({ transaction in
- transaction.updatePeerCachedData(peerIds: Set([peerId])) { _, cachedData in
- if let cachedData = cachedData as? CachedUserData {
- return cachedData.withUpdatedWallpaper(temporaryWallpaper)
- } else {
- return cachedData
- }
- }
- }).start()
+ let settings = WallpaperSettings(blur: mode.contains(.blur), motion: mode.contains(.motion), colors: [], intensity: intensity)
+ let temporaryWallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(thumbnailDimensions), resource: thumbnailResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), TelegramMediaImageRepresentation(dimensions: PixelDimensions(croppedImage.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], settings)
Queue.mainQueue().async {
completion()
}
- let _ = uploadWallpaper(account: context.account, resource: resource, settings: WallpaperSettings(blur: false, motion: mode.contains(.motion), colors: [], intensity: nil), forChat: true).start(next: { status in
- if case let .complete(wallpaper) = status {
- if case let .file(file) = wallpaper {
- context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true)
- for representation in file.file.previewRepresentations {
- context.account.postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id, synchronous: true)
- }
- }
- let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: wallpaper).start()
- }
- })
+ context.account.pendingPeerMediaUploadManager.add(peerId: peerId, content: .wallpaper(temporaryWallpaper))
}
return croppedImage
}).start()
}
+
+class LegacyWallpaperItem: NSObject, TGMediaEditableItem, TGMediaSelectableItem {
+ var isVideo: Bool {
+ return false
+ }
+
+ var uniqueIdentifier: String! {
+ return self.asset.localIdentifier
+ }
+
+ let asset: PHAsset
+ let screenImage: UIImage
+ private(set) var thumbnailResource: TelegramMediaResource?
+ private(set) var imageResource: TelegramMediaResource?
+ let dimensions: CGSize
+
+
+ init(asset: PHAsset, screenImage: UIImage, dimensions: CGSize) {
+ self.asset = asset
+ self.screenImage = screenImage
+ self.dimensions = dimensions
+ }
+
+ var originalSize: CGSize {
+ return self.dimensions
+ }
+
+ func thumbnailImageSignal() -> SSignal! {
+ return SSignal.complete()
+// return SSignal(generator: { subscriber -> SDisposable? in
+// let disposable = self.thumbnailImage.start(next: { image in
+// subscriber.putNext(image)
+// subscriber.putCompletion()
+// })
+//
+// return SBlockDisposable(block: {
+// disposable.dispose()
+// })
+// })
+ }
+
+ func screenImageSignal(_ position: TimeInterval) -> SSignal! {
+ return SSignal.single(self.screenImage)
+ }
+
+ var originalImage: Signal {
+ return fetchPhotoLibraryImage(localIdentifier: self.asset.localIdentifier, thumbnail: false)
+ |> filter { value in
+ return !(value?.1 ?? true)
+ }
+ |> mapToSignal { result -> Signal in
+ if let result = result {
+ return .single(result.0)
+ } else {
+ return .complete()
+ }
+ }
+ }
+
+ func originalImageSignal(_ position: TimeInterval) -> SSignal! {
+ return SSignal(generator: { subscriber -> SDisposable? in
+ let disposable = self.originalImage.start(next: { image in
+ subscriber.putNext(image)
+ if !image.degraded() {
+ subscriber.putCompletion()
+ }
+ })
+
+ return SBlockDisposable(block: {
+ disposable.dispose()
+ })
+ })
+ }
+}
diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift
index 41b777cc59..0dcdee3f62 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorController.swift
@@ -119,7 +119,8 @@ final class ThemeAccentColorController: ViewController {
} else {
self.navigationItem.titleView = self.segmentedTitleView
}
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
+
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
}
required init(coder aDecoder: NSCoder) {
@@ -129,6 +130,10 @@ final class ThemeAccentColorController: ViewController {
deinit {
self.applyDisposable.dispose()
}
+
+ @objc private func cancelPressed() {
+ self.dismiss()
+ }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
@@ -169,6 +174,12 @@ final class ThemeAccentColorController: ViewController {
}
}
+ if case let .peer(peer) = strongSelf.resultMode {
+ let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: coloredWallpaper).start()
+ strongSelf.completion?()
+ return
+ }
+
let prepareWallpaper: Signal
if let patternWallpaper = state.patternWallpaper, case let .file(file) = patternWallpaper, !state.backgroundColors.isEmpty {
let resource = file.file.resource
diff --git a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift
index 9ead4f5c37..2044e541f8 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemeAccentColorControllerNode.swift
@@ -152,6 +152,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
private let context: AccountContext
private var theme: PresentationTheme
private let mode: ThemeAccentColorControllerMode
+ private let resultMode: ThemeAccentColorController.ResultMode
private var presentationData: PresentationData
private let animationCache: AnimationCache
@@ -227,6 +228,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
init(context: AccountContext, mode: ThemeAccentColorControllerMode, resultMode: ThemeAccentColorController.ResultMode, theme: PresentationTheme, wallpaper: TelegramWallpaper, dismiss: @escaping () -> Void, apply: @escaping (ThemeColorState, UIColor?) -> Void, ready: Promise) {
self.context = context
self.mode = mode
+ self.resultMode = resultMode
self.state = ThemeColorState()
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.theme = theme
@@ -311,6 +313,7 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
doneButtonType = .set
}
self.toolbarNode = WallpaperGalleryToolbarNode(theme: self.theme, strings: self.presentationData.strings, doneButtonType: doneButtonType)
+ self.toolbarNode.dark = true
self.toolbarNode.setDoneIsSolid(true, transition: .immediate)
self.maskNode = ASImageNode()
@@ -766,6 +769,8 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
} else {
if case .edit(_, _, _, _, _, true, _) = self.mode {
doneButtonType = .proceed
+ } else if case .peer = self.resultMode {
+ doneButtonType = .setPeer
} else {
doneButtonType = .set
}
@@ -1173,12 +1178,16 @@ final class ThemeAccentColorControllerNode: ASDisplayNode, UIScrollViewDelegate
let colorPanelHeight = max(standardInputHeight, layout.inputHeight ?? 0.0) - bottomInset + inputFieldPanelHeight
var colorPanelOffset: CGFloat = 0.0
+ var colorPanelY = layout.size.height - bottomInset - colorPanelHeight
if self.state.colorPanelCollapsed {
colorPanelOffset = colorPanelHeight
+ colorPanelY = layout.size.height
}
- let colorPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomInset - colorPanelHeight + colorPanelOffset), size: CGSize(width: layout.size.width, height: colorPanelHeight))
+ let colorPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: colorPanelY), size: CGSize(width: layout.size.width, height: colorPanelHeight))
bottomInset += (colorPanelHeight - colorPanelOffset)
+ self.toolbarNode.setDoneIsSolid(!self.state.colorPanelCollapsed, transition: transition)
+
if bottomInset + navigationBarHeight > bounds.height {
return
}
diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift
index bc402517a9..75ec7e4f8a 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridController.swift
@@ -9,9 +9,10 @@ import LegacyComponents
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
+import AttachmentUI
-private func availableGradients(theme: PresentationTheme) -> [[UInt32]] {
- if theme.overallDarkAppearance {
+private func availableGradients(dark: Bool) -> [[UInt32]] {
+ if dark {
return [
[0x1e3557, 0x151a36, 0x1c4352, 0x2a4541] as [UInt32],
[0x1d223f, 0x1d1832, 0x1b2943, 0x141631] as [UInt32],
@@ -38,8 +39,8 @@ private func availableGradients(theme: PresentationTheme) -> [[UInt32]] {
}
}
-private func availableColors(theme: PresentationTheme) -> [UInt32] {
- if theme.overallDarkAppearance {
+private func availableColors(dark: Bool) -> [UInt32] {
+ if dark {
return [
0x1D2D3C,
0x111B26,
@@ -102,7 +103,7 @@ private func availableColors(theme: PresentationTheme) -> [UInt32] {
}
}
-public final class ThemeColorsGridController: ViewController {
+public final class ThemeColorsGridController: ViewController, AttachmentContainable {
public enum Mode {
case `default`
case peer(EnginePeer)
@@ -145,14 +146,19 @@ public final class ThemeColorsGridController: ViewController {
private var previousContentOffset: GridNodeVisibleContentOffset?
- public init(context: AccountContext, mode: Mode = .default) {
+ fileprivate var mainButtonState: AttachmentMainButtonState?
+
+ var pushController: (ViewController) -> Void = { _ in }
+ var dismissControllers: (() -> Void)?
+ var openGallery: (() -> Void)?
+
+ public init(context: AccountContext, mode: Mode = .default, canDelete: Bool = false) {
self.context = context
self.mode = mode
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
- self.title = self.presentationData.strings.WallpaperColors_Title
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.scrollToTop = { [weak self] in
@@ -174,6 +180,20 @@ public final class ThemeColorsGridController: ViewController {
}
}
})
+
+ if case .peer = mode {
+ self.title = self.presentationData.strings.Conversation_Theme_ChooseColorTitle
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
+ self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Conversation_Theme_SetCustomColor, style: .plain, target: self, action: #selector(self.customPressed))
+ } else {
+ self.title = self.presentationData.strings.WallpaperColors_Title
+ }
+
+ self.pushController = { [weak self] controller in
+ self?.push(controller)
+ }
+
+ self.mainButtonState = AttachmentMainButtonState(text: self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper, font: .regular, background: .color(.clear), textColor: self.presentationData.theme.actionSheet.controlAccentColor, isVisible: true, progress: .none, isEnabled: true)
}
required public init(coder aDecoder: NSCoder) {
@@ -184,6 +204,14 @@ public final class ThemeColorsGridController: ViewController {
self.presentationDataDisposable?.dispose()
}
+ @objc private func cancelPressed() {
+ self.dismiss()
+ }
+
+ @objc private func customPressed() {
+ self.presentColorPicker()
+ }
+
private func updateThemeAndStrings() {
self.title = self.presentationData.strings.WallpaperColors_Title
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
@@ -194,56 +222,77 @@ public final class ThemeColorsGridController: ViewController {
}
}
+ private func presentColorPicker() {
+ let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] sharedData in
+ guard let strongSelf = self else {
+ return
+ }
+ let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
+
+ let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered
+ let themeReference: PresentationThemeReference
+ if autoNightModeTriggered {
+ themeReference = settings.automaticThemeSwitchSetting.theme
+ } else {
+ themeReference = settings.theme
+ }
+
+ let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode)
+ controller.completion = { [weak self, weak controller] in
+ if let strongSelf = self {
+ if let dismissControllers = strongSelf.dismissControllers {
+ dismissControllers()
+ controller?.dismiss(animated: true)
+ } else if let navigationController = strongSelf.navigationController as? NavigationController {
+ var controllers = navigationController.viewControllers
+ controllers = controllers.filter { controller in
+ if controller is ThemeColorsGridController {
+ return false
+ }
+ return true
+ }
+ navigationController.setViewControllers(controllers, animated: false)
+ controllers = controllers.filter { controller in
+ if controller is ThemeAccentColorController {
+ return false
+ }
+ return true
+ }
+ navigationController.setViewControllers(controllers, animated: true)
+ }
+ }
+ }
+ strongSelf.pushController(controller)
+ })
+ }
+
public override func loadDisplayNode() {
- self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(theme: self.presentationData.theme), colors: availableColors(theme: self.presentationData.theme), push: { [weak self] controller in
- self?.push(controller)
+ var dark = false
+ if case .default = self.mode {
+ dark = self.presentationData.theme.overallDarkAppearance
+ }
+ self.displayNode = ThemeColorsGridControllerNode(context: self.context, presentationData: self.presentationData, controller: self, gradients: availableGradients(dark: dark), colors: availableColors(dark: dark), push: { [weak self] controller in
+ self?.pushController(controller)
}, pop: { [weak self] in
if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
let _ = navigationController.popViewController(animated: true)
}
}, presentColorPicker: { [weak self] in
if let strongSelf = self {
- let _ = (strongSelf.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
- |> take(1)
- |> deliverOnMainQueue).start(next: { [weak self] sharedData in
- guard let strongSelf = self else {
- return
- }
- let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
-
- let autoNightModeTriggered = strongSelf.presentationData.autoNightModeTriggered
- let themeReference: PresentationThemeReference
- if autoNightModeTriggered {
- themeReference = settings.automaticThemeSwitchSetting.theme
- } else {
- themeReference = settings.theme
- }
-
- let controller = ThemeAccentColorController(context: strongSelf.context, mode: .background(themeReference: themeReference), resultMode: strongSelf.mode.colorPickerMode)
- controller.completion = { [weak self] in
- if let strongSelf = self, let navigationController = strongSelf.navigationController as? NavigationController {
- var controllers = navigationController.viewControllers
- controllers = controllers.filter { controller in
- if controller is ThemeColorsGridController {
- return false
- }
- return true
- }
- navigationController.setViewControllers(controllers, animated: false)
- controllers = controllers.filter { controller in
- if controller is ThemeAccentColorController {
- return false
- }
- return true
- }
- navigationController.setViewControllers(controllers, animated: true)
- }
- }
- strongSelf.push(controller)
- })
+ strongSelf.presentColorPicker()
}
})
+ let transitionOffset: CGFloat
+ switch self.mode {
+ case .default:
+ transitionOffset = 30.0
+ case .peer:
+ transitionOffset = 2.0
+ }
+
self.controllerNode.gridNode.visibleContentOffsetChanged = { [weak self] offset in
if let strongSelf = self {
var previousContentOffsetValue: CGFloat?
@@ -253,12 +302,12 @@ public final class ThemeColorsGridController: ViewController {
switch offset {
case let .known(value):
let transition: ContainedViewLayoutTransition
- if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > 30.0 {
+ if let previousContentOffsetValue = previousContentOffsetValue, value <= 0.0, previousContentOffsetValue > transitionOffset {
transition = .animated(duration: 0.2, curve: .easeInOut)
} else {
transition = .immediate
}
- strongSelf.navigationBar?.updateBackgroundAlpha(min(30.0, value) / 30.0, transition: transition)
+ strongSelf.navigationBar?.updateBackgroundAlpha(min(transitionOffset, value) / transitionOffset, transition: transition)
case .unknown, .none:
strongSelf.navigationBar?.updateBackgroundAlpha(1.0, transition: .immediate)
}
@@ -279,4 +328,83 @@ public final class ThemeColorsGridController: ViewController {
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
}
+
+ @objc fileprivate func mainButtonPressed() {
+ self.dismiss(animated: true)
+ self.openGallery?()
+ }
+
+ public var requestAttachmentMenuExpansion: () -> Void = {}
+ public var updateNavigationStack: (@escaping ([AttachmentContainable]) -> ([AttachmentContainable], AttachmentMediaPickerContext?)) -> Void = { _ in }
+ public var updateTabBarAlpha: (CGFloat, ContainedViewLayoutTransition) -> Void = { _, _ in }
+ public var cancelPanGesture: () -> Void = { }
+ public var isContainerPanning: () -> Bool = { return false }
+ public var isContainerExpanded: () -> Bool = { return false }
+
+ public var mediaPickerContext: AttachmentMediaPickerContext? {
+ return ThemeColorsGridContext(controller: self)
+ }
+}
+
+private final class ThemeColorsGridContext: AttachmentMediaPickerContext {
+ private weak var controller: ThemeColorsGridController?
+
+ var selectionCount: Signal {
+ return .single(0)
+ }
+
+ var caption: Signal {
+ return .single(nil)
+ }
+
+ public var loadingProgress: Signal {
+ return .single(nil)
+ }
+
+ public var mainButtonState: Signal {
+ return .single(self.controller?.mainButtonState)
+ }
+
+ init(controller: ThemeColorsGridController) {
+ self.controller = controller
+ }
+
+ func setCaption(_ caption: NSAttributedString) {
+ }
+
+ func send(mode: AttachmentMediaPickerSendMode, attachmentMode: AttachmentMediaPickerAttachmentMode) {
+ }
+
+ func schedule() {
+ }
+
+ func mainButtonAction() {
+ self.controller?.mainButtonPressed()
+ }
+}
+
+
+public func standaloneColorPickerController(
+ context: AccountContext,
+ updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil,
+ peer: EnginePeer,
+ push: @escaping (ViewController) -> Void,
+ openGallery: @escaping () -> Void
+) -> ViewController {
+ let controller = AttachmentController(context: context, updatedPresentationData: updatedPresentationData, chatLocation: nil, buttons: [.standalone], initialButton: .standalone, fromMenu: false, hasTextInput: false, makeEntityInputView: {
+ return nil
+ })
+ //controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
+ controller.requestController = { _, present in
+ let colorPickerController = ThemeColorsGridController(context: context, mode: .peer(peer))
+ colorPickerController.pushController = { controller in
+ push(controller)
+ }
+ colorPickerController.dismissControllers = { [weak controller] in
+ controller?.dismiss(animated: true)
+ }
+ colorPickerController.openGallery = openGallery
+ present(colorPickerController, colorPickerController.mediaPickerContext)
+ }
+ return controller
}
diff --git a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift
index 72d0a6ca95..64e2c7f57b 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemeColorsGridControllerNode.swift
@@ -73,7 +73,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
let ready = ValuePromise()
- private var backgroundNode: ASDisplayNode
+ private var topBackgroundNode: ASDisplayNode
private var separatorNode: ASDisplayNode
private let customColorItemNode: ItemListActionItemNode
@@ -102,8 +102,8 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
self.rightOverlayNode = ASDisplayNode()
self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
- self.backgroundNode = ASDisplayNode()
- self.backgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
+ self.topBackgroundNode = ASDisplayNode()
+ self.topBackgroundNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
self.separatorNode = ASDisplayNode()
self.separatorNode.backgroundColor = presentationData.theme.list.itemBlocksSeparatorColor
@@ -121,33 +121,44 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
- self.gridNode.addSubnode(self.backgroundNode)
- self.gridNode.addSubnode(self.separatorNode)
- self.gridNode.addSubnode(self.customColorItemNode)
+ if case .default = controller.mode {
+ self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
+ self.gridNode.addSubnode(self.topBackgroundNode)
+ self.gridNode.addSubnode(self.separatorNode)
+ self.gridNode.addSubnode(self.customColorItemNode)
+ } else {
+ self.backgroundColor = presentationData.theme.list.plainBackgroundColor
+ }
self.addSubnode(self.gridNode)
let previousEntries = Atomic<[ThemeColorsGridControllerEntry]?>(value: nil)
-
- let dismissControllers = { [weak self] in
- if let self, let navigationController = self.controller?.navigationController as? NavigationController {
- let controllers = navigationController.viewControllers.filter({ controller in
- if controller is ThemeColorsGridController || controller is WallpaperGalleryController {
- return false
- }
- return true
- })
- navigationController.setViewControllers(controllers, animated: true)
- }
- }
-
+
let interaction = ThemeColorsGridControllerInteraction(openWallpaper: { [weak self] wallpaper in
if let strongSelf = self {
let entries = previousEntries.with { $0 }
if let entries = entries, !entries.isEmpty {
let wallpapers = entries.map { $0.wallpaper }
let controller = WallpaperGalleryController(context: context, source: .list(wallpapers: wallpapers, central: wallpaper, type: .colors), mode: strongSelf.controller?.mode.galleryMode ?? .default)
+
+ let dismissControllers = { [weak self, weak controller] in
+ if let self {
+ if let dismissControllers = self.controller?.dismissControllers {
+ dismissControllers()
+ controller?.dismiss(animated: true)
+ } else if let navigationController = self.controller?.navigationController as? NavigationController {
+ let controllers = navigationController.viewControllers.filter({ controller in
+ if controller is ThemeColorsGridController || controller is WallpaperGalleryController {
+ return false
+ }
+ return true
+ })
+ navigationController.setViewControllers(controllers, animated: true)
+ }
+ }
+ }
+
controller.navigationPresentation = .modal
- controller.apply = { [weak self] wallpaper, _, _ in
+ controller.apply = { [weak self] wallpaper, _, _, _, _ in
if let strongSelf = self, let mode = strongSelf.controller?.mode, case let .peer(peer) = mode, case let .wallpaper(wallpaperValue, _) = wallpaper {
let _ = (strongSelf.context.engine.themes.setChatWallpaper(peerId: peer.id, wallpaper: wallpaperValue)
|> deliverOnMainQueue).start(completed: {
@@ -240,9 +251,13 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
- self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
- self.leftOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
- self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
+ if let controller = self.controller, case .default = controller.mode {
+ self.backgroundColor = presentationData.theme.list.itemBlocksBackgroundColor
+ self.leftOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
+ self.rightOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
+ } else {
+ self.backgroundColor = presentationData.theme.list.plainBackgroundColor
+ }
self.customColorItem = ItemListActionItem(presentationData: ItemListPresentationData(presentationData), title: presentationData.strings.WallpaperColors_SetCustomColor, kind: .generic, alignment: .natural, sectionId: 0, style: .blocks, action: { [weak self] in
self?.presentColorPicker()
@@ -272,6 +287,9 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
+ guard let controller = self.controller else {
+ return
+ }
let hadValidLayout = self.validLayout != nil
var insets = layout.insets(options: [.input])
@@ -280,7 +298,18 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
insets.right = layout.safeInsets.right
let scrollIndicatorInsets = insets
- let minSpacing: CGFloat = 8.0
+ let itemsPerRow: Int
+ if case .compact = layout.metrics.widthClass {
+ switch layout.orientation {
+ case .portrait:
+ itemsPerRow = 3
+ case .landscape:
+ itemsPerRow = 5
+ }
+ } else {
+ itemsPerRow = 3
+ }
+
let referenceImageSize: CGSize
let screenWidth = min(layout.size.width, layout.size.height)
if screenWidth >= 375.0 {
@@ -288,29 +317,56 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
} else {
referenceImageSize = CGSize(width: 91.0, height: 91.0)
}
- let imageCount = Int((layout.size.width - insets.left - insets.right - minSpacing * 2.0) / (referenceImageSize.width + minSpacing))
- let imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((layout.size.width - CGFloat(imageCount + 1) * minSpacing) / CGFloat(imageCount)), height: referenceImageSize.height))
- let spacing = floor((layout.size.width - CGFloat(imageCount) * imageSize.width) / CGFloat(imageCount + 1))
+
+ let width = layout.size.width - layout.safeInsets.left - layout.safeInsets.right
+ let imageSize: CGSize
+ let spacing: CGFloat
+ var fillWidth: Bool?
+ if case .peer = controller.mode {
+ spacing = 1.0
+
+ let itemWidth = floorToScreenPixels((width - spacing * CGFloat(itemsPerRow - 1)) / CGFloat(itemsPerRow))
+ imageSize = CGSize(width: itemWidth, height: itemWidth)
+ fillWidth = true
+ } else {
+ let minSpacing = 8.0
+
+ imageSize = referenceImageSize.aspectFilled(CGSize(width: floor((width - CGFloat(itemsPerRow + 1) * minSpacing) / CGFloat(itemsPerRow)), height: referenceImageSize.height))
+ spacing = floor((width - CGFloat(itemsPerRow) * imageSize.width) / CGFloat(itemsPerRow + 1))
+ }
+
+ let buttonTopInset: CGFloat = 32.0
+ let buttonHeight: CGFloat = 44.0
+ let buttonBottomInset: CGFloat = 35.0
+
+ var buttonInset: CGFloat = buttonTopInset + buttonHeight + buttonBottomInset
+ var buttonOffset = buttonInset + 10.0
var listInsets = insets
- if layout.size.width >= 375.0 {
- let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
- listInsets.left += inset
- listInsets.right += inset
-
- if self.leftOverlayNode.supernode == nil {
- self.gridNode.addSubnode(self.leftOverlayNode)
- }
- if self.rightOverlayNode.supernode == nil {
- self.gridNode.addSubnode(self.rightOverlayNode)
+ if case .default = controller.mode {
+ if layout.size.width >= 375.0 {
+ let inset = max(16.0, floor((layout.size.width - 674.0) / 2.0))
+ listInsets.left += inset
+ listInsets.right += inset
+
+ if self.leftOverlayNode.supernode == nil {
+ self.gridNode.addSubnode(self.leftOverlayNode)
+ }
+ if self.rightOverlayNode.supernode == nil {
+ self.gridNode.addSubnode(self.rightOverlayNode)
+ }
+ } else {
+ if self.leftOverlayNode.supernode != nil {
+ self.leftOverlayNode.removeFromSupernode()
+ }
+ if self.rightOverlayNode.supernode != nil {
+ self.rightOverlayNode.removeFromSupernode()
+ }
}
} else {
- if self.leftOverlayNode.supernode != nil {
- self.leftOverlayNode.removeFromSupernode()
- }
- if self.rightOverlayNode.supernode != nil {
- self.rightOverlayNode.removeFromSupernode()
- }
+ self.customColorItemNode.isHidden = true
+ buttonOffset = 0.0
+ buttonInset = 0.0
}
let makeColorLayout = self.customColorItemNode.asyncLayout()
@@ -318,14 +374,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
let (colorLayout, colorApply) = makeColorLayout(self.customColorItem, params, ItemListNeighbors(top: .none, bottom: .none))
colorApply()
- let buttonTopInset: CGFloat = 32.0
- let buttonHeight: CGFloat = 44.0
- let buttonBottomInset: CGFloat = 35.0
-
- let buttonInset: CGFloat = buttonTopInset + buttonHeight + buttonBottomInset
- let buttonOffset = buttonInset + 10.0
-
- transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0)))
+ transition.updateFrame(node: self.topBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset - 500.0), size: CGSize(width: layout.size.width, height: buttonInset + 500.0)))
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonInset - UIScreenPixel), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.customColorItemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -buttonOffset + buttonTopInset), size: colorLayout.contentSize))
@@ -334,7 +383,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
insets.top += spacing + buttonInset
- self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: nil, lineSpacing: spacing, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
+ self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: insets, scrollIndicatorInsets: scrollIndicatorInsets, preloadSize: 300.0, type: .fixed(itemSize: imageSize, fillWidth: fillWidth, lineSpacing: spacing, itemSpacing: fillWidth != nil ? spacing : nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
self.gridNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
@@ -350,7 +399,7 @@ final class ThemeColorsGridControllerNode: ASDisplayNode {
self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .animated(duration: 0.25, curve: .easeInOut), directionHint: .up, adjustForSection: true, adjustForTopInset: true), updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in })
- self.backgroundNode.layer.animatePosition(from: self.backgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.backgroundNode.layer.position, duration: duration)
+ self.topBackgroundNode.layer.animatePosition(from: self.topBackgroundNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.topBackgroundNode.layer.position, duration: duration)
self.separatorNode.layer.animatePosition(from: self.separatorNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.separatorNode.layer.position, duration: duration)
self.customColorItemNode.layer.animatePosition(from: self.customColorItemNode.layer.position.offsetBy(dx: 0.0, dy: -offset), to: self.customColorItemNode.layer.position, duration: duration)
}
diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift
index 852059bd5f..048a0af79e 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift
@@ -120,9 +120,9 @@ public final class ThemeGridController: ViewController {
self.displayNode = ThemeGridControllerNode(context: self.context, presentationData: self.presentationData, presentPreviewController: { [weak self] source in
if let strongSelf = self {
let controller = WallpaperGalleryController(context: strongSelf.context, source: source)
- controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in
+ controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness in
if let strongSelf = self {
- uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak self, weak controller] in
+ uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak self, weak controller] in
if let strongSelf = self {
strongSelf.deactivateSearch(animated: false)
strongSelf.controllerNode.scrollToTop(animated: false)
@@ -142,15 +142,15 @@ public final class ThemeGridController: ViewController {
}
}, presentGallery: { [weak self] in
if let strongSelf = self {
- let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, true))
+ let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper))
controller.customSelection = { [weak self] asset in
guard let strongSelf = self else {
return
}
let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset))
- controller.apply = { [weak self, weak controller] wallpaper, options, cropRect in
+ controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness in
if let strongSelf = self, let controller = controller {
- uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, completion: { [weak controller] in
+ uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak controller] in
if let controller = controller {
controller.dismiss(forceAway: true)
}
diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift
index 8307d58d62..49426bc211 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewController.swift
@@ -142,7 +142,7 @@ public final class ThemePreviewController: ViewController {
let titleView = CounterContollerTitleView(theme: self.previewTheme)
titleView.title = CounterContollerTitle(title: themeName, counter: hasInstallsCount ? " " : "")
self.navigationItem.titleView = titleView
- self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: UIView())
+ self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
self.statusBar.statusBarStyle = self.previewTheme.rootController.statusBarStyle.style
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
@@ -179,6 +179,10 @@ public final class ThemePreviewController: ViewController {
self.applyDisposable.dispose()
}
+ @objc private func cancelPressed() {
+ self.dismiss(animated: true)
+ }
+
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
diff --git a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift
index 55892a0071..dbd2386ad8 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemePreviewControllerNode.swift
@@ -244,6 +244,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
guard let strongSelf = self else {
return
}
+ var useDarkButton = true
if case let .file(file) = wallpaper {
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let displaySize = dimensions.cgSize.dividedByScreenScale().integralFloor
@@ -258,6 +259,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
if wallpaper.isPattern {
signal = .complete()
} else {
+ useDarkButton = false
signal = .complete()
}
strongSelf.remoteChatBackgroundNode.setSignal(signal)
@@ -296,6 +298,7 @@ final class ThemePreviewControllerNode: ASDisplayNode, UIScrollViewDelegate {
}
strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments))()
+ strongSelf.toolbarNode.dark = useDarkButton
}
})
}
diff --git a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift
index 93eab9cfd5..7fa3424a02 100644
--- a/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift
+++ b/submodules/SettingsUI/Sources/Themes/ThemeSettingsController.swift
@@ -58,13 +58,13 @@ private final class ThemeSettingsControllerArguments {
let openBubbleSettings: () -> Void
let openPowerSavingSettings: () -> Void
let openStickersAndEmoji: () -> Void
- let disableAnimations: (Bool) -> Void
+ let toggleShowNextMediaOnTap: (Bool) -> Void
let selectAppIcon: (PresentationAppIcon) -> Void
let editTheme: (PresentationCloudTheme) -> Void
let themeContextAction: (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void
let colorContextAction: (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void
- init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, disableAnimations: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
+ init(context: AccountContext, selectTheme: @escaping (PresentationThemeReference) -> Void, openThemeSettings: @escaping () -> Void, openWallpaperSettings: @escaping () -> Void, selectAccentColor: @escaping (PresentationThemeAccentColor?) -> Void, openAccentColorPicker: @escaping (PresentationThemeReference, Bool) -> Void, toggleNightTheme: @escaping (Bool) -> Void, openAutoNightTheme: @escaping () -> Void, openTextSize: @escaping () -> Void, openBubbleSettings: @escaping () -> Void, openPowerSavingSettings: @escaping () -> Void, openStickersAndEmoji: @escaping () -> Void, toggleShowNextMediaOnTap: @escaping (Bool) -> Void, selectAppIcon: @escaping (PresentationAppIcon) -> Void, editTheme: @escaping (PresentationCloudTheme) -> Void, themeContextAction: @escaping (Bool, PresentationThemeReference, ASDisplayNode, ContextGesture?) -> Void, colorContextAction: @escaping (Bool, PresentationThemeReference, ThemeSettingsColorOption?, ASDisplayNode, ContextGesture?) -> Void) {
self.context = context
self.selectTheme = selectTheme
self.openThemeSettings = openThemeSettings
@@ -77,7 +77,7 @@ private final class ThemeSettingsControllerArguments {
self.openBubbleSettings = openBubbleSettings
self.openPowerSavingSettings = openPowerSavingSettings
self.openStickersAndEmoji = openStickersAndEmoji
- self.disableAnimations = disableAnimations
+ self.toggleShowNextMediaOnTap = toggleShowNextMediaOnTap
self.selectAppIcon = selectAppIcon
self.editTheme = editTheme
self.themeContextAction = themeContextAction
@@ -128,8 +128,8 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
case powerSaving
case stickersAndEmoji
case otherHeader(PresentationTheme, String)
- case animations(PresentationTheme, String, Bool)
- case animationsInfo(PresentationTheme, String)
+ case showNextMediaOnTap(PresentationTheme, String, Bool)
+ case showNextMediaOnTapInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
@@ -143,7 +143,7 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return ThemeSettingsControllerSection.icon.rawValue
case .powerSaving, .stickersAndEmoji:
return ThemeSettingsControllerSection.message.rawValue
- case .otherHeader, .animations, .animationsInfo:
+ case .otherHeader, .showNextMediaOnTap, .showNextMediaOnTapInfo:
return ThemeSettingsControllerSection.other.rawValue
}
}
@@ -178,9 +178,9 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
return 12
case .otherHeader:
return 13
- case .animations:
+ case .showNextMediaOnTap:
return 14
- case .animationsInfo:
+ case .showNextMediaOnTapInfo:
return 15
}
}
@@ -271,14 +271,14 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
} else {
return false
}
- case let .animations(lhsTheme, lhsTitle, lhsValue):
- if case let .animations(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
+ case let .showNextMediaOnTap(lhsTheme, lhsTitle, lhsValue):
+ if case let .showNextMediaOnTap(rhsTheme, rhsTitle, rhsValue) = rhs, lhsTheme === rhsTheme, lhsTitle == rhsTitle, lhsValue == rhsValue {
return true
} else {
return false
}
- case let .animationsInfo(lhsTheme, lhsText):
- if case let .animationsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
+ case let .showNextMediaOnTapInfo(lhsTheme, lhsText):
+ if case let .showNextMediaOnTapInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
@@ -343,17 +343,17 @@ private enum ThemeSettingsControllerEntry: ItemListNodeEntry {
})
case let .otherHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
- case let .animations(_, title, value):
+ case let .showNextMediaOnTap(_, title, value):
return ItemListSwitchItem(presentationData: presentationData, title: title, value: value, sectionId: self.section, style: .blocks, updated: { value in
- arguments.disableAnimations(value)
+ arguments.toggleShowNextMediaOnTap(value)
}, tag: ThemeSettingsEntryTag.animations)
- case let .animationsInfo(_, text):
+ case let .showNextMediaOnTapInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
}
}
}
-private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] {
+private func themeSettingsControllerEntries(presentationData: PresentationData, presentationThemeSettings: PresentationThemeSettings, mediaSettings: MediaDisplaySettings, themeReference: PresentationThemeReference, availableThemes: [PresentationThemeReference], availableAppIcons: [PresentationAppIcon], currentAppIconName: String?, isPremium: Bool, chatThemes: [PresentationThemeReference], animatedEmojiStickers: [String: [StickerPackItem]]) -> [ThemeSettingsControllerEntry] {
var entries: [ThemeSettingsControllerEntry] = []
let strings = presentationData.strings
@@ -404,8 +404,8 @@ private func themeSettingsControllerEntries(presentationData: PresentationData,
}
entries.append(.otherHeader(presentationData.theme, strings.Appearance_Other.uppercased()))
- entries.append(.animations(presentationData.theme, strings.Appearance_ReduceMotion, presentationData.reduceMotion))
- entries.append(.animationsInfo(presentationData.theme, strings.Appearance_ReduceMotionInfo))
+ entries.append(.showNextMediaOnTap(presentationData.theme, strings.Appearance_ShowNextMediaOnTap, mediaSettings.showNextMediaOnTap))
+ entries.append(.showNextMediaOnTapInfo(presentationData.theme, strings.Appearance_ShowNextMediaOnTapInfo))
return entries
}
@@ -522,9 +522,9 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
pushControllerImpl?(installedStickerPacksController(context: context, mode: .general, archivedPacks: archivedStickerPacks, updatedPacks: { _ in
}))
})
- }, disableAnimations: { reduceMotion in
- let _ = updatePresentationThemeSettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
- return current.withUpdatedReduceMotion(reduceMotion)
+ }, toggleShowNextMediaOnTap: { value in
+ let _ = updateMediaDisplaySettingsInteractively(accountManager: context.sharedContext.accountManager, { current in
+ return current.withUpdatedShowNextMediaOnTap(value)
}).start()
}, selectAppIcon: { icon in
let _ = (context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
@@ -1000,10 +1000,11 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
})
})
- let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId))
+ let signal = combineLatest(queue: .mainQueue(), context.sharedContext.presentationData, context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings, SharedDataKeys.chatThemes, ApplicationSpecificSharedDataKeys.mediaDisplaySettings]), cloudThemes.get(), availableAppIcons, currentAppIconName.get(), removedThemeIndexesPromise.get(), animatedEmojiStickers, context.account.postbox.peerView(id: context.account.peerId))
|> map { presentationData, sharedData, cloudThemes, availableAppIcons, currentAppIconName, removedThemeIndexes, animatedEmojiStickers, peerView -> (ItemListControllerState, (ItemListNodeState, Any)) in
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) ?? PresentationThemeSettings.defaultSettings
-
+ let mediaSettings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) ?? MediaDisplaySettings.defaultSettings
+
let isPremium = peerView.peers[peerView.peerId]?.isPremium ?? false
let themeReference: PresentationThemeReference
@@ -1041,7 +1042,7 @@ public func themeSettingsController(context: AccountContext, focusOnItemTag: The
chatThemes.insert(.builtin(.dayClassic), at: 0)
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Appearance_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
- let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
+ let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: themeSettingsControllerEntries(presentationData: presentationData, presentationThemeSettings: settings, mediaSettings: mediaSettings, themeReference: themeReference, availableThemes: availableThemes, availableAppIcons: availableAppIcons, currentAppIconName: currentAppIconName, isPremium: isPremium, chatThemes: chatThemes, animatedEmojiStickers: animatedEmojiStickers), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false)
return (controllerState, (listState, arguments))
}
diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift
index d87c423254..00dbe5b327 100644
--- a/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift
+++ b/submodules/SettingsUI/Sources/Themes/WallpaperCropNode.swift
@@ -4,7 +4,7 @@ import Display
import AsyncDisplayKit
final class WallpaperCropNode: ASDisplayNode, UIScrollViewDelegate {
- private let scrollNode: ASScrollNode
+ let scrollNode: ASScrollNode
private var ignoreZoom = false
private var ignoreZoomTransition: ContainedViewLayoutTransition?
diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift
index e0e357ec39..2fa114ef65 100644
--- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift
+++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryController.swift
@@ -16,6 +16,8 @@ import GalleryUI
import HexColor
import CounterContollerTitleView
import UndoUI
+import LegacyComponents
+import LegacyMediaPickerUI
public enum WallpaperListType {
case wallpapers(WallpaperPresentationOptions?)
@@ -164,6 +166,20 @@ private func updatedFileWallpaper(id: Int64? = nil, accessHash: Int64? = nil, sl
return .file(TelegramWallpaper.File(id: id ?? 0, accessHash: accessHash ?? 0, isCreator: false, isDefault: false, isPattern: isPattern, isDark: false, slug: slug, file: file, settings: WallpaperSettings(colors: colorValues, intensity: intensityValue, rotation: rotation)))
}
+class WallpaperGalleryInteraction {
+ let editMedia: (PHAsset, UIImage, CGRect, TGMediaEditAdjustments?, UIView, @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, @escaping (UIImage?) -> Void) -> Void
+ let beginTransitionToEditor: () -> Void
+ let beginTransitionFromEditor: () -> Void
+ let finishTransitionFromEditor: () -> Void
+
+ init(editMedia: @escaping (PHAsset, UIImage, CGRect, TGMediaEditAdjustments?, UIView, @escaping (UIImage?, TGMediaEditAdjustments?) -> Void, @escaping (UIImage?) -> Void) -> Void, beginTransitionToEditor: @escaping () -> Void, beginTransitionFromEditor: @escaping () -> Void, finishTransitionFromEditor: @escaping () -> Void) {
+ self.editMedia = editMedia
+ self.beginTransitionToEditor = beginTransitionToEditor
+ self.beginTransitionFromEditor = beginTransitionFromEditor
+ self.finishTransitionFromEditor = finishTransitionFromEditor
+ }
+}
+
public class WallpaperGalleryController: ViewController {
public enum Mode {
case `default`
@@ -176,7 +192,9 @@ public class WallpaperGalleryController: ViewController {
private let context: AccountContext
private let source: WallpaperListSource
private let mode: Mode
- public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, CGRect?) -> Void)?
+ public var apply: ((WallpaperGalleryEntry, WallpaperPresentationOptions, UIImage?, CGRect?, CGFloat?) -> Void)?
+
+ private var interaction: WallpaperGalleryInteraction?
private let _ready = Promise()
override public var ready: Promise {
@@ -230,9 +248,54 @@ public class WallpaperGalleryController: ViewController {
//self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
+ self.interaction = WallpaperGalleryInteraction(editMedia: { [weak self] asset, image, cropRect, adjustments, referenceView, apply, fullSizeApply in
+ guard let self else {
+ return
+ }
+ let item = LegacyWallpaperItem(asset: asset, screenImage: image, dimensions: CGSize(width: asset.pixelWidth, height: asset.pixelHeight))
+ legacyWallpaperEditor(context: context, item: item, cropRect: cropRect, adjustments: adjustments, referenceView: referenceView, beginTransitionOut: { [weak self] in
+ self?.interaction?.beginTransitionFromEditor()
+ }, finishTransitionOut: { [weak self] in
+ self?.interaction?.finishTransitionFromEditor()
+ }, completion: { image, adjustments in
+ apply(image, adjustments)
+ }, fullSizeCompletion: { image in
+ fullSizeApply(image)
+ }, present: { [weak self] c, a in
+ if let self {
+ self.present(c, in: .window(.root))
+ }
+ })
+ }, beginTransitionToEditor: { [weak self] in
+ guard let self else {
+ return
+ }
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
+ if let toolbarNode = self.toolbarNode {
+ transition.updateAlpha(node: toolbarNode, alpha: 0.0)
+ }
+ }, beginTransitionFromEditor: { [weak self] in
+ guard let self else {
+ return
+ }
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
+ if let toolbarNode = self.toolbarNode {
+ transition.updateAlpha(node: toolbarNode, alpha: 1.0)
+ }
+ if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
+ centralItemNode.beginTransitionFromEditor()
+ }
+ }, finishTransitionFromEditor: { [weak self] in
+ guard let self else {
+ return
+ }
+ if let centralItemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
+ centralItemNode.finishTransitionFromEditor()
+ }
+ })
+
var entries: [WallpaperGalleryEntry] = []
var centralEntryIndex: Int?
-
switch source {
case let .list(wallpapers, central, type):
entries = wallpapers.map { .wallpaper($0, nil) }
@@ -350,7 +413,7 @@ public class WallpaperGalleryController: ViewController {
var i: Int = 0
var updateItems: [GalleryPagerUpdateItem] = []
for entry in entries {
- let item = GalleryPagerUpdateItem(index: i, previousIndex: i, item: WallpaperGalleryItem(context: self.context, index: updateItems.count, entry: entry, arguments: arguments, source: self.source, mode: self.mode))
+ let item = GalleryPagerUpdateItem(index: i, previousIndex: i, item: WallpaperGalleryItem(context: self.context, index: updateItems.count, entry: entry, arguments: arguments, source: self.source, mode: self.mode, interaction: self.interaction!))
updateItems.append(item)
i += 1
}
@@ -361,7 +424,7 @@ public class WallpaperGalleryController: ViewController {
var updateItems: [GalleryPagerUpdateItem] = []
for i in 0 ..< self.entries.count {
if i == index {
- let item = GalleryPagerUpdateItem(index: index, previousIndex: index, item: WallpaperGalleryItem(context: self.context, index: index, entry: entry, arguments: arguments, source: self.source, mode: self.mode))
+ let item = GalleryPagerUpdateItem(index: index, previousIndex: index, item: WallpaperGalleryItem(context: self.context, index: index, entry: entry, arguments: arguments, source: self.source, mode: self.mode, interaction: self.interaction!))
updateItems.append(item)
}
}
@@ -437,6 +500,12 @@ public class WallpaperGalleryController: ViewController {
}
let toolbarNode = WallpaperGalleryToolbarNode(theme: presentationData.theme, strings: presentationData.strings, doneButtonType: doneButtonType)
+ switch self.source {
+ case .asset, .contextResult:
+ toolbarNode.dark = false
+ default:
+ toolbarNode.dark = true
+ }
self.toolbarNode = toolbarNode
overlayNode.addSubnode(toolbarNode)
@@ -446,14 +515,17 @@ public class WallpaperGalleryController: ViewController {
var dismissed = false
toolbarNode.done = { [weak self] in
if let strongSelf = self, !dismissed {
- dismissed = true
if let centralItemNode = strongSelf.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
+ if centralItemNode.cropNode.scrollNode.view.isDecelerating {
+ return
+ }
+ dismissed = true
let options = centralItemNode.options
if !strongSelf.entries.isEmpty {
let entry = strongSelf.entries[centralItemNode.index]
if case .peer = strongSelf.mode {
- strongSelf.apply?(entry, options, centralItemNode.cropRect)
+ strongSelf.apply?(entry, options, centralItemNode.editedFullSizeImage, centralItemNode.editedCropRect, centralItemNode.brightness)
return
}
@@ -611,7 +683,7 @@ public class WallpaperGalleryController: ViewController {
break
}
- strongSelf.apply?(entry, options, centralItemNode.cropRect)
+ strongSelf.apply?(entry, options, nil, centralItemNode.cropRect, centralItemNode.brightness)
}
}
}
@@ -689,7 +761,7 @@ public class WallpaperGalleryController: ViewController {
break
}
}
- strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
+ strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
}
}
@@ -717,7 +789,7 @@ public class WallpaperGalleryController: ViewController {
itemNode.updateIsColorsPanelActive(strongSelf.colorsPanelEnabled, animated: true)
- strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring))
+ strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.4, curve: .spring))
}
}
@@ -954,7 +1026,7 @@ public class WallpaperGalleryController: ViewController {
colors = true
}
- self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source, mode: self.mode) }), centralItemIndex: self.centralEntryIndex)
+ self.galleryNode.pager.replaceItems(zip(0 ..< self.entries.count, self.entries).map({ WallpaperGalleryItem(context: self.context, index: $0, entry: $1, arguments: WallpaperGalleryItemArguments(isColorsList: colors), source: self.source, mode: self.mode, interaction: self.interaction!) }), centralItemIndex: self.centralEntryIndex)
if let initialOptions = self.initialOptions, let itemNode = self.galleryNode.pager.centralItemNode() as? WallpaperGalleryItemNode {
itemNode.options = initialOptions
diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift
index a63e7ff887..4e751c1845 100644
--- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift
+++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift
@@ -19,6 +19,8 @@ import WallpaperResources
import AppBundle
import WallpaperBackgroundNode
import TextFormat
+import TooltipUI
+import TelegramNotices
struct WallpaperGalleryItemArguments {
let colorPreview: Bool
@@ -44,25 +46,27 @@ class WallpaperGalleryItem: GalleryItem {
let arguments: WallpaperGalleryItemArguments
let source: WallpaperListSource
let mode: WallpaperGalleryController.Mode
+ let interaction: WallpaperGalleryInteraction
- init(context: AccountContext, index: Int, entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode) {
+ init(context: AccountContext, index: Int, entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode, interaction: WallpaperGalleryInteraction) {
self.context = context
self.index = index
self.entry = entry
self.arguments = arguments
self.source = source
self.mode = mode
+ self.interaction = interaction
}
func node(synchronous: Bool) -> GalleryItemNode {
let node = WallpaperGalleryItemNode(context: self.context)
- node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode)
+ node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode, interaction: self.interaction)
return node
}
func updateNode(node: GalleryItemNode, synchronous: Bool) {
if let node = node as? WallpaperGalleryItemNode {
- node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode)
+ node.setEntry(self.entry, arguments: self.arguments, source: self.source, mode: self.mode, interaction: self.interaction)
}
}
@@ -83,7 +87,7 @@ private func reference(for resource: MediaResource, media: Media, message: Messa
final class WallpaperGalleryItemNode: GalleryItemNode {
private let context: AccountContext
- private let presentationData: PresentationData
+ private var presentationData: PresentationData
var entry: WallpaperGalleryEntry?
var source: WallpaperListSource?
@@ -91,27 +95,36 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
private var colorPreview: Bool = false
private var contentSize: CGSize?
private var arguments = WallpaperGalleryItemArguments()
+ private var interaction: WallpaperGalleryInteraction?
let wrapperNode: ASDisplayNode
let imageNode: TransformImageNode
+ private let temporaryImageNode: ASImageNode
let nativeNode: WallpaperBackgroundNode
+ let brightnessNode: ASDisplayNode
private let statusNode: RadialStatusNode
private let blurredNode: BlurredImageNode
let cropNode: WallpaperCropNode
- private var cancelButtonNode: WallpaperNavigationButtonNode
- private var shareButtonNode: WallpaperNavigationButtonNode
-
- private var blurButtonNode: WallpaperOptionButtonNode
- private var motionButtonNode: WallpaperOptionButtonNode
- private var patternButtonNode: WallpaperOptionButtonNode
- private var colorsButtonNode: WallpaperOptionButtonNode
- private var playButtonNode: WallpaperNavigationButtonNode
+ private let cancelButtonNode: WallpaperNavigationButtonNode
+ private let shareButtonNode: WallpaperNavigationButtonNode
+ private let dayNightButtonNode: WallpaperNavigationButtonNode
+ private let editButtonNode: WallpaperNavigationButtonNode
+
+ private let buttonsContainerNode: SparseNode
+ private let blurButtonNode: WallpaperOptionButtonNode
+ private let motionButtonNode: WallpaperOptionButtonNode
+ private let patternButtonNode: WallpaperOptionButtonNode
+ private let colorsButtonNode: WallpaperOptionButtonNode
+ private let playButtonNode: WallpaperNavigationButtonNode
+ private let sliderNode: WallpaperSliderNode
private let messagesContainerNode: ASDisplayNode
private var messageNodes: [ListViewItemNode]?
private var validMessages: [String]?
+ private let serviceBackgroundNode: NavigationBackgroundNode
+
fileprivate let _ready = Promise()
private let fetchDisposable = MetaDisposable()
private let statusDisposable = MetaDisposable()
@@ -135,13 +148,21 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
private var isReadyDisposable: Disposable?
+ private var isDarkAppearance: Bool = false
+ private var didChangeAppearance: Bool = false
+ private var darkAppearanceIntensity: CGFloat = 0.8
+
init(context: AccountContext) {
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ self.isDarkAppearance = self.presentationData.theme.overallDarkAppearance
+
self.wrapperNode = ASDisplayNode()
self.imageNode = TransformImageNode()
self.imageNode.contentAnimations = .subsequentUpdates
+ self.temporaryImageNode = ASImageNode()
+ self.temporaryImageNode.isUserInteractionEnabled = false
self.nativeNode = createWallpaperBackgroundNode(context: context, forChatDisplay: false)
self.cropNode = WallpaperCropNode()
self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(white: 0.0, alpha: 0.6))
@@ -149,23 +170,39 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.statusNode.isUserInteractionEnabled = false
self.blurredNode = BlurredImageNode()
+ self.brightnessNode = ASDisplayNode()
+ self.brightnessNode.alpha = 0.0
self.messagesContainerNode = ASDisplayNode()
self.messagesContainerNode.transform = CATransform3DMakeScale(1.0, -1.0, 1.0)
self.messagesContainerNode.isUserInteractionEnabled = false
+ self.buttonsContainerNode = SparseNode()
self.blurButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Blurred, value: .check(false))
self.blurButtonNode.setEnabled(false)
self.motionButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Motion, value: .check(false))
self.motionButtonNode.setEnabled(false)
self.patternButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_Pattern, value: .check(false))
self.patternButtonNode.setEnabled(false)
+
+ self.serviceBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35))
+
+ var sliderValueChangedImpl: ((CGFloat) -> Void)?
+ self.sliderNode = WallpaperSliderNode(minValue: 0.0, maxValue: 1.0, value: 0.7, valueChanged: { value, _ in
+ sliderValueChangedImpl?(value)
+ })
self.colorsButtonNode = WallpaperOptionButtonNode(title: self.presentationData.strings.WallpaperPreview_WallpaperColors, value: .colors(false, [.clear]))
- self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel))
- self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)))
-
+ self.cancelButtonNode = WallpaperNavigationButtonNode(content: .text(self.presentationData.strings.Common_Cancel), dark: true)
+ self.cancelButtonNode.enableSaturation = true
+ self.shareButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Chat/Links/Share"), size: CGSize(width: 28.0, height: 28.0)), dark: true)
+ self.shareButtonNode.enableSaturation = true
+ self.dayNightButtonNode = WallpaperNavigationButtonNode(content: .dayNight(isNight: self.isDarkAppearance), dark: true)
+ self.dayNightButtonNode.enableSaturation = true
+ self.editButtonNode = WallpaperNavigationButtonNode(content: .icon(image: UIImage(bundleImageName: "Settings/WallpaperAdjustments"), size: CGSize(width: 28.0, height: 28.0)), dark: true)
+ self.editButtonNode.enableSaturation = true
+
self.playButtonPlayImage = generateImage(CGSize(width: 48.0, height: 48.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setFillColor(UIColor.white.cgColor)
@@ -193,7 +230,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.playButtonRotateImage = generateTintedImage(image: UIImage(bundleImageName: "Settings/ThemeColorRotateIcon"), color: .white)
- self.playButtonNode = WallpaperNavigationButtonNode(content: .icon(image: self.playButtonPlayImage, size: CGSize(width: 48.0, height: 48.0)))
+ self.playButtonNode = WallpaperNavigationButtonNode(content: .icon(image: self.playButtonPlayImage, size: CGSize(width: 48.0, height: 48.0)), dark: true)
super.init()
@@ -216,16 +253,24 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.imageNode.clipsToBounds = true
self.addSubnode(self.wrapperNode)
+ self.addSubnode(self.temporaryImageNode)
//self.addSubnode(self.statusNode)
+ self.addSubnode(self.serviceBackgroundNode)
self.addSubnode(self.messagesContainerNode)
+ self.addSubnode(self.buttonsContainerNode)
- self.addSubnode(self.blurButtonNode)
- self.addSubnode(self.motionButtonNode)
- self.addSubnode(self.patternButtonNode)
- self.addSubnode(self.colorsButtonNode)
- self.addSubnode(self.playButtonNode)
- self.addSubnode(self.cancelButtonNode)
- self.addSubnode(self.shareButtonNode)
+ self.buttonsContainerNode.addSubnode(self.blurButtonNode)
+ self.buttonsContainerNode.addSubnode(self.motionButtonNode)
+ self.buttonsContainerNode.addSubnode(self.patternButtonNode)
+ self.buttonsContainerNode.addSubnode(self.colorsButtonNode)
+ self.buttonsContainerNode.addSubnode(self.playButtonNode)
+ self.buttonsContainerNode.addSubnode(self.sliderNode)
+ self.buttonsContainerNode.addSubnode(self.cancelButtonNode)
+ self.buttonsContainerNode.addSubnode(self.shareButtonNode)
+ self.buttonsContainerNode.addSubnode(self.dayNightButtonNode)
+ self.buttonsContainerNode.addSubnode(self.editButtonNode)
+
+ self.imageNode.addSubnode(self.brightnessNode)
self.blurButtonNode.addTarget(self, action: #selector(self.toggleBlur), forControlEvents: .touchUpInside)
self.motionButtonNode.addTarget(self, action: #selector(self.toggleMotion), forControlEvents: .touchUpInside)
@@ -234,6 +279,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.playButtonNode.addTarget(self, action: #selector(self.togglePlay), forControlEvents: .touchUpInside)
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
self.shareButtonNode.addTarget(self, action: #selector(self.actionPressed), forControlEvents: .touchUpInside)
+ self.dayNightButtonNode.addTarget(self, action: #selector(self.dayNightPressed), forControlEvents: .touchUpInside)
+ self.editButtonNode.addTarget(self, action: #selector(self.editPressed), forControlEvents: .touchUpInside)
+
+ sliderValueChangedImpl = { [weak self] value in
+ if let self {
+ self.updateIntensity(transition: .immediate)
+ }
+ }
}
deinit {
@@ -255,6 +308,30 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
}
+ var editedCropRect: CGRect? {
+ guard let cropRect = self.cropRect, let contentSize = self.contentSize else {
+ return nil
+ }
+ if let editedFullSizeImage = self.editedFullSizeImage {
+ let scale = editedFullSizeImage.size.height / contentSize.height
+ return CGRect(origin: CGPoint(x: cropRect.minX * scale, y: cropRect.minY * scale), size: CGSize(width: cropRect.width * scale, height: cropRect.height * scale))
+ } else {
+ return cropRect
+ }
+ }
+
+ var brightness: CGFloat? {
+ guard let entry = self.entry else {
+ return nil
+ }
+ switch entry {
+ case .asset, .contextResult:
+ return 1.0 - self.sliderNode.value
+ default:
+ return nil
+ }
+ }
+
override func ready() -> Signal {
return self._ready.get()
}
@@ -263,15 +340,258 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.action?()
}
+ private func switchTheme() {
+ if let messageNodes = self.messageNodes {
+ for messageNode in messageNodes.prefix(2) {
+ if let snapshotView = messageNode.view.snapshotContentTree() {
+ messageNode.view.addSubview(snapshotView)
+ snapshotView.frame = messageNode.bounds
+ snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.35, removeOnCompletion: false, completion: { [weak snapshotView] _ in
+ snapshotView?.removeFromSuperview()
+ })
+ }
+ }
+ }
+ let themeSettings = self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
+ |> map { sharedData -> PresentationThemeSettings in
+ let themeSettings: PresentationThemeSettings
+ if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.presentationThemeSettings]?.get(PresentationThemeSettings.self) {
+ themeSettings = current
+ } else {
+ themeSettings = PresentationThemeSettings.defaultSettings
+ }
+ return themeSettings
+ }
+
+ let _ = (themeSettings
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] themeSettings in
+ guard let strongSelf = self else {
+ return
+ }
+ var presentationData = strongSelf.presentationData
+
+ let lightTheme: PresentationTheme
+ let lightWallpaper: TelegramWallpaper
+
+ let darkTheme: PresentationTheme
+ let darkWallpaper: TelegramWallpaper
+
+ if !strongSelf.isDarkAppearance {
+ darkTheme = presentationData.theme
+ darkWallpaper = presentationData.chatWallpaper
+
+ var currentColors = themeSettings.themeSpecificAccentColors[themeSettings.theme.index]
+ if let colors = currentColors, colors.baseColor == .theme {
+ currentColors = nil
+ }
+
+ let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: themeSettings.theme, accentColor: currentColors)] ?? themeSettings.themeSpecificChatWallpapers[themeSettings.theme.index])
+
+ if let themeSpecificWallpaper = themeSpecificWallpaper {
+ lightWallpaper = themeSpecificWallpaper
+ } else {
+ let theme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, preview: true) ?? defaultPresentationTheme
+ lightWallpaper = theme.chat.defaultWallpaper
+ }
+
+ var preferredBaseTheme: TelegramBaseTheme?
+ if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) {
+ preferredBaseTheme = baseTheme
+ }
+
+ lightTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
+ } else {
+ lightTheme = presentationData.theme
+ lightWallpaper = presentationData.chatWallpaper
+
+ let automaticTheme = themeSettings.automaticThemeSwitchSetting.theme
+ let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
+ let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index])
+
+ var preferredBaseTheme: TelegramBaseTheme?
+ if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) {
+ preferredBaseTheme = baseTheme
+ } else {
+ preferredBaseTheme = .night
+ }
+
+ darkTheme = makePresentationTheme(mediaBox: strongSelf.context.sharedContext.accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
+
+ if let themeSpecificWallpaper = themeSpecificWallpaper {
+ darkWallpaper = themeSpecificWallpaper
+ } else {
+ switch lightWallpaper {
+ case .builtin, .color, .gradient:
+ darkWallpaper = darkTheme.chat.defaultWallpaper
+ case .file:
+ if lightWallpaper.isPattern {
+ darkWallpaper = darkTheme.chat.defaultWallpaper
+ } else {
+ darkWallpaper = lightWallpaper
+ }
+ default:
+ darkWallpaper = lightWallpaper
+ }
+ }
+ }
+
+ if strongSelf.isDarkAppearance {
+ darkTheme.forceSync = true
+ Queue.mainQueue().after(1.0, {
+ darkTheme.forceSync = false
+ })
+ presentationData = presentationData.withUpdated(theme: darkTheme).withUpdated(chatWallpaper: darkWallpaper)
+ } else {
+ lightTheme.forceSync = true
+ Queue.mainQueue().after(1.0, {
+ lightTheme.forceSync = false
+ })
+ presentationData = presentationData.withUpdated(theme: lightTheme).withUpdated(chatWallpaper: lightWallpaper)
+ }
+
+ strongSelf.presentationData = presentationData
+
+ if let (layout, _) = strongSelf.validLayout {
+ strongSelf.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .animated(duration: 0.3, curve: .easeInOut))
+ }
+ })
+ }
+
+ @objc private func dayNightPressed() {
+ self.isDarkAppearance = !self.isDarkAppearance
+ self.dayNightButtonNode.setIsNight(self.isDarkAppearance)
+
+ if let layout = self.validLayout?.0 {
+ let offset = CGPoint(x: self.validOffset ?? 0.0, y: 0.0)
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.4, curve: .spring)
+ self.updateButtonsLayout(layout: layout, offset: offset, transition: transition)
+ self.updateMessagesLayout(layout: layout, offset: offset, transition: transition)
+
+ if !self.didChangeAppearance {
+ self.didChangeAppearance = true
+ self.animateIntensityChange(delay: 0.15)
+ } else {
+ self.updateIntensity(transition: .animated(duration: 0.3, curve: .easeInOut))
+ }
+ }
+
+ self.switchTheme()
+ }
+
+ @objc private func editPressed() {
+ guard let image = self.imageNode.image, case let .asset(asset) = self.entry else {
+ return
+ }
+ let originalImage = self.originalImage ?? image
+ guard let cropRect = self.cropRect else {
+ return
+ }
+ self.interaction?.editMedia(asset, originalImage, cropRect, self.currentAdjustments, self.cropNode.view, { [weak self] result, adjustments in
+ guard let self else {
+ return
+ }
+ self.originalImage = originalImage
+ self.editedImage = result
+ self.currentAdjustments = adjustments
+
+ self.imageNode.setSignal(.single({ arguments in
+ let context = DrawingContext(size: arguments.drawingSize, opaque: false)
+ context?.withFlippedContext({ context in
+ let image = result ?? originalImage
+ if let cgImage = image.cgImage {
+ context.draw(cgImage, in: CGRect(origin: .zero, size: arguments.drawingSize))
+ }
+ })
+ return context
+ }))
+
+ self.temporaryImageNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
+ self?.temporaryImageNode.image = nil
+ self?.temporaryImageNode.layer.removeAllAnimations()
+ })
+ }, { [weak self] image in
+ guard let self else {
+ return
+ }
+ self.editedFullSizeImage = image
+
+ self.temporaryImageNode.frame = self.imageNode.view.convert(self.imageNode.bounds, to: self.view)
+ self.temporaryImageNode.image = image ?? originalImage
+
+ if self.cropNode.isHidden {
+ self.temporaryImageNode.alpha = 0.0
+ }
+ })
+
+ self.beginTransitionToEditor()
+ }
+
+ private var originalImage: UIImage?
+ public private(set) var editedImage: UIImage?
+ public private(set) var editedFullSizeImage: UIImage?
+ private var currentAdjustments: TGMediaEditAdjustments?
+
+ private func animateIntensityChange(delay: Double) {
+ let targetValue: CGFloat = self.sliderNode.value
+ self.sliderNode.internalUpdateLayout(size: self.sliderNode.frame.size, value: 1.0)
+ self.sliderNode.ignoreUpdates = true
+ Queue.mainQueue().after(delay, {
+ self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000)
+ self.brightnessNode.layer.compositingFilter = nil
+
+ self.sliderNode.ignoreUpdates = false
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut)
+ self.sliderNode.animateValue(from: 1.0, to: targetValue, transition: transition)
+ self.updateIntensity(transition: transition)
+ })
+ }
+
+ private func updateIntensity(transition: ContainedViewLayoutTransition) {
+ let value = self.isDarkAppearance ? self.sliderNode.value : 1.0
+ if value < 1.0 {
+ self.brightnessNode.backgroundColor = UIColor(rgb: 0x000000)
+ self.brightnessNode.layer.compositingFilter = nil
+ transition.updateAlpha(node: self.brightnessNode, alpha: 1.0 - value)
+ } else {
+ self.brightnessNode.layer.compositingFilter = nil
+ transition.updateAlpha(node: self.brightnessNode, alpha: 0.0)
+ }
+ }
+
+ func beginTransitionToEditor() {
+ self.cropNode.isHidden = true
+
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
+ transition.updateAlpha(node: self.messagesContainerNode, alpha: 0.0)
+ transition.updateAlpha(node: self.buttonsContainerNode, alpha: 0.0)
+ transition.updateAlpha(node: self.serviceBackgroundNode, alpha: 0.0)
+
+ self.interaction?.beginTransitionToEditor()
+ }
+
+ func beginTransitionFromEditor() {
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)
+ transition.updateAlpha(node: self.messagesContainerNode, alpha: 1.0)
+ transition.updateAlpha(node: self.buttonsContainerNode, alpha: 1.0)
+ transition.updateAlpha(node: self.serviceBackgroundNode, alpha: 1.0)
+ }
+
+ func finishTransitionFromEditor() {
+ self.cropNode.isHidden = false
+ self.temporaryImageNode.alpha = 1.0
+ }
+
@objc private func cancelPressed() {
self.dismiss()
}
- func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode) {
+ func setEntry(_ entry: WallpaperGalleryEntry, arguments: WallpaperGalleryItemArguments, source: WallpaperListSource, mode: WallpaperGalleryController.Mode, interaction: WallpaperGalleryInteraction) {
let previousArguments = self.arguments
self.arguments = arguments
self.source = source
self.mode = mode
+ self.interaction = interaction
if self.arguments.colorPreview != previousArguments.colorPreview {
if self.arguments.colorPreview {
@@ -281,12 +601,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
}
+ var showPreviewTooltip = false
+
if self.entry != entry || self.arguments.colorPreview != previousArguments.colorPreview {
- let previousEntry = self.entry
self.entry = entry
- if previousEntry != entry {
- self.preparePatternEditing()
- }
self.colorsButtonNode.colors = self.calculateGradientColors() ?? defaultBuiltinWallpaperGradientColors
@@ -308,12 +626,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let progressAction = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: presentationData.theme.rootController.navigationBar.accentTextColor))
var isBlurrable = true
-
+
self.nativeNode.updateBubbleTheme(bubbleTheme: presentationData.theme, bubbleCorners: presentationData.chatBubbleCorners)
+ var isColor = false
switch entry {
case let .wallpaper(wallpaper, _):
- self.nativeNode.update(wallpaper: wallpaper)
+ Queue.mainQueue().justDispatch {
+ self.nativeNode.update(wallpaper: wallpaper)
+ }
if case let .file(file) = wallpaper, file.isPattern {
self.nativeNode.isHidden = false
@@ -324,6 +645,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
} else {
self.playButtonNode.setIcon(self.playButtonRotateImage)
}
+ isColor = true
} else if case let .gradient(gradient) = wallpaper {
self.nativeNode.isHidden = false
self.nativeNode.update(wallpaper: wallpaper)
@@ -334,11 +656,14 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
} else {
self.playButtonNode.setIcon(self.playButtonRotateImage)
}
+ isColor = true
} else if case .color = wallpaper {
self.nativeNode.isHidden = false
self.nativeNode.update(wallpaper: wallpaper)
self.patternButtonNode.isSelected = false
+ isColor = true
} else {
+ self.nativeNode._internalUpdateIsSettingUpWallpaper()
self.nativeNode.isHidden = true
self.patternButtonNode.isSelected = false
self.playButtonNode.setIcon(self.playButtonRotateImage)
@@ -354,7 +679,20 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.playButtonNode.setIcon(self.playButtonRotateImage)
}
+ self.cancelButtonNode.enableSaturation = isColor
+ self.dayNightButtonNode.enableSaturation = isColor
+ self.editButtonNode.enableSaturation = isColor
+ self.shareButtonNode.enableSaturation = isColor
+ self.patternButtonNode.backgroundNode.enableSaturation = isColor
+ self.blurButtonNode.backgroundNode.enableSaturation = isColor
+ self.motionButtonNode.backgroundNode.enableSaturation = isColor
+ self.colorsButtonNode.backgroundNode.enableSaturation = isColor
+ self.playButtonNode.enableSaturation = isColor
+
var canShare = false
+ var canSwitchTheme = false
+ var canEdit = false
+
switch entry {
case let .wallpaper(wallpaper, message):
self.initialWallpaper = wallpaper
@@ -526,6 +864,9 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
subtitleSignal = .single(nil)
colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3))
self.wrapperNode.addSubnode(self.cropNode)
+ showPreviewTooltip = true
+ canSwitchTheme = true
+ canEdit = true
case let .contextResult(result):
var imageDimensions: CGSize?
var imageResource: TelegramMediaResource?
@@ -580,16 +921,32 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
colorSignal = .single(UIColor(rgb: 0x000000, alpha: 0.3))
subtitleSignal = .single(nil)
self.wrapperNode.addSubnode(self.cropNode)
+ showPreviewTooltip = true
+ canSwitchTheme = true
}
self.contentSize = contentSize
- self.shareButtonNode.isHidden = !canShare
+ if case .wallpaper = source {
+ canSwitchTheme = true
+ } else if case let .list(_, _, type) = source, case .colors = type {
+ canSwitchTheme = true
+ }
+
+ if canSwitchTheme {
+ self.dayNightButtonNode.isHidden = false
+ self.shareButtonNode.isHidden = true
+ } else {
+ self.dayNightButtonNode.isHidden = true
+ self.shareButtonNode.isHidden = !canShare
+ }
+ self.editButtonNode.isHidden = !canEdit
if self.cropNode.supernode == nil {
self.imageNode.contentMode = .scaleAspectFill
self.wrapperNode.addSubnode(self.imageNode)
self.wrapperNode.addSubnode(self.nativeNode)
} else {
+ self.wrapperNode.insertSubnode(self.nativeNode, at: 0)
self.imageNode.contentMode = .scaleToFill
}
@@ -603,12 +960,20 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
var image = isBlurrable ? image : nil
if let imageToScale = image {
let actualSize = CGSize(width: imageToScale.size.width * imageToScale.scale, height: imageToScale.size.height * imageToScale.scale)
- if actualSize.width > 1280.0 || actualSize.height > 1280.0 {
- image = TGScaleImageToPixelSize(image, actualSize.fitted(CGSize(width: 1280.0, height: 1280.0)))
+ if actualSize.width > 960.0 || actualSize.height > 960.0 {
+ image = TGScaleImageToPixelSize(image, actualSize.fitted(CGSize(width: 960.0, height: 960.0)))
}
}
strongSelf.blurredNode.image = image
imagePromise.set(.single(image))
+
+ if case .asset = entry, let image, let data = image.jpegData(compressionQuality: 0.5) {
+ let resource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
+ strongSelf.context.sharedContext.accountManager.mediaBox.storeResourceData(resource.id, data: data, synchronous: true)
+
+ let wallpaper: TelegramWallpaper = .image([TelegramMediaImageRepresentation(dimensions: PixelDimensions(image.size), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false)], WallpaperSettings())
+ strongSelf.nativeNode.update(wallpaper: wallpaper)
+ }
}
}
self.fetchDisposable.set(fetchSignal.start())
@@ -660,6 +1025,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.updateButtonsLayout(layout: layout, offset: CGPoint(), transition: .immediate)
self.updateMessagesLayout(layout: layout, offset: CGPoint(), transition: .immediate)
}
+
+ if showPreviewTooltip {
+ Queue.mainQueue().after(0.35) {
+ self.maybePresentPreviewTooltip()
+ }
+ if self.isDarkAppearance && !self.didChangeAppearance {
+ Queue.mainQueue().justDispatch {
+ self.didChangeAppearance = true
+ self.animateIntensityChange(delay: 0.35)
+ }
+ }
+ }
}
override func screenFrameUpdated(_ frame: CGRect) {
@@ -715,13 +1092,17 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
@objc func toggleBlur() {
+ guard !self.animatingBlur else {
+ return
+ }
let value = !self.blurButtonNode.isSelected
self.blurButtonNode.setSelected(value, animated: true)
self.setBlurEnabled(value, animated: true)
}
+ private var animatingBlur = false
func setBlurEnabled(_ enabled: Bool, animated: Bool) {
- let blurRadius: CGFloat = 45.0
+ let blurRadius: CGFloat = 30.0
var animated = animated
if animated, let (layout, _) = self.validLayout {
@@ -732,29 +1113,29 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
if enabled {
if self.blurredNode.supernode == nil {
- if self.cropNode.supernode != nil {
- self.blurredNode.frame = self.imageNode.bounds
- self.imageNode.addSubnode(self.blurredNode)
- } else {
- self.blurredNode.frame = self.imageNode.bounds
- self.imageNode.addSubnode(self.blurredNode)
- }
+ self.blurredNode.frame = self.imageNode.bounds
+ self.imageNode.insertSubnode(self.blurredNode, at: 0)
}
if animated {
+ self.animatingBlur = true
self.blurredNode.blurView.blurRadius = 0.0
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: {
self.blurredNode.blurView.blurRadius = blurRadius
- }, completion: nil)
+ }, completion: { _ in
+ self.animatingBlur = false
+ })
} else {
self.blurredNode.blurView.blurRadius = blurRadius
}
} else {
if self.blurredNode.supernode != nil {
if animated {
+ self.animatingBlur = true
UIView.animate(withDuration: 0.3, delay: 0.0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: {
self.blurredNode.blurView.blurRadius = 0.0
}, completion: { finished in
+ self.animatingBlur = false
if finished {
self.blurredNode.removeFromSupernode()
}
@@ -853,9 +1234,6 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
}
- private func preparePatternEditing() {
- }
-
func setMotionEnabled(_ enabled: Bool, animated: Bool) {
if enabled {
let horizontal = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
@@ -914,12 +1292,23 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let buttonSize = CGSize(width: maxButtonWidth, height: 30.0)
let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0))
- let additionalYOffset: CGFloat = 0.0
- /*if self.patternButtonNode.isSelected {
- additionalYOffset = -235.0
- } else if self.colorsButtonNode.isSelected {
- additionalYOffset = -235.0
- }*/
+ var additionalYOffset: CGFloat = 0.0
+ var canEditIntensity = false
+ if let source = self.source {
+ switch source {
+ case .asset, .contextResult:
+ canEditIntensity = true
+ case let .wallpaper(wallpaper, _, _, _, _, _):
+ if case let .file(file) = wallpaper, !file.isPattern {
+ canEditIntensity = true
+ }
+ default:
+ break
+ }
+ }
+ if canEditIntensity && self.isDarkAppearance {
+ additionalYOffset -= 44.0
+ }
let buttonSpacing: CGFloat = 18.0
@@ -937,17 +1326,28 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
var motionFrame = centerButtonFrame
var motionAlpha: CGFloat = 0.0
-
+
var colorsFrame = CGRect(origin: CGPoint(x: rightButtonFrame.maxX - colorsButtonSize.width, y: rightButtonFrame.minY), size: colorsButtonSize)
var colorsAlpha: CGFloat = 0.0
let playFrame = CGRect(origin: CGPoint(x: centerButtonFrame.midX - playButtonSize.width / 2.0, y: centerButtonFrame.midY - playButtonSize.height / 2.0), size: playButtonSize)
var playAlpha: CGFloat = 0.0
+ let sliderSize = CGSize(width: 268.0, height: 30.0)
+ var sliderFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - sliderSize.width) / 2.0) + offset.x, y: layout.size.height - toolbarHeight - layout.intrinsicInsets.bottom - 52.0 + offset.y), size: sliderSize)
+ var sliderAlpha: CGFloat = 0.0
+ var sliderScale: CGFloat = 0.2
+ if !additionalYOffset.isZero {
+ sliderAlpha = 1.0
+ sliderScale = 1.0
+ } else {
+ sliderFrame = sliderFrame.offsetBy(dx: 0.0, dy: 22.0)
+ }
+
let cancelSize = self.cancelButtonNode.measure(layout.size)
let cancelFrame = CGRect(origin: CGPoint(x: 16.0 + offset.x, y: 16.0), size: cancelSize)
-
let shareFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x, y: 16.0), size: CGSize(width: 28.0, height: 28.0))
+ let editFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - 28.0 + offset.x - 46.0, y: 16.0), size: CGSize(width: 28.0, height: 28.0))
let centerOffset: CGFloat = 32.0
@@ -1047,17 +1447,22 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
transition.updateAlpha(node: self.playButtonNode, alpha: playAlpha * alpha)
transition.updateSublayerTransformScale(node: self.playButtonNode, scale: max(0.1, playAlpha))
+ transition.updateFrameAsPositionAndBounds(node: self.sliderNode, frame: sliderFrame)
+ transition.updateAlpha(node: self.sliderNode, alpha: sliderAlpha * alpha)
+ transition.updateTransformScale(node: self.sliderNode, scale: sliderScale)
+ self.sliderNode.updateLayout(size: sliderFrame.size)
+
transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame)
transition.updateFrame(node: self.shareButtonNode, frame: shareFrame)
+ transition.updateFrame(node: self.dayNightButtonNode, frame: shareFrame)
+ transition.updateFrame(node: self.editButtonNode, frame: editFrame)
}
private func updateMessagesLayout(layout: ContainerViewLayout, offset: CGPoint, transition: ContainedViewLayoutTransition) {
- let bottomInset: CGFloat = 132.0
-
- if self.patternButtonNode.isSelected || self.colorsButtonNode.isSelected {
- //bottomInset = 350.0
- }
+ self.nativeNode.updateBubbleTheme(bubbleTheme: self.presentationData.theme, bubbleCorners: self.presentationData.chatBubbleCorners)
+ var bottomInset: CGFloat = 132.0
+
var items: [ListViewItem] = []
let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(1))
let otherPeerId = self.context.account.peerId
@@ -1074,6 +1479,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
currentWallpaper = wallpaper
}
+ var canEditIntensity = false
if let source = self.source {
switch source {
case .slug, .wallpaper:
@@ -1096,6 +1502,10 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
if hasAnimatableGradient {
bottomMessageText = presentationData.strings.WallpaperPreview_PreviewBottomTextAnimatable
}
+
+ if case let .wallpaper(wallpaper, _, _, _, _, _) = source, case let .file(file) = wallpaper, !file.isPattern {
+ canEditIntensity = true
+ }
case let .list(_, _, type):
switch type {
case .wallpapers:
@@ -1125,6 +1535,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
case .asset, .contextResult:
topMessageText = presentationData.strings.WallpaperPreview_CropTopText
bottomMessageText = presentationData.strings.WallpaperPreview_CropBottomText
+ canEditIntensity = true
case .customColor:
topMessageText = presentationData.strings.WallpaperPreview_CustomColorTopText
bottomMessageText = presentationData.strings.WallpaperPreview_CustomColorBottomText
@@ -1138,8 +1549,12 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
serviceMessageText = presentationData.strings.WallpaperPreview_NotAppliedInfo(peer.compactDisplayTitle).string
}
}
+
+ if canEditIntensity && self.isDarkAppearance {
+ bottomInset += 44.0
+ }
- let theme = self.presentationData.theme.withUpdated(preview: true)
+ let theme = self.presentationData.theme
let message1 = Message(stableId: 2, stableVersion: 0, id: MessageId(peerId: peerId, namespace: 0, id: 2), globallyUniqueId: nil, groupingKey: nil, groupInfo: nil, threadId: nil, timestamp: 66001, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, author: peers[otherPeerId], text: bottomMessageText, attributes: [], media: [], peers: peers, associatedMessages: messages, associatedMessageIds: [], associatedMedia: [:], associatedThreadInfo: nil)
items.append(self.context.sharedContext.makeChatMessagePreviewItem(context: self.context, messages: [message1], theme: theme, strings: self.presentationData.strings, wallpaper: currentWallpaper, fontSize: self.presentationData.chatFontSize, chatBubbleCorners: self.presentationData.chatBubbleCorners, dateTimeFormat: self.presentationData.dateTimeFormat, nameOrder: self.presentationData.nameDisplayOrder, forcedResourceStatus: nil, tapMessage: nil, clickThroughMessage: nil, backgroundNode: self.nativeNode, availableReactions: nil, isCentered: false))
@@ -1157,18 +1572,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
let params = ListViewItemLayoutParams(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, availableHeight: layout.size.height)
if let messageNodes = self.messageNodes {
- if self.validMessages != [topMessageText, bottomMessageText] {
- self.validMessages = [topMessageText, bottomMessageText]
- for i in 0 ..< items.count {
- items[i].updateNode(async: { f in f() }, node: { return messageNodes[i] }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None) { layout, apply in
- let nodeFrame = CGRect(origin: messageNodes[i].frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height))
+ for i in 0 ..< items.count {
+ items[i].updateNode(async: { f in f() }, node: { return messageNodes[i] }, params: params, previousItem: i == 0 ? nil : items[i - 1], nextItem: i == (items.count - 1) ? nil : items[i + 1], animation: .None) { layout, apply in
+ let nodeFrame = CGRect(origin: messageNodes[i].frame.origin, size: CGSize(width: layout.size.width, height: layout.size.height))
- messageNodes[i].contentSize = layout.contentSize
- messageNodes[i].insets = layout.insets
- messageNodes[i].frame = nodeFrame
+ messageNodes[i].contentSize = layout.contentSize
+ messageNodes[i].insets = layout.insets
+ messageNodes[i].frame = nodeFrame
- apply(ListViewItemApply(isOnScreen: true))
- }
+ apply(ListViewItemApply(isOnScreen: true))
}
}
} else {
@@ -1187,7 +1599,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
}
self.messageNodes = messageNodes
}
-
+
let alpha = 1.0 - min(1.0, max(0.0, abs(offset.y) / 50.0))
if let messageNodes = self.messageNodes {
@@ -1199,6 +1611,15 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
transition.updateAlpha(node: itemNode, alpha: alpha)
}
}
+
+ if let _ = serviceMessageText, let messageNodes = self.messageNodes, let node = messageNodes.last {
+ if let backgroundNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.first, let backdropNode = node.subnodes?.first?.subnodes?.first?.subnodes?.first?.subnodes?.last?.subnodes?.last?.subnodes?.first {
+ backdropNode.isHidden = true
+ let serviceBackgroundFrame = backgroundNode.view.convert(backgroundNode.bounds, to: self.view).offsetBy(dx: 0.0, dy: -1.0).insetBy(dx: 0.0, dy: -1.0)
+ transition.updateFrame(node: self.serviceBackgroundNode, frame: serviceBackgroundFrame)
+ self.serviceBackgroundNode.update(size: serviceBackgroundFrame.size, cornerRadius: serviceBackgroundFrame.height / 2.0, transition: transition)
+ }
+ }
}
override func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@@ -1212,6 +1633,7 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.wrapperNode.bounds = CGRect(origin: CGPoint(), size: layout.size)
self.updateWrapperLayout(layout: layout, offset: offset, transition: transition)
self.messagesContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
+ self.buttonsContainerNode.frame = CGRect(origin: CGPoint(), size: layout.size)
if self.cropNode.supernode == nil {
self.imageNode.frame = self.wrapperNode.bounds
@@ -1236,7 +1658,18 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
self.cropNode.zoom(to: CGRect(x: (contentSize.width - fittedSize.width) / 2.0, y: (contentSize.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height))
}
self.blurredNode.frame = self.imageNode.bounds
+
+ let displayMode: WallpaperDisplayMode
+ if case .regular = layout.metrics.widthClass {
+ displayMode = .aspectFit
+ } else {
+ displayMode = .aspectFill
+ }
+
+ self.nativeNode.frame = self.wrapperNode.bounds
+ self.nativeNode.updateLayout(size: self.nativeNode.bounds.size, displayMode: displayMode, transition: .immediate)
}
+ self.brightnessNode.frame = self.imageNode.bounds
let additionalYOffset: CGFloat = 0.0
@@ -1255,4 +1688,41 @@ final class WallpaperGalleryItemNode: GalleryItemNode {
func animateWallpaperAppeared() {
self.nativeNode.animateEvent(transition: .animated(duration: 2.0, curve: .spring), extendAnimation: true)
}
+
+ private var displayedPreviewTooltip = false
+ private func maybePresentPreviewTooltip() {
+ guard !self.displayedPreviewTooltip else {
+ return
+ }
+
+ let frame = self.dayNightButtonNode.view.convert(self.dayNightButtonNode.bounds, to: self.view)
+ let currentTimestamp = Int32(Date().timeIntervalSince1970)
+
+ let isDark = self.isDarkAppearance
+
+ let signal: Signal<(Int32, Int32), NoError>
+ if isDark {
+ signal = ApplicationSpecificNotice.getChatWallpaperLightPreviewTip(accountManager: self.context.sharedContext.accountManager)
+ } else {
+ signal = ApplicationSpecificNotice.getChatWallpaperDarkPreviewTip(accountManager: self.context.sharedContext.accountManager)
+ }
+
+ let _ = (signal
+ |> deliverOnMainQueue).start(next: { [weak self] count, timestamp in
+ if let strongSelf = self, (count < 2 && currentTimestamp > timestamp + 24 * 60 * 60) {
+ strongSelf.displayedPreviewTooltip = true
+
+ let controller = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.WallpaperPreview_PreviewInDayMode : strongSelf.presentationData.strings.WallpaperPreview_PreviewInNightMode, style: .customBlur(UIColor(rgb: 0x333333, alpha: 0.35)), icon: nil, location: .point(frame.offsetBy(dx: 1.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
+ return .dismiss(consume: false)
+ })
+ strongSelf.galleryController()?.present(controller, in: .current)
+
+ if isDark {
+ let _ = ApplicationSpecificNotice.incrementChatWallpaperLightPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start()
+ } else {
+ let _ = ApplicationSpecificNotice.incrementChatWallpaperDarkPreviewTip(accountManager: strongSelf.context.sharedContext.accountManager, timestamp: currentTimestamp).start()
+ }
+ }
+ })
+ }
}
diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift
index e29590d657..52ed12bfe9 100644
--- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift
+++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryToolbarNode.swift
@@ -32,12 +32,25 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
}
}
+ var dark: Bool {
+ didSet {
+ if self.dark != oldValue {
+ self.doneButtonBackgroundNode.removeFromSupernode()
+ if self.dark {
+ self.doneButtonBackgroundNode = WallpaperOptionBackgroundNode(enableSaturation: true)
+ } else {
+ self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode()
+ }
+ self.doneButtonBackgroundNode.cornerRadius = 14.0
+ self.insertSubnode(self.doneButtonBackgroundNode, at: 0)
+ }
+ }
+ }
+
private let doneButton = HighlightTrackingButtonNode()
- private let doneButtonBackgroundNode: NavigationBackgroundNode
+ private var doneButtonBackgroundNode: ASDisplayNode
private let doneButtonTitleNode: ImmediateTextNode
- private let doneButtonVibrancyView: UIVisualEffectView
- private let doneButtonVibrancyTitleNode: ImmediateTextNode
private let doneButtonSolidBackgroundNode: ASDisplayNode
private let doneButtonSolidTitleNode: ImmediateTextNode
@@ -50,26 +63,15 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
self.strings = strings
self.cancelButtonType = cancelButtonType
self.doneButtonType = doneButtonType
+ self.dark = false
- self.doneButtonBackgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75))
+ self.doneButtonBackgroundNode = WallpaperLightButtonBackgroundNode()
self.doneButtonBackgroundNode.cornerRadius = 14.0
- let blurEffect: UIBlurEffect
- if #available(iOS 13.0, *) {
- blurEffect = UIBlurEffect(style: .extraLight)
- } else {
- blurEffect = UIBlurEffect(style: .light)
- }
- self.doneButtonVibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect))
-
self.doneButtonTitleNode = ImmediateTextNode()
self.doneButtonTitleNode.displaysAsynchronously = false
self.doneButtonTitleNode.isUserInteractionEnabled = false
- self.doneButtonVibrancyTitleNode = ImmediateTextNode()
- self.doneButtonVibrancyTitleNode.displaysAsynchronously = false
- self.doneButtonVibrancyTitleNode.isUserInteractionEnabled = false
-
self.doneButtonSolidBackgroundNode = ASDisplayNode()
self.doneButtonSolidBackgroundNode.alpha = 0.0
self.doneButtonSolidBackgroundNode.clipsToBounds = true
@@ -85,11 +87,11 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
self.doneButtonSolidTitleNode.isUserInteractionEnabled = false
super.init()
+
+ self.doneButton.isExclusiveTouch = true
self.addSubnode(self.doneButtonBackgroundNode)
- self.doneButtonVibrancyView.contentView.addSubnode(self.doneButtonVibrancyTitleNode)
- self.doneButtonBackgroundNode.view.addSubview(self.doneButtonVibrancyView)
- self.doneButtonBackgroundNode.addSubnode(self.doneButtonTitleNode)
+ self.addSubnode(self.doneButtonTitleNode)
self.addSubnode(self.doneButtonSolidBackgroundNode)
self.addSubnode(self.doneButtonSolidTitleNode)
@@ -109,8 +111,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
} else {
strongSelf.doneButtonBackgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.doneButtonBackgroundNode.alpha = 0.55
- strongSelf.doneButtonVibrancyTitleNode.layer.removeAnimation(forKey: "opacity")
- strongSelf.doneButtonVibrancyTitleNode.alpha = 0.55
+ strongSelf.doneButtonTitleNode.layer.removeAnimation(forKey: "opacity")
+ strongSelf.doneButtonTitleNode.alpha = 0.55
}
} else {
if strongSelf.isSolid {
@@ -121,8 +123,8 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
} else {
strongSelf.doneButtonBackgroundNode.alpha = 1.0
strongSelf.doneButtonBackgroundNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
- strongSelf.doneButtonVibrancyTitleNode.alpha = 1.0
- strongSelf.doneButtonVibrancyTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
+ strongSelf.doneButtonTitleNode.alpha = 1.0
+ strongSelf.doneButtonTitleNode.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
}
}
}
@@ -146,7 +148,6 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
transition.updateAlpha(node: self.doneButtonBackgroundNode, alpha: isSolid ? 0.0 : 1.0)
transition.updateAlpha(node: self.doneButtonSolidBackgroundNode, alpha: isSolid ? 1.0 : 0.0)
transition.updateAlpha(node: self.doneButtonTitleNode, alpha: isSolid ? 0.0 : 1.0)
- transition.updateAlpha(node: self.doneButtonVibrancyTitleNode, alpha: isSolid ? 0.0 : 1.0)
transition.updateAlpha(node: self.doneButtonSolidTitleNode, alpha: isSolid ? 1.0 : 0.0)
}
@@ -167,8 +168,7 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
doneTitle = ""
self.doneButton.isUserInteractionEnabled = false
}
- self.doneButtonTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: UIColor(rgb: 0x000000, alpha: 0.25))
- self.doneButtonVibrancyTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: .white)
+ self.doneButtonTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: .white)
self.doneButtonSolidBackgroundNode.backgroundColor = theme.list.itemCheckColors.fillColor
self.doneButtonSolidTitleNode.attributedText = NSAttributedString(string: doneTitle, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor)
@@ -181,18 +181,18 @@ final class WallpaperGalleryToolbarNode: ASDisplayNode {
let doneFrame = CGRect(origin: CGPoint(x: inset, y: 2.0), size: CGSize(width: size.width - inset * 2.0, height: buttonHeight))
self.doneButton.frame = doneFrame
self.doneButtonBackgroundNode.frame = doneFrame
- self.doneButtonBackgroundNode.update(size: doneFrame.size, cornerRadius: 14.0, transition: transition)
- self.doneButtonVibrancyView.frame = self.doneButtonBackgroundNode.bounds
+ if let backgroundNode = self.doneButtonBackgroundNode as? WallpaperOptionBackgroundNode {
+ backgroundNode.updateLayout(size: doneFrame.size)
+ } else if let backgroundNode = self.doneButtonBackgroundNode as? WallpaperLightButtonBackgroundNode {
+ backgroundNode.updateLayout(size: doneFrame.size)
+ }
self.doneButtonSolidBackgroundNode.frame = doneFrame
let doneTitleSize = self.doneButtonTitleNode.updateLayout(doneFrame.size)
- self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((doneFrame.width - doneTitleSize.width) / 2.0), y: floorToScreenPixels((doneFrame.height - doneTitleSize.height) / 2.0)), size: doneTitleSize)
-
- let _ = self.doneButtonVibrancyTitleNode.updateLayout(doneFrame.size)
- self.doneButtonVibrancyTitleNode.frame = self.doneButtonTitleNode.frame
+ self.doneButtonTitleNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((doneFrame.width - doneTitleSize.width) / 2.0), y: floorToScreenPixels((doneFrame.height - doneTitleSize.height) / 2.0)), size: doneTitleSize).offsetBy(dx: doneFrame.minX, dy: doneFrame.minY)
let _ = self.doneButtonSolidTitleNode.updateLayout(doneFrame.size)
- self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame.offsetBy(dx: doneFrame.minX, dy: doneFrame.minY)
+ self.doneButtonSolidTitleNode.frame = self.doneButtonTitleNode.frame
}
@objc func cancelPressed() {
diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift
index 202496bd49..444e2382b1 100644
--- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift
+++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift
@@ -5,6 +5,7 @@ import AsyncDisplayKit
import SwiftSignalKit
import Postbox
import CheckNode
+import AnimationUI
enum WallpaperOptionButtonValue {
case check(Bool)
@@ -34,49 +35,121 @@ private func generateColorsImage(diameter: CGFloat, colors: [UIColor]) -> UIImag
})
}
+final class WallpaperLightButtonBackgroundNode: ASDisplayNode {
+ private let backgroundNode: NavigationBackgroundNode
+ private let overlayNode: ASDisplayNode
+ private let lightNode: ASDisplayNode
+
+ override init() {
+ self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: false)
+ self.overlayNode = ASDisplayNode()
+ self.overlayNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75)
+ self.overlayNode.layer.compositingFilter = "overlayBlendMode"
+
+ self.lightNode = ASDisplayNode()
+ self.lightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.2)
+
+ super.init()
+
+ self.clipsToBounds = true
+
+ self.addSubnode(self.backgroundNode)
+ self.addSubnode(self.overlayNode)
+ self.addSubnode(self.lightNode)
+ }
+
+ func updateLayout(size: CGSize) {
+ let frame = CGRect(origin: .zero, size: size)
+ self.backgroundNode.frame = frame
+ self.overlayNode.frame = frame
+ self.lightNode.frame = frame
+
+ self.backgroundNode.update(size: size, transition: .immediate)
+ }
+}
+
+final class WallpaperOptionBackgroundNode: ASDisplayNode {
+ private let backgroundNode: NavigationBackgroundNode
+
+ var enableSaturation: Bool {
+ didSet {
+ self.backgroundNode.updateColor(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: self.enableSaturation, transition: .immediate)
+ }
+ }
+
+ init(enableSaturation: Bool = false) {
+ self.enableSaturation = enableSaturation
+ self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: enableSaturation)
+
+ super.init()
+
+ self.clipsToBounds = true
+ self.isUserInteractionEnabled = false
+
+ self.addSubnode(self.backgroundNode)
+ }
+
+ func updateLayout(size: CGSize) {
+ let frame = CGRect(origin: .zero, size: size)
+ self.backgroundNode.frame = frame
+
+ self.backgroundNode.update(size: size, transition: .immediate)
+ }
+}
+
final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode {
enum Content {
case icon(image: UIImage?, size: CGSize)
case text(String)
+ case dayNight(isNight: Bool)
+ }
+
+ var enableSaturation: Bool = false {
+ didSet {
+ if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode {
+ backgroundNode.enableSaturation = self.enableSaturation
+ }
+ }
}
private let content: Content
+ var dark: Bool {
+ didSet {
+ if self.dark != oldValue {
+ self.backgroundNode.removeFromSupernode()
+ if self.dark {
+ self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation)
+ } else {
+ self.backgroundNode = WallpaperLightButtonBackgroundNode()
+ }
+ self.insertSubnode(self.backgroundNode, at: 0)
+ }
+ }
+ }
- private let backgroundNode: NavigationBackgroundNode
- private let vibrancyView: UIVisualEffectView
-
+ private var backgroundNode: ASDisplayNode
private let iconNode: ASImageNode
- private let darkIconNode: ASImageNode
-
private let textNode: ImmediateTextNode
- private let darkTextNode: ImmediateTextNode
+ private var animationNode: AnimationNode?
func setIcon(_ image: UIImage?) {
self.iconNode.image = generateTintedImage(image: image, color: .white)
- self.darkIconNode.image = generateTintedImage(image: image, color: UIColor(rgb: 0x000000, alpha: 0.25))
}
- init(content: Content) {
+ init(content: Content, dark: Bool) {
self.content = content
+ self.dark = dark
- self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75))
-
- let blurEffect: UIBlurEffect
- if #available(iOS 13.0, *) {
- blurEffect = UIBlurEffect(style: .extraLight)
+ if dark {
+ self.backgroundNode = WallpaperOptionBackgroundNode(enableSaturation: self.enableSaturation)
} else {
- blurEffect = UIBlurEffect(style: .light)
+ self.backgroundNode = WallpaperLightButtonBackgroundNode()
}
- self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect))
self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false
self.iconNode.contentMode = .center
- self.darkIconNode = ASImageNode()
- self.darkIconNode.displaysAsynchronously = false
- self.darkIconNode.contentMode = .center
-
var title: String
switch content {
case let .text(text):
@@ -84,44 +157,72 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode {
case let .icon(icon, _):
title = ""
self.iconNode.image = generateTintedImage(image: icon, color: .white)
- self.darkIconNode.image = generateTintedImage(image: icon, color: UIColor(rgb: 0x000000, alpha: 0.25))
+ case let .dayNight(isNight):
+ title = ""
+ let animationNode = AnimationNode(animation: isNight ? "anim_sun_reverse" : "anim_sun", colors: [:], scale: 1.0)
+ animationNode.speed = 1.66
+ animationNode.isUserInteractionEnabled = false
+ self.animationNode = animationNode
}
self.textNode = ImmediateTextNode()
self.textNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: .white)
- self.darkTextNode = ImmediateTextNode()
- self.darkTextNode.attributedText = NSAttributedString(string: title, font: Font.semibold(15.0), textColor: UIColor(rgb: 0x000000, alpha: 0.25))
-
super.init()
+ self.isExclusiveTouch = true
+
self.addSubnode(self.backgroundNode)
- self.vibrancyView.contentView.addSubnode(self.iconNode)
- self.vibrancyView.contentView.addSubnode(self.textNode)
- self.backgroundNode.view.addSubview(self.vibrancyView)
- self.backgroundNode.addSubnode(self.darkIconNode)
- self.backgroundNode.addSubnode(self.darkTextNode)
+ self.addSubnode(self.iconNode)
+ self.addSubnode(self.textNode)
+
+ if let animationNode = self.animationNode {
+ self.addSubnode(animationNode)
+ }
self.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
strongSelf.backgroundNode.alpha = 0.4
+
+ strongSelf.iconNode.layer.removeAnimation(forKey: "opacity")
+ strongSelf.iconNode.alpha = 0.4
+
+ strongSelf.textNode.layer.removeAnimation(forKey: "opacity")
+ strongSelf.textNode.alpha = 0.4
+
+// if let animationNode = strongSelf.animationNode {
+// animationNode.layer.removeAnimation(forKey: "opacity")
+// animationNode.alpha = 0.4
+// }
} else {
strongSelf.backgroundNode.alpha = 1.0
strongSelf.backgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
+
+ strongSelf.iconNode.alpha = 1.0
+ strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
+
+ strongSelf.textNode.alpha = 1.0
+ strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
+
+// if let animationNode = strongSelf.animationNode {
+// animationNode.alpha = 1.0
+// animationNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
+// }
}
}
}
}
+ func setIsNight(_ isNight: Bool) {
+ self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: [:])
+ self.animationNode?.speed = 1.66
+ self.animationNode?.playOnce()
+ }
+
var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) {
didSet {
- if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) {
- self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate)
- } else {
- self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate)
- }
}
}
@@ -130,11 +231,12 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode {
switch self.content {
case .text:
let size = self.textNode.updateLayout(constrainedSize)
- let _ = self.darkTextNode.updateLayout(constrainedSize)
self.textSize = size
return CGSize(width: ceil(size.width) + 16.0, height: 28.0)
case let .icon(_, size):
return size
+ case .dayNight:
+ return CGSize(width: 28.0, height: 28.0)
}
}
@@ -143,31 +245,34 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode {
let size = self.bounds.size
self.backgroundNode.frame = self.bounds
- self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: self.bounds.size.height / 2.0, transition: .immediate)
- self.vibrancyView.frame = self.bounds
+ if let backgroundNode = self.backgroundNode as? WallpaperOptionBackgroundNode {
+ backgroundNode.updateLayout(size: self.backgroundNode.bounds.size)
+ } else if let backgroundNode = self.backgroundNode as? WallpaperLightButtonBackgroundNode {
+ backgroundNode.updateLayout(size: self.backgroundNode.bounds.size)
+ }
+ self.backgroundNode.cornerRadius = size.height / 2.0
self.iconNode.frame = self.bounds
- self.darkIconNode.frame = self.bounds
if let textSize = self.textSize {
self.textNode.frame = CGRect(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0), width: textSize.width, height: textSize.height)
- self.darkTextNode.frame = self.textNode.frame
+ }
+
+ if let animationNode = self.animationNode {
+ animationNode.bounds = CGRect(origin: .zero, size: CGSize(width: 24.0, height: 24.0))
+ animationNode.position = CGPoint(x: 14.0, y: 14.0)
}
}
}
final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
- private let backgroundNode: NavigationBackgroundNode
- private let vibrancyView: UIVisualEffectView
+ let backgroundNode: WallpaperOptionBackgroundNode
private let checkNode: CheckNode
- private let darkCheckNode: CheckNode
-
private let colorNode: ASImageNode
private let textNode: ImmediateTextNode
- private let darkTextNode: ImmediateTextNode
private var textSize: CGSize?
@@ -189,14 +294,12 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
self._value = .colors(newValue, colors)
}
self.checkNode.setSelected(newValue, animated: false)
- self.darkCheckNode.setSelected(newValue, animated: false)
}
}
var title: String {
didSet {
self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white)
- self.darkTextNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: UIColor(rgb: 0x000000, alpha: 0.25))
}
}
@@ -204,62 +307,42 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
self._value = value
self.title = title
- self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0xffffff, alpha: 0.4))
- self.backgroundNode.cornerRadius = 14.0
-
- let blurEffect: UIBlurEffect
- if #available(iOS 13.0, *) {
- blurEffect = UIBlurEffect(style: .extraLight)
- } else {
- blurEffect = UIBlurEffect(style: .light)
- }
- self.vibrancyView = UIVisualEffectView(effect: UIVibrancyEffect(blurEffect: blurEffect))
-
- let darkColor = UIColor(rgb: 0x000000, alpha: 0.25)
+ self.backgroundNode = WallpaperOptionBackgroundNode()
self.checkNode = CheckNode(theme: CheckNodeTheme(backgroundColor: .white, strokeColor: .clear, borderColor: .white, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5))
self.checkNode.isUserInteractionEnabled = false
- self.darkCheckNode = CheckNode(theme: CheckNodeTheme(backgroundColor: darkColor, strokeColor: .clear, borderColor: darkColor, overlayBorder: false, hasInset: false, hasShadow: false, borderWidth: 1.5))
- self.darkCheckNode.isUserInteractionEnabled = false
-
self.colorNode = ASImageNode()
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: .white)
-
- self.darkTextNode = ImmediateTextNode()
- self.darkTextNode.displaysAsynchronously = false
- self.darkTextNode.attributedText = NSAttributedString(string: title, font: Font.medium(13), textColor: UIColor(rgb: 0x000000, alpha: 0.25))
-
+
super.init()
+ self.clipsToBounds = true
+ self.cornerRadius = 14.0
+ self.isExclusiveTouch = true
+
switch value {
case let .check(selected):
self.checkNode.isHidden = false
- self.darkCheckNode.isHidden = false
self.colorNode.isHidden = true
self.checkNode.selected = selected
- self.darkCheckNode.selected = selected
case let .color(_, color):
self.checkNode.isHidden = true
- self.darkCheckNode.isHidden = true
self.colorNode.isHidden = false
self.colorNode.image = generateFilledCircleImage(diameter: 18.0, color: color)
case let .colors(_, colors):
self.checkNode.isHidden = true
- self.darkCheckNode.isHidden = true
self.colorNode.isHidden = false
self.colorNode.image = generateColorsImage(diameter: 18.0, colors: colors)
}
self.addSubnode(self.backgroundNode)
- self.vibrancyView.contentView.addSubnode(self.checkNode)
- self.vibrancyView.contentView.addSubnode(self.textNode)
- self.backgroundNode.view.addSubview(self.vibrancyView)
- self.addSubnode(self.darkCheckNode)
- self.addSubnode(self.darkTextNode)
+
+ self.addSubnode(self.checkNode)
+ self.addSubnode(self.textNode)
self.addSubnode(self.colorNode)
self.highligthedChanged = { [weak self] highlighted in
@@ -283,11 +366,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) {
didSet {
- if self.buttonColor == UIColor(rgb: 0x000000, alpha: 0.3) {
- self.backgroundNode.updateColor(color: UIColor(rgb: 0xf2f2f2, alpha: 0.75), transition: .immediate)
- } else {
- self.backgroundNode.updateColor(color: self.buttonColor, transition: .immediate)
- }
}
}
@@ -357,12 +435,10 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
self._value = .colors(selected, colors)
}
self.checkNode.setSelected(selected, animated: animated)
- self.darkCheckNode.setSelected(selected, animated: animated)
}
func setEnabled(_ enabled: Bool) {
let alpha: CGFloat = enabled ? 1.0 : 0.4
- self.backgroundNode.alpha = alpha
self.checkNode.alpha = alpha
self.colorNode.alpha = alpha
self.textNode.alpha = alpha
@@ -371,7 +447,6 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
override func measure(_ constrainedSize: CGSize) -> CGSize {
let size = self.textNode.updateLayout(constrainedSize)
- let _ = self.darkTextNode.updateLayout(constrainedSize)
self.textSize = size
return CGSize(width: ceil(size.width) + 48.0, height: 30.0)
}
@@ -380,8 +455,7 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
super.layout()
self.backgroundNode.frame = self.bounds
- self.backgroundNode.update(size: self.backgroundNode.bounds.size, cornerRadius: 15.0, transition: .immediate)
- self.vibrancyView.frame = self.bounds
+ self.backgroundNode.updateLayout(size: self.backgroundNode.bounds.size)
guard let _ = self.textSize else {
return
@@ -392,12 +466,166 @@ final class WallpaperOptionButtonNode: HighlightTrackingButtonNode {
let checkSize = CGSize(width: 18.0, height: 18.0)
let checkFrame = CGRect(origin: CGPoint(x: padding, y: padding), size: checkSize)
self.checkNode.frame = checkFrame
- self.darkCheckNode.frame = checkFrame
self.colorNode.frame = checkFrame
if let textSize = self.textSize {
self.textNode.frame = CGRect(x: max(padding + checkSize.width + spacing, padding + checkSize.width + floor((self.bounds.width - padding - checkSize.width - textSize.width) / 2.0) - 2.0), y: floorToScreenPixels((self.bounds.height - textSize.height) / 2.0), width: textSize.width, height: textSize.height)
- self.darkTextNode.frame = self.textNode.frame
}
}
}
+
+final class WallpaperSliderNode: ASDisplayNode {
+ let minValue: CGFloat
+ let maxValue: CGFloat
+ var value: CGFloat = 1.0 {
+ didSet {
+ if let size = self.validLayout {
+ self.updateLayout(size: size)
+ }
+ }
+ }
+
+ private let backgroundNode: NavigationBackgroundNode
+
+ private let foregroundNode: ASDisplayNode
+ private let foregroundLightNode: ASDisplayNode
+ private let leftIconNode: ASImageNode
+ private let rightIconNode: ASImageNode
+
+ private let valueChanged: (CGFloat, Bool) -> Void
+
+ private let hapticFeedback = HapticFeedback()
+
+ private var validLayout: CGSize?
+
+ init(minValue: CGFloat, maxValue: CGFloat, value: CGFloat, valueChanged: @escaping (CGFloat, Bool) -> Void) {
+ self.minValue = minValue
+ self.maxValue = maxValue
+ self.value = value
+ self.valueChanged = valueChanged
+
+ self.backgroundNode = NavigationBackgroundNode(color: UIColor(rgb: 0x333333, alpha: 0.35), enableBlur: true, enableSaturation: false)
+
+ self.foregroundNode = ASDisplayNode()
+ self.foregroundNode.clipsToBounds = true
+ self.foregroundNode.cornerRadius = 3.0
+ self.foregroundNode.isAccessibilityElement = false
+ self.foregroundNode.backgroundColor = UIColor(rgb: 0xffffff, alpha: 0.75)
+ self.foregroundNode.layer.compositingFilter = "overlayBlendMode"
+ self.foregroundNode.isUserInteractionEnabled = false
+
+ self.foregroundLightNode = ASDisplayNode()
+ self.foregroundLightNode.clipsToBounds = true
+ self.foregroundLightNode.cornerRadius = 3.0
+ self.foregroundLightNode.backgroundColor = UIColor(rgb: 0xf2f2f2, alpha: 0.2)
+
+ self.leftIconNode = ASImageNode()
+ self.leftIconNode.displaysAsynchronously = false
+ self.leftIconNode.image = UIImage(bundleImageName: "Settings/WallpaperBrightnessMin")
+ self.leftIconNode.contentMode = .center
+
+ self.rightIconNode = ASImageNode()
+ self.rightIconNode.displaysAsynchronously = false
+ self.rightIconNode.image = UIImage(bundleImageName: "Settings/WallpaperBrightnessMax")
+ self.rightIconNode.contentMode = .center
+
+ super.init()
+
+ self.clipsToBounds = true
+ self.cornerRadius = 15.0
+ self.isUserInteractionEnabled = true
+
+ self.addSubnode(self.backgroundNode)
+
+ self.addSubnode(self.foregroundNode)
+ self.addSubnode(self.foregroundLightNode)
+
+ self.addSubnode(self.leftIconNode)
+ self.addSubnode(self.rightIconNode)
+ }
+
+ override func didLoad() {
+ super.didLoad()
+
+ let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
+ self.view.addGestureRecognizer(panGestureRecognizer)
+
+ let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))
+ self.view.addGestureRecognizer(tapGestureRecognizer)
+ }
+
+ var ignoreUpdates = false
+ func animateValue(from: CGFloat, to: CGFloat, transition: ContainedViewLayoutTransition = .immediate) {
+ guard let size = self.validLayout else {
+ return
+ }
+ self.internalUpdateLayout(size: size, value: from)
+ self.internalUpdateLayout(size: size, value: to, transition: transition)
+ }
+
+ func internalUpdateLayout(size: CGSize, value: CGFloat, transition: ContainedViewLayoutTransition = .immediate) {
+ self.validLayout = size
+
+ transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: .zero, size: size))
+ self.backgroundNode.update(size: size, transition: transition)
+
+ if let icon = self.leftIconNode.image {
+ transition.updateFrame(node: self.leftIconNode, frame: CGRect(origin: CGPoint(x: 7.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size))
+ }
+
+ if let icon = self.rightIconNode.image {
+ transition.updateFrame(node: self.rightIconNode, frame: CGRect(origin: CGPoint(x: size.width - icon.size.width - 6.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size))
+ }
+
+ let range = self.maxValue - self.minValue
+ let value = (value - self.minValue) / range
+ let foregroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: value * size.width, height: size.height))
+ transition.updateFrame(node: self.foregroundNode, frame: foregroundFrame)
+ transition.updateFrame(node: self.foregroundLightNode, frame: foregroundFrame)
+ }
+
+ func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition = .immediate) {
+ guard !self.ignoreUpdates else {
+ return
+ }
+ self.internalUpdateLayout(size: size, value: self.value, transition: transition)
+ }
+
+ @objc private func panGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
+ let range = self.maxValue - self.minValue
+ switch gestureRecognizer.state {
+ case .began:
+ break
+ case .changed:
+ let previousValue = self.value
+
+ let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
+ let delta = translation / self.bounds.width * range
+ self.value = max(self.minValue, min(self.maxValue, self.value + delta))
+ gestureRecognizer.setTranslation(CGPoint(), in: gestureRecognizer.view)
+
+ if self.value == 0.0 && previousValue != 0.0 {
+ self.hapticFeedback.impact(.soft)
+ } else if self.value == 1.0 && previousValue != 1.0 {
+ self.hapticFeedback.impact(.soft)
+ }
+ if abs(previousValue - self.value) >= 0.001 {
+ self.valueChanged(self.value, false)
+ }
+ case .ended:
+ let translation: CGFloat = gestureRecognizer.translation(in: gestureRecognizer.view).x
+ let delta = translation / self.bounds.width * range
+ self.value = max(self.minValue, min(self.maxValue, self.value + delta))
+ self.valueChanged(self.value, true)
+ default:
+ break
+ }
+ }
+
+ @objc private func tapGesture(_ gestureRecognizer: UITapGestureRecognizer) {
+ let range = self.maxValue - self.minValue
+ let location = gestureRecognizer.location(in: gestureRecognizer.view)
+ self.value = max(self.minValue, min(self.maxValue, self.minValue + location.x / self.bounds.width * range))
+ self.valueChanged(self.value, true)
+ }
+}
diff --git a/submodules/ShareController/Sources/ShareController.swift b/submodules/ShareController/Sources/ShareController.swift
index d40af9242a..f3a36a73be 100644
--- a/submodules/ShareController/Sources/ShareController.swift
+++ b/submodules/ShareController/Sources/ShareController.swift
@@ -181,9 +181,17 @@ private func collectExternalShareItems(strings: PresentationStrings, dateTimeFor
case .progress:
return .progress
case let .done(data):
- if let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)), let image = UIImage(data: fileData) {
+ guard let fileData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) else {
+ return .progress
+ }
+ if let image = UIImage(data: fileData) {
return .done(.image(image))
} else {
+ #if DEBUG
+ if "".isEmpty {
+ return .done(.file(URL(fileURLWithPath: data.path), "image.bin", "application/octet-stream"))
+ }
+ #endif
return .progress
}
}
diff --git a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift
index 9cb348c33d..a27fad04d5 100644
--- a/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift
+++ b/submodules/SparseItemGrid/Sources/SparseItemGridScrollingArea.swift
@@ -759,7 +759,7 @@ final class SparseItemGridScrollingIndicatorComponent: CombinedComponent {
let date = context.component.date
let components = date.0.components(separatedBy: " ")
- let month = components.first ?? ""
+ let month = String(components.prefix(upTo: components.count - 1).joined(separator: " "))
let year = components.last ?? ""
var monthAnimation: RollingText.AnimationDirection?
diff --git a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift
index dcfd793e92..f28a29dc5f 100644
--- a/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift
+++ b/submodules/TelegramAnimatedStickerNode/Sources/AnimatedStickerUtils.swift
@@ -72,8 +72,8 @@ public func fetchCompressedLottieFirstFrameAJpeg(data: Data, size: CGSize, fitzM
let alphaData = NSMutableData()
if let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
- CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
+ CGImageDestinationSetProperties(alphaDestination, NSDictionary() as CFDictionary)
let colorQuality: Float
let alphaQuality: Float
@@ -387,7 +387,7 @@ public func cacheVideoStickerFrames(path: String, size: CGSize, cacheKey: String
}
if frameCount > 0 {
- file.seek(position: 4)
+ let _ = file.seek(position: 4)
let _ = file.write(&frameCount, count: 4)
}
diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift
index 6822745a8e..bd9372bcd0 100644
--- a/submodules/TelegramApi/Sources/Api0.swift
+++ b/submodules/TelegramApi/Sources/Api0.swift
@@ -479,7 +479,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1434950843] = { return Api.MessageAction.parse_messageActionSetChatTheme($0) }
dict[-1136350937] = { return Api.MessageAction.parse_messageActionSetChatWallPaper($0) }
dict[1007897979] = { return Api.MessageAction.parse_messageActionSetMessagesTTL($0) }
- dict[-632006598] = { return Api.MessageAction.parse_messageActionSetSameChatWallPaper($0) }
+ dict[-1065845395] = { return Api.MessageAction.parse_messageActionSetSameChatWallPaper($0) }
dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) }
dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) }
dict[-1064024032] = { return Api.MessageAction.parse_messageActionTopicEdit($0) }
diff --git a/submodules/TelegramApi/Sources/Api12.swift b/submodules/TelegramApi/Sources/Api12.swift
index 4e3c6116ff..55ad86a29f 100644
--- a/submodules/TelegramApi/Sources/Api12.swift
+++ b/submodules/TelegramApi/Sources/Api12.swift
@@ -263,7 +263,7 @@ public extension Api {
case messageActionSetChatTheme(emoticon: String)
case messageActionSetChatWallPaper(wallpaper: Api.WallPaper)
case messageActionSetMessagesTTL(flags: Int32, period: Int32, autoSettingFrom: Int64?)
- case messageActionSetSameChatWallPaper
+ case messageActionSetSameChatWallPaper(wallpaper: Api.WallPaper)
case messageActionSuggestProfilePhoto(photo: Api.Photo)
case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?)
case messageActionTopicEdit(flags: Int32, title: String?, iconEmojiId: Int64?, closed: Api.Bool?, hidden: Api.Bool?)
@@ -522,11 +522,11 @@ public extension Api {
serializeInt32(period, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 0) != 0 {serializeInt64(autoSettingFrom!, buffer: buffer, boxed: false)}
break
- case .messageActionSetSameChatWallPaper:
+ case .messageActionSetSameChatWallPaper(let wallpaper):
if boxed {
- buffer.appendInt32(-632006598)
+ buffer.appendInt32(-1065845395)
}
-
+ wallpaper.serialize(buffer, true)
break
case .messageActionSuggestProfilePhoto(let photo):
if boxed {
@@ -637,8 +637,8 @@ public extension Api {
return ("messageActionSetChatWallPaper", [("wallpaper", wallpaper as Any)])
case .messageActionSetMessagesTTL(let flags, let period, let autoSettingFrom):
return ("messageActionSetMessagesTTL", [("flags", flags as Any), ("period", period as Any), ("autoSettingFrom", autoSettingFrom as Any)])
- case .messageActionSetSameChatWallPaper:
- return ("messageActionSetSameChatWallPaper", [])
+ case .messageActionSetSameChatWallPaper(let wallpaper):
+ return ("messageActionSetSameChatWallPaper", [("wallpaper", wallpaper as Any)])
case .messageActionSuggestProfilePhoto(let photo):
return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)])
case .messageActionTopicCreate(let flags, let title, let iconColor, let iconEmojiId):
@@ -1092,7 +1092,17 @@ public extension Api {
}
}
public static func parse_messageActionSetSameChatWallPaper(_ reader: BufferReader) -> MessageAction? {
- return Api.MessageAction.messageActionSetSameChatWallPaper
+ var _1: Api.WallPaper?
+ if let signature = reader.readInt32() {
+ _1 = Api.parse(reader, signature: signature) as? Api.WallPaper
+ }
+ let _c1 = _1 != nil
+ if _c1 {
+ return Api.MessageAction.messageActionSetSameChatWallPaper(wallpaper: _1!)
+ }
+ else {
+ return nil
+ }
}
public static func parse_messageActionSuggestProfilePhoto(_ reader: BufferReader) -> MessageAction? {
var _1: Api.Photo?
diff --git a/submodules/TelegramApi/Sources/Api30.swift b/submodules/TelegramApi/Sources/Api30.swift
index 697f9d22ba..66622b9ad0 100644
--- a/submodules/TelegramApi/Sources/Api30.swift
+++ b/submodules/TelegramApi/Sources/Api30.swift
@@ -6884,11 +6884,11 @@ public extension Api.functions.messages {
public extension Api.functions.messages {
static func setChatWallPaper(flags: Int32, peer: Api.InputPeer, wallpaper: Api.InputWallPaper?, settings: Api.WallPaperSettings?, id: Int32?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) {
let buffer = Buffer()
- buffer.appendInt32(-609568219)
+ buffer.appendInt32(-1879389471)
serializeInt32(flags, buffer: buffer, boxed: false)
peer.serialize(buffer, true)
if Int(flags) & Int(1 << 0) != 0 {wallpaper!.serialize(buffer, true)}
- if Int(flags) & Int(1 << 0) != 0 {settings!.serialize(buffer, true)}
+ if Int(flags) & Int(1 << 2) != 0 {settings!.serialize(buffer, true)}
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(id!, buffer: buffer, boxed: false)}
return (FunctionDescription(name: "messages.setChatWallPaper", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("wallpaper", String(describing: wallpaper)), ("settings", String(describing: settings)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
diff --git a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift
index 87222f20e4..de6f7c7965 100644
--- a/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift
+++ b/submodules/TelegramBaseController/Sources/MediaNavigationAccessoryHeaderNode.swift
@@ -526,7 +526,7 @@ public final class MediaNavigationAccessoryHeaderNode: ASDisplayNode, UIScrollVi
let _ = (ApplicationSpecificNotice.incrementAudioRateOptionsTip(accountManager: self.context.sharedContext.accountManager)
|> deliverOnMainQueue).start(next: { [weak self] value in
if let strongSelf = self, let controller = strongSelf.getController?(), value == 2 {
- let tooltipController = TooltipScreen(account: strongSelf.context.account, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
+ let tooltipController = TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: strongSelf.strings.Conversation_AudioRateOptionsTooltip, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 0.0, dy: 4.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
})
controller.present(tooltipController, in: .window(.root))
diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift
index f1d8d74b9c..25cc58eee2 100644
--- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift
+++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift
@@ -760,8 +760,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
}) else {
return
}
-
- self.present?(TooltipScreen(account: self.account, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in
+
+ self.present?(TooltipScreen(account: self.account, sharedContext: self.sharedContext, text: self.presentationData.strings.Call_CameraOrScreenTooltip, style: .light, icon: nil, location: .point(location.offsetBy(dx: 0.0, dy: -14.0), .bottom), displayDuration: .custom(5.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}))
}
diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift
index 9af33d1ca0..f468a4bada 100644
--- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift
+++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift
@@ -286,11 +286,11 @@ private extension CurrentImpl {
case .mediaStream:
let ssrcId = UInt32.random(in: 0 ..< UInt32(Int32.max - 1))
let dict: [String: Any] = [
- "fingerprints": [],
+ "fingerprints": [] as [Any],
"ufrag": "",
"pwd": "",
"ssrc": Int32(bitPattern: ssrcId),
- "ssrc-groups": []
+ "ssrc-groups": [] as [Any]
]
guard let jsonString = (try? JSONSerialization.data(withJSONObject: dict, options: [])).flatMap({ String(data: $0, encoding: .utf8) }) else {
return .never()
diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift
index ccd9a5bfee..b6020e48de 100644
--- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift
+++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift
@@ -2310,7 +2310,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else {
text = presentationData.strings.VoiceChat_RecordingInProgress
}
- strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
+ strongSelf.controller?.present(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: text, icon: nil, location: .point(location.offsetBy(dx: 1.0, dy: 0.0), .top), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: true)
}), in: .window(.root))
}
@@ -3507,7 +3507,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
if !callState.subscribedToScheduled {
let location = self.actionButton.view.convert(self.actionButton.bounds, to: self.view).center
let point = CGRect(origin: CGPoint(x: location.x - 5.0, y: location.y - 5.0 - 68.0), size: CGSize(width: 10.0, height: 10.0))
- self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
+ self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_ReminderNotify, style: .gradient(UIColor(rgb: 0x262c5a), UIColor(rgb: 0x5d2835)), icon: nil, location: .point(point, .bottom), displayDuration: .custom(3.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))
}
@@ -6411,7 +6411,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
point.origin.y += 32.0
}
}
- self.controller?.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in
+ self.controller?.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.VoiceChat_UnmuteSuggestion, style: .gradient(UIColor(rgb: 0x1d446c), UIColor(rgb: 0x193e63)), icon: nil, location: .point(point, position), displayDuration: .custom(8.0), shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}), in: .window(.root))
}
diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift
index 1b48a4be4c..a478bb3e8a 100644
--- a/submodules/TelegramCore/Sources/Account/Account.swift
+++ b/submodules/TelegramCore/Sources/Account/Account.swift
@@ -915,6 +915,7 @@ public class Account {
public private(set) var pendingUpdateMessageManager: PendingUpdateMessageManager!
private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager!
private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext!
+ public private(set) var pendingPeerMediaUploadManager: PendingPeerMediaUploadManager!
private var peerInputActivityManager: PeerInputActivityManager!
private var localInputActivityManager: PeerInputActivityManager!
private var accountPresenceManager: AccountPresenceManager!
@@ -970,6 +971,8 @@ public class Account {
private var lastSmallLogPostTimestamp: Double?
private let smallLogPostDisposable = MetaDisposable()
+ let networkStatsContext: NetworkStatsContext
+
public init(accountManager: AccountManager, id: AccountRecordId, basePath: String, testingEnvironment: Bool, postbox: Postbox, network: Network, networkArguments: NetworkInitializationArguments, peerId: PeerId, auxiliaryMethods: AccountAuxiliaryMethods, supplementary: Bool) {
self.accountManager = accountManager
self.id = id
@@ -983,6 +986,8 @@ public class Account {
self.auxiliaryMethods = auxiliaryMethods
self.supplementary = supplementary
+ self.networkStatsContext = NetworkStatsContext(postbox: postbox)
+
self.peerInputActivityManager = PeerInputActivityManager()
self.callSessionManager = CallSessionManager(postbox: postbox, network: network, maxLayer: networkArguments.voipMaxLayer, versions: networkArguments.voipVersions, addUpdates: { [weak self] updates in
self?.stateManager?.addUpdates(updates)
@@ -1025,6 +1030,7 @@ public class Account {
self.messageMediaPreuploadManager = MessageMediaPreuploadManager()
self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, accountPeerId: peerId, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, localInputActivityManager: self.localInputActivityManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.mediaReferenceRevalidationContext)
self.pendingUpdateMessageManager = PendingUpdateMessageManager(postbox: postbox, network: network, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, mediaReferenceRevalidationContext: self.mediaReferenceRevalidationContext)
+ self.pendingPeerMediaUploadManager = PendingPeerMediaUploadManager(postbox: postbox, network: network, stateManager: self.stateManager, accountPeerId: self.peerId)
self.network.loggedOut = { [weak self] in
Logger.shared.log("Account", "network logged out")
@@ -1134,13 +1140,15 @@ public class Account {
self.managedOperationsDisposable.add(managedPeerTimestampAttributeOperations(network: self.network, postbox: self.postbox).start())
self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.stateManager.postbox, network: self.stateManager.network, accountPeerId: self.stateManager.accountPeerId).start())
- let importantBackgroundOperations: [Signal] = [
+ let extractedExpr: [Signal] = [
managedSynchronizeChatInputStateOperations(postbox: self.postbox, network: self.network) |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.pendingMessageManager.hasPendingMessages |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
self.pendingUpdateMessageManager.updatingMessageMedia |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
+ self.pendingPeerMediaUploadManager.uploadingPeerMedia |> map { !$0.isEmpty ? AccountRunningImportantTasks.pendingMessages : [] },
self.accountPresenceManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] },
self.notificationAutolockReportManager.isPerformingUpdate() |> map { $0 ? AccountRunningImportantTasks.other : [] }
]
+ let importantBackgroundOperations: [Signal] = extractedExpr
let importantBackgroundOperationsRunning = combineLatest(queue: Queue(), importantBackgroundOperations)
|> map { values -> AccountRunningImportantTasks in
var result: AccountRunningImportantTasks = []
diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift
index 9ac86e8e8d..00f0da5916 100644
--- a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift
+++ b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift
@@ -17,8 +17,25 @@ extension ReplyMarkupButton {
self.init(title: text, titleWhenForwarded: nil, action: .requestMap)
case let .keyboardButtonRequestPhone(text):
self.init(title: text, titleWhenForwarded: nil, action: .requestPhone)
- case let .keyboardButtonSwitchInline(flags, text, query, _):
- self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query))
+ case let .keyboardButtonSwitchInline(flags, text, query, types):
+ var peerTypes = ReplyMarkupButtonAction.PeerTypes()
+ if let types = types {
+ for type in types {
+ switch type {
+ case .inlineQueryPeerTypePM:
+ peerTypes.insert(.users)
+ case .inlineQueryPeerTypeBotPM:
+ peerTypes.insert(.bots)
+ case .inlineQueryPeerTypeBroadcast:
+ peerTypes.insert(.channels)
+ case .inlineQueryPeerTypeChat, .inlineQueryPeerTypeMegagroup:
+ peerTypes.insert(.groups)
+ case .inlineQueryPeerTypeSameBotPM:
+ break
+ }
+ }
+ }
+ self.init(title: text, titleWhenForwarded: nil, action: .switchInline(samePeer: (flags & (1 << 0)) != 0, query: query, peerTypes: peerTypes))
case let .keyboardButtonUrl(text, url):
self.init(title: text, titleWhenForwarded: nil, action: .url(url))
case let .keyboardButtonGame(text):
diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift
index e8972b5cf6..fef8fb0c46 100644
--- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift
+++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift
@@ -110,8 +110,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .requestedPeer(buttonId: buttonId, peerId: peer.peerId))
case let .messageActionSetChatWallPaper(wallpaper):
return TelegramMediaAction(action: .setChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper)))
- case .messageActionSetSameChatWallPaper:
- return TelegramMediaAction(action: .setSameChatWallpaper)
+ case let .messageActionSetSameChatWallPaper(wallpaper):
+ return TelegramMediaAction(action: .setSameChatWallpaper(wallpaper: TelegramWallpaper(apiWallpaper: wallpaper)))
}
}
diff --git a/submodules/TelegramCore/Sources/Network/Download.swift b/submodules/TelegramCore/Sources/Network/Download.swift
index 477be727de..ff00e74251 100644
--- a/submodules/TelegramCore/Sources/Network/Download.swift
+++ b/submodules/TelegramCore/Sources/Network/Download.swift
@@ -362,16 +362,16 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
return true
}
- request.completed = { (boxedResponse, timestamp, error) -> () in
+ request.completed = { (boxedResponse, info, error) -> () in
if let error = error {
- subscriber.putError((error, timestamp))
+ subscriber.putError((error, info?.timestamp ?? 0.0))
} else {
if let result = (boxedResponse as! BoxedMessage).body as? T {
- subscriber.putNext((result, timestamp))
+ subscriber.putNext((result, info?.timestamp ?? 0.0))
subscriber.putCompletion()
}
else {
- subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), timestamp))
+ subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), info?.timestamp ?? 0.0))
}
}
}
@@ -386,7 +386,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
}
}
- func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "") -> Signal<(Any, Double), (MTRpcError, Double)> {
+ func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "") -> Signal<(Any, NetworkResponseInfo), (MTRpcError, Double)> {
let requestService = self.requestService
return Signal { subscriber in
let request = MTRequest()
@@ -414,11 +414,16 @@ class Download: NSObject, MTRequestMessageServiceDelegate {
return true
}
- request.completed = { (boxedResponse, timestamp, error) -> () in
+ request.completed = { (boxedResponse, info, error) -> () in
if let error = error {
- subscriber.putError((error, timestamp))
+ subscriber.putError((error, info?.timestamp ?? 0))
} else {
- subscriber.putNext(((boxedResponse as! BoxedMessage).body, timestamp))
+ let mappedInfo = NetworkResponseInfo(
+ timestamp: info?.timestamp ?? 0.0,
+ networkType: info?.networkType == 0 ? .wifi : .cellular,
+ networkDuration: info?.duration ?? 0.0
+ )
+ subscriber.putNext(((boxedResponse as! BoxedMessage).body, mappedInfo))
subscriber.putCompletion()
}
}
diff --git a/submodules/TelegramCore/Sources/Network/FetchV2.swift b/submodules/TelegramCore/Sources/Network/FetchV2.swift
index e4a05c99f8..14d489c6d5 100644
--- a/submodules/TelegramCore/Sources/Network/FetchV2.swift
+++ b/submodules/TelegramCore/Sources/Network/FetchV2.swift
@@ -647,8 +647,8 @@ private final class FetchImpl {
Logger.shared.log("FetchV2", "\(self.loggingIdentifier): setting known size to \(resultingSize)")
self.knownSize = resultingSize
}
- Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(fetchRange.lowerBound + actualLength)")
- self.onNext(.resourceSizeUpdated(fetchRange.lowerBound + actualLength))
+ Logger.shared.log("FetchV2", "\(self.loggingIdentifier): reporting resource size \(resultingSize)")
+ self.onNext(.resourceSizeUpdated(resultingSize))
}
state.completedRanges.formUnion(RangeSet(partRange))
diff --git a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift
index 568eb5fac4..3bd4c3dbe0 100644
--- a/submodules/TelegramCore/Sources/Network/MultipartFetch.swift
+++ b/submodules/TelegramCore/Sources/Network/MultipartFetch.swift
@@ -86,14 +86,17 @@ private struct DownloadWrapper {
let network: Network
let useMainConnection: Bool
- func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool) -> Signal {
+ func request(_ data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool) -> Signal<(T, NetworkResponseInfo), MTRpcError> {
let target: MultiplexedRequestTarget
if self.isCdn {
target = .cdn(Int(self.datacenterId))
} else {
target = .main(Int(self.datacenterId))
}
- return network.multiplexedRequestManager.request(to: target, consumerId: self.consumerId, data: data, tag: tag, continueInBackground: continueInBackground)
+ return network.multiplexedRequestManager.requestWithAdditionalInfo(to: target, consumerId: self.consumerId, data: data, tag: tag, continueInBackground: continueInBackground)
+ |> mapError { error, _ -> MTRpcError in
+ return error
+ }
}
}
@@ -172,7 +175,7 @@ private final class MultipartCdnHashSource {
self.clusterContexts[offset] = clusterContext
disposable.set((self.masterDownload.request(Api.functions.upload.getCdnFileHashes(fileToken: Buffer(data: self.fileToken), offset: offset), tag: nil, continueInBackground: self.continueInBackground)
- |> map { partHashes -> [Int64: Data] in
+ |> map { partHashes, _ -> [Int64: Data] in
var parsedPartHashes: [Int64: Data] = [:]
for part in partHashes {
switch part {
@@ -288,9 +291,20 @@ private final class MultipartCdnHashSource {
private enum MultipartFetchSource {
case none
case master(location: MultipartFetchMasterLocation, download: DownloadWrapper)
- case cdn(masterDatacenterId: Int32, fileToken: Data, key: Data, iv: Data, download: DownloadWrapper, masterDownload: DownloadWrapper, hashSource: MultipartCdnHashSource)
+ case cdn(masterDatacenterId: Int32, cdnDatacenterId: Int32, fileToken: Data, key: Data, iv: Data, download: DownloadWrapper, masterDownload: DownloadWrapper, hashSource: MultipartCdnHashSource)
- func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal {
+ var effectiveDatacenterId: Int32 {
+ switch self {
+ case .none:
+ return 0
+ case let .master(location, _):
+ return location.datacenterId
+ case let .cdn(_, cdnDatacenterId, _, _, _, _, _, _):
+ return cdnDatacenterId
+ }
+ }
+
+ func request(offset: Int64, limit: Int64, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: FetchResourceReference, fileReference: Data?, continueInBackground: Bool) -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> {
var resourceReferenceValue: MediaResourceReference?
switch resourceReference {
case .forceRevalidate:
@@ -323,14 +337,14 @@ private enum MultipartFetchSource {
}
return .generic
}
- |> mapToSignal { result -> Signal in
+ |> mapToSignal { result, info -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in
switch result {
case let .file(_, _, bytes):
var resultData = bytes.makeData()
if resultData.count > Int(limit) {
resultData.count = Int(limit)
}
- return .single(resultData)
+ return .single((resultData, info))
case let .fileCdnRedirect(dcId, fileToken, encryptionKey, encryptionIv, partHashes):
var parsedPartHashes: [Int64: Data] = [:]
for part in partHashes {
@@ -350,18 +364,18 @@ private enum MultipartFetchSource {
|> mapError { error -> MultipartFetchDownloadError in
return .fatal
}
- |> mapToSignal { result -> Signal in
+ |> mapToSignal { result, info -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in
switch result {
case let .webFile(_, _, _, _, bytes):
var resultData = bytes.makeData()
if resultData.count > Int(limit) {
resultData.count = Int(limit)
}
- return .single(resultData)
+ return .single((resultData, info))
}
}
}
- case let .cdn(masterDatacenterId, fileToken, key, iv, download, _, hashSource):
+ case let .cdn(masterDatacenterId, _, fileToken, key, iv, download, _, hashSource):
var updatedLength = roundUp(Int64(limit), to: 4096)
while updatedLength % 4096 != 0 || 1048576 % updatedLength != 0 {
updatedLength += 1
@@ -371,13 +385,13 @@ private enum MultipartFetchSource {
|> mapError { _ -> MultipartFetchDownloadError in
return .generic
}
- |> mapToSignal { result -> Signal in
+ |> mapToSignal { result, info -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in
switch result {
case let .cdnFileReuploadNeeded(token):
return .fail(.reuploadToCdn(masterDatacenterId: masterDatacenterId, token: token.makeData()))
case let .cdnFile(bytes):
if bytes.size == 0 {
- return .single(bytes.makeData())
+ return .single((bytes.makeData(), info))
} else {
var partIv = iv
let partIvCount = partIv.count
@@ -386,13 +400,14 @@ private enum MultipartFetchSource {
var ivOffset: Int32 = Int32(clamping: (offset / 16)).bigEndian
memcpy(bytes.advanced(by: partIvCount - 4), &ivOffset, 4)
}
- return .single(MTAesCtrDecrypt(bytes.makeData(), key, partIv)!)
+ return .single((MTAesCtrDecrypt(bytes.makeData(), key, partIv)!, info))
}
}
}
return combineLatest(part, hashSource.get(offset: offset, limit: limit))
- |> mapToSignal { partData, hashData -> Signal in
+ |> mapToSignal { partDataAndInfo, hashData -> Signal<(Data, NetworkResponseInfo), MultipartFetchDownloadError> in
+ let (partData, info) = partDataAndInfo
var localOffset: Int64 = 0
while localOffset < partData.count {
let dataToHash = partData.subdata(in: Int(localOffset) ..< min(partData.count, Int(localOffset + Int64(dataHashLength))))
@@ -407,7 +422,7 @@ private enum MultipartFetchSource {
localOffset += Int64(dataHashLength)
}
- return .single(partData)
+ return .single((partData, info))
}
}
}
@@ -425,6 +440,21 @@ private final class MultipartFetchManager {
var byteCount: Int
}
+ private final class FetchingPart {
+ let size: Int64
+ let disposable: Disposable
+ let startTime: Double
+
+ init(
+ size: Int64,
+ disposable: Disposable
+ ) {
+ self.size = size
+ self.disposable = disposable
+ self.startTime = CFAbsoluteTimeGetCurrent()
+ }
+ }
+
let parallelParts: Int
let defaultPartSize: Int64
var partAlignment: Int64 = 4 * 1024
@@ -445,6 +475,7 @@ private final class MultipartFetchManager {
let postbox: Postbox
let network: Network
+ let networkStatsContext: NetworkStatsContext?
let revalidationContext: MediaReferenceRevalidationContext?
let continueInBackground: Bool
let partReady: (Int64, Data) -> Void
@@ -454,7 +485,7 @@ private final class MultipartFetchManager {
private let useMainConnection: Bool
private var source: MultipartFetchSource
- var fetchingParts: [Int64: (Int64, Disposable)] = [:]
+ private var fetchingParts: [Int64: FetchingPart] = [:]
var nextFetchingPartId = 0
var fetchedParts: [Int64: (Int64, Data)] = [:]
var cachedPartHashes: [Int64: Data] = [:]
@@ -474,7 +505,7 @@ private final class MultipartFetchManager {
private var fetchSpeedRecords: [FetchSpeedRecord] = []
private var totalFetchedByteCount: Int = 0
- init(resource: TelegramMediaResource, parameters: MediaResourceFetchParameters?, size: Int64?, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, encryptionKey: SecretFileEncryptionKey?, decryptedSize: Int64?, location: MultipartFetchMasterLocation, postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext?, partReady: @escaping (Int64, Data) -> Void, reportCompleteSize: @escaping (Int64) -> Void, finishWithError: @escaping (MediaResourceDataFetchError) -> Void, useMainConnection: Bool) {
+ init(resource: TelegramMediaResource, parameters: MediaResourceFetchParameters?, size: Int64?, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, encryptionKey: SecretFileEncryptionKey?, decryptedSize: Int64?, location: MultipartFetchMasterLocation, postbox: Postbox, network: Network, networkStatsContext: NetworkStatsContext?, revalidationContext: MediaReferenceRevalidationContext?, partReady: @escaping (Int64, Data) -> Void, reportCompleteSize: @escaping (Int64) -> Void, finishWithError: @escaping (MediaResourceDataFetchError) -> Void, useMainConnection: Bool) {
self.resource = resource
self.parameters = parameters
self.consumerId = Int64.random(in: Int64.min ... Int64.max)
@@ -526,6 +557,7 @@ private final class MultipartFetchManager {
self.state = MultipartDownloadState(encryptionKey: encryptionKey, decryptedSize: decryptedSize)
self.postbox = postbox
self.network = network
+ self.networkStatsContext = networkStatsContext
self.revalidationContext = revalidationContext
self.source = .master(location: location, download: DownloadWrapper(consumerId: self.consumerId, datacenterId: location.datacenterId, isCdn: false, network: network, useMainConnection: self.useMainConnection))
self.partReady = partReady
@@ -613,8 +645,8 @@ private final class MultipartFetchManager {
func cancel() {
self.queue.async {
self.source = .none
- for (_, (_, disposable)) in self.fetchingParts {
- disposable.dispose()
+ for (_, fetchingPart) in self.fetchingParts {
+ fetchingPart.disposable.dispose()
}
self.reuploadToCdnDisposable.dispose()
self.revalidateMediaReferenceDisposable.dispose()
@@ -680,8 +712,8 @@ private final class MultipartFetchManager {
}
}
- for (offset, (size, _)) in self.fetchingParts {
- removeFromFetchIntervals.formUnion(RangeSet(offset ..< (offset + size)))
+ for (offset, fetchingPart) in self.fetchingParts {
+ removeFromFetchIntervals.formUnion(RangeSet(offset ..< (offset + fetchingPart.size)))
}
if let completeSize = self.completeSize {
@@ -758,16 +790,27 @@ private final class MultipartFetchManager {
insertIndex += 1
}
+ let partSize: Int32 = Int32(downloadRange.upperBound - downloadRange.lowerBound)
let part = self.source.request(offset: downloadRange.lowerBound, limit: downloadRange.upperBound - downloadRange.lowerBound, tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground)
- //|> delay(5.0, queue: self.queue)
|> deliverOn(self.queue)
let partDisposable = MetaDisposable()
- self.fetchingParts[downloadRange.lowerBound] = (Int64(downloadRange.count), partDisposable)
-
- partDisposable.set(part.start(next: { [weak self] data in
+ self.fetchingParts[downloadRange.lowerBound] = FetchingPart(size: Int64(downloadRange.count), disposable: partDisposable)
+ let partStartTimestamp = CFAbsoluteTimeGetCurrent()
+ let effectiveDatacenterId = self.source.effectiveDatacenterId
+ partDisposable.set(part.start(next: { [weak self] data, info in
guard let strongSelf = self else {
return
}
+
+ strongSelf.networkStatsContext?.add(downloadEvents: [
+ NetworkStatsContext.DownloadEvent(
+ networkType: info.networkType,
+ datacenterId: effectiveDatacenterId,
+ size: Double(partSize),
+ networkDuration: info.networkDuration,
+ issueDuration: CFAbsoluteTimeGetCurrent() - partStartTimestamp
+ )
+ ])
if data.count < downloadRange.count {
strongSelf.completeSize = downloadRange.lowerBound + Int64(data.count)
}
@@ -788,7 +831,7 @@ private final class MultipartFetchManager {
if !strongSelf.revalidatingMediaReference && !strongSelf.revalidatedMediaReference {
strongSelf.revalidatingMediaReference = true
for (_, part) in strongSelf.fetchingParts {
- part.1.dispose()
+ part.disposable.dispose()
}
strongSelf.fetchingParts.removeAll()
@@ -819,7 +862,7 @@ private final class MultipartFetchManager {
switch strongSelf.source {
case let .master(location, download):
strongSelf.partAlignment = dataHashLength
- strongSelf.source = .cdn(masterDatacenterId: location.datacenterId, fileToken: token, key: key, iv: iv, download: DownloadWrapper(consumerId: strongSelf.consumerId, datacenterId: id, isCdn: true, network: strongSelf.network, useMainConnection: strongSelf.useMainConnection), masterDownload: download, hashSource: MultipartCdnHashSource(queue: strongSelf.queue, fileToken: token, hashes: partHashes, masterDownload: download, continueInBackground: strongSelf.continueInBackground))
+ strongSelf.source = .cdn(masterDatacenterId: location.datacenterId, cdnDatacenterId: id, fileToken: token, key: key, iv: iv, download: DownloadWrapper(consumerId: strongSelf.consumerId, datacenterId: id, isCdn: true, network: strongSelf.network, useMainConnection: strongSelf.useMainConnection), masterDownload: download, hashSource: MultipartCdnHashSource(queue: strongSelf.queue, fileToken: token, hashes: partHashes, masterDownload: download, continueInBackground: strongSelf.continueInBackground))
strongSelf.checkState()
case .cdn, .none:
break
@@ -828,10 +871,13 @@ private final class MultipartFetchManager {
switch strongSelf.source {
case .master, .none:
break
- case let .cdn(_, fileToken, _, _, _, masterDownload, _):
+ case let .cdn(_, _, fileToken, _, _, _, masterDownload, _):
if !strongSelf.reuploadingToCdn {
strongSelf.reuploadingToCdn = true
let reupload: Signal<[Api.FileHash], NoError> = masterDownload.request(Api.functions.upload.reuploadCdnFile(fileToken: Buffer(data: fileToken), requestToken: Buffer(data: token)), tag: nil, continueInBackground: strongSelf.continueInBackground)
+ |> map { result, _ -> [Api.FileHash] in
+ return result
+ }
|> `catch` { _ -> Signal<[Api.FileHash], NoError> in
return .single([])
}
@@ -856,6 +902,7 @@ public func standaloneMultipartFetch(postbox: Postbox, network: Network, resourc
postbox: postbox,
network: network,
mediaReferenceRevalidationContext: nil,
+ networkStatsContext: nil,
resource: resource,
datacenterId: datacenterId,
size: size,
@@ -877,6 +924,7 @@ private func multipartFetchV1(
postbox: Postbox,
network: Network,
mediaReferenceRevalidationContext: MediaReferenceRevalidationContext?,
+ networkStatsContext: NetworkStatsContext?,
resource: TelegramMediaResource,
datacenterId: Int,
size: Int64?,
@@ -944,7 +992,7 @@ private func multipartFetchV1(
subscriber.putNext(.reset)
}
- let manager = MultipartFetchManager(resource: resource, parameters: parameters, size: size, intervals: intervals, encryptionKey: encryptionKey, decryptedSize: decryptedSize, location: location, postbox: postbox, network: network, revalidationContext: mediaReferenceRevalidationContext, partReady: { dataOffset, data in
+ let manager = MultipartFetchManager(resource: resource, parameters: parameters, size: size, intervals: intervals, encryptionKey: encryptionKey, decryptedSize: decryptedSize, location: location, postbox: postbox, network: network, networkStatsContext: networkStatsContext, revalidationContext: mediaReferenceRevalidationContext, partReady: { dataOffset, data in
subscriber.putNext(.dataPart(resourceOffset: dataOffset, data: data, range: 0 ..< Int64(data.count), complete: false))
}, reportCompleteSize: { size in
subscriber.putNext(.resourceSizeUpdated(size))
@@ -968,6 +1016,7 @@ func multipartFetch(
postbox: Postbox,
network: Network,
mediaReferenceRevalidationContext: MediaReferenceRevalidationContext?,
+ networkStatsContext: NetworkStatsContext?,
resource: TelegramMediaResource,
datacenterId: Int,
size: Int64?,
@@ -998,6 +1047,7 @@ func multipartFetch(
postbox: postbox,
network: network,
mediaReferenceRevalidationContext: mediaReferenceRevalidationContext,
+ networkStatsContext: networkStatsContext,
resource: resource,
datacenterId: datacenterId,
size: size,
diff --git a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift
index 53b79f1e65..0bff10aa24 100644
--- a/submodules/TelegramCore/Sources/Network/MultipartUpload.swift
+++ b/submodules/TelegramCore/Sources/Network/MultipartUpload.swift
@@ -186,7 +186,7 @@ private final class MultipartUploadManager {
self.bigTotalParts = nil
} else {
self.bigParts = false
- self.defaultPartSize = 16 * 1024
+ self.defaultPartSize = 128 * 1024
self.bigTotalParts = nil
}
}
@@ -317,7 +317,7 @@ private final class MultipartUploadManager {
switch resourceData {
case let .resourceData(data):
if let file = ManagedFile(queue: nil, path: data.path, mode: .read) {
- file.seek(position: Int64(partOffset))
+ let _ = file.seek(position: Int64(partOffset))
let data = file.readData(count: Int(partSize))
if data.count == partSize {
partData = data
diff --git a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift
index 16c1c22e64..73b7d0c2c8 100644
--- a/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift
+++ b/submodules/TelegramCore/Sources/Network/MultiplexedRequestManager.swift
@@ -33,10 +33,10 @@ private final class RequestData {
let continueInBackground: Bool
let automaticFloodWait: Bool
let deserializeResponse: (Buffer) -> Any?
- let completed: (Any, Double) -> Void
+ let completed: (Any, NetworkResponseInfo) -> Void
let error: (MTRpcError, Double) -> Void
- init(id: Int32, consumerId: Int64, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, Double) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
+ init(id: Int32, consumerId: Int64, target: MultiplexedRequestTarget, functionDescription: FunctionDescription, payload: Buffer, tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, deserializeResponse: @escaping (Buffer) -> Any?, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) {
self.id = id
self.consumerId = consumerId
self.target = target
@@ -80,6 +80,11 @@ private struct MultiplexedRequestTargetTimerKey: Equatable, Hashable {
private typealias SignalKitTimer = SwiftSignalKit.Timer
+struct NetworkResponseInfo {
+ var timestamp: Double
+ var networkType: NetworkStatsContext.NetworkType
+ var networkDuration: Double
+}
private final class MultiplexedRequestManagerContext {
private let queue: Queue
@@ -109,15 +114,15 @@ private final class MultiplexedRequestManagerContext {
}
}
- func request(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, completed: @escaping (Any, Double) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
+ func request(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, (Buffer) -> Any?), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool, completed: @escaping (Any, NetworkResponseInfo) -> Void, error: @escaping (MTRpcError, Double) -> Void) -> Disposable {
let targetKey = MultiplexedRequestTargetKey(target: target, continueInBackground: continueInBackground)
let requestId = self.nextId
self.nextId += 1
self.queuedRequests.append(RequestData(id: requestId, consumerId: consumerId, target: target, functionDescription: data.0, payload: data.1, tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, deserializeResponse: { buffer in
return data.2(buffer)
- }, completed: { result, timestamp in
- completed(result, timestamp)
+ }, completed: { result, info in
+ completed(result, info)
}, error: { e, timestamp in
error(e, timestamp)
}))
@@ -189,7 +194,7 @@ private final class MultiplexedRequestManagerContext {
let requestId = request.id
selectedContext.requests.append(ExecutingRequestData(requestId: requestId, disposable: disposable))
let queue = self.queue
- disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait).start(next: { [weak self, weak selectedContext] result, timestamp in
+ disposable.set(selectedContext.worker.rawRequest((request.functionDescription, request.payload, request.deserializeResponse), automaticFloodWait: request.automaticFloodWait).start(next: { [weak self, weak selectedContext] result, info in
queue.async {
guard let strongSelf = self else {
return
@@ -202,7 +207,7 @@ private final class MultiplexedRequestManagerContext {
}
}
}
- request.completed(result, timestamp)
+ request.completed(result, info)
strongSelf.updateState()
}
}, error: { [weak self, weak selectedContext] error, timestamp in
@@ -299,18 +304,18 @@ final class MultiplexedRequestManager {
}
}
- func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<(T, Double), (MTRpcError, Double)> {
+ func requestWithAdditionalInfo(to target: MultiplexedRequestTarget, consumerId: Int64, data: (FunctionDescription, Buffer, DeserializeFunctionResponse), tag: MediaResourceFetchTag?, continueInBackground: Bool, automaticFloodWait: Bool = true) -> Signal<(T, NetworkResponseInfo), (MTRpcError, Double)> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.context.with { context in
disposable.set(context.request(to: target, consumerId: consumerId, data: (data.0, data.1, { buffer in
return data.2.parse(buffer)
- }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, completed: { result, timestamp in
+ }), tag: tag, continueInBackground: continueInBackground, automaticFloodWait: automaticFloodWait, completed: { result, info in
if let result = result as? T {
- subscriber.putNext((result, timestamp))
+ subscriber.putNext((result, info))
subscriber.putCompletion()
} else {
- subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), timestamp))
+ subscriber.putError((MTRpcError(errorCode: 500, errorDescription: "TL_VERIFICATION_ERROR"), info.timestamp))
}
}, error: { error, timestamp in
subscriber.putError((error, timestamp))
diff --git a/submodules/TelegramCore/Sources/Network/Network.swift b/submodules/TelegramCore/Sources/Network/Network.swift
index 23b71f1ef2..61ccf91ab4 100644
--- a/submodules/TelegramCore/Sources/Network/Network.swift
+++ b/submodules/TelegramCore/Sources/Network/Network.swift
@@ -434,8 +434,9 @@ public struct NetworkInitializationArguments {
public let encryptionProvider: EncryptionProvider
public let deviceModelName:String?
public let useBetaFeatures: Bool
+ public let isICloudEnabled: Bool
- public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool) {
+ public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, voipVersions: [CallSessionManagerImplementationVersion], appData: Signal, autolockDeadine: Signal, encryptionProvider: EncryptionProvider, deviceModelName: String?, useBetaFeatures: Bool, isICloudEnabled: Bool) {
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
@@ -447,6 +448,7 @@ public struct NetworkInitializationArguments {
self.encryptionProvider = encryptionProvider
self.deviceModelName = deviceModelName
self.useBetaFeatures = useBetaFeatures
+ self.isICloudEnabled = isICloudEnabled
}
}
#if os(iOS)
@@ -541,7 +543,7 @@ func initializedNetwork(accountId: AccountRecordId, arguments: NetworkInitializa
context.keychain = keychain
var wrappedAdditionalSource: MTSignal?
#if os(iOS)
- if #available(iOS 10.0, *), !supplementary {
+ if #available(iOS 10.0, *), !supplementary, arguments.isICloudEnabled {
var cloudDataContextValue: CloudDataContext?
if let value = cloudDataContext.with({ $0 }) {
cloudDataContextValue = value
diff --git a/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift b/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift
index 7cd4f87958..dd1def5974 100644
--- a/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift
+++ b/submodules/TelegramCore/Sources/Network/NetworkFrameworkTcpConnectionInterface.swift
@@ -222,9 +222,10 @@ final class NetworkFrameworkTcpConnectionInterface: NSObject, MTTcpConnectionInt
self.currentReadRequest = nil
weak var delegate = self.delegate
+ let currentInterfaceIsWifi = self.currentInterfaceIsWifi
self.delegateQueue.async {
if let delegate = delegate {
- delegate.connectionInterfaceDidRead(currentReadRequest.data, withTag: currentReadRequest.request.tag)
+ delegate.connectionInterfaceDidRead(currentReadRequest.data, withTag: currentReadRequest.request.tag, networkType: currentInterfaceIsWifi ? 0 : 1)
}
}
diff --git a/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift b/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift
new file mode 100644
index 0000000000..21b656fed5
--- /dev/null
+++ b/submodules/TelegramCore/Sources/Network/NetworkStatsContext.swift
@@ -0,0 +1,122 @@
+import Foundation
+import SwiftSignalKit
+import Postbox
+
+final class NetworkStatsContext {
+ enum NetworkType: Int32 {
+ case wifi = 0
+ case cellular = 1
+ }
+
+ struct DownloadEvent {
+ let networkType: NetworkType
+ let datacenterId: Int32
+ let size: Double
+ let networkDuration: Double
+ let issueDuration: Double
+
+ init(
+ networkType: NetworkType,
+ datacenterId: Int32,
+ size: Double,
+ networkDuration: Double,
+ issueDuration: Double
+ ) {
+ self.networkType = networkType
+ self.datacenterId = datacenterId
+ self.size = size
+ self.networkDuration = networkDuration
+ self.issueDuration = issueDuration
+ }
+ }
+
+ private struct TargetKey: Hashable {
+ let networkType: NetworkType
+ let datacenterId: Int32
+
+ init(networkType: NetworkType, datacenterId: Int32) {
+ self.networkType = networkType
+ self.datacenterId = datacenterId
+ }
+ }
+
+ private final class AverageStats {
+ var networkBps: Double = 0.0
+ var issueDuration: Double = 0.0
+ var networkDelay: Double = 0.0
+ var count: Int = 0
+ var size: Int64 = 0
+ }
+
+ private final class Impl {
+ let queue: Queue
+ let postbox: Postbox
+
+ var averageTargetStats: [TargetKey: AverageStats] = [:]
+
+ init(queue: Queue, postbox: Postbox) {
+ self.queue = queue
+ self.postbox = postbox
+ }
+
+ func add(downloadEvents: [DownloadEvent]) {
+ for event in downloadEvents {
+ if event.networkDuration == 0.0 {
+ continue
+ }
+ let targetKey = TargetKey(networkType: event.networkType, datacenterId: event.datacenterId)
+ let averageStats: AverageStats
+ if let current = self.averageTargetStats[targetKey] {
+ averageStats = current
+ } else {
+ averageStats = AverageStats()
+ self.averageTargetStats[targetKey] = averageStats
+ }
+ averageStats.count += 1
+ averageStats.issueDuration += event.issueDuration
+ averageStats.networkDelay += event.issueDuration - event.networkDuration
+ averageStats.networkBps += event.size / event.networkDuration
+ averageStats.size += Int64(event.size)
+ }
+
+ self.maybeFlushStats()
+ }
+
+ private func maybeFlushStats() {
+ var removeKeys: [TargetKey] = []
+ for (targetKey, averageStats) in self.averageTargetStats {
+ if averageStats.count >= 1000 || averageStats.size >= 4 * 1024 * 1024 {
+ addAppLogEvent(postbox: self.postbox, type: "download", data: .dictionary([
+ "n": .number(Double(targetKey.networkType.rawValue)),
+ "d": .number(Double(targetKey.datacenterId)),
+ "b": .number(averageStats.networkBps / Double(averageStats.count)),
+ "nd": .number(averageStats.networkDelay / Double(averageStats.count))
+ ]))
+ removeKeys.append(targetKey)
+ }
+ }
+ for key in removeKeys {
+ self.averageTargetStats.removeValue(forKey: key)
+ }
+ }
+ }
+
+ private static let sharedQueue = Queue(name: "NetworkStatsContext")
+
+ private let queue: Queue
+ private let impl: QueueLocalObject
+
+ init(postbox: Postbox) {
+ let queue = NetworkStatsContext.sharedQueue
+ self.queue = queue
+ self.impl = QueueLocalObject(queue: queue, generate: {
+ return Impl(queue: queue, postbox: postbox)
+ })
+ }
+
+ func add(downloadEvents: [DownloadEvent]) {
+ self.impl.with { impl in
+ impl.add(downloadEvents: downloadEvents)
+ }
+ }
+}
diff --git a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift
index e4da935a56..176b04e711 100644
--- a/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift
+++ b/submodules/TelegramCore/Sources/PendingMessages/EnqueueMessage.swift
@@ -693,9 +693,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
buttons.append(button)
} else if case .urlAuth = button.action {
buttons.append(button)
- } else if case let .switchInline(samePeer, query) = button.action, sourceSentViaBot {
+ } else if case let .switchInline(samePeer, query, peerTypes) = button.action, sourceSentViaBot {
let samePeer = samePeer && peerId == sourceMessage.id.peerId
- let updatedButton = ReplyMarkupButton(title: button.titleWhenForwarded ?? button.title, titleWhenForwarded: button.titleWhenForwarded, action: .switchInline(samePeer: samePeer, query: query))
+ let updatedButton = ReplyMarkupButton(title: button.titleWhenForwarded ?? button.title, titleWhenForwarded: button.titleWhenForwarded, action: .switchInline(samePeer: samePeer, query: query, peerTypes: peerTypes))
buttons.append(updatedButton)
} else {
rows.removeAll()
diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift
new file mode 100644
index 0000000000..2e6468064e
--- /dev/null
+++ b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift
@@ -0,0 +1,352 @@
+import Foundation
+import SwiftSignalKit
+import Postbox
+import TelegramApi
+
+public final class PeerMediaUploadingItem: Equatable {
+ public enum ProgressValue {
+ case progress(Float)
+ case done(Api.Updates)
+ }
+
+ public enum Error {
+ case generic
+ }
+
+ public enum Content: Equatable {
+ case wallpaper(TelegramWallpaper)
+ }
+
+ public let content: Content
+ public let messageId: EngineMessage.Id?
+ public let progress: Float
+
+ init(content: Content, messageId: EngineMessage.Id?, progress: Float) {
+ self.content = content
+ self.messageId = messageId
+ self.progress = progress
+ }
+
+ public static func ==(lhs: PeerMediaUploadingItem, rhs: PeerMediaUploadingItem) -> Bool {
+ if lhs.content != rhs.content {
+ return false
+ }
+ if lhs.messageId != rhs.messageId {
+ return false
+ }
+ if lhs.progress != rhs.progress {
+ return false
+ }
+ return true
+ }
+
+ func withMessageId(_ messageId: EngineMessage.Id) -> PeerMediaUploadingItem {
+ return PeerMediaUploadingItem(content: self.content, messageId: messageId, progress: self.progress)
+ }
+
+ func withProgress(_ progress: Float) -> PeerMediaUploadingItem {
+ return PeerMediaUploadingItem(content: self.content, messageId: self.messageId, progress: progress)
+ }
+}
+
+private func uploadPeerMedia(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) -> Signal {
+ switch content {
+ case let .wallpaper(wallpaper):
+ if case let .image(representations, settings) = wallpaper, let resource = representations.last?.resource as? LocalFileMediaResource {
+ return _internal_uploadWallpaper(postbox: postbox, network: network, resource: resource, settings: settings, forChat: true)
+ |> mapError { error -> PeerMediaUploadingItem.Error in
+ return .generic
+ }
+ |> mapToSignal { value -> Signal in
+ switch value {
+ case let .progress(progress):
+ return .single(.progress(progress))
+ case let .complete(result):
+ if case let .file(file) = result {
+ postbox.mediaBox.copyResourceData(from: resource.id, to: file.file.resource.id, synchronous: true)
+ for representation in file.file.previewRepresentations {
+ postbox.mediaBox.copyResourceData(from: resource.id, to: representation.resource.id, synchronous: true)
+ }
+ }
+ return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: result, applyUpdates: false)
+ |> castError(PeerMediaUploadingItem.Error.self)
+ |> map { updates -> PeerMediaUploadingItem.ProgressValue in
+ return .done(updates)
+ }
+ }
+
+ }
+ } else {
+ return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: wallpaper, applyUpdates: false)
+ |> castError(PeerMediaUploadingItem.Error.self)
+ |> map { updates -> PeerMediaUploadingItem.ProgressValue in
+ return .done(updates)
+ }
+ }
+ }
+}
+
+private func generatePeerMediaMessage(network: Network, accountPeerId: EnginePeer.Id, transaction: Transaction, peerId: PeerId, content: PeerMediaUploadingItem.Content) -> StoreMessage {
+ var randomId: Int64 = 0
+ arc4random_buf(&randomId, 8)
+
+ var timestamp = Int32(network.context.globalTime())
+ switch peerId.namespace {
+ case Namespaces.Peer.CloudChannel, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudUser:
+ if let topIndex = transaction.getTopPeerMessageIndex(peerId: peerId, namespace: Namespaces.Message.Cloud) {
+ timestamp = max(timestamp, topIndex.timestamp)
+ }
+ default:
+ break
+ }
+
+ var flags = StoreMessageFlags()
+ flags.insert(.Unsent)
+ flags.insert(.Sending)
+
+ var attributes: [MessageAttribute] = []
+ attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId, flags: [], acknowledged: false, correlationId: nil, bubbleUpEmojiOrStickersets: []))
+
+ var media: [Media] = []
+ switch content {
+ case let .wallpaper(wallpaper):
+ media.append(TelegramMediaAction(action: .setChatWallpaper(wallpaper: wallpaper)))
+ }
+
+ return StoreMessage(peerId: peerId, namespace: Namespaces.Message.Local, globallyUniqueId: randomId, groupingKey: nil, threadId: nil, timestamp: timestamp, flags: [], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: accountPeerId, text: "", attributes: attributes, media: media)
+}
+
+private final class PendingPeerMediaUploadContext {
+ var value: PeerMediaUploadingItem
+ let disposable = MetaDisposable()
+
+ init(value: PeerMediaUploadingItem) {
+ self.value = value
+ }
+}
+
+private final class PendingPeerMediaUploadManagerImpl {
+ let queue: Queue
+ let postbox: Postbox
+ let network: Network
+ let stateManager: AccountStateManager
+ let accountPeerId: EnginePeer.Id
+
+ private var uploadingPeerMediaValue: [EnginePeer.Id: PeerMediaUploadingItem] = [:] {
+ didSet {
+ if self.uploadingPeerMediaValue != oldValue {
+ self.uploadingPeerMediaPromise.set(.single(self.uploadingPeerMediaValue))
+ }
+ }
+ }
+ private let uploadingPeerMediaPromise = Promise<[EnginePeer.Id: PeerMediaUploadingItem]>()
+ fileprivate var uploadingPeerMedia: Signal<[EnginePeer.Id: PeerMediaUploadingItem], NoError> {
+ return self.uploadingPeerMediaPromise.get()
+ }
+
+ private var contexts: [PeerId: PendingPeerMediaUploadContext] = [:]
+
+ init(queue: Queue, postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: EnginePeer.Id) {
+ self.queue = queue
+ self.postbox = postbox
+ self.network = network
+ self.stateManager = stateManager
+ self.accountPeerId = accountPeerId
+
+ self.uploadingPeerMediaPromise.set(.single(self.uploadingPeerMediaValue))
+ }
+
+ deinit {
+ for (_, context) in self.contexts {
+ context.disposable.dispose()
+ }
+ }
+
+ private func updateValues() {
+ self.uploadingPeerMediaValue = self.contexts.mapValues { context in
+ return context.value
+ }
+ }
+
+ func add(peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) {
+ if let context = self.contexts[peerId] {
+ self.contexts.removeValue(forKey: peerId)
+ context.disposable.dispose()
+ }
+
+ let postbox = self.postbox
+ let network = self.network
+ let stateManager = self.stateManager
+ let accountPeerId = self.accountPeerId
+
+ let queue = self.queue
+ let context = PendingPeerMediaUploadContext(value: PeerMediaUploadingItem(content: content, messageId: nil, progress: 0.0))
+ self.contexts[peerId] = context
+
+ context.disposable.set(
+ (self.postbox.transaction({ transaction -> EngineMessage.Id? in
+ let storeMessage = generatePeerMediaMessage(network: network, accountPeerId: accountPeerId, transaction: transaction, peerId: peerId, content: content)
+ let globallyUniqueIdToMessageId = transaction.addMessages([storeMessage], location: .Random)
+ guard let globallyUniqueId = storeMessage.globallyUniqueId, let messageId = globallyUniqueIdToMessageId[globallyUniqueId] else {
+ return nil
+ }
+ return messageId
+ })
+ |> deliverOn(queue)).start(next: { [weak self, weak context] messageId in
+ guard let strongSelf = self, let initialContext = context else {
+ return
+ }
+ if let context = strongSelf.contexts[peerId], context === initialContext {
+ guard let messageId = messageId else {
+ strongSelf.contexts.removeValue(forKey: peerId)
+ context.disposable.dispose()
+ strongSelf.updateValues()
+ return
+ }
+ context.value = context.value.withMessageId(messageId)
+ strongSelf.updateValues()
+
+ context.disposable.set((uploadPeerMedia(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, content: content)
+ |> deliverOn(queue)).start(next: { [weak self, weak context] value in
+ guard let strongSelf = self, let initialContext = context else {
+ return
+ }
+ if let context = strongSelf.contexts[peerId], context === initialContext {
+ switch value {
+ case let .done(result):
+ context.disposable.set(
+ (postbox.transaction({ transaction -> Message? in
+ return transaction.getMessage(messageId)
+ })
+ |> deliverOn(queue)
+ ).start(next: { [weak self, weak context] message in
+ guard let strongSelf = self, let initialContext = context else {
+ return
+ }
+ if let context = strongSelf.contexts[peerId], context === initialContext {
+ guard let message = message else {
+ strongSelf.contexts.removeValue(forKey: peerId)
+ context.disposable.dispose()
+ strongSelf.updateValues()
+ return
+ }
+ context.disposable.set(
+ (applyUpdateMessage(
+ postbox: postbox,
+ stateManager: stateManager,
+ message: message,
+ cacheReferenceKey: nil,
+ result: result,
+ accountPeerId: accountPeerId
+ )
+ |> deliverOn(queue)).start(completed: { [weak self, weak context] in
+ guard let strongSelf = self, let initialContext = context else {
+ return
+ }
+ if let context = strongSelf.contexts[peerId], context === initialContext {
+ strongSelf.contexts.removeValue(forKey: peerId)
+ context.disposable.dispose()
+ strongSelf.updateValues()
+ }
+ })
+ )
+ }
+ })
+ )
+ strongSelf.updateValues()
+ case let .progress(progress):
+ context.value = context.value.withProgress(progress)
+ strongSelf.updateValues()
+ }
+ }
+ }, error: { [weak self, weak context] error in
+ guard let strongSelf = self, let initialContext = context else {
+ return
+ }
+ if let context = strongSelf.contexts[peerId], context === initialContext {
+ strongSelf.contexts.removeValue(forKey: peerId)
+ context.disposable.dispose()
+ strongSelf.updateValues()
+ }
+ }))
+ }
+ })
+ )
+ }
+
+ func cancel(peerId: EnginePeer.Id) {
+ if let context = self.contexts[peerId] {
+ self.contexts.removeValue(forKey: peerId)
+
+ if let messageId = context.value.messageId {
+ context.disposable.set(self.postbox.transaction({ transaction in
+ transaction.deleteMessages([messageId], forEachMedia: nil)
+ }).start())
+ } else {
+ context.disposable.dispose()
+ }
+
+ self.updateValues()
+ }
+ }
+
+ func uploadProgress(messageId: EngineMessage.Id) -> Signal {
+ return self.uploadingPeerMedia
+ |> map { uploadingPeerMedia in
+ if let item = uploadingPeerMedia[messageId.peerId], item.messageId == messageId {
+ return item.progress
+ } else {
+ return nil
+ }
+ }
+ |> distinctUntilChanged
+ }
+}
+
+public final class PendingPeerMediaUploadManager {
+ private let queue = Queue()
+ private let impl: QueueLocalObject
+
+ public var uploadingPeerMedia: Signal<[EnginePeer.Id: PeerMediaUploadingItem], NoError> {
+ return Signal { subscriber in
+ let disposable = MetaDisposable()
+ self.impl.with { impl in
+ disposable.set(impl.uploadingPeerMedia.start(next: { value in
+ subscriber.putNext(value)
+ }))
+ }
+ return disposable
+ }
+ }
+
+ init(postbox: Postbox, network: Network, stateManager: AccountStateManager, accountPeerId: EnginePeer.Id) {
+ let queue = self.queue
+ self.impl = QueueLocalObject(queue: queue, generate: {
+ return PendingPeerMediaUploadManagerImpl(queue: queue, postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId)
+ })
+ }
+
+ public func add(peerId: EnginePeer.Id, content: PeerMediaUploadingItem.Content) {
+ self.impl.with { impl in
+ impl.add(peerId: peerId, content: content)
+ }
+ }
+
+ public func cancel(peerId: EnginePeer.Id) {
+ self.impl.with { impl in
+ impl.cancel(peerId: peerId)
+ }
+ }
+
+ public func uploadProgress(messageId: EngineMessage.Id) -> Signal {
+ return Signal { subscriber in
+ let disposable = MetaDisposable()
+ self.impl.with { impl in
+ disposable.set(impl.uploadProgress(messageId: messageId).start(next: { value in
+ subscriber.putNext(value)
+ }))
+ }
+ return disposable
+ }
+ }
+}
diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift
index 511563eefc..8c8bd96131 100644
--- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift
+++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift
@@ -3405,6 +3405,22 @@ func replayFinalState(
for (space, _) in holesAtHistoryStart {
transaction.removeHole(peerId: chatPeerId, threadId: nil, namespace: Namespaces.Message.Cloud, space: space, range: 1 ... id.id)
}
+ case let .setChatWallpaper(wallpaper), let .setSameChatWallpaper(wallpaper):
+ if message.authorId == accountPeerId {
+ transaction.updatePeerCachedData(peerIds: [message.id.peerId], update: { peerId, current in
+ var current = current
+ if current == nil {
+ if peerId.namespace == Namespaces.Peer.CloudUser {
+ current = CachedUserData()
+ }
+ }
+ if let cachedData = current as? CachedUserData {
+ return cachedData.withUpdatedWallpaper(wallpaper)
+ } else {
+ return current
+ }
+ })
+ }
default:
break
}
diff --git a/submodules/TelegramCore/Sources/State/Fetch.swift b/submodules/TelegramCore/Sources/State/Fetch.swift
index 5e65c43197..831354282a 100644
--- a/submodules/TelegramCore/Sources/State/Fetch.swift
+++ b/submodules/TelegramCore/Sources/State/Fetch.swift
@@ -23,7 +23,7 @@ private final class MediaResourceDataCopyFile : MediaResourceDataFetchCopyLocalI
}
public func fetchCloudMediaLocation(account: Account, resource: TelegramMediaResource, datacenterId: Int, size: Int64?, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, parameters: MediaResourceFetchParameters?) -> Signal {
- return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, resource: resource, datacenterId: datacenterId, size: size, intervals: intervals, parameters: parameters)
+ return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, networkStatsContext: account.networkStatsContext, resource: resource, datacenterId: datacenterId, size: size, intervals: intervals, parameters: parameters)
}
private func fetchLocalFileResource(path: String, move: Bool) -> Signal {
diff --git a/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift b/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift
index c352f45184..13491cbbf3 100644
--- a/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift
+++ b/submodules/TelegramCore/Sources/State/FetchSecretFileResource.swift
@@ -5,5 +5,5 @@ import MtProtoKit
func fetchSecretFileResource(account: Account, resource: SecretFileMediaResource, intervals: Signal<[(Range, MediaBoxFetchPriority)], NoError>, parameters: MediaResourceFetchParameters?) -> Signal {
- return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, resource: resource, datacenterId: resource.datacenterId, size: resource.size, intervals: intervals, parameters: parameters, encryptionKey: resource.key, decryptedSize: resource.decryptedSize)
+ return multipartFetch(postbox: account.postbox, network: account.network, mediaReferenceRevalidationContext: account.mediaReferenceRevalidationContext, networkStatsContext: account.networkStatsContext, resource: resource, datacenterId: resource.datacenterId, size: resource.size, intervals: intervals, parameters: parameters, encryptionKey: resource.key, decryptedSize: resource.decryptedSize)
}
diff --git a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift
index 2f761b47a7..267fa01d44 100644
--- a/submodules/TelegramCore/Sources/State/PeerInputActivity.swift
+++ b/submodules/TelegramCore/Sources/State/PeerInputActivity.swift
@@ -52,7 +52,7 @@ public struct EmojiInteraction: Equatable {
fileprivate let roundingBehavior = NSDecimalNumberHandler(roundingMode: .plain, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: true)
public var apiDataJson: Api.DataJSON {
- let dict = ["v": 1, "a": self.animations.map({ ["i": $0.index, "t": NSDecimalNumber(value: $0.timeOffset).rounding(accordingToBehavior: roundingBehavior)] })] as [String : Any]
+ let dict = ["v": 1, "a": self.animations.map({ ["i": $0.index, "t": NSDecimalNumber(value: $0.timeOffset).rounding(accordingToBehavior: roundingBehavior)] as [String : Any] })] as [String : Any]
if let data = try? JSONSerialization.data(withJSONObject: dict, options: []), let dataString = String(data: data, encoding: .utf8) {
return .dataJSON(data: dataString)
} else {
diff --git a/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift b/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift
index 7feccde0c8..d2b8582bdb 100644
--- a/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift
+++ b/submodules/TelegramCore/Sources/State/UnauthorizedAccountStateManager.swift
@@ -26,7 +26,7 @@ private final class UnauthorizedUpdateMessageService: NSObject, MTMessageService
self.pipe.putNext(updates)
}
- func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) {
+ func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector, networkType: Int32) {
if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates {
self.addUpdates(updates)
}
diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift
index 7778a947fc..ea5a7ddb52 100644
--- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift
+++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift
@@ -34,7 +34,7 @@ class UpdateMessageService: NSObject, MTMessageService {
self.pipe.putNext(groups)
}
- func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector) {
+ func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!, authInfoSelector: MTDatacenterAuthInfoSelector, networkType: Int32) {
if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates {
self.addUpdates(updates)
}
diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift
index 09f9611cd8..e07e11baed 100644
--- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift
+++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift
@@ -181,12 +181,50 @@ public enum ReplyMarkupButtonRequestPeerType: Codable, Equatable {
}
public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
+ public struct PeerTypes: OptionSet {
+ public var rawValue: Int32
+
+ public init(rawValue: Int32) {
+ self.rawValue = rawValue
+ }
+
+ public init() {
+ self.rawValue = 0
+ }
+
+ public static let users = PeerTypes(rawValue: 1 << 0)
+ public static let bots = PeerTypes(rawValue: 1 << 1)
+ public static let channels = PeerTypes(rawValue: 1 << 2)
+ public static let groups = PeerTypes(rawValue: 1 << 3)
+
+ public var requestPeerTypes: [ReplyMarkupButtonRequestPeerType]? {
+ if self.isEmpty {
+ return nil
+ }
+
+ var types: [ReplyMarkupButtonRequestPeerType] = []
+ if self.contains(.users) {
+ types.append(.user(.init(isBot: false, isPremium: nil)))
+ }
+ if self.contains(.bots) {
+ types.append(.user(.init(isBot: true, isPremium: nil)))
+ }
+ if self.contains(.channels) {
+ types.append(.channel(.init(isCreator: false, hasUsername: nil, userAdminRights: nil, botAdminRights: nil)))
+ }
+ if self.contains(.groups) {
+ types.append(.group(.init(isCreator: false, hasUsername: nil, isForum: nil, botParticipant: false, userAdminRights: nil, botAdminRights: nil)))
+ }
+ return types
+ }
+ }
+
case text
case url(String)
case callback(requiresPassword: Bool, data: MemoryBuffer)
case requestPhone
case requestMap
- case switchInline(samePeer: Bool, query: String)
+ case switchInline(samePeer: Bool, query: String, peerTypes: PeerTypes)
case openWebApp
case payment
case urlAuth(url: String, buttonId: Int32)
@@ -208,7 +246,7 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
case 4:
self = .requestMap
case 5:
- self = .switchInline(samePeer: decoder.decodeInt32ForKey("s", orElse: 0) != 0, query: decoder.decodeStringForKey("q", orElse: ""))
+ self = .switchInline(samePeer: decoder.decodeInt32ForKey("s", orElse: 0) != 0, query: decoder.decodeStringForKey("q", orElse: ""), peerTypes: PeerTypes(rawValue: decoder.decodeInt32ForKey("pt", orElse: 0)))
case 6:
self = .openWebApp
case 7:
@@ -243,10 +281,11 @@ public enum ReplyMarkupButtonAction: PostboxCoding, Equatable {
encoder.encodeInt32(3, forKey: "v")
case .requestMap:
encoder.encodeInt32(4, forKey: "v")
- case let .switchInline(samePeer, query):
+ case let .switchInline(samePeer, query, peerTypes):
encoder.encodeInt32(5, forKey: "v")
encoder.encodeInt32(samePeer ? 1 : 0, forKey: "s")
encoder.encodeString(query, forKey: "q")
+ encoder.encodeInt32(peerTypes.rawValue, forKey: "pt")
case .openWebApp:
encoder.encodeInt32(6, forKey: "v")
case .payment:
diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift
index 22442aba6a..d8c8d6d550 100644
--- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift
+++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift
@@ -102,7 +102,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case attachMenuBotAllowed
case requestedPeer(buttonId: Int32, peerId: PeerId)
case setChatWallpaper(wallpaper: TelegramWallpaper)
- case setSameChatWallpaper
+ case setSameChatWallpaper(wallpaper: TelegramWallpaper)
public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@@ -190,7 +190,11 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .unknown
}
case 34:
- self = .setSameChatWallpaper
+ if let wallpaper = decoder.decode(TelegramWallpaperNativeCodable.self, forKey: "wallpaper")?.value {
+ self = .setSameChatWallpaper(wallpaper: wallpaper)
+ } else {
+ self = .unknown
+ }
default:
self = .unknown
}
@@ -355,8 +359,9 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case let .setChatWallpaper(wallpaper):
encoder.encodeInt32(33, forKey: "_rawValue")
encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper")
- case .setSameChatWallpaper:
+ case let .setSameChatWallpaper(wallpaper):
encoder.encodeInt32(34, forKey: "_rawValue")
+ encoder.encode(TelegramWallpaperNativeCodable(wallpaper), forKey: "wallpaper")
}
}
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift
index c51b687b89..ba0442c7c5 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift
@@ -657,6 +657,7 @@ public extension TelegramEngine {
}
public func getNextUnreadChannel(peerId: PeerId, chatListFilterId: Int32?, getFilterPredicate: @escaping (ChatListFilterData) -> ChatListFilterPredicate) -> Signal<(peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)?, NoError> {
+ let startTime = CFAbsoluteTimeGetCurrent()
return self.account.postbox.transaction { transaction -> (peer: EnginePeer, unreadCount: Int, location: NextUnreadChannelLocation)? in
func getForFilter(predicate: ChatListFilterPredicate?, isArchived: Bool) -> (peer: EnginePeer, unreadCount: Int)? {
let additionalFilter: (Peer) -> Bool = { peer in
@@ -756,6 +757,12 @@ public extension TelegramEngine {
return nil
}
}
+ |> beforeNext { _ in
+ let delayTime = CFAbsoluteTimeGetCurrent() - startTime
+ if delayTime > 0.3 {
+ Logger.shared.log("getNextUnreadChannel", "took \(delayTime) s")
+ }
+ }
}
public func getOpaqueChatInterfaceState(peerId: PeerId, threadId: Int64?) -> Signal {
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift
index e71bff8e58..7d286c3289 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift
@@ -339,7 +339,7 @@ public extension TelegramEngine {
|> mapToSignal { datacenterId -> Signal in
let resource = AlbumCoverResource(datacenterId: Int(datacenterId), file: file, title: title, performer: performer, isThumbnail: isThumbnail)
- return multipartFetch(postbox: self.account.postbox, network: self.account.network, mediaReferenceRevalidationContext: self.account.mediaReferenceRevalidationContext, resource: resource, datacenterId: Int(datacenterId), size: nil, intervals: .single([(0 ..< Int64.max, .default)]), parameters: MediaResourceFetchParameters(
+ return multipartFetch(postbox: self.account.postbox, network: self.account.network, mediaReferenceRevalidationContext: self.account.mediaReferenceRevalidationContext, networkStatsContext: self.account.networkStatsContext, resource: resource, datacenterId: Int(datacenterId), size: nil, intervals: .single([(0 ..< Int64.max, .default)]), parameters: MediaResourceFetchParameters(
tag: nil,
info: TelegramCloudMediaResourceFetchInfo(
reference: MediaResourceReference.standalone(resource: resource),
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift
index 4d7c7609cf..8e97221e71 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift
@@ -118,14 +118,13 @@ func managedChatThemesUpdates(accountManager: AccountManager then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart
}
-func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal {
- return account.postbox.loadedPeerWithId(peerId)
+func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, wallpaper: TelegramWallpaper?, applyUpdates: Bool = true) -> Signal {
+ return postbox.loadedPeerWithId(peerId)
|> mapToSignal { peer in
guard let inputPeer = apiInputPeer(peer) else {
return .complete()
}
-
- return account.postbox.transaction { transaction -> Signal in
+ return postbox.transaction { transaction -> Signal in
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedWallpaper(wallpaper)
@@ -134,21 +133,24 @@ func _internal_setChatWallpaper(account: Account, peerId: PeerId, wallpaper: Tel
}
})
+ var flags: Int32 = 0
var inputWallpaper: Api.InputWallPaper?
var inputSettings: Api.WallPaperSettings?
if let inputWallpaperAndInputSettings = wallpaper?.apiInputWallpaperAndSettings {
+ flags |= 1 << 0
+ flags |= 1 << 2
inputWallpaper = inputWallpaperAndInputSettings.0
inputSettings = inputWallpaperAndInputSettings.1
}
-
- let flags: Int32 = 1 << 0
- return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper ?? .inputWallPaperNoFile(id: 0), settings: inputSettings ?? apiWallpaperSettings(WallpaperSettings()), id: nil))
+ return network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil), automaticFloodWait: false)
|> `catch` { error in
return .complete()
}
- |> mapToSignal { updates -> Signal in
- account.stateManager.addUpdates(updates)
- return .complete()
+ |> mapToSignal { updates -> Signal in
+ if applyUpdates {
+ stateManager.addUpdates(updates)
+ }
+ return .single(updates)
}
} |> switchToLatest
}
@@ -158,10 +160,14 @@ public enum SetExistingChatWallpaperError {
case generic
}
-func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId) -> Signal {
+func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId, settings: WallpaperSettings?) -> Signal {
return account.postbox.transaction { transaction -> Peer? in
if let peer = transaction.getPeer(messageId.peerId), let message = transaction.getMessage(messageId) {
if let action = message.media.first(where: { $0 is TelegramMediaAction }) as? TelegramMediaAction, case let .setChatWallpaper(wallpaper) = action.action {
+ var wallpaper = wallpaper
+ if let settings = settings {
+ wallpaper = wallpaper.withUpdatedSettings(settings)
+ }
transaction.updatePeerCachedData(peerIds: Set([peer.id]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedWallpaper(wallpaper)
@@ -180,8 +186,14 @@ func _internal_setExistingChatWallpaper(account: Account, messageId: MessageId)
guard let peer = peer, let inputPeer = apiInputPeer(peer) else {
return .complete()
}
- let flags: Int32 = 1 << 1
- return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: nil, settings: nil, id: messageId.id))
+ var flags: Int32 = 1 << 1
+
+ var inputSettings: Api.WallPaperSettings?
+ if let settings = settings {
+ flags |= 1 << 2
+ inputSettings = apiWallpaperSettings(settings)
+ }
+ return account.network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: nil, settings: inputSettings, id: messageId.id), automaticFloodWait: false)
|> `catch` { _ -> Signal in
return .fail(.generic)
}
diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift
index 7cfd04ef32..623efabd58 100644
--- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift
+++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift
@@ -17,12 +17,13 @@ public extension TelegramEngine {
return _internal_setChatTheme(account: self.account, peerId: peerId, emoticon: emoticon)
}
- public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal {
- return _internal_setChatWallpaper(account: self.account, peerId: peerId, wallpaper: wallpaper)
+ public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal {
+ return _internal_setChatWallpaper(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, wallpaper: wallpaper)
+ |> ignoreValues
}
- public func setExistingChatWallpaper(messageId: MessageId) -> Signal {
- return _internal_setExistingChatWallpaper(account: self.account, messageId: messageId)
+ public func setExistingChatWallpaper(messageId: MessageId, settings: WallpaperSettings?) -> Signal {
+ return _internal_setExistingChatWallpaper(account: self.account, messageId: messageId, settings: settings)
}
}
}
diff --git a/submodules/TelegramCore/Sources/Wallpapers.swift b/submodules/TelegramCore/Sources/Wallpapers.swift
index c962996430..68eff19da9 100644
--- a/submodules/TelegramCore/Sources/Wallpapers.swift
+++ b/submodules/TelegramCore/Sources/Wallpapers.swift
@@ -119,7 +119,11 @@ private func uploadedWallpaper(postbox: Postbox, network: Network, resource: Med
}
public func uploadWallpaper(account: Account, resource: MediaResource, mimeType: String = "image/jpeg", settings: WallpaperSettings, forChat: Bool) -> Signal {
- return uploadedWallpaper(postbox: account.postbox, network: account.network, resource: resource)
+ return _internal_uploadWallpaper(postbox: account.postbox, network: account.network, resource: resource, settings: settings, forChat: forChat)
+}
+
+func _internal_uploadWallpaper(postbox: Postbox, network: Network, resource: MediaResource, mimeType: String = "image/jpeg", settings: WallpaperSettings, forChat: Bool) -> Signal {
+ return uploadedWallpaper(postbox: postbox, network: network, resource: resource)
|> mapError { _ -> UploadWallpaperError in }
|> mapToSignal { result -> Signal<(UploadWallpaperStatus, MediaResource?), UploadWallpaperError> in
switch result.content {
@@ -134,11 +138,11 @@ public func uploadWallpaper(account: Account, resource: MediaResource, mimeType:
if forChat {
flags |= 1 << 0
}
- return account.network.request(Api.functions.account.uploadWallPaper(flags: flags, file: file, mimeType: mimeType, settings: apiWallpaperSettings(settings)))
- |> mapError { _ in return UploadWallpaperError.generic }
- |> map { wallpaper -> (UploadWallpaperStatus, MediaResource?) in
- return (.complete(TelegramWallpaper(apiWallpaper: wallpaper)), result.resource)
- }
+ return network.request(Api.functions.account.uploadWallPaper(flags: flags, file: file, mimeType: mimeType, settings: apiWallpaperSettings(settings)))
+ |> mapError { _ in return UploadWallpaperError.generic }
+ |> map { wallpaper -> (UploadWallpaperStatus, MediaResource?) in
+ return (.complete(TelegramWallpaper(apiWallpaper: wallpaper)), result.resource)
+ }
default:
return .fail(.generic)
}
diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift
index b5e2bb1294..aef9b83c44 100644
--- a/submodules/TelegramNotices/Sources/Notices.swift
+++ b/submodules/TelegramNotices/Sources/Notices.swift
@@ -170,6 +170,9 @@ private enum ApplicationSpecificGlobalNotice: Int32 {
case audioRateOptionsTip = 36
case translationSuggestion = 37
case sendWhenOnlineTip = 38
+ case chatWallpaperLightPreviewTip = 39
+ case chatWallpaperDarkPreviewTip = 40
+ case displayChatListContacts = 41
var key: ValueBoxKey {
let v = ValueBoxKey(length: 4)
@@ -332,6 +335,14 @@ private struct ApplicationSpecificNoticeKeys {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatSpecificThemeDarkPreviewTip.key)
}
+ static func chatWallpaperLightPreviewTip() -> NoticeEntryKey {
+ return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperLightPreviewTip.key)
+ }
+
+ static func chatWallpaperDarkPreviewTip() -> NoticeEntryKey {
+ return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatWallpaperDarkPreviewTip.key)
+ }
+
static func chatForwardOptionsTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.chatForwardOptionsTip.key)
}
@@ -379,6 +390,10 @@ private struct ApplicationSpecificNoticeKeys {
static func sendWhenOnlineTip() -> NoticeEntryKey {
return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sendWhenOnlineTip.key)
}
+
+ static func displayChatListContacts() -> NoticeEntryKey {
+ return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.displayChatListContacts.key)
+ }
}
public struct ApplicationSpecificNotice {
@@ -1109,6 +1124,60 @@ public struct ApplicationSpecificNotice {
}
}
+ public static func getChatWallpaperLightPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> {
+ return accountManager.transaction { transaction -> (Int32, Int32) in
+ if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
+ return (value.counter, value.timestamp)
+ } else {
+ return (0, 0)
+ }
+ }
+ }
+
+ public static func incrementChatWallpaperLightPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal {
+ return accountManager.transaction { transaction -> Int in
+ var currentValue: Int32 = 0
+ if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
+ currentValue = value.counter
+ }
+ let previousValue = currentValue
+ currentValue += Int32(count)
+
+ if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) {
+ transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperLightPreviewTip(), entry)
+ }
+
+ return Int(previousValue)
+ }
+ }
+
+ public static func getChatWallpaperDarkPreviewTip(accountManager: AccountManager) -> Signal<(Int32, Int32), NoError> {
+ return accountManager.transaction { transaction -> (Int32, Int32) in
+ if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
+ return (value.counter, value.timestamp)
+ } else {
+ return (0, 0)
+ }
+ }
+ }
+
+ public static func incrementChatWallpaperDarkPreviewTip(accountManager: AccountManager, count: Int = 1, timestamp: Int32) -> Signal {
+ return accountManager.transaction { transaction -> Int in
+ var currentValue: Int32 = 0
+ if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip())?.get(ApplicationSpecificTimestampAndCounterNotice.self) {
+ currentValue = value.counter
+ }
+ let previousValue = currentValue
+ currentValue += Int32(count)
+
+ if let entry = CodableEntry(ApplicationSpecificTimestampAndCounterNotice(counter: currentValue, timestamp: timestamp)) {
+ transaction.setNotice(ApplicationSpecificNoticeKeys.chatWallpaperDarkPreviewTip(), entry)
+ }
+
+ return Int(previousValue)
+ }
+ }
+
public static func getChatForwardOptionsTip(accountManager: AccountManager) -> Signal {
return accountManager.transaction { transaction -> Int32 in
if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.chatForwardOptionsTip())?.get(ApplicationSpecificCounterNotice.self) {
@@ -1356,6 +1425,26 @@ public struct ApplicationSpecificNotice {
}
}
+ public static func displayChatListContacts(accountManager: AccountManager) -> Signal {
+ return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.displayChatListContacts())
+ |> map { view -> Bool in
+ if let _ = view.value?.get(ApplicationSpecificBoolNotice.self) {
+ return true
+ } else {
+ return false
+ }
+ }
+ }
+
+ public static func setDisplayChatListContacts(accountManager: AccountManager) -> Signal {
+ return accountManager.transaction { transaction -> Void in
+ if let entry = CodableEntry(ApplicationSpecificBoolNotice()) {
+ transaction.setNotice(ApplicationSpecificNoticeKeys.displayChatListContacts(), entry)
+ }
+ }
+ |> ignoreValues
+ }
+
public static func reset(accountManager: AccountManager) -> Signal {
return accountManager.transaction { transaction -> Void in
}
diff --git a/submodules/TelegramPresentationData/BUILD b/submodules/TelegramPresentationData/BUILD
index d15b450263..75689afad7 100644
--- a/submodules/TelegramPresentationData/BUILD
+++ b/submodules/TelegramPresentationData/BUILD
@@ -20,6 +20,7 @@ swift_library(
"//submodules/StringPluralization:StringPluralization",
"//submodules/Sunrise:Sunrise",
"//submodules/TinyThumbnail:TinyThumbnail",
+ "//submodules/FastBlur:FastBlur",
"//Telegram:PresentationStrings",
],
visibility = [
diff --git a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift
index a410693ee4..884cae8f3a 100644
--- a/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift
+++ b/submodules/TelegramPresentationData/Sources/ChatControllerBackgroundNode.swift
@@ -8,6 +8,7 @@ import Postbox
import MediaResources
import AppBundle
import TinyThumbnail
+import FastBlur
private var backgroundImageForWallpaper: (TelegramWallpaper, Bool, UIImage)?
@@ -200,8 +201,25 @@ public func chatControllerBackgroundImageSignal(wallpaper: TelegramWallpaper, me
} else if !didOutputBlurred {
didOutputBlurred = true
if let immediateThumbnailData = file.file.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: immediateThumbnailData) {
- if let image = UIImage(data: decodedData)?.precomposed() {
- subscriber.putNext((image, false))
+ if let thumbnailImage = UIImage(data: decodedData)?.precomposed() {
+ let thumbnailContextSize = CGSize(width: thumbnailImage.size.width * 6.0, height: thumbnailImage.size.height * 6.0)
+ guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else {
+ subscriber.putNext((thumbnailImage, false))
+ return
+ }
+ thumbnailContext.withFlippedContext { c in
+ if let image = thumbnailImage.cgImage {
+ c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
+ }
+ }
+ telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
+ telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
+
+ if let blurredThumbnailImage = thumbnailContext.generateImage() {
+ subscriber.putNext((blurredThumbnailImage, false))
+ } else {
+ subscriber.putNext((thumbnailImage, false))
+ }
}
}
}
@@ -238,8 +256,25 @@ public func chatControllerBackgroundImageSignal(wallpaper: TelegramWallpaper, me
} else if !didOutputBlurred {
didOutputBlurred = true
if let immediateThumbnailData = file.file.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: immediateThumbnailData) {
- if let image = UIImage(data: decodedData)?.precomposed() {
- subscriber.putNext((image, false))
+ if let thumbnailImage = UIImage(data: decodedData)?.precomposed() {
+ let thumbnailContextSize = CGSize(width: thumbnailImage.size.width * 6.0, height: thumbnailImage.size.height * 6.0)
+ guard let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0) else {
+ subscriber.putNext((thumbnailImage, false))
+ return
+ }
+ thumbnailContext.withFlippedContext { c in
+ if let image = thumbnailImage.cgImage {
+ c.draw(image, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
+ }
+ }
+ telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
+ telegramFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
+
+ if let blurredThumbnailImage = thumbnailContext.generateImage() {
+ subscriber.putNext((blurredThumbnailImage, false))
+ } else {
+ subscriber.putNext((thumbnailImage, false))
+ }
}
}
}
diff --git a/submodules/TelegramPresentationData/Sources/PresentationData.swift b/submodules/TelegramPresentationData/Sources/PresentationData.swift
index 3458593c55..002043cccf 100644
--- a/submodules/TelegramPresentationData/Sources/PresentationData.swift
+++ b/submodules/TelegramPresentationData/Sources/PresentationData.swift
@@ -218,16 +218,18 @@ public final class InitialPresentationDataAndSettings {
public let callListSettings: CallListSettings
public let inAppNotificationSettings: InAppNotificationSettings
public let mediaInputSettings: MediaInputSettings
+ public let mediaDisplaySettings: MediaDisplaySettings
public let stickerSettings: StickerSettings
public let experimentalUISettings: ExperimentalUISettings
- public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) {
+ public init(presentationData: PresentationData, automaticMediaDownloadSettings: MediaAutoDownloadSettings, autodownloadSettings: AutodownloadSettings, callListSettings: CallListSettings, inAppNotificationSettings: InAppNotificationSettings, mediaInputSettings: MediaInputSettings, mediaDisplaySettings: MediaDisplaySettings, stickerSettings: StickerSettings, experimentalUISettings: ExperimentalUISettings) {
self.presentationData = presentationData
self.automaticMediaDownloadSettings = automaticMediaDownloadSettings
self.autodownloadSettings = autodownloadSettings
self.callListSettings = callListSettings
self.inAppNotificationSettings = inAppNotificationSettings
self.mediaInputSettings = mediaInputSettings
+ self.mediaDisplaySettings = mediaDisplaySettings
self.stickerSettings = stickerSettings
self.experimentalUISettings = experimentalUISettings
}
@@ -242,6 +244,7 @@ public func currentPresentationDataAndSettings(accountManager: AccountManager Bool
public let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void
public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void
- public let activateSwitchInline: (PeerId?, String) -> Void
+ public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void
public let openUrl: (String, Bool, Bool?, Message?) -> Void
public let shareCurrentLocation: () -> Void
public let shareAccountContact: () -> Void
@@ -217,7 +217,7 @@ public final class ChatControllerInteraction {
sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool,
requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void,
requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void,
- activateSwitchInline: @escaping (PeerId?, String) -> Void,
+ activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void,
openUrl: @escaping (String, Bool, Bool?, Message?) -> Void,
shareCurrentLocation: @escaping () -> Void,
shareAccountContact: @escaping () -> Void,
diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift
index c3f97c1ef4..b58d9105de 100644
--- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift
+++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkHeaderComponent.swift
@@ -151,9 +151,8 @@ final class ChatFolderLinkHeaderComponent: Component {
let badgeSpacing: CGFloat = 6.0
if themeUpdated {
- //TODO:localize
- let leftString = NSAttributedString(string: "All Chats", font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor)
- let rightString = NSAttributedString(string: "Personal", font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor)
+ let leftString = NSAttributedString(string: component.strings.FolderLinkPreview_IconTabLeft, font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor)
+ let rightString = NSAttributedString(string: component.strings.FolderLinkPreview_IconTabRight, font: Font.semibold(14.0), textColor: component.theme.list.freeTextColor)
let leftStringBounds = leftString.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
let rightStringBounds = rightString.boundingRect(with: CGSize(width: 200.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift
index b38ce65ad0..b91f2cbd83 100644
--- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift
+++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/ChatFolderLinkPreviewScreen.swift
@@ -386,27 +386,25 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let titleString: String
var allChatsAdded = false
+ var canAddChatCount = 0
if case .linkList = component.subject {
- //TODO:localize
- titleString = "Share Folder"
+ titleString = environment.strings.FolderLinkPreview_TitleShare
} else if let linkContents = component.linkContents {
- //TODO:localize
if case .remove = component.subject {
- titleString = "Remove Folder"
+ titleString = environment.strings.FolderLinkPreview_TitleRemove
} else if linkContents.localFilterId != nil {
if linkContents.alreadyMemberPeerIds == Set(linkContents.peers.map(\.id)) {
allChatsAdded = true
}
+ canAddChatCount = linkContents.peers.map(\.id).count - linkContents.alreadyMemberPeerIds.count
if allChatsAdded {
- titleString = "Add Folder"
- } else if linkContents.peers.count == 1 {
- titleString = "Add \(linkContents.peers.count) chat"
+ titleString = environment.strings.FolderLinkPreview_TitleAddFolder
} else {
- titleString = "Add \(linkContents.peers.count) chats"
+ titleString = environment.strings.FolderLinkPreview_TitleAddChats(Int32(canAddChatCount))
}
} else {
- titleString = "Add Folder"
+ titleString = environment.strings.FolderLinkPreview_TitleAddFolder
}
} else {
titleString = " "
@@ -433,8 +431,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
var topBadge: String?
if case .linkList = component.subject {
} else if case .remove = component.subject {
- } else if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil {
- topBadge = "+\(linkContents.peers.count)"
+ } else if !allChatsAdded, let linkContents = component.linkContents, linkContents.localFilterId != nil, canAddChatCount != 0 {
+ topBadge = "+\(canAddChatCount)"
}
let topIconSize = self.topIcon.update(
@@ -462,26 +460,17 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let text: String
if case .linkList = component.subject {
- text = "Create more links to set up different access\nlevels for different people."
+ text = environment.strings.FolderLinkPreview_TextLinkList
} else if let linkContents = component.linkContents {
if case .remove = component.subject {
- text = "Do you also want to quit the chats included in this folder?"
+ text = environment.strings.FolderLinkPreview_TextRemoveFolder
} else if allChatsAdded {
- text = "You have already added this\nfolder and its chats."
+ text = environment.strings.FolderLinkPreview_TextAllAdded
} else if linkContents.localFilterId == nil {
- text = "Do you want to add a new chat folder\nand join its groups and channels?"
+ text = environment.strings.FolderLinkPreview_TextAddFolder
} else {
- let chatCountString: String
- if linkContents.peers.count == 1 {
- chatCountString = "1 chat"
- } else {
- chatCountString = "\(linkContents.peers.count) chats"
- }
- if let title = linkContents.title {
- text = "Do you want to add **\(chatCountString)** to the\nfolder **\(title)**?"
- } else {
- text = "Do you want to add **\(chatCountString)** chats to the\nfolder?"
- }
+ let chatCountString: String = environment.strings.FolderLinkPreview_TextAddChatsCount(Int32(canAddChatCount))
+ text = environment.strings.FolderLinkPreview_TextAddChats(chatCountString, linkContents.title ?? "").string
}
} else {
text = " "
@@ -542,7 +531,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
theme: environment.theme,
sideInset: 0.0,
iconName: "Contact List/LinkActionIcon",
- title: "Create a New Link",
+ title: environment.strings.InviteLink_Create,
hasNext: !self.linkListItems.isEmpty,
action: { [weak self] in
self?.openCreateLink()
@@ -580,12 +569,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
self.items[id] = item
}
- let subtitle: String
- if link.peerIds.count == 1 {
- subtitle = "includes 1 chat"
- } else {
- subtitle = "includes \(link.peerIds.count) chats"
- }
+ let subtitle: String = environment.strings.ChatListFilter_LinkLabelChatCount(Int32(link.peerIds.count))
let itemComponent = LinkListItemComponent(
theme: environment.theme,
@@ -711,15 +695,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
var subtitle: String?
if case let .channel(channel) = peer, case .broadcast = channel.info {
if linkContents.alreadyMemberPeerIds.contains(peer.id) {
- subtitle = "You are already a subscriber"
+ subtitle = environment.strings.FolderLinkPreview_LabelPeerSubscriber
} else if let memberCount = linkContents.memberCounts[peer.id] {
- subtitle = "\(memberCount) subscribers"
+ subtitle = environment.strings.FolderLinkPreview_LabelPeerSubscribers(Int32(memberCount))
}
} else {
if linkContents.alreadyMemberPeerIds.contains(peer.id) {
- subtitle = "You are already a member"
+ subtitle = environment.strings.FolderLinkPreview_LabelPeerMember
} else if let memberCount = linkContents.memberCounts[peer.id] {
- subtitle = "\(memberCount) participants"
+ subtitle = environment.strings.FolderLinkPreview_LabelPeerMembers(Int32(memberCount))
}
}
@@ -751,9 +735,9 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let text: String
if case let .channel(channel) = peer, case .broadcast = channel.info {
- text = "You are already a member of this channel."
+ text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberChannel
} else {
- text = "You are already a member of this group."
+ text = presentationData.strings.FolderLinkPreview_ToastAlreadyMemberGroup
}
controller.present(UndoOverlayController(presentationData: presentationData, content: .peers(context: component.context, peers: [peer], title: nil, text: text, customUndoText: nil), elevatedLayout: false, action: { _ in true }), in: .current)
} else {
@@ -796,43 +780,59 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let listHeaderTitle: String
if case .linkList = component.subject {
- listHeaderTitle = "INVITE LINKS"
+ listHeaderTitle = environment.strings.FolderLinkPreview_LinkSectionHeader
} else if let linkContents = component.linkContents {
if case .remove = component.subject {
- if linkContents.peers.count == 1 {
- listHeaderTitle = "1 CHAT TO QUIT"
- } else {
- listHeaderTitle = "\(linkContents.peers.count) CHATS TO QUIT"
- }
+ listHeaderTitle = environment.strings.FolderLinkPreview_RemoveSectionSelectedHeader(Int32(linkContents.peers.count))
} else if allChatsAdded {
- if linkContents.peers.count == 1 {
- listHeaderTitle = "1 CHAT IN THIS FOLDER"
- } else {
- listHeaderTitle = "\(linkContents.peers.count) CHATS IN THIS FOLDER"
- }
+ listHeaderTitle = environment.strings.FolderLinkPreview_ChatSectionHeader(Int32(linkContents.peers.count))
} else {
- if linkContents.peers.count == 1 {
- listHeaderTitle = "1 CHAT IN FOLDER TO JOIN"
- } else {
- listHeaderTitle = "\(linkContents.peers.count) CHATS IN FOLDER TO JOIN"
- }
+ listHeaderTitle = environment.strings.FolderLinkPreview_ChatSectionJoinHeader(Int32(linkContents.peers.count))
}
} else {
listHeaderTitle = " "
}
- //TODO:localize
- let listHeaderActionItems: [AnimatedCounterComponent.Item]
+ var listHeaderActionItems: [AnimatedCounterComponent.Item] = []
+
+ let dynamicIndex = environment.strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{dynamic}")
+ let staticIndex = environment.strings.FolderLinkPreview_ListSelectionSelectAllFormat.range(of: "{static}")
+ var headerActionItemIndices: [Int: Int] = [:]
+ if let dynamicIndex, let staticIndex {
+ if dynamicIndex.lowerBound < staticIndex.lowerBound {
+ headerActionItemIndices[0] = 0
+ headerActionItemIndices[1] = 1
+ } else {
+ headerActionItemIndices[0] = 1
+ headerActionItemIndices[1] = 0
+ }
+ } else if dynamicIndex != nil {
+ headerActionItemIndices[0] = 0
+ } else if staticIndex != nil {
+ headerActionItemIndices[1] = 0
+ }
+
+ let dynamicItem: AnimatedCounterComponent.Item
+ let staticItem: AnimatedCounterComponent.Item
+
if self.selectedItems.count == self.items.count {
- listHeaderActionItems = [
- AnimatedCounterComponent.Item(id: AnyHashable(0), text: "DESELECT", numericValue: 0),
- AnimatedCounterComponent.Item(id: AnyHashable(1), text: "ALL", numericValue: 1)
- ]
+ dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartDeselect, numericValue: 0)
+ staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllStaticPartDeselect, numericValue: 1)
} else {
- listHeaderActionItems = [
- AnimatedCounterComponent.Item(id: AnyHashable(0), text: "SELECT", numericValue: 1),
- AnimatedCounterComponent.Item(id: AnyHashable(1), text: "ALL", numericValue: 1)
- ]
+ dynamicItem = AnimatedCounterComponent.Item(id: AnyHashable(0), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllDynamicPartSelect, numericValue: 1)
+ staticItem = AnimatedCounterComponent.Item(id: AnyHashable(1), text: environment.strings.FolderLinkPreview_ListSelectionSelectAllStaticPartSelect, numericValue: 1)
+ }
+
+ if let dynamicIndex = headerActionItemIndices[0], let staticIndex = headerActionItemIndices[1] {
+ if dynamicIndex < staticIndex {
+ listHeaderActionItems = [dynamicItem, staticItem]
+ } else {
+ listHeaderActionItems = [staticItem, dynamicItem]
+ }
+ } else if headerActionItemIndices[0] != nil {
+ listHeaderActionItems = [dynamicItem]
+ } else if headerActionItemIndices[1] != nil {
+ listHeaderActionItems = [staticItem]
}
let listHeaderBody = MarkdownAttributeSet(font: Font.with(size: 13.0, design: .regular, traits: [.monospacedNumbers]), textColor: environment.theme.list.freeTextColor)
@@ -926,23 +926,23 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
if case .remove = component.subject {
actionButtonBadge = self.selectedItems.count
if self.selectedItems.isEmpty {
- actionButtonTitle = "Remove Folder"
+ actionButtonTitle = environment.strings.FolderLinkPreview_ButtonRemoveFolder
} else {
- actionButtonTitle = "Remove Folder and Chats"
+ actionButtonTitle = environment.strings.FolderLinkPreview_ButtonRemoveFolderAndChats
}
} else if allChatsAdded {
actionButtonBadge = 0
- actionButtonTitle = "OK"
+ actionButtonTitle = environment.strings.Common_OK
} else if let linkContents = component.linkContents {
- actionButtonBadge = self.selectedItems.count
+ actionButtonBadge = max(0, self.selectedItems.count - (linkContents.peers.count - canAddChatCount))
if linkContents.localFilterId != nil {
if self.selectedItems.isEmpty {
- actionButtonTitle = "Do Not Join Any Chats"
+ actionButtonTitle = environment.strings.FolderLinkPreview_ButtonDoNotJoinChats
} else {
- actionButtonTitle = "Join Chats"
+ actionButtonTitle = environment.strings.FolderLinkPreview_ButtonJoinChats
}
} else {
- actionButtonTitle = "Add Folder"
+ actionButtonTitle = environment.strings.FolderLinkPreview_ButtonAddFolder
}
} else {
actionButtonTitle = " "
@@ -985,17 +985,13 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let folderTitle = linkContents.title ?? ""
+ let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
+
var additionalText: String?
if !self.selectedItems.isEmpty {
- if self.selectedItems.count == 1 {
- additionalText = "You also left **1** chat"
- } else {
- additionalText = "You also left **\(self.selectedItems.count)** chats"
- }
+ additionalText = presentationData.strings.FolderLinkPreview_ToastLeftChatsText(Int32(self.selectedItems.count))
}
- let presentationData = component.context.sharedContext.currentPresentationData.with({ $0 })
-
var chatListController: ChatListController?
if let navigationController = controller.navigationController as? NavigationController {
for viewController in navigationController.viewControllers.reversed() {
@@ -1023,7 +1019,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let selectedItems = self.selectedItems
let undoOverlayController = UndoOverlayController(
presentationData: presentationData,
- content: .removedChat(title: "Folder \(folderTitle) deleted", text: additionalText),
+ content: .removedChat(title: presentationData.strings.FolderLinkPreview_ToastLeftTitle(folderTitle).string, text: additionalText),
elevatedLayout: false,
action: { value in
if case .commit = value {
@@ -1104,7 +1100,6 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
return
}
- //TODO:localize
let presentationData = context.sharedContext.currentPresentationData.with({ $0 })
var isUpdates = false
@@ -1117,29 +1112,15 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
}
if isUpdates {
- let chatCountString: String
- if result.newChatCount == 1 {
- chatCountString = "1 new chat"
- } else {
- chatCountString = "\(result.newChatCount) new chats"
- }
-
- chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: "Folder \(result.title) Updated", text: "You have joined \(chatCountString)", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
+ chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_add_to_folder", scale: 0.1, colors: ["__allcolors__": UIColor.white], title: presentationData.strings.FolderLinkPreview_ToastChatsAddedTitle(result.title).string, text: presentationData.strings.FolderLinkPreview_ToastChatsAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
} else if result.newChatCount != 0 {
- let chatCountString: String
- if result.newChatCount == 1 {
- chatCountString = "1 chat"
- } else {
- chatCountString = "\(result.newChatCount) chats"
- }
-
let animationBackgroundColor: UIColor
if presentationData.theme.overallDarkAppearance {
animationBackgroundColor = presentationData.theme.rootController.tabBar.backgroundColor
} else {
animationBackgroundColor = UIColor(rgb: 0x474747)
}
- chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: "Folder \(result.title) Added", text: "You also joined \(chatCountString)", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
+ chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title).string, text: presentationData.strings.FolderLinkPreview_ToastFolderAddedText(Int32(result.newChatCount)), customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
} else {
let animationBackgroundColor: UIColor
if presentationData.theme.overallDarkAppearance {
@@ -1147,7 +1128,7 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
} else {
animationBackgroundColor = UIColor(rgb: 0x474747)
}
- chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: "Folder \(result.title) Added", text: "", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
+ chatListController.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_success", scale: 1.0, colors: ["info1.info1.stroke": animationBackgroundColor, "info2.info2.Fill": animationBackgroundColor], title: presentationData.strings.FolderLinkPreview_ToastFolderAddedTitle(result.title).string, text: "", customUndoText: nil, timeout: 5), elevatedLayout: false, action: { _ in true }), in: .current)
}
})
}
@@ -1421,11 +1402,12 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
let context = component.context
let navigationController = controller.navigationController as? NavigationController
- //TODO:localize
+ let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
+
let text: String
switch error {
case .generic:
- text = "An error occurred"
+ text = presentationData.strings.ChatListFilter_CreateLinkUnknownError
case let .sharedFolderLimitExceeded(limit, _):
let limitController = component.context.sharedContext.makePremiumLimitController(context: component.context, subject: .membershipInSharedFolders, count: limit, action: { [weak navigationController] in
guard let navigationController else {
@@ -1470,10 +1452,8 @@ private final class ChatFolderLinkPreviewScreenComponent: Component {
return
case .someUserTooManyChannels:
- //TODO:localize
- text = "One of the groups in this folder can’t be added because one of its admins has too many groups and channels."
+ text = presentationData.strings.ChatListFilter_CreateLinkErrorSomeoneHasChannelLimit
}
- let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
})
}
diff --git a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift
index 4f8e5a3ad9..d5acbc67b8 100644
--- a/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift
+++ b/submodules/TelegramUI/Components/ChatFolderLinkPreviewScreen/Sources/PeerListItemComponent.swift
@@ -218,20 +218,19 @@ final class PeerListItemComponent: Component {
}
}
- //TODO:localize
let labelData: (String, Bool)
if let subtitle = component.subtitle {
labelData = (subtitle, false)
} else if case .legacyGroup = component.peer {
- labelData = ("group", false)
+ labelData = (component.strings.Group_Status, false)
} else if case let .channel(channel) = component.peer {
if case .group = channel.info {
- labelData = ("group", false)
+ labelData = (component.strings.Group_Status, false)
} else {
- labelData = ("channel", false)
+ labelData = (component.strings.Channel_Status, false)
}
} else {
- labelData = ("group", false)
+ labelData = (component.strings.Group_Status, false)
}
let labelSize = self.label.update(
diff --git a/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json
new file mode 100644
index 0000000000..5d62e00060
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "heart_18.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/heart_18.pdf b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/heart_18.pdf
new file mode 100644
index 0000000000..d65614e11f
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Media Grid/Favorite.imageset/heart_18.pdf
@@ -0,0 +1,75 @@
+%PDF-1.7
+
+1 0 obj
+ << >>
+endobj
+
+2 0 obj
+ << /Length 3 0 R >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 2.625000 2.815430 cm
+0.000000 0.000000 0.000000 scn
+6.375000 0.000042 m
+6.539109 0.000042 6.772649 0.119967 6.962005 0.239893 c
+10.490347 2.512170 12.750000 5.175784 12.750000 7.877269 c
+12.750000 10.193728 11.153094 11.809570 9.139605 11.809570 c
+7.883540 11.809570 6.943069 11.115263 6.375000 10.073803 c
+5.819555 11.108952 4.872772 11.809570 3.616708 11.809570 c
+1.603218 11.809570 0.000000 10.193728 0.000000 7.877269 c
+0.000000 5.175784 2.259654 2.512170 5.787995 0.239893 c
+5.983664 0.119967 6.217203 0.000042 6.375000 0.000042 c
+h
+f
+n
+Q
+
+endstream
+endobj
+
+3 0 obj
+ 611
+endobj
+
+4 0 obj
+ << /Annots []
+ /Type /Page
+ /MediaBox [ 0.000000 0.000000 18.000000 18.000000 ]
+ /Resources 1 0 R
+ /Contents 2 0 R
+ /Parent 5 0 R
+ >>
+endobj
+
+5 0 obj
+ << /Kids [ 4 0 R ]
+ /Count 1
+ /Type /Pages
+ >>
+endobj
+
+6 0 obj
+ << /Pages 5 0 R
+ /Type /Catalog
+ >>
+endobj
+
+xref
+0 7
+0000000000 65535 f
+0000000010 00000 n
+0000000034 00000 n
+0000000701 00000 n
+0000000723 00000 n
+0000000896 00000 n
+0000000970 00000 n
+trailer
+<< /ID [ (some) (id) ]
+ /Root 6 0 R
+ /Size 7
+>>
+startxref
+1029
+%%EOF
\ No newline at end of file
diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json
new file mode 100644
index 0000000000..962ddea299
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "edit.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/edit.pdf b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/edit.pdf
new file mode 100644
index 0000000000..0259952bc5
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperAdjustments.imageset/edit.pdf
@@ -0,0 +1,101 @@
+%PDF-1.7
+
+1 0 obj
+ << >>
+endobj
+
+2 0 obj
+ << /Length 3 0 R >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 7.170227 6.170044 cm
+1.000000 1.000000 1.000000 scn
+4.830000 16.659882 m
+5.288396 16.659882 5.660000 16.288279 5.660000 15.829882 c
+5.660000 13.659912 l
+14.830000 13.659912 l
+15.288396 13.659912 15.660000 13.288309 15.660000 12.829912 c
+15.660000 12.371515 15.288396 11.999912 14.830000 11.999912 c
+5.660000 11.999912 l
+5.660000 9.829882 l
+5.660000 9.371485 5.288396 8.999882 4.830000 8.999882 c
+4.371603 8.999882 4.000000 9.371485 4.000000 9.829882 c
+4.000000 11.999912 l
+0.830000 11.999912 l
+0.371604 11.999912 0.000000 12.371515 0.000000 12.829912 c
+0.000000 13.288309 0.371604 13.659912 0.830000 13.659912 c
+4.000000 13.659912 l
+4.000000 15.829882 l
+4.000000 16.288279 4.371603 16.659882 4.830000 16.659882 c
+h
+14.830078 2.999956 m
+15.288474 2.999956 15.660078 3.371560 15.660078 3.829956 c
+15.660078 4.288352 15.288474 4.659956 14.830078 4.659956 c
+11.660078 4.659956 l
+11.660078 6.829987 l
+11.660078 7.288383 11.288474 7.659986 10.830078 7.659986 c
+10.371682 7.659986 10.000078 7.288383 10.000078 6.829987 c
+10.000078 4.659956 l
+0.830078 4.659956 l
+0.371682 4.659956 0.000078 4.288352 0.000078 3.829956 c
+0.000078 3.371560 0.371682 2.999956 0.830078 2.999956 c
+10.000078 2.999956 l
+10.000078 0.829987 l
+10.000078 0.371590 10.371682 -0.000013 10.830078 -0.000013 c
+11.288474 -0.000013 11.660078 0.371590 11.660078 0.829987 c
+11.660078 2.999956 l
+14.830078 2.999956 l
+h
+f*
+n
+Q
+
+endstream
+endobj
+
+3 0 obj
+ 1448
+endobj
+
+4 0 obj
+ << /Annots []
+ /Type /Page
+ /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ]
+ /Resources 1 0 R
+ /Contents 2 0 R
+ /Parent 5 0 R
+ >>
+endobj
+
+5 0 obj
+ << /Kids [ 4 0 R ]
+ /Count 1
+ /Type /Pages
+ >>
+endobj
+
+6 0 obj
+ << /Pages 5 0 R
+ /Type /Catalog
+ >>
+endobj
+
+xref
+0 7
+0000000000 65535 f
+0000000010 00000 n
+0000000034 00000 n
+0000001538 00000 n
+0000001561 00000 n
+0000001734 00000 n
+0000001808 00000 n
+trailer
+<< /ID [ (some) (id) ]
+ /Root 6 0 R
+ /Size 7
+>>
+startxref
+1867
+%%EOF
\ No newline at end of file
diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/Contents.json
new file mode 100644
index 0000000000..cf201488d6
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "brightness_max.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/brightness_max.pdf b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/brightness_max.pdf
new file mode 100644
index 0000000000..c696ee26d5
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMax.imageset/brightness_max.pdf
@@ -0,0 +1,135 @@
+%PDF-1.7
+
+1 0 obj
+ << >>
+endobj
+
+2 0 obj
+ << /Length 3 0 R >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm
+1.000000 1.000000 1.000000 scn
+9.140625 16.273438 m
+9.140625 16.648438 8.828125 16.960938 8.453125 16.960938 c
+8.085938 16.960938 7.773438 16.648438 7.773438 16.273438 c
+7.773438 14.632812 l
+7.773438 14.265625 8.085938 13.953125 8.453125 13.953125 c
+8.828125 13.953125 9.140625 14.265625 9.140625 14.632812 c
+9.140625 16.273438 l
+h
+12.312500 13.296875 m
+12.054688 13.031250 12.054688 12.601562 12.312500 12.335938 c
+12.578125 12.078125 13.015625 12.070312 13.281250 12.335938 c
+14.445312 13.500000 l
+14.710938 13.765625 14.710938 14.210938 14.445312 14.468750 c
+14.187500 14.734375 13.750000 14.734375 13.492188 14.468750 c
+12.312500 13.296875 l
+h
+3.625000 12.335938 m
+3.890625 12.078125 4.328125 12.078125 4.585938 12.335938 c
+4.851562 12.585938 4.851562 13.039062 4.593750 13.296875 c
+3.429688 14.468750 l
+3.179688 14.726562 2.734375 14.734375 2.468750 14.468750 c
+2.210938 14.210938 2.210938 13.765625 2.460938 13.507812 c
+3.625000 12.335938 l
+h
+8.453125 12.468750 m
+6.273438 12.468750 4.468750 10.664062 4.468750 8.476562 c
+4.468750 6.296875 6.273438 4.492188 8.453125 4.492188 c
+10.625000 4.492188 12.429688 6.296875 12.429688 8.476562 c
+12.429688 10.664062 10.625000 12.468750 8.453125 12.468750 c
+h
+16.226562 7.796875 m
+16.601562 7.796875 16.914062 8.109375 16.914062 8.476562 c
+16.914062 8.843750 16.601562 9.156250 16.226562 9.156250 c
+14.593750 9.156250 l
+14.226562 9.156250 13.914062 8.843750 13.914062 8.476562 c
+13.914062 8.109375 14.226562 7.796875 14.593750 7.796875 c
+16.226562 7.796875 l
+h
+0.679688 9.156250 m
+0.312500 9.156250 0.000000 8.843750 0.000000 8.476562 c
+0.000000 8.109375 0.312500 7.796875 0.679688 7.796875 c
+2.312500 7.796875 l
+2.687500 7.796875 3.000000 8.109375 3.000000 8.476562 c
+3.000000 8.843750 2.687500 9.156250 2.312500 9.156250 c
+0.679688 9.156250 l
+h
+13.273438 4.609375 m
+13.015625 4.875000 12.578125 4.875000 12.312500 4.609375 c
+12.054688 4.351562 12.054688 3.914062 12.312500 3.648438 c
+13.492188 2.476562 l
+13.750000 2.218750 14.187500 2.226562 14.445312 2.484375 c
+14.710938 2.750000 14.710938 3.187500 14.445312 3.445312 c
+13.273438 4.609375 l
+h
+2.460938 3.453125 m
+2.203125 3.195312 2.203125 2.757812 2.453125 2.492188 c
+2.710938 2.234375 3.156250 2.226562 3.421875 2.484375 c
+4.585938 3.648438 l
+4.851562 3.906250 4.851562 4.343750 4.593750 4.609375 c
+4.335938 4.867188 3.890625 4.867188 3.625000 4.609375 c
+2.460938 3.453125 l
+h
+9.140625 2.320312 m
+9.140625 2.695312 8.828125 3.007812 8.453125 3.007812 c
+8.085938 3.007812 7.773438 2.695312 7.773438 2.320312 c
+7.773438 0.679688 l
+7.773438 0.312500 8.085938 0.000000 8.453125 0.000000 c
+8.828125 0.000000 9.140625 0.312500 9.140625 0.679688 c
+9.140625 2.320312 l
+h
+f
+n
+Q
+
+endstream
+endobj
+
+3 0 obj
+ 2760
+endobj
+
+4 0 obj
+ << /Annots []
+ /Type /Page
+ /MediaBox [ 0.000000 0.000000 16.914062 16.960938 ]
+ /Resources 1 0 R
+ /Contents 2 0 R
+ /Parent 5 0 R
+ >>
+endobj
+
+5 0 obj
+ << /Kids [ 4 0 R ]
+ /Count 1
+ /Type /Pages
+ >>
+endobj
+
+6 0 obj
+ << /Pages 5 0 R
+ /Type /Catalog
+ >>
+endobj
+
+xref
+0 7
+0000000000 65535 f
+0000000010 00000 n
+0000000034 00000 n
+0000002850 00000 n
+0000002873 00000 n
+0000003046 00000 n
+0000003120 00000 n
+trailer
+<< /ID [ (some) (id) ]
+ /Root 6 0 R
+ /Size 7
+>>
+startxref
+3179
+%%EOF
\ No newline at end of file
diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json
new file mode 100644
index 0000000000..68cb6916eb
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "brightness_min.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf
new file mode 100644
index 0000000000..ec3ccfc3af
--- /dev/null
+++ b/submodules/TelegramUI/Images.xcassets/Settings/WallpaperBrightnessMin.imageset/brightness_min.pdf
@@ -0,0 +1,198 @@
+%PDF-1.7
+
+1 0 obj
+ << /Length 2 0 R >>
+stream
+1.154297 0 0.087402 -0.137207 1.066895 1.066895 d1
+
+endstream
+endobj
+
+2 0 obj
+ 51
+endobj
+
+3 0 obj
+ [ 1.154297 ]
+endobj
+
+4 0 obj
+ << /Length 5 0 R >>
+stream
+/CIDInit /ProcSet findresource begin
+12 dict begin
+begincmap
+/CIDSystemInfo
+<< /Registry (FigmaPDF)
+ /Ordering (FigmaPDF)
+ /Supplement 0
+>> def
+/CMapName /A-B-C def
+/CMapType 2 def
+1 begincodespacerange
+<00>
+endcodespacerange
+1 beginbfchar
+<00>
+endbfchar
+endcmap
+CMapName currentdict /CMap defineresource pop
+end
+end
+endstream
+endobj
+
+5 0 obj
+ 336
+endobj
+
+6 0 obj
+ << /Subtype /Type3
+ /CharProcs << /C0 1 0 R >>
+ /Encoding << /Type /Encoding
+ /Differences [ 0 /C0 ]
+ >>
+ /Widths 3 0 R
+ /FontBBox [ 0.000000 0.000000 0.000000 0.000000 ]
+ /FontMatrix [ 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 ]
+ /Type /Font
+ /ToUnicode 4 0 R
+ /FirstChar 0
+ /LastChar 0
+ /Resources << >>
+ >>
+endobj
+
+7 0 obj
+ << /Font << /F1 6 0 R >> >>
+endobj
+
+8 0 obj
+ << /Length 9 0 R >>
+stream
+/DeviceRGB CS
+/DeviceRGB cs
+q
+1.000000 0.000000 -0.000000 1.000000 -1.653809 1.365723 cm
+1.000000 1.000000 1.000000 scn
+0.342773 0.692383 m
+h
+9.000000 11.612793 m
+9.468750 11.612793 9.856934 12.000977 9.856934 12.469727 c
+9.856934 12.938477 9.468750 13.326660 9.000000 13.326660 c
+8.531250 13.326660 8.143066 12.938477 8.143066 12.469727 c
+8.143066 12.000977 8.531250 11.612793 9.000000 11.612793 c
+h
+4.407715 9.715820 m
+4.883789 9.715820 5.264648 10.104004 5.264648 10.572754 c
+5.264648 11.041504 4.883789 11.429688 4.407715 11.429688 c
+3.938965 11.429688 3.550781 11.041504 3.550781 10.572754 c
+3.550781 10.104004 3.938965 9.715820 4.407715 9.715820 c
+h
+13.592285 9.715820 m
+14.061035 9.715820 14.449219 10.104004 14.449219 10.572754 c
+14.449219 11.041504 14.061035 11.429688 13.592285 11.429688 c
+13.123535 11.429688 12.735352 11.041504 12.735352 10.572754 c
+12.735352 10.104004 13.123535 9.715820 13.592285 9.715820 c
+h
+9.000000 2.252441 m
+11.036133 2.252441 12.735352 3.944336 12.735352 5.980469 c
+12.735352 8.023926 11.036133 9.715820 9.000000 9.715820 c
+6.963867 9.715820 5.271973 8.023926 5.271973 5.980469 c
+5.271973 3.944336 6.963867 2.252441 9.000000 2.252441 c
+h
+2.510742 5.123535 m
+2.979492 5.123535 3.367676 5.511719 3.367676 5.980469 c
+3.367676 6.456543 2.979492 6.837402 2.510742 6.837402 c
+2.041992 6.837402 1.653809 6.456543 1.653809 5.980469 c
+1.653809 5.511719 2.041992 5.123535 2.510742 5.123535 c
+h
+15.489258 5.123535 m
+15.958008 5.123535 16.346191 5.511719 16.346191 5.980469 c
+16.346191 6.456543 15.958008 6.837402 15.489258 6.837402 c
+15.020508 6.837402 14.632324 6.456543 14.632324 5.980469 c
+14.632324 5.511719 15.020508 5.123535 15.489258 5.123535 c
+h
+13.592285 0.531250 m
+14.061035 0.531250 14.449219 0.919434 14.449219 1.388184 c
+14.449219 1.864258 14.061035 2.245117 13.592285 2.245117 c
+13.123535 2.245117 12.735352 1.864258 12.735352 1.388184 c
+12.735352 0.919434 13.123535 0.531250 13.592285 0.531250 c
+h
+4.407715 0.531250 m
+4.883789 0.531250 5.264648 0.919434 5.264648 1.388184 c
+5.264648 1.864258 4.883789 2.245117 4.407715 2.245117 c
+3.938965 2.245117 3.550781 1.864258 3.550781 1.388184 c
+3.550781 0.919434 3.938965 0.531250 4.407715 0.531250 c
+h
+9.000000 -1.365723 m
+9.468750 -1.365723 9.856934 -0.977539 9.856934 -0.508789 c
+9.856934 -0.032715 9.468750 0.348145 9.000000 0.348145 c
+8.531250 0.348145 8.143066 -0.032715 8.143066 -0.508789 c
+8.143066 -0.977539 8.531250 -1.365723 9.000000 -1.365723 c
+h
+f
+n
+Q
+q
+1.000000 0.000000 -0.000000 1.000000 -1.653809 1.365723 cm
+BT
+15.000000 0.000000 0.000000 15.000000 0.342773 0.692383 Tm
+/F1 1.000000 Tf
+[ (\000) ] TJ
+ET
+Q
+
+endstream
+endobj
+
+9 0 obj
+ 2605
+endobj
+
+10 0 obj
+ << /Annots []
+ /Type /Page
+ /MediaBox [ 0.000000 0.000000 14.692383 14.692383 ]
+ /Resources 7 0 R
+ /Contents 8 0 R
+ /Parent 11 0 R
+ >>
+endobj
+
+11 0 obj
+ << /Kids [ 10 0 R ]
+ /Count 1
+ /Type /Pages
+ >>
+endobj
+
+12 0 obj
+ << /Pages 11 0 R
+ /Type /Catalog
+ >>
+endobj
+
+xref
+0 13
+0000000000 65535 f
+0000000010 00000 n
+0000000117 00000 n
+0000000138 00000 n
+0000000169 00000 n
+0000000561 00000 n
+0000000583 00000 n
+0000000995 00000 n
+0000001041 00000 n
+0000003702 00000 n
+0000003725 00000 n
+0000003900 00000 n
+0000003976 00000 n
+trailer
+<< /ID [ (some) (id) ]
+ /Root 12 0 R
+ /Size 13
+>>
+startxref
+4037
+%%EOF
\ No newline at end of file
diff --git a/submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json b/submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json
new file mode 100644
index 0000000000..5ca19d8603
--- /dev/null
+++ b/submodules/TelegramUI/Resources/Animations/anim_remove_from_folder.json
@@ -0,0 +1 @@
+{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":60,"ip":0,"op":54,"w":400,"h":350,"nm":"Folder Out New 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.655],"y":[-0.884]},"o":{"x":[0.326],"y":[5.74]},"t":27,"s":[-10.082]},{"i":{"x":[0.648],"y":[1.018]},"o":{"x":[0.318],"y":[0.111]},"t":30,"s":[-11.275]},{"i":{"x":[0.613],"y":[1]},"o":{"x":[0.289],"y":[0.151]},"t":33,"s":[-30]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":41,"s":[-25]},{"t":50,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.649,"y":1},"o":{"x":0.553,"y":0.281},"t":27,"s":[153.06,67.855,0],"to":[-103.81,-23.605,0],"ti":[0,0,0]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":45,"s":[74.753,206.779,0],"to":[0,0,0],"ti":[0,0,0]},{"t":53,"s":[74.753,178.779,0]}],"ix":2},"a":{"a":0,"k":[-125.497,4.029,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":27,"s":[{"i":[[0,0],[11.608,-1.924],[8.098,-2.588],[0,0],[-1.468,0],[0,0],[0,0],[-0.83,-0.012],[-1.731,0.026],[-4.322,4.661],[0,0],[0,0],[3.535,0.518],[0,0],[1.174,0],[0,-0.025]],"o":[[0,0],[-5.659,0.938],[-53.918,17.233],[0,1.231],[0,0],[0,0],[0,0.017],[1.73,0.026],[0,0],[4.322,-4.661],[0,0],[1.196,-1.448],[-20.978,-3.073],[-0.831,-0.008],[-2.447,0],[0,0]],"v":[[-144.218,-10.647],[-167.073,-8.009],[-188.918,-3.005],[-234.982,19.257],[-232.323,21.486],[-142.067,19.122],[-141.199,19.014],[-139.902,19.061],[-112.739,17.694],[-101.876,4.123],[-85.409,-3.347],[-85.876,-3.687],[-86.394,-8.424],[-133.697,-11.082],[-137.701,-11.004],[-142.972,-10.683]],"c":true}]},{"i":{"x":0.7,"y":1},"o":{"x":0.3,"y":0},"t":33,"s":[{"i":[[0,0],[0,0],[0,-1.12],[0,0],[-1.657,0],[0,0],[0,0],[-0.937,-0.634],[-1.953,1.319],[0,0],[0,0],[0,0],[1.739,1.171],[0,0],[1.325,0],[0,-1.866]],"o":[[0,0],[-1.657,0],[0,0],[0,1.12],[0,0],[0,0],[0,0.896],[1.952,1.32],[0,0],[0,0],[0,0],[1.349,-1.317],[0,0],[-0.938,-0.633],[-2.761,0],[0,0]],"v":[[-136.662,-11.028],[-154.169,-11.028],[-157.169,-9],[-157.168,13.734],[-154.168,15.761],[-136.661,15.762],[-136.661,34.474],[-135.198,36.863],[-128.126,36.864],[-105.302,21.449],[-80.17,4.475],[-79.586,4.007],[-80.17,-0.302],[-128.129,-32.684],[-131.662,-33.673],[-136.662,-30.294]],"c":true}]},{"i":{"x":0.833,"y":1},"o":{"x":0.3,"y":0},"t":45,"s":[{"i":[[0,0],[0,0],[0,-1.441],[0,0],[-1.657,0],[0,0],[0,0],[-0.937,-0.816],[-1.953,1.698],[0,0],[0,0],[0,0],[1.739,1.507],[0,0],[1.325,0],[0,-2.402]],"o":[[0,0],[-1.657,0],[0,0],[0,1.441],[0,0],[0,0],[0,1.153],[1.952,1.699],[0,0],[0,0],[0,0],[1.349,-1.696],[0,0],[-0.938,-0.815],[-2.761,0],[0,0]],"v":[[-144.223,-12.822],[-161.73,-12.822],[-164.73,-10.212],[-164.73,19.046],[-161.73,21.655],[-144.223,21.655],[-144.223,45.738],[-142.76,48.812],[-135.689,48.814],[-112.864,28.974],[-87.731,7.128],[-87.147,6.526],[-87.732,0.98],[-135.689,-40.694],[-139.223,-41.966],[-144.223,-37.617]],"c":true}]},{"t":53,"s":[{"i":[[0,0],[0,0],[0,-1.657],[0,0],[-1.657,0],[0,0],[0,0],[-0.937,-0.938],[-1.953,1.952],[0,0],[0,0],[0,0],[1.739,1.732],[0,0],[1.325,0],[0,-2.761]],"o":[[0,0],[-1.657,0],[0,0],[0,1.657],[0,0],[0,0],[0,1.326],[1.952,1.953],[0,0],[0,0],[0,0],[1.349,-1.949],[0,0],[-0.938,-0.937],[-2.761,0],[0,0]],"v":[[-144.223,-15.381],[-161.73,-15.381],[-164.73,-12.381],[-164.73,21.257],[-161.73,24.257],[-144.223,24.257],[-144.223,51.945],[-142.76,55.479],[-135.689,55.481],[-112.864,32.671],[-87.731,7.556],[-87.147,6.863],[-87.732,0.487],[-135.689,-47.424],[-139.223,-48.887],[-144.223,-43.887]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":-0.004,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path-5","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":27,"op":115,"st":-5,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Folder Front","parent":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.78,31.05,0],"ix":2},"a":{"a":0,"k":[57.78,31.05,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":3,"s":[{"i":[[0,0],[2.227,-6.498],[0,0],[0,-1.195],[-11.046,0],[0,0],[-2.227,6.498],[0,0],[0,1.195],[11.046,0]],"o":[[-9.176,0],[0,0],[-0.397,1.159],[0,8.063],[0,0],[9.176,0],[0,0],[0.397,-1.159],[0,-8.063],[0,0]],"v":[[-12.096,6.216],[-31.498,17.271],[-48.512,66.908],[-49.11,70.451],[-29.11,85.05],[127.657,85.05],[147.059,73.994],[163.661,26.719],[164.259,23.176],[144.259,8.577]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.4,"y":0},"t":26,"s":[{"i":[[0,0],[5.516,-7.616],[0,0],[0,-1.636],[-11.046,0],[0,0],[-3.702,4.718],[0,0],[-0.287,1.611],[17.899,0.533]],"o":[[-13.886,0.099],[0,0],[-0.397,1.588],[0,11.046],[0,0],[9.176,0],[0,0],[1.817,-2.298],[2.019,-11.354],[0,0]],"v":[[15.169,-0.792],[-12.083,14.287],[-46.038,57.422],[-49.11,65.05],[-29.11,85.05],[127.657,85.05],[147.059,69.904],[179.824,27.398],[186.808,16.342],[164.086,-0.858]],"c":true}]},{"i":{"x":0.4,"y":1},"o":{"x":0.167,"y":0},"t":42,"s":[{"i":[[0,0],[2.227,-8.902],[0,0],[0,-1.636],[-11.046,0],[0,0],[-2.227,8.902],[0,0],[0,1.636],[11.046,0]],"o":[[-9.176,0],[0,0],[-0.397,1.588],[0,11.046],[0,0],[9.176,0],[0,0],[0.397,-1.588],[0,-11.046],[0,0]],"v":[[-19.91,-30.122],[-39.312,-14.977],[-48.512,60.196],[-49.11,65.05],[-29.11,85.05],[127.657,85.05],[147.059,69.904],[156.259,-5.268],[156.857,-10.122],[136.857,-30.122]],"c":true}]},{"t":52,"s":[{"i":[[0,0],[2.227,-8.902],[0,0],[0,-1.636],[-11.046,0],[0,0],[-2.227,8.902],[0,0],[0,1.636],[11.046,0]],"o":[[-9.176,0],[0,0],[-0.397,1.588],[0,11.046],[0,0],[9.176,0],[0,0],[0.397,-1.588],[0,-11.046],[0,0]],"v":[[-12.096,-22.95],[-31.498,-7.804],[-48.512,60.196],[-49.11,65.05],[-29.11,85.05],[127.657,85.05],[147.059,69.904],[164.072,1.904],[164.671,-2.95],[144.671,-22.95]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Paper","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.562],"y":[0.795]},"o":{"x":[0.193],"y":[0]},"t":8,"s":[-2]},{"i":{"x":[0.655],"y":[4.184]},"o":{"x":[0.314],"y":[-0.57]},"t":15,"s":[-4]},{"i":{"x":[0.718],"y":[0.734]},"o":{"x":[0.356],"y":[0.331]},"t":20.154,"s":[-3.621]},{"t":28,"s":[-9.363]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.322,"y":0},"t":8,"s":[257.567,220.153,0],"to":[0.382,-4.007,0],"ti":[2.941,3.83,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11.785,"s":[262.921,208.399,0],"to":[-5.646,-7.352,0],"ti":[0.534,6.723,0]},{"i":{"x":0.661,"y":0.618},"o":{"x":0.167,"y":0.167},"t":15,"s":[237.946,187.294,0],"to":[-1.543,-19.419,0],"ti":[6.059,14.623,0]},{"i":{"x":0.67,"y":0.693},"o":{"x":0.332,"y":0.331},"t":20,"s":[226.102,138.397,0],"to":[-13.538,-32.678,0],"ti":[21.79,14.533,0]},{"t":28,"s":[168.17,69.295,0]}],"ix":2},"a":{"a":0,"k":[62.027,-12.355,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.164},"t":8,"s":[{"i":[[2.516,-6.249],[0,0],[6.903,0.419],[0,0],[-1.987,4.299],[0,0],[-7.209,-0.602],[0,0]],"o":[[0,0],[-1.967,2.18],[0,0],[-8.816,-1.347],[0,0],[2.17,-2.46],[0,0],[5.299,0.792]],"v":[[161.945,-34.476],[134.595,10.574],[120.346,16.613],[-37.025,-0.992],[-48.931,-13.662],[-19.761,-62.799],[-2.707,-68.708],[155.9,-48.303]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":11.785,"s":[{"i":[[-0.62,-7.146],[24.266,-15.772],[6.897,0.475],[0,0],[-10.144,18.755],[0,0],[-7.201,-0.66],[0,0]],"o":[[0,0],[-1.611,6.106],[0,0],[-8.801,-1.419],[17.458,-31.917],[2.189,-2.441],[0,0],[5.29,0.835]],"v":[[176.594,-30.415],[136.76,12.668],[122.468,18.587],[-20.244,0.054],[-32.041,-12.709],[3.268,-65.026],[17.25,-70.48],[165.907,-44.98]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[7.268,-7.526],[11.042,-12.924],[6.905,0.324],[0,0],[-18.589,32.59],[0,0],[-7.213,-0.503],[0,0]],"o":[[0,0],[-6.535,7.649],[0,0],[-8.829,-1.226],[29.318,-51.402],[2.135,-2.488],[0,0],[5.306,0.719]],"v":[[208.562,-6.419],[189.704,17.731],[162.555,45.287],[24.586,36.525],[7.468,18.105],[44.979,-43.573],[61.315,-49.118],[208.152,-28.033]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":18,"s":[{"i":[[-1.674,-7.821],[5.846,-31.918],[6.855,0.89],[0,0],[-1.209,12.301],[0,0],[-7.147,-1.093],[0,0]],"o":[[0,0],[-2.049,7.934],[0,0],[-8.699,-1.946],[15.144,-51.127],[2.332,-2.305],[0,0],[5.23,1.152]],"v":[[191.552,-12.575],[190.205,69.374],[178.047,75.855],[41.045,65.235],[24.048,51.03],[24.881,-38.407],[37.635,-43.057],[176.588,-28.143]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":21,"s":[{"i":[[-6.996,-8.612],[-0.448,-28.68],[6.911,-0.175],[0,0],[1.699,15.655],[0,0],[-7.231,0.019],[0,0]],"o":[[13.328,16.408],[-1.152,6.252],[0,0],[-8.896,-0.586],[0.268,-41.886],[1.95,-2.636],[0,0],[5.345,0.335]],"v":[[163.235,-21.22],[199.589,61.124],[191.209,70.479],[50.449,70.568],[33.588,58.016],[-10.506,-34.863],[2.092,-37.752],[142.978,-32.696]],"c":true}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":24,"s":[{"i":[[-7.256,-4.475],[-15.232,-36.815],[6.916,-0.045],[0,0],[-0.582,9.556],[1.574,4.124],[-7.233,-0.087],[0,0]],"o":[[0,0],[3.929,9.495],[0,0],[-8.887,-0.606],[2.621,-43.063],[-2.347,-6.149],[0,0],[5.341,0.35]],"v":[[131.416,-34.831],[185.982,24.651],[176.431,36.124],[18.207,41.687],[5.462,32.007],[-35.329,-37.09],[-19.1,-42.305],[114.097,-40.411]],"c":true}]},{"t":28,"s":[{"i":[[-7.583,-1.408],[-10.482,-12.74],[5.339,0.114],[0,0],[1.24,3.011],[3.16,1.374],[-5.191,0.695],[-29.909,-3.709]],"o":[[0,0],[-0.397,1.013],[-26.62,-2.211],[-6.606,-0.203],[-8.18,-13.777],[-1.646,-2.337],[5.772,-0.945],[3.215,0.189]],"v":[[92.11,-12.848],[153.114,11.129],[146.391,14.333],[35.34,12.786],[26.602,8.157],[0.215,-9.662],[8.591,-13.668],[76.61,-15.324]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[5,-14.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":8,"op":29,"st":-2,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Folder Far","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.4],"y":[1]},"o":{"x":[0.29],"y":[0]},"t":0,"s":[-13]},{"i":{"x":[0.466],"y":[1]},"o":{"x":[0.553],"y":[0]},"t":12,"s":[5]},{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.506],"y":[0]},"t":29,"s":[-10]},{"i":{"x":[0.7],"y":[1]},"o":{"x":[0.3],"y":[0]},"t":42,"s":[2]},{"t":50,"s":[0]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.4,"y":1},"o":{"x":0.29,"y":0},"t":0,"s":[174.274,223.861,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":10,"s":[234.274,131.861,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.5,"y":1},"o":{"x":0.4,"y":0},"t":27,"s":[234.274,189.861,0],"to":[0,0,0],"ti":[0,0,0]},{"t":41,"s":[234.274,153.861,0]}],"ix":2},"a":{"a":0,"k":[34.274,-21.139,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.2],"y":[1,1,1]},"o":{"x":[0.3,0.3,0.3],"y":[0,0,0]},"t":0,"s":[40,0,100]},{"t":10,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.3,"y":0},"t":3,"s":[{"i":[[0,0],[-11.641,-0.603],[0,0],[-3.346,-2.144],[0,0],[-4.559,-0.236],[0,0],[0.179,-3.451],[0,0],[5.246,0],[0,0],[3.437,-10.77],[0,0]],"o":[[0.4,-7.669],[0,0],[4.559,0.236],[0,0],[3.346,2.144],[0,0],[5.239,0.272],[0,0],[-1.081,2.903],[0,0],[-14.946,0],[0,0],[0,0]],"v":[[-31.343,-23.38],[-9.543,-36.173],[52.203,-34.019],[64.458,-30.329],[77.186,-22.173],[89.441,-18.482],[151.238,-14.104],[159.509,-7.163],[158.328,-2.715],[147.415,-0.44],[-7.364,-2.697],[-36.675,13.091],[-53.574,55.488]],"c":true}]},{"i":{"x":0.5,"y":1},"o":{"x":0.4,"y":0},"t":20,"s":[{"i":[[0,0],[-11.045,0.001],[0,0],[-3.271,-2.83],[0,0],[-4.326,0],[0,0],[0,-4.971],[0,0],[4.971,0],[0,0],[2.948,-13.852],[0,0]],"o":[[0.002,-11.045],[0,0],[4.326,0],[0,0],[3.271,2.83],[0,0],[4.971,0],[0,0],[0,4.971],[0,0],[-14.162,0],[0,0],[0,0]],"v":[[-64.694,-61.634],[-44.692,-81.634],[23.101,-81.634],[34.879,-77.246],[46.309,-70.961],[58.086,-66.573],[123.293,-66.573],[133.279,-57.406],[132.954,-51.02],[121.043,-44.289],[-25.669,-44.289],[-54.929,-21.027],[-65.353,60.809]],"c":true}]},{"t":41,"s":[{"i":[[0,0],[-11.045,0.001],[0,0],[-3.271,-2.83],[0,0],[-4.326,0],[0,0],[0,-4.971],[0,0],[4.971,0],[0,0],[2.948,-13.852],[0,0]],"o":[[0.002,-11.045],[0,0],[4.326,0],[0,0],[3.271,2.83],[0,0],[4.971,0],[0,0],[0,4.971],[0,0],[-14.162,0],[0,0],[0,0]],"v":[[-64.723,-65.95],[-44.721,-85.95],[23.072,-85.95],[34.85,-81.562],[47.295,-70.794],[59.072,-66.406],[124.279,-66.406],[133.279,-57.406],[133.279,-49.95],[124.279,-40.95],[-22.434,-40.95],[-51.777,-17.195],[-64.73,43.672]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift
index d77804fefc..78a5260652 100644
--- a/submodules/TelegramUI/Sources/AppDelegate.swift
+++ b/submodules/TelegramUI/Sources/AppDelegate.swift
@@ -483,7 +483,7 @@ private func extractAccountManagerState(records: AccountRecordsView Bool = {
+ if strongSelf.presentVoiceMessageDiscardAlert(action: { [weak self] in
+ if let strongSelf = self {
+ Queue.mainQueue().after(0.1, {
+ let _ = strongSelf.controllerInteraction?.openMessage(message, mode)
+ })
+ }
+ }, performAction: false) {
+ return false
}
- }, performAction: false) {
- return false
+ return true
}
strongSelf.commitPurposefulAction()
@@ -696,6 +699,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
for media in message.media {
if media is TelegramMediaMap {
+ if !displayVoiceMessageDiscardAlert() {
+ return false
+ }
isLocation = true
}
if let file = media as? TelegramMediaFile, file.isInstantVideo {
@@ -707,12 +713,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let invoice = media as? TelegramMediaInvoice, let extendedMedia = invoice.extendedMedia {
switch extendedMedia {
case .preview:
- strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id)
- return true
+ if displayVoiceMessageDiscardAlert() {
+ strongSelf.controllerInteraction?.openCheckoutOrReceipt(message.id)
+ return true
+ } else {
+ return false
+ }
case .full:
break
}
} else if let action = media as? TelegramMediaAction {
+ if !displayVoiceMessageDiscardAlert() {
+ return false
+ }
switch action.action {
case .pinnedMessageUpdated:
for attribute in message.attributes {
@@ -845,11 +858,18 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
strongSelf.chatDisplayNode.dismissInput()
let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true))
- wallpaperPreviewController.apply = { wallpaper, options, _ in
- let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id)
- |> deliverOnMainQueue).start(completed: { [weak wallpaperPreviewController] in
- wallpaperPreviewController?.dismiss()
- })
+ wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, _ in
+ if case let .wallpaper(wallpaper, _) = entry, case let .file(file) = wallpaper, !file.isPattern && options.contains(.blur) {
+ uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: entry, mode: options, editedImage: nil, cropRect: nil, brightness: nil, peerId: message.id.peerId, completion: {
+ wallpaperPreviewController?.dismiss()
+ })
+ } else {
+ let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: nil)
+ |> deliverOnMainQueue).start()
+ Queue.mainQueue().after(0.1) {
+ wallpaperPreviewController?.dismiss()
+ }
+ }
}
strongSelf.push(wallpaperPreviewController)
return true
@@ -1076,7 +1096,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
- legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
+ legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
transitionCompletion()
}, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView()
@@ -2492,7 +2512,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}))
}
- }, activateSwitchInline: { [weak self] peerId, inputString in
+ }, activateSwitchInline: { [weak self] peerId, inputString, peerTypes in
guard let strongSelf = self else {
return
}
@@ -2516,7 +2536,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
})
} else {
- strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil)
+ strongSelf.openPeer(peer: nil, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil, peekData: nil), fromMessage: nil, peerTypes: peerTypes)
}
}
}, openUrl: { [weak self] url, concealed, _, message in
@@ -3950,7 +3970,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
let inputText = strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText
- legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in
+ legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: strongSelf.threadInfo?.title, media: mediaReference, mode: .draw, initialCaption: inputText, snapshots: [], transitionCompletion: nil, getCaptionPanelView: { [weak self] in
return self?.getCaptionPanelView()
}, sendMessagesWithSignals: { [weak self] signals, _, _ in
if let strongSelf = self {
@@ -4206,12 +4226,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
completion()
controller?.dismiss()
- strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)")
+ strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
}
}
strongSelf.push(controller)
} else {
- strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)")
+ strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
}
}
}, getNavigationController: { [weak self] in
@@ -4264,12 +4284,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if value {
openWebView()
} else {
- strongSelf.present(textAlertController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, title: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertTitle, text: strongSelf.presentationData.strings.WebApp_OpenWebViewAlertText(botName).string, actions: [TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { }), TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {
- if let strongSelf = self {
- let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start()
- openWebView()
- }
- })], parseMarkdown: true), in: .window(.root), with: nil)
+ let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: EnginePeer(peer), commit: {
+ let _ = ApplicationSpecificNotice.setBotGameNotice(accountManager: strongSelf.context.sharedContext.accountManager, peerId: peer.id).start()
+ openWebView()
+ }, showMore: nil)
+ strongSelf.present(controller, in: .window(.root))
}
})
}, activateAdAction: { [weak self] messageId in
@@ -4279,22 +4298,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
switch adAttribute.target {
case let .peer(id, messageId, startParam):
- let navigationData: ChatControllerInteractionNavigateToPeer
- if let bot = message.author as? TelegramUser, bot.botInfo != nil, let startParam = startParam {
- navigationData = .withBotStartPayload(ChatControllerInitialBotStart(payload: startParam, behavior: .interactive))
+ if case let .peer(currentPeerId) = self.chatLocation, currentPeerId == id {
+ if let messageId {
+ self.navigateToMessage(from: nil, to: .id(messageId, nil), rememberInStack: false)
+ }
} else {
- var subject: ChatControllerSubject?
- if let messageId = messageId {
- subject = .message(id: .id(messageId), highlight: true, timecode: nil)
+ let navigationData: ChatControllerInteractionNavigateToPeer
+ if let bot = message.author as? TelegramUser, bot.botInfo != nil, let startParam = startParam {
+ navigationData = .withBotStartPayload(ChatControllerInitialBotStart(payload: startParam, behavior: .interactive))
+ } else {
+ var subject: ChatControllerSubject?
+ if let messageId = messageId {
+ subject = .message(id: .id(messageId), highlight: true, timecode: nil)
+ }
+ navigationData = .chat(textInputState: nil, subject: subject, peekData: nil)
}
- navigationData = .chat(textInputState: nil, subject: subject, peekData: nil)
+ let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id))
+ |> deliverOnMainQueue).start(next: { [weak self] peer in
+ if let self, let peer = peer {
+ self.openPeer(peer: peer, navigation: navigationData, fromMessage: nil)
+ }
+ })
}
- let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: id))
- |> deliverOnMainQueue).start(next: { [weak self] peer in
- if let self, let peer = peer {
- self.openPeer(peer: peer, navigation: navigationData, fromMessage: nil)
- }
- })
case let .join(_, joinHash):
self.controllerInteraction?.openJoinLink(joinHash)
}
@@ -5811,8 +5836,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let themeEmoticon: Signal = self.chatThemeEmoticonPromise.get()
|> distinctUntilChanged
+
+ let uploadingChatWallpaper: Signal
+ if let peerId = self.chatLocation.peerId {
+ uploadingChatWallpaper = self.context.account.pendingPeerMediaUploadManager.uploadingPeerMedia
+ |> map { uploadingPeerMedia -> TelegramWallpaper? in
+ if let item = uploadingPeerMedia[peerId], case let .wallpaper(wallpaper) = item.content {
+ return wallpaper
+ } else {
+ return nil
+ }
+ }
+ |> distinctUntilChanged
+ } else {
+ uploadingChatWallpaper = .single(nil)
+ }
- let chatWallpaper: Signal = self.chatWallpaperPromise.get()
+ let chatWallpaper: Signal = combineLatest(self.chatWallpaperPromise.get(), uploadingChatWallpaper)
+ |> map { chatWallpaper, uploadingChatWallpaper in
+ return uploadingChatWallpaper ?? chatWallpaper
+ }
|> distinctUntilChanged
let themeSettings = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.presentationThemeSettings])
@@ -5840,6 +5883,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
if let strongSelf = self {
let (themeEmoticonPreview, darkAppearancePreview) = themeEmoticonAndDarkAppearance
+ var chatWallpaper = chatWallpaper
+
let previousTheme = strongSelf.presentationData.theme
let previousStrings = strongSelf.presentationData.strings
let previousChatWallpaper = strongSelf.presentationData.chatWallpaper
@@ -5847,7 +5892,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var themeEmoticon = themeEmoticon
if let themeEmoticonPreview = themeEmoticonPreview {
if !themeEmoticonPreview.isEmpty {
- themeEmoticon = themeEmoticonPreview
+ if themeEmoticon?.strippedEmoji != themeEmoticonPreview.strippedEmoji {
+ chatWallpaper = nil
+ themeEmoticon = themeEmoticonPreview
+ }
} else {
themeEmoticon = nil
}
@@ -5858,7 +5906,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
var presentationData = presentationData
var useDarkAppearance = presentationData.theme.overallDarkAppearance
-
+
if let themeEmoticon = themeEmoticon, let theme = chatThemes.first(where: { $0.emoticon?.strippedEmoji == themeEmoticon.strippedEmoji }) {
if let darkAppearancePreview = darkAppearancePreview {
useDarkAppearance = darkAppearancePreview
@@ -5897,7 +5945,12 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
lightWallpaper = theme.chat.defaultWallpaper
}
- lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
+ var preferredBaseTheme: TelegramBaseTheme?
+ if let baseTheme = themeSettings.themePreferredBaseTheme[themeSettings.theme.index], [.classic, .day].contains(baseTheme) {
+ preferredBaseTheme = baseTheme
+ }
+
+ lightTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: themeSettings.theme, baseTheme: preferredBaseTheme, accentColor: currentColors?.color, bubbleColors: currentColors?.customBubbleColors ?? [], wallpaper: currentColors?.wallpaper, baseColor: currentColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
} else {
lightTheme = presentationData.theme
lightWallpaper = presentationData.chatWallpaper
@@ -5906,7 +5959,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let effectiveColors = themeSettings.themeSpecificAccentColors[automaticTheme.index]
let themeSpecificWallpaper = (themeSettings.themeSpecificChatWallpapers[coloredThemeIndex(reference: automaticTheme, accentColor: effectiveColors)] ?? themeSettings.themeSpecificChatWallpapers[automaticTheme.index])
- darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
+ var preferredBaseTheme: TelegramBaseTheme?
+ if let baseTheme = themeSettings.themePreferredBaseTheme[automaticTheme.index], [.night, .tinted].contains(baseTheme) {
+ preferredBaseTheme = baseTheme
+ } else {
+ preferredBaseTheme = .night
+ }
+
+ darkTheme = makePresentationTheme(mediaBox: accountManager.mediaBox, themeReference: automaticTheme, baseTheme: preferredBaseTheme, accentColor: effectiveColors?.color, bubbleColors: effectiveColors?.customBubbleColors ?? [], wallpaper: effectiveColors?.wallpaper, baseColor: effectiveColors?.baseColor, serviceBackgroundColor: defaultServiceBackgroundColor) ?? defaultPresentationTheme
if let themeSpecificWallpaper = themeSpecificWallpaper {
darkWallpaper = themeSpecificWallpaper
@@ -5945,7 +6005,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
presentationData = presentationData.withUpdated(chatWallpaper: chatWallpaper)
}
-
let isFirstTime = !strongSelf.didSetPresentationData
strongSelf.presentationData = presentationData
strongSelf.didSetPresentationData = true
@@ -10942,6 +11001,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return false
}
+ if strongSelf.effectiveNavigationController?.topViewController !== strongSelf {
+ return false
+ }
+
if strongSelf.presentationInterfaceState.inputTextPanelState.mediaRecordingState != nil {
return false
}
@@ -12765,7 +12828,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.presentAttachmentMenu(subject: .bot(id: botId, payload: payload, justInstalled: justInstalled))
}
- public func presentBotApp(botApp: BotApp, payload: String?) {
+ public func presentBotApp(botApp: BotApp, botPeer: EnginePeer, payload: String?) {
guard let peerId = self.chatLocation.peerId else {
return
}
@@ -12813,6 +12876,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}
+ let botAddress = botPeer.addressName ?? ""
+
self.messageActionCallbackDisposable.set(((self.context.engine.messages.requestAppWebView(peerId: peerId, appReference: .id(id: botApp.id, accessHash: botApp.accessHash), payload: payload, themeParams: generateWebAppThemeParams(self.presentationData.theme), allowWrite: false)
|> afterDisposed {
updateProgress()
@@ -12824,6 +12889,22 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let params = WebAppParameters(peerId: peerId, botId: peerId, botName: botApp.title, url: url, queryId: 0, payload: payload, buttonText: "", keepAliveSignal: nil, fromMenu: false, fromAttachMenu: false, isInline: false, isSimple: false)
let controller = standaloneWebAppController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, params: params, threadId: strongSelf.chatLocation.threadId, openUrl: { [weak self] url in
self?.openUrl(url, concealed: true, forceExternal: true)
+ }, requestSwitchInline: { [weak self] query, chatTypes, completion in
+ if let strongSelf = self {
+ if let chatTypes {
+ let controller = strongSelf.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: strongSelf.context, filter: [.excludeRecent, .doNotSearchMessages], requestPeerType: chatTypes, hasContactSelector: false, hasCreation: false))
+ controller.peerSelected = { [weak self, weak controller] peer, _ in
+ if let strongSelf = self {
+ completion()
+ controller?.dismiss()
+ strongSelf.controllerInteraction?.activateSwitchInline(peer.id, "@\(botAddress) \(query)", nil)
+ }
+ }
+ strongSelf.push(controller)
+ } else {
+ strongSelf.controllerInteraction?.activateSwitchInline(peerId, "@\(botAddress) \(query)", nil)
+ }
+ }
}, completion: { [weak self] in
self?.chatDisplayNode.historyNode.scrollToEndOfHistory()
}, getNavigationController: { [weak self] in
@@ -13826,7 +13907,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
self.present(actionSheet, in: .window(.root))
}
- private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, false), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
+ private func presentMediaPicker(subject: MediaPickerScreen.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreen, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping ([Any], Bool, Int32?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) {
guard let peer = self.presentationInterfaceState.renderedPeer?.peer else {
return
}
@@ -14315,7 +14396,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
- let tooltipScreen = TooltipScreen(account: self.context.account, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in
+ let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {
@@ -14408,7 +14489,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
- let tooltipScreen = TooltipScreen(account: self.context.account, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
+ let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {
@@ -14522,7 +14603,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
- let tooltipScreen = TooltipScreen(account: self.context.account, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
+ let tooltipScreen = TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: psaText, textEntities: psaEntities, icon: .info, location: .top, displayDuration: .custom(10.0), shouldDismissOnTouch: { point in
return .ignore
}, openActiveTextItem: { [weak self] item, action in
guard let strongSelf = self else {
@@ -16659,7 +16740,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
- private func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false) {
+ private func openPeer(peer: EnginePeer?, navigation: ChatControllerInteractionNavigateToPeer, fromMessage: MessageReference?, fromReactionMessageId: MessageId? = nil, expandAvatar: Bool = false, peerTypes: ReplyMarkupButtonAction.PeerTypes? = nil) {
let _ = self.presentVoiceMessageDiscardAlert(action: {
if case let .peer(currentPeerId) = self.chatLocation, peer?.id == currentPeerId {
switch navigation {
@@ -16758,7 +16839,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
break
case let .chat(textInputState, _, _):
if let textInputState = textInputState {
- let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, selectForumThreads: true))
+ let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, requestPeerType: peerTypes.flatMap { $0.requestPeerTypes }, selectForumThreads: true))
controller.peerSelected = { [weak self, weak controller] peer, threadId in
let peerId = peer.id
@@ -17173,7 +17254,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}))
}
- private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, forceExternal: Bool = false) {
+ private func openResolved(result: ResolvedUrl, sourceMessageId: MessageId?, forceExternal: Bool = false, concealed: Bool = false) {
guard let peerId = self.chatLocation.peerId else {
return
}
@@ -17230,7 +17311,28 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(peerId), attachBotStart: attachBotStart))
}
case let .withBotApp(botAppStart):
- strongSelf.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload)
+ let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId.id))
+ |> deliverOnMainQueue).start(next: { [weak self] peer in
+ if let strongSelf = self, let peer {
+ let openBotApp = { [weak self] in
+ if let strongSelf = self {
+ strongSelf.presentBotApp(botApp: botAppStart.botApp, botPeer: peerId, payload: botAppStart.payload)
+ }
+ }
+ if concealed {
+ let controller = webAppLaunchConfirmationController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, peer: peer, commit: {
+ openBotApp()
+ }, showMore: { [weak self] in
+ if let strongSelf = self {
+ strongSelf.openResolved(result: .peer(peer._asPeer(), .info), sourceMessageId: nil)
+ }
+ })
+ strongSelf.present(controller, in: .window(.root))
+ } else {
+ openBotApp()
+ }
+ }
+ })
default:
break
}
@@ -17256,7 +17358,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
openUserGeneratedUrl(context: self.context, peerId: self.peerView?.peerId, url: url, concealed: concealed, skipUrlAuth: skipUrlAuth, skipConcealedAlert: skipConcealedAlert, present: { [weak self] c in
self?.present(c, in: .window(.root))
}, openResolved: { [weak self] resolved in
- self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal)
+ self?.openResolved(result: resolved, sourceMessageId: message?.id, forceExternal: forceExternal, concealed: concealed)
})
}, performAction: true)
}
@@ -18447,6 +18549,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
public func presentThemeSelection() {
+ guard self.themeScreen == nil else {
+ return
+ }
let context = self.context
let peerId = self.chatLocation.peerId
@@ -18485,22 +18590,25 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
return
}
-
- let selectedEmoticon: String? = themeEmoticon
+ var canResetWallpaper = false
+ if let cachedUserData = strongSelf.peerView?.cachedData as? CachedUserData {
+ canResetWallpaper = cachedUserData.wallpaper != nil
+ }
let controller = ChatThemeScreen(
context: context,
updatedPresentationData: strongSelf.updatedPresentationData,
animatedEmojiStickers: animatedEmojiStickers,
- initiallySelectedEmoticon: selectedEmoticon,
+ initiallySelectedEmoticon: themeEmoticon,
peerName: strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer.flatMap(EnginePeer.init)?.compactDisplayTitle ?? "",
+ canResetWallpaper: canResetWallpaper,
previewTheme: { [weak self] emoticon, dark in
if let strongSelf = self {
strongSelf.presentCrossfadeSnapshot()
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon, dark)))
}
},
- changeWallpaper: {
+ changeWallpaper: { [weak self] in
guard let strongSelf = self, let peerId else {
return
}
@@ -18511,7 +18619,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let dismissControllers = { [weak self] in
if let self, let navigationController = self.navigationController as? NavigationController {
let controllers = navigationController.viewControllers.filter({ controller in
- if controller is WallpaperGalleryController || controller is MediaPickerScreen {
+ if controller is WallpaperGalleryController || controller is AttachmentController {
return false
}
return true
@@ -18519,42 +18627,67 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
navigationController.setViewControllers(controllers, animated: true)
}
}
-
- let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, true))
- controller.navigationPresentation = .modal
- controller.customSelection = { [weak self] asset in
+ var openWallpaperPickerImpl: ((Bool) -> Void)?
+ let openWallpaperPicker = { [weak self] animateAppearance in
guard let strongSelf = self else {
return
}
- let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false))
- controller.navigationPresentation = .modal
- controller.apply = { [weak self] wallpaper, options, cropRect in
- if let strongSelf = self {
- uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, cropRect: cropRect, peerId: peerId, completion: {
- dismissControllers()
+ let controller = wallpaperMediaPickerController(
+ context: strongSelf.context,
+ updatedPresentationData: strongSelf.updatedPresentationData,
+ peer: EnginePeer(peer),
+ animateAppearance: animateAppearance,
+ completion: { [weak self] asset in
+ guard let strongSelf = self else {
+ return
+ }
+ let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset), mode: .peer(EnginePeer(peer), false))
+ controller.navigationPresentation = .modal
+ controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness in
+ if let strongSelf = self {
+ uploadCustomPeerWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, peerId: peerId, completion: {
+ Queue.mainQueue().after(0.3, {
+ dismissControllers()
+ })
+ })
+ }
+ }
+ strongSelf.push(controller)
+ },
+ openColors: { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+ let controller = standaloneColorPickerController(context: strongSelf.context, peer: EnginePeer(peer), push: { [weak self] controller in
+ if let strongSelf = self {
+ strongSelf.push(controller)
+ }
+ }, openGallery: {
+ openWallpaperPickerImpl?(false)
})
+ controller.navigationPresentation = .flatModal
+ strongSelf.push(controller)
}
- }
+ )
+ controller.navigationPresentation = .flatModal
strongSelf.push(controller)
}
- strongSelf.push(controller)
+ openWallpaperPickerImpl = openWallpaperPicker
+ openWallpaperPicker(true)
},
- changeColor: {
- guard let strongSelf = self else {
+ resetWallpaper: { [weak self] in
+ guard let strongSelf = self, let peerId else {
return
}
- if let themeController = strongSelf.themeScreen {
- strongSelf.themeScreen = nil
- themeController.dimTapped()
- }
- let controller = ThemeColorsGridController(context: context, mode: .peer(EnginePeer(peer)))
- controller.navigationPresentation = .modal
- strongSelf.push(controller)
+ let _ = strongSelf.context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start()
},
completion: { [weak self] emoticon in
guard let strongSelf = self, let peerId else {
return
}
+ if canResetWallpaper && emoticon != nil {
+ let _ = context.engine.themes.setChatWallpaper(peerId: peerId, wallpaper: nil).start()
+ }
strongSelf.themeEmoticonAndDarkAppearancePreviewPromise.set(.single((emoticon ?? "", nil)))
let _ = context.engine.themes.setChatTheme(peerId: peerId, emoticon: emoticon).start(completed: { [weak self] in
if let strongSelf = self {
@@ -18563,6 +18696,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
})
}
)
+ controller.navigationPresentation = .flatModal
controller.passthroughHitTestImpl = { [weak self] _ in
if let strongSelf = self {
return strongSelf.chatDisplayNode.historyNode.view
@@ -18578,7 +18712,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
strongSelf.chatDisplayNode.historyNode.tapped = { [weak controller] in
controller?.dimTapped()
}
- strongSelf.present(controller, in: .window(.root))
+ strongSelf.push(controller)
strongSelf.themeScreen = controller
})
}
@@ -18612,7 +18746,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
}) {
let isEmoji = actions[0].0.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
-
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_RemovedTitle : presentationData.strings.StickerPackActionInfo_RemovedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_MultipleRemovedText(Int32(actions.count)) : presentationData.strings.StickerPackActionInfo_MultipleRemovedText(Int32(actions.count)), undo: true, info: actions[0].0, topItem: actions[0].1.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { action in
if case .undo = action {
var itemsAndIndices: [(StickerPackCollectionInfo, [StickerPackItem], Int)] = actions.compactMap { action -> (StickerPackCollectionInfo, [StickerPackItem], Int)? in
@@ -18632,7 +18765,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
} else if let (info, items, action) = actions.first {
let isEmoji = info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks
-
switch action {
case .add:
strongSelf.presentInGlobalOverlay(UndoOverlayController(presentationData: presentationData, content: .stickersModified(title: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedTitle : presentationData.strings.StickerPackActionInfo_AddedTitle, text: isEmoji ? presentationData.strings.EmojiPackActionInfo_AddedText(info.title).string : presentationData.strings.StickerPackActionInfo_AddedText(info.title).string, undo: false, info: info, topItem: items.first, context: context), elevatedLayout: true, animateInAsReplacement: false, action: { _ in
diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift
index cdb10a3a38..5235878a23 100644
--- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift
+++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift
@@ -662,7 +662,21 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
)
}
- let readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId))
+ let readCounters: Signal
+ if case let .replyThread(threadMessage) = chatPresentationInterfaceState.chatLocation, threadMessage.isForumPost {
+ readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.ThreadData(id: threadMessage.messageId.peerId, threadId: Int64(threadMessage.messageId.id)))
+ |> map { threadData -> Bool in
+ guard let threadData else {
+ return false
+ }
+ return threadData.maxOutgoingReadId >= message.id.id
+ }
+ } else {
+ readCounters = context.engine.data.get(TelegramEngine.EngineData.Item.Messages.PeerReadCounters(id: messages[0].id.peerId))
+ |> map { readCounters -> Bool in
+ return readCounters.isOutgoingMessageIndexRead(message.index)
+ }
+ }
let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?), NoError> = combineLatest(
loadLimits,
@@ -679,15 +693,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
context.engine.peers.notificationSoundList() |> take(1),
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
)
- |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, readCounters, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
+ |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, infoSummaryData, isMessageRead, messageViewsPrivacyTips, availableReactions, sharedData, notificationSoundList, accountPeer -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], InfoSummaryData, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings, NotificationSoundList?, EnginePeer?) in
let (limitsConfiguration, appConfig) = limitsAndAppConfig
var canEdit = false
if !isAction {
let message = messages[0]
canEdit = canEditMessage(context: context, limitsConfiguration: limitsConfiguration, message: message)
}
-
- let isMessageRead = readCounters.isOutgoingMessageIndexRead(message.index)
let translationSettings: TranslationSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift
index dd2d188c44..ea27f2b5f4 100644
--- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift
+++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift
@@ -101,6 +101,9 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
displayActionsPanel = true
}
}
+ if peerStatusSettings.requestChatTitle != nil {
+ displayActionsPanel = true
+ }
}
if displayActionsPanel && (selectedContext == nil || selectedContext! <= .pinnedMessage) {
diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift
index 4debf79fd8..5cf74c2ebc 100644
--- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift
@@ -1475,7 +1475,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
}
- let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
+ let foregroundColor: UIColor = .clear// = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
strongSelf.placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size, enableEffect: item.context.sharedContext.energyUsageSettings.fullTranslucency, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0))
strongSelf.placeholderNode.frame = animationNodeFrame
diff --git a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift
index 3d10f3ae39..603e1b46ae 100644
--- a/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageInteractiveMediaNode.swift
@@ -733,7 +733,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
} else {
unboundSize = CGSize(width: 54.0, height: 54.0)
}
- case .themeSettings:
+ case .themeSettings, .image:
unboundSize = CGSize(width: 160.0, height: 240.0).fitted(CGSize(width: 240.0, height: 240.0))
case .color, .gradient:
unboundSize = CGSize(width: 128.0, height: 128.0)
@@ -1116,6 +1116,8 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(message), media: file), representations: representations, alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true)
}
}
+ case let .image(representations):
+ return wallpaperImage(account: context.account, accountManager: context.sharedContext.accountManager, fileReference: nil, representations: representations.map({ ImageRepresentationWithReference(representation: $0, reference: .standalone(resource: $0.resource)) }), alwaysShowThumbnailFirst: false, thumbnail: true, autoFetchFullSize: true)
case let .themeSettings(settings):
return themeImage(account: context.account, accountManager: context.sharedContext.accountManager, source: .settings(settings))
case let .color(color):
@@ -1182,7 +1184,7 @@ final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTransitio
|> map { resourceStatus -> (MediaResourceStatus, MediaResourceStatus?) in
return (resourceStatus, nil)
}
- case .themeSettings, .color, .gradient:
+ case .themeSettings, .color, .gradient, .image:
updatedStatusSignal = .single((.Local, nil))
}
}
diff --git a/submodules/TelegramUI/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Sources/ChatMessageItemView.swift
index e1a36e8609..5555d4e440 100644
--- a/submodules/TelegramUI/Sources/ChatMessageItemView.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageItemView.swift
@@ -860,7 +860,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
item.controllerInteraction.requestMessageActionCallback(item.message.id, nil, true, false)
case let .callback(requiresPassword, data):
item.controllerInteraction.requestMessageActionCallback(item.message.id, data, false, requiresPassword)
- case let .switchInline(samePeer, query):
+ case let .switchInline(samePeer, query, peerTypes):
var botPeer: Peer?
var found = false
@@ -881,7 +881,7 @@ public class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol
peerId = item.message.id.peerId
}
if let botPeer = botPeer, let addressName = botPeer.addressName {
- item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)")
+ item.controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
}
case .payment:
item.controllerInteraction.openCheckoutOrReceipt(item.message.id)
diff --git a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift
index 311ab53b93..6b8b5f050e 100644
--- a/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageNotificationItem.swift
@@ -314,8 +314,17 @@ final class ChatMessageNotificationItemNode: NotificationItemNode {
title = "📅 \(currentTitle)"
}
- if let attribute = item.messages.first?.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title {
- title = "\(currentTitle) 🔕"
+ if let message = item.messages.first, let attribute = message.attributes.first(where: { $0 is NotificationInfoMessageAttribute }) as? NotificationInfoMessageAttribute, attribute.flags.contains(.muted), let currentTitle = title {
+ var isAction = false
+ for media in message.media {
+ if media is TelegramMediaAction {
+ isAction = true
+ break
+ }
+ }
+ if !isAction {
+ title = "\(currentTitle) 🔕"
+ }
}
let textFont = compact ? Font.regular(15.0) : Font.regular(16.0)
diff --git a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift
index 213d5c2789..cec115a682 100644
--- a/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageProfilePhotoSuggestionContentNode.swift
@@ -46,6 +46,7 @@ class ChatMessageProfilePhotoSuggestionContentNode: ChatMessageBubbleContentNode
self.subtitleNode.displaysAsynchronously = false
self.imageNode = TransformImageNode()
+ self.imageNode.contentAnimations = [.subsequentUpdates]
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift
index 070c7142e4..45c05615fd 100644
--- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift
@@ -898,7 +898,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
}
- let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
+ let foregroundColor: UIColor = .clear//bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
let placeholderFrame = updatedImageFrame.insetBy(dx: innerImageInset, dy: innerImageInset)
diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift
index 0c25ebe888..853caa832f 100644
--- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift
+++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift
@@ -15,12 +15,20 @@ import WallpaperBackgroundNode
import PhotoResources
import WallpaperResources
import Markdown
+import RadialStatusNode
+import ComponentFlow
+import AudioTranscriptionPendingIndicatorComponent
class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
private var mediaBackgroundContent: WallpaperBubbleBackgroundNode?
private let mediaBackgroundNode: NavigationBackgroundNode
private let subtitleNode: TextNode
+ private let progressNode: ImmediateTextNode
private let imageNode: TransformImageNode
+ private var transcriptionPendingIndicator: ComponentHostView?
+
+ private var statusOverlayNode: ASDisplayNode
+ private var statusNode: RadialStatusNode
private let buttonNode: HighlightTrackingButtonNode
private let buttonTitleNode: TextNode
@@ -28,6 +36,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
private var absoluteRect: (CGRect, CGSize)?
private let fetchDisposable = MetaDisposable()
+ private let statusDisposable = MetaDisposable()
required init() {
self.mediaBackgroundNode = NavigationBackgroundNode(color: .clear)
@@ -38,7 +47,12 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
self.subtitleNode.isUserInteractionEnabled = false
self.subtitleNode.displaysAsynchronously = false
+ self.progressNode = ImmediateTextNode()
+ self.progressNode.isUserInteractionEnabled = false
+ self.progressNode.displaysAsynchronously = false
+
self.imageNode = TransformImageNode()
+ self.imageNode.contentAnimations = [.subsequentUpdates]
self.buttonNode = HighlightTrackingButtonNode()
self.buttonNode.clipsToBounds = true
@@ -48,15 +62,28 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
self.buttonTitleNode.isUserInteractionEnabled = false
self.buttonTitleNode.displaysAsynchronously = false
+ self.statusOverlayNode = ASDisplayNode()
+ self.statusOverlayNode.alpha = 0.0
+ self.statusOverlayNode.clipsToBounds = true
+ self.statusOverlayNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.4)
+ self.statusOverlayNode.cornerRadius = 50.0
+
+ self.statusNode = RadialStatusNode(backgroundNodeColor: UIColor(rgb: 0x000000, alpha: 0.6))
+ self.statusNode.isUserInteractionEnabled = false
+
super.init()
self.addSubnode(self.mediaBackgroundNode)
self.addSubnode(self.subtitleNode)
+ self.addSubnode(self.progressNode)
self.addSubnode(self.imageNode)
self.addSubnode(self.buttonNode)
self.addSubnode(self.buttonTitleNode)
+ self.addSubnode(self.statusOverlayNode)
+ self.statusOverlayNode.addSubnode(self.statusNode)
+
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
@@ -82,6 +109,25 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
deinit {
self.fetchDisposable.dispose()
+ self.statusDisposable.dispose()
+ }
+
+ override func didLoad() {
+ super.didLoad()
+
+ if #available(iOS 13.0, *) {
+ self.statusOverlayNode.layer.cornerCurve = .circular
+ }
+
+ let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.progressPressed))
+ self.statusOverlayNode.view.addGestureRecognizer(tapGestureRecognizer)
+ }
+
+ @objc private func progressPressed() {
+ guard let item = self.item else {
+ return
+ }
+ item.context.account.pendingPeerMediaUploadManager.cancel(peerId: item.message.id.peerId)
}
override func transitionNode(messageId: MessageId, media: Media) -> (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? {
@@ -134,6 +180,29 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
}
let _ = item.controllerInteraction.openMessage(item.message, .default)
}
+
+ private func updateProgress(_ progress: Float?) {
+ guard let item = self.item else {
+ return
+ }
+ let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
+ if let progress {
+ let progressValue = CGFloat(max(0.027, progress))
+ self.statusNode.transitionToState(.progress(color: .white, lineWidth: nil, value: progressValue, cancelEnabled: true, animateRotation: true))
+ transition.updateAlpha(node: self.statusOverlayNode, alpha: 1.0)
+
+ let primaryTextColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper).primaryText
+ self.progressNode.attributedText = NSAttributedString(string: "\(Int(progress * 100.0))%", font: Font.semibold(13.0), textColor: primaryTextColor, paragraphAlignment: .center)
+ let progressSize = self.progressNode.updateLayout(CGSize(width: 100.0, height: 100.0))
+ let progressFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.subtitleNode.frame.midX - progressSize.width / 2.0), y: self.subtitleNode.frame.maxY + 1.0), size: progressSize)
+ self.progressNode.isHidden = false
+ self.progressNode.frame = progressFrame
+ } else {
+ self.statusNode.transitionToState(.none)
+ transition.updateAlpha(node: self.statusOverlayNode, alpha: 0.0)
+ self.progressNode.isHidden = true
+ }
+ }
override func asyncLayoutContent() -> (_ item: ChatMessageBubbleContentItem, _ layoutConstants: ChatMessageItemLayoutConstants, _ preparePosition: ChatMessageBubblePreparePosition, _ messageSelection: Bool?, _ constrainedSize: CGSize, _ avatarInset: CGFloat) -> (ChatMessageBubbleContentProperties, unboundSize: CGSize?, maxWidth: CGFloat, layout: (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation, Bool, ListViewItemApply?) -> Void))) {
let makeImageLayout = self.imageNode.asyncLayout()
@@ -170,8 +239,14 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
let peerName = item.message.peers[item.message.id.peerId].flatMap { EnginePeer($0).compactDisplayTitle } ?? ""
let text: String
+ var displayTrailingAnimatedDots = false
if fromYou {
- text = item.presentationData.strings.Notification_YouChangedWallpaper
+ if item.message.id.namespace == Namespaces.Message.Local {
+ text = item.presentationData.strings.Notification_YouChangingWallpaper
+ displayTrailingAnimatedDots = true
+ } else {
+ text = item.presentationData.strings.Notification_YouChangedWallpaper
+ }
} else {
text = item.presentationData.strings.Notification_ChangedWallpaper(peerName).string
}
@@ -179,15 +254,24 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
let body = MarkdownAttributeSet(font: Font.regular(13.0), textColor: primaryTextColor)
let bold = MarkdownAttributeSet(font: Font.semibold(13.0), textColor: primaryTextColor)
- let subtitle = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in
+ var subtitle = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in
return nil
}), textAlignment: .center)
+ if displayTrailingAnimatedDots {
+ let modifiedString = NSMutableAttributedString(attributedString: subtitle)
+ modifiedString.append(NSAttributedString(string: "...", font: Font.regular(13.0), textColor: .clear))
+ subtitle = modifiedString
+ }
let (subtitleLayout, subtitleApply) = makeSubtitleLayout(TextNodeLayoutArguments(attributedString: subtitle, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
let (buttonTitleLayout, buttonTitleApply) = makeButtonTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.Notification_Wallpaper_View, font: Font.semibold(15.0), textColor: primaryTextColor, paragraphAlignment: .center), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
- let backgroundSize = CGSize(width: width, height: subtitleLayout.size.height + 140.0 + (fromYou ? 0.0 : 42.0))
+ var textHeight = subtitleLayout.size.height
+ if displayTrailingAnimatedDots {
+ textHeight += subtitleLayout.size.height
+ }
+ let backgroundSize = CGSize(width: width, height: textHeight + 140.0 + (fromYou ? 0.0 : 42.0))
return (backgroundSize.width, { boundingWidth in
return (backgroundSize, { [weak self] animation, synchronousLoads, _ in
@@ -202,14 +286,37 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
let boundingSize = imageSize
var imageSize = boundingSize
let updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>
+ var patternArguments: PatternWallpaperArguments?
switch media.content {
- case let .file(file, _, _, _, _, _):
+ case let .file(file, patternColors, rotation, intensity, _, _):
var representations: [ImageRepresentationWithReference] = file.previewRepresentations.map({ ImageRepresentationWithReference(representation: $0, reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference($0.resource)) })
if file.mimeType == "image/svg+xml" || file.mimeType == "application/x-tgwallpattern" {
representations.append(ImageRepresentationWithReference(representation: .init(dimensions: PixelDimensions(width: 1440, height: 2960), resource: file.resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: false), reference: AnyMediaReference.message(message: MessageReference(item.message), media: file).resourceReference(file.resource)))
+
+ var colors: [UIColor] = []
+ var customPatternColor: UIColor? = nil
+ var bakePatternAlpha: CGFloat = 1.0
+ if let intensity = intensity, intensity < 0 {
+ if patternColors.isEmpty {
+ colors.append(UIColor(rgb: 0xd6e2ee, alpha: 0.5))
+ } else {
+ colors.append(contentsOf: patternColors.map(UIColor.init(rgb:)))
+ }
+ customPatternColor = UIColor(white: 0.0, alpha: 1.0 - CGFloat(abs(intensity)))
+ } else {
+ if patternColors.isEmpty {
+ colors.append(UIColor(rgb: 0xd6e2ee, alpha: 0.5))
+ } else {
+ colors.append(contentsOf: patternColors.map(UIColor.init(rgb:)))
+ }
+ let isLight = UIColor.average(of: patternColors.map(UIColor.init(rgb:))).hsb.b > 0.3
+ customPatternColor = isLight ? .black : .white
+ bakePatternAlpha = CGFloat(intensity ?? 50) / 100.0
+ }
+ patternArguments = PatternWallpaperArguments(colors: colors, rotation: rotation, customPatternColor: customPatternColor, bakePatternAlpha: bakePatternAlpha)
}
if ["image/png", "image/svg+xml", "application/x-tgwallpattern"].contains(file.mimeType) {
- updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .screen)
+ updateImageSignal = patternWallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, representations: representations, mode: .thumbnail)
|> mapToSignal { value -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in
if let value {
return .single(value)
@@ -223,6 +330,11 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
}
updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), representations: representations, alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true)
}
+ case let .image(representations):
+ if let dimensions = representations.last?.dimensions.cgSize {
+ imageSize = dimensions.aspectFilled(boundingSize)
+ }
+ updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: nil, representations: representations.map({ ImageRepresentationWithReference(representation: $0, reference: .standalone(resource: $0.resource)) }), alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true)
case let .color(color):
updateImageSignal = solidColorImage(color)
case let .gradient(colors, rotation):
@@ -233,13 +345,31 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
strongSelf.imageNode.setSignal(updateImageSignal, attemptSynchronously: synchronousLoads)
- let arguments = TransformImageArguments(corners: ImageCorners(radius: boundingSize.width / 2.0), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())
+ let arguments = TransformImageArguments(corners: ImageCorners(radius: boundingSize.width / 2.0), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets(), custom: patternArguments)
let apply = makeImageLayout(arguments)
apply()
strongSelf.imageNode.frame = imageFrame
}
+ let radialStatusSize: CGFloat = 50.0
+ strongSelf.statusOverlayNode.frame = imageFrame
+ strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: floor((imageFrame.width - radialStatusSize) / 2.0), y: floor((imageFrame.height - radialStatusSize) / 2.0)), size: CGSize(width: radialStatusSize, height: radialStatusSize))
+
+ if mediaUpdated {
+ if item.message.id.namespace == Namespaces.Message.Local {
+ strongSelf.statusDisposable.set((item.context.account.pendingPeerMediaUploadManager.uploadProgress(messageId: item.message.id)
+ |> deliverOnMainQueue).start(next: { [weak self] progress in
+ if let strongSelf = self {
+ strongSelf.updateProgress(progress)
+ }
+ }))
+ } else {
+ strongSelf.statusDisposable.set(nil)
+ strongSelf.updateProgress(nil)
+ }
+ }
+
let mediaBackgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - width) / 2.0), y: 0.0), size: backgroundSize)
strongSelf.mediaBackgroundNode.frame = mediaBackgroundFrame
@@ -253,6 +383,34 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
let subtitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - subtitleLayout.size.width) / 2.0) , y: mediaBackgroundFrame.minY + 127.0), size: subtitleLayout.size)
strongSelf.subtitleNode.frame = subtitleFrame
+ if displayTrailingAnimatedDots {
+ let transcriptionPendingIndicator: ComponentHostView
+ if let current = strongSelf.transcriptionPendingIndicator {
+ transcriptionPendingIndicator = current
+ } else {
+ transcriptionPendingIndicator = ComponentHostView()
+ strongSelf.transcriptionPendingIndicator = transcriptionPendingIndicator
+ strongSelf.view.addSubview(transcriptionPendingIndicator)
+ }
+
+ let indicatorComponent: AnyComponent
+ indicatorComponent = AnyComponent(AudioTranscriptionPendingLottieIndicatorComponent(color: primaryTextColor, font: Font.regular(13.0)))
+
+ let indicatorSize = transcriptionPendingIndicator.update(
+ transition: .immediate,
+ component: indicatorComponent,
+ environment: {},
+ containerSize: CGSize(width: 100.0, height: 100.0)
+ )
+
+ transcriptionPendingIndicator.frame = CGRect(origin: CGPoint(x: strongSelf.subtitleNode.frame.midX + subtitleLayout.trailingLineWidth / 2.0 - indicatorSize.width + 2.0 - UIScreenPixel, y: strongSelf.subtitleNode.frame.maxY - indicatorSize.height - 3.0 - UIScreenPixel), size: indicatorSize)
+ } else {
+ if let transcriptionPendingIndicator = strongSelf.transcriptionPendingIndicator {
+ strongSelf.transcriptionPendingIndicator = nil
+ transcriptionPendingIndicator.removeFromSuperview()
+ }
+ }
+
let buttonTitleFrame = CGRect(origin: CGPoint(x: mediaBackgroundFrame.minX + floorToScreenPixels((mediaBackgroundFrame.width - buttonTitleLayout.size.width) / 2.0), y: subtitleFrame.maxY + 18.0), size: buttonTitleLayout.size)
strongSelf.buttonTitleNode.frame = buttonTitleFrame
@@ -299,7 +457,9 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode {
}
override func tapActionAtPoint(_ point: CGPoint, gesture: TapLongTapOrDoubleTapGesture, isEstimating: Bool) -> ChatMessageBubbleContentTapAction {
- if self.mediaBackgroundNode.frame.contains(point) {
+ if self.statusOverlayNode.alpha > 0.0 {
+ return .none
+ } else if self.mediaBackgroundNode.frame.contains(point) {
return .openMessage
} else {
return .none
diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift
index 9ace618b46..a4be07755f 100644
--- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift
+++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift
@@ -863,7 +863,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
controllerInteraction.requestMessageActionCallback(message.id, nil, true, false)
case let .callback(requiresPassword, data):
controllerInteraction.requestMessageActionCallback(message.id, data, false, requiresPassword)
- case let .switchInline(samePeer, query):
+ case let .switchInline(samePeer, query, peerTypes):
var botPeer: Peer?
var found = false
@@ -884,7 +884,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
peerId = message.id.peerId
}
if let botPeer = botPeer, let addressName = botPeer.addressName {
- controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)")
+ controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
}
case .payment:
controllerInteraction.openCheckoutOrReceipt(message.id)
diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift
index 18ac683337..cf5a2c4150 100644
--- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift
+++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift
@@ -274,7 +274,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, openMessageContextActions: { _, _, _, _ in
}, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in
}, navigateToThreadMessage: { _, _, _ in
- }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in
+ }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url, _, _, _ in
self?.openUrl(url)
}, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in
if let strongSelf = self, let navigationController = strongSelf.getNavigationController() {
diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift
index a695150d1c..e92874376b 100644
--- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift
+++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift
@@ -1472,43 +1472,47 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
} else {
transition.animatePosition(layer: self.startButton.layer, from: CGPoint(x: 0.0, y: 80.0), to: CGPoint(), additive: true)
}
- }
-
- if let context = self.context {
- let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
- let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
-
- if let tooltipController = self.tooltipController {
- if self.view.window != nil {
- tooltipController.location = .point(location, .bottom)
- }
- } else {
- let controller = TooltipScreen(account: context.account, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
- return .ignore
- })
- controller.alwaysVisible = true
- self.tooltipController = controller
+ if let context = self.context {
+ let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
+ let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
- let delay: Double
- if case .regular = metrics.widthClass {
- delay = 0.1
+ if let tooltipController = self.tooltipController {
+ if self.view.window != nil {
+ tooltipController.location = .point(location, .bottom)
+ }
} else {
- delay = 0.35
+ let controller = TooltipScreen(account: context.account, sharedContext: context.sharedContext, text: interfaceState.strings.Bot_TapToUse, icon: .downArrows, location: .point(location, .bottom), displayDuration: .infinite, shouldDismissOnTouch: { _ in
+ return .ignore
+ })
+ controller.alwaysVisible = true
+ self.tooltipController = controller
+
+ let delay: Double
+ if case .regular = metrics.widthClass {
+ delay = 0.1
+ } else {
+ delay = 0.35
+ }
+ Queue.mainQueue().after(delay, {
+ let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
+ let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
+ controller.location = .point(location, .bottom)
+ self.interfaceInteraction?.presentControllerInCurrent(controller, nil)
+ })
}
- Queue.mainQueue().after(delay, {
- let absoluteFrame = self.startButton.view.convert(self.startButton.bounds, to: nil)
- let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 1.0), size: CGSize())
- controller.location = .point(location, .bottom)
- self.interfaceInteraction?.presentControllerInCurrent(controller, nil)
- })
+ }
+ } else {
+ if hasMenuButton && !self.animatingTransition {
+ self.menuButton.isHidden = true
}
}
} else if !self.startButton.isHidden {
if hasMenuButton {
self.animateBotButtonOutToMenu(transition: transition)
} else {
- transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), additive: true, completion: { _ in
+ transition.animatePosition(node: self.startButton, to: CGPoint(x: 0.0, y: 80.0), removeOnCompletion: false, additive: true, completion: { _ in
self.startButton.isHidden = true
+ self.startButton.layer.removeAllAnimations()
})
}
}
diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift
index dd015c8b58..f53617080e 100644
--- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift
+++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift
@@ -20,27 +20,7 @@ import TooltipUI
import AnimatedStickerNode
import TelegramAnimatedStickerNode
import ShimmerEffect
-
-private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
- return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
- context.clear(CGRect(origin: CGPoint(), size: size))
-
- context.setFillColor(UIColor(rgb: 0x808084, alpha: 0.1).cgColor)
- context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
-
- context.setLineWidth(2.0)
- context.setLineCap(.round)
- context.setStrokeColor(theme.actionSheet.inputClearButtonColor.cgColor)
-
- context.move(to: CGPoint(x: 10.0, y: 10.0))
- context.addLine(to: CGPoint(x: 20.0, y: 20.0))
- context.strokePath()
-
- context.move(to: CGPoint(x: 20.0, y: 10.0))
- context.addLine(to: CGPoint(x: 10.0, y: 20.0))
- context.strokePath()
- })
-}
+import WebUI
private struct ThemeSettingsThemeEntry: Comparable, Identifiable {
let index: Int
@@ -554,9 +534,10 @@ final class ChatThemeScreen: ViewController {
private let animatedEmojiStickers: [String: [StickerPackItem]]
private let initiallySelectedEmoticon: String?
private let peerName: String
+ let canResetWallpaper: Bool
private let previewTheme: (String?, Bool?) -> Void
fileprivate let changeWallpaper: () -> Void
- fileprivate let changeColor: () -> Void
+ fileprivate let resetWallpaper: () -> Void
private let completion: (String?) -> Void
private var presentationData: PresentationData
@@ -578,9 +559,10 @@ final class ChatThemeScreen: ViewController {
animatedEmojiStickers: [String: [StickerPackItem]],
initiallySelectedEmoticon: String?,
peerName: String,
+ canResetWallpaper: Bool,
previewTheme: @escaping (String?, Bool?) -> Void,
changeWallpaper: @escaping () -> Void,
- changeColor: @escaping () -> Void,
+ resetWallpaper: @escaping () -> Void,
completion: @escaping (String?) -> Void
) {
self.context = context
@@ -588,15 +570,18 @@ final class ChatThemeScreen: ViewController {
self.animatedEmojiStickers = animatedEmojiStickers
self.initiallySelectedEmoticon = initiallySelectedEmoticon
self.peerName = peerName
+ self.canResetWallpaper = canResetWallpaper
self.previewTheme = previewTheme
self.changeWallpaper = changeWallpaper
- self.changeColor = changeColor
+ self.resetWallpaper = resetWallpaper
self.completion = completion
super.init(navigationBarPresentationData: nil)
self.statusBar.statusBarStyle = .Ignore
+ self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
+
self.blocksBackgroundWhenInOverlay = true
self.presentationDataDisposable = (updatedPresentationData.signal
@@ -634,20 +619,20 @@ final class ChatThemeScreen: ViewController {
guard let strongSelf = self else {
return
}
- strongSelf.dismiss()
+ strongSelf.dismiss(animated: true)
if strongSelf.initiallySelectedEmoticon == nil && emoticon == nil {
} else {
strongSelf.completion(emoticon)
}
}
self.controllerNode.dismiss = { [weak self] in
- self?.presentingViewController?.dismiss(animated: false, completion: nil)
+ self?.dismiss(animated: false)
}
self.controllerNode.cancel = { [weak self] in
guard let strongSelf = self else {
return
}
- strongSelf.dismiss()
+ strongSelf.dismiss(animated: true)
strongSelf.previewTheme(nil, nil)
}
}
@@ -667,15 +652,22 @@ final class ChatThemeScreen: ViewController {
}
}
- override public func dismiss(completion: (() -> Void)? = nil) {
+ override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
self.forEachController({ controller in
if let controller = controller as? TooltipScreen {
controller.dismiss()
}
return true
})
-
- self.controllerNode.animateOut(completion: completion)
+
+ if flag {
+ self.controllerNode.animateOut(completion: {
+ super.dismiss(animated: flag, completion: completion)
+ completion?()
+ })
+ } else {
+ super.dismiss(animated: flag, completion: completion)
+ }
self.dismissed?()
}
@@ -733,7 +725,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
private let contentBackgroundNode: ASDisplayNode
private let titleNode: ASTextNode
private let textNode: ImmediateTextNode
- private let cancelButton: HighlightableButtonNode
+ private let cancelButtonNode: WebAppCancelButtonNode
private let switchThemeButton: HighlightTrackingButtonNode
private let animationContainerNode: ASDisplayNode
private var animationNode: AnimationNode
@@ -818,13 +810,13 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.contentBackgroundNode.backgroundColor = backgroundColor
self.titleNode = ASTextNode()
- self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(16.0), textColor: textColor)
+ self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Title, font: Font.semibold(17.0), textColor: textColor)
self.textNode = ImmediateTextNode()
- self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(12.0), textColor: secondaryTextColor)
+ self.textNode.attributedText = NSAttributedString(string: self.presentationData.strings.Conversation_Theme_Subtitle(peerName).string, font: Font.regular(15.0), textColor: secondaryTextColor)
+ self.textNode.isHidden = true
- self.cancelButton = HighlightableButtonNode()
- self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal)
+ self.cancelButtonNode = WebAppCancelButtonNode(theme: self.presentationData.theme, strings: self.presentationData.strings)
self.switchThemeButton = HighlightTrackingButtonNode()
self.animationContainerNode = ASDisplayNode()
@@ -833,7 +825,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.animationNode = AnimationNode(animation: self.isDarkAppearance ? "anim_sun_reverse" : "anim_sun", colors: iconColors(theme: self.presentationData.theme), scale: 1.0)
self.animationNode.isUserInteractionEnabled = false
- self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
+ self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 11.0, gloss: false)
self.otherButton = HighlightableButtonNode()
@@ -860,7 +852,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.backgroundNode.addSubnode(self.effectNode)
self.backgroundNode.addSubnode(self.contentBackgroundNode)
self.contentContainerNode.addSubnode(self.titleNode)
- self.contentContainerNode.addSubnode(self.textNode)
+ self.buttonsContentContainerNode.addSubnode(self.textNode)
self.buttonsContentContainerNode.addSubnode(self.doneButton)
self.buttonsContentContainerNode.addSubnode(self.otherButton)
@@ -868,10 +860,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
self.animationContainerNode.addSubnode(self.animationNode)
self.topContentContainerNode.addSubnode(self.switchThemeButton)
self.topContentContainerNode.addSubnode(self.listNode)
- self.topContentContainerNode.addSubnode(self.cancelButton)
+ self.topContentContainerNode.addSubnode(self.cancelButtonNode)
self.switchThemeButton.addTarget(self, action: #selector(self.switchThemePressed), forControlEvents: .touchUpInside)
- self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
+ self.cancelButtonNode.buttonNode.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
self.doneButton.pressed = { [weak self] in
if let strongSelf = self {
strongSelf.doneButton.isUserInteractionEnabled = false
@@ -889,6 +881,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
return
}
+ let selectedEmoticon = selectedEmoticon?.strippedEmoji
+
let isFirstTime = strongSelf.entries == nil
let presentationData = strongSelf.presentationData
@@ -898,7 +892,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
guard let emoticon = theme.emoticon else {
continue
}
- entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
+ entries.append(ThemeSettingsThemeEntry(index: entries.count, emoticon: emoticon, emojiFile: animatedEmojiStickers[emoticon]?.first?.file, themeReference: .cloud(PresentationCloudTheme(theme: theme, resolvedWallpaper: nil, creatorAccountId: nil)), nightMode: isDarkAppearance, selected: selectedEmoticon == theme.emoticon?.strippedEmoji, theme: presentationData.theme, strings: presentationData.strings, wallpaper: nil))
}
let action: (String?) -> Void = { [weak self] emoticon in
@@ -956,6 +950,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
}
}
}
+
+ self.updateCancelButton()
}
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
@@ -981,7 +977,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
var scrollToItem: ListViewScrollToItem?
if !self.initialized {
if let index = transition.entries.firstIndex(where: { entry in
- return entry.emoticon == self.initiallySelectedEmoticon
+ return entry.emoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji
}) {
scrollToItem = ListViewScrollToItem(index: index, position: .bottom(-57.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Down)
self.initialized = true
@@ -1004,6 +1000,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
UIView.transition(with: self.buttonsContentContainerNode.view, duration: ChatThemeScreen.themeCrossfadeDuration, options: [.transitionCrossDissolve, .curveLinear]) {
self.updateButtons()
}
+ self.updateCancelButton()
self.skipButtonsUpdate = false
self.themeSelectionsCount += 1
@@ -1014,18 +1011,16 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
private func updateButtons() {
let doneButtonTitle: String
- let otherButtonTitle: String
var accentButtonTheme = true
- if self.selectedEmoticon == self.initiallySelectedEmoticon {
+ var otherIsEnabled = false
+ if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji {
doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper
- otherButtonTitle = self.presentationData.strings.Conversation_Theme_SetColorWallpaper
+ otherIsEnabled = self.controller?.canResetWallpaper == true
accentButtonTheme = false
} else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil {
doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset
- otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions
} else {
doneButtonTitle = self.presentationData.strings.Conversation_Theme_Apply
- otherButtonTitle = self.presentationData.strings.Conversation_Theme_OtherOptions
}
let buttonTheme: SolidRoundedButtonTheme
@@ -1040,7 +1035,25 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
}
self.doneButton.updateTheme(buttonTheme)
- self.otherButton.setTitle(otherButtonTitle, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal)
+ self.otherButton.setTitle(self.presentationData.strings.Conversation_Theme_ResetWallpaper, with: Font.regular(17.0), with: self.presentationData.theme.actionSheet.destructiveActionTextColor, for: .normal)
+ self.otherButton.isHidden = !otherIsEnabled
+ self.textNode.isHidden = !accentButtonTheme || self.controller?.canResetWallpaper == false
+
+ if let (layout, navigationBarHeight) = self.containerLayout {
+ self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
+ }
+ }
+
+ private func updateCancelButton() {
+ var cancelButtonState: WebAppCancelButtonNode.State = .cancel
+ if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji {
+
+ } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil {
+ cancelButtonState = .back
+ } else {
+ cancelButtonState = .back
+ }
+ self.cancelButtonNode.setState(cancelButtonState, animated: true)
}
private var switchThemeIconAnimator: DisplayLinkAnimator?
@@ -1051,14 +1064,20 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let previousTheme = self.presentationData.theme
self.presentationData = presentationData
- self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(16.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
- self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(12.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
+ self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.semibold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor)
+ self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(15.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor)
if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
}
- self.cancelButton.setImage(closeButtonImage(theme: self.presentationData.theme), for: .normal)
+ if let animatingCrossFade = self.animatingCrossFade {
+ Queue.mainQueue().after(!animatingCrossFade ? ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor() : 0.0, {
+ self.cancelButtonNode.setTheme(presentationData.theme, animated: true)
+ })
+ } else {
+ self.cancelButtonNode.setTheme(presentationData.theme, animated: false)
+ }
let previousIconColors = iconColors(theme: previousTheme)
let newIconColors = iconColors(theme: self.presentationData.theme)
@@ -1095,19 +1114,28 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
}
@objc func cancelButtonPressed() {
- self.cancel?()
+ if self.cancelButtonNode.state == .back {
+ self.setEmoticon(self.initiallySelectedEmoticon)
+ } else {
+ self.cancel?()
+ }
}
@objc func otherButtonPressed() {
- if self.selectedEmoticon != self.initiallySelectedEmoticon {
+ if self.selectedEmoticon?.strippedEmoji != self.initiallySelectedEmoticon?.strippedEmoji {
self.setEmoticon(self.initiallySelectedEmoticon)
} else {
- self.controller?.changeColor()
+ if self.controller?.canResetWallpaper == true {
+ self.controller?.resetWallpaper()
+ self.cancelButtonPressed()
+ } else {
+ self.cancelButtonPressed()
+ }
}
}
func dimTapped() {
- if self.selectedEmoticon == self.initiallySelectedEmoticon {
+ if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji {
self.cancelButtonPressed()
} else {
let alertController = textAlertController(context: self.context, updatedPresentationData: (self.presentationData, .single(self.presentationData)), title: nil, text: self.presentationData.strings.Conversation_Theme_DismissAlert, actions: [TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Conversation_Theme_DismissAlertApply, action: { [weak self] in
@@ -1142,6 +1170,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
}
}
+ private var animatingCrossFade: Bool?
private func animateCrossfade(animateIcon: Bool) {
if animateIcon, let snapshotView = self.animationNode.view.snapshotView(afterScreenUpdates: false) {
snapshotView.frame = self.animationNode.frame
@@ -1152,6 +1181,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
})
}
+ self.animatingCrossFade = animateIcon
Queue.mainQueue().after(ChatThemeScreen.themeCrossfadeDelay * UIView.animationDurationFactor()) {
if let effectView = self.effectNode.view as? UIVisualEffectView {
UIView.animate(withDuration: ChatThemeScreen.themeCrossfadeDuration, delay: 0.0, options: .curveLinear) {
@@ -1163,6 +1193,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let previousColor = self.contentBackgroundNode.backgroundColor ?? .clear
self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.itemBackgroundColor
self.contentBackgroundNode.layer.animate(from: previousColor.cgColor, to: (self.contentBackgroundNode.backgroundColor ?? .clear).cgColor, keyPath: "backgroundColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: ChatThemeScreen.themeCrossfadeDuration)
+
+ self.animatingCrossFade = nil
}
if let snapshotView = self.contentContainerNode.view.snapshotView(afterScreenUpdates: false) {
@@ -1229,7 +1261,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
if let strongSelf = self, count < 2 && currentTimestamp > timestamp + 24 * 60 * 60 {
strongSelf.displayedPreviewTooltip = true
- strongSelf.present?(TooltipScreen(account: strongSelf.context.account, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLight(strongSelf.peerName).string : strongSelf.presentationData.strings.Conversation_Theme_PreviewDark(strongSelf.peerName).string, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
+ strongSelf.present?(TooltipScreen(account: strongSelf.context.account, sharedContext: strongSelf.context.sharedContext, text: isDark ? strongSelf.presentationData.strings.Conversation_Theme_PreviewLightShort : strongSelf.presentationData.strings.Conversation_Theme_PreviewDarkShort, style: .default, icon: nil, location: .point(frame.offsetBy(dx: 3.0, dy: 6.0), .bottom), displayDuration: .custom(3.0), inset: 3.0, shouldDismissOnTouch: { _ in
return .dismiss(consume: false)
}))
@@ -1294,7 +1326,10 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
let titleHeight: CGFloat = 54.0
- let contentHeight = titleHeight + bottomInset + 188.0 + 50.0
+ var contentHeight = titleHeight + bottomInset + 168.0
+ if self.controller?.canResetWallpaper == true {
+ contentHeight += 50.0
+ }
let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0)
@@ -1313,29 +1348,38 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let titleSize = self.titleNode.measure(CGSize(width: width - 90.0, height: titleHeight))
- let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 11.0 + UIScreenPixel), size: titleSize)
+ let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 18.0 + UIScreenPixel), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
- let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight))
- let textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: 31.0), size: textSize)
- transition.updateFrame(node: self.textNode, frame: textFrame)
-
let switchThemeSize = CGSize(width: 44.0, height: 44.0)
- let switchThemeFrame = CGRect(origin: CGPoint(x: 3.0, y: 6.0), size: switchThemeSize)
+ let switchThemeFrame = CGRect(origin: CGPoint(x: contentFrame.width - switchThemeSize.width - 3.0, y: 6.0), size: switchThemeSize)
transition.updateFrame(node: self.switchThemeButton, frame: switchThemeFrame)
transition.updateFrame(node: self.animationContainerNode, frame: switchThemeFrame.insetBy(dx: 9.0, dy: 9.0))
transition.updateFrameAsPositionAndBounds(node: self.animationNode, frame: CGRect(origin: .zero, size: self.animationContainerNode.frame.size))
- let cancelSize = CGSize(width: 44.0, height: 44.0)
- let cancelFrame = CGRect(origin: CGPoint(x: contentFrame.width - cancelSize.width - 3.0, y: 6.0), size: cancelSize)
- transition.updateFrame(node: self.cancelButton, frame: cancelFrame)
-
+ let cancelSize = self.cancelButtonNode.calculateSizeThatFits(CGSize(width: layout.size.width, height: 56.0))
+ let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 0.0), size: cancelSize)
+ transition.updateFrame(node: self.cancelButtonNode, frame: cancelFrame)
+
let buttonInset: CGFloat = 16.0
let doneButtonHeight = self.doneButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
- transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - 50.0 - insets.bottom - 6.0, width: contentFrame.width, height: doneButtonHeight))
+ var doneY = contentHeight - doneButtonHeight - 2.0 - insets.bottom
+ if self.controller?.canResetWallpaper == true {
+ doneY = contentHeight - doneButtonHeight - 52.0 - insets.bottom
+ }
+ transition.updateFrame(node: self.doneButton, frame: CGRect(x: buttonInset, y: doneY, width: contentFrame.width, height: doneButtonHeight))
- let colorButtonSize = self.otherButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude))
- transition.updateFrame(node: self.otherButton, frame: CGRect(origin: CGPoint(x: floor((contentFrame.width - colorButtonSize.width) / 2.0), y: contentHeight - colorButtonSize.height - insets.bottom - 6.0 - 9.0), size: colorButtonSize))
+ let otherButtonSize = self.otherButton.measure(CGSize(width: contentFrame.width - buttonInset * 2.0, height: .greatestFiniteMagnitude))
+ self.otherButton.frame = CGRect(origin: CGPoint(x: floor((contentFrame.width - otherButtonSize.width) / 2.0), y: contentHeight - otherButtonSize.height - insets.bottom - 15.0), size: otherButtonSize)
+
+ let textSize = self.textNode.updateLayout(CGSize(width: width - 90.0, height: titleHeight))
+ let textFrame: CGRect
+ if self.controller?.canResetWallpaper == true {
+ textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: contentHeight - textSize.height - insets.bottom - 17.0), size: textSize)
+ } else {
+ textFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - textSize.width) / 2.0), y: contentHeight - textSize.height - insets.bottom - 15.0), size: textSize)
+ }
+ transition.updateFrame(node: self.textNode, frame: textFrame)
transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame)
transition.updateFrame(node: self.topContentContainerNode, frame: contentContainerFrame)
@@ -1348,7 +1392,7 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega
let contentSize = CGSize(width: contentFrame.width, height: 120.0)
self.listNode.bounds = CGRect(x: 0.0, y: 0.0, width: contentSize.height, height: contentSize.width)
- self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight + 6.0)
+ self.listNode.position = CGPoint(x: contentSize.width / 2.0, y: contentSize.height / 2.0 + titleHeight - 4.0)
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous], scrollToItem: nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets(size: CGSize(width: contentSize.height, height: contentSize.width), insets: listInsets, duration: 0.0, curve: .Default(duration: nil)), stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
}
}
diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift
index be5e680b97..b2170a46ff 100644
--- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift
+++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift
@@ -205,8 +205,8 @@ private func fetchCachedStickerAJpegRepresentation(account: Account, resource: M
}, scale: 1.0)
if let alphaImage = alphaImage, let colorDestination = CGImageDestinationCreateWithData(colorData as CFMutableData, kUTTypeJPEG, 1, nil), let alphaDestination = CGImageDestinationCreateWithData(alphaData as CFMutableData, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
- CGImageDestinationSetProperties(alphaDestination, [:] as CFDictionary)
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
+ CGImageDestinationSetProperties(alphaDestination, NSDictionary() as CFDictionary)
let colorQuality: Float
let alphaQuality: Float
@@ -270,7 +270,7 @@ private func fetchCachedScaledImageRepresentation(resource: MediaResource, resou
}, scale: 1.0)!
if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
let colorQuality: Float = 0.5
@@ -330,7 +330,7 @@ private func fetchCachedVideoFirstFrameRepresentation(account: Account, resource
let url = URL(fileURLWithPath: path)
if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
let colorQuality: Float = 0.6
@@ -371,7 +371,7 @@ private func fetchCachedScaledVideoFirstFrameRepresentation(account: Account, re
}, scale: 1.0)!
if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
let colorQuality: Float = 0.5
@@ -398,9 +398,9 @@ private func fetchCachedBlurredWallpaperRepresentation(resource: MediaResource,
let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))"
let url = URL(fileURLWithPath: path)
- if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
-
+ if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
+
let colorQuality: Float = 0.5
let options = NSMutableDictionary()
@@ -447,8 +447,8 @@ private func fetchCachedBlurredWallpaperRepresentation(account: Account, resourc
let path = NSTemporaryDirectory() + "\(Int64.random(in: Int64.min ... Int64.max))"
let url = URL(fileURLWithPath: path)
- if let colorImage = blurredImage(image, radius: 20.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
+ if let colorImage = blurredImage(image, radius: 30.0), let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
let colorQuality: Float = 0.5
@@ -491,7 +491,7 @@ private func fetchCachedAlbumArtworkRepresentation(account: Account, resource: M
})!
if let colorDestination = CGImageDestinationCreateWithURL(url as CFURL, kUTTypeJPEG, 1, nil) {
- CGImageDestinationSetProperties(colorDestination, [:] as CFDictionary)
+ CGImageDestinationSetProperties(colorDestination, NSDictionary() as CFDictionary)
let colorQuality: Float = 0.5
diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift
index 396aba0eab..4776b1c213 100644
--- a/submodules/TelegramUI/Sources/NavigateToChatController.swift
+++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift
@@ -132,8 +132,8 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam
if let attachBotStart = params.attachBotStart {
controller.presentAttachmentBot(botId: attachBotStart.botId, payload: attachBotStart.payload, justInstalled: attachBotStart.justInstalled)
}
- if let botAppStart = params.botAppStart {
- controller.presentBotApp(botApp: botAppStart.botApp, payload: botAppStart.payload)
+ if let botAppStart = params.botAppStart, case let .peer(peer) = params.chatLocation {
+ controller.presentBotApp(botApp: botAppStart.botApp, botPeer: peer, payload: botAppStart.payload)
}
} else {
controller = ChatControllerImpl(context: params.context, chatLocation: params.chatLocation.asChatLocation, chatLocationContextHolder: params.chatLocationContextHolder, subject: params.subject, botStart: params.botStart, attachBotStart: params.attachBotStart, botAppStart: params.botAppStart, peekData: params.peekData, peerNearbyData: params.peerNearbyData, chatListFilter: params.chatListFilter, chatNavigationStack: params.chatNavigationStack)
diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift
index 49e8fdfdc4..950bfccdfe 100644
--- a/submodules/TelegramUI/Sources/NotificationContentContext.swift
+++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift
@@ -140,7 +140,7 @@ public final class NotificationViewControllerImpl {
return nil
})
- sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
+ sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
presentationDataPromise.set(sharedAccountContext!.presentationData)
}
diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift
index e34778b835..bf8b2a44f6 100644
--- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift
+++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift
@@ -324,7 +324,14 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
}
})
} else {
- let query = to.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789").inverted)
+ let _ = (context.engine.peers.resolvePeerByPhone(phone: to)
+ |> deliverOnMainQueue).start(next: { peer in
+ if let peer = peer {
+ context.sharedContext.applicationBindings.dismissNativeController()
+ continueWithPeer(peer.id)
+ }
+ })
+ /*let query = to.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789").inverted)
let _ = (context.account.postbox.searchContacts(query: query)
|> deliverOnMainQueue).start(next: { (peers, _) in
for case let peer as TelegramUser in peers {
@@ -334,7 +341,7 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
break
}
}
- })
+ })*/
}
} else {
if let url = url, !url.isEmpty {
diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift
index 9ddce665eb..5c295155b3 100644
--- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift
+++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift
@@ -97,7 +97,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
return false
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _ in
- }, activateSwitchInline: { _, _ in
+ }, activateSwitchInline: { _, _, _ in
}, openUrl: { _, _, _, _ in
}, shareCurrentLocation: {
}, shareAccountContact: {
diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift
index 3ae82617db..27f6840b40 100644
--- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift
+++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift
@@ -2678,7 +2678,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return false
}, requestMessageActionCallback: { _, _, _, _ in
}, requestMessageActionUrlAuth: { _, _ in
- }, activateSwitchInline: { _, _ in
+ }, activateSwitchInline: { _, _, _ in
}, openUrl: { [weak self] url, concealed, external, _ in
guard let strongSelf = self else {
return
@@ -3906,6 +3906,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
var previousAbout: String?
var currentAbout: String?
+ var previousIsBlocked: Bool?
+ var currentIsBlocked: Bool?
+
var previousPhotoIsPersonal: Bool?
var currentPhotoIsPersonal: Bool?
if let previousUser = previousData?.peer as? TelegramUser {
@@ -3932,6 +3935,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
currentVideoCallsAvailable = cachedData.videoCallsAvailable
previousAbout = previousCachedData.about
currentAbout = cachedData.about
+ previousIsBlocked = previousCachedData.isBlocked
+ currentIsBlocked = cachedData.isBlocked
}
if self.isSettings {
@@ -3954,6 +3959,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
if let previousPhotoIsPersonal, let currentPhotoIsPersonal, previousPhotoIsPersonal != currentPhotoIsPersonal {
infoUpdated = true
}
+ if let previousIsBlocked, let currentIsBlocked, previousIsBlocked != currentIsBlocked {
+ infoUpdated = true
+ }
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: self.didSetReady && (membersUpdated || infoUpdated) ? .animated(duration: 0.3, curve: .spring) : .immediate)
}
}
@@ -4091,7 +4099,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
if let mediaReference = mediaReference, let peer = message.peers[message.id.peerId] {
- legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
+ legacyMediaEditor(context: strongSelf.context, peer: peer, threadTitle: message.associatedThreadInfo?.title, media: mediaReference, mode: .draw, initialCaption: NSAttributedString(), snapshots: snapshots, transitionCompletion: {
transitionCompletion()
}, getCaptionPanelView: {
return nil
@@ -4755,8 +4763,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}
if let user = peer as? TelegramUser {
- if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil {
- items.append(.action(ContextMenuActionItem(text: "Change Background", icon: { theme in
+ if user.botInfo == nil && strongSelf.data?.encryptionKeyFingerprint == nil && !user.isDeleted {
+ items.append(.action(ContextMenuActionItem(text: presentationData.strings.UserInfo_ChangeWallpaper, icon: { theme in
generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/ApplyTheme"), color: theme.contextMenu.primaryColor)
}, action: { _, f in
f(.dismissWithoutContent)
@@ -5396,6 +5404,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
case .leave:
self.openLeavePeer(delete: false)
case .stop:
+ self.controller?.present(UndoOverlayController(presentationData: self.presentationData, content: .universal(animation: "anim_banned", scale: 0.066, colors: [:], title: self.presentationData.strings.PeerInfo_BotBlockedTitle, text: self.presentationData.strings.PeerInfo_BotBlockedText, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
self.updateBlocked(block: true)
}
}
@@ -8467,7 +8476,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return
}
let buttonFrame = buttonNode.view.convert(buttonNode.bounds, to: self.view)
- controller.present(TooltipScreen(account: self.context.account, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in
+ controller.present(TooltipScreen(account: self.context.account, sharedContext: self.context.sharedContext, text: self.presentationData.strings.SharedMedia_CalendarTooltip, style: .default, icon: .none, location: .point(buttonFrame.insetBy(dx: 0.0, dy: 5.0), .top), shouldDismissOnTouch: { point in
return .dismiss(consume: false)
}), in: .current)
}
diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift
index 53e0881675..8888465a23 100644
--- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift
+++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift
@@ -241,7 +241,7 @@ public class ShareRootControllerImpl {
return nil
})
- let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
+ let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider(), deviceModelName: nil, useBetaFeatures: self.initializationData.useBetaFeatures, isICloudEnabled: false), hasInAppPurchases: false, rootPath: rootPath, legacyBasePath: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), firebaseSecretStream: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in }, appDelegate: nil)
presentationDataPromise.set(sharedContext.presentationData)
internalContext = InternalContext(sharedContext: sharedContext)
globalInternalContext = internalContext
diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift
index 2d8bcb82c5..3c756c16bb 100644
--- a/submodules/TelegramUI/Sources/SharedAccountContext.swift
+++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift
@@ -162,6 +162,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public let currentMediaInputSettings: Atomic
private var mediaInputSettingsDisposable: Disposable?
+ public let currentMediaDisplaySettings: Atomic
+ private var mediaDisplaySettingsDisposable: Disposable?
+
public let currentStickerSettings: Atomic
private var stickerSettingsDisposable: Disposable?
@@ -241,6 +244,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.currentAutomaticMediaDownloadSettings = initialPresentationDataAndSettings.automaticMediaDownloadSettings
self.currentAutodownloadSettings = Atomic(value: initialPresentationDataAndSettings.autodownloadSettings)
self.currentMediaInputSettings = Atomic(value: initialPresentationDataAndSettings.mediaInputSettings)
+ self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings)
self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings)
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
@@ -359,6 +363,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
})
+ self.mediaDisplaySettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.mediaDisplaySettings])
+ |> deliverOnMainQueue).start(next: { [weak self] sharedData in
+ if let strongSelf = self {
+ if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.mediaDisplaySettings]?.get(MediaDisplaySettings.self) {
+ let _ = strongSelf.currentMediaDisplaySettings.swap(settings)
+ }
+ }
+ })
+
self.stickerSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings])
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
if let strongSelf = self {
@@ -895,6 +908,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.currentAutodownloadSettingsDisposable.dispose()
self.inAppNotificationSettingsDisposable?.dispose()
self.mediaInputSettingsDisposable?.dispose()
+ self.mediaDisplaySettingsDisposable?.dispose()
self.callDisposable?.dispose()
self.groupCallDisposable?.dispose()
self.callStateDisposable?.dispose()
@@ -1443,7 +1457,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
clickThroughMessage?()
}, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in
return false
- }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
+ }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in
}, presentController: { _, _ in
}, presentControllerInCurrent: { _, _ in
}, navigationController: {
diff --git a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift b/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift
index 04ce3d01be..0a232c2355 100644
--- a/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift
+++ b/submodules/TelegramUI/Sources/WallpaperPreviewMedia.swift
@@ -5,6 +5,7 @@ import TelegramCore
enum WallpaperPreviewMediaContent: Equatable {
case file(file: TelegramMediaFile, colors: [UInt32], rotation: Int32?, intensity: Int32?, Bool, Bool)
+ case image(representations: [TelegramMediaImageRepresentation])
case color(UIColor)
case gradient([UInt32], Int32?)
case themeSettings(TelegramThemeSettings)
@@ -55,6 +56,8 @@ extension WallpaperPreviewMedia {
self.init(content: .gradient(gradient.colors, gradient.settings.rotation))
case let .file(file):
self.init(content: .file(file: file.file, colors: file.settings.colors, rotation: file.settings.rotation, intensity: file.settings.intensity, false, false))
+ case let .image(representations, _):
+ self.init(content: .image(representations: representations))
default:
return nil
}
diff --git a/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift
new file mode 100644
index 0000000000..2e59eab787
--- /dev/null
+++ b/submodules/TelegramUIPreferences/Sources/MediaDisplaySettings.swift
@@ -0,0 +1,50 @@
+import Foundation
+import Postbox
+import TelegramCore
+import SwiftSignalKit
+
+public struct MediaDisplaySettings: Codable, Equatable {
+ public let showNextMediaOnTap: Bool
+
+ public static var defaultSettings: MediaDisplaySettings {
+ return MediaDisplaySettings(showNextMediaOnTap: true)
+ }
+
+ public init(showNextMediaOnTap: Bool) {
+ self.showNextMediaOnTap = showNextMediaOnTap
+ }
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: StringCodingKey.self)
+
+ self.showNextMediaOnTap = (try container.decode(Int32.self, forKey: "showNextMediaOnTap")) != 0
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: StringCodingKey.self)
+
+ try container.encode((self.showNextMediaOnTap ? 1 : 0) as Int32, forKey: "showNextMediaOnTap")
+ }
+
+ public static func ==(lhs: MediaDisplaySettings, rhs: MediaDisplaySettings) -> Bool {
+ return lhs.showNextMediaOnTap == rhs.showNextMediaOnTap
+ }
+
+ public func withUpdatedShowNextMediaOnTap(_ showNextMediaOnTap: Bool) -> MediaDisplaySettings {
+ return MediaDisplaySettings(showNextMediaOnTap: showNextMediaOnTap)
+ }
+}
+
+public func updateMediaDisplaySettingsInteractively(accountManager: AccountManager, _ f: @escaping (MediaDisplaySettings) -> MediaDisplaySettings) -> Signal {
+ return accountManager.transaction { transaction -> Void in
+ transaction.updateSharedData(ApplicationSpecificSharedDataKeys.mediaDisplaySettings, { entry in
+ let currentSettings: MediaDisplaySettings
+ if let entry = entry?.get(MediaDisplaySettings.self) {
+ currentSettings = entry
+ } else {
+ currentSettings = MediaDisplaySettings.defaultSettings
+ }
+ return PreferencesEntry(f(currentSettings))
+ })
+ }
+}
diff --git a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift
index f53c296037..5ac78a3560 100644
--- a/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift
+++ b/submodules/TelegramUIPreferences/Sources/PostboxKeys.swift
@@ -39,6 +39,7 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
case intentsSettings = 17
case translationSettings = 18
case drawingSettings = 19
+ case mediaDisplaySettings = 20
}
public struct ApplicationSpecificSharedDataKeys {
@@ -62,6 +63,7 @@ public struct ApplicationSpecificSharedDataKeys {
public static let intentsSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.intentsSettings.rawValue)
public static let translationSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.translationSettings.rawValue)
public static let drawingSettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.drawingSettings.rawValue)
+ public static let mediaDisplaySettings = applicationSpecificPreferencesKey(ApplicationSpecificSharedDataKeyValues.mediaDisplaySettings.rawValue)
}
private enum ApplicationSpecificItemCacheCollectionIdValues: Int8 {
diff --git a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift
index d58ba3931b..c972da599d 100644
--- a/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift
+++ b/submodules/TelegramUniversalVideoContent/Sources/YoutubeEmbedImplementation.swift
@@ -653,10 +653,10 @@ public final class YoutubeEmbedFramePreview: FramePreview {
let frame: Int32 = globalFrame % framesOnStoryboard
let num: Int32 = Int32(floor(Double(globalFrame) / Double(framesOnStoryboard)))
- let url = storyboardUrl(spec: storyboardSpec, sizeIndex: bestSize.0, num: num)
+ let url = strongSelf.storyboardUrl(spec: storyboardSpec, sizeIndex: bestSize.0, num: num)
strongSelf.framePipe.putNext(.waitingForData)
- strongSelf.currentFrameDisposable.set(youtubeEmbedStoryboardImage(account: strongSelf.context.account, resource: YoutubeEmbedStoryboardMediaResource(videoId: youtubeImpl.videoId, storyboardId: num, url: url), frame: frame, size: bestSize.1).start(next: { [weak self] image in
+ strongSelf.currentFrameDisposable.set(youtubeEmbedStoryboardImage(account: strongSelf.context.account, resource: YoutubeEmbedStoryboardMediaResource(videoId: youtubeImpl.videoId, storyboardId: num, url: url), frame: frame, size: bestSize.1).start(next: { image in
if let strongSelf = self {
if let image = image {
strongSelf.framePipe.putNext(.image(image))
diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift
index ddd3f5e954..bb60b75c2b 100644
--- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift
+++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift
@@ -933,22 +933,58 @@ public final class OngoingCallContext {
}
}
- let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: Data(), key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, allowTCP: enableTCP, enableStunMarking: enableStunMarking, logPath: logPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in
- queue.async {
- guard let strongSelf = self else {
- return
- }
- if let signalingConnectionManager = strongSelf.signalingConnectionManager {
- signalingConnectionManager.with { impl in
- impl.send(payloadData: data)
+ var directConnection: OngoingCallDirectConnection?
+ if version == "9.0.0" {
+ if #available(iOS 12.0, *) {
+ for connection in filteredConnections {
+ if connection.username == "reflector" && connection.reflectorId == 1 && !connection.hasTcp && connection.hasTurn {
+ directConnection = CallDirectConnectionImpl(host: connection.ip, port: Int(connection.port), peerTag: dataWithHexString(connection.password))
+ break
}
}
-
- if let callSessionManager = callSessionManager {
- callSessionManager.sendSignalingData(internalId: internalId, data: data)
- }
}
- }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "", audioDevice: audioDevice?.impl)
+ } else {
+ directConnection = nil
+ }
+
+ let context = OngoingCallThreadLocalContextWebrtc(
+ version: version,
+ queue: OngoingCallThreadLocalContextQueueImpl(queue: queue),
+ proxy: voipProxyServer,
+ networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType),
+ dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving),
+ derivedState: Data(),
+ key: key,
+ isOutgoing: isOutgoing,
+ connections: filteredConnections,
+ maxLayer: maxLayer,
+ allowP2P: allowP2P,
+ allowTCP: enableTCP,
+ enableStunMarking: enableStunMarking,
+ logPath: logPath,
+ statsLogPath: tempStatsLogPath,
+ sendSignalingData: { [weak callSessionManager] data in
+ queue.async {
+ guard let strongSelf = self else {
+ return
+ }
+ if let signalingConnectionManager = strongSelf.signalingConnectionManager {
+ signalingConnectionManager.with { impl in
+ impl.send(payloadData: data)
+ }
+ }
+
+ if let callSessionManager = callSessionManager {
+ callSessionManager.sendSignalingData(internalId: internalId, data: data)
+ }
+ }
+ },
+ videoCapturer: video?.impl,
+ preferredVideoCodec: preferredVideoCodec,
+ audioInputDeviceId: "",
+ audioDevice: audioDevice?.impl,
+ directConnection: directConnection
+ )
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in
@@ -1287,12 +1323,204 @@ public final class OngoingCallContext {
}
}
-private protocol CallSignalingConnection {
+private protocol CallSignalingConnection: AnyObject {
func start()
func stop()
func send(payloadData: Data)
}
+@available(iOS 13.0, *)
+private class CustomWrapperProtocol: NWProtocolFramerImplementation {
+ static var label: String = "CustomWrapperProtocol"
+
+ static let definition = NWProtocolFramer.Definition(implementation: CustomWrapperProtocol.self)
+
+ required init(framer: NWProtocolFramer.Instance) {
+
+ }
+
+ func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult {
+ return .ready
+ }
+
+ func handleInput(framer: NWProtocolFramer.Instance) -> Int {
+ preconditionFailure()
+ }
+
+ func handleOutput(framer: NWProtocolFramer.Instance, message: NWProtocolFramer.Message, messageLength: Int, isComplete: Bool) {
+ preconditionFailure()
+ }
+
+ func wakeup(framer: NWProtocolFramer.Instance) {
+ }
+
+ func stop(framer: NWProtocolFramer.Instance) -> Bool {
+ return true
+ }
+
+ func cleanup(framer: NWProtocolFramer.Instance) {
+ }
+}
+
+@available(iOS 12.0, *)
+private final class CallDirectConnectionImpl: NSObject, OngoingCallDirectConnection {
+ private final class Impl {
+ private let queue: Queue
+ private let peerTag: Data
+
+ private var connection: NWConnection?
+
+ var incomingDataHandler: ((Data) -> Void)?
+
+ init(queue: Queue, host: String, port: Int, peerTag: Data) {
+ self.queue = queue
+
+ var peerTag = peerTag
+ peerTag.withUnsafeMutableBytes { buffer in
+ let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
+ for i in (buffer.count - 4) ..< buffer.count {
+ bytes.advanced(by: i).pointee = 1
+ }
+ }
+ self.peerTag = peerTag
+
+ if let port = NWEndpoint.Port(rawValue: UInt16(clamping: port)) {
+ self.connection = NWConnection(host: NWEndpoint.Host(host), port: port, using: .udp)
+ }
+
+ self.connection?.stateUpdateHandler = { newState in
+ switch newState {
+ case .ready:
+ print("CallDirectConnection: State: Ready")
+ case .setup:
+ print("CallDirectConnection: State: Setup")
+ case .cancelled:
+ print("CallDirectConnection: State: Cancelled")
+ case .preparing:
+ print("CallDirectConnection: State: Preparing")
+ case let .waiting(error):
+ print("CallDirectConnection: State: Waiting (\(error))")
+ case let .failed(error):
+ print("CallDirectConnection: State: Error (\(error))")
+ @unknown default:
+ print("CallDirectConnection: State: Unknown")
+ }
+ }
+
+ self.connection?.start(queue: self.queue.queue)
+ self.receive()
+ }
+
+ deinit {
+
+ }
+
+ private func receive() {
+ let queue = self.queue
+ self.connection?.receiveMessage(completion: { [weak self] data, _, _, error in
+ assert(queue.isCurrent())
+
+ guard let self else {
+ return
+ }
+
+ if let data {
+ if data.count >= 16 {
+ var unwrappedData = Data(count: data.count - 16)
+ unwrappedData.withUnsafeMutableBytes { destBuffer -> Void in
+ data.withUnsafeBytes { sourceBuffer -> Void in
+ sourceBuffer.copyBytes(to: destBuffer, from: 16 ..< sourceBuffer.count)
+ }
+ }
+
+ self.incomingDataHandler?(unwrappedData)
+ } else {
+ print("Invalid data size")
+ }
+ }
+ if error == nil {
+ self.receive()
+ }
+ })
+ }
+
+ func send(data: Data) {
+ var wrappedData = Data()
+ wrappedData.append(self.peerTag)
+ wrappedData.append(data)
+
+ self.connection?.send(content: wrappedData, completion: .contentProcessed({ error in
+ if let error {
+ print("Send error: \(error)")
+ }
+ }))
+ }
+ }
+
+ private static let sharedQueue = Queue(name: "CallDirectConnectionImpl")
+
+ private let queue: Queue
+ private let impl: QueueLocalObject
+
+ private let incomingDataHandlers = Atomic Void>>(value: Bag())
+
+ init(host: String, port: Int, peerTag: Data) {
+ let queue = CallDirectConnectionImpl.sharedQueue
+ self.queue = queue
+ self.impl = QueueLocalObject(queue: queue, generate: {
+ return Impl(queue: queue, host: host, port: port, peerTag: peerTag)
+ })
+
+ let incomingDataHandlers = self.incomingDataHandlers
+ self.impl.with { [weak incomingDataHandlers] impl in
+ impl.incomingDataHandler = { data in
+ guard let incomingDataHandlers else {
+ return
+ }
+ for f in incomingDataHandlers.with({ return $0.copyItems() }) {
+ f(data)
+ }
+ }
+ }
+ }
+
+ func add(onIncomingPacket addOnIncomingPacket: @escaping (Data) -> Void) -> Data {
+ var token = self.incomingDataHandlers.with { bag -> Int32 in
+ return Int32(bag.add(addOnIncomingPacket))
+ }
+ return withUnsafeBytes(of: &token, { buffer -> Data in
+ let bytes = buffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
+ return Data(bytes: bytes, count: 4)
+ })
+ }
+
+ func remove(onIncomingPacket token: Data) {
+ if token.count != 4 {
+ return
+ }
+
+ var tokenValue: Int32 = 0
+ withUnsafeMutableBytes(of: &tokenValue, { tokenBuffer in
+ let tokenBytes = tokenBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
+
+ token.withUnsafeBytes { sourceBuffer in
+ let sourceBytes = sourceBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
+ memcpy(tokenBytes, sourceBytes, 4)
+ }
+ })
+
+ self.incomingDataHandlers.with { bag in
+ bag.remove(Int(tokenValue))
+ }
+ }
+
+ func sendPacket(_ packet: Data) {
+ self.impl.with { impl in
+ impl.send(data: packet)
+ }
+ }
+}
+
@available(iOS 12.0, *)
private final class CallSignalingConnectionImpl: CallSignalingConnection {
private let queue: Queue
@@ -1316,7 +1544,18 @@ private final class CallSignalingConnectionImpl: CallSignalingConnection {
self.peerTag = peerTag
self.dataReceived = dataReceived
self.isClosed = isClosed
+
+ #if DEBUG
+ if #available(iOS 15.0, *) {
+ let parameters = NWParameters.quic(alpn: ["tgcalls"])
+ parameters.defaultProtocolStack.internetProtocol = NWProtocolFramer.Options(definition: CustomWrapperProtocol.definition)
+ self.connection = NWConnection(host: self.host, port: self.port, using: parameters)
+ } else {
+ preconditionFailure()
+ }
+ #else
self.connection = NWConnection(host: self.host, port: self.port, using: .tcp)
+ #endif
self.connection.stateUpdateHandler = { [weak self] state in
queue.async {
diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h
index 734f600161..daf0875498 100644
--- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h
+++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h
@@ -215,6 +215,14 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
@end
+@protocol OngoingCallDirectConnection
+
+- (NSData * _Nonnull)addOnIncomingPacket:(void (^_Nonnull)(NSData * _Nonnull))addOnIncomingPacket;
+- (void)removeOnIncomingPacket:(NSData * _Nonnull)token;
+- (void)sendPacket:(NSData * _Nonnull)packet;
+
+@end
+
@interface OngoingCallThreadLocalContextWebrtc : NSObject
+ (void)logMessage:(NSString * _Nonnull)string;
@@ -245,7 +253,8 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
- audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice;
+ audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
+ directConnection:(id _Nullable)directConnection;
- (void)setManualAudioSessionIsActive:(bool)isAudioSessionActive;
diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm
index 907e01cd87..110536a4ed 100644
--- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm
+++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm
@@ -134,7 +134,7 @@ private:
self = [super init];
if (self != nil) {
_audioDeviceModule.reset(new tgcalls::ThreadLocalObject(tgcalls::StaticThreads::getThreads()->getWorkerThread(), [disableRecording]() mutable {
- return (tgcalls::SharedAudioDeviceModule *)(new SharedAudioDeviceModuleImpl(disableRecording));
+ return std::static_pointer_cast(std::make_shared(disableRecording));
}));
}
return self;
@@ -535,6 +535,37 @@ private:
void (^_frameReceived)(webrtc::VideoFrame const &);
};
+class DirectConnectionChannelImpl : public tgcalls::DirectConnectionChannel {
+public:
+ DirectConnectionChannelImpl(id _Nonnull impl) {
+ _impl = impl;
+ }
+
+ virtual ~DirectConnectionChannelImpl() {
+ }
+
+ virtual std::vector addOnIncomingPacket(std::function>)> &&handler) override {
+ __block auto localHandler = std::move(handler);
+
+ NSData *token = [_impl addOnIncomingPacket:^(NSData * _Nonnull data) {
+ std::shared_ptr> mappedData = std::make_shared>((uint8_t const *)data.bytes, (uint8_t const *)data.bytes + data.length);
+ localHandler(mappedData);
+ }];
+ return std::vector((uint8_t * const)token.bytes, (uint8_t * const)token.bytes + token.length);
+ }
+
+ virtual void removeOnIncomingPacket(std::vector &token) override {
+ [_impl removeOnIncomingPacket:[[NSData alloc] initWithBytes:token.data() length:token.size()]];
+ }
+
+ virtual void sendPacket(std::unique_ptr> &&packet) override {
+ [_impl sendPacket:[[NSData alloc] initWithBytes:packet->data() length:packet->size()]];
+ }
+
+private:
+ id _impl;
+};
+
}
@interface GroupCallVideoSink : NSObject {
@@ -1024,7 +1055,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer
preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec
audioInputDeviceId:(NSString * _Nonnull)audioInputDeviceId
- audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice {
+ audioDevice:(SharedCallAudioDevice * _Nullable)audioDevice
+ directConnection:(id _Nullable)directConnection {
self = [super init];
if (self != nil) {
_version = version;
@@ -1149,6 +1181,11 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
audioDeviceModule = [_audioDevice getAudioDeviceModule];
}
+ std::shared_ptr directConnectionChannel;
+ if (directConnection) {
+ directConnectionChannel = std::static_pointer_cast(std::make_shared(directConnection));
+ }
+
__weak OngoingCallThreadLocalContextWebrtc *weakSelf = self;
_tgVoip = tgcalls::Meta::Create([version UTF8String], (tgcalls::Descriptor){
.version = [version UTF8String],
@@ -1288,7 +1325,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
}];
return resultModule;
}
- }
+ },
+ .directConnectionChannel = directConnectionChannel
});
_state = OngoingCallStateInitializing;
_signalBars = 4;
diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls
index 35d5d408ce..1e528141ed 160000
--- a/submodules/TgVoipWebrtc/tgcalls
+++ b/submodules/TgVoipWebrtc/tgcalls
@@ -1 +1 @@
-Subproject commit 35d5d408cee8c69b61a32bc96dbfccc37980a5ae
+Subproject commit 1e528141ed28f188b9b6dc721c8b630541dfb1b0
diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift
index 0d0b017547..36e6eaacc7 100644
--- a/submodules/TooltipUI/Sources/TooltipScreen.swift
+++ b/submodules/TooltipUI/Sources/TooltipScreen.swift
@@ -11,6 +11,7 @@ import TelegramCore
import TextFormat
import Postbox
import UrlEscaping
+import AccountContext
public protocol TooltipCustomContentNode: ASDisplayNode {
func animateIn()
@@ -127,12 +128,11 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private let backgroundContainerNode: ASDisplayNode
private let backgroundClipNode: ASDisplayNode
private let backgroundMaskNode: ASDisplayNode
- private var effectView: UIView?
+ private var effectNode: NavigationBackgroundNode?
private var gradientNode: ASDisplayNode?
private var arrowGradientNode: ASDisplayNode?
private let arrowNode: ASImageNode
private let arrowContainer: ASDisplayNode
- private var arrowEffectView: UIView?
private let animatedStickerNode: AnimatedStickerNode
private var downArrowsNode: DownArrowsIconNode?
private let textNode: ImmediateTextNode
@@ -143,7 +143,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
private var validLayout: ContainerViewLayout?
- init(account: Account, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) {
+ init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity], style: TooltipScreen.Style, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: TooltipScreen.DisplayDuration, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)?) {
self.tooltipStyle = style
self.icon = icon
self.customContentNode = customContentNode
@@ -210,9 +210,16 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
self.arrowContainer = ASDisplayNode()
+ let theme = sharedContext.currentPresentationData.with { $0 }.theme
let fontSize: CGFloat
if case .top = location {
- self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
+ let backgroundColor: UIColor
+ if theme.overallDarkAppearance {
+ backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor
+ } else {
+ backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6)
+ }
+ self.effectNode = NavigationBackgroundNode(color: backgroundColor)
self.backgroundMaskNode.addSubnode(self.backgroundClipNode)
self.backgroundClipNode.clipsToBounds = true
if case let .point(_, arrowPosition) = location, case .right = arrowPosition {
@@ -258,20 +265,28 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
maskLayer.frame = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowContainer.layer.mask = maskLayer
} else {
- let effect: UIBlurEffect
- if case .light = style {
- effect = UIBlurEffect(style: .light)
+ var enableSaturation = true
+ let backgroundColor: UIColor
+ if case let .customBlur(color) = style {
+ backgroundColor = color
+ enableSaturation = false
+ } else if case .light = style {
+ backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor
} else {
- effect = UIBlurEffect(style: .dark)
+ if theme.overallDarkAppearance {
+ backgroundColor = theme.rootController.navigationBar.blurredBackgroundColor
+ } else {
+ backgroundColor = UIColor(rgb: 0x000000, alpha: 0.6)
+ }
}
- self.effectView = UIVisualEffectView(effect: effect)
+ self.effectNode = NavigationBackgroundNode(color: backgroundColor, enableBlur: true, enableSaturation: enableSaturation)
self.backgroundMaskNode.addSubnode(self.backgroundClipNode)
self.backgroundClipNode.clipsToBounds = true
if case let .point(_, arrowPosition) = location, case .right = arrowPosition {
self.backgroundClipNode.cornerRadius = 8.5
} else {
- self.backgroundClipNode.cornerRadius = 14.0
+ self.backgroundClipNode.cornerRadius = 12.5
}
if #available(iOS 13.0, *) {
self.backgroundClipNode.layer.cornerCurve = .continuous
@@ -318,8 +333,8 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
if let gradientNode = self.gradientNode {
self.backgroundContainerNode.addSubnode(gradientNode)
self.containerNode.addSubnode(self.arrowContainer)
- } else if let effectView = self.effectView {
- self.backgroundContainerNode.view.addSubview(effectView)
+ } else if let effectNode = self.effectNode {
+ self.backgroundContainerNode.addSubnode(effectNode)
self.backgroundContainerNode.layer.mask = self.backgroundMaskNode.layer
}
self.containerNode.addSubnode(self.textNode)
@@ -430,7 +445,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let backgroundHeight: CGFloat
switch self.tooltipStyle {
- case .default, .gradient:
+ case .default, .gradient, .customBlur:
backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0
case .light:
backgroundHeight = max(28.0, max(animationSize.height, textSize.height) + 4.0 * 2.0)
@@ -472,8 +487,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
transition.updateFrame(node: self.backgroundMaskNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0))
transition.updateFrame(node: self.backgroundClipNode, frame: CGRect(origin: CGPoint(x: 10.0, y: 10.0), size: backgroundFrame.size))
- if let effectView = self.effectView {
- transition.updateFrame(view: effectView, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0))
+ if let effectNode = self.effectNode {
+ let effectFrame = CGRect(origin: CGPoint(), size: backgroundFrame.size).insetBy(dx: -10.0, dy: -10.0)
+ transition.updateFrame(node: effectNode, frame: effectFrame)
+ effectNode.update(size: effectFrame.size, transition: transition)
}
if let gradientNode = self.gradientNode {
transition.updateFrame(node: gradientNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size))
@@ -501,7 +518,6 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let arrowBounds = CGRect(origin: CGPoint(), size: arrowSize)
self.arrowNode.frame = arrowBounds
- self.arrowEffectView?.frame = arrowBounds
self.arrowGradientNode?.frame = CGRect(origin: CGPoint(x: -arrowFrame.minX + backgroundFrame.minX, y: 0.0), size: backgroundFrame.size)
case .right:
arrowFrame = CGRect(origin: CGPoint(x: backgroundFrame.width + arrowSize.height, y: rect.midY), size: CGSize(width: arrowSize.height, height: arrowSize.width))
@@ -512,12 +528,10 @@ private final class TooltipScreenNode: ViewControllerTracingNode {
let arrowBounds = CGRect(origin: .zero, size: arrowSize)
self.arrowNode.frame = arrowBounds
- self.arrowEffectView?.frame = arrowBounds
self.arrowGradientNode?.frame = arrowBounds
}
} else {
self.arrowNode.isHidden = true
- self.arrowEffectView?.isHidden = true
}
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize))
@@ -672,10 +686,12 @@ public final class TooltipScreen: ViewController {
public enum Style {
case `default`
case light
+ case customBlur(UIColor)
case gradient(UIColor, UIColor)
}
private let account: Account
+ private let sharedContext: SharedAccountContext
public let text: String
public let textEntities: [MessageTextEntity]
private let style: TooltipScreen.Style
@@ -707,8 +723,9 @@ public final class TooltipScreen: ViewController {
public var alwaysVisible = false
- public init(account: Account, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) {
+ public init(account: Account, sharedContext: SharedAccountContext, text: String, textEntities: [MessageTextEntity] = [], style: TooltipScreen.Style = .default, icon: TooltipScreen.Icon?, customContentNode: TooltipCustomContentNode? = nil, location: TooltipScreen.Location, displayDuration: DisplayDuration = .default, inset: CGFloat = 13.0, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: ((TooltipActiveTextItem, TooltipActiveTextAction) -> Void)? = nil) {
self.account = account
+ self.sharedContext = sharedContext
self.text = text
self.textEntities = textEntities
self.style = style
@@ -776,7 +793,7 @@ public final class TooltipScreen: ViewController {
}
override public func loadDisplayNode() {
- self.displayNode = TooltipScreenNode(account: self.account, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
+ self.displayNode = TooltipScreenNode(account: self.account, sharedContext: self.sharedContext, text: self.text, textEntities: self.textEntities, style: self.style, icon: self.icon, customContentNode: self.customContentNode, location: self.location, displayDuration: self.displayDuration, inset: self.inset, shouldDismissOnTouch: self.shouldDismissOnTouch, requestDismiss: { [weak self] in
guard let strongSelf = self else {
return
}
diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift
index aeefeafe42..ea4eccf27c 100644
--- a/submodules/UrlHandling/Sources/UrlHandling.swift
+++ b/submodules/UrlHandling/Sources/UrlHandling.swift
@@ -194,6 +194,22 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if let phone = phone, let hash = hash {
return .cancelAccountReset(phone: phone, hash: hash)
}
+ } else if peerName == "msg" {
+ var url: String?
+ var text: String?
+ var to: String?
+ for queryItem in queryItems {
+ if let value = queryItem.value {
+ if queryItem.name == "url" {
+ url = value
+ } else if queryItem.name == "text" {
+ text = value
+ } else if queryItem.name == "to" {
+ to = value
+ }
+ }
+ }
+ return .share(url: url, text: text, to: to)
} else {
for queryItem in queryItems {
if let value = queryItem.value {
diff --git a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift
index 0095b1c454..957db88bfb 100644
--- a/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift
+++ b/submodules/WallpaperBackgroundNode/Sources/WallpaperBackgroundNode.swift
@@ -743,6 +743,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
private var gradientBackgroundNode: GradientBackgroundNode?
private var outgoingBubbleGradientBackgroundNode: GradientBackgroundNode?
private let patternImageLayer: EffectImageLayer
+ private let dimLayer: SimpleLayer
private var isGeneratingPatternImage: Bool = false
private let bakedBackgroundView: UIImageView
@@ -862,6 +863,10 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.bakedBackgroundView = UIImageView()
self.bakedBackgroundView.isHidden = true
+ self.dimLayer = SimpleLayer()
+ self.dimLayer.opacity = 0.0
+ self.dimLayer.backgroundColor = UIColor.black.cgColor
+
super.init()
if #available(iOS 12.0, *) {
@@ -885,6 +890,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.contentNode.frame = self.bounds
self.addSubnode(self.contentNode)
self.layer.addSublayer(self.patternImageLayer)
+
+ self.layer.addSublayer(self.dimLayer)
}
deinit {
@@ -892,6 +899,30 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.wallpaperDisposable.dispose()
self.imageDisposable.dispose()
}
+
+ private func updateDimming() {
+ guard let wallpaper = self.wallpaper, let theme = self.bubbleTheme else {
+ return
+ }
+ var dimAlpha: Float = 0.0
+ if theme.overallDarkAppearance == true {
+ var intensity: Int32?
+ switch wallpaper {
+ case let .image(_, settings):
+ intensity = settings.intensity
+ case let .file(file):
+ if !file.isPattern {
+ intensity = file.settings.intensity
+ }
+ default:
+ break
+ }
+ if let intensity, intensity > 0 {
+ dimAlpha = max(0.0, min(1.0, Float(intensity) / 100.0))
+ }
+ }
+ self.dimLayer.opacity = dimAlpha
+ }
func update(wallpaper: TelegramWallpaper) {
if self.wallpaper == wallpaper {
@@ -915,7 +946,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
gradientColors = file.settings.colors
gradientAngle = file.settings.rotation ?? 0
}
-
+
var scheduleLoopingEvent = false
if gradientColors.count >= 3 {
let mappedColors = gradientColors.map { color -> UIColor in
@@ -999,6 +1030,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
} else {
strongSelf.blurredBackgroundContents = nil
}
+ strongSelf.updateBubbles()
strongSelf._isReady.set(true)
}))
}
@@ -1032,6 +1064,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
self.animateEvent(transition: .animated(duration: 0.7, curve: .linear), extendAnimation: false)
}
}
+
+ self.updateDimming()
}
func _internalUpdateIsSettingUpWallpaper() {
@@ -1304,6 +1338,8 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
transition.updateFrame(node: outgoingBackgroundNode, frame: CGRect(origin: CGPoint(), size: size))
outgoingBackgroundNode.update(rect: CGRect(origin: CGPoint(), size: size), within: size, transition: transition)
}
+
+ transition.updateFrame(layer: self.dimLayer, frame: CGRect(origin: CGPoint(), size: size))
self.loadPatternForSizeIfNeeded(size: size, displayMode: displayMode, transition: transition)
@@ -1378,6 +1414,7 @@ final class WallpaperBackgroundNodeImpl: ASDisplayNode, WallpaperBackgroundNode
}
self.updateBubbles()
+ self.updateDimming()
}
}
diff --git a/submodules/WallpaperResources/Sources/WallpaperResources.swift b/submodules/WallpaperResources/Sources/WallpaperResources.swift
index a4028d56ee..41c46e91b5 100644
--- a/submodules/WallpaperResources/Sources/WallpaperResources.swift
+++ b/submodules/WallpaperResources/Sources/WallpaperResources.swift
@@ -144,7 +144,11 @@ public func wallpaperDatas(account: Account, accountManager: AccountManager Void
+
+ override var dismissOnOutsideTap: Bool {
+ return self.isUserInteractionEnabled
+ }
+
+ init(context: AccountContext, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peer: EnginePeer, title: String, text: String, showMore: Bool, actions: [TextAlertAction], morePressed: @escaping () -> Void) {
+ self.strings = strings
+ self.title = title
+ self.text = text
+ self.showMore = showMore
+ self.morePressed = morePressed
+
+ self.titleNode = ImmediateTextNode()
+ self.titleNode.displaysAsynchronously = false
+ self.titleNode.maximumNumberOfLines = 1
+ self.titleNode.textAlignment = .center
+
+ self.textNode = ASTextNode()
+ self.textNode.displaysAsynchronously = false
+ self.textNode.maximumNumberOfLines = 0
+
+ self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 26.0))
+
+ self.moreButton = HighlightableButtonNode()
+
+ self.arrowNode = ASImageNode()
+ self.arrowNode.displaysAsynchronously = false
+ self.arrowNode.displayWithoutProcessing = true
+ self.arrowNode.isHidden = !showMore
+ self.arrowNode.contentMode = .scaleAspectFit
+
+ self.actionNodesSeparator = ASDisplayNode()
+ self.actionNodesSeparator.isLayerBacked = true
+
+ self.actionNodes = actions.map { action -> TextAlertContentActionNode in
+ return TextAlertContentActionNode(theme: theme, action: action)
+ }
+
+ var actionVerticalSeparators: [ASDisplayNode] = []
+ if actions.count > 1 {
+ for _ in 0 ..< actions.count - 1 {
+ let separatorNode = ASDisplayNode()
+ separatorNode.isLayerBacked = true
+ actionVerticalSeparators.append(separatorNode)
+ }
+ }
+ self.actionVerticalSeparators = actionVerticalSeparators
+
+ super.init()
+
+ self.addSubnode(self.titleNode)
+ self.addSubnode(self.textNode)
+ self.addSubnode(self.avatarNode)
+ self.addSubnode(self.moreButton)
+ self.moreButton.addSubnode(self.arrowNode)
+
+ self.addSubnode(self.actionNodesSeparator)
+
+ for actionNode in self.actionNodes {
+ self.addSubnode(actionNode)
+ }
+
+ for separatorNode in self.actionVerticalSeparators {
+ self.addSubnode(separatorNode)
+ }
+
+ self.updateTheme(theme)
+
+ self.avatarNode.setPeer(context: context, theme: ptheme, peer: peer)
+
+ self.moreButton.addTarget(self, action: #selector(self.moreButtonPressed), forControlEvents: .touchUpInside)
+ }
+
+ @objc private func moreButtonPressed() {
+ self.morePressed()
+ }
+
+ override func updateTheme(_ theme: AlertControllerTheme) {
+ self.titleNode.attributedText = NSAttributedString(string: self.title, font: Font.semibold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
+ self.textNode.attributedText = NSAttributedString(string: self.text, font: Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center)
+
+ self.moreButton.setAttributedTitle(NSAttributedString(string: self.strings.WebApp_LaunchMoreInfo, font: Font.regular(13.0), textColor: theme.accentColor), for: .normal)
+ self.arrowNode.image = generateTintedImage(image: UIImage(bundleImageName: "Peer Info/AlertArrow"), color: theme.accentColor)
+
+ self.actionNodesSeparator.backgroundColor = theme.separatorColor
+ for actionNode in self.actionNodes {
+ actionNode.updateTheme(theme)
+ }
+ for separatorNode in self.actionVerticalSeparators {
+ separatorNode.backgroundColor = theme.separatorColor
+ }
+
+ if let size = self.validLayout {
+ _ = self.updateLayout(size: size, transition: .immediate)
+ }
+ }
+
+ override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
+ var size = size
+ size.width = min(size.width, 270.0)
+
+ self.validLayout = size
+
+ var origin: CGPoint = CGPoint(x: 0.0, y: 20.0)
+
+ let avatarSize = CGSize(width: 60.0, height: 60.0)
+ self.avatarNode.updateSize(size: avatarSize)
+
+ let avatarFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: origin.y), size: avatarSize)
+ transition.updateFrame(node: self.avatarNode, frame: avatarFrame)
+
+ origin.y += avatarSize.height + 17.0
+
+ if let arrowImage = self.arrowNode.image {
+ let arrowFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - arrowImage.size.width) / 2.0), y: origin.y + floorToScreenPixels((avatarSize.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
+ transition.updateFrame(node: self.arrowNode, frame: arrowFrame)
+ }
+
+ let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 32.0, height: size.height))
+ transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: origin.y), size: titleSize))
+ origin.y += titleSize.height + 6.0
+
+ if self.showMore {
+ let moreButtonSize = self.moreButton.measure(CGSize(width: size.width - 32.0, height: size.height))
+ transition.updateFrame(node: self.moreButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - moreButtonSize.width) / 2.0) - 5.0, y: origin.y), size: moreButtonSize))
+ transition.updateFrame(node: self.arrowNode, frame: CGRect(origin: CGPoint(x: moreButtonSize.width + 3.0, y: 4.0), size: CGSize(width: 9.0, height: 9.0)))
+ origin.y += moreButtonSize.height + 22.0
+ }
+
+ let textSize = self.textNode.measure(CGSize(width: size.width - 32.0, height: size.height))
+ transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize))
+
+ let actionButtonHeight: CGFloat = 44.0
+ var minActionsWidth: CGFloat = 0.0
+ let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
+ let actionTitleInsets: CGFloat = 8.0
+
+ var effectiveActionLayout = TextAlertContentActionLayout.horizontal
+ for actionNode in self.actionNodes {
+ let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight))
+ if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 {
+ effectiveActionLayout = .vertical
+ }
+ switch effectiveActionLayout {
+ case .horizontal:
+ minActionsWidth += actionTitleSize.width + actionTitleInsets
+ case .vertical:
+ minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets)
+ }
+ }
+
+ let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
+
+ let contentWidth = max(size.width, minActionsWidth)
+
+ var actionsHeight: CGFloat = 0.0
+ switch effectiveActionLayout {
+ case .horizontal:
+ actionsHeight = actionButtonHeight
+ case .vertical:
+ actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count)
+ }
+
+ var resultSize = CGSize(width: contentWidth, height: avatarSize.height + titleSize.height + textSize.height + actionsHeight + 25.0 + insets.top + insets.bottom)
+ if self.showMore {
+ resultSize.height += 37.0
+ }
+
+ transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
+
+ var actionOffset: CGFloat = 0.0
+ let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
+ var separatorIndex = -1
+ var nodeIndex = 0
+ for actionNode in self.actionNodes {
+ if separatorIndex >= 0 {
+ let separatorNode = self.actionVerticalSeparators[separatorIndex]
+ switch effectiveActionLayout {
+ case .horizontal:
+ transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
+ case .vertical:
+ transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
+ }
+ }
+ separatorIndex += 1
+
+ let currentActionWidth: CGFloat
+ switch effectiveActionLayout {
+ case .horizontal:
+ if nodeIndex == self.actionNodes.count - 1 {
+ currentActionWidth = resultSize.width - actionOffset
+ } else {
+ currentActionWidth = actionWidth
+ }
+ case .vertical:
+ currentActionWidth = resultSize.width
+ }
+
+ let actionNodeFrame: CGRect
+ switch effectiveActionLayout {
+ case .horizontal:
+ actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
+ actionOffset += currentActionWidth
+ case .vertical:
+ actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight))
+ actionOffset += actionButtonHeight
+ }
+
+ transition.updateFrame(node: actionNode, frame: actionNodeFrame)
+
+ nodeIndex += 1
+ }
+
+ return resultSize
+ }
+}
+
+public func webAppLaunchConfirmationController(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, peer: EnginePeer, commit: @escaping () -> Void, showMore: (() -> Void)?) -> AlertController {
+ let theme = defaultDarkColorPresentationTheme
+ let presentationData: PresentationData
+ if let updatedPresentationData {
+ presentationData = updatedPresentationData.initial
+ } else {
+ presentationData = context.sharedContext.currentPresentationData.with { $0 }
+ }
+ let strings = presentationData.strings
+
+ var dismissImpl: ((Bool) -> Void)?
+ var contentNode: WebAppLaunchConfirmationAlertContentNode?
+ let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {
+ dismissImpl?(true)
+ }), TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
+ dismissImpl?(true)
+ commit()
+ })]
+
+ let title = peer.compactDisplayTitle
+ let text = presentationData.strings.WebApp_LaunchConfirmation
+
+ contentNode = WebAppLaunchConfirmationAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peer: peer, title: title, text: text, showMore: showMore != nil, actions: actions, morePressed: {
+ dismissImpl?(true)
+ showMore?()
+ })
+
+ let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
+ dismissImpl = { [weak controller] animated in
+ if animated {
+ controller?.dismissAnimated()
+ } else {
+ controller?.dismiss()
+ }
+ }
+ return controller
+}
diff --git a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh
index 50f4990e21..95404823f2 100755
--- a/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh
+++ b/submodules/ffmpeg/Sources/FFMpeg/build-ffmpeg-bazel.sh
@@ -181,7 +181,7 @@ then
echo "$CONFIGURE_FLAGS" > "$CONFIGURED_MARKER"
fi
- CORE_COUNT=`sysctl -n hw.logicalcpu`
+ CORE_COUNT=`PATH="$PATH:/usr/sbin" sysctl -n hw.logicalcpu`
make -j$CORE_COUNT install $EXPORT || exit 1
popd
diff --git a/third-party/boringssl/BUILD b/third-party/boringssl/BUILD
index be357e69ca..d7972a8355 100644
--- a/third-party/boringssl/BUILD
+++ b/third-party/boringssl/BUILD
@@ -51,6 +51,7 @@ posix_copts = [
"-Wwrite-strings",
"-Wshadow",
"-fno-common",
+ "-Wno-unused-but-set-variable",
# Modern build environments should be able to set this to use atomic
# operations for reference counting rather than locks. However, it's
diff --git a/third-party/yasm/BUILD b/third-party/yasm/BUILD
index 38efc12b07..bc405ece85 100644
--- a/third-party/yasm/BUILD
+++ b/third-party/yasm/BUILD
@@ -9,7 +9,7 @@ genrule(
cmd_bash =
"""
set -x
- core_count="`sysctl -n hw.logicalcpu`"
+ core_count=`PATH="$$PATH:/usr/sbin" sysctl -n hw.logicalcpu`
BUILD_DIR="$(RULEDIR)/build"
rm -rf "$$BUILD_DIR"
mkdir -p "$$BUILD_DIR"
diff --git a/versions.json b/versions.json
index e608eb649c..3508f160e9 100644
--- a/versions.json
+++ b/versions.json
@@ -1,5 +1,5 @@
{
"app": "9.6",
"bazel": "6.1.1",
- "xcode": "14.2"
+ "xcode": "14.3"
}