From 3880af10aedcd1cf22af621a7e079547c718a7ca Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 9 Feb 2021 22:55:41 +0400 Subject: [PATCH] [WIP] Message auto-delete and widget --- BUILD | 10 + ...tentdefinition => Intents.intentdefinition | 72 +-- Telegram/BUILD | 20 +- Telegram/Generated/Contents.swift | 36 -- Telegram/Generated/Friend.swift | 60 -- Telegram/Generated/SelectFriendsIntent.swift | 120 ---- Telegram/SiriIntents/IntentHandler.swift | 251 +++++++- .../Telegram-iOS/en.lproj/Localizable.strings | 2 +- Telegram/WidgetKitWidget/PeerNode.swift | 6 +- .../WidgetKitWidget/TodayViewController.swift | 564 ++++++++++++------ build-system/bazel-rules/apple_support | 2 +- build-system/bazel-rules/rules_apple | 2 +- build-system/bazel-rules/rules_swift | 2 +- .../Sources/TGPhotoEditorSliderView.m | 12 +- .../Sources/PeerAutoremoveSetupScreen.swift | 25 +- .../Sources/PeerAutoremoveTimeoutItem.swift | 5 +- .../Sources/AccountManagerMetadataTable.swift | 11 + submodules/Postbox/Sources/ChatListView.swift | 20 +- .../Postbox/Sources/ContactPeersView.swift | 6 +- submodules/Postbox/Sources/MessageView.swift | 4 +- .../Sources/PeerMergedOperationLogView.swift | 12 +- submodules/Postbox/Sources/Postbox.swift | 152 ++--- .../TimestampBasedMessageAttributesView.swift | 8 +- submodules/Postbox/Sources/ViewTracker.swift | 45 +- .../Sources/VoiceChatController.swift | 61 +- .../Sources/StoreMessage_Telegram.swift | 14 +- submodules/TelegramUI/BUILD | 2 +- .../TelegramUI/Sources/ChatController.swift | 78 ++- .../ChatInterfaceStateContextMenus.swift | 3 +- .../ChatInterfaceStateNavigationButtons.swift | 10 + .../ChatMessageDateAndStatusNode.swift | 12 +- .../Sources/SharedAccountContext.swift | 3 +- .../Sources/WidgetDataContext.swift | 89 +-- submodules/TgVoipWebrtc/tgcalls | 2 +- .../Sources/UndoOverlayControllerNode.swift | 2 +- .../WidgetItems/Sources/WidgetItems.swift | 36 +- .../Sources/WidgetItemsUtils.swift | 14 +- third-party/webrtc/webrtc | 2 +- 38 files changed, 1009 insertions(+), 766 deletions(-) create mode 100644 BUILD rename Telegram/SiriIntents/Intents.intentdefinition => Intents.intentdefinition (77%) delete mode 100644 Telegram/Generated/Contents.swift delete mode 100644 Telegram/Generated/Friend.swift delete mode 100644 Telegram/Generated/SelectFriendsIntent.swift diff --git a/BUILD b/BUILD new file mode 100644 index 0000000000..f31c6fe902 --- /dev/null +++ b/BUILD @@ -0,0 +1,10 @@ + +load("@build_bazel_rules_apple//apple:resources.bzl", + "swift_intent_library", +) + +swift_intent_library( + name = "GeneratedSources", + src = "Intents.intentdefinition", + visibility = ["//visibility:public"], +) diff --git a/Telegram/SiriIntents/Intents.intentdefinition b/Intents.intentdefinition similarity index 77% rename from Telegram/SiriIntents/Intents.intentdefinition rename to Intents.intentdefinition index dd46b6e4ca..d6b7b879c8 100644 --- a/Telegram/SiriIntents/Intents.intentdefinition +++ b/Intents.intentdefinition @@ -75,66 +75,6 @@ SelectFriends INIntentParameters - - INIntentParameterConfigurable - - INIntentParameterDisplayName - Contents - INIntentParameterDisplayNameID - WAiyZm - INIntentParameterDisplayPriority - 1 - INIntentParameterEnumType - Contents - INIntentParameterEnumTypeNamespace - p74MWb - INIntentParameterMetadata - - INIntentParameterMetadataDefaultValue - recent - - INIntentParameterName - contents - INIntentParameterPromptDialogs - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Configuration - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogType - Primary - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - There are ${count} options matching ‘${contents}’. - INIntentParameterPromptDialogFormatStringID - oSRWBb - INIntentParameterPromptDialogType - DisambiguationIntroduction - - - INIntentParameterPromptDialogCustom - - INIntentParameterPromptDialogFormatString - Just to confirm, you wanted ‘${contents}’? - INIntentParameterPromptDialogFormatStringID - jvYJCG - INIntentParameterPromptDialogType - Confirmation - - - INIntentParameterTag - 5 - INIntentParameterType - Integer - INIntentParameterArraySizes @@ -160,11 +100,11 @@ INIntentParameterConfigurable INIntentParameterDisplayName - Chats + INIntentParameterDisplayNameID WIf4LD INIntentParameterDisplayPriority - 2 + 1 INIntentParameterFixedSizeArray 1 INIntentParameterName @@ -178,6 +118,10 @@ INIntentParameterPromptDialogCustom + INIntentParameterPromptDialogFormatString + Search + INIntentParameterPromptDialogFormatStringID + ORCbLf INIntentParameterPromptDialogType Configuration @@ -190,8 +134,6 @@ INIntentParameterRelationship - INIntentParameterRelationshipParentName - contents INIntentParameterRelationshipPredicateName EnumHasExactValue INIntentParameterRelationshipPredicateValue @@ -201,6 +143,8 @@ INIntentParameterSupportsMultipleValues + INIntentParameterSupportsSearch + INIntentParameterTag 19 INIntentParameterType diff --git a/Telegram/BUILD b/Telegram/BUILD index 03c601571e..4ff16a70ec 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -8,6 +8,10 @@ load("@build_bazel_rules_apple//apple:ios.bzl", "ios_framework", ) +load("@build_bazel_rules_apple//apple:resources.bzl", + "swift_intent_library", +) + load("@build_bazel_rules_apple//apple:watchos.bzl", "watchos_application", "watchos_extension", @@ -1158,7 +1162,7 @@ plist_fragment( ) swift_library( - name = "GeneratedSources", + name = "GeneratedSources1", module_name = "GeneratedSources", srcs = glob([ "Generated/**/*.swift", @@ -1166,6 +1170,12 @@ swift_library( visibility = ["//visibility:public"], ) +'''swift_intent_library( + name = "GeneratedSources", + src = "SiriIntents/Intents.intentdefinition", + visibility = ["//visibility:public"], +)''' + swift_library( name = "WidgetExtensionLib", module_name = "WidgetExtensionLib", @@ -1173,7 +1183,7 @@ swift_library( "WidgetKitWidget/**/*.swift", ]), data = [ - "SiriIntents/Intents.intentdefinition", + #"SiriIntents/Intents.intentdefinition", ], deps = [ "//submodules/BuildConfig:BuildConfig", @@ -1185,7 +1195,7 @@ swift_library( "//submodules/TelegramCore:TelegramCore", "//submodules/SyncCore:SyncCore", "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", - ":GeneratedSources", + "//:GeneratedSources", ], ) @@ -1267,7 +1277,7 @@ swift_library( "SiriIntents/**/*.swift", ]), data = [ - "SiriIntents/Intents.intentdefinition", + #"SiriIntents/Intents.intentdefinition", ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", @@ -1278,7 +1288,7 @@ swift_library( "//submodules/BuildConfig:BuildConfig", "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", "//submodules/AppLockState:AppLockState", - ":GeneratedSources", + "//:GeneratedSources", ], ) diff --git a/Telegram/Generated/Contents.swift b/Telegram/Generated/Contents.swift deleted file mode 100644 index b66b8e019e..0000000000 --- a/Telegram/Generated/Contents.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Contents.swift -// -// This file was automatically generated and should not be edited. -// - -#if canImport(Intents) - -import Intents - -@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable) -@objc public enum Contents: Int { - case `unknown` = 0 - case `recent` = 1 - case `custom` = 2 -} - -@available(iOS 13.0, macOS 10.16, watchOS 6.0, *) @available(tvOS, unavailable) -@objc(ContentsResolutionResult) -public class ContentsResolutionResult: INEnumResolutionResult { - - // This resolution result is for when the app extension wants to tell Siri to proceed, with a given Contents. The resolvedValue can be different than the original Contents. This allows app extensions to apply business logic constraints. - // Use notRequired() to continue with a 'nil' value. - @objc(successWithResolvedContents:) - public class func success(with resolvedValue: Contents) -> Self { - return __success(withResolvedValue: resolvedValue.rawValue) - } - - // This resolution result is to ask Siri to confirm if this is the value with which the user wants to continue. - @objc(confirmationRequiredWithContentsToConfirm:) - public class func confirmationRequired(with valueToConfirm: Contents) -> Self { - return __confirmationRequiredWithValue(toConfirm: valueToConfirm.rawValue) - } -} - -#endif diff --git a/Telegram/Generated/Friend.swift b/Telegram/Generated/Friend.swift deleted file mode 100644 index 5f0c1933ba..0000000000 --- a/Telegram/Generated/Friend.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// Friend.swift -// -// This file was automatically generated and should not be edited. -// - -#if canImport(Intents) - -import Intents - -@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable) -@objc(Friend) -public class Friend: INObject { - - @available(iOS 13.0, macOS 10.16, watchOS 6.0, *) - @NSManaged public var subtitle: String? - -} - -@available(iOS 13.0, macOS 10.16, watchOS 6.0, *) @available(tvOS, unavailable) -@objc(FriendResolutionResult) -public class FriendResolutionResult: INObjectResolutionResult { - - // This resolution result is for when the app extension wants to tell Siri to proceed, with a given Friend. The resolvedValue can be different than the original Friend. This allows app extensions to apply business logic constraints. - // Use notRequired() to continue with a 'nil' value. - @objc(successWithResolvedFriend:) - public class func success(with resolvedObject: Friend) -> Self { - return super.success(with: resolvedObject) as! Self - } - - // This resolution result is to ask Siri to disambiguate between the provided Friend. - @objc(disambiguationWithFriendsToDisambiguate:) - public class func disambiguation(with objectsToDisambiguate: [Friend]) -> Self { - return super.disambiguation(with: objectsToDisambiguate) as! Self - } - - // This resolution result is to ask Siri to confirm if this is the value with which the user wants to continue. - @objc(confirmationRequiredWithFriendToConfirm:) - public class func confirmationRequired(with objectToConfirm: Friend?) -> Self { - return super.confirmationRequired(with: objectToConfirm) as! Self - } - - @available(*, unavailable) - override public class func success(with resolvedObject: INObject) -> Self { - fatalError() - } - - @available(*, unavailable) - override public class func disambiguation(with objectsToDisambiguate: [INObject]) -> Self { - fatalError() - } - - @available(*, unavailable) - override public class func confirmationRequired(with objectToConfirm: INObject?) -> Self { - fatalError() - } - -} - -#endif diff --git a/Telegram/Generated/SelectFriendsIntent.swift b/Telegram/Generated/SelectFriendsIntent.swift deleted file mode 100644 index 82afd21925..0000000000 --- a/Telegram/Generated/SelectFriendsIntent.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// SelectFriendsIntent.swift -// -// This file was automatically generated and should not be edited. -// - -#if canImport(Intents) - -import Intents - -@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable) -@objc(SelectFriendsIntent) -public class SelectFriendsIntent: INIntent { - - @NSManaged public var contents: Contents - @NSManaged public var friends: [Friend]? - -} - -/*! - @abstract Protocol to declare support for handling a SelectFriendsIntent. By implementing this protocol, a class can provide logic for resolving, confirming and handling the intent. - @discussion The minimum requirement for an implementing class is that it should be able to handle the intent. The confirmation method is optional. The handling method is always called last, after confirming the intent. - */ -@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable) -@objc(SelectFriendsIntentHandling) -public protocol SelectFriendsIntentHandling: NSObjectProtocol { - - /*! - @abstract Dynamic options methods - provide options for the parameter at runtime - @discussion Called to query dynamic options for the parameter and this intent in its current form. - - @param intent The input intent - @param completion The response block contains options for the parameter - */ - @available(iOS 14.0, macOS 10.16, watchOS 7.0, *) - @objc(provideFriendsOptionsCollectionForSelectFriends:withCompletion:) - func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, with completion: @escaping (INObjectCollection?, Error?) -> Swift.Void) - - /*! - @abstract Confirmation method - Validate that this intent is ready for the next step (i.e. handling) - @discussion Called prior to asking the app to handle the intent. The app should return a response object that contains additional information about the intent, which may be relevant for the system to show the user prior to handling. If unimplemented, the system will assume the intent is valid, and will assume there is no additional information relevant to this intent. - - @param intent The input intent - @param completion The response block contains a SelectFriendsIntentResponse containing additional details about the intent that may be relevant for the system to show the user prior to handling. - - @see SelectFriendsIntentResponse - */ - @objc(confirmSelectFriends:completion:) - optional func confirm(intent: SelectFriendsIntent, completion: @escaping (SelectFriendsIntentResponse) -> Swift.Void) - - /*! - @abstract Handling method - Execute the task represented by the SelectFriendsIntent that's passed in - @discussion Called to actually execute the intent. The app must return a response for this intent. - - @param intent The input intent - @param completion The response handling block takes a SelectFriendsIntentResponse containing the details of the result of having executed the intent - - @see SelectFriendsIntentResponse - */ - @objc(handleSelectFriends:completion:) - optional func handle(intent: SelectFriendsIntent, completion: @escaping (SelectFriendsIntentResponse) -> Swift.Void) - - /*! - @abstract Default values for parameters with dynamic options - @discussion Called to query the parameter default value. - */ - @available(iOS 14.0, macOS 10.16, watchOS 7.0, *) - @objc(defaultFriendsForSelectFriends:) - optional func defaultFriends(for intent: SelectFriendsIntent) -> [Friend]? - - /*! - @abstract Deprecated dynamic options methods. - */ - @available(iOS, introduced: 13.0, deprecated: 14.0, message: "") - @available(watchOS, introduced: 6.0, deprecated: 7.0, message: "") - @objc(provideFriendsOptionsForSelectFriends:withCompletion:) - optional func provideFriendsOptions(for intent: SelectFriendsIntent, with completion: @escaping ([Friend]?, Error?) -> Swift.Void) - -} - -/*! - @abstract Constants indicating the state of the response. - */ -@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable) -@objc public enum SelectFriendsIntentResponseCode: Int { - case unspecified = 0 - case ready - case continueInApp - case inProgress - case success - case failure - case failureRequiringAppLaunch -} - -@available(iOS 12.0, macOS 10.16, watchOS 5.0, *) @available(tvOS, unavailable) -@objc(SelectFriendsIntentResponse) -public class SelectFriendsIntentResponse: INIntentResponse { - - /*! - @abstract The response code indicating your success or failure in confirming or handling the intent. - */ - @objc public fileprivate(set) var code: SelectFriendsIntentResponseCode = .unspecified - - /*! - @abstract Initializes the response object with the specified code and user activity object. - @discussion The app extension has the option of capturing its private state as an NSUserActivity and returning it as the 'currentActivity'. If the app is launched, an NSUserActivity will be passed in with the private state. The NSUserActivity may also be used to query the app's UI extension (if provided) for a view controller representing the current intent handling state. In the case of app launch, the NSUserActivity will have its activityType set to the name of the intent. This intent object will also be available in the NSUserActivity.interaction property. - - @param code The response code indicating your success or failure in confirming or handling the intent. - @param userActivity The user activity object to use when launching your app. Provide an object if you want to add information that is specific to your app. If you specify nil, the system automatically creates a user activity object for you, sets its type to the class name of the intent being handled, and fills it with an INInteraction object containing the intent and your response. - */ - @objc(initWithCode:userActivity:) - public convenience init(code: SelectFriendsIntentResponseCode, userActivity: NSUserActivity?) { - self.init() - self.code = code - self.userActivity = userActivity - } - -} - -#endif diff --git a/Telegram/SiriIntents/IntentHandler.swift b/Telegram/SiriIntents/IntentHandler.swift index b4ba6cc3b2..843eea96d7 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -56,10 +56,15 @@ enum IntentHandlingError { @objc(IntentHandler) class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling, SelectFriendsIntentHandling { private let accountPromise = Promise() + private let allAccounts = Promise<[(AccountRecordId, PeerId)]>() private let resolvePersonsDisposable = MetaDisposable() private let actionDisposable = MetaDisposable() + private let searchDisposable = MetaDisposable() + private var rootPath: String? + private var accountManager: AccountManager? + private var encryptionParameters: ValueBoxEncryptionParameters? private var appGroupUrl: URL? override init() { @@ -88,6 +93,8 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let rootPath = rootPathForBasePath(appGroupUrl.path) performAppGroupUpgrades(appGroupPath: appGroupUrl.path, rootPath: rootPath) + self.rootPath = rootPath + TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: arc4random64()) let logsPath = rootPath + "/siri-logs" @@ -97,16 +104,61 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown" + initializeAccountManagement() + let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") + self.accountManager = accountManager + + let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) + let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) + self.encryptionParameters = encryptionParameters + + self.allAccounts.set(accountManager.accountRecords() + |> take(1) + |> map { view -> [(AccountRecordId, PeerId)] in + var result: [(AccountRecordId, Int, PeerId)] = [] + for record in view.records { + let isLoggedOut = record.attributes.contains(where: { attribute in + return attribute is LoggedOutAccountAttribute + }) + if isLoggedOut { + continue + } + /*let isTestingEnvironment = record.attributes.contains(where: { attribute in + if let attribute = attribute as? AccountEnvironmentAttribute, case .test = attribute.environment { + return true + } else { + return false + } + })*/ + var backupData: AccountBackupData? + var sortIndex: Int32 = 0 + for attribute in record.attributes { + if let attribute = attribute as? AccountSortOrderAttribute { + sortIndex = attribute.order + } else if let attribute = attribute as? AccountBackupDataAttribute { + backupData = attribute.data + } + } + if let backupData = backupData { + result.append((record.id, Int(sortIndex), PeerId(backupData.peerId))) + } + } + result.sort(by: { lhs, rhs in + if lhs.1 != rhs.1 { + return lhs.1 < rhs.1 + } else { + return lhs.0 < rhs.0 + } + }) + return result.map { record -> (AccountRecordId, PeerId) in + return (record.0, record.2) + } + }) + let account: Signal if let accountCache = accountCache { account = .single(accountCache) } else { - initializeAccountManagement() - let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata") - - let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId) - let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!) - 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()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters) |> mapToSignal { account -> Signal in if let account = account { @@ -139,6 +191,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag deinit { self.resolvePersonsDisposable.dispose() self.actionDisposable.dispose() + self.searchDisposable.dispose() } override public func handler(for intent: INIntent) -> Any { @@ -701,45 +754,181 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag } @available(iOSApplicationExtension 14.0, iOS 14.0, *) - func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { - let _ = (self.accountPromise.get() + func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, searchTerm: String?, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + guard let rootPath = self.rootPath, let _ = self.accountManager, let encryptionParameters = self.encryptionParameters else { + completion(nil, nil) + return + } + + if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data) { + if state.isManuallyLocked || state.autolockTimeout != nil { + let error = NSError(domain: "Locked", code: 1, userInfo: [ + NSLocalizedDescriptionKey: "Open Telegram and enter passcode to edit widget." + ]) + + completion(nil, error) + return + } + } + + self.searchDisposable.set((self.allAccounts.get() + |> castError(Error.self) + |> take(1) + |> mapToSignal { accounts -> Signal, Error> in + var accountResults: [Signal, Error>] = [] + + for (accountId, accountPeerId) in accounts { + accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> INObjectSection in + var accountTitle: String = "" + if let peer = transaction.getPeer(accountPeerId) as? TelegramUser { + if let username = peer.username, !username.isEmpty { + accountTitle = "@\(username)" + } else { + accountTitle = peer.debugDisplayTitle + } + } + + var peers: [Peer] = [] + + if let searchTerm = searchTerm { + if !searchTerm.isEmpty { + for renderedPeer in transaction.searchPeers(query: searchTerm) { + if let peer = renderedPeer.peer, !(peer is TelegramSecretChat) { + peers.append(peer) + } + } + + if peers.count > 30 { + peers = Array(peers.dropLast(peers.count - 30)) + } + } + } else { + for renderedPeer in transaction.getTopChatListEntries(groupId: .root, count: 50) { + if let peer = renderedPeer.peer, !(peer is TelegramSecretChat) { + peers.append(peer) + } + } + } + + var items: [Friend] = [] + for peer in peers { + let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in + return postbox.mediaBox.resourcePath(representation.resource) + }.flatMap { path -> INImage? in + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + return INImage(imageData: data) + } else { + return nil + } + } + items.append(Friend(identifier: "\(accountId.int64):\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil)) + } + + return INObjectSection(title: accountTitle, items: items) + }) + |> castError(Error.self)) + } + + return combineLatest(accountResults) + |> map { accountResults -> INObjectCollection in + let filteredSections = accountResults.filter { section in + return !section.items.isEmpty + } + if filteredSections.count == 1 { + return INObjectCollection(items: filteredSections[0].items) + } else { + return INObjectCollection(sections: filteredSections) + } + } + }).start(next: { result in + completion(result, nil) + }, error: { error in + completion(nil, error) + })) + + /*let _ = (self.accountPromise.get() |> take(1) |> mapToSignal { account -> Signal<[Friend], NoError> in guard let account = account else { return .single([]) } - return account.postbox.transaction { transaction -> [Friend] in - var peers: [Peer] = [] - - outer: for peerId in transaction.getContactPeerIds() { - if let peer = transaction.getPeer(peerId) as? TelegramUser { - peers.append(peer) + + if let searchTerm = searchTerm { + if !searchTerm.isEmpty { + return account.postbox.searchPeers(query: searchTerm) + |> map { renderedPeers -> [Friend] in + var peers: [Peer] = [] + + for renderedPeer in renderedPeers { + if let peer = renderedPeer.peer, !(peer is TelegramSecretChat) { + peers.append(peer) + } + } + + peers.sort(by: { lhs, rhs in + return lhs.debugDisplayTitle < rhs.debugDisplayTitle + }) + + if peers.count > 30 { + peers = Array(peers.dropLast(peers.count - 30)) + } + + var result: [Friend] = [] + for peer in peers { + let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in + return account.postbox.mediaBox.resourcePath(representation.resource) + }.flatMap { path -> INImage? in + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + return INImage(imageData: data) + } else { + return nil + } + } + result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil)) + } + return result } + } else { + return .single([]) } - - peers.sort(by: { lhs, rhs in - return lhs.debugDisplayTitle < rhs.debugDisplayTitle - }) - - var result: [Friend] = [] - for peer in peers { - let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in - return account.postbox.mediaBox.resourcePath(representation.resource) - }.flatMap { path -> INImage? in - if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { - return INImage(imageData: data) - } else { - return nil + } else { + return account.postbox.transaction { transaction -> [Friend] in + var peers: [Peer] = [] + + for peerId in transaction.getContactPeerIds() { + if let peer = transaction.getPeer(peerId) as? TelegramUser { + peers.append(peer) } } - result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil)) + + peers.sort(by: { lhs, rhs in + return lhs.debugDisplayTitle < rhs.debugDisplayTitle + }) + + if peers.count > 50 { + peers = Array(peers.dropLast(peers.count - 50)) + } + + var result: [Friend] = [] + for peer in peers { + let profileImage = smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in + return account.postbox.mediaBox.resourcePath(representation.resource) + }.flatMap { path -> INImage? in + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + return INImage(imageData: data) + } else { + return nil + } + } + result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil)) + } + return result } - return result } } |> deliverOnMainQueue).start(next: { result in let collection = INObjectCollection(items: result) completion(collection, nil) - }) + })*/ } } diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d79fb5b5cf..b83cde5a9c 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -2187,7 +2187,7 @@ Unused sets are archived when you add more."; "Widget.AuthRequired" = "Log in to Telegram"; "Widget.NoUsers" = "Start messaging to see your friends here"; "Widget.GalleryTitle" = "Telegram"; -"Widget.GalleryDescription" = "See your friends here"; +"Widget.GalleryDescription" = "Select chats"; "ShareMenu.CopyShareLinkGame" = "Copy link to game"; diff --git a/Telegram/WidgetKitWidget/PeerNode.swift b/Telegram/WidgetKitWidget/PeerNode.swift index e1ffc91b47..97cd9ba502 100644 --- a/Telegram/WidgetKitWidget/PeerNode.swift +++ b/Telegram/WidgetKitWidget/PeerNode.swift @@ -94,11 +94,11 @@ private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: private let avatarSize = CGSize(width: 50.0, height: 50.0) -func avatarImage(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) -> UIImage { - if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) { +func avatarImage(accountPeerId: Int64?, peer: WidgetDataPeer?, size: CGSize) -> UIImage { + if let path = peer?.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) { return roundImage } else { - return avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)! + return avatarViewLettersImage(size: size, peerId: peer?.id ?? 1, accountPeerId: accountPeerId ?? 1, letters: peer?.letters ?? [" "])! } } diff --git a/Telegram/WidgetKitWidget/TodayViewController.swift b/Telegram/WidgetKitWidget/TodayViewController.swift index 41f86bc74b..969c96ad86 100644 --- a/Telegram/WidgetKitWidget/TodayViewController.swift +++ b/Telegram/WidgetKitWidget/TodayViewController.swift @@ -17,6 +17,29 @@ import WidgetItemsUtils import GeneratedSources +struct ParsedPeer { + var accountId: Int64 + var accountPeerId: Int64 + var peer: WidgetDataPeer +} + +struct ParsedPeers { + var peers: [ParsedPeer] + var updateTimestamp: Int32 +} + +private extension ParsedPeers { + init(accountId: Int64, peers: WidgetDataPeers) { + self.init(peers: peers.peers.map { peer -> ParsedPeer in + return ParsedPeer( + accountId: accountId, + accountPeerId: peers.accountPeerId, + peer: peer + ) + }, updateTimestamp: peers.updateTimestamp) + } +} + private var installedSharedLogger = false private func setupSharedLogger(rootPath: String, path: String) { @@ -64,14 +87,7 @@ struct Provider: IntentTimelineProvider { } func getSnapshot(for configuration: SelectFriendsIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { - let contents: SimpleEntry.Contents - switch configuration.contents { - case .unknown, .recent: - contents = .recent - case .custom: - contents = .recent - } - let entry = SimpleEntry(date: Date(), contents: contents) + let entry = SimpleEntry(date: Date(), contents: .peers(ParsedPeers(accountId: 0, peers: WidgetDataPeers(accountPeerId: 0, peers: [], updateTimestamp: 0)))) completion(entry) } @@ -79,111 +95,209 @@ struct Provider: IntentTimelineProvider { let currentDate = Date() let entryDate = Calendar.current.date(byAdding: .hour, value: 0, to: currentDate)! - switch configuration.contents { - case .unknown, .recent: + guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd)) - case .custom: - guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { - completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd)) - return + return + } + + let baseAppBundleId = String(appBundleIdentifier[.. WidgetDataPeers in - var peers: [WidgetDataPeer] = [] - if let items = configuration.friends { - for item in items { - guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else { - continue + } + + var friendsByAccount: [Signal<[ParsedPeer], NoError>] = [] + for (accountId, items) in itemsByAccount { + friendsByAccount.append(accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> [ParsedPeer] in + guard let state = transaction.getState() as? AuthorizedAccountState else { + return [] + } + + var result: [ParsedPeer] = [] + + for (peerId, _) in items { + guard let peer = transaction.getPeer(PeerId(peerId)) else { + continue + } + + var name: String = "" + var lastName: String? + + if let user = peer as? TelegramUser { + if let firstName = user.firstName { + name = firstName + lastName = user.lastName + } else if let lastName = user.lastName { + name = lastName + } else if let phone = user.phone, !phone.isEmpty { + name = phone } - guard let peer = transaction.getPeer(PeerId(peerIdValue)) else { - continue + } else { + name = peer.debugDisplayTitle + } + + var badge: WidgetDataPeer.Badge? + + if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 { + var isMuted = false + if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings { + isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false) } - - var name: String = "" - var lastName: String? - - if let user = peer as? TelegramUser { - if let firstName = user.firstName { - name = firstName - lastName = user.lastName - } else if let lastName = user.lastName { - name = lastName - } else if let phone = user.phone, !phone.isEmpty { - name = phone - } - } else { - name = peer.debugDisplayTitle + badge = WidgetDataPeer.Badge( + count: Int(readState.count), + isMuted: isMuted + ) + } + + var mappedMessage: WidgetDataPeer.Message? + if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) { + if let message = transaction.getMessage(index.id) { + mappedMessage = WidgetDataPeer.Message(message: message) } - - var badge: WidgetDataPeer.Badge? - - if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 { - var isMuted = false - if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings { - isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false) - } - badge = WidgetDataPeer.Badge( - count: Int(readState.count), - isMuted: isMuted - ) + } + + let widgetPeer = WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in + return postbox.mediaBox.resourcePath(representation.resource) + }, badge: badge, message: mappedMessage) + + result.append(ParsedPeer(accountId: accountId, accountPeerId: state.peerId.toInt64(), peer: widgetPeer)) + } + + return result + })) + } + + let _ = combineLatest(friendsByAccount).start(next: { allPeers in + var orderedPeers: [ParsedPeer] = [] + + outer: for (accountId, peerId) in itemOrder { + for peerSet in allPeers { + for peer in peerSet { + if peer.accountId == accountId && peer.peer.id == peerId { + orderedPeers.append(peer) + continue outer } - - var mappedMessage: WidgetDataPeer.Message? - if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) { - if let message = transaction.getMessage(index.id) { - mappedMessage = WidgetDataPeer.Message(message: message) - } - } - - peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in - return postbox.mediaBox.resourcePath(representation.resource) - }, badge: badge, message: mappedMessage)) } } - return WidgetDataPeers(accountPeerId: widgetPeers.accountPeerId, peers: peers, updateTimestamp: Int32(Date().timeIntervalSince1970)) - }) - |> deliverOnMainQueue).start(next: { peers in - completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(peers))], policy: .atEnd)) - }) - } + } + + let result = ParsedPeers(peers: orderedPeers, updateTimestamp: Int32(Date().timeIntervalSince1970)) + completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(result))], policy: .atEnd)) + }) + + /*let _ = (accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: widgetData.accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> ParsedPeers in + var peers: [ParsedPeer] = [] + if let items = configuration.friends { + for item in items { + guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else { + continue + } + guard let peer = transaction.getPeer(PeerId(peerIdValue)) else { + continue + } + + var name: String = "" + var lastName: String? + + if let user = peer as? TelegramUser { + if let firstName = user.firstName { + name = firstName + lastName = user.lastName + } else if let lastName = user.lastName { + name = lastName + } else if let phone = user.phone, !phone.isEmpty { + name = phone + } + } else { + name = peer.debugDisplayTitle + } + + var badge: WidgetDataPeer.Badge? + + if let readState = transaction.getCombinedPeerReadState(peer.id), readState.count > 0 { + var isMuted = false + if let notificationSettings = transaction.getPeerNotificationSettings(peer.id) as? TelegramPeerNotificationSettings { + isMuted = notificationSettings.isRemovedFromTotalUnreadCount(default: false) + } + badge = WidgetDataPeer.Badge( + count: Int(readState.count), + isMuted: isMuted + ) + } + + var mappedMessage: WidgetDataPeer.Message? + if let index = transaction.getTopPeerMessageIndex(peerId: peer.id) { + if let message = transaction.getMessage(index.id) { + mappedMessage = WidgetDataPeer.Message(message: message) + } + } + + peers.append(WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in + return postbox.mediaBox.resourcePath(representation.resource) + }, badge: badge, message: mappedMessage)) + } + } + return ParsedPeers(peers: peers, updateTimestamp: Int32(Date().timeIntervalSince1970)) + }) + |> deliverOnMainQueue).start(next: { peers in + completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .peers(peers))], policy: .atEnd)) + })*/ } } struct SimpleEntry: TimelineEntry { enum Contents { case recent - case peers(WidgetDataPeers) + case peers(ParsedPeers) } let date: Date @@ -191,27 +305,32 @@ struct SimpleEntry: TimelineEntry { } enum PeersWidgetData { - case placeholder case empty - case locked - case peers(WidgetDataPeers) + case peers(ParsedPeers) } extension PeersWidgetData { - static let previewData = PeersWidgetData.placeholder + static let previewData = PeersWidgetData.empty } struct AvatarItemView: View { - var accountPeerId: Int64 - var peer: WidgetDataPeer + var peer: ParsedPeer? var itemSize: CGFloat var displayBadge: Bool = true var body: some View { return ZStack { - Image(uiImage: avatarImage(accountPeerId: accountPeerId, peer: peer, size: CGSize(width: itemSize, height: itemSize))) + if let peer = peer { + Image(uiImage: avatarImage(accountPeerId: peer.accountPeerId, peer: peer.peer, size: CGSize(width: itemSize, height: itemSize))) .clipShape(Circle()) - if displayBadge, let badge = peer.badge, badge.count > 0 { + } else { + Image(uiImage: avatarImage(accountPeerId: nil, peer: nil, size: CGSize(width: itemSize, height: itemSize))) + //Rectangle() + .frame(width: itemSize, height: itemSize) + .clipShape(Circle()) + .redacted(reason: .placeholder) + } + /*if let peer = peer, displayBadge, let badge = peer.badge, badge.count > 0 { Text("\(badge.count)") .font(Font.system(size: 16.0)) .multilineTextAlignment(.center) @@ -223,7 +342,7 @@ struct AvatarItemView: View { .frame(minWidth: 20, idealWidth: 20, maxWidth: .infinity, minHeight: 20, idealHeight: 20, maxHeight: 20.0, alignment: .center) ) .position(x: floor(0.84 * itemSize), y: floor(0.16 * itemSize)) - } + }*/ } } } @@ -259,7 +378,7 @@ struct WidgetView: View { } } - func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View { + func peersView(geometry: GeometryProxy, peers: ParsedPeers) -> some View { let columnCount: Int let rowCount: Int @@ -308,9 +427,8 @@ struct WidgetView: View { return ZStack { ForEach(0 ..< min(peers.peers.count, columnCount * rowCount), content: { i in - Link(destination: URL(string: linkForPeer(id: peers.peers[i].id))!, label: { + Link(destination: URL(string: linkForPeer(id: peers.peers[i].peer.id))!, label: { AvatarItemView( - accountPeerId: peers.accountPeerId, peer: peers.peers[i], itemSize: itemSize ).frame(width: itemSize, height: itemSize) @@ -322,18 +440,10 @@ struct WidgetView: View { func peerViews() -> AnyView { switch data { - case .placeholder: + case .empty: return AnyView(GeometryReader { geometry in placeholder(geometry: geometry) }) - case .empty: - return AnyView(VStack { - Text(presentationData.applicationStartRequiredString) - }) - case .locked: - return AnyView(VStack { - Text(presentationData.applicationLockedString) - }) case let .peers(peers): return AnyView(GeometryReader { geometry in peersView(geometry: geometry, peers: peers) @@ -348,23 +458,50 @@ struct WidgetView: View { .padding(0.0) } - func chatTopLine(_ peer: WidgetDataPeer) -> some View { + func chatTopLine(_ peer: ParsedPeer?) -> some View { let dateText: String - if let message = peer.message { - dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short) + + let chatTitle: Text + let date: Text + + if let peer = peer { + if let message = peer.peer.message { + dateText = DateFormatter.localizedString(from: Date(timeIntervalSince1970: Double(message.timestamp)), dateStyle: .none, timeStyle: .short) + } else { + dateText = "" + } + chatTitle = Text(peer.peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary) + date = Text(dateText) + .font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary) } else { - dateText = "" + dateText = "10:00" + chatTitle = Text("Chat Title").font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary) + date = Text(dateText) + .font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary) } return HStack(alignment: .center, spacing: 0.0, content: { - Text(peer.name).font(Font.system(size: 16.0, weight: .medium, design: .default)).foregroundColor(.primary) + if peer != nil { + chatTitle + } else { + chatTitle.redacted(reason: .placeholder) + } Spacer() - Text(dateText).font(Font.system(size: 14.0, weight: .regular, design: .default)).foregroundColor(.secondary) + if peer != nil { + date + } else { + date.redacted(reason: .placeholder) + } }) + .padding(0.0) } - func chatBottomLine(_ peer: WidgetDataPeer) -> some View { - var text = peer.message?.text ?? "" - if let message = peer.message { + func chatBottomLine(_ peer: ParsedPeer?) -> AnyView { + var text = peer?.peer.message?.text ?? "" + text += "\n" + if peer == nil { + text = "First Line Of Text Here\nSecond line fwqefeqwfqwef qwef wq" + } + if let message = peer?.peer.message { //TODO:localize switch message.content { case .text: @@ -406,17 +543,47 @@ struct WidgetView: View { case let .poll(poll): text = "📊 \(poll.title)" } + + if let author = message.author { + if author.isMe { + text = "You: \(text)" + } else { + text = "\(author.title): \(text)" + } + } } var hasBadge = false - if let badge = peer.badge, badge.count > 0 { + if let peer = peer, let badge = peer.peer.badge, badge.count > 0 { hasBadge = true } - return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: { - Text(text).lineLimit(nil).font(Font.system(size: 15.0, weight: .regular, design: .default)).foregroundColor(.secondary).multilineTextAlignment(.leading).frame(maxHeight: .infinity, alignment: .topLeading) - Spacer() - if let badge = peer.badge, badge.count > 0 { + let textView = Text(text) + .lineLimit(2) + .font(Font.system(size: 15.0, weight: .regular, design: .default)) + .foregroundColor(.secondary) + .multilineTextAlignment(.leading) + .padding(0.0) + //.frame(maxHeight: .infinity, alignment: .topLeading) + //.background(Rectangle().foregroundColor(.gray)) + + if peer != nil { + return AnyView(textView) + } else { + return AnyView( + textView + .redacted(reason: .placeholder) + ) + } + + /*return HStack(alignment: .center, spacing: hasBadge ? 6.0 : 0.0, content: { + if peer != nil { + textView + } else { + textView.redacted(reason: .placeholder) + } + //Spacer() + /*if let peer = peer, let badge = peer.badge, badge.count > 0 { VStack { Spacer() Text("\(badge.count)") @@ -431,78 +598,114 @@ struct WidgetView: View { ) .padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 6.0, trailing: 3.0)) } - } - }) + }*/ + })*/ } - func chatContent(_ peer: WidgetDataPeer) -> some View { - return VStack(alignment: .leading, spacing: 2.0, content: { + func chatContent(_ peer: ParsedPeer?) -> some View { + return VStack(alignment: .leading, spacing: 0.0, content: { chatTopLine(peer) - chatBottomLine(peer).frame(maxHeight: .infinity) + chatBottomLine(peer) }) } func chatContentView(_ index: Int, size: CGSize) -> AnyView { - let peers: WidgetDataPeers + let peers: ParsedPeers? switch data { case let .peers(peersValue): - peers = peersValue - if peers.peers.count <= index { - return AnyView(Spacer()) + if peersValue.peers.count <= index { + peers = nil + } else { + peers = peersValue } default: - return AnyView(Spacer()) + peers = nil } let itemHeight = (size.height - 22.0) / 2.0 + let url: URL + if let peers = peers { + url = URL(string: linkForPeer(id: peers.peers[index].peer.id))! + } else { + url = URL(string: "\(buildConfig.appSpecificUrlScheme)://")! + } + return AnyView( - Link(destination: URL(string: linkForPeer(id: peers.peers[index].id))!, label: { + Link(destination: url, label: { HStack(alignment: .center, spacing: 0.0, content: { - AvatarItemView(accountPeerId: peers.accountPeerId, peer: peers.peers[index], itemSize: 54.0, displayBadge: false).frame(width: 54.0, height: 54.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0)) - chatContent(peers.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 10.0, leading: 0.0, bottom: 10.0, trailing: 10.0)) + AvatarItemView(peer: peers?.peers[index], itemSize: 54.0, displayBadge: false).frame(width: 54.0, height: 54.0, alignment: .leading).padding(EdgeInsets(top: 0.0, leading: 10.0, bottom: 0.0, trailing: 10.0)) + chatContent(peers?.peers[index]).frame(maxWidth: .infinity).padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0)) }) - }).position(x: size.width / 2.0, y: (itemHeight * 2.0) / 4.0 + CGFloat(index) * itemHeight).frame(width: size.width, height: itemHeight, alignment: .leading) + }) + .frame(width: size.width, height: itemHeight, alignment: .leading) ) } func chatSeparatorView(size: CGSize) -> some View { - let separatorWidth = size.width - 54.0 - 20.0 + return HStack(alignment: .center, spacing: 0.0, content: { + Spacer() + Rectangle() + .foregroundColor(getSeparatorColor()) + .frame(width: size.width - 54.0 - 20.0, height: 0.5, alignment: .leading) + }) + .frame(width: size.width, height: 1.0, alignment: .leading) + /*let separatorWidth = size.width - 54.0 - 20.0 let itemHeight = (size.height - 22.0) / 2.0 - return Rectangle().foregroundColor(getSeparatorColor()).position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0).frame(width: separatorWidth, height: 0.33, alignment: .leading) + return Rectangle().foregroundColor(getSeparatorColor()) + //.position(x: (54.0 + 20.0 + separatorWidth) / 2.0, y: itemHeight / 2.0) + .frame(width: size.width, height: 1.0, alignment: .leading)*/ } func chatsUpdateBackgroundView(size: CGSize) -> some View { - return Rectangle().foregroundColor(getUpdatedBackgroundColor()).position(x: size.width / 2.0, y: size.height - 22.0 - 11.0).frame(width: size.width, height: 22.0, alignment: .leading) + return Rectangle().foregroundColor(getUpdatedBackgroundColor()) + //.position(x: size.width / 2.0, y: size.height - 11.0) + .frame(width: size.width, height: 22.0, alignment: .center) + } + + func chatUpdateView(size: CGSize) -> some View { + return ZStack(alignment: Alignment(horizontal: .center, vertical: .center), content: { + Rectangle().foregroundColor(getUpdatedBackgroundColor()) + chatsUpdateTimestampView(size: size) + }) + .frame(width: size.width, height: 22.0 - 1.0, alignment: .center) } func chatsUpdateTimestampView(size: CGSize) -> some View { let text: String switch data { case let .peers(peersValue): - let date = Date(timeIntervalSince1970: Double(peersValue.updateTimestamp)) - let calendar = Calendar.current - //TODO:localize - if !calendar.isDate(Date(), inSameDayAs: date) { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .none - text = "updated on \(formatter.string(from: date))" + if peersValue.peers.isEmpty { + text = "Long tap to edit widget" } else { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .short - text = "updated at \(formatter.string(from: date))" + let date = Date(timeIntervalSince1970: Double(peersValue.updateTimestamp)) + let calendar = Calendar.current + //TODO:localize + if !calendar.isDate(Date(), inSameDayAs: date) { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + text = "updated on \(formatter.string(from: date))" + } else { + let formatter = DateFormatter() + formatter.dateStyle = .none + formatter.timeStyle = .short + text = "updated at \(formatter.string(from: date))" + } } default: text = "" } - return HStack(alignment: .center, spacing: 0.0, content: { + return Text(text) + .font(Font.system(size: 12.0)) + .foregroundColor(getUpdatedTextColor()) + + /*return HStack(alignment: .center, spacing: 0.0, content: { Text(text) .font(Font.system(size: 12.0)) .foregroundColor(getUpdatedTextColor()) - }).position(x: size.width / 2.0, y: size.height - 22.0 - 11.0).frame(width: size.width, height: 22.0, alignment: .leading) + }).position(x: size.width / 2.0, y: size.height - 11.0).frame(width: size.width, height: 22.0, alignment: .leading)*/ } func getSeparatorColor() -> Color { @@ -540,13 +743,12 @@ struct WidgetView: View { var body: some View { GeometryReader(content: { geometry in - return ZStack { + return VStack(alignment: .center, spacing: 0.0, content: { chatContentView(0, size: geometry.size) - chatContentView(1, size: geometry.size) chatSeparatorView(size: geometry.size) - chatsUpdateBackgroundView(size: geometry.size) - chatsUpdateTimestampView(size: geometry.size) - } + chatContentView(1, size: geometry.size) + chatUpdateView(size: geometry.size) + }) }) .padding(0.0) } @@ -600,7 +802,7 @@ func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData { case .recent: let appBundleIdentifier = Bundle.main.bundleIdentifier! guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { - return .placeholder + return .empty } let baseAppBundleId = String(appBundleIdentifier[.. PeersWidgetData { let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName) guard let appGroupUrl = maybeAppGroupUrl else { - return .placeholder + return .empty } let rootPath = rootPathForBasePath(appGroupUrl.path) - if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) { - return .locked - } + /*if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data) { + if state.isManuallyLocked || state.autolockTimeout != nil { + return .empty + } + }*/ let dataPath = rootPath + "/widget-data" if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) { switch widgetData.content { case let .peers(peers): - return .peers(peers) - case .disabled: - return .placeholder - case .notAuthorized: - return .locked + return .peers(ParsedPeers(accountId: widgetData.accountId, peers: peers)) + case .empty: + return .empty } } else { - return .placeholder + return .empty } case let .peers(peers): return .peers(peers) diff --git a/build-system/bazel-rules/apple_support b/build-system/bazel-rules/apple_support index f7f2b6d7c9..c1f83903e8 160000 --- a/build-system/bazel-rules/apple_support +++ b/build-system/bazel-rules/apple_support @@ -1 +1 @@ -Subproject commit f7f2b6d7c952f3cf6bdcedce6a0a2a40a27ff596 +Subproject commit c1f83903e864d753477e51d66d3ada6c2c6d096f diff --git a/build-system/bazel-rules/rules_apple b/build-system/bazel-rules/rules_apple index e2b1e1e439..49ef5bc098 160000 --- a/build-system/bazel-rules/rules_apple +++ b/build-system/bazel-rules/rules_apple @@ -1 +1 @@ -Subproject commit e2b1e1e4399bd168a00d1c3125eaa3ae52835340 +Subproject commit 49ef5bc098f68e40f730518f1c795d642e5c11a2 diff --git a/build-system/bazel-rules/rules_swift b/build-system/bazel-rules/rules_swift index bfb54953ce..e80b9795db 160000 --- a/build-system/bazel-rules/rules_swift +++ b/build-system/bazel-rules/rules_swift @@ -1 +1 @@ -Subproject commit bfb54953cee1bc985ba8113dd2e635e1d294abdb +Subproject commit e80b9795db5e35f86d643e0fba7776f1b1f71066 diff --git a/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m b/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m index 21fcdc9223..d16eb66790 100644 --- a/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m +++ b/submodules/LegacyComponents/Sources/TGPhotoEditorSliderView.m @@ -40,6 +40,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f; _startValue = 0.0f; _value = _startValue; _dotSize = 10.5f; + _minimumUndottedValue = -1; _lineSize = TGPhotoEditorSliderViewLineSize; _knobPadding = TGPhotoEditorSliderViewInternalMargin; @@ -174,7 +175,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f; CGContextSetBlendMode(context, kCGBlendModeCopy); } - if (_minimumUndottedValue > 0 && self.positionsCount > 1) { + if (_minimumUndottedValue > -1 && self.positionsCount > 1) { CGContextSetLineWidth(context, backFrame.size.height); CGContextSetLineCap(context, kCGLineCapRound); @@ -222,7 +223,7 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f; CGContextSetBlendMode(context, kCGBlendModeNormal); - if (_minimumUndottedValue > 0) { + if (_minimumUndottedValue > -1) { } else { CGContextSetFillColorWithColor(context, _trackColor.CGColor); [self drawRectangle:trackFrame cornerRadius:self.trackCornerRadius context:context]; @@ -359,6 +360,13 @@ const CGFloat TGPhotoEditorSliderViewInternalMargin = 7.0f; [self setNeedsDisplay]; } +- (void)setMinimumUndottedValue:(int)minimumUndottedValue { + if (_minimumUndottedValue != minimumUndottedValue) { + _minimumUndottedValue = minimumUndottedValue; + [self setNeedsDisplay]; + } +} + #pragma mark - Properties - (bool)isTracking diff --git a/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift b/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift index 09539ffa7d..54f0f96046 100644 --- a/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift +++ b/submodules/PeerInfoUI/Sources/PeerAutoremoveSetupScreen.swift @@ -148,7 +148,11 @@ private func peerAutoremoveSetupEntries(peer: Peer?, presentationData: Presentat if let channel = peer as? TelegramChannel, case .broadcast = channel.info { entries.append(.timeComment("Automatically delete messages sent in this channel after a certain period of time.")) } else { - entries.append(.timeComment("Automatically delete messages sent in this chat after a certain period of time.")) + if resolvedMaxValue != Int32.max { + entries.append(.timeComment("\(peer?.compactDisplayTitle ?? "") has set messages to auto-delete in \(timeIntervalString(strings: presentationData.strings, value: resolvedMaxValue)). You can't cancel it or make this interval longer.")) + } else { + entries.append(.timeComment("Automatically delete messages sent in this chat after a certain period of time.")) + } } if let user = peer as? TelegramUser { entries.append(.globalSwitch("Also auto-delete for \(user.compactDisplayTitle)", globalValue)) @@ -158,8 +162,13 @@ private func peerAutoremoveSetupEntries(peer: Peer?, presentationData: Presentat } public enum PeerAutoremoveSetupScreenResult { + public struct Updated { + public var myValue: Int32? + public var limitedByValue: Int32? + } + case unchanged - case updated(Int32?) + case updated(Updated) } public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, completion: @escaping (PeerAutoremoveSetupScreenResult) -> Void = { _ in }) -> ViewController { @@ -257,6 +266,13 @@ public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, c resolvedValue = nil } + let resolvedMaxValue: Int32 + if peer is TelegramUser { + resolvedMaxValue = peerValue + } else { + resolvedMaxValue = Int32.max + } + let resolvedGlobalValue = globalValue ?? defaultGlobalValue let signal = setChatMessageAutoremoveTimeoutInteractively(account: context.account, peerId: peerId, timeout: resolvedValue, isGlobal: resolvedGlobalValue) @@ -267,7 +283,10 @@ public func peerAutoremoveSetupScreen(context: AccountContext, peerId: PeerId, c }, completed: { dismissImpl?() if resolvedValue != resolvedDefaultValue { - completion(.updated(changedValue)) + completion(.updated(PeerAutoremoveSetupScreenResult.Updated( + myValue: resolvedValue, + limitedByValue: resolvedMaxValue == Int32.max ? nil : resolvedMaxValue + ))) } else { completion(.unchanged) } diff --git a/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift b/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift index 3bbb4b0aa3..9f1776f579 100644 --- a/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift +++ b/submodules/PeerInfoUI/Sources/PeerAutoremoveTimeoutItem.swift @@ -90,7 +90,7 @@ class PeerRemoveTimeoutItem: ListViewItem, ItemListItem { private func generateKnobImage() -> UIImage? { return generateImage(CGSize(width: 40.0, height: 40.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) - context.setShadow(offset: CGSize(width: 0.0, height: -1.0), blur: 3.5, color: UIColor(white: 0.0, alpha: 0.25).cgColor) + context.setShadow(offset: CGSize(width: 0.0, height: -3.0), blur: 8.0, color: UIColor(white: 0.0, alpha: 0.15).cgColor) context.setFillColor(UIColor.white.cgColor) context.fillEllipse(in: CGRect(origin: CGPoint(x: 6.0, y: 6.0), size: CGSize(width: 28.0, height: 28.0))) }) @@ -160,7 +160,7 @@ class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode { sliderView.value = mapTimeoutToSliderValue(item.value) - sliderView.minimumUndottedValue = 2 - Int32(mapTimeoutToSliderValue(item.maxValue)) + sliderView.minimumUndottedValue = Int32(mapTimeoutToSliderValue(item.maxValue)) sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor sliderView.backColor = item.theme.list.disclosureArrowColor @@ -287,6 +287,7 @@ class PeerRemoveTimeoutItemNode: ListViewItemNode, ItemListItemNode { if let sliderView = strongSelf.sliderView { sliderView.isUserInteractionEnabled = item.enabled sliderView.trackColor = item.enabled ? item.theme.list.itemAccentColor : item.theme.list.itemDisabledTextColor + sliderView.minimumUndottedValue = Int32(mapTimeoutToSliderValue(item.maxValue)) if themeUpdated { sliderView.backgroundColor = item.theme.list.itemBlocksBackgroundColor diff --git a/submodules/Postbox/Sources/AccountManagerMetadataTable.swift b/submodules/Postbox/Sources/AccountManagerMetadataTable.swift index 9f5eb28ba1..56407516f3 100644 --- a/submodules/Postbox/Sources/AccountManagerMetadataTable.swift +++ b/submodules/Postbox/Sources/AccountManagerMetadataTable.swift @@ -79,6 +79,17 @@ public enum PostboxAccessChallengeData: PostboxCoding, Equatable, Codable { return true } } + + public var lockId: String? { + switch self { + case .none: + return nil + case let .numericalPassword(value): + return "numericalPassword:\(value)" + case let .plaintextPassword(value): + return "plaintextPassword:\(value)" + } + } } public struct AuthAccountRecord: PostboxCoding, Codable { diff --git a/submodules/Postbox/Sources/ChatListView.swift b/submodules/Postbox/Sources/ChatListView.swift index 9cc854f9fd..772deea1f9 100644 --- a/submodules/Postbox/Sources/ChatListView.swift +++ b/submodules/Postbox/Sources/ChatListView.swift @@ -559,31 +559,31 @@ final class MutableChatListView { return self.sampledState.hole } - private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) -> MutableChatListEntry? { + private func renderEntry(_ entry: MutableChatListEntry, postbox: Postbox) -> MutableChatListEntry? { switch entry { case let .IntermediateMessageEntry(index, messageIndex): var renderedMessages: [Message] = [] if let messageIndex = messageIndex { if let messageGroup = postbox.messageHistoryTable.getMessageGroup(at: messageIndex, limit: 10) { - renderedMessages.append(contentsOf: messageGroup.compactMap(renderMessage)) + renderedMessages.append(contentsOf: messageGroup.compactMap(postbox.renderIntermediateMessage)) } } var peers = SimpleDictionary() var notificationSettings: PeerNotificationSettings? var presence: PeerPresence? var isContact: Bool = false - if let peer = getPeer(index.messageIndex.id.peerId) { + if let peer = postbox.peerTable.get(index.messageIndex.id.peerId) { peers[peer.id] = peer if let associatedPeerId = peer.associatedPeerId { - if let associatedPeer = getPeer(associatedPeerId) { + if let associatedPeer = postbox.peerTable.get(associatedPeerId) { peers[associatedPeer.id] = associatedPeer } - notificationSettings = getPeerNotificationSettings(associatedPeerId) - presence = getPeerPresence(associatedPeerId) + notificationSettings = postbox.peerNotificationSettingsTable.getEffective(associatedPeerId) + presence = postbox.peerPresenceTable.get(associatedPeerId) isContact = postbox.contactsTable.isContact(peerId: associatedPeerId) } else { - notificationSettings = getPeerNotificationSettings(index.messageIndex.id.peerId) - presence = getPeerPresence(index.messageIndex.id.peerId) + notificationSettings = postbox.peerNotificationSettingsTable.getEffective(index.messageIndex.id.peerId) + presence = postbox.peerPresenceTable.get(index.messageIndex.id.peerId) isContact = postbox.contactsTable.isContact(peerId: peer.id) } } @@ -597,9 +597,9 @@ final class MutableChatListView { } } - func render(postbox: Postbox, renderMessage: (IntermediateMessage) -> Message, getPeer: (PeerId) -> Peer?, getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings?, getPeerPresence: (PeerId) -> PeerPresence?) { + func render(postbox: Postbox) { for i in 0 ..< self.additionalItemEntries.count { - if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox, renderMessage: renderMessage, getPeer: getPeer, getPeerNotificationSettings: getPeerNotificationSettings, getPeerPresence: getPeerPresence) { + if let updatedEntry = self.renderEntry(self.additionalItemEntries[i].entry, postbox: postbox) { self.additionalItemEntries[i].entry = updatedEntry } } diff --git a/submodules/Postbox/Sources/ContactPeersView.swift b/submodules/Postbox/Sources/ContactPeersView.swift index 5814a27d60..3cbf0fe0a2 100644 --- a/submodules/Postbox/Sources/ContactPeersView.swift +++ b/submodules/Postbox/Sources/ContactPeersView.swift @@ -15,7 +15,7 @@ final class MutableContactPeersView { self.includePresences = includePresences } - func replay(replacePeerIds: Set?, updatedPeerPresences: [PeerId: PeerPresence], getPeer: (PeerId) -> Peer?, getPeerPresence: (PeerId) -> PeerPresence?) -> Bool { + func replay(postbox: Postbox, replacePeerIds: Set?, updatedPeerPresences: [PeerId: PeerPresence]) -> Bool { var updated = false if let replacePeerIds = replacePeerIds { let removedPeerIds = self.peerIds.subtracting(replacePeerIds) @@ -29,11 +29,11 @@ final class MutableContactPeersView { } for peerId in addedPeerIds { - if let peer = getPeer(peerId) { + if let peer = postbox.peerTable.get(peerId) { self.peers[peerId] = peer } if self.includePresences { - if let presence = getPeerPresence(peerId) { + if let presence = postbox.peerPresenceTable.get(peerId) { self.peerPresences[peerId] = presence } } diff --git a/submodules/Postbox/Sources/MessageView.swift b/submodules/Postbox/Sources/MessageView.swift index 7b1eee9d76..3bbd9a8e66 100644 --- a/submodules/Postbox/Sources/MessageView.swift +++ b/submodules/Postbox/Sources/MessageView.swift @@ -11,7 +11,7 @@ final class MutableMessageView { self.stableId = message?.stableId } - func replay(_ operations: [MessageHistoryOperation], updatedMedia: [MediaId: Media?], renderIntermediateMessage: (IntermediateMessage) -> Message) -> Bool { + func replay(postbox: Postbox, operations: [MessageHistoryOperation], updatedMedia: [MediaId: Media?]) -> Bool { var updated = false for operation in operations { switch operation { @@ -28,7 +28,7 @@ final class MutableMessageView { } case let .InsertMessage(message): if message.id == self.messageId || message.stableId == self.stableId { - self.message = renderIntermediateMessage(message) + self.message = postbox.renderIntermediateMessage(message) self.stableId = message.stableId updated = true } diff --git a/submodules/Postbox/Sources/PeerMergedOperationLogView.swift b/submodules/Postbox/Sources/PeerMergedOperationLogView.swift index 155b5e3da4..01bfcf965d 100644 --- a/submodules/Postbox/Sources/PeerMergedOperationLogView.swift +++ b/submodules/Postbox/Sources/PeerMergedOperationLogView.swift @@ -6,14 +6,14 @@ final class MutablePeerMergedOperationLogView { var tailIndex: Int32? let limit: Int - init(tag: PeerOperationLogTag, limit: Int, getOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], getTailIndex: (PeerOperationLogTag) -> Int32?) { + init(postbox: Postbox, tag: PeerOperationLogTag, limit: Int) { self.tag = tag - self.entries = getOperations(tag, 0, limit) - self.tailIndex = getTailIndex(tag) + self.entries = postbox.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: 0, limit: limit) + self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: tag) self.limit = limit } - func replay(operations: [PeerMergedOperationLogOperation], getOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], getTailIndex: (PeerOperationLogTag) -> Int32?) -> Bool { + func replay(postbox: Postbox, operations: [PeerMergedOperationLogOperation]) -> Bool { var updated = false var invalidatedTail = false @@ -65,7 +65,7 @@ final class MutablePeerMergedOperationLogView { if updated { if invalidatedTail { - self.tailIndex = getTailIndex(self.tag) + self.tailIndex = postbox.peerMergedOperationLogIndexTable.tailIndex(tag: self.tag) } if self.entries.count < self.limit { if let tailIndex = self.tailIndex { @@ -74,7 +74,7 @@ final class MutablePeerMergedOperationLogView { if !self.entries.isEmpty { fromIndex = self.entries.last!.mergedIndex + 1 } - for entry in getOperations(self.tag, fromIndex, self.limit - self.entries.count) { + for entry in postbox.peerOperationLogTable.getMergedEntries(tag: self.tag, fromIndex: fromIndex, limit: self.limit - self.entries.count) { self.entries.append(entry) } for i in 0 ..< self.entries.count { diff --git a/submodules/Postbox/Sources/Postbox.swift b/submodules/Postbox/Sources/Postbox.swift index 65d183d15e..dd6c9087e3 100644 --- a/submodules/Postbox/Sources/Postbox.swift +++ b/submodules/Postbox/Sources/Postbox.swift @@ -1058,6 +1058,25 @@ public final class Transaction { return postbox.chatListTable.getNamespaceEntries(groupId: groupId, namespace: namespace, summaryTag: summaryTag, messageIndexTable: postbox.messageHistoryIndexTable, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, readStateTable: postbox.readStateTable, summaryTable: postbox.messageHistoryTagsSummaryTable) } + public func getTopChatListEntries(groupId: PeerGroupId, count: Int) -> [RenderedPeer] { + assert(!self.disposed) + guard let postbox = self.postbox else { + return [] + } + return postbox.chatListTable.earlierEntryInfos(groupId: groupId, index: nil, messageHistoryTable: postbox.messageHistoryTable, peerChatInterfaceStateTable: postbox.peerChatInterfaceStateTable, count: count).compactMap { entry -> RenderedPeer? in + switch entry { + case let .message(index, _): + if let peer = self.getPeer(index.messageIndex.id.peerId) { + return RenderedPeer(peer: peer) + } else { + return nil + } + case .hole: + return nil + } + } + } + public func addHolesEverywhere(peerNamespaces: [PeerId.Namespace], holeNamespace: MessageId.Namespace) { assert(!self.disposed) self.postbox?.addHolesEverywhere(peerNamespaces: peerNamespaces, holeNamespace: holeNamespace) @@ -1067,6 +1086,11 @@ public final class Transaction { assert(!self.disposed) self.postbox?.reindexUnreadCounters() } + + public func searchPeers(query: String) -> [RenderedPeer] { + assert(!self.disposed) + return self.postbox?.searchPeers(query: query) ?? [] + } } public enum PostboxResult { @@ -1481,27 +1505,13 @@ public final class Postbox { self.transactionStateVersion = self.metadataTable.transactionStateVersion() - self.viewTracker = ViewTracker(queue: self.queue, renderMessage: self.renderIntermediateMessage, getPeer: { peerId in - return self.peerTable.get(peerId) - }, getPeerNotificationSettings: { peerId in - return self.peerNotificationSettingsTable.getEffective(peerId) - }, getCachedPeerData: { peerId in - return self.cachedPeerDataTable.get(peerId) - }, getPeerPresence: { peerId in - return self.peerPresenceTable.get(peerId) - }, getPeerReadState: { peerId in - return self.readStateTable.getCombinedState(peerId) - }, operationLogGetOperations: { tag, fromIndex, limit in - return self.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: fromIndex, limit: limit) - }, operationLogGetTailIndex: { tag in - return self.peerMergedOperationLogIndexTable.tailIndex(tag: tag) - }, getTimestampBasedMessageAttributesHead: { tag in - return self.timestampBasedMessageAttributesTable.head(tag: tag) - }, getPreferencesEntry: { key in - return self.preferencesTable.get(key: key) - }, unsentMessageIds: self.messageHistoryUnsentTable.get(), synchronizePeerReadStateOperations: self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in - return self.readStateTable.getCombinedState(peerId) - })) + self.viewTracker = ViewTracker( + queue: self.queue, + unsentMessageIds: self.messageHistoryUnsentTable.get(), + synchronizePeerReadStateOperations: self.synchronizeReadStateTable.get(getCombinedPeerReadState: { peerId in + return self.readStateTable.getCombinedState(peerId) + }) + ) print("(Postbox initialization took \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0) ms") @@ -2784,13 +2794,7 @@ public final class Postbox { public func aroundChatListView(groupId: PeerGroupId, filterPredicate: ChatListFilterPredicate? = nil, index: ChatListIndex, count: Int, summaryComponents: ChatListEntrySummaryComponents, userInteractive: Bool = false) -> Signal<(ChatListView, ViewUpdateType), NoError> { return self.transactionSignal(userInteractive: userInteractive, { subscriber, transaction in let mutableView = MutableChatListView(postbox: self, groupId: groupId, filterPredicate: filterPredicate, aroundIndex: index, count: count, summaryComponents: summaryComponents) - mutableView.render(postbox: self, renderMessage: self.renderIntermediateMessage, getPeer: { id in - return self.peerTable.get(id) - }, getPeerNotificationSettings: { - self.peerNotificationSettingsTable.getEffective($0) - }, getPeerPresence: { - self.peerPresenceTable.get($0) - }) + mutableView.render(postbox: self) let (index, signal) = self.viewTracker.addChatListView(mutableView) @@ -2890,52 +2894,56 @@ public final class Postbox { public func searchPeers(query: String) -> Signal<[RenderedPeer], NoError> { return self.transaction { transaction -> Signal<[RenderedPeer], NoError> in - var peerIds = Set() - var chatPeers: [RenderedPeer] = [] - - var (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable) - - var additionalChatPeerIds: [PeerId] = [] - for peerId in chatPeerIds { - for associatedId in self.reverseAssociatedPeerTable.get(peerId: peerId) { - let inclusionIndex = self.chatListIndexTable.get(peerId: associatedId) - if inclusionIndex.includedIndex(peerId: associatedId) != nil { - additionalChatPeerIds.append(associatedId) - } + return .single(transaction.searchPeers(query: query)) + } |> switchToLatest + } + + fileprivate func searchPeers(query: String) -> [RenderedPeer] { + var peerIds = Set() + var chatPeers: [RenderedPeer] = [] + + var (chatPeerIds, contactPeerIds) = self.peerNameIndexTable.matchingPeerIds(tokens: (regular: stringIndexTokens(query, transliteration: .none), transliterated: stringIndexTokens(query, transliteration: .transliterated)), categories: [.chats, .contacts], chatListIndexTable: self.chatListIndexTable, contactTable: self.contactsTable) + + var additionalChatPeerIds: [PeerId] = [] + for peerId in chatPeerIds { + for associatedId in self.reverseAssociatedPeerTable.get(peerId: peerId) { + let inclusionIndex = self.chatListIndexTable.get(peerId: associatedId) + if inclusionIndex.includedIndex(peerId: associatedId) != nil { + additionalChatPeerIds.append(associatedId) } } - chatPeerIds.append(contentsOf: additionalChatPeerIds) - - for peerId in chatPeerIds { + } + chatPeerIds.append(contentsOf: additionalChatPeerIds) + + for peerId in chatPeerIds { + if let peer = self.peerTable.get(peerId) { + var peers = SimpleDictionary() + peers[peer.id] = peer + if let associatedPeerId = peer.associatedPeerId { + if let associatedPeer = self.peerTable.get(associatedPeerId) { + peers[associatedPeer.id] = associatedPeer + } + } + chatPeers.append(RenderedPeer(peerId: peer.id, peers: peers)) + peerIds.insert(peerId) + } + } + + var contactPeers: [RenderedPeer] = [] + for peerId in contactPeerIds { + if !peerIds.contains(peerId) { if let peer = self.peerTable.get(peerId) { var peers = SimpleDictionary() peers[peer.id] = peer - if let associatedPeerId = peer.associatedPeerId { - if let associatedPeer = self.peerTable.get(associatedPeerId) { - peers[associatedPeer.id] = associatedPeer - } - } - chatPeers.append(RenderedPeer(peerId: peer.id, peers: peers)) - peerIds.insert(peerId) + contactPeers.append(RenderedPeer(peerId: peer.id, peers: peers)) } } - - var contactPeers: [RenderedPeer] = [] - for peerId in contactPeerIds { - if !peerIds.contains(peerId) { - if let peer = self.peerTable.get(peerId) { - var peers = SimpleDictionary() - peers[peer.id] = peer - contactPeers.append(RenderedPeer(peerId: peer.id, peers: peers)) - } - } - } - - contactPeers.sort(by: { lhs, rhs in - lhs.peers[lhs.peerId]!.indexName.indexName(.lastNameFirst) < rhs.peers[rhs.peerId]!.indexName.indexName(.lastNameFirst) - }) - return .single(chatPeers + contactPeers) - } |> switchToLatest + } + + contactPeers.sort(by: { lhs, rhs in + lhs.peers[lhs.peerId]!.indexName.indexName(.lastNameFirst) < rhs.peers[rhs.peerId]!.indexName.indexName(.lastNameFirst) + }) + return chatPeers + contactPeers } public func peerView(id: PeerId) -> Signal { @@ -3128,11 +3136,7 @@ public final class Postbox { public func mergedOperationLogView(tag: PeerOperationLogTag, limit: Int) -> Signal { return self.transactionSignal { subscriber, transaction in - let view = MutablePeerMergedOperationLogView(tag: tag, limit: limit, getOperations: { tag, fromIndex, limit in - return self.peerOperationLogTable.getMergedEntries(tag: tag, fromIndex: fromIndex, limit: limit) - }, getTailIndex: { tag in - return self.peerMergedOperationLogIndexTable.tailIndex(tag: tag) - }) + let view = MutablePeerMergedOperationLogView(postbox: self, tag: tag, limit: limit) subscriber.putNext(PeerMergedOperationLogView(view)) @@ -3155,9 +3159,7 @@ public final class Postbox { public func timestampBasedMessageAttributesView(tag: UInt16) -> Signal { return self.transactionSignal { subscriber, transaction in - let view = MutableTimestampBasedMessageAttributesView(tag: tag, getHead: { tag in - return self.timestampBasedMessageAttributesTable.head(tag: tag) - }) + let view = MutableTimestampBasedMessageAttributesView(postbox: self, tag: tag) let (index, signal) = self.viewTracker.addTimestampBasedMessageAttributesView(view) subscriber.putNext(TimestampBasedMessageAttributesView(view)) diff --git a/submodules/Postbox/Sources/TimestampBasedMessageAttributesView.swift b/submodules/Postbox/Sources/TimestampBasedMessageAttributesView.swift index c8fe4a4036..bbff66b6d0 100644 --- a/submodules/Postbox/Sources/TimestampBasedMessageAttributesView.swift +++ b/submodules/Postbox/Sources/TimestampBasedMessageAttributesView.swift @@ -4,12 +4,12 @@ final class MutableTimestampBasedMessageAttributesView { let tag: UInt16 var head: TimestampBasedMessageAttributesEntry? - init(tag: UInt16, getHead: (UInt16) -> TimestampBasedMessageAttributesEntry?) { + init(postbox: Postbox, tag: UInt16) { self.tag = tag - self.head = getHead(tag) + self.head = postbox.timestampBasedMessageAttributesTable.head(tag: tag) } - func replay(operations: [TimestampBasedMessageAttributesOperation], getHead: (UInt16) -> TimestampBasedMessageAttributesEntry?) -> Bool { + func replay(postbox: Postbox, operations: [TimestampBasedMessageAttributesOperation]) -> Bool { var updated = false var invalidatedHead = false for operation in operations { @@ -37,7 +37,7 @@ final class MutableTimestampBasedMessageAttributesView { } } if invalidatedHead { - self.head = getHead(self.tag) + self.head = postbox.timestampBasedMessageAttributesTable.head(tag: self.tag) } return updated } diff --git a/submodules/Postbox/Sources/ViewTracker.swift b/submodules/Postbox/Sources/ViewTracker.swift index 0abae1dd7d..c76ff7dd21 100644 --- a/submodules/Postbox/Sources/ViewTracker.swift +++ b/submodules/Postbox/Sources/ViewTracker.swift @@ -11,15 +11,6 @@ public enum ViewUpdateType { final class ViewTracker { private let queue: Queue - private let renderMessage: (IntermediateMessage) -> Message - private let getPeer: (PeerId) -> Peer? - private let getPeerNotificationSettings: (PeerId) -> PeerNotificationSettings? - private let getCachedPeerData: (PeerId) -> CachedPeerData? - private let getPeerPresence: (PeerId) -> PeerPresence? - private let getPeerReadState: (PeerId) -> CombinedPeerReadState? - private let operationLogGetOperations: (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry] - private let operationLogGetTailIndex: (PeerOperationLogTag) -> Int32? - private let getPreferencesEntry: (ValueBoxKey) -> PreferencesEntry? private var chatListViews = Bag<(MutableChatListView, ValuePipe<(ChatListView, ViewUpdateType)>)>() private var messageHistoryViews = Bag<(MutableMessageHistoryView, ValuePipe<(MessageHistoryView, ViewUpdateType)>)>() @@ -46,7 +37,6 @@ final class ViewTracker { private var unreadMessageCountsViews = Bag<(MutableUnreadMessageCountsView, ValuePipe)>() private var peerMergedOperationLogViews = Bag<(MutablePeerMergedOperationLogView, ValuePipe)>() - private let getTimestampBasedMessageAttributesHead: (UInt16) -> TimestampBasedMessageAttributesEntry? private var timestampBasedMessageAttributesViews = Bag<(MutableTimestampBasedMessageAttributesView, ValuePipe)>() private var combinedViews = Bag<(CombinedMutableView, ValuePipe)>() @@ -56,23 +46,14 @@ final class ViewTracker { private var preferencesViews = Bag<(MutablePreferencesView, ValuePipe)>() private var multiplePeersViews = Bag<(MutableMultiplePeersView, ValuePipe)>() private var itemCollectionsViews = Bag<(MutableItemCollectionsView, ValuePipe)>() - - private var failedMessageIdsViews = Bag<(MutableFailedMessageIdsView, ValuePipe)>() - - init(queue: Queue, renderMessage: @escaping (IntermediateMessage) -> Message, getPeer: @escaping (PeerId) -> Peer?, getPeerNotificationSettings: @escaping (PeerId) -> PeerNotificationSettings?, getCachedPeerData: @escaping (PeerId) -> CachedPeerData?, getPeerPresence: @escaping (PeerId) -> PeerPresence?, getPeerReadState: @escaping (PeerId) -> CombinedPeerReadState?, operationLogGetOperations: @escaping (PeerOperationLogTag, Int32, Int) -> [PeerMergedOperationLogEntry], operationLogGetTailIndex: @escaping (PeerOperationLogTag) -> Int32?, getTimestampBasedMessageAttributesHead: @escaping (UInt16) -> TimestampBasedMessageAttributesEntry?, getPreferencesEntry: @escaping (ValueBoxKey) -> PreferencesEntry?, unsentMessageIds: [MessageId], synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation]) { + init( + queue: Queue, + unsentMessageIds: [MessageId], + synchronizePeerReadStateOperations: [PeerId: PeerReadStateSynchronizationOperation] + ) { self.queue = queue - self.renderMessage = renderMessage - self.getPeer = getPeer - self.getPeerNotificationSettings = getPeerNotificationSettings - self.getCachedPeerData = getCachedPeerData - self.getPeerPresence = getPeerPresence - self.getPeerReadState = getPeerReadState - self.operationLogGetOperations = operationLogGetOperations - self.operationLogGetTailIndex = operationLogGetTailIndex - self.getTimestampBasedMessageAttributesHead = getTimestampBasedMessageAttributesHead - self.getPreferencesEntry = getPreferencesEntry self.unsentMessageView = UnsentMessageHistoryView(ids: unsentMessageIds) self.synchronizeReadStatesView = MutableSynchronizePeerReadStatesView(operations: synchronizePeerReadStateOperations) @@ -254,9 +235,7 @@ final class ViewTracker { for (mutableView, pipe) in self.chatListViews.copyItems() { if mutableView.refreshDueToExternalTransaction(postbox: postbox) { - mutableView.render(postbox: postbox, renderMessage: self.renderMessage, getPeer: { id in - return self.getPeer(id) - }, getPeerNotificationSettings: self.getPeerNotificationSettings, getPeerPresence: self.getPeerPresence) + mutableView.render(postbox: postbox) pipe.putNext((ChatListView(mutableView), .Generic)) } } @@ -350,7 +329,7 @@ final class ViewTracker { for (mutableView, pipe) in self.messageViews.copyItems() { let operations = transaction.currentOperationsByPeerId[mutableView.messageId.peerId] if operations != nil || !transaction.updatedMedia.isEmpty || !transaction.currentUpdatedCachedPeerData.isEmpty { - if mutableView.replay(operations ?? [], updatedMedia: transaction.updatedMedia, renderIntermediateMessage: self.renderMessage) { + if mutableView.replay(postbox: postbox, operations: operations ?? [], updatedMedia: transaction.updatedMedia) { pipe.putNext(MessageView(mutableView)) } } @@ -360,9 +339,7 @@ final class ViewTracker { let context = MutableChatListViewReplayContext() if mutableView.replay(postbox: postbox, operations: transaction.chatListOperations, updatedPeerNotificationSettings: transaction.currentUpdatedPeerNotificationSettings, updatedPeers: transaction.currentUpdatedPeers, updatedPeerPresences: transaction.currentUpdatedPeerPresences, transaction: transaction, context: context) { mutableView.complete(postbox: postbox, context: context) - mutableView.render(postbox: postbox, renderMessage: self.renderMessage, getPeer: { id in - return self.getPeer(id) - }, getPeerNotificationSettings: self.getPeerNotificationSettings, getPeerPresence: self.getPeerPresence) + mutableView.render(postbox: postbox) pipe.putNext((ChatListView(mutableView), .Generic)) } } @@ -396,7 +373,7 @@ final class ViewTracker { } for (mutableView, pipe) in self.contactPeersViews.copyItems() { - if mutableView.replay(replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences, getPeer: self.getPeer, getPeerPresence: self.getPeerPresence) { + if mutableView.replay(postbox: postbox, replacePeerIds: transaction.replaceContactPeerIds, updatedPeerPresences: transaction.currentUpdatedPeerPresences) { pipe.putNext(ContactPeersView(mutableView)) } } @@ -408,13 +385,13 @@ final class ViewTracker { } for (mutableView, pipe) in self.peerMergedOperationLogViews.copyItems() { - if mutableView.replay(operations: transaction.currentPeerMergedOperationLogOperations, getOperations: self.operationLogGetOperations, getTailIndex: self.operationLogGetTailIndex) { + if mutableView.replay(postbox: postbox, operations: transaction.currentPeerMergedOperationLogOperations) { pipe.putNext(PeerMergedOperationLogView(mutableView)) } } for (mutableView, pipe) in self.timestampBasedMessageAttributesViews.copyItems() { - if mutableView.replay(operations: transaction.currentTimestampBasedMessageAttributesOperations, getHead: self.getTimestampBasedMessageAttributesHead) { + if mutableView.replay(postbox: postbox, operations: transaction.currentTimestampBasedMessageAttributesOperations) { pipe.putNext(TimestampBasedMessageAttributesView(mutableView)) } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index e3bc357249..c05c53b1b4 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -571,7 +571,7 @@ public final class VoiceChatController: ViewController { private let dimNode: ASDisplayNode private let contentContainer: ASDisplayNode private let backgroundNode: ASDisplayNode - private let mainVideoContainer: MainVideoContainerNode + private var mainVideoContainer: MainVideoContainerNode? private let listNode: ListView private let topPanelNode: ASDisplayNode private let topPanelEdgeNode: ASDisplayNode @@ -672,7 +672,9 @@ public final class VoiceChatController: ViewController { self.backgroundNode.backgroundColor = secondaryPanelBackgroundColor self.backgroundNode.clipsToBounds = false - self.mainVideoContainer = MainVideoContainerNode(context: call.accountContext, call: call) + if false { + self.mainVideoContainer = MainVideoContainerNode(context: call.accountContext, call: call) + } self.listNode = ListView() self.listNode.verticalScrollIndicatorColor = UIColor(white: 1.0, alpha: 0.3) @@ -763,11 +765,11 @@ public final class VoiceChatController: ViewController { if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != source { strongSelf.currentDominantSpeakerWithVideo = (peerId, source) strongSelf.call.setFullSizeVideo(peerId: peerId) - strongSelf.mainVideoContainer.updatePeer(peer: (peerId: peerId, source: source)) + strongSelf.mainVideoContainer?.updatePeer(peer: (peerId: peerId, source: source)) } else { strongSelf.currentDominantSpeakerWithVideo = nil strongSelf.call.setFullSizeVideo(peerId: nil) - strongSelf.mainVideoContainer.updatePeer(peer: nil) + strongSelf.mainVideoContainer?.updatePeer(peer: nil) } } default: @@ -1029,7 +1031,7 @@ public final class VoiceChatController: ViewController { }), true)) } - items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in + /*items.append(.action(ContextMenuActionItem(text: "Toggle Full Screen", icon: { theme in return nil }, action: { _, f in guard let strongSelf = self else { @@ -1038,7 +1040,7 @@ public final class VoiceChatController: ViewController { strongSelf.itemInteraction?.openPeer(peer.id) f(.default) - }))) + })))*/ if peer.id != strongSelf.context.account.peerId { if let callState = strongSelf.callState, (callState.canManageCall || callState.adminIds.contains(strongSelf.context.account.peerId)) { @@ -1203,7 +1205,7 @@ public final class VoiceChatController: ViewController { self.bottomPanelNode.addSubnode(self.bottomCornersNode) self.bottomPanelNode.addSubnode(self.bottomPanelBackgroundNode) self.bottomPanelNode.addSubnode(self.audioOutputNode) - self.bottomPanelNode.addSubnode(self.cameraButtonNode) + //self.bottomPanelNode.addSubnode(self.cameraButtonNode) self.bottomPanelNode.addSubnode(self.leaveNode) self.bottomPanelNode.addSubnode(self.actionButton) @@ -1212,7 +1214,9 @@ public final class VoiceChatController: ViewController { self.contentContainer.addSubnode(self.backgroundNode) self.contentContainer.addSubnode(self.listNode) - self.contentContainer.addSubnode(self.mainVideoContainer) + if let mainVideoContainer = self.mainVideoContainer { + self.contentContainer.addSubnode(mainVideoContainer) + } self.contentContainer.addSubnode(self.topPanelNode) self.contentContainer.addSubnode(self.leftBorderNode) self.contentContainer.addSubnode(self.rightBorderNode) @@ -1347,7 +1351,7 @@ public final class VoiceChatController: ViewController { if strongSelf.currentDominantSpeakerWithVideo?.0 != peerId || strongSelf.currentDominantSpeakerWithVideo?.1 != source { strongSelf.currentDominantSpeakerWithVideo = (peerId, source) strongSelf.call.setFullSizeVideo(peerId: peerId) - strongSelf.mainVideoContainer.updatePeer(peer: (peerId: peerId, source: source)) + strongSelf.mainVideoContainer?.updatePeer(peer: (peerId: peerId, source: source)) } } @@ -1578,7 +1582,7 @@ public final class VoiceChatController: ViewController { if !validSources.contains(source) { strongSelf.currentDominantSpeakerWithVideo = nil strongSelf.call.setFullSizeVideo(peerId: nil) - strongSelf.mainVideoContainer.updatePeer(peer: nil) + strongSelf.mainVideoContainer?.updatePeer(peer: nil) } } @@ -1589,8 +1593,8 @@ public final class VoiceChatController: ViewController { } })) - self.isFullscreen = true - self.isExpanded = true + //self.isFullscreen = true + //self.isExpanded = true } deinit { @@ -1862,9 +1866,11 @@ public final class VoiceChatController: ViewController { let panelOffset = max(layoutTopInset, rawPanelOffset) let topPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: panelOffset), size: CGSize(width: size.width, height: topPanelHeight)) - let videoContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: layout.size.width, height: 200.0)) - transition.updateFrameAdditive(node: self.mainVideoContainer, frame: videoContainerFrame) - self.mainVideoContainer.update(size: videoContainerFrame.size, transition: transition) + if let mainVideoContainer = self.mainVideoContainer { + let videoContainerFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: layout.size.width, height: 200.0)) + transition.updateFrameAdditive(node: mainVideoContainer, frame: videoContainerFrame) + mainVideoContainer.update(size: videoContainerFrame.size, transition: transition) + } let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: topPanelFrame.maxY), size: CGSize(width: size.width, height: layout.size.height)) let sideInset: CGFloat = 16.0 @@ -2132,7 +2138,10 @@ public final class VoiceChatController: ViewController { } let bottomPanelHeight = bottomAreaHeight + layout.intrinsicInsets.bottom - let listTopInset = layoutTopInset + topPanelHeight + 200.0 + var listTopInset = layoutTopInset + topPanelHeight + if self.mainVideoContainer != nil { + listTopInset += 200.0 + } let listSize = CGSize(width: size.width, height: layout.size.height - listTopInset - bottomPanelHeight) let topInset: CGFloat @@ -2237,14 +2246,20 @@ public final class VoiceChatController: ViewController { let sideButtonOrigin = max(sideButtonMinimalInset, floor((size.width - 144.0) / 2.0) - sideButtonOffset - sideButtonSize.width) if self.audioOutputNode.supernode === self.bottomPanelNode { - let cameraButtonDistance: CGFloat = 4.0 + if true { + let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize) + transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame) + } else { + let cameraButtonDistance: CGFloat = 4.0 + + let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height - cameraButtonDistance - cameraButtonSize.height) / 2.0) + cameraButtonDistance + cameraButtonSize.height), size: sideButtonSize) + + transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame) + + transition.updateFrame(node: self.cameraButtonNode, frame: CGRect(origin: CGPoint(x: floor(audioOutputFrame.midX - cameraButtonSize.width / 2.0), y: audioOutputFrame.minY - cameraButtonDistance - cameraButtonSize.height), size: cameraButtonSize)) + } - let audioOutputFrame = CGRect(origin: CGPoint(x: sideButtonOrigin, y: floor((bottomAreaHeight - sideButtonSize.height - cameraButtonDistance - cameraButtonSize.height) / 2.0) + cameraButtonDistance + cameraButtonSize.height), size: sideButtonSize) - - transition.updateFrame(node: self.audioOutputNode, frame: audioOutputFrame) transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: size.width - sideButtonOrigin - sideButtonSize.width, y: floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)) - - transition.updateFrame(node: self.cameraButtonNode, frame: CGRect(origin: CGPoint(x: floor(audioOutputFrame.midX - cameraButtonSize.width / 2.0), y: audioOutputFrame.minY - cameraButtonDistance - cameraButtonSize.height), size: cameraButtonSize)) } if isFirstTime { while !self.enqueuedTransitions.isEmpty { @@ -2276,7 +2291,7 @@ public final class VoiceChatController: ViewController { self.cameraButtonNode.layer.removeAllAnimations() self.leaveNode.layer.removeAllAnimations() self.bottomPanelNode.addSubnode(self.audioOutputNode) - self.bottomPanelNode.addSubnode(self.cameraButtonNode) + //self.bottomPanelNode.addSubnode(self.cameraButtonNode) self.bottomPanelNode.addSubnode(self.leaveNode) self.bottomPanelNode.addSubnode(self.actionButton) self.containerLayoutUpdated(layout, navigationHeight :navigationHeight, transition: .immediate) diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 7edfec93fb..1ce692f6bf 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -371,7 +371,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, _): + case let .message(flags, id, fromId, chatPeerId, fwdFrom, viaBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, restrictionReason, ttlPeriod): let resolvedFromId = fromId?.peerId ?? chatPeerId.peerId let peerId: PeerId @@ -468,19 +468,29 @@ extension StoreMessage { var consumableContent: (Bool, Bool)? = nil + var resolvedTtlPeriod: Int32? = ttlPeriod + if let media = media { let (mediaValue, expirationTimer) = textMediaAndExpirationTimerFromApiMedia(media, peerId) if let mediaValue = mediaValue { medias.append(mediaValue) if let expirationTimer = expirationTimer, expirationTimer > 0 { - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil)) + if let resolvedTtlPeriodValue = resolvedTtlPeriod { + resolvedTtlPeriod = min(resolvedTtlPeriodValue, expirationTimer) + } else { + resolvedTtlPeriod = expirationTimer + } consumableContent = (true, false) } } } + if let resolvedTtlPeriod = resolvedTtlPeriod { + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: resolvedTtlPeriod, countdownBeginTime: ttlPeriod == nil ? nil : date)) + } + if let postAuthor = postAuthor { attributes.append(AuthorSignatureMessageAttribute(signature: postAuthor)) } diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index ff1a6f8c0d..dc79d3c7f4 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -212,7 +212,7 @@ swift_library( "//submodules/SlotMachineAnimationNode:SlotMachineAnimationNode", "//submodules/AnimatedNavigationStripeNode:AnimatedNavigationStripeNode", "//submodules/AudioBlob:AudioBlob", - "//Telegram:GeneratedSources", + "//:GeneratedSources", "//third-party/ZipArchive:ZipArchive", "//submodules/ChatImportUI:ChatImportUI", "//submodules/ChatHistoryImportTasks:ChatHistoryImportTasks", diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 4512c229f6..b91a333be0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5505,10 +5505,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } if canSetupAutoremoveTimeout { - strongSelf.chatDisplayNode.dismissInput() - - let controller = peerAutoremoveSetupScreen(context: strongSelf.context, peerId: peerId) - strongSelf.push(controller) + strongSelf.presentAutoremoveSetup() } else if let currentAutoremoveTimeout = currentAutoremoveTimeout, let rect = strongSelf.chatDisplayNode.frameForInputPanelAccessoryButton(.messageAutoremoveTimeout(currentAutoremoveTimeout)) { //TODO:localize @@ -7535,13 +7532,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } + var isClearCache = false let text: String if peerId == self.context.account.peerId { text = self.presentationData.strings.Conversation_ClearSelfHistory } else if peerId.namespace == Namespaces.Peer.SecretChat { text = self.presentationData.strings.Conversation_ClearSecretHistory } else if peerId.namespace == Namespaces.Peer.CloudGroup || peerId.namespace == Namespaces.Peer.CloudChannel { - text = self.presentationData.strings.Conversation_ClearGroupHistory + if let channel = peer.peer as? TelegramChannel, case .broadcast = channel.info { + isClearCache = true + text = self.presentationData.strings.Conversation_ClearCache + } else { + text = self.presentationData.strings.Conversation_ClearGroupHistory + } } else { text = self.presentationData.strings.Conversation_ClearPrivateHistory } @@ -7631,20 +7634,23 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } else { items.append(ActionSheetTextItem(title: text)) - items.append(ActionSheetButtonItem(title: self.presentationData.strings.Conversation_ClearAll, color: .destructive, action: { [weak self, weak actionSheet] in + items.append(ActionSheetButtonItem(title: isClearCache ? self.presentationData.strings.Conversation_ClearCache : self.presentationData.strings.Conversation_ClearAll, color: isClearCache ? .accent : .destructive, action: { [weak self, weak actionSheet] in actionSheet?.dismissAnimated() guard let strongSelf = self else { return } - - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { - }), - TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { - beginClear(.forLocalPeer) - }) - ], parseMarkdown: true), in: .window(.root)) + if isClearCache { + strongSelf.navigationButtonAction(.clearCache) + } else { + strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationTitle, text: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationText, actions: [ + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: { + }), + TextAlertAction(type: .destructiveAction, title: strongSelf.presentationData.strings.ChatList_DeleteSavedMessagesConfirmationAction, action: { + beginClear(.forLocalPeer) + }) + ], parseMarkdown: true), in: .window(.root)) + } })) } @@ -7691,19 +7697,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G actionSheet.dismissAnimated() - let controller = peerAutoremoveSetupScreen(context: strongSelf.context, peerId: peer.id, completion: { updatedValue in - if case let .updated(value) = updatedValue { - guard let strongSelf = self else { - return - } - - if let value = value { - strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: value))").0), elevatedLayout: false, action: { _ in return false }), in: .current) - } - } - }) - strongSelf.chatDisplayNode.dismissInput() - strongSelf.push(controller) + strongSelf.presentAutoremoveSetup() })) } } @@ -11856,6 +11850,36 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } + private func presentAutoremoveSetup() { + guard let peer = self.presentationInterfaceState.renderedPeer?.peer else { + return + } + + let controller = peerAutoremoveSetupScreen(context: self.context, peerId: peer.id, completion: { [weak self] updatedValue in + if case let .updated(value) = updatedValue { + guard let strongSelf = self else { + return + } + + var text: String? + if let myValue = value.myValue { + if let limitedByValue = value.limitedByValue, limitedByValue < myValue { + text = "\(peer.compactDisplayTitle) has set messages to auto-delete in \(timeIntervalString(strings: strongSelf.presentationData.strings, value: limitedByValue)). You can't cancel it or make this interval longer." + } else { + text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0 + } + } else if let limitedByValue = value.limitedByValue { + text = "\(peer.compactDisplayTitle) has set messages to auto-delete in \(timeIntervalString(strings: strongSelf.presentationData.strings, value: limitedByValue)). You can't cancel it or make this interval longer." + } + if let text = text { + strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: text), elevatedLayout: false, action: { _ in return false }), in: .current) + } + } + }) + self.chatDisplayNode.dismissInput() + self.push(controller) + } + private var effectiveNavigationController: NavigationController? { if let navigationController = self.navigationController as? NavigationController { return navigationController diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index b8775bc678..93f9bd9735 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1436,7 +1436,8 @@ private func stringForRemainingTime(_ duration: Int32, strings: PresentationStri let seconds = duration % 60 let durationString: String if days > 0 { - return strings.Conversation_AutoremoveRemainingDays(days) + let roundDays = round(Double(duration) / (3600.0 * 24.0)) + return strings.Conversation_AutoremoveRemainingDays(Int32(roundDays)) } else if hours > 0 { durationString = String(format: "%d:%02d:%02d", hours, minutes, seconds) } else { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift index 5ba0fd39b5..793d4f4732 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateNavigationButtons.swift @@ -44,6 +44,16 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha canClear = true } else if let peer = peer as? TelegramChannel, case .group = peer.info, peer.addressName == nil && presentationInterfaceState.peerGeoLocation == nil { canClear = true + } else if let peer = peer as? TelegramChannel { + if case .broadcast = peer.info { + //TODO:localize + title = "Clear Channel" + } + if peer.hasPermission(.changeInfo) { + canClear = true + } else { + canClear = false + } } else { canClear = false } diff --git a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift index 7ed769d0d6..2803acb223 100644 --- a/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageDateAndStatusNode.swift @@ -263,7 +263,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { repliesImage = graphics.incomingDateAndStatusPinnedIcon } if hasAutoremove { - selfExpiringImage = graphics.incomingDateAndStatusSelfExpiringIcon + //selfExpiringImage = graphics.incomingDateAndStatusSelfExpiringIcon } case let .BubbleOutgoing(status): dateColor = presentationData.theme.theme.chat.message.outgoing.secondaryTextColor @@ -282,7 +282,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { repliesImage = graphics.outgoingDateAndStatusPinnedIcon } if hasAutoremove { - selfExpiringImage = graphics.outgoingDateAndStatusSelfExpiringIcon + //selfExpiringImage = graphics.outgoingDateAndStatusSelfExpiringIcon } case .ImageIncoming: dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor @@ -301,7 +301,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { repliesImage = graphics.mediaPinnedIcon } if hasAutoremove { - selfExpiringImage = graphics.mediaSelfExpiringIcon + //selfExpiringImage = graphics.mediaSelfExpiringIcon } case let .ImageOutgoing(status): dateColor = presentationData.theme.theme.chat.message.mediaDateAndStatusTextColor @@ -321,7 +321,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { repliesImage = graphics.mediaPinnedIcon } if hasAutoremove { - selfExpiringImage = graphics.mediaSelfExpiringIcon + //selfExpiringImage = graphics.mediaSelfExpiringIcon } case .FreeIncoming: let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) @@ -341,7 +341,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { repliesImage = graphics.freePinnedIcon } if hasAutoremove { - selfExpiringImage = graphics.freeSelfExpiringIcon + //selfExpiringImage = graphics.freeSelfExpiringIcon } case let .FreeOutgoing(status): let serviceColor = serviceMessageColorComponents(theme: presentationData.theme.theme, wallpaper: presentationData.theme.wallpaper) @@ -362,7 +362,7 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { repliesImage = graphics.freePinnedIcon } if hasAutoremove { - selfExpiringImage = graphics.freeSelfExpiringIcon + //selfExpiringImage = graphics.freeSelfExpiringIcon } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 924a564719..c2bd0fdc93 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -23,6 +23,7 @@ import OverlayStatusController import AlertUI import PresentationDataUtils import LocationUI +import AppLock private final class AccountUserInterfaceInUseContext { let subscribers = Bag<(Bool) -> Void>() @@ -742,7 +743,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.widgetDataContext = WidgetDataContext(basePath: self.basePath, activeAccount: self.activeAccounts |> map { primary, _, _ in return primary - }, presentationData: self.presentationData) + }, presentationData: self.presentationData, appLockContext: self.appLockContext as! AppLockContextImpl) let enableSpotlight = accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.intentsSettings])) |> map { sharedData -> Bool in diff --git a/submodules/TelegramUI/Sources/WidgetDataContext.swift b/submodules/TelegramUI/Sources/WidgetDataContext.swift index bd3930a600..d8d15d21ab 100644 --- a/submodules/TelegramUI/Sources/WidgetDataContext.swift +++ b/submodules/TelegramUI/Sources/WidgetDataContext.swift @@ -9,23 +9,40 @@ import NotificationsPresentationData import WidgetKit import TelegramUIPreferences import WidgetItemsUtils +import AccountContext +import AppLock import GeneratedSources +@available(iOSApplicationExtension 14.0, iOS 14.0, *) +private extension SelectFriendsIntent { + var configurationHash: String { + var result = "widget" + if let items = self.friends { + for item in items { + if let identifier = item.identifier { + result.append("+\(identifier)") + } + } + } + return result + } +} + final class WidgetDataContext { private var currentAccount: Account? private var currentAccountDisposable: Disposable? private var widgetPresentationDataDisposable: Disposable? private var notificationPresentationDataDisposable: Disposable? - init(basePath: String, activeAccount: Signal, presentationData: Signal) { + init(basePath: String, activeAccount: Signal, presentationData: Signal, appLockContext: AppLockContextImpl) { self.currentAccountDisposable = (activeAccount |> distinctUntilChanged(isEqual: { lhs, rhs in return lhs === rhs }) |> mapToSignal { account -> Signal in guard let account = account else { - return .single(WidgetData(accountId: 0, content: .notAuthorized)) + return .single(WidgetData(accountId: 0, content: .empty, unlockedForLockId: nil)) } enum CombinedRecentPeers { @@ -38,11 +55,12 @@ final class WidgetDataContext { case peers(peers: [Peer], unread: [PeerId: Unread], messages: [PeerId: WidgetDataPeer.Message]) } - let updatedAdditionalPeerIds: Signal, NoError> = Signal { subscriber in + let updatedAdditionalPeerIds: Signal<(Set, Set), NoError> = Signal { subscriber in if #available(iOSApplicationExtension 14.0, iOS 14.0, *) { #if arch(arm64) || arch(i386) || arch(x86_64) WidgetCenter.shared.getCurrentConfigurations({ result in var peerIds = Set() + var configurationHashes = Set() if case let .success(infos) = result { for info in infos { if let configuration = info.configuration as? SelectFriendsIntent { @@ -54,19 +72,20 @@ final class WidgetDataContext { peerIds.insert(PeerId(peerIdValue)) } } + configurationHashes.insert(configuration.configurationHash) } } } - subscriber.putNext(peerIds) + subscriber.putNext((peerIds, configurationHashes)) subscriber.putCompletion() }) #else - subscriber.putNext(Set()) + subscriber.putNext((Set(), Set())) subscriber.putCompletion() #endif } else { - subscriber.putNext(Set()) + subscriber.putNext((Set(), Set())) subscriber.putCompletion() } @@ -74,33 +93,9 @@ final class WidgetDataContext { } |> runOn(.mainQueue()) - let preferencesKey: PostboxViewKey = .preferences(keys: Set([ - ApplicationSpecificPreferencesKeys.widgetSettings - ])) - let sourcePeers: Signal = account.postbox.combinedView(keys: [ - preferencesKey - ]) - |> mapToSignal { views -> Signal in - let widgetSettings: WidgetSettings - if let view = views.views[preferencesKey] as? PreferencesView, let value = view.values[ApplicationSpecificPreferencesKeys.widgetSettings] as? WidgetSettings { - widgetSettings = value - } else { - widgetSettings = .default - } - - if widgetSettings.useHints { - return recentPeers(account: account) - } else { - return account.postbox.transaction { transaction -> RecentPeers in - return .peers(widgetSettings.peers.compactMap { peerId -> Peer? in - guard let peer = transaction.getPeer(peerId) else { - return nil - } - return peer - }) - } - } - } + let unlockedForLockId: Signal = .single(nil) + + let sourcePeers: Signal = recentPeers(account: account) let recent: Signal = sourcePeers |> mapToSignal { recent -> Signal in @@ -160,7 +155,7 @@ final class WidgetDataContext { |> map { result -> WidgetData in switch result { case .disabled: - return WidgetData(accountId: account.id.int64, content: .disabled) + return WidgetData(accountId: account.id.int64, content: .empty, unlockedForLockId: nil) case let .peers(peers, unread, messages): return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in var name: String = "" @@ -192,15 +187,23 @@ final class WidgetDataContext { return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in return account.postbox.mediaBox.resourcePath(representation.resource) }, badge: badge, message: message) - }, updateTimestamp: Int32(Date().timeIntervalSince1970)))) + }, updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil) } } |> distinctUntilChanged - let additionalPeerIds = Signal, NoError>.single(Set()) |> then(updatedAdditionalPeerIds) + let additionalPeerIds = Signal<(Set, Set), NoError>.complete() |> then(updatedAdditionalPeerIds) let processedCustom: Signal = additionalPeerIds - |> distinctUntilChanged - |> mapToSignal { additionalPeerIds -> Signal in + |> distinctUntilChanged(isEqual: { lhs, rhs in + if lhs.0 != rhs.0 { + return false + } + if lhs.1 != rhs.1 { + return false + } + return true + }) + |> mapToSignal { additionalPeerIds, _ -> Signal in return combineLatest(queue: .mainQueue(), additionalPeerIds.map { account.postbox.peerView(id: $0) }) |> mapToSignal { peerViews -> Signal in let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map { @@ -250,7 +253,7 @@ final class WidgetDataContext { |> map { result -> WidgetData in switch result { case .disabled: - return WidgetData(accountId: account.id.int64, content: .disabled) + return WidgetData(accountId: account.id.int64, content: .empty, unlockedForLockId: nil) case let .peers(peers, unread, messages): return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in var name: String = "" @@ -282,13 +285,15 @@ final class WidgetDataContext { return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in return account.postbox.mediaBox.resourcePath(representation.resource) }, badge: badge, message: message) - }, updateTimestamp: Int32(Date().timeIntervalSince1970)))) + }, updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil) } } |> distinctUntilChanged - return combineLatest(processedRecent, processedCustom) - |> map { processedRecent, _ -> WidgetData in + return combineLatest(processedRecent, processedCustom, unlockedForLockId) + |> map { processedRecent, _, unlockedForLockId -> WidgetData in + var processedRecent = processedRecent + processedRecent.unlockedForLockId = unlockedForLockId return processedRecent } }).start(next: { widgetData in diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index ef3baff23e..28aa4cb94c 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit ef3baff23ea69ff11673266b62996b88397b260b +Subproject commit 28aa4cb94cd150f825fefbf1400bf2eb6500a9f7 diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index daee640f7b..ee125a5707 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -147,7 +147,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural) self.textNode.attributedText = attributedText - self.textNode.maximumNumberOfLines = 2 + self.textNode.maximumNumberOfLines = 5 displayUndo = false self.originalRemainingSeconds = 3 case let .info(text): diff --git a/submodules/WidgetItems/Sources/WidgetItems.swift b/submodules/WidgetItems/Sources/WidgetItems.swift index ea2dc86599..d587730484 100644 --- a/submodules/WidgetItems/Sources/WidgetItems.swift +++ b/submodules/WidgetItems/Sources/WidgetItems.swift @@ -16,6 +16,16 @@ public struct WidgetDataPeer: Codable, Equatable { } public struct Message: Codable, Equatable { + public struct Author: Codable, Equatable { + public var isMe: Bool + public var title: String + + public init(isMe: Bool, title: String) { + self.isMe = isMe + self.title = title + } + } + public enum Content: Codable, Equatable { public enum DecodingError: Error { case generic @@ -205,11 +215,13 @@ public struct WidgetDataPeer: Codable, Equatable { } } + public var author: Author? public var text: String public var content: Content public var timestamp: Int32 - public init(text: String, content: Content, timestamp: Int32) { + public init(author: Author?, text: String, content: Content, timestamp: Int32) { + self.author = author self.text = text self.content = content self.timestamp = timestamp @@ -273,23 +285,19 @@ public struct WidgetData: Codable, Equatable { } private enum Cases: Int32, Codable { - case notAuthorized - case disabled + case empty case peers } - case notAuthorized - case disabled + case empty case peers(WidgetDataPeers) public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let discriminator = try container.decode(Cases.self, forKey: .discriminator) switch discriminator { - case .notAuthorized: - self = .notAuthorized - case .disabled: - self = .disabled + case .empty: + self = .empty case .peers: self = .peers(try container.decode(WidgetDataPeers.self, forKey: .peers)) } @@ -298,10 +306,8 @@ public struct WidgetData: Codable, Equatable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) switch self { - case .notAuthorized: - try container.encode(Cases.notAuthorized, forKey: .discriminator) - case .disabled: - try container.encode(Cases.disabled, forKey: .discriminator) + case .empty: + try container.encode(Cases.empty, forKey: .discriminator) case let .peers(peers): try container.encode(Cases.peers, forKey: .discriminator) try container.encode(peers, forKey: .peers) @@ -311,9 +317,11 @@ public struct WidgetData: Codable, Equatable { public var accountId: Int64 public var content: Content + public var unlockedForLockId: String? - public init(accountId: Int64, content: Content) { + public init(accountId: Int64, content: Content, unlockedForLockId: String?) { self.accountId = accountId self.content = content + self.unlockedForLockId = unlockedForLockId } } diff --git a/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift b/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift index 75c64372c0..f95d7fcdd1 100644 --- a/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift +++ b/submodules/WidgetItemsUtils/Sources/WidgetItemsUtils.swift @@ -54,6 +54,18 @@ public extension WidgetDataPeer.Message { break } } - self.init(text: message.text, content: content, timestamp: message.timestamp) + + var author: Author? + if let _ = message.peers[message.id.peerId] as? TelegramGroup { + if let authorPeer = message.author { + author = Author(isMe: false, title: authorPeer.debugDisplayTitle) + } + } else if let channel = message.peers[message.id.peerId] as? TelegramChannel, case .group = channel.info { + if let authorPeer = message.author { + author = Author(isMe: false, title: authorPeer.debugDisplayTitle) + } + } + + self.init(author: author, text: message.text, content: content, timestamp: message.timestamp) } } diff --git a/third-party/webrtc/webrtc b/third-party/webrtc/webrtc index 5746d4b647..e42b463011 160000 --- a/third-party/webrtc/webrtc +++ b/third-party/webrtc/webrtc @@ -1 +1 @@ -Subproject commit 5746d4b64798196743aaf083b0c07a3cc58a713f +Subproject commit e42b4630117498504692fbac80ec6dbb970b2314