Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
overtake 2020-12-23 18:58:20 +03:00
commit 954768fb00
56 changed files with 1196 additions and 539 deletions

View File

@ -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(
<string>INSearchForMessagesIntent</string>
<string>INSetMessageAttributeIntent</string>
<string>INSearchCallHistoryIntent</string>
<string>SelectFriendsIntent</string>
</array>
</dict>
<key>NSExtensionPointIdentifier</key>
@ -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 = [

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,317 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>INEnums</key>
<array>
<dict>
<key>INEnumDisplayName</key>
<string>Display Chats</string>
<key>INEnumDisplayNameID</key>
<string>k9oGrF</string>
<key>INEnumGeneratesHeader</key>
<true/>
<key>INEnumName</key>
<string>Contents</string>
<key>INEnumType</key>
<string>Regular</string>
<key>INEnumValues</key>
<array>
<dict>
<key>INEnumValueDisplayName</key>
<string>unknown</string>
<key>INEnumValueDisplayNameID</key>
<string>OnVgw0</string>
<key>INEnumValueName</key>
<string>unknown</string>
</dict>
<dict>
<key>INEnumValueDisplayName</key>
<string>Recent Chats</string>
<key>INEnumValueDisplayNameID</key>
<string>X0HAPm</string>
<key>INEnumValueIndex</key>
<integer>1</integer>
<key>INEnumValueName</key>
<string>recent</string>
</dict>
<dict>
<key>INEnumValueDisplayName</key>
<string>Custom</string>
<key>INEnumValueDisplayNameID</key>
<string>VpZ0cC</string>
<key>INEnumValueIndex</key>
<integer>2</integer>
<key>INEnumValueName</key>
<string>custom</string>
</dict>
</array>
</dict>
</array>
<key>INIntentDefinitionModelVersion</key>
<string>1.2</string>
<key>INIntentDefinitionNamespace</key>
<string>p74MWb</string>
<key>INIntentDefinitionSystemVersion</key>
<string>20C69</string>
<key>INIntentDefinitionToolsBuildVersion</key>
<string>12C33</string>
<key>INIntentDefinitionToolsVersion</key>
<string>12.3</string>
<key>INIntents</key>
<array>
<dict>
<key>INIntentCategory</key>
<string>information</string>
<key>INIntentDescriptionID</key>
<string>jmsEbj</string>
<key>INIntentEligibleForWidgets</key>
<true/>
<key>INIntentIneligibleForSuggestions</key>
<true/>
<key>INIntentLastParameterTag</key>
<integer>19</integer>
<key>INIntentName</key>
<string>SelectFriends</string>
<key>INIntentParameters</key>
<array>
<dict>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>Contents</string>
<key>INIntentParameterDisplayNameID</key>
<string>WAiyZm</string>
<key>INIntentParameterDisplayPriority</key>
<integer>1</integer>
<key>INIntentParameterEnumType</key>
<string>Contents</string>
<key>INIntentParameterEnumTypeNamespace</key>
<string>p74MWb</string>
<key>INIntentParameterMetadata</key>
<dict>
<key>INIntentParameterMetadataDefaultValue</key>
<string>recent</string>
</dict>
<key>INIntentParameterName</key>
<string>contents</string>
<key>INIntentParameterPromptDialogs</key>
<array>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Primary</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>There are ${count} options matching ${contents}.</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>oSRWBb</string>
<key>INIntentParameterPromptDialogType</key>
<string>DisambiguationIntroduction</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogFormatString</key>
<string>Just to confirm, you wanted ${contents}?</string>
<key>INIntentParameterPromptDialogFormatStringID</key>
<string>jvYJCG</string>
<key>INIntentParameterPromptDialogType</key>
<string>Confirmation</string>
</dict>
</array>
<key>INIntentParameterTag</key>
<integer>5</integer>
<key>INIntentParameterType</key>
<string>Integer</string>
</dict>
<dict>
<key>INIntentParameterArraySizes</key>
<array>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>4</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Small</string>
</dict>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>8</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Medium</string>
</dict>
<dict>
<key>INIntentParameterArraySizeSize</key>
<integer>16</integer>
<key>INIntentParameterArraySizeSizeClass</key>
<string>Large</string>
</dict>
</array>
<key>INIntentParameterConfigurable</key>
<true/>
<key>INIntentParameterDisplayName</key>
<string>Chats</string>
<key>INIntentParameterDisplayNameID</key>
<string>WIf4LD</string>
<key>INIntentParameterDisplayPriority</key>
<integer>2</integer>
<key>INIntentParameterFixedSizeArray</key>
<integer>1</integer>
<key>INIntentParameterName</key>
<string>friends</string>
<key>INIntentParameterObjectType</key>
<string>Friend</string>
<key>INIntentParameterObjectTypeNamespace</key>
<string>p74MWb</string>
<key>INIntentParameterPromptDialogs</key>
<array>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Configuration</string>
</dict>
<dict>
<key>INIntentParameterPromptDialogCustom</key>
<true/>
<key>INIntentParameterPromptDialogType</key>
<string>Primary</string>
</dict>
</array>
<key>INIntentParameterRelationship</key>
<dict>
<key>INIntentParameterRelationshipParentName</key>
<string>contents</string>
<key>INIntentParameterRelationshipPredicateName</key>
<string>EnumHasExactValue</string>
<key>INIntentParameterRelationshipPredicateValue</key>
<string>custom</string>
</dict>
<key>INIntentParameterSupportsDynamicEnumeration</key>
<true/>
<key>INIntentParameterSupportsMultipleValues</key>
<true/>
<key>INIntentParameterTag</key>
<integer>19</integer>
<key>INIntentParameterType</key>
<string>Object</string>
</dict>
</array>
<key>INIntentResponse</key>
<dict>
<key>INIntentResponseCodes</key>
<array>
<dict>
<key>INIntentResponseCodeName</key>
<string>success</string>
<key>INIntentResponseCodeSuccess</key>
<true/>
</dict>
<dict>
<key>INIntentResponseCodeName</key>
<string>failure</string>
</dict>
</array>
</dict>
<key>INIntentTitle</key>
<string>Select Chats</string>
<key>INIntentTitleID</key>
<string>lMot0c</string>
<key>INIntentType</key>
<string>Custom</string>
<key>INIntentVerb</key>
<string>View</string>
</dict>
</array>
<key>INTypes</key>
<array>
<dict>
<key>INTypeDisplayName</key>
<string>Chat</string>
<key>INTypeDisplayNameID</key>
<string>zsoXow</string>
<key>INTypeLastPropertyTag</key>
<integer>100</integer>
<key>INTypeName</key>
<string>Friend</string>
<key>INTypeProperties</key>
<array>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>1</integer>
<key>INTypePropertyName</key>
<string>identifier</string>
<key>INTypePropertyTag</key>
<integer>1</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>2</integer>
<key>INTypePropertyName</key>
<string>displayString</string>
<key>INTypePropertyTag</key>
<integer>2</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>3</integer>
<key>INTypePropertyName</key>
<string>pronunciationHint</string>
<key>INTypePropertyTag</key>
<integer>3</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
<dict>
<key>INTypePropertyDefault</key>
<true/>
<key>INTypePropertyDisplayPriority</key>
<integer>4</integer>
<key>INTypePropertyName</key>
<string>alternativeSpeakableMatches</string>
<key>INTypePropertySupportsMultipleValues</key>
<true/>
<key>INTypePropertyTag</key>
<integer>4</integer>
<key>INTypePropertyType</key>
<string>SpeakableString</string>
</dict>
<dict>
<key>INTypePropertyDisplayName</key>
<string>Subtitle</string>
<key>INTypePropertyDisplayNameID</key>
<string>nNNpdC</string>
<key>INTypePropertyDisplayPriority</key>
<integer>5</integer>
<key>INTypePropertyName</key>
<string>subtitle</string>
<key>INTypePropertyTag</key>
<integer>100</integer>
<key>INTypePropertyType</key>
<string>String</string>
</dict>
</array>
</dict>
</array>
</dict>
</plist>

View File

@ -2687,7 +2687,7 @@ Unused sets are archived when you add more.";
"Channel.AdminLogFilter.EventsEditedMessages" = "Edited Messages";
"Channel.AdminLogFilter.EventsPinned" = "Pinned Messages";
"Channel.AdminLogFilter.EventsLeaving" = "Members Removed";
"Channel.AdminLogFilter.EventsCalls" = "Vocie Chats";
"Channel.AdminLogFilter.EventsCalls" = "Voice Chats";
"Channel.AdminLogFilter.AdminsTitle" = "ADMINS";
"Channel.AdminLogFilter.AdminsAll" = "All Admins";
@ -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" = "Youre 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" = "Lets 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$@?";

View File

@ -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<ApplicationSettings, NoError> {
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<SimpleEntry>) -> 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<Entry>) -> ()) {
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[..<lastDotRange.lowerBound])
let appGroupName = "group.\(baseAppBundleId)"
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
}
let rootPath = rootPathForBasePath(appGroupUrl.path)
let dataPath = rootPath + "/widget-data"
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
return
}
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
let logsPath = rootPath + "/widget-logs"
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
setupSharedLogger(rootPath: rootPath, path: logsPath)
initializeAccountManagement()
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
let _ = (accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: widgetData.accountId), encryptionParameters: encryptionParameters, transaction: { postbox, transaction -> 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))
@ -108,31 +248,51 @@ struct WidgetView: View {
}
func peersView(geometry: GeometryProxy, peers: WidgetDataPeers) -> some View {
let defaultItemSize: CGFloat = 60.0
let defaultPaddingFraction: CGFloat = 0.36
let columnCount: Int
let rowCount: Int
let rowHeight: CGFloat
let topOffset: CGFloat
let itemSizeFraction: CGFloat
let horizontalInsetFraction: CGFloat
let verticalInsetFraction: CGFloat
let horizontalSpacingFraction: CGFloat
let verticalSpacingFraction: CGFloat
switch self.widgetFamily {
case .systemLarge:
itemSizeFraction = 0.1762917933
horizontalInsetFraction = 0.04863221884
verticalInsetFraction = 0.04863221884
horizontalSpacingFraction = 0.06079027356
verticalSpacingFraction = 0.06079027356
columnCount = 4
rowCount = 4
rowHeight = 88.0
topOffset = 12.0
default:
case .systemMedium:
itemSizeFraction = 0.1762917933
horizontalInsetFraction = 0.04863221884
verticalInsetFraction = 0.1032258065
horizontalSpacingFraction = 0.06079027356
verticalSpacingFraction = 0.07741935484
columnCount = 4
rowCount = 2
case .systemSmall:
itemSizeFraction = 0.335483871
horizontalInsetFraction = 0.1032258065
verticalInsetFraction = 0.1032258065
horizontalSpacingFraction = 0.1161290323
verticalSpacingFraction = 0.1161290323
columnCount = 2
rowCount = 2
@unknown default:
itemSizeFraction = 0.335483871
horizontalInsetFraction = 0.1032258065
verticalInsetFraction = 0.1032258065
horizontalSpacingFraction = 0.1161290323
verticalSpacingFraction = 0.1161290323
columnCount = 2
rowCount = 2
rowHeight = 76.0
topOffset = 0.0
}
let columnCount = Int(round(geometry.size.width / (defaultItemSize * (1.0 + defaultPaddingFraction))))
let itemSize = floor(geometry.size.width / (CGFloat(columnCount) + defaultPaddingFraction * CGFloat(columnCount - 1)))
let rowOffset: [CGFloat] = [
topOffset + itemSize / 2.0,
topOffset + itemSize / 2.0 + rowHeight,
topOffset + itemSize / 2.0 + rowHeight * 2,
topOffset + itemSize / 2.0 + rowHeight * 3,
]
let itemSize = floor(geometry.size.width * itemSizeFraction)
return ZStack {
ForEach(0 ..< min(peers.peers.count, columnCount * rowCount), content: { i in
@ -143,7 +303,7 @@ struct WidgetView: View {
itemSize: itemSize
).frame(width: itemSize, height: itemSize)
}).frame(width: itemSize, height: itemSize)
.position(x: itemSize / 2.0 + floor(CGFloat(i % columnCount) * itemSize * (1.0 + defaultPaddingFraction)), y: rowOffset[i / columnCount])
.position(x: floor(horizontalInsetFraction * geometry.size.width + itemSize / 2.0 + CGFloat(i % columnCount) * (itemSize + horizontalSpacingFraction * geometry.size.width)), y: floor(verticalInsetFraction * geometry.size.height + itemSize / 2.0 + CGFloat(i / columnCount) * (itemSize + verticalSpacingFraction * geometry.size.height)))
})
}
}
@ -162,17 +322,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)
})
}
}
@ -180,7 +333,7 @@ struct WidgetView: View {
ZStack {
peerViews()
}
.padding(.all)
.padding(0.0)
}
}
@ -227,32 +380,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[..<lastDotRange.lowerBound])
let appGroupName = "group.\(baseAppBundleId)"
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
return .placeholder
}
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
}
let dataPath = rootPath + "/widget-data"
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
return .data(widgetData)
} else {
return .placeholder
func getWidgetData(contents: SimpleEntry.Contents) -> 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[..<lastDotRange.lowerBound])
let appGroupName = "group.\(baseAppBundleId)"
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
guard let appGroupUrl = maybeAppGroupUrl else {
return .placeholder
}
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
}
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
}
} else {
return .placeholder
}
case let .peers(peers):
return .peers(peers)
}
}
@ -261,14 +426,10 @@ struct Static_Widget: Widget {
private let kind: String = "Static_Widget"
public var body: some WidgetConfiguration {
return StaticConfiguration(
kind: kind,
provider: Provider(),
content: { entry in
WidgetView(data: getWidgetData())
}
)
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
return IntentConfiguration(kind: kind, intent: SelectFriendsIntent.self, provider: Provider(), content: { entry in
WidgetView(data: getWidgetData(contents: entry.contents))
})
.supportedFamilies([.systemSmall, .systemMedium])
.configurationDisplayName(presentationData.widgetGalleryTitle)
.description(presentationData.widgetGalleryDescription)
}

View File

@ -640,6 +640,9 @@ public final class TonContext {
#endif
public protocol ComposeController: ViewController {
}
public protocol ChatLocationContextHolder: class {
}

View File

@ -227,6 +227,7 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
private let camera: Camera
private let codeDisposable = MetaDisposable()
private var torchDisposable: Disposable?
fileprivate let focusedCode = ValuePromise<CameraCode?>(ignoreRepeated: true)
private var focusedRect: CGRect?
@ -305,6 +306,13 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.torchDisposable = (self.camera.hasTorch
|> deliverOnMainQueue).start(next: { [weak self] hasTorch in
if let strongSelf = self {
strongSelf.torchButtonNode.isHidden = !hasTorch
}
})
self.addSubnode(self.previewNode)
self.addSubnode(self.fadeNode)
self.addSubnode(self.topDimNode)
@ -323,6 +331,7 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
deinit {
self.codeDisposable.dispose()
self.torchDisposable?.dispose()
self.camera.stopCapture(invalidate: true)
}

View File

@ -93,6 +93,10 @@ private final class CameraContext {
self.session.commitConfiguration()
}
var hasTorch: Signal<Bool, NoError> {
return self.device.isFlashAvailable
}
func setTorchActive(_ active: Bool) {
self.device.setTorchActive(active)
}
@ -189,6 +193,21 @@ public final class Camera {
}
}
public var hasTorch: Signal<Bool, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.queue.async {
if let context = self.contextRef?.takeUnretainedValue() {
disposable.set(context.hasTorch.start(next: { hasTorch in
subscriber.putNext(hasTorch)
subscriber.putCompletion()
}))
}
}
return disposable
}
}
public func attachPreviewNode(_ node: CameraPreviewNode) {
let nodeRef: Unmanaged<CameraPreviewNode> = Unmanaged.passRetained(node)
self.queue.async {

View File

@ -1799,8 +1799,20 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
}
@objc private func composePressed() {
let controller = self.context.sharedContext.makeComposeController(context: self.context)
(self.navigationController as? NavigationController)?.pushViewController(controller)
guard let navigationController = self.navigationController as? NavigationController else {
return
}
var hasComposeController = false
navigationController.viewControllers.forEach { controller in
if controller is ComposeController {
hasComposeController = true
}
}
if !hasComposeController {
let controller = self.context.sharedContext.makeComposeController(context: self.context)
navigationController.pushViewController(controller)
}
}
public override var keyShortcuts: [KeyShortcut] {

View File

@ -893,6 +893,14 @@ final class ChatListContainerNode: ASDisplayNode, UIGestureRecognizerDelegate {
insets.left += layout.safeInsets.left
insets.right += layout.safeInsets.right
if isEditing {
if !layout.safeInsets.left.isZero {
insets.bottom += 34.0
} else {
insets.bottom += 49.0
}
}
transition.updateAlpha(node: self, alpha: isReorderingFilters ? 0.5 : 1.0)
self.isUserInteractionEnabled = !isReorderingFilters

View File

@ -1250,28 +1250,30 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
currentSecretIconImage = PresentationResourcesChatList.secretIcon(item.presentationData.theme)
}
var credibilityIconOffset: CGFloat = 0.0
if displayAsMessage {
switch item.content {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
if let peer = messages.last?.author {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
if !isPeerGroup {
if displayAsMessage {
switch item.content {
case let .peer(messages, _, _, _, _, _, _, _, _, _, _, _):
if let peer = messages.last?.author {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
}
}
default:
break
}
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
}
default:
break
}
} else if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatMainPeer {
if peer.isScam {
currentCredibilityIconImage = PresentationResourcesChatList.scamIcon(item.presentationData.theme, type: .regular)
credibilityIconOffset = 2.0
} else if peer.isVerified {
currentCredibilityIconImage = PresentationResourcesChatList.verifiedIcon(item.presentationData.theme)
credibilityIconOffset = 3.0
}
}
if let currentSecretIconImage = currentSecretIconImage {

View File

@ -671,7 +671,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode {
if let placeholderNode = strongSelf.placeholderNode {
placeholderNode.frame = imageFrame
placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, small: true)
placeholderNode.update(backgroundColor: nil, foregroundColor: item.presentationData.theme.list.disclosureArrowColor.blitOver(item.presentationData.theme.list.itemBlocksBackgroundColor, alpha: 0.55), shimmeringColor: item.presentationData.theme.list.itemBlocksBackgroundColor.withAlphaComponent(0.4), data: item.packInfo.immediateThumbnailData, size: imageFrame.size, imageSize: CGSize(width: 100.0, height: 100.0))
}
}

View File

@ -38,6 +38,7 @@ typedef enum
@property (nonatomic, readonly) NSString *uniformTypeIdentifier;
@property (nonatomic, readonly) NSString *fileName;
@property (nonatomic, readonly) NSInteger fileSize;
@property (nonatomic, readonly) bool isFavorite;
@property (nonatomic, readonly) TGMediaAssetType type;
@property (nonatomic, readonly) TGMediaAssetSubtype subtypes;

View File

@ -9,6 +9,8 @@
@property (nonatomic, readonly) TGImageView *imageView;
@property (nonatomic, readonly) TGCheckButtonView *checkButton;
@property (nonatomic, readonly) UIImageView *typeIconView;
- (void)setHidden:(bool)hidden animated:(bool)animated;
@property (nonatomic, strong) TGMediaSelectionContext *selectionContext;

View File

@ -102,6 +102,11 @@
return [self.uniformTypeIdentifier isEqualToString:(NSString *)kUTTypeGIF];
}
- (bool)isFavorite
{
return _backingAsset.isFavorite;
}
- (TGMediaAssetType)type
{
if (_cachedType == nil)

View File

@ -1,5 +1,7 @@
#import "TGMediaAssetsPhotoCell.h"
#import <LegacyComponents/TGMediaAsset.h>
#import "LegacyComponentsInternal.h"
NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind";
@ -14,4 +16,15 @@ NSString *const TGMediaAssetsPhotoCellKind = @"TGMediaAssetsPhotoCellKind";
return self;
}
- (void)setItem:(NSObject *)item signal:(SSignal *)signal
{
[super setItem:item signal:signal];
TGMediaAsset *asset = (TGMediaAsset *)item;
if (![asset isKindOfClass:[TGMediaAsset class]])
return;
self.typeIconView.image = asset.isFavorite ? TGComponentsImageNamed(@"MediaGroupFavorites") : nil;
}
@end

View File

@ -15,7 +15,6 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
@interface TGMediaAssetsVideoCell ()
{
UIImageView *_shadowView;
UIImageView *_iconView;
UILabel *_durationLabel;
SMetaDisposable *_adjustmentsDisposable;
@ -64,10 +63,7 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
_shadowView = [[UIImageView alloc] initWithFrame:CGRectMake(0, frame.size.height - 20, frame.size.width, 20)];
_shadowView.image = shadowImage;
[self addSubview:_shadowView];
_iconView = [[UIImageView alloc] init];
_iconView.contentMode = UIViewContentModeCenter;
_durationLabel = [[UILabel alloc] init];
_durationLabel.textColor = [UIColor whiteColor];
_durationLabel.backgroundColor = [UIColor clearColor];
@ -81,7 +77,6 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
if (iosMajorVersion() >= 11)
{
_shadowView.accessibilityIgnoresInvertColors = true;
_iconView.accessibilityIgnoresInvertColors = true;
_durationLabel.accessibilityIgnoresInvertColors = true;
}
@ -103,7 +98,6 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
if (![asset isKindOfClass:[TGMediaAsset class]])
return;
NSString *durationString = nil;
int duration = (int)ceil(asset.videoDuration);
if (duration >= 3600)
@ -114,12 +108,7 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
_durationLabel.text = durationString;
[_durationLabel sizeToFit];
if (asset.subtypes & TGMediaAssetSubtypeVideoTimelapse)
_iconView.image = TGComponentsImageNamed(@"ModernMediaItemTimelapseIcon");
else if (asset.subtypes & TGMediaAssetSubtypeVideoHighFrameRate)
_iconView.image = TGComponentsImageNamed(@"ModernMediaItemSloMoIcon");
else
_iconView.image = TGComponentsImageNamed(@"ModernMediaItemVideoIcon");
self.typeIconView.image = asset.isFavorite ? TGComponentsImageNamed(@"MediaGroupFavorites") : nil;
SSignal *adjustmentsSignal = [self.editingContext adjustmentsSignalForItem:(id<TGMediaEditableItem>)self.item];
@ -234,7 +223,6 @@ NSString *const TGMediaAssetsVideoCellKind = @"TGMediaAssetsVideoCellKind";
{
self.checkButton.frame = (CGRect){ { self.frame.size.width - self.checkButton.frame.size.width - 2, 2 }, self.checkButton.frame.size };
_shadowView.frame = (CGRect){ { 0, self.frame.size.height - _shadowView.frame.size.height }, {self.frame.size.width, _shadowView.frame.size.height } };
_iconView.frame = CGRectMake(0, self.frame.size.height - 19, 19, 19);
CGSize durationSize = _durationLabel.frame.size;
_durationLabel.frame = CGRectMake(self.frame.size.width - floor(durationSize.width) - 5.0, self.frame.size.height - floor(durationSize.height) - 4.0, durationSize.width, durationSize.height);

View File

@ -34,6 +34,10 @@
if (iosMajorVersion() >= 11)
_imageView.accessibilityIgnoresInvertColors = true;
_typeIconView = [[UIImageView alloc] init];
_typeIconView.contentMode = UIViewContentModeCenter;
[self addSubview:_typeIconView];
self.isAccessibilityElement = true;
}
return self;
@ -212,6 +216,8 @@
_imageView.frame = self.bounds;
_imageView.transform = transform;
_typeIconView.frame = CGRectMake(2.0, self.frame.size.height - 19 - 2, 19, 19);
_checkButton.frame = (CGRect){ { self.frame.size.width - _checkButton.frame.size.width - 2, 2 }, _checkButton.frame.size };
}

View File

@ -1072,7 +1072,7 @@ static id<LegacyComponentsContext> _defaultContext = nil;
+ (UIEdgeInsets)safeAreaInsetForOrientation:(UIInterfaceOrientation)orientation hasOnScreenNavigation:(bool)hasOnScreenNavigation
{
int height = (int)TGScreenSize().height;
if (!TGIsPad() && (height != 812 && height != 896 && height != 780 && height != 844 && height != 926))
if (!TGIsPad() && (height != 812 && height != 896 && height != 780 && height != 844 && height != 926) && !hasOnScreenNavigation)
return UIEdgeInsetsZero;
if (TGIsPad()) {

View File

@ -853,9 +853,9 @@ public func channelVisibilityController(context: AccountContext, peerId: PeerId,
let revokeLinkDisposable = MetaDisposable()
actionsDisposable.add(revokeLinkDisposable)
actionsDisposable.add((context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<Void, NoError> in
actionsDisposable.add((context.account.viewTracker.peerView(peerId) |> filter { $0.cachedData != nil } |> take(1) |> mapToSignal { view -> Signal<String?, NoError> in
return ensuredExistingPeerExportedInvitation(account: context.account, peerId: peerId)
|> mapToSignal { _ -> Signal<Void, NoError> in
|> mapToSignal { _ -> Signal<String?, NoError> in
return .complete()
}
}).start())

View File

@ -1105,7 +1105,7 @@ func debugRestoreState(basePath:String, name: String) {
private let sharedQueue = Queue(name: "org.telegram.postbox.Postbox")
public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32) -> Signal<PostboxResult, NoError> {
public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration, encryptionParameters: ValueBoxEncryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32, isTemporary: Bool) -> Signal<PostboxResult, NoError> {
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()
}

View File

@ -128,7 +128,7 @@ private enum BlockedPeersEntry: ItemListNodeEntry {
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPersonIcon(theme), title: text, sectionId: self.section, editing: false, action: {
arguments.addPeer()
})
case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
case let .peerItem(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled):
let revealOptions = ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: strings.BlockedUsers_Unblock, action: {
arguments.removePeer(peer.id)
})])
@ -219,8 +219,6 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext:
let removePeerDisposable = MetaDisposable()
actionsDisposable.add(removePeerDisposable)
let peersPromise = Promise<[Peer]?>(nil)
let arguments = BlockedPeersControllerArguments(context: context, setPeerIdWithRevealedOptions: { peerId, fromPeerId in
updateState { state in
if (peerId == nil && fromPeerId == state.peerIdWithRevealedOptions) || (peerId != nil && fromPeerId == nil) {
@ -231,7 +229,7 @@ public func blockedPeersController(context: AccountContext, blockedPeersContext:
}
}, addPeer: {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeSavedMessages, .removeSearchHeader, .excludeRecent], title: presentationData.strings.BlockedUsers_SelectUserTitle))
let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyPrivateChats, .excludeSavedMessages, .removeSearchHeader, .excludeRecent, .doNotSearchMessages], title: presentationData.strings.BlockedUsers_SelectUserTitle))
controller.peerSelected = { [weak controller] peerId in
guard let strongController = controller else {
return

View File

@ -168,7 +168,7 @@ public class StickerShimmerEffectNode: ASDisplayNode {
self.effectNode.updateAbsoluteRect(rect, within: containerSize)
}
public func update(backgroundColor: UIColor?, foregroundColor: UIColor, shimmeringColor: UIColor, data: Data?, size: CGSize, small: Bool = false) {
public func update(backgroundColor: UIColor?, foregroundColor: UIColor, shimmeringColor: UIColor, data: Data?, size: CGSize, imageSize: CGSize = CGSize(width: 512.0, height: 512.0)) {
if data == nil {
return
}
@ -199,13 +199,13 @@ public class StickerShimmerEffectNode: ASDisplayNode {
if let data = data {
var path = decodeStickerThumbnailData(data)
if !path.hasPrefix("z") {
if !path.hasSuffix("z") {
path = "\(path)z"
}
let reader = PathDataReader(input: path)
let segments = reader.read()
let scale = size.width / (small ? 100.0 : 512.0)
let scale = max(size.width, size.height) / max(imageSize.width, imageSize.height)
context.scaleBy(x: scale, y: scale)
renderPath(segments, context: context)
} else {

View File

@ -85,6 +85,13 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
private let highlightedBackgroundNode: ASDisplayNode
private let maskNode: ASImageNode
let contextSourceNode: ContextExtractedContentContainingNode
private let containerNode: ContextControllerSourceNode
private let extractedBackgroundImageNode: ASImageNode
private var extractedRect: CGRect?
private var nonExtractedRect: CGRect?
let contentImageNode: TransformImageNode
let titleNode: TextNode
let labelNode: TextNode
@ -117,6 +124,13 @@ public class StatsMessageItemNode: ListViewItemNode, ItemListItemNode {
self.bottomStripeNode = ASDisplayNode()
self.bottomStripeNode.isLayerBacked = true
self.contextSourceNode = ContextExtractedContentContainingNode()
self.containerNode = ContextControllerSourceNode()
self.extractedBackgroundImageNode = ASImageNode()
self.extractedBackgroundImageNode.displaysAsynchronously = false
self.extractedBackgroundImageNode.alpha = 0.0
self.contentImageNode = TransformImageNode()
self.contentImageNode.isLayerBacked = true

View File

@ -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<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Transaction) -> T) -> Signal<T, NoError> {
public func accountTransaction<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Postbox, Transaction) -> T) -> Signal<T, NoError> {
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<T, NoError> in
switch value {
case let .postbox(postbox):
return postbox.transaction(transaction)
return postbox.transaction { t in
transaction(postbox, t)
}
default:
return .complete()
}

View File

@ -348,8 +348,12 @@ public class CallStatusBarNodeImpl: CallStatusBarNode {
if let membersCount = membersCount {
var membersPart = presentationData.strings.VoiceChat_Status_Members(membersCount)
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
membersPart.removeSubrange(startIndex ... endIndex)
if membersPart.contains("[") && membersPart.contains("]") {
if let startIndex = membersPart.firstIndex(of: "["), let endIndex = membersPart.firstIndex(of: "]") {
membersPart.removeSubrange(startIndex ... endIndex)
}
} else {
membersPart = membersPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
}
let rawTextAndRanges = presentationData.strings.VoiceChat_Status_MembersFormat("\(membersCount)", membersPart)

View File

@ -15,6 +15,8 @@ private let blue = UIColor(rgb: 0x0078ff)
private let lightBlue = UIColor(rgb: 0x59c7f8)
private let green = UIColor(rgb: 0x33c659)
private let activeBlue = UIColor(rgb: 0x00a0b9)
private let purple = UIColor(rgb: 0x6b81f0)
private let pink = UIColor(rgb: 0xd75a76)
private let areaSize = CGSize(width: 440.0, height: 440.0)
private let blobSize = CGSize(width: 244.0, height: 244.0)

View File

@ -2079,6 +2079,7 @@ public final class VoiceChatController: ViewController {
private var panGestureArguments: (topInset: CGFloat, offset: CGFloat)?
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
let contentOffset = self.listNode.visibleContentOffset()
switch recognizer.state {
case .began:
let topInset: CGFloat
@ -2096,7 +2097,8 @@ public final class VoiceChatController: ViewController {
if let (currentTopInset, currentPanOffset) = self.panGestureArguments {
topInset = currentTopInset
if case let .known(value) = self.listNode.visibleContentOffset(), value > 0 {
if case let .known(value) = contentOffset, value <= 0.5 {
} else {
translation = currentPanOffset
if self.isExpanded {
recognizer.setTranslation(CGPoint(), in: self.contentContainer.view)
@ -2114,7 +2116,6 @@ public final class VoiceChatController: ViewController {
}
if self.isExpanded {
} else {
if currentOffset > 0.0 {
self.listNode.scroller.panGestureRecognizer.setTranslation(CGPoint(), in: self.listNode.scroller)
@ -2136,7 +2137,9 @@ public final class VoiceChatController: ViewController {
let translation = recognizer.translation(in: self.contentContainer.view)
var velocity = recognizer.velocity(in: self.contentContainer.view)
if case let .known(value) = self.listNode.visibleContentOffset(), value > 0 {
if case let .known(value) = contentOffset, value > 0.0 {
velocity = CGPoint()
} else if case .unknown = contentOffset {
velocity = CGPoint()
}
@ -2164,6 +2167,7 @@ public final class VoiceChatController: ViewController {
self.isExpanded = false
self.updateIsFullscreen(false)
self.animatingExpansion = true
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
@ -2193,6 +2197,12 @@ public final class VoiceChatController: ViewController {
self.updateIsFullscreen(true)
self.animatingExpansion = true
if velocity.y > -1500.0 {
DispatchQueue.main.async {
self.listNode.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: [.Synchronous, .LowLatency], scrollToItem: ListViewScrollToItem(index: 0, position: .top(0.0), animated: true, curve: .Default(duration: nil), directionHint: .Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })
}
}
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
@ -2202,6 +2212,7 @@ public final class VoiceChatController: ViewController {
} else {
self.updateIsFullscreen(false)
self.animatingExpansion = true
self.listNode.scroller.setContentOffset(CGPoint(), animated: false)
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))

View File

@ -160,7 +160,7 @@ public enum AccountPreferenceEntriesResult {
public func accountPreferenceEntries(rootPath: String, id: AccountRecordId, keys: Set<ValueBoxKey>, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<AccountPreferenceEntriesResult, NoError> {
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<AccountPreferenceEntriesResult, NoError> in
switch value {
@ -187,7 +187,7 @@ public enum AccountNoticeEntriesResult {
public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<AccountNoticeEntriesResult, NoError> {
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<AccountNoticeEntriesResult, NoError> in
switch value {
@ -208,7 +208,7 @@ public enum LegacyAccessChallengeDataResult {
public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters) -> Signal<LegacyAccessChallengeDataResult, NoError> {
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<LegacyAccessChallengeDataResult, NoError> 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<AccountResult, NoError> {
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<AccountResult, NoError> in

View File

@ -210,7 +210,7 @@ public func temporaryAccount(manager: AccountManager, rootPath: String, encrypti
return manager.allocatedTemporaryAccountId()
|> mapToSignal { id -> Signal<TemporaryAccount, NoError> 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<TemporaryAccount, NoError> in
switch result {
case .upgrading:

View File

@ -224,10 +224,11 @@ public func stringForMediaKind(_ kind: MessageContentKind, strings: Presentation
}
public func descriptionStringForMessage(contentSettings: ContentSettings, message: Message, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, accountPeerId: PeerId) -> (String, Bool) {
if !message.text.isEmpty {
let contentKind = messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId)
if !message.text.isEmpty && ![.expiredImage, .expiredVideo].contains(contentKind.key) {
return (foldLineBreaks(message.text), false)
}
return stringForMediaKind(messageContentKind(contentSettings: contentSettings, message: message, strings: strings, nameDisplayOrder: nameDisplayOrder, accountPeerId: accountPeerId), strings: strings)
return stringForMediaKind(contentKind, strings: strings)
}
public func foldLineBreaks(_ text: String) -> String {

View File

@ -9778,7 +9778,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
}
private func forwardMessages(messages: [Message], resetCurrent: Bool) {
var filter: ChatListNodePeersFilter = [.onlyWriteable, .includeSavedMessages, .excludeDisabled]
var filter: ChatListNodePeersFilter = [.onlyWriteable, .includeSavedMessages, .excludeDisabled, .doNotSearchMessages]
var hasPublicPolls = false
var hasPublicQuiz = false
for message in messages {

View File

@ -115,7 +115,6 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
self.highlightNode.frame = CGRect(origin: CGPoint(x: floor((boundingSize.width - highlightSize.width) / 2.0) + verticalOffset - UIScreenPixel, y: floor((boundingSize.height - highlightSize.height) / 2.0) - UIScreenPixel), size: highlightSize)
self.imageNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
self.imageNode.contentAnimations = [.firstUpdate]
super.init(layerBacked: false, dynamicBounce: false)
@ -236,7 +235,7 @@ final class ChatMediaInputStickerPackItemNode: ListViewItemNode {
let placeholderFrame = CGRect(origin: CGPoint(x: floor((boundingSize.width - imageSize.width) / 2.0) + verticalOffset, y: floor((boundingSize.height - imageSize.height) / 2.0)), size: imageSize)
placeholderNode.frame = placeholderFrame
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, small: true)
placeholderNode.update(backgroundColor: nil, foregroundColor: theme.chat.inputMediaPanel.stickersSectionTextColor.blitOver(theme.chat.inputPanel.panelBackgroundColor, alpha: 0.4), shimmeringColor: theme.chat.inputMediaPanel.panelHighlightedIconBackgroundColor.withMultipliedAlpha(0.2), data: info.immediateThumbnailData, size: imageSize, imageSize: CGSize(width: 100.0, height: 100.0))
}
self.updateIsHighlighted()

View File

@ -243,6 +243,9 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
}
if image != nil {
strongSelf.removePlaceholder(animated: !firstTime)
if firstTime {
strongSelf.animationNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
firstTime = false
}
@ -963,10 +966,10 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
file = telegramFile
}
if let immediateThumbnailData = file?.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
if let file = file, let immediateThumbnailData = file.immediateThumbnailData, let placeholderNode = strongSelf.placeholderNode {
let foregroundColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderColor, wallpaper: item.presentationData.theme.wallpaper)
let shimmeringColor = bubbleVariableColor(variableColor: item.presentationData.theme.theme.chat.message.stickerPlaceholderShimmerColor, wallpaper: item.presentationData.theme.wallpaper)
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size)
placeholderNode.update(backgroundColor: nil, foregroundColor: foregroundColor, shimmeringColor: shimmeringColor, data: immediateThumbnailData, size: animationNodeFrame.size, imageSize: file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0))
placeholderNode.frame = animationNodeFrame
}
@ -1528,7 +1531,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
selectionNode.updateSelected(selected, animated: false)
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
} else {
let selectionNode = ChatMessageSelectionNode(wallpaper: item.presentationData.theme.wallpaper, theme: item.presentationData.theme.theme, toggle: { [weak self] value in
@ -1539,7 +1542,7 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView {
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
self.addSubnode(selectionNode)
self.selectionNode = selectionNode
selectionNode.updateSelected(selected, animated: false)

View File

@ -2636,7 +2636,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let offset: CGFloat = params.leftInset + (incoming ? 42.0 : 0.0)
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: params.width, height: layout.contentSize.height))
strongSelf.selectionNode?.frame = selectionFrame
strongSelf.selectionNode?.updateLayout(size: selectionFrame.size)
strongSelf.selectionNode?.updateLayout(size: selectionFrame.size, leftInset: params.leftInset)
if let actionButtonsSizeAndApply = actionButtonsSizeAndApply {
var animated = false
@ -3446,7 +3446,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
selectionNode.updateSelected(selected, animated: animated)
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentSize.width, height: self.contentSize.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
} else {
let selectionNode = ChatMessageSelectionNode(wallpaper: item.presentationData.theme.wallpaper, theme: item.presentationData.theme.theme, toggle: { [weak self] value in
@ -3462,7 +3462,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentSize.width, height: self.contentSize.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
self.insertSubnode(selectionNode, belowSubnode: self.messageAccessibilityArea)
self.selectionNode = selectionNode
selectionNode.updateSelected(selected, animated: false)

View File

@ -148,8 +148,12 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
rawAlternativeSegments = rawSegments
} else if dateReplies > 0 {
var commentsPart = item.presentationData.strings.Conversation_MessageViewComments(Int32(dateReplies))
if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") {
commentsPart.removeSubrange(startIndex ... endIndex)
if commentsPart.contains("[") && commentsPart.contains("]") {
if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") {
commentsPart.removeSubrange(startIndex ... endIndex)
}
} else {
commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,. "))
}
var segments: [AnimatedCountLabelNode.Segment] = []
@ -170,7 +174,7 @@ final class ChatMessageCommentFooterContentNode: ChatMessageBubbleContentNode {
}
latestIndex = range.upperBound
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: range.upperBound)])
let part = String(rawText[rawText.index(rawText.startIndex, offsetBy: lowerSegmentIndex) ..< rawText.index(rawText.startIndex, offsetBy: min(rawText.count, range.upperBound))])
if index == 0 {
segments.append(.number(dateReplies, NSAttributedString(string: part, font: textFont, textColor: messageTheme.accentTextColor)))
} else {

View File

@ -452,7 +452,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
strongSelf.appliedItem = item
strongSelf.appliedForwardInfo = (forwardSource, forwardAuthorSignature)
let transition: ContainedViewLayoutTransition
if animation.isAnimated {
transition = .animated(duration: 0.2, curve: .spring)
@ -886,7 +886,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
if let selectionNode = self.selectionNode {
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
selectionNode.updateSelected(selected, animated: animated)
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
} else {
@ -897,7 +897,7 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView {
})
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
self.addSubnode(selectionNode)
self.selectionNode = selectionNode
selectionNode.updateSelected(selected, animated: false)

View File

@ -424,6 +424,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
node.contentSize = layout.contentSize
node.insets = layout.insets
node.safeInsets = UIEdgeInsets(top: 0.0, left: params.leftInset, bottom: 0.0, right: params.rightInset)
node.updateSelectionState(animated: false)
node.updateHighlightedState(animated: false)
@ -493,6 +494,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible {
completion(layout, { _ in
apply(animation, false)
if let nodeValue = node() as? ChatMessageItemView {
nodeValue.safeInsets = UIEdgeInsets(top: 0.0, left: params.leftInset, bottom: 0.0, right: params.rightInset)
nodeValue.updateSelectionState(animated: false)
nodeValue.updateHighlightedState(animated: false)
}

View File

@ -647,6 +647,7 @@ public class ChatMessageItemView: ListViewItemNode {
var item: ChatMessageItem?
var accessibilityData: ChatMessageAccessibilityData?
var safeInsets = UIEdgeInsets()
var awaitingAppliedReaction: (String?, () -> Void)?

View File

@ -50,8 +50,8 @@ final class ChatMessageSelectionNode: ASDisplayNode {
}
}
func updateLayout(size: CGSize) {
func updateLayout(size: CGSize, leftInset: CGFloat) {
let checkSize = CGSize(width: 32.0, height: 32.0)
self.checkNode.frame = CGRect(origin: CGPoint(x: 4.0, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
self.checkNode.frame = CGRect(origin: CGPoint(x: 4.0 + leftInset, y: floor((size.height - checkSize.height) / 2.0)), size: checkSize)
}
}

View File

@ -65,6 +65,9 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
}
if image != nil {
strongSelf.removePlaceholder(animated: !firstTime)
if firstTime {
strongSelf.imageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
}
}
firstTime = false
}
@ -673,9 +676,14 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
replyInfoNode.frame = replyInfoFrame
strongSelf.replyBackgroundNode?.frame = replyBackgroundFrame ?? CGRect()
if let _ = item.controllerInteraction.selectionState, isEmoji {
replyInfoNode.alpha = 0.0
strongSelf.replyBackgroundNode?.alpha = 0.0
if isEmoji && !incoming {
if let _ = item.controllerInteraction.selectionState {
replyInfoNode.alpha = 0.0
strongSelf.replyBackgroundNode?.alpha = 0.0
} else {
replyInfoNode.alpha = 1.0
strongSelf.replyBackgroundNode?.alpha = 1.0
}
}
} else if let replyInfoNode = strongSelf.replyInfoNode {
replyInfoNode.removeFromSupernode()
@ -1007,7 +1015,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
if let selectionNode = self.selectionNode {
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
selectionNode.updateSelected(selected, animated: animated)
self.subnodeTransform = CATransform3DMakeTranslation(offset, 0.0, 0.0);
} else {
@ -1018,7 +1026,7 @@ class ChatMessageStickerItemNode: ChatMessageItemView {
})
let selectionFrame = CGRect(origin: CGPoint(x: -offset, y: 0.0), size: CGSize(width: self.contentBounds.size.width, height: self.contentBounds.size.height))
selectionNode.frame = selectionFrame
selectionNode.updateLayout(size: selectionFrame.size)
selectionNode.updateLayout(size: selectionFrame.size, leftInset: self.safeInsets.left)
self.addSubnode(selectionNode)
self.selectionNode = selectionNode
selectionNode.updateSelected(selected, animated: false)

View File

@ -677,7 +677,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate {
if let previousAdditionalSideInsets = previousAdditionalSideInsets, previousAdditionalSideInsets.right != additionalSideInsets.right {
additionalOffset = (previousAdditionalSideInsets.right - additionalSideInsets.right) / 3.0
if case let .animated(duration, curve) = transition {
if case let .animated(duration, _) = transition {
transition = .animated(duration: 0.2, curve: .easeInOut)
}
}

View File

@ -152,8 +152,13 @@ final class ChatTitleView: UIView, NavigationBarTitleView {
case .replies:
commentsPart = self.strings.Conversation_TitleReplies(Int32(count))
}
if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") {
commentsPart.removeSubrange(startIndex ... endIndex)
if commentsPart.contains("[") && commentsPart.contains("]") {
if let startIndex = commentsPart.firstIndex(of: "["), let endIndex = commentsPart.firstIndex(of: "]") {
commentsPart.removeSubrange(startIndex ... endIndex)
}
} else {
commentsPart = commentsPart.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
}
let rawTextAndRanges: (String, [(Int, NSRange)])

View File

@ -15,7 +15,7 @@ import TelegramPermissionsUI
import AppBundle
import DeviceAccess
public class ComposeController: ViewController {
public class ComposeControllerImpl: ViewController, ComposeController {
private let context: AccountContext
private var contactsNode: ComposeControllerNode {

View File

@ -36,10 +36,12 @@ private func chatInputStateString(attributedString: NSAttributedString) -> NSAtt
attributedString.enumerateAttributes(in: NSRange(location: 0, length: attributedString.length), options: [], using: { attributes, range, _ in
if let value = attributes[.link], let url = (value as? URL)?.absoluteString {
string.addAttribute(ChatTextInputAttributes.textUrl, value: ChatTextInputTextUrlAttribute(url: url), range: range)
}
else if let value = attributes[.font], let font = value as? UIFont {
} else if let value = attributes[.font], let font = value as? UIFont {
let fontName = font.fontName.lowercased()
if fontName.contains("bold") {
if fontName.contains("bolditalic") {
string.addAttribute(ChatTextInputAttributes.bold, value: true as NSNumber, range: range)
string.addAttribute(ChatTextInputAttributes.italic, value: true as NSNumber, range: range)
} else if fontName.contains("bold") {
string.addAttribute(ChatTextInputAttributes.bold, value: true as NSNumber, range: range)
} else if fontName.contains("italic") {
string.addAttribute(ChatTextInputAttributes.italic, value: true as NSNumber, range: range)

View File

@ -974,6 +974,13 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro
displayMore = false
}
}
var canReport = true
if channel.isVerified || channel.adminRights != nil || channel.flags.contains(.isCreator) {
canReport = false
}
if !canReport && !canViewStats && displayLeave {
displayMore = false
}
if displayMore {
result.append(.more)
}

View File

@ -1595,6 +1595,14 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c.dismiss(completion: {
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(id: message.id, highlight: true)))
}
})
})))
if let linkForCopying = linkForCopying {
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_ContextMenuCopyLink, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Copy"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c.dismiss(completion: {})
@ -1609,13 +1617,6 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}
})
})))
items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.SharedMedia_ViewInChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/GoToMessage"), color: theme.contextMenu.primaryColor) }, action: { c, _ in
c.dismiss(completion: {
if let strongSelf = self, let navigationController = strongSelf.controller?.navigationController as? NavigationController {
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: strongSelf.context, chatLocation: .peer(strongSelf.peerId), subject: .message(id: message.id, highlight: true)))
}
})
})))
if actions.options.contains(.deleteLocally) || actions.options.contains(.deleteGlobally) {
let context = strongSelf.context
let presentationData = strongSelf.presentationData
@ -4615,11 +4616,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|> deliverOnMainQueue).start(next: { [weak self] activeSessionsContextAndCount in
if let strongSelf = self, let activeSessionsContextAndCount = activeSessionsContextAndCount {
let (activeSessionsContext, count, webSessionsContext) = activeSessionsContextAndCount
if count == 0 && settings.enableQRLogin {
strongSelf.controller?.push(AuthDataTransferSplashScreen(context: strongSelf.context, activeSessionsContext: activeSessionsContext))
} else {
strongSelf.controller?.push(recentSessionsController(context: strongSelf.context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
}
strongSelf.controller?.push(recentSessionsController(context: strongSelf.context, activeSessionsContext: activeSessionsContext, webSessionsContext: webSessionsContext, websitesOnly: false))
}
})
}

View File

@ -228,7 +228,8 @@ final class PeerSelectionControllerNode: ASDisplayNode {
if let requestOpenMessageFromSearch = self?.requestOpenMessageFromSearch {
requestOpenMessageFromSearch(peer, messageId)
}
}, addContact: nil, peerContextAction: nil, present: { _, _ in
}, addContact: nil, peerContextAction: nil, present: { [weak self] c, a in
self?.present(c, a)
}, presentInGlobalOverlay: { _, _ in
}, navigationController: nil), cancel: { [weak self] in
if let requestDeactivateSearch = self?.requestDeactivateSearch {

View File

@ -1176,7 +1176,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeComposeController(context: AccountContext) -> ViewController {
return ComposeController(context: context)
return ComposeControllerImpl(context: context)
}
public func makeProxySettingsController(context: AccountContext) -> ViewController {

View File

@ -290,7 +290,7 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e
|> mapToSignal { globalSettings, ids -> Signal<Never, NoError> in
var importSignal: Signal<Never, NoError> = .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

View File

@ -22,7 +22,7 @@ final class WidgetDataContext {
})
|> mapToSignal { account -> Signal<WidgetData, NoError> 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

View File

@ -48,6 +48,9 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
if query.hasPrefix("s/") {
query = String(query[query.index(query.startIndex, offsetBy: 2)...])
}
if query.hasSuffix("/") {
query.removeLast()
}
if let components = URLComponents(string: "/" + query) {
var pathComponents = components.path.components(separatedBy: "/")
if !pathComponents.isEmpty {

View File

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