diff --git a/Telegram/BUILD b/Telegram/BUILD index 45d52d72a5..d570c88f26 100644 --- a/Telegram/BUILD +++ b/Telegram/BUILD @@ -1162,11 +1162,20 @@ swift_library( module_name = "WidgetExtensionLib", srcs = glob([ "WidgetKitWidget/**/*.swift", + "Generated/**/*.swift", ]), + data = [ + "SiriIntents/Intents.intentdefinition", + ], deps = [ "//submodules/BuildConfig:BuildConfig", "//submodules/WidgetItems:WidgetItems", "//submodules/AppLockState:AppLockState", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/SyncCore:SyncCore", + "//submodules/OpenSSLEncryptionProvider:OpenSSLEncryptionProvider", ], ) @@ -1188,7 +1197,12 @@ ios_extension( provides_main = True, provisioning_profile = "//build-input/data/provisioning-profiles:Widget.mobileprovision", deps = [":WidgetExtensionLib"], - frameworks = [], + frameworks = [ + ":SwiftSignalKitFramework", + ":PostboxFramework", + ":SyncCoreFramework", + ":TelegramCoreFramework", + ], ) plist_fragment( @@ -1219,6 +1233,7 @@ plist_fragment( INSearchForMessagesIntent INSetMessageAttributeIntent INSearchCallHistoryIntent + SelectFriendsIntent NSExtensionPointIdentifier @@ -1236,7 +1251,11 @@ swift_library( module_name = "IntentsExtensionLib", srcs = glob([ "SiriIntents/**/*.swift", + "Generated/**/*.swift", ]), + data = [ + "SiriIntents/Intents.intentdefinition", + ], deps = [ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Postbox:Postbox", @@ -1528,12 +1547,13 @@ ios_application( strings = [ ":AppStringResources", ], - extensions = [] if telegram_disable_extensions else [ + extensions = [ + ] if telegram_disable_extensions else [ ":ShareExtension", ":NotificationContentExtension", ":NotificationServiceExtension", ":IntentsExtension", - #":WidgetExtension", + ":WidgetExtension", ], watch_application = ":TelegramWatchApp", deps = [ diff --git a/Telegram/Generated/Contents.swift b/Telegram/Generated/Contents.swift new file mode 100644 index 0000000000..b66b8e019e --- /dev/null +++ b/Telegram/Generated/Contents.swift @@ -0,0 +1,36 @@ +// +// 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 new file mode 100644 index 0000000000..5f0c1933ba --- /dev/null +++ b/Telegram/Generated/Friend.swift @@ -0,0 +1,60 @@ +// +// 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 new file mode 100644 index 0000000000..82afd21925 --- /dev/null +++ b/Telegram/Generated/SelectFriendsIntent.swift @@ -0,0 +1,120 @@ +// +// 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 3ae6e2e5b9..41ac71b129 100644 --- a/Telegram/SiriIntents/IntentHandler.swift +++ b/Telegram/SiriIntents/IntentHandler.swift @@ -8,6 +8,7 @@ import BuildConfig import Contacts import OpenSSLEncryptionProvider import AppLockState +import UIKit private var accountCache: Account? @@ -52,7 +53,7 @@ enum IntentHandlingError { @available(iOSApplicationExtension 10.0, iOS 10.0, *) @objc(IntentHandler) -public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling { +class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessagesIntentHandling, INSetMessageAttributeIntentHandling, INStartAudioCallIntentHandling, INSearchCallHistoryIntentHandling, SelectFriendsIntentHandling { private let accountPromise = Promise() private let resolvePersonsDisposable = MetaDisposable() @@ -697,4 +698,47 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo completion(response) })) } + + @available(iOSApplicationExtension 14.0, iOS 14.0, *) + func provideFriendsOptionsCollection(for intent: SelectFriendsIntent, with completion: @escaping (INObjectCollection?, Error?) -> Void) { + 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) + } + } + + 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 + } + } + result.append(Friend(identifier: "\(peer.id.toInt64())", display: peer.debugDisplayTitle, subtitle: nil, image: nil)) + } + 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 0905ce280e..eede724e1f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -4115,7 +4115,6 @@ Unused sets are archived when you add more."; "LogoutOptions.ContactSupportText" = "Tell us about any issues; logging out doesn't usually help."; "LogoutOptions.LogOut" = "Log Out"; "LogoutOptions.LogOutInfo" = "Remember, logging out kills all your Secret Chats."; -"LogoutOptions.LogOutWalletInfo" = "Logging out will cancel all your secret chats and unlink your Gram wallet from the app."; "GroupPermission.PermissionGloballyDisabled" = "This permission is disabled in this group."; @@ -4830,251 +4829,6 @@ Sorry for the inconvenience."; "ChatSearch.ResultsTooltip" = "Tap to view as a list."; -"Wallet.Updated.JustNow" = "updated just now"; -"Wallet.Updated.MinutesAgo_0" = "%@ minutes ago"; //three to ten -"Wallet.Updated.MinutesAgo_1" = "1 minute ago"; //one -"Wallet.Updated.MinutesAgo_2" = "2 minutes ago"; //two -"Wallet.Updated.MinutesAgo_3_10" = "%@ minutes ago"; //three to ten -"Wallet.Updated.MinutesAgo_many" = "%@ minutes ago"; // more than ten -"Wallet.Updated.MinutesAgo_any" = "%@ minutes ago"; // more than ten -"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_1" = "1 hour ago"; -"Wallet.Updated.HoursAgo_2" = "2 hours ago"; -"Wallet.Updated.HoursAgo_3_10" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_any" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_many" = "%@ hours ago"; -"Wallet.Updated.HoursAgo_0" = "%@ hours ago"; -"Wallet.Updated.YesterdayAt" = "yesterday at %@"; -"Wallet.Updated.AtDate" = "%@"; -"Wallet.Updated.TodayAt" = "today at %@"; - -"Wallet.Info.WalletCreated" = "Wallet Created"; -"Wallet.Info.Address" = "Your wallet address"; -"Wallet.Info.YourBalance" = "your balance"; -"Wallet.Info.Receive" = "Receive"; -"Wallet.Info.ReceiveGrams" = "Receive Grams"; -"Wallet.Info.Send" = "Send"; -"Wallet.Info.RefreshErrorTitle" = "No network"; -"Wallet.Info.RefreshErrorText" = "Couldn't refresh balance. Please make sure your internet connection is working and try again."; -"Wallet.Info.RefreshErrorNetworkText" = "Wallet state can not be retrieved at this time. Please try again later."; -"Wallet.Info.UnknownTransaction" = "Empty Transaction"; -"Wallet.Info.TransactionTo" = "to"; -"Wallet.Info.TransactionFrom" = "from"; -"Wallet.Info.Updating" = "updating"; -"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fees"; -"Wallet.Info.TransactionPendingHeader" = "Pending"; -"Wallet.Qr.ScanCode" = "Scan QR Code"; -"Wallet.Qr.Title" = "QR Code"; -"Wallet.Receive.Title" = "Receive Grams"; -"Wallet.Receive.AddressHeader" = "YOUR WALLET ADDRESS"; -"Wallet.Receive.InvoiceUrlHeader" = "INVOICE URL"; -"Wallet.Receive.CopyAddress" = "Copy Wallet Address"; -"Wallet.Receive.CopyInvoiceUrl" = "Copy Invoice URL"; -"Wallet.Receive.ShareAddress" = "Share Wallet Address"; -"Wallet.Receive.ShareInvoiceUrl" = "Share Invoice URL"; -"Wallet.Receive.ShareUrlInfo" = "Share this link with other Gram wallet owners to receive Grams from them. Note: this link won't work for real Grams."; -"Wallet.Receive.AmountHeader" = "AMOUNT"; -"Wallet.Receive.AmountText" = "Grams to receive"; -"Wallet.Receive.AmountInfo" = "You can specify the amount and purpose of the payment to save the sender some time."; -"Wallet.Receive.CommentHeader" = "COMMENT (OPTIONAL)"; -"Wallet.Receive.CommentInfo" = "Description of the payment"; -"Wallet.Receive.AddressCopied" = "Address copied to clipboard."; -"Wallet.Receive.InvoiceUrlCopied" = "Invoice URL copied to clipboard."; -"Wallet.Send.Title" = "Send Grams"; -"Wallet.Send.AddressHeader" = "RECIPIENT WALLET ADDRESS"; -"Wallet.Send.AddressText" = "Enter wallet address..."; -"Wallet.Send.AddressInfo" = "Paste the 48-letter address of the recipient here or ask them to send you a ton:// link."; -"Wallet.Send.Balance" = "Balance: %@"; -"Wallet.Send.AmountText" = "Grams to send"; -"Wallet.Send.Confirmation" = "Confirmation"; -"Wallet.Send.ConfirmationText" = "Do you want to send **%1$@** Grams to\n\n%2$@?\n\nBlockchain fees: ~%3$@ grams"; -"Wallet.Send.ConfirmationConfirm" = "Confirm"; -"Wallet.Send.Send" = "Send"; -"Wallet.Send.OwnAddressAlertTitle" = "Warning"; -"Wallet.Send.OwnAddressAlertText" = "Sending Grams from a wallet to the same wallet doesn't make sense, you will simply waste a portion of the value on blockchain fees."; -"Wallet.Send.OwnAddressAlertProceed" = "Proceed"; -"Wallet.Send.TransactionInProgress" = "Please wait until the current transaction is completed."; -"Wallet.Send.SyncInProgress" = "Please wait while the wallet finishes syncing with the TON Blockchain."; -"Wallet.Send.EncryptComment" = "Encrypt Text"; -"Wallet.Settings.Title" = "Settings"; -"Wallet.Settings.Configuration" = "Server Settings"; -"Wallet.Settings.ConfigurationInfo" = "Advanced Settings"; -"Wallet.Settings.BackupWallet" = "Backup Wallet"; -"Wallet.Settings.DeleteWallet" = "Delete Wallet"; -"Wallet.Settings.DeleteWalletInfo" = "This will disconnect the wallet from this app. You will be able to restore your wallet using 24 secret words – or import another wallet.\n\nGram Wallets are located in the decentralized TON Blockchain. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty."; -"Wallet.Intro.NotNow" = "Not Now"; -"Wallet.Intro.ImportExisting" = "Import existing wallet"; -"Wallet.Intro.CreateErrorTitle" = "An Error Occurred"; -"Wallet.Intro.CreateErrorText" = "Sorry. Please try again."; -"Wallet.Intro.Title" = "Gram Wallet"; -"AppWallet.Intro.Text" = "The gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; -"Wallet.Intro.Text" = "Gram wallet allows you to make fast and secure blockchain-based payments without intermediaries."; -"Wallet.Intro.CreateWallet" = "Create My Wallet"; -"Wallet.Intro.Terms" = "By creating a wallet you accept the\n[Terms of Conditions]()."; -"TelegramWallet.Intro.TermsUrl" = "https://telegram.org/tos/wallet"; -"Wallet.Created.Title" = "Congratulations"; -"Wallet.Created.Text" = "Your Gram wallet has just been created. Only you control it.\n\nTo be able to always have access to it, please write down your secret words and\nset up a secure passcode."; -"Wallet.Created.Proceed" = "Proceed"; -"Wallet.Created.ExportErrorTitle" = "Error"; -"Wallet.Created.ExportErrorText" = "Encryption error. Please make sure you have enabled a device passcode in iOS settings and try again."; -"Wallet.Completed.Title" = "Ready to go!"; -"Wallet.Completed.Text" = "You’re all set. Now you have a wallet that only you control - directly, without middlemen or bankers."; -"Wallet.Completed.ViewWallet" = "View My Wallet"; -"Wallet.RestoreFailed.Title" = "Too Bad"; -"Wallet.RestoreFailed.Text" = "Without the secret words, you can't\nrestore access to your wallet."; -"Wallet.RestoreFailed.CreateWallet" = "Create a New Wallet"; -"Wallet.RestoreFailed.EnterWords" = "Enter 24 words"; -"Wallet.Sending.Title" = "Sending Grams"; -"Wallet.Sending.Text" = "Please wait a few seconds for your transaction to be processed..."; -"Wallet.Sending.Title" = "Sending Grams"; -"Wallet.Sent.Title" = "Done!"; -"Wallet.Sent.Text" = "**%@ Grams** have been sent."; -"Wallet.Sent.ViewWallet" = "View My Wallet"; -"Wallet.SecureStorageNotAvailable.Title" = "Set a Passcode"; -"Wallet.SecureStorageNotAvailable.Text" = "Please set up a Passcode on your device to enable secure payments with your Gram wallet."; -"Wallet.SecureStorageReset.Title" = "Security Settings Have Changed"; -"Wallet.SecureStorageReset.BiometryTouchId" = "Touch ID"; -"Wallet.SecureStorageReset.BiometryFaceId" = "Face ID"; -"Wallet.SecureStorageReset.BiometryText" = "Unfortunately, your wallet is no longer available because your system Passcode or %@ has been turned off. Please enable them before proceeding."; -"Wallet.SecureStorageReset.PasscodeText" = "Unfortunately, your wallet is no longer available because your system Passcode has been turned off. Please enable it before proceeding."; -"Wallet.SecureStorageChanged.BiometryText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode/%@). To restore your wallet, tap \"Import existing wallet\"."; -"Wallet.SecureStorageChanged.PasscodeText" = "Unfortunately, your wallet is no longer available due to the change in your system security settings (Passcode). To restore your wallet, tap \"Import existing wallet\"."; -"Wallet.SecureStorageChanged.ImportWallet" = "Import Existing Wallet"; -"Wallet.SecureStorageChanged.CreateWallet" = "Create New Wallet"; -"Wallet.TransactionInfo.Title" = "Transaction"; -"Wallet.TransactionInfo.NoAddress" = "No Address"; -"Wallet.TransactionInfo.RecipientHeader" = "RECIPIENT"; -"Wallet.TransactionInfo.SenderHeader" = "SENDER"; -"Wallet.TransactionInfo.CopyAddress" = "Copy Wallet Address"; -"Wallet.TransactionInfo.AddressCopied" = "Address copied to clipboard."; -"Wallet.TransactionInfo.SendGrams" = "Send Grams to This Address"; -"Wallet.TransactionInfo.CommentHeader" = "COMMENT"; -"Wallet.TransactionInfo.StorageFeeHeader" = "STORAGE FEE"; -"Wallet.TransactionInfo.OtherFeeHeader" = "TRANSACTION FEE"; -"Wallet.TransactionInfo.StorageFeeInfo" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions."; -"Wallet.TransactionInfo.StorageFeeInfoUrl" = "Blockchain validators collect a tiny fee for storing information about your decentralized wallet and processing your transactions. [More info]()"; -"Wallet.TransactionInfo.OtherFeeInfo" = "Blockchain validators collect a tiny fee for processing your decentralized transactions."; -"Wallet.TransactionInfo.OtherFeeInfoUrl" = "Blockchain validators collect a tiny fee for processing your decentralized transactions. [More info]()"; -"AppWallet.TransactionInfo.FeeInfoURL" = "https://telegram.org/wallet/fee"; -"Wallet.WordCheck.Title" = "Test Time!"; -"Wallet.WordCheck.Text" = "Let’s check that you wrote them down correctly. Please enter the words\n**%1$@**, **%2$@** and **%3$@**"; -"Wallet.WordCheck.Continue" = "Continue"; -"Wallet.WordCheck.IncorrectHeader" = "Incorrect words!"; -"Wallet.WordCheck.IncorrectText" = "The secret words you have entered do not match the ones in the list."; -"Wallet.WordCheck.TryAgain" = "Try Again"; -"Wallet.WordCheck.ViewWords" = "View Words"; -"Wallet.WordImport.Title" = "24 Secret Words"; -"Wallet.WordImport.Text" = "Please restore access to your wallet by\nentering the 24 secret words you wrote down when creating the wallet."; -"Wallet.WordImport.Continue" = "Continue"; -"Wallet.WordImport.CanNotRemember" = "I don't have them"; -"Wallet.WordImport.IncorrectTitle" = "Incorrect words"; -"Wallet.WordImport.IncorrectText" = "Sorry, you have entered incorrect secret words. Please double check and try again."; -"Wallet.Words.Title" = "24 Secret Words"; -"Wallet.Words.Text" = "Write down these 24 words in the correct order and store them in a secret place.\n\nUse these secret words to restore access to your wallet if you lose your passcode or device."; -"Wallet.Words.Done" = "Done"; -"Wallet.Words.NotDoneTitle" = "Sure Done?"; -"Wallet.Words.NotDoneText" = "You didn't have enough time to write those words down."; -"Wallet.Words.NotDoneOk" = "OK, Sorry"; -"Wallet.Words.NotDoneResponse" = "Apologies Accepted"; -"Wallet.Send.NetworkErrorTitle" = "No network"; -"Wallet.Send.NetworkErrorText" = "Couldn't send grams. Please make sure your internet connection is working and try again."; -"Wallet.Send.ErrorNotEnoughFundsTitle" = "Insufficient Grams"; -"Wallet.Send.ErrorNotEnoughFundsText" = "Unfortunately, your transfer couldn't be completed. You don't have enough grams."; -"Wallet.Send.ErrorInvalidAddress" = "Invalid wallet address. Please correct and try again."; -"Wallet.Send.ErrorDecryptionFailed" = "Please make sure that your device has a passcode set in iOS Settings and try again."; -"Wallet.Send.UninitializedTitle" = "Warning"; -"Wallet.Send.UninitializedText" = "This address belongs to an empty wallet. Are you sure you want to transfer grams to it?"; -"Wallet.Send.SendAnyway" = "Send Anyway"; -"Wallet.Receive.CreateInvoice" = "Create Invoice"; -"Wallet.Receive.CreateInvoiceInfo" = "You can specify the amount and purpose of the payment to save the sender some time."; - -"Conversation.WalletRequiredTitle" = "Gram Wallet Required"; -"Conversation.WalletRequiredText" = "This link can be used to send money on the TON Blockchain. To do this, you need to set up a Gram wallet first."; -"Conversation.WalletRequiredNotNow" = "Not Now"; -"Conversation.WalletRequiredSetup" = "Set Up"; - -"Wallet.Configuration.Title" = "Server Settings"; -"Wallet.Configuration.Apply" = "Save"; -"Wallet.Configuration.SourceHeader" = "SOURCE"; -"Wallet.Configuration.SourceURL" = "URL"; -"Wallet.Configuration.SourceJSON" = "JSON"; -"Wallet.Configuration.SourceInfo" = "Using a different configuration allows you to change Lite Server addresses."; -"Wallet.Configuration.BlockchainIdHeader" = "BLOCKCHAIN ID"; -"Wallet.Configuration.BlockchainIdPlaceholder" = "Blockchain ID"; -"Wallet.Configuration.BlockchainIdInfo" = "This setting is for developers. Change it only if you are working on creating your own TON network."; -"Wallet.Configuration.ApplyErrorTitle" = "Error"; -"Wallet.Configuration.ApplyErrorTextURLInvalid" = "The URL you have entered is invalid. Please try again."; -"Wallet.Configuration.ApplyErrorTextURLUnreachable" = "There was an error while downloading configuration from %@\nPlease try again."; -"Wallet.Configuration.ApplyErrorTextURLInvalidData" = "This blockchain configuration is invalid. Please try again."; -"Wallet.Configuration.ApplyErrorTextJSONInvalidData" = "This blockchain configuration is invalid. Please try again."; -"Wallet.Configuration.BlockchainNameChangedTitle" = "Warning"; -"Wallet.Configuration.BlockchainNameChangedText" = "Are you sure you want to change the blockchain ID? You don't need this unless you're testing your own TON network.\n\nIf you proceed, you will need to reconnect your wallet using 24 secret words."; -"Wallet.Configuration.BlockchainNameChangedProceed" = "Proceed"; - -"Wallet.CreateInvoice.Title" = "Create Invoice"; - -"Wallet.Navigation.Close" = "Close"; -"Wallet.Navigation.Back" = "Back"; -"Wallet.Navigation.Done" = "Done"; -"Wallet.Navigation.Cancel" = "Cancel"; -"Wallet.Alert.OK" = "OK"; -"Wallet.Alert.Cancel" = "Cancel"; - -"Wallet.Month.GenJanuary" = "January"; -"Wallet.Month.GenFebruary" = "February"; -"Wallet.Month.GenMarch" = "March"; -"Wallet.Month.GenApril" = "April"; -"Wallet.Month.GenMay" = "May"; -"Wallet.Month.GenJune" = "June"; -"Wallet.Month.GenJuly" = "July"; -"Wallet.Month.GenAugust" = "August"; -"Wallet.Month.GenSeptember" = "September"; -"Wallet.Month.GenOctober" = "October"; -"Wallet.Month.GenNovember" = "November"; -"Wallet.Month.GenDecember" = "December"; -"Wallet.Month.ShortJanuary" = "Jan"; -"Wallet.Month.ShortFebruary" = "Feb"; -"Wallet.Month.ShortMarch" = "Mar"; -"Wallet.Month.ShortApril" = "Apr"; -"Wallet.Month.ShortMay" = "May"; -"Wallet.Month.ShortJune" = "Jun"; -"Wallet.Month.ShortJuly" = "Jul"; -"Wallet.Month.ShortAugust" = "Aug"; -"Wallet.Month.ShortSeptember" = "Sep"; -"Wallet.Month.ShortOctober" = "Oct"; -"Wallet.Month.ShortNovember" = "Nov"; -"Wallet.Month.ShortDecember" = "Dec"; - -"Wallet.Weekday.Today" = "Today"; -"Wallet.Weekday.Yesterday" = "Yesterday"; - -"Wallet.Info.TransactionDateHeader" = "%1$@ %2$@"; -"Wallet.Info.TransactionDateHeaderYear" = "%1$@ %2$@, %3$@"; - -"Wallet.UnknownError" = "An error occurred. Please try again later."; - -"Wallet.ContextMenuCopy" = "Copy"; - -"Wallet.Time.PreciseDate_m1" = "Jan %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m2" = "Feb %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m3" = "Mar %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m4" = "Apr %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m5" = "May %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m6" = "Jun %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m7" = "Jul %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m8" = "Aug %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m9" = "Sep %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m10" = "Oct %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m11" = "Nov %1$@, %2$@ at %3$@"; -"Wallet.Time.PreciseDate_m12" = "Dec %1$@, %2$@ at %3$@"; - -"Wallet.VoiceOver.Editing.ClearText" = "Clear text"; - -"Wallet.Receive.ShareInvoiceUrlInfo" = "Share this link with other Gram wallet owners to receive %@ Grams from them."; - -"Settings.Wallet" = "Gram Wallet"; -"SettingsSearch.Synonyms.Wallet" = "TON Telegram Open Network Crypto"; - "Conversation.ClearCache" = "Clear Cache"; "ClearCache.Description" = "Media files will be deleted from your phone, but available for re-downloading when necessary."; "ClearCache.FreeSpaceDescription" = "If you want to save space on your device, you don't need to delete anything.\n\nYou can use cache settings to remove unnecessary media — and re-download files if you need them again."; @@ -5153,10 +4907,6 @@ Sorry for the inconvenience."; "UserInfo.StartSecretChatConfirmation" = "Are you sure you want to start a secret chat with\n%@?"; "UserInfo.StartSecretChatStart" = "Start"; -"Wallet.AccessDenied.Title" = "Please Allow Access"; -"Wallet.AccessDenied.Camera" = "TON Wallet needs access to your camera to take photos and videos.\n\nPlease go to Settings > Privacy > Camera and set TON Wallet to ON."; -"Wallet.AccessDenied.Settings" = "Settings"; - "GroupInfo.ShowMoreMembers_0" = "%@ more"; "GroupInfo.ShowMoreMembers_1" = "%@ more"; "GroupInfo.ShowMoreMembers_2" = "%@ more"; @@ -5996,9 +5746,9 @@ Sorry for the inconvenience."; "VoiceChat.CreateNewVoiceChatText" = "Voice chat ended. Start a new one?"; "VoiceChat.CreateNewVoiceChatStart" = "Start"; -"CHAT_VOICECHAT_START" = "%1$@ has started voice chat in the group %2$@"; -"CHAT_VOICECHAT_INVITE" = "%1$@ has invited %3$@ in the group %2$@"; -"CHAT_VOICECHAT_INVITE_YOU" = "%1$@ has invited you to voice chat in the group %2$@"; +"PUSH_CHAT_VOICECHAT_START" = "%2$@|%1$@ started a voice chat"; +"PUSH_CHAT_VOICECHAT_INVITE" = "%2$@|%1$@ invited %3$@ to the voice chat"; +"PUSH_CHAT_VOICECHAT_INVITE_YOU" = "%2$|@%1$@ invited you to the voice chat"; "Call.VoiceChatInProgressTitle" = "Voice Chat in Progress"; "Call.VoiceChatInProgressMessageCall" = "Leave voice chat in %1$@ and start a call with %2$@?"; diff --git a/Telegram/WidgetKitWidget/TodayViewController.swift b/Telegram/WidgetKitWidget/TodayViewController.swift index 2da48c0b4d..82db72421c 100644 --- a/Telegram/WidgetKitWidget/TodayViewController.swift +++ b/Telegram/WidgetKitWidget/TodayViewController.swift @@ -5,47 +5,186 @@ import WidgetItems import AppLockState import SwiftUI import WidgetKit +import Intents + +import OpenSSLEncryptionProvider +import SwiftSignalKit +import Postbox +import SyncCore +import TelegramCore +import OpenSSLEncryptionProvider + +private var installedSharedLogger = false + +private func setupSharedLogger(rootPath: String, path: String) { + if !installedSharedLogger { + installedSharedLogger = true + Logger.setSharedLogger(Logger(rootPath: rootPath, basePath: path)) + } +} + +private let accountAuxiliaryMethods = AccountAuxiliaryMethods(updatePeerChatInputState: { interfaceState, inputState -> PeerChatInterfaceState? in + return interfaceState +}, fetchResource: { account, resource, ranges, _ in + return nil +}, fetchResourceMediaReferenceHash: { resource in + return .single(nil) +}, prepareSecretThumbnailData: { _ in + return nil +}) + +private struct ApplicationSettings { + let logging: LoggingSettings +} + +private func applicationSettings(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> ApplicationSettings in + let loggingSettings: LoggingSettings + if let value = transaction.getSharedData(SharedDataKeys.loggingSettings) as? LoggingSettings { + loggingSettings = value + } else { + loggingSettings = LoggingSettings.defaultSettings + } + return ApplicationSettings(logging: loggingSettings) + } +} private func rootPathForBasePath(_ appGroupPath: String) -> String { return appGroupPath + "/telegram-data" } -struct Provider: TimelineProvider { +struct Provider: IntentTimelineProvider { public typealias Entry = SimpleEntry func placeholder(in context: Context) -> SimpleEntry { - return SimpleEntry(date: Date()) + return SimpleEntry(date: Date(), contents: .recent) } - - func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) { - let entry = SimpleEntry(date: Date()) + + 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) completion(entry) } - - func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { - var entries: [SimpleEntry] = [] - - let currentDate = Date() - for hourOffset in 0 ..< 1 { - let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! - let entry = SimpleEntry(date: entryDate) - entries.append(entry) - } - let timeline = Timeline(entries: entries, policy: .atEnd) - completion(timeline) + func getTimeline(for configuration: SelectFriendsIntent, in context: Context, completion: @escaping (Timeline) -> ()) { + let currentDate = Date() + let entryDate = Calendar.current.date(byAdding: .hour, value: 0, to: currentDate)! + + switch configuration.contents { + case .unknown, .recent: + 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 + } + + 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 + } + 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 + ) + } + + 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)) + } + } + return WidgetDataPeers(accountPeerId: widgetPeers.accountPeerId, peers: peers) + }) + |> 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) + } + let date: Date + let contents: Contents } enum PeersWidgetData { case placeholder case empty case locked - case data(WidgetData) + case peers(WidgetDataPeers) } extension PeersWidgetData { @@ -60,6 +199,7 @@ struct AvatarItemView: View { var body: some View { return ZStack { Image(uiImage: avatarImage(accountPeerId: accountPeerId, peer: peer, size: CGSize(width: itemSize, height: itemSize))) + .clipShape(Circle()) if let badge = peer.badge, badge.count > 0 { Text("\(badge.count)") .font(Font.system(size: 16.0)) @@ -117,11 +257,19 @@ struct WidgetView: View { switch self.widgetFamily { case .systemLarge: rowCount = 4 - rowHeight = 88.0 - topOffset = 12.0 - default: + rowHeight = 80.0 + topOffset = 10.0 + case .systemMedium: rowCount = 2 - rowHeight = 76.0 + rowHeight = 70.0 + topOffset = 0.0 + case .systemSmall: + rowCount = 2 + rowHeight = 72.0 + topOffset = 0.0 + @unknown default: + rowCount = 2 + rowHeight = 72.0 topOffset = 0.0 } let columnCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction)))) @@ -162,17 +310,10 @@ struct WidgetView: View { return AnyView(VStack { Text(presentationData.applicationLockedString) }) - case let .data(data): - switch data { - case let .peers(peers): - return AnyView(GeometryReader { geometry in - peersView(geometry: geometry, peers: peers) - }) - default: - return AnyView(ZStack { - Circle() - }) - } + case let .peers(peers): + return AnyView(GeometryReader { geometry in + peersView(geometry: geometry, peers: peers) + }) } } @@ -227,32 +368,44 @@ private let presentationData: WidgetPresentationData = { } }() -func getWidgetData() -> PeersWidgetData { - let appBundleIdentifier = Bundle.main.bundleIdentifier! - guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { - return .placeholder - } - let baseAppBundleId = String(appBundleIdentifier[.. PeersWidgetData { + switch contents { + case .recent: + let appBundleIdentifier = Bundle.main.bundleIdentifier! + guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else { + return .placeholder + } + let baseAppBundleId = String(appBundleIdentifier[.. Signal { +public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool) -> Signal { let queue = sharedQueue return Signal { subscriber in queue.async { @@ -1130,43 +1130,48 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, if let userVersion = userVersion { if userVersion != currentUserVersion { - if userVersion > currentUserVersion { - postboxLog("Version \(userVersion) is newer than supported") - assertionFailure("Version \(userVersion) is newer than supported") - valueBox.drop() - valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in - subscriber.putNext(.upgrading(progress)) - }) + if isTemporary { + subscriber.putNext(.upgrading(0.0)) + return } else { - if let operation = registeredUpgrades()[userVersion] { - switch operation { - case let .inplace(f): - valueBox.begin() - f(metadataTable, valueBox, { progress in - subscriber.putNext(.upgrading(progress)) - }) - valueBox.commit() - case let .standalone(f): - let updatedPath = f(queue, basePath, valueBox, encryptionParameters, { progress in - subscriber.putNext(.upgrading(progress)) - }) - if let updatedPath = updatedPath { - valueBox.internalClose() - let _ = try? FileManager.default.removeItem(atPath: basePath + "/db") - let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: basePath + "/db") - valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in - subscriber.putNext(.upgrading(progress)) - }) - } - } - continue loop - } else { - assertionFailure("Couldn't find any upgrade for \(userVersion)") - postboxLog("Couldn't find any upgrade for \(userVersion)") + if userVersion > currentUserVersion { + postboxLog("Version \(userVersion) is newer than supported") + assertionFailure("Version \(userVersion) is newer than supported") valueBox.drop() valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in subscriber.putNext(.upgrading(progress)) }) + } else { + if let operation = registeredUpgrades()[userVersion] { + switch operation { + case let .inplace(f): + valueBox.begin() + f(metadataTable, valueBox, { progress in + subscriber.putNext(.upgrading(progress)) + }) + valueBox.commit() + case let .standalone(f): + let updatedPath = f(queue, basePath, valueBox, encryptionParameters, { progress in + subscriber.putNext(.upgrading(progress)) + }) + if let updatedPath = updatedPath { + valueBox.internalClose() + let _ = try? FileManager.default.removeItem(atPath: basePath + "/db") + let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: basePath + "/db") + valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + subscriber.putNext(.upgrading(progress)) + }) + } + } + continue loop + } else { + assertionFailure("Couldn't find any upgrade for \(userVersion)") + postboxLog("Couldn't find any upgrade for \(userVersion)") + valueBox.drop() + valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in + subscriber.putNext(.upgrading(progress)) + }) + } } } } @@ -1177,7 +1182,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, let endTime = CFAbsoluteTimeGetCurrent() print("Postbox load took \((endTime - startTime) * 1000.0) ms") - subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox, timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations))) + subscriber.putNext(.postbox(Postbox(queue: queue, basePath: basePath, seedConfiguration: seedConfiguration, valueBox: valueBox, timestampForAbsoluteTimeBasedOperations: timestampForAbsoluteTimeBasedOperations, isTemporary: isTemporary))) subscriber.putCompletion() break } @@ -1330,7 +1335,7 @@ public final class Postbox { var installedMessageActionsByPeerId: [PeerId: Bag<([StoreMessage], Transaction) -> Void>] = [:] - init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32) { + init(queue: Queue, basePath: String, seedConfiguration: SeedConfiguration, valueBox: SqliteValueBox, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool) { assert(queue.isCurrent()) let startTime = CFAbsoluteTimeGetCurrent() @@ -1531,25 +1536,27 @@ public final class Postbox { self.messageHistoryMetadataTable.setShouldReindexUnreadCounts(value: false) } - for id in self.messageHistoryUnsentTable.get() { - transaction.updateMessage(id, update: { message in - if !message.flags.contains(.Failed) { - if message.timestamp + 60 * 10 > timestampForAbsoluteTimeBasedOperations { + if !isTemporary { + for id in self.messageHistoryUnsentTable.get() { + transaction.updateMessage(id, update: { message in + if !message.flags.contains(.Failed) { + if message.timestamp + 60 * 10 > timestampForAbsoluteTimeBasedOperations { + return .skip + } + var flags = StoreMessageFlags(message.flags) + flags.remove(.Unsent) + flags.remove(.Sending) + flags.insert(.Failed) + var storeForwardInfo: StoreMessageForwardInfo? + if let forwardInfo = message.forwardInfo { + storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) + } + return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) + } else { return .skip } - var flags = StoreMessageFlags(message.flags) - flags.remove(.Unsent) - flags.remove(.Sending) - flags.insert(.Failed) - var storeForwardInfo: StoreMessageForwardInfo? - if let forwardInfo = message.forwardInfo { - storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author?.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature, psaType: forwardInfo.psaType) - } - return .update(StoreMessage(id: message.id, globallyUniqueId: message.globallyUniqueId, groupingKey: message.groupingKey, threadId: message.threadId, timestamp: message.timestamp, flags: flags, tags: message.tags, globalTags: message.globalTags, localTags: message.localTags, forwardInfo: storeForwardInfo, authorId: message.author?.id, text: message.text, attributes: message.attributes, media: message.media)) - } else { - return .skip - } - }) + }) + } } }).start() } diff --git a/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift b/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift index c122d0c404..08d7d25b29 100644 --- a/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift +++ b/submodules/SyncCore/Sources/StandaloneAccountTransaction.swift @@ -69,14 +69,16 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = { }, additionalChatListIndexNamespace: Namespaces.Message.Cloud, messageNamespacesRequiringGroupStatsValidation: [Namespaces.Message.Cloud], defaultMessageNamespaceReadStates: [Namespaces.Message.Local: .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false)], chatMessagesNamespaces: Set([Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]), globalNotificationSettingsPreferencesKey: PreferencesKeys.globalNotifications, defaultGlobalNotificationSettings: GlobalNotificationSettings.defaultSettings) }() -public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Transaction) -> T) -> Signal { +public func accountTransaction(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Postbox, Transaction) -> T) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true) return postbox |> mapToSignal { value -> Signal in switch value { case let .postbox(postbox): - return postbox.transaction(transaction) + return postbox.transaction { t in + transaction(postbox, t) + } default: return .complete() } diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index f99d7bb4ea..59b8df9556 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -160,7 +160,7 @@ public enum AccountPreferenceEntriesResult { public func accountPreferenceEntries(rootPath: String, id: AccountRecordId, keys: Set, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true) return postbox |> mapToSignal { value -> Signal in switch value { @@ -187,7 +187,7 @@ public enum AccountNoticeEntriesResult { public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true) return postbox |> mapToSignal { value -> Signal in switch value { @@ -208,7 +208,7 @@ public enum LegacyAccessChallengeDataResult { public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true) return postbox |> mapToSignal { value -> Signal in switch value { @@ -225,7 +225,7 @@ public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecord public func accountWithId(accountManager: AccountManager, networkArguments: NetworkInitializationArguments, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, supplementary: Bool, rootPath: String, beginWithTestingEnvironment: Bool, backupData: AccountBackupData?, auxiliaryMethods: AccountAuxiliaryMethods, shouldKeepAutoConnection: Bool = true) -> Signal { let path = "\(rootPath)/\(accountRecordIdPathName(id))" - let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: false) return postbox |> mapToSignal { result -> Signal in diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index fc054406a1..d03a95ae5a 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -210,7 +210,7 @@ public func temporaryAccount(manager: AccountManager, rootPath: String, encrypti return manager.allocatedTemporaryAccountId() |> mapToSignal { id -> Signal in let path = "\(rootPath)/\(accountRecordIdPathName(id))" - return openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970)) + return openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: false) |> mapToSignal { result -> Signal in switch result { case .upgrading: diff --git a/submodules/TelegramUI/Sources/UpgradedAccounts.swift b/submodules/TelegramUI/Sources/UpgradedAccounts.swift index 6e8797da91..616c65da58 100644 --- a/submodules/TelegramUI/Sources/UpgradedAccounts.swift +++ b/submodules/TelegramUI/Sources/UpgradedAccounts.swift @@ -290,7 +290,7 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e |> mapToSignal { globalSettings, ids -> Signal in var importSignal: Signal = .complete() for id in ids { - let importInfoAccounttSignal = accountTransaction(rootPath: rootPath, id: id, encryptionParameters: encryptionParameters, transaction: { transaction -> Void in + let importInfoAccounttSignal = accountTransaction(rootPath: rootPath, id: id, encryptionParameters: encryptionParameters, transaction: { _, transaction -> Void in transaction.updatePreferencesEntry(key: PreferencesKeys.contactsSettings, { current in var settings = current as? ContactsSettings ?? ContactsSettings.defaultSettings settings.synchronizeContacts = globalSettings._legacySynchronizeDeviceContacts diff --git a/submodules/TelegramUI/Sources/WidgetDataContext.swift b/submodules/TelegramUI/Sources/WidgetDataContext.swift index fa70a4b152..1822576ce5 100644 --- a/submodules/TelegramUI/Sources/WidgetDataContext.swift +++ b/submodules/TelegramUI/Sources/WidgetDataContext.swift @@ -22,7 +22,7 @@ final class WidgetDataContext { }) |> mapToSignal { account -> Signal in guard let account = account else { - return .single(.notAuthorized) + return .single(WidgetData(accountId: 0, content: .notAuthorized)) } enum CombinedRecentPeers { @@ -106,9 +106,9 @@ final class WidgetDataContext { |> map { result -> WidgetData in switch result { case .disabled: - return .disabled + return WidgetData(accountId: account.id.int64, content: .disabled) case let .peers(peers, unread): - return .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in + return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in var name: String = "" var lastName: String? @@ -136,7 +136,7 @@ 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) - })) + }))) } } |> distinctUntilChanged diff --git a/submodules/WidgetItems/Sources/WidgetItems.swift b/submodules/WidgetItems/Sources/WidgetItems.swift index 6af023281c..703299b475 100644 --- a/submodules/WidgetItems/Sources/WidgetItems.swift +++ b/submodules/WidgetItems/Sources/WidgetItems.swift @@ -60,45 +60,55 @@ public func widgetPresentationDataPath(rootPath: String) -> String { return rootPath + "/widgetPresentationData.json" } -public enum WidgetData: Codable, Equatable { - private enum CodingKeys: CodingKey { - case discriminator - case peers - } - - private enum Cases: Int32, Codable { +public struct WidgetData: Codable, Equatable { + public enum Content: Codable, Equatable { + private enum CodingKeys: CodingKey { + case discriminator + case peers + } + + private enum Cases: Int32, Codable { + case notAuthorized + case disabled + case peers + } + case notAuthorized case disabled - case peers - } - - case notAuthorized - case disabled - 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 .peers: - self = .peers(try container.decode(WidgetDataPeers.self, forKey: .peers)) + 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 .peers: + self = .peers(try container.decode(WidgetDataPeers.self, forKey: .peers)) + } + } + + 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 let .peers(peers): + try container.encode(Cases.peers, forKey: .discriminator) + try container.encode(peers, forKey: .peers) + } } } - 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 let .peers(peers): - try container.encode(Cases.peers, forKey: .discriminator) - try container.encode(peers, forKey: .peers) - } + public var accountId: Int64 + public var content: Content + + public init(accountId: Int64, content: Content) { + self.accountId = accountId + self.content = content } }