Merge commit '0005eebcd9cab0a3ee8ae7caa97218a094a66555' into beta

This commit is contained in:
Ali 2023-04-12 23:44:54 +04:00
commit 8be2f274ee
213 changed files with 6418 additions and 1397 deletions

View File

@ -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
```

View File

@ -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 = """
<key>com.apple.developer.usernotifications.communication</key>
<true/>
"""
communication_notifications_fragment = official_communication_notifications_fragment if telegram_bundle_id in official_bundle_ids else ""
store_signin_fragment = """
<key>com.apple.developer.applesignin</key>
@ -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

View File

@ -21,6 +21,7 @@ swift_library(
"//submodules/rlottie:RLottieBinding",
"//submodules/GZip:GZip",
"//submodules/PersistentStringHash:PersistentStringHash",
"//submodules/Utils/RangeSet",
],
visibility = [

View File

@ -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<Int64>(0 ..< Int64.max)
}
let collectedData = Atomic<DataValue>(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

View File

@ -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<Account?, NoError> in
if let account = account {
switch account {

View File

@ -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 cant 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 cant 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.";

View File

@ -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",
)

View File

@ -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

View File

@ -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(

View File

@ -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):

@ -1 +1 @@
Subproject commit dc226d129aca2237982b98a95c80ed1ccc74f0c5
Subproject commit 7f600ddd7cb3ebc59c696a4639b84b93267c7b6e

View File

@ -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
}

View File

@ -756,6 +756,7 @@ public protocol SharedAccountContext: AnyObject {
var currentInAppNotificationSettings: Atomic<InAppNotificationSettings> { get }
var currentMediaInputSettings: Atomic<MediaInputSettings> { get }
var currentStickerSettings: Atomic<StickerSettings> { get }
var currentMediaDisplaySettings: Atomic<MediaDisplaySettings> { get }
var energyUsageSettings: EnergyUsageSettings { get }

View File

@ -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 {

View File

@ -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 {

View File

@ -177,12 +177,13 @@ private func generateMaskImage() -> UIImage? {
public class AttachmentController: ViewController {
private let context: AccountContext
private let updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?
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<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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

View File

@ -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<PresentationData, NoError>)?, makeEntityInputView: @escaping () -> AttachmentTextInputPanelInputView?) {
init(context: AccountContext, chatLocation: ChatLocation?, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)?, 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

View File

@ -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",

View File

@ -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;

View File

@ -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,

View File

@ -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)
}

View File

@ -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()
}),

View File

@ -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

View File

@ -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 cant 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 cant share folders which have chat types or excluded chats."
unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
} else if data.excludeArchived || data.excludeRead || data.excludeMuted {
unavailableText = "You cant share folders which have chat types or excluded chats."
unavailableText = presentationData.strings.ChatListFilter_ErrorShareInvalidFolder
} else if !data.excludePeers.isEmpty {
unavailableText = "You cant 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 cant 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: {})]))
})
}

View File

@ -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()
}),

View File

@ -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<Void, NoError>?, (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<Void, NoError>?, (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
})
}
}
}

View File

@ -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<Empty>
@ -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

View File

@ -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<EngineContactList, NoError> 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<ChatListNodeListViewTransition, NoError> in
|> mapToQueue { (hideArchivedFolderByDefault, displayArchiveIntro, storageInfo, suggestedChatListNotice, savedMessagesPeer, updateAndFilter, chatFolderUpdates, state, contacts) -> Signal<ChatListNodeListViewTransition, NoError> 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()
}

View File

@ -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 {

View File

@ -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: []))
}

View File

@ -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))

View File

@ -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()
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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))

View File

@ -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)

View File

@ -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)
}

View File

@ -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: []))
}

View File

@ -4,6 +4,7 @@
@class PGCameraMovieWriter;
@class PGRectangleDetector;
@class SQueue;
@interface PGCameraCaptureSession : AVCaptureSession
@ -65,4 +66,6 @@
+ (bool)_isZoomAvailableForDevice:(AVCaptureDevice *)device;
+ (SQueue *)cameraQueue;
@end

View File

@ -49,6 +49,6 @@
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context items:(NSArray *)items focusItem:(id<TGModernGalleryItem>)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<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab;
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots;
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots fromRect:(CGRect)fromRect;
@end

View File

@ -48,6 +48,8 @@ typedef NS_ENUM(NSUInteger, TGModernGalleryScrollAnimationDirection) {
- (void)dismissWhenReady;
- (void)dismissWhenReadyAnimated:(bool)animated;
- (void)setScrollViewHidden:(bool)hidden;
- (bool)isFullyOpaque;
@end

View File

@ -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<TGPhotoDrawingEntitiesView> *entitiesView;
@property (nonatomic, assign) bool ignoreCropForResult;
- (instancetype)initWithContext:(id<LegacyComponentsContext>)context item:(id<TGMediaEditableItem>)item intent:(TGPhotoEditorControllerIntent)intent adjustments:(id<TGMediaEditAdjustments>)adjustments caption:(NSAttributedString *)caption screenImage:(UIImage *)screenImage availableTabs:(TGPhotoEditorTab)availableTabs selectedTab:(TGPhotoEditorTab)selectedTab;
- (void)dismissEditor;

View File

@ -46,6 +46,7 @@ CGSize TGPhotoThumbnailSizeForCurrentScreen();
CGSize TGPhotoEditorScreenImageMaxSize();
extern const CGSize TGPhotoEditorResultImageMaxSize;
extern const CGSize TGPhotoEditorResultImageWallpaperMaxSize;
#ifdef __cplusplus
}

View File

@ -4,6 +4,8 @@
+ (void)presentWithContext:(id<LegacyComponentsContext>)context parentController:(TGViewController *)parentController image:(UIImage *)image video:(NSURL *)video stickersContext:(id<TGPhotoPaintStickersContext>)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<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed;
+ (void)presentEditorWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller withItem:(id<TGMediaEditableItem>)item cropRect:(CGRect)cropRect adjustments:(id<TGMediaEditAdjustments>)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id<TGMediaEditAdjustments>))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)())beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut;
@end

View File

@ -218,6 +218,11 @@
return false;
}
- (bool)isRegional
{
return true;
}
- (bool)isAvialableForVideo
{
return false;

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -38,6 +38,11 @@
return true;
}
- (bool)isRegional
{
return false;
}
- (bool)isAvialableForVideo
{
return true;

View File

@ -38,6 +38,11 @@
return (ABS(((NSNumber *)self.displayValue).floatValue - (float)self.defaultValue) < FLT_EPSILON);
}
- (bool)isRegional
{
return true;
}
- (NSArray *)parameters
{
if (!_parameters)

View File

@ -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)

View File

@ -338,10 +338,10 @@
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab
{
[self presentPhotoEditorForItem:item tab:tab snapshots:@[]];
[self presentPhotoEditorForItem:item tab:tab snapshots:@[] fromRect:CGRectZero];
}
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)item tab:(TGPhotoEditorTab)tab snapshots:(NSArray *)snapshots
- (void)presentPhotoEditorForItem:(id<TGModernGalleryEditableItem>)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<TGPhotoDrawingEntitiesView> *entitiesView = nil;
UIView<TGPhotoDrawingEntitiesView> *entitiesView = nil;
id<TGMediaEditableItem> editableMediaItem = item.editableMediaItem;

View File

@ -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) {

View File

@ -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<SDisposable>(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<TGMediaEditAdjustments>, 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];

View File

@ -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);
}];

View File

@ -7,6 +7,7 @@
#import <Accelerate/Accelerate.h>
const CGSize TGPhotoEditorResultImageMaxSize = { 1280, 1280 };
const CGSize TGPhotoEditorResultImageWallpaperMaxSize = { 2048, 2048 };
const CGSize TGPhotoEditorScreenImageHardLimitSize = { 1280, 1280 };
const CGSize TGPhotoEditorScreenImageHardLimitLegacySize = { 750, 750 };

View File

@ -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;
}

View File

@ -154,7 +154,7 @@
}
}
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
+ (void)presentWithContext:(id<LegacyComponentsContext>)context controller:(TGViewController *)controller caption:(NSAttributedString *)caption withItem:(id<TGMediaEditableItem, TGMediaSelectableItem>)item paint:(bool)paint adjustments:(bool)adjustments recipientName:(NSString *)recipientName stickersContext:(id<TGPhotoPaintStickersContext>)stickersContext fromRect:(CGRect)fromRect mainSnapshot:(UIView *)mainSnapshot snapshots:(NSArray *)snapshots immediate:(bool)immediate appeared:(void (^)(void))appeared completion:(void (^)(id<TGMediaEditableItem>, TGMediaEditingContext *))completion dismissed:(void (^)())dismissed
{
id<LegacyComponentsOverlayWindowManager> windowManager = [context makeOverlayWindowManager];
id<LegacyComponentsContext> windowContext = [windowManager context];
@ -201,6 +201,11 @@
[editingContext setCaption:caption forItem:editableItem];
};
model.didFinishRenderingFullSizeImage = ^(id<TGMediaEditableItem> 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<LegacyComponentsContext>)context controller:(TGViewController *)controller withItem:(id<TGMediaEditableItem>)item cropRect:(CGRect)cropRect adjustments:(id<TGMediaEditAdjustments>)adjustments referenceView:(UIView *)referenceView completion:(void (^)(UIImage *, id<TGMediaEditAdjustments>))completion fullSizeCompletion:(void (^)(UIImage *))fullSizeCompletion beginTransitionOut:(void (^)())beginTransitionOut finishTransitionOut:(void (^)())finishTransitionOut;
{
id<LegacyComponentsOverlayWindowManager> 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<TGMediaEditAdjustments> 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<TGMediaEditableItem> editableItem)
{
return [editableItem thumbnailImageSignal];
};
editorController.requestOriginalScreenSizeImage = ^(id<TGMediaEditableItem> editableItem, NSTimeInterval position)
{
return [editableItem screenImageSignal:position];
};
editorController.requestOriginalFullSizeImage = ^(id<TGMediaEditableItem> 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<TGModernGalleryItem> item, TGModernGalleryItemView *itemView) {
// appeared();
// };
// //galleryController.hasFadeOutTransition = true;
//
// id<TGModernGalleryEditableItem> 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<TGMediaEditableItem> editableItem, id<TGMediaEditAdjustments> adjustments, id representation, bool hasChanges)
// {
// if (hasChanges)
// {
// [editingContext setAdjustments:adjustments forItem:editableItem];
// [editingContext setTemporaryRep:representation forItem:editableItem];
// }
// };
//
// model.didFinishEditingItem = ^(id<TGMediaEditableItem> editableItem, __unused id<TGMediaEditAdjustments> adjustments, UIImage *resultImage, UIImage *thumbnailImage)
// {
// [editingContext setImage:resultImage thumbnailImage:thumbnailImage forItem:editableItem synchronous:false];
// };
//
// model.saveItemCaption = ^(id<TGMediaEditableItem> editableItem, NSAttributedString *caption)
// {
// [editingContext setCaption:caption forItem:editableItem];
// };
//
// model.didFinishRenderingFullSizeImage = ^(id<TGMediaEditableItem> 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

View File

@ -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?

View File

@ -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() {

View File

@ -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)
}))
})

View File

@ -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
}
}

View File

@ -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) {

View File

@ -5,12 +5,16 @@ import Photos
import AVFoundation
class MediaAssetsContext: NSObject, PHPhotoLibraryChangeObserver {
private let assetType: PHAssetMediaType?
private var registeredChangeObserver = false
private let changeSink = ValuePipe<PHChange>()
private let mediaAccessSink = ValuePipe<PHAuthorizationStatus>()
private let cameraAccessSink = ValuePipe<AVAuthorizationStatus?>()
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<PHFetchResult<PHAsset>, 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<PHFetchResult<PHAsset>>(value: initialFetchResult)
return .single(initialFetchResult)
|> then(

View File

@ -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) {

View File

@ -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<TGMediaSelectableItem>()
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<TGMediaSelectableItem>()
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<PresentationData, NoError>)? = 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<PresentationData, NoError>)? = 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<Int, NoError> {
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<NSAttributedString?, NoError> {
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<AttachmentMainButtonState?, NoError> {
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<T> : UIPanGestureRecognizer {
self.updateIsScrollEnabled(true)
}
}
public func wallpaperMediaPickerController(
context: AccountContext,
updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = 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
}

View File

@ -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)
}
}

View File

@ -302,7 +302,7 @@ public func chatMessageBubbleMosaicLayout(maxSize: CGSize, itemSizes: [CGSize],
innerPositionFlags.insert(.right)
}
if positionFlags == .none {
if positionFlags == [] {
innerPositionFlags = .inside
}

View File

@ -21,7 +21,7 @@
@protocol MTTcpConnectionInterfaceDelegate <NSObject>
- (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;

View File

@ -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;

View File

@ -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)();

View File

@ -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;

View File

@ -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;

View File

@ -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.

View File

@ -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];
}});
}

View File

@ -313,7 +313,7 @@ static NSString *makeRandomPadding() {
__weak MTContext *weakCurrentContext = currentContext;
return [[MTSignal alloc] initWithGenerator:^id<MTDisposable>(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;

View File

@ -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) {

View File

@ -410,7 +410,7 @@ static NSData *encryptRSAModernPadding(id<EncryptionProvider> 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]])
{

View File

@ -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)

View File

@ -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) {

View File

@ -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<MTMessageService> 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)

View File

@ -5,6 +5,20 @@
#import <os/lock.h>
#import <libkern/OSAtomic.h>
@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 <NSCopying>
{
NSUInteger _value;

View File

@ -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;

View File

@ -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)
{

View File

@ -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;

View File

@ -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<MTTcpConnectionInterfaceDelegate> delegate = _delegate;
if (delegate) {
[delegate connectionInterfaceDidReadData:rawData withTag:tag];
[delegate connectionInterfaceDidReadData:rawData withTag:tag networkType:networkType];
}
}
@ -717,6 +717,7 @@ struct ctr_state {
id<MTTcpConnectionInterface> _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<MTTcpConnectionDelegate> 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) {

View File

@ -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]])
{

View File

@ -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)
{

View File

@ -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<MTTransportDelegate> 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];
}
}

View File

@ -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<MediaResourceDataFetchResult, MediaResourceDataFetchError> { 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()

View File

@ -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

View File

@ -421,21 +421,28 @@ final class MediaBoxFileContextV2Impl: MediaBoxFileContext {
private func processWrite(resourceOffset: Int64, data: Data, dataRange: Range<Int64>) {
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<Int64> = 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<Int64> = 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)")
}

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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() {

View File

@ -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

View File

@ -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 = ""

View File

@ -112,6 +112,7 @@ swift_library(
"//submodules/FeaturedStickersScreen:FeaturedStickersScreen",
"//submodules/MediaPickerUI:MediaPickerUI",
"//submodules/ImageBlur:ImageBlur",
"//submodules/AttachmentUI:AttachmentUI",
],
visibility = [
"//visibility:public",

View File

@ -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))

Some files were not shown because too many files have changed in this diff Show More