Added support for derived localizations

Added support for confirming two-step verification email via code
This commit is contained in:
Peter Iakovlev 2018-11-06 00:37:00 +04:00
parent 532285b062
commit eecd183a28
13 changed files with 347 additions and 199 deletions

View File

@ -113,6 +113,8 @@
D017495E1E118F790057C89A /* AccountStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495D1E118F790057C89A /* AccountStateManager.swift */; }; D017495E1E118F790057C89A /* AccountStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495D1E118F790057C89A /* AccountStateManager.swift */; };
D01749601E118FC30057C89A /* AccountIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495F1E118FC30057C89A /* AccountIntermediateState.swift */; }; D01749601E118FC30057C89A /* AccountIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D017495F1E118FC30057C89A /* AccountIntermediateState.swift */; };
D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; }; D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; };
D01843A82190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */; };
D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */; };
D018D3371E648ACF00C5E089 /* ChannelCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* ChannelCreation.swift */; }; D018D3371E648ACF00C5E089 /* ChannelCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* ChannelCreation.swift */; };
D018D3381E648ACF00C5E089 /* ChannelCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* ChannelCreation.swift */; }; D018D3381E648ACF00C5E089 /* ChannelCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* ChannelCreation.swift */; };
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */; }; D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */; };
@ -807,6 +809,7 @@
D017495D1E118F790057C89A /* AccountStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStateManager.swift; sourceTree = "<group>"; }; D017495D1E118F790057C89A /* AccountStateManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStateManager.swift; sourceTree = "<group>"; };
D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = "<group>"; }; D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = "<group>"; };
D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = "<group>"; }; D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = "<group>"; };
D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmTwoStepRecoveryEmail.swift; sourceTree = "<group>"; };
D018D3361E648ACF00C5E089 /* ChannelCreation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelCreation.swift; sourceTree = "<group>"; }; D018D3361E648ACF00C5E089 /* ChannelCreation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelCreation.swift; sourceTree = "<group>"; };
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer73.swift; sourceTree = "<group>"; }; D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer73.swift; sourceTree = "<group>"; };
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretChatLayerNegotiation.swift; sourceTree = "<group>"; }; D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretChatLayerNegotiation.swift; sourceTree = "<group>"; };
@ -1552,6 +1555,7 @@
D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */, D0B1671C1F9EA2C300976B40 /* ChatHistoryPreloadManager.swift */,
D06ECFC720B810D300C576C2 /* TermsOfService.swift */, D06ECFC720B810D300C576C2 /* TermsOfService.swift */,
D051DB16215ECC4D00F30F92 /* AppChangelog.swift */, D051DB16215ECC4D00F30F92 /* AppChangelog.swift */,
D01843A72190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift */,
); );
name = Account; name = Account;
sourceTree = "<group>"; sourceTree = "<group>";
@ -2209,6 +2213,7 @@
D0ADF911212B00DD00310BBC /* SecureIdConfiguration.swift in Sources */, D0ADF911212B00DD00310BBC /* SecureIdConfiguration.swift in Sources */,
D0448CA51E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */, D0448CA51E29215A005A61A7 /* MediaResourceApiUtils.swift in Sources */,
D0C26D661FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */, D0C26D661FE022DB004ABF18 /* SynchronizeGroupedPeersOperation.swift in Sources */,
D01843A82190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */,
D03C53771DAFF20F004C17B3 /* MultipartUpload.swift in Sources */, D03C53771DAFF20F004C17B3 /* MultipartUpload.swift in Sources */,
D00C7CE01E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */, D00C7CE01E3785710080C3D5 /* MarkMessageContentAsConsumedInteractively.swift in Sources */,
C2E0646D1ECF171D00387BB8 /* TelegramMediaWebDocument.swift in Sources */, C2E0646D1ECF171D00387BB8 /* TelegramMediaWebDocument.swift in Sources */,
@ -2790,6 +2795,7 @@
D0B844301DAB91E0005F29E1 /* NBNumberFormat.m in Sources */, D0B844301DAB91E0005F29E1 /* NBNumberFormat.m in Sources */,
D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */, D001F3F71E128A1C007A8C60 /* ApplyUpdateMessage.swift in Sources */,
D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */, D0B418971D7E0580004562A4 /* TelegramMediaImage.swift in Sources */,
D01843A92190C28100278AFF /* ConfirmTwoStepRecoveryEmail.swift in Sources */,
D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */, D041E3F91E535A88008C24B4 /* RemovePeerMember.swift in Sources */,
D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */, D049EAF61E44DF3300A2CD3A /* AccountState.swift in Sources */,
D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */, D0467D1620D7F2C90055C28F /* ManagedSynchronizeMarkAllUnseenPersonalMessagesOperations.swift in Sources */,

View File

@ -153,7 +153,7 @@ public class UnauthorizedAccount {
return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in return self.postbox.transaction { transaction -> (LocalizationSettings?, ProxySettings?, NetworkSettings?) in
return (transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings, transaction.getPreferencesEntry(key: PreferencesKeys.proxySettings) as? ProxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings) return (transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings, transaction.getPreferencesEntry(key: PreferencesKeys.proxySettings) as? ProxySettings, transaction.getPreferencesEntry(key: PreferencesKeys.networkSettings) as? NetworkSettings)
} |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in } |> mapToSignal { (localizationSettings, proxySettings, networkSettings) -> Signal<UnauthorizedAccount, NoError> in
return initializedNetwork(arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) return initializedNetwork(arguments: self.networkArguments, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
|> map { network in |> map { network in
let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) let updated = UnauthorizedAccount(networkArguments: self.networkArguments, id: self.id, rootPath: self.rootPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network)
updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get())
@ -340,7 +340,7 @@ public func accountWithId(networkArguments: NetworkInitializationArguments, id:
if let accountState = accountState { if let accountState = accountState {
switch accountState { switch accountState {
case let unauthorizedState as UnauthorizedAccountState: case let unauthorizedState as UnauthorizedAccountState:
return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
|> map { network -> AccountResult in |> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: unauthorizedState.isTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
} }
@ -349,7 +349,7 @@ public func accountWithId(networkArguments: NetworkInitializationArguments, id:
return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone return (transaction.getPeer(authorizedState.peerId) as? TelegramUser)?.phone
} }
|> mapToSignal { phoneNumber in |> mapToSignal { phoneNumber in
return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber) return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: phoneNumber)
|> map { network -> AccountResult in |> map { network -> AccountResult in
return .authorized(Account(id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods)) return .authorized(Account(id: id, basePath: path, testingEnvironment: authorizedState.isTestingEnvironment, postbox: postbox, network: network, networkArguments: networkArguments, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods))
} }
@ -359,7 +359,7 @@ public func accountWithId(networkArguments: NetworkInitializationArguments, id:
} }
} }
return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil) return initializedNetwork(arguments: networkArguments, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: beginWithTestingEnvironment, languageCode: localizationSettings?.primaryComponent.languageCode, proxySettings: proxySettings, networkSettings: networkSettings, phoneNumber: nil)
|> map { network -> AccountResult in |> map { network -> AccountResult in
return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) return .unauthorized(UnauthorizedAccount(networkArguments: networkArguments, id: id, rootPath: rootPath, basePath: path, testingEnvironment: beginWithTestingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection))
} }
@ -963,6 +963,9 @@ public class Account {
}) })
self.localInputActivityManager = PeerInputActivityManager() self.localInputActivityManager = PeerInputActivityManager()
self.accountPresenceManager = AccountPresenceManager(shouldKeepOnlinePresence: self.shouldKeepOnlinePresence.get(), network: network) self.accountPresenceManager = AccountPresenceManager(shouldKeepOnlinePresence: self.shouldKeepOnlinePresence.get(), network: network)
let _ = (postbox.transaction { transaction -> Void in
transaction.updatePeerPresencesInternal([peerId: TelegramUserPresence(status: .present(until: Int32.max - 1))])
}).start()
self.notificationAutolockReportManager = NotificationAutolockReportManager(deadline: self.autolockReportDeadline.get(), network: network) self.notificationAutolockReportManager = NotificationAutolockReportManager(deadline: self.autolockReportDeadline.get(), network: network)
self.autolockReportDeadline.set( self.autolockReportDeadline.set(
postbox.combinedView(keys: [.accessChallengeData]) postbox.combinedView(keys: [.accessChallengeData])
@ -1128,27 +1131,27 @@ public class Account {
})) }))
let shouldBeMaster = combineLatest(shouldBeServiceTaskMaster.get(), postbox.isMasterClient()) let shouldBeMaster = combineLatest(shouldBeServiceTaskMaster.get(), postbox.isMasterClient())
|> map { [weak self] shouldBeMaster, isMaster -> Bool in |> map { [weak self] shouldBeMaster, isMaster -> Bool in
if shouldBeMaster == .always && !isMaster { if shouldBeMaster == .always && !isMaster {
self?.postbox.becomeMasterClient() self?.postbox.becomeMasterClient()
}
return (shouldBeMaster == .now || shouldBeMaster == .always) && isMaster
} }
|> distinctUntilChanged return (shouldBeMaster == .now || shouldBeMaster == .always) && isMaster
}
|> distinctUntilChanged
self.network.shouldKeepConnection.set(shouldBeMaster) self.network.shouldKeepConnection.set(shouldBeMaster)
self.network.shouldExplicitelyKeepWorkerConnections.set(self.shouldExplicitelyKeepWorkerConnections.get()) self.network.shouldExplicitelyKeepWorkerConnections.set(self.shouldExplicitelyKeepWorkerConnections.get())
let serviceTasksMaster = shouldBeMaster let serviceTasksMaster = shouldBeMaster
|> deliverOn(self.serviceQueue) |> deliverOn(self.serviceQueue)
|> mapToSignal { [weak self] value -> Signal<Void, NoError> in |> mapToSignal { [weak self] value -> Signal<Void, NoError> in
if let strongSelf = self, value { if let strongSelf = self, value {
Logger.shared.log("Account", "Became master") Logger.shared.log("Account", "Became master")
return managedServiceViews(accountPeerId: peerId, network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager) return managedServiceViews(accountPeerId: peerId, network: strongSelf.network, postbox: strongSelf.postbox, stateManager: strongSelf.stateManager, pendingMessageManager: strongSelf.pendingMessageManager)
} else { } else {
Logger.shared.log("Account", "Resigned master") Logger.shared.log("Account", "Resigned master")
return .never() return .never()
} }
} }
self.managedServiceViewsDisposable.set(serviceTasksMaster.start()) self.managedServiceViewsDisposable.set(serviceTasksMaster.start())
@ -1214,39 +1217,6 @@ public class Account {
let settings: CacheStorageSettings = ((view.views[storagePreferencesKey] as? PreferencesView)?.values[PreferencesKeys.cacheStorageSettings] as? CacheStorageSettings) ?? CacheStorageSettings.defaultSettings let settings: CacheStorageSettings = ((view.views[storagePreferencesKey] as? PreferencesView)?.values[PreferencesKeys.cacheStorageSettings] as? CacheStorageSettings) ?? CacheStorageSettings.defaultSettings
mediaBox.setMaxStoreTime(settings.defaultCacheStorageTimeout) mediaBox.setMaxStoreTime(settings.defaultCacheStorageTimeout)
}) })
/*let updatedPresence = self.shouldKeepOnlinePresence.get()
|> distinctUntilChanged
|> mapToSignal { [weak self] online -> Signal<Void, NoError> in
if let strongSelf = self {
let delayRequest: Signal<Void, NoError> = .complete()
|> delay(60.0, queue: Queue.concurrentDefaultQueue())
let pushStatusOnce = strongSelf.network.request(Api.functions.account.updateStatus(offline: online ? .boolFalse : .boolTrue))
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in return .complete() }
let pushStatusRepeatedly = (pushStatusOnce
|> then(delayRequest))
|> restart
let peerId = strongSelf.peerId
let updatePresenceLocally = strongSelf.postbox.transaction { transaction -> Void in
let timestamp: Double
if online {
timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 + 60.0 * 60.0 * 24.0 * 356.0
} else {
timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970 - 1.0
}
transaction.updatePeerPresences([peerId: TelegramUserPresence(status: .present(until: Int32(timestamp)))])
}
return combineLatest(pushStatusRepeatedly, updatePresenceLocally)
|> mapToSignal { _ -> Signal<Void, NoError> in return .complete()
}
} else {
return .complete()
}
}
self.updatedPresenceDisposable.set(updatedPresence.start())*/
} }
deinit { deinit {

View File

@ -172,10 +172,14 @@ public final class AccountStateManager {
return .complete() return .complete()
} }
} else { } else {
return network.request(Api.functions.messages.receivedQueue(maxQts: value)) if value == 0 {
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete() return .complete()
} else {
return network.request(Api.functions.messages.receivedQueue(maxQts: value))
|> ignoreValues
|> `catch` { _ -> Signal<Never, NoError> in
return .complete()
}
} }
} }
}).start()) }).start())

View File

@ -416,7 +416,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[182649427] = { return Api.MessageRange.parse_messageRange($0) } dict[182649427] = { return Api.MessageRange.parse_messageRange($0) }
dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) } dict[946083368] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultSuccess($0) }
dict[904138920] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultArchive($0) } dict[904138920] = { return Api.messages.StickerSetInstallResult.parse_stickerSetInstallResultArchive($0) }
dict[840162234] = { return Api.Config.parse_config($0) } dict[-422959626] = { return Api.Config.parse_config($0) }
dict[-75283823] = { return Api.TopPeerCategoryPeers.parse_topPeerCategoryPeers($0) } dict[-75283823] = { return Api.TopPeerCategoryPeers.parse_topPeerCategoryPeers($0) }
dict[-1107729093] = { return Api.Game.parse_game($0) } dict[-1107729093] = { return Api.Game.parse_game($0) }
dict[4883767] = { return Api.SecurePasswordKdfAlgo.parse_securePasswordKdfAlgoUnknown($0) } dict[4883767] = { return Api.SecurePasswordKdfAlgo.parse_securePasswordKdfAlgoUnknown($0) }

View File

@ -10170,13 +10170,13 @@ extension Api {
} }
enum Config: TypeConstructorDescription { enum Config: TypeConstructorDescription {
case config(flags: Int32, date: Int32, expires: Int32, testMode: Api.Bool, thisDc: Int32, dcOptions: [Api.DcOption], dcTxtDomainName: String, chatSizeMax: Int32, megagroupSizeMax: Int32, forwardedCountMax: Int32, onlineUpdatePeriodMs: Int32, offlineBlurTimeoutMs: Int32, offlineIdleTimeoutMs: Int32, onlineCloudTimeoutMs: Int32, notifyCloudDelayMs: Int32, notifyDefaultDelayMs: Int32, pushChatPeriodMs: Int32, pushChatLimit: Int32, savedGifsLimit: Int32, editTimeLimit: Int32, revokeTimeLimit: Int32, revokePmTimeLimit: Int32, ratingEDecay: Int32, stickersRecentLimit: Int32, stickersFavedLimit: Int32, channelsReadMediaPeriod: Int32, tmpSessions: Int32?, pinnedDialogsCountMax: Int32, callReceiveTimeoutMs: Int32, callRingTimeoutMs: Int32, callConnectTimeoutMs: Int32, callPacketTimeoutMs: Int32, meUrlPrefix: String, autoupdateUrlPrefix: String?, gifSearchUsername: String?, venueSearchUsername: String?, imgSearchUsername: String?, staticMapsProvider: String?, captionLengthMax: Int32, messageLengthMax: Int32, webfileDcId: Int32, suggestedLangCode: String?, langPackVersion: Int32?) case config(flags: Int32, date: Int32, expires: Int32, testMode: Api.Bool, thisDc: Int32, dcOptions: [Api.DcOption], dcTxtDomainName: String, chatSizeMax: Int32, megagroupSizeMax: Int32, forwardedCountMax: Int32, onlineUpdatePeriodMs: Int32, offlineBlurTimeoutMs: Int32, offlineIdleTimeoutMs: Int32, onlineCloudTimeoutMs: Int32, notifyCloudDelayMs: Int32, notifyDefaultDelayMs: Int32, pushChatPeriodMs: Int32, pushChatLimit: Int32, savedGifsLimit: Int32, editTimeLimit: Int32, revokeTimeLimit: Int32, revokePmTimeLimit: Int32, ratingEDecay: Int32, stickersRecentLimit: Int32, stickersFavedLimit: Int32, channelsReadMediaPeriod: Int32, tmpSessions: Int32?, pinnedDialogsCountMax: Int32, callReceiveTimeoutMs: Int32, callRingTimeoutMs: Int32, callConnectTimeoutMs: Int32, callPacketTimeoutMs: Int32, meUrlPrefix: String, autoupdateUrlPrefix: String?, gifSearchUsername: String?, venueSearchUsername: String?, imgSearchUsername: String?, staticMapsProvider: String?, captionLengthMax: Int32, messageLengthMax: Int32, webfileDcId: Int32, suggestedLangCode: String?, langPackVersion: Int32?, baseLangPackVersion: Int32?)
func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .config(let flags, let date, let expires, let testMode, let thisDc, let dcOptions, let dcTxtDomainName, let chatSizeMax, let megagroupSizeMax, let forwardedCountMax, let onlineUpdatePeriodMs, let offlineBlurTimeoutMs, let offlineIdleTimeoutMs, let onlineCloudTimeoutMs, let notifyCloudDelayMs, let notifyDefaultDelayMs, let pushChatPeriodMs, let pushChatLimit, let savedGifsLimit, let editTimeLimit, let revokeTimeLimit, let revokePmTimeLimit, let ratingEDecay, let stickersRecentLimit, let stickersFavedLimit, let channelsReadMediaPeriod, let tmpSessions, let pinnedDialogsCountMax, let callReceiveTimeoutMs, let callRingTimeoutMs, let callConnectTimeoutMs, let callPacketTimeoutMs, let meUrlPrefix, let autoupdateUrlPrefix, let gifSearchUsername, let venueSearchUsername, let imgSearchUsername, let staticMapsProvider, let captionLengthMax, let messageLengthMax, let webfileDcId, let suggestedLangCode, let langPackVersion): case .config(let flags, let date, let expires, let testMode, let thisDc, let dcOptions, let dcTxtDomainName, let chatSizeMax, let megagroupSizeMax, let forwardedCountMax, let onlineUpdatePeriodMs, let offlineBlurTimeoutMs, let offlineIdleTimeoutMs, let onlineCloudTimeoutMs, let notifyCloudDelayMs, let notifyDefaultDelayMs, let pushChatPeriodMs, let pushChatLimit, let savedGifsLimit, let editTimeLimit, let revokeTimeLimit, let revokePmTimeLimit, let ratingEDecay, let stickersRecentLimit, let stickersFavedLimit, let channelsReadMediaPeriod, let tmpSessions, let pinnedDialogsCountMax, let callReceiveTimeoutMs, let callRingTimeoutMs, let callConnectTimeoutMs, let callPacketTimeoutMs, let meUrlPrefix, let autoupdateUrlPrefix, let gifSearchUsername, let venueSearchUsername, let imgSearchUsername, let staticMapsProvider, let captionLengthMax, let messageLengthMax, let webfileDcId, let suggestedLangCode, let langPackVersion, let baseLangPackVersion):
if boxed { if boxed {
buffer.appendInt32(840162234) buffer.appendInt32(-422959626)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt32(date, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false)
@ -10225,14 +10225,15 @@ extension Api {
serializeInt32(webfileDcId, buffer: buffer, boxed: false) serializeInt32(webfileDcId, buffer: buffer, boxed: false)
if Int(flags) & Int(1 << 2) != 0 {serializeString(suggestedLangCode!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeString(suggestedLangCode!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(langPackVersion!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {serializeInt32(langPackVersion!, buffer: buffer, boxed: false)}
if Int(flags) & Int(1 << 2) != 0 {serializeInt32(baseLangPackVersion!, buffer: buffer, boxed: false)}
break break
} }
} }
func descriptionFields() -> (String, [(String, Any)]) { func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .config(let flags, let date, let expires, let testMode, let thisDc, let dcOptions, let dcTxtDomainName, let chatSizeMax, let megagroupSizeMax, let forwardedCountMax, let onlineUpdatePeriodMs, let offlineBlurTimeoutMs, let offlineIdleTimeoutMs, let onlineCloudTimeoutMs, let notifyCloudDelayMs, let notifyDefaultDelayMs, let pushChatPeriodMs, let pushChatLimit, let savedGifsLimit, let editTimeLimit, let revokeTimeLimit, let revokePmTimeLimit, let ratingEDecay, let stickersRecentLimit, let stickersFavedLimit, let channelsReadMediaPeriod, let tmpSessions, let pinnedDialogsCountMax, let callReceiveTimeoutMs, let callRingTimeoutMs, let callConnectTimeoutMs, let callPacketTimeoutMs, let meUrlPrefix, let autoupdateUrlPrefix, let gifSearchUsername, let venueSearchUsername, let imgSearchUsername, let staticMapsProvider, let captionLengthMax, let messageLengthMax, let webfileDcId, let suggestedLangCode, let langPackVersion): case .config(let flags, let date, let expires, let testMode, let thisDc, let dcOptions, let dcTxtDomainName, let chatSizeMax, let megagroupSizeMax, let forwardedCountMax, let onlineUpdatePeriodMs, let offlineBlurTimeoutMs, let offlineIdleTimeoutMs, let onlineCloudTimeoutMs, let notifyCloudDelayMs, let notifyDefaultDelayMs, let pushChatPeriodMs, let pushChatLimit, let savedGifsLimit, let editTimeLimit, let revokeTimeLimit, let revokePmTimeLimit, let ratingEDecay, let stickersRecentLimit, let stickersFavedLimit, let channelsReadMediaPeriod, let tmpSessions, let pinnedDialogsCountMax, let callReceiveTimeoutMs, let callRingTimeoutMs, let callConnectTimeoutMs, let callPacketTimeoutMs, let meUrlPrefix, let autoupdateUrlPrefix, let gifSearchUsername, let venueSearchUsername, let imgSearchUsername, let staticMapsProvider, let captionLengthMax, let messageLengthMax, let webfileDcId, let suggestedLangCode, let langPackVersion, let baseLangPackVersion):
return ("config", [("flags", flags), ("date", date), ("expires", expires), ("testMode", testMode), ("thisDc", thisDc), ("dcOptions", dcOptions), ("dcTxtDomainName", dcTxtDomainName), ("chatSizeMax", chatSizeMax), ("megagroupSizeMax", megagroupSizeMax), ("forwardedCountMax", forwardedCountMax), ("onlineUpdatePeriodMs", onlineUpdatePeriodMs), ("offlineBlurTimeoutMs", offlineBlurTimeoutMs), ("offlineIdleTimeoutMs", offlineIdleTimeoutMs), ("onlineCloudTimeoutMs", onlineCloudTimeoutMs), ("notifyCloudDelayMs", notifyCloudDelayMs), ("notifyDefaultDelayMs", notifyDefaultDelayMs), ("pushChatPeriodMs", pushChatPeriodMs), ("pushChatLimit", pushChatLimit), ("savedGifsLimit", savedGifsLimit), ("editTimeLimit", editTimeLimit), ("revokeTimeLimit", revokeTimeLimit), ("revokePmTimeLimit", revokePmTimeLimit), ("ratingEDecay", ratingEDecay), ("stickersRecentLimit", stickersRecentLimit), ("stickersFavedLimit", stickersFavedLimit), ("channelsReadMediaPeriod", channelsReadMediaPeriod), ("tmpSessions", tmpSessions), ("pinnedDialogsCountMax", pinnedDialogsCountMax), ("callReceiveTimeoutMs", callReceiveTimeoutMs), ("callRingTimeoutMs", callRingTimeoutMs), ("callConnectTimeoutMs", callConnectTimeoutMs), ("callPacketTimeoutMs", callPacketTimeoutMs), ("meUrlPrefix", meUrlPrefix), ("autoupdateUrlPrefix", autoupdateUrlPrefix), ("gifSearchUsername", gifSearchUsername), ("venueSearchUsername", venueSearchUsername), ("imgSearchUsername", imgSearchUsername), ("staticMapsProvider", staticMapsProvider), ("captionLengthMax", captionLengthMax), ("messageLengthMax", messageLengthMax), ("webfileDcId", webfileDcId), ("suggestedLangCode", suggestedLangCode), ("langPackVersion", langPackVersion)]) return ("config", [("flags", flags), ("date", date), ("expires", expires), ("testMode", testMode), ("thisDc", thisDc), ("dcOptions", dcOptions), ("dcTxtDomainName", dcTxtDomainName), ("chatSizeMax", chatSizeMax), ("megagroupSizeMax", megagroupSizeMax), ("forwardedCountMax", forwardedCountMax), ("onlineUpdatePeriodMs", onlineUpdatePeriodMs), ("offlineBlurTimeoutMs", offlineBlurTimeoutMs), ("offlineIdleTimeoutMs", offlineIdleTimeoutMs), ("onlineCloudTimeoutMs", onlineCloudTimeoutMs), ("notifyCloudDelayMs", notifyCloudDelayMs), ("notifyDefaultDelayMs", notifyDefaultDelayMs), ("pushChatPeriodMs", pushChatPeriodMs), ("pushChatLimit", pushChatLimit), ("savedGifsLimit", savedGifsLimit), ("editTimeLimit", editTimeLimit), ("revokeTimeLimit", revokeTimeLimit), ("revokePmTimeLimit", revokePmTimeLimit), ("ratingEDecay", ratingEDecay), ("stickersRecentLimit", stickersRecentLimit), ("stickersFavedLimit", stickersFavedLimit), ("channelsReadMediaPeriod", channelsReadMediaPeriod), ("tmpSessions", tmpSessions), ("pinnedDialogsCountMax", pinnedDialogsCountMax), ("callReceiveTimeoutMs", callReceiveTimeoutMs), ("callRingTimeoutMs", callRingTimeoutMs), ("callConnectTimeoutMs", callConnectTimeoutMs), ("callPacketTimeoutMs", callPacketTimeoutMs), ("meUrlPrefix", meUrlPrefix), ("autoupdateUrlPrefix", autoupdateUrlPrefix), ("gifSearchUsername", gifSearchUsername), ("venueSearchUsername", venueSearchUsername), ("imgSearchUsername", imgSearchUsername), ("staticMapsProvider", staticMapsProvider), ("captionLengthMax", captionLengthMax), ("messageLengthMax", messageLengthMax), ("webfileDcId", webfileDcId), ("suggestedLangCode", suggestedLangCode), ("langPackVersion", langPackVersion), ("baseLangPackVersion", baseLangPackVersion)])
} }
} }
@ -10327,6 +10328,8 @@ extension Api {
if Int(_1!) & Int(1 << 2) != 0 {_42 = parseString(reader) } if Int(_1!) & Int(1 << 2) != 0 {_42 = parseString(reader) }
var _43: Int32? var _43: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_43 = reader.readInt32() } if Int(_1!) & Int(1 << 2) != 0 {_43 = reader.readInt32() }
var _44: Int32?
if Int(_1!) & Int(1 << 2) != 0 {_44 = reader.readInt32() }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
let _c3 = _3 != nil let _c3 = _3 != nil
@ -10370,8 +10373,9 @@ extension Api {
let _c41 = _41 != nil let _c41 = _41 != nil
let _c42 = (Int(_1!) & Int(1 << 2) == 0) || _42 != nil let _c42 = (Int(_1!) & Int(1 << 2) == 0) || _42 != nil
let _c43 = (Int(_1!) & Int(1 << 2) == 0) || _43 != nil let _c43 = (Int(_1!) & Int(1 << 2) == 0) || _43 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 { let _c44 = (Int(_1!) & Int(1 << 2) == 0) || _44 != nil
return Api.Config.config(flags: _1!, date: _2!, expires: _3!, testMode: _4!, thisDc: _5!, dcOptions: _6!, dcTxtDomainName: _7!, chatSizeMax: _8!, megagroupSizeMax: _9!, forwardedCountMax: _10!, onlineUpdatePeriodMs: _11!, offlineBlurTimeoutMs: _12!, offlineIdleTimeoutMs: _13!, onlineCloudTimeoutMs: _14!, notifyCloudDelayMs: _15!, notifyDefaultDelayMs: _16!, pushChatPeriodMs: _17!, pushChatLimit: _18!, savedGifsLimit: _19!, editTimeLimit: _20!, revokeTimeLimit: _21!, revokePmTimeLimit: _22!, ratingEDecay: _23!, stickersRecentLimit: _24!, stickersFavedLimit: _25!, channelsReadMediaPeriod: _26!, tmpSessions: _27, pinnedDialogsCountMax: _28!, callReceiveTimeoutMs: _29!, callRingTimeoutMs: _30!, callConnectTimeoutMs: _31!, callPacketTimeoutMs: _32!, meUrlPrefix: _33!, autoupdateUrlPrefix: _34, gifSearchUsername: _35, venueSearchUsername: _36, imgSearchUsername: _37, staticMapsProvider: _38, captionLengthMax: _39!, messageLengthMax: _40!, webfileDcId: _41!, suggestedLangCode: _42, langPackVersion: _43) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 && _c30 && _c31 && _c32 && _c33 && _c34 && _c35 && _c36 && _c37 && _c38 && _c39 && _c40 && _c41 && _c42 && _c43 && _c44 {
return Api.Config.config(flags: _1!, date: _2!, expires: _3!, testMode: _4!, thisDc: _5!, dcOptions: _6!, dcTxtDomainName: _7!, chatSizeMax: _8!, megagroupSizeMax: _9!, forwardedCountMax: _10!, onlineUpdatePeriodMs: _11!, offlineBlurTimeoutMs: _12!, offlineIdleTimeoutMs: _13!, onlineCloudTimeoutMs: _14!, notifyCloudDelayMs: _15!, notifyDefaultDelayMs: _16!, pushChatPeriodMs: _17!, pushChatLimit: _18!, savedGifsLimit: _19!, editTimeLimit: _20!, revokeTimeLimit: _21!, revokePmTimeLimit: _22!, ratingEDecay: _23!, stickersRecentLimit: _24!, stickersFavedLimit: _25!, channelsReadMediaPeriod: _26!, tmpSessions: _27, pinnedDialogsCountMax: _28!, callReceiveTimeoutMs: _29!, callRingTimeoutMs: _30!, callConnectTimeoutMs: _31!, callPacketTimeoutMs: _32!, meUrlPrefix: _33!, autoupdateUrlPrefix: _34, gifSearchUsername: _35, venueSearchUsername: _36, imgSearchUsername: _37, staticMapsProvider: _38, captionLengthMax: _39!, messageLengthMax: _40!, webfileDcId: _41!, suggestedLangCode: _42, langPackVersion: _43, baseLangPackVersion: _44)
} }
else { else {
return nil return nil

View File

@ -0,0 +1,33 @@
import Foundation
#if os(macOS)
import SwiftSignalKitMac
import MtProtoKitMac
#else
import SwiftSignalKit
import MtProtoKitDynamic
#endif
public enum ConfirmTwoStepRecoveryEmailError {
case invalidEmail
case invalidCode
case flood
case expired
case generic
}
public func confirmTwoStepRecoveryEmail(network: Network, email: String, code: String) -> Signal<Never, ConfirmTwoStepRecoveryEmailError> {
return network.request(Api.functions.account.confirmPasswordEmail(email: email, code: code), automaticFloodWait: false)
|> mapError { error -> ConfirmTwoStepRecoveryEmailError in
if error.errorDescription == "EMAIL_INVALID" {
return .invalidEmail
} else if error.errorDescription == "CODE_INVALID" {
return .invalidCode
} else if error.errorDescription == "EMAIL_HASH_EXPIRED" {
return .expired
} else if error.errorDescription.hasPrefix("FLOOD_WAIT") {
return .flood
}
return .generic
}
|> ignoreValues
}

View File

@ -8,15 +8,17 @@ import Foundation
public final class LocalizationInfo: PostboxCoding { public final class LocalizationInfo: PostboxCoding {
public let languageCode: String public let languageCode: String
public let baseLanguageCode: String? public let baseLanguageCode: String?
public let customPluralizationCode: String?
public let title: String public let title: String
public let localizedTitle: String public let localizedTitle: String
public let isOfficial: Bool public let isOfficial: Bool
public let totalStringCount: Int32 public let totalStringCount: Int32
public let translatedStringCount: Int32 public let translatedStringCount: Int32
public init(languageCode: String, baseLanguageCode: String?, title: String, localizedTitle: String, isOfficial: Bool, totalStringCount: Int32, translatedStringCount: Int32) { public init(languageCode: String, baseLanguageCode: String?, customPluralizationCode: String?, title: String, localizedTitle: String, isOfficial: Bool, totalStringCount: Int32, translatedStringCount: Int32) {
self.languageCode = languageCode self.languageCode = languageCode
self.baseLanguageCode = baseLanguageCode self.baseLanguageCode = baseLanguageCode
self.customPluralizationCode = customPluralizationCode
self.title = title self.title = title
self.localizedTitle = localizedTitle self.localizedTitle = localizedTitle
self.isOfficial = isOfficial self.isOfficial = isOfficial
@ -27,6 +29,7 @@ public final class LocalizationInfo: PostboxCoding {
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.languageCode = decoder.decodeStringForKey("lc", orElse: "") self.languageCode = decoder.decodeStringForKey("lc", orElse: "")
self.baseLanguageCode = decoder.decodeOptionalStringForKey("nlc") self.baseLanguageCode = decoder.decodeOptionalStringForKey("nlc")
self.customPluralizationCode = decoder.decodeOptionalStringForKey("cpc")
self.title = decoder.decodeStringForKey("t", orElse: "") self.title = decoder.decodeStringForKey("t", orElse: "")
self.localizedTitle = decoder.decodeStringForKey("lt", orElse: "") self.localizedTitle = decoder.decodeStringForKey("lt", orElse: "")
self.isOfficial = decoder.decodeInt32ForKey("of", orElse: 0) != 0 self.isOfficial = decoder.decodeInt32ForKey("of", orElse: 0) != 0
@ -41,6 +44,11 @@ public final class LocalizationInfo: PostboxCoding {
} else { } else {
encoder.encodeNil(forKey: "nlc") encoder.encodeNil(forKey: "nlc")
} }
if let customPluralizationCode = self.customPluralizationCode {
encoder.encodeString(customPluralizationCode, forKey: "cpc")
} else {
encoder.encodeNil(forKey: "cpc")
}
encoder.encodeString(self.title, forKey: "t") encoder.encodeString(self.title, forKey: "t")
encoder.encodeString(self.localizedTitle, forKey: "lt") encoder.encodeString(self.localizedTitle, forKey: "lt")
encoder.encodeInt32(self.isOfficial ? 1 : 0, forKey: "of") encoder.encodeInt32(self.isOfficial ? 1 : 0, forKey: "of")
@ -53,7 +61,7 @@ extension LocalizationInfo {
convenience init(apiLanguage: Api.LangPackLanguage) { convenience init(apiLanguage: Api.LangPackLanguage) {
switch apiLanguage { switch apiLanguage {
case let .langPackLanguage(language): case let .langPackLanguage(language):
self.init(languageCode: language.langCode, baseLanguageCode: nil/*language.baseLangCode*/, title: language.name, localizedTitle: language.nativeName, isOfficial: true/*(language.flags & (1 << 0)) != 0*/, totalStringCount: 1/*language.stringsCount*/, translatedStringCount: 1/*language.translatedCount*/) self.init(languageCode: language.langCode, baseLanguageCode: language.baseLangCode, customPluralizationCode: language.pluralCode, title: language.name, localizedTitle: language.nativeName, isOfficial: (language.flags & (1 << 0)) != 0, totalStringCount: language.stringsCount, translatedStringCount: language.translatedCount)
} }
} }
} }

View File

@ -12,13 +12,12 @@ public enum RequestLocalizationPreviewError {
case generic case generic
} }
public func requestLocalizationPreview(postbox: Postbox, network: Network, identifier: String) -> Signal<LocalizationInfo, RequestLocalizationPreviewError> { public func requestLocalizationPreview(network: Network, identifier: String) -> Signal<LocalizationInfo, RequestLocalizationPreviewError> {
return .never() return network.request(Api.functions.langpack.getLanguage(langPack: "", langCode: identifier))
/*return network.request(Api.functions.langpack.getLanguage(langPack: "", langCode: identifier))
|> mapError { _ -> RequestLocalizationPreviewError in |> mapError { _ -> RequestLocalizationPreviewError in
return .generic return .generic
} }
|> map { language -> LocalizationInfo in |> map { language -> LocalizationInfo in
return LocalizationInfo(apiLanguage: language) return LocalizationInfo(apiLanguage: language)
}*/ }
} }

View File

@ -5,23 +5,73 @@ import Foundation
import Postbox import Postbox
#endif #endif
public final class LocalizationSettings: PreferencesEntry, Equatable { public final class LocalizationComponent: Equatable, PostboxCoding {
public let languageCode: String public let languageCode: String
public let localization: Localization public let localization: Localization
public let customPluralizationCode: String?
public init(languageCode: String, localization: Localization) { public init(languageCode: String, localization: Localization, customPluralizationCode: String?) {
self.languageCode = languageCode self.languageCode = languageCode
self.localization = localization self.localization = localization
self.customPluralizationCode = customPluralizationCode
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
self.languageCode = decoder.decodeStringForKey("lc", orElse: "en") self.languageCode = decoder.decodeStringForKey("lc", orElse: "")
self.localization = decoder.decodeObjectForKey("loc", decoder: { Localization(decoder: $0) }) as! Localization self.localization = decoder.decodeObjectForKey("loc", decoder: { Localization(decoder: $0) }) as! Localization
self.customPluralizationCode = decoder.decodeOptionalStringForKey("cpl")
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
encoder.encodeString(self.languageCode, forKey: "lc") encoder.encodeString(self.languageCode, forKey: "lc")
encoder.encodeObject(self.localization, forKey: "loc") encoder.encodeObject(self.localization, forKey: "loc")
if let customPluralizationCode = self.customPluralizationCode {
encoder.encodeString(customPluralizationCode, forKey: "cpl")
} else {
encoder.encodeNil(forKey: "cpl")
}
}
public static func ==(lhs: LocalizationComponent, rhs: LocalizationComponent) -> Bool {
if lhs.languageCode != rhs.languageCode {
return false
}
if lhs.localization != rhs.localization {
return false
}
if lhs.customPluralizationCode != rhs.customPluralizationCode {
return false
}
return true
}
}
public final class LocalizationSettings: PreferencesEntry, Equatable {
public let primaryComponent: LocalizationComponent
public let secondaryComponent: LocalizationComponent?
public init(primaryComponent: LocalizationComponent, secondaryComponent: LocalizationComponent?) {
self.primaryComponent = primaryComponent
self.secondaryComponent = secondaryComponent
}
public init(decoder: PostboxDecoder) {
if let languageCode = decoder.decodeOptionalStringForKey("lc") {
self.primaryComponent = LocalizationComponent(languageCode: languageCode, localization: decoder.decodeObjectForKey("loc", decoder: { Localization(decoder: $0) }) as! Localization, customPluralizationCode: nil)
self.secondaryComponent = nil
} else {
self.primaryComponent = decoder.decodeObjectForKey("primaryComponent", decoder: { LocalizationComponent(decoder: $0) }) as! LocalizationComponent
self.secondaryComponent = decoder.decodeObjectForKey("secondaryComponent", decoder: { LocalizationComponent(decoder: $0) }) as? LocalizationComponent
}
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.primaryComponent, forKey: "primaryComponent")
if let secondaryComponent = self.secondaryComponent {
encoder.encodeObject(secondaryComponent, forKey: "secondaryComponent")
} else {
encoder.encodeNil(forKey: "secondaryComponent")
}
} }
public func isEqual(to: PreferencesEntry) -> Bool { public func isEqual(to: PreferencesEntry) -> Bool {
@ -33,6 +83,6 @@ public final class LocalizationSettings: PreferencesEntry, Equatable {
} }
public static func ==(lhs: LocalizationSettings, rhs: LocalizationSettings) -> Bool { public static func ==(lhs: LocalizationSettings, rhs: LocalizationSettings) -> Bool {
return lhs.languageCode == rhs.languageCode && lhs.localization == rhs.localization return lhs.primaryComponent == rhs.primaryComponent && lhs.secondaryComponent == rhs.secondaryComponent
} }
} }

View File

@ -114,35 +114,53 @@ public func downloadLocalization(network: Network, languageCode: String) -> Sign
} }
} }
public enum DownoadAndApplyLocalizationError { public enum DownloadAndApplyLocalizationError {
case generic case generic
} }
public func downoadAndApplyLocalization(postbox: Postbox, network: Network, languageCode: String) -> Signal<Void, DownoadAndApplyLocalizationError> { public func downloadAndApplyLocalization(postbox: Postbox, network: Network, languageCode: String) -> Signal<Void, DownloadAndApplyLocalizationError> {
return downloadLocalization(network: network, languageCode: languageCode) return requestLocalizationPreview(network: network, identifier: languageCode)
|> mapError { _ -> DownoadAndApplyLocalizationError in |> mapError { _ -> DownloadAndApplyLocalizationError in
return .generic return .generic
} }
|> mapToSignal { language -> Signal<Void, DownoadAndApplyLocalizationError> in |> mapToSignal { preview -> Signal<Void, DownloadAndApplyLocalizationError> in
return postbox.transaction { transaction -> Signal<Void, DownoadAndApplyLocalizationError> in var primaryAndSecondaryLocalizations: [Signal<Localization, DownloadLocalizationError>] = []
transaction.updatePreferencesEntry(key: PreferencesKeys.localizationSettings, { _ in primaryAndSecondaryLocalizations.append(downloadLocalization(network: network, languageCode: preview.languageCode))
return LocalizationSettings(languageCode: languageCode, localization: language) if let secondaryCode = preview.baseLanguageCode {
}) primaryAndSecondaryLocalizations.append(downloadLocalization(network: network, languageCode: secondaryCode))
}
network.context.updateApiEnvironment { current in return combineLatest(primaryAndSecondaryLocalizations)
return current?.withUpdatedLangPackCode(languageCode) |> mapError { _ -> DownloadAndApplyLocalizationError in
} return .generic
}
return network.request(Api.functions.help.test()) |> mapToSignal { components -> Signal<Void, DownloadAndApplyLocalizationError> in
|> `catch` { _ -> Signal<Api.Bool, NoError> in guard let primaryLocalization = components.first else {
return .complete() return .fail(.generic)
} }
|> mapToSignal { _ -> Signal<Void, NoError> in var secondaryComponent: LocalizationComponent?
return .complete() if let secondaryCode = preview.baseLanguageCode, components.count > 1 {
} secondaryComponent = LocalizationComponent(languageCode: secondaryCode, localization: components[1], customPluralizationCode: nil)
|> introduceError(DownoadAndApplyLocalizationError.self) }
return postbox.transaction { transaction -> Signal<Void, DownloadAndApplyLocalizationError> in
transaction.updatePreferencesEntry(key: PreferencesKeys.localizationSettings, { _ in
return LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: preview.languageCode, localization: primaryLocalization, customPluralizationCode: preview.customPluralizationCode), secondaryComponent: secondaryComponent)
})
network.context.updateApiEnvironment { current in
return current?.withUpdatedLangPackCode(preview.languageCode)
}
return network.request(Api.functions.help.test())
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .complete()
}
|> mapToSignal { _ -> Signal<Void, NoError> in
return .complete()
}
|> introduceError(DownloadAndApplyLocalizationError.self)
}
|> introduceError(DownloadAndApplyLocalizationError.self)
|> switchToLatest
} }
|> introduceError(DownoadAndApplyLocalizationError.self)
|> switchToLatest
} }
} }

View File

@ -68,8 +68,17 @@ func managedConfigurationUpdates(postbox: Postbox, network: Network) -> Signal<V
return configuration return configuration
}) })
let (_, version, _) = getLocalization(transaction) let (primary, secondary) = getLocalization(transaction)
if version != config.langPackVersion { var invalidateLocalization = false
if primary.version != config.langPackVersion {
invalidateLocalization = true
}
if let secondary = secondary, let baseLangPackVersion = config.baseLangPackVersion {
if secondary.version != baseLangPackVersion {
invalidateLocalization = true
}
}
if invalidateLocalization {
addSynchronizeLocalizationUpdatesOperation(transaction: transaction) addSynchronizeLocalizationUpdatesOperation(transaction: transaction)
} }
} }

View File

@ -120,7 +120,7 @@ private enum SynchronizeLocalizationUpdatesError {
case reset case reset
} }
func getLocalization(_ transaction: Transaction) -> (String, Int32, [LocalizationEntry]) { func getLocalization(_ transaction: Transaction) -> (primary: (code: String, version: Int32, entries: [LocalizationEntry]), secondary: (code: String, version: Int32, entries: [LocalizationEntry])?) {
let localizationSettings: LocalizationSettings? let localizationSettings: LocalizationSettings?
if let current = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings { if let current = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings {
localizationSettings = current localizationSettings = current
@ -128,76 +128,98 @@ func getLocalization(_ transaction: Transaction) -> (String, Int32, [Localizatio
localizationSettings = nil localizationSettings = nil
} }
if let localizationSettings = localizationSettings { if let localizationSettings = localizationSettings {
return (localizationSettings.languageCode, localizationSettings.localization.version, localizationSettings.localization.entries) return (primary: (localizationSettings.primaryComponent.languageCode, localizationSettings.primaryComponent.localization.version, localizationSettings.primaryComponent.localization.entries), secondary: localizationSettings.secondaryComponent.flatMap({ ($0.languageCode, $0.localization.version, $0.localization.entries) }))
} else { } else {
return ("en", 0, []) return (primary: ("en", 0, []), secondary: nil)
}
}
private func parseLangPackDifference(_ difference: Api.LangPackDifference) -> (code: String, fromVersion: Int32, version: Int32, entries: [LocalizationEntry]) {
switch difference {
case let .langPackDifference(code, fromVersion, version, strings):
var entries: [LocalizationEntry] = []
for string in strings {
switch string {
case let .langPackString(key, value):
entries.append(.string(key: key, value: value))
case let .langPackStringPluralized(_, key, zeroValue, oneValue, twoValue, fewValue, manyValue, otherValue):
entries.append(.pluralizedString(key: key, zero: zeroValue, one: oneValue, two: twoValue, few: fewValue, many: manyValue, other: otherValue))
case let .langPackStringDeleted(key):
entries.append(.string(key: key, value: ""))
}
}
return (code, fromVersion, version, entries)
} }
} }
private func synchronizeLocalizationUpdates(transaction: Transaction, postbox: Postbox, network: Network) -> Signal<Void, NoError> { private func synchronizeLocalizationUpdates(transaction: Transaction, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
let currentLanguageAndVersion = postbox.transaction { transaction -> (String, Int32) in let currentLanguageAndVersion = postbox.transaction { transaction -> (primary: (code: String, version: Int32), secondary: (code: String, version: Int32)?) in
let (code, version, _) = getLocalization(transaction) let (primary, secondary) = getLocalization(transaction)
return (code, version) return ((primary.code, primary.version), secondary.flatMap({ ($0.code, $0.version) }))
} }
let poll = currentLanguageAndVersion let poll = currentLanguageAndVersion
|> mapError { _ -> SynchronizeLocalizationUpdatesError in return .done } |> introduceError(SynchronizeLocalizationUpdatesError.self)
|> mapToSignal { (languageCode, fromVersion) -> Signal<Void, SynchronizeLocalizationUpdatesError> in |> mapToSignal { (primary, secondary) -> Signal<Void, SynchronizeLocalizationUpdatesError> in
return network.request(Api.functions.langpack.getDifference(langCode: languageCode, fromVersion: fromVersion)) var differences: [Signal<Api.LangPackDifference, MTRpcError>] = []
|> mapError { _ -> SynchronizeLocalizationUpdatesError in return .reset } differences.append(network.request(Api.functions.langpack.getDifference(langCode: primary.code, fromVersion: primary.version)))
|> mapToSignal { result -> Signal<Void, SynchronizeLocalizationUpdatesError> in if let secondary = secondary {
let updatedCode: String differences.append(network.request(Api.functions.langpack.getDifference(langCode: secondary.code, fromVersion: secondary.version)))
let updatedVersion: Int32 }
var updatedEntries: [LocalizationEntry] = []
switch result {
case let .langPackDifference(code, _, versionValue, strings):
updatedCode = code
updatedVersion = versionValue
for string in strings {
switch string {
case let .langPackString(key, value):
updatedEntries.append(.string(key: key, value: value))
case let .langPackStringPluralized(_, key, zeroValue, oneValue, twoValue, fewValue, manyValue, otherValue):
updatedEntries.append(.pluralizedString(key: key, zero: zeroValue, one: oneValue, two: twoValue, few: fewValue, many: manyValue, other: otherValue))
case let .langPackStringDeleted(key):
updatedEntries.append(.string(key: key, value: ""))
}
}
}
return postbox.transaction { transaction -> Signal<Void, SynchronizeLocalizationUpdatesError> in return combineLatest(differences)
let (code, version, entries) = getLocalization(transaction) |> mapError { _ -> SynchronizeLocalizationUpdatesError in return .reset }
|> mapToSignal { differences -> Signal<Void, SynchronizeLocalizationUpdatesError> in
let parsedDifferences = differences.map(parseLangPackDifference)
return postbox.transaction { transaction -> Signal<Void, SynchronizeLocalizationUpdatesError> in
let (primary, secondary) = getLocalization(transaction)
if code == updatedCode { var currentSettings = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings ?? LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: "en", localization: Localization(version: 0, entries: []), customPluralizationCode: nil), secondaryComponent: nil)
if fromVersion == version {
var updatedEntryKeys = Set<String>()
for entry in updatedEntries {
updatedEntryKeys.insert(entry.key)
}
var mergedEntries: [LocalizationEntry] = [] for difference in parsedDifferences {
for entry in entries { let current: (isPrimary: Bool, entries: [LocalizationEntry])
if !updatedEntryKeys.contains(entry.key) { if difference.code == primary.code {
mergedEntries.append(entry) if primary.version != difference.fromVersion {
}
}
mergedEntries.append(contentsOf: updatedEntries)
transaction.setPreferencesEntry(key: PreferencesKeys.localizationSettings, value: LocalizationSettings(languageCode: updatedCode, localization: Localization(version: updatedVersion, entries: mergedEntries)))
return .fail(.done)
} else {
return .complete() return .complete()
} }
current = (true, primary.entries)
} else if let secondary = secondary, difference.code == secondary.code {
if secondary.version != difference.fromVersion {
return .complete()
}
current = (false, secondary.entries)
} else { } else {
return .fail(.reset) return .fail(.reset)
} }
var updatedEntryKeys = Set<String>()
for entry in difference.entries {
updatedEntryKeys.insert(entry.key)
}
var mergedEntries: [LocalizationEntry] = []
for entry in current.entries {
if !updatedEntryKeys.contains(entry.key) {
mergedEntries.append(entry)
}
}
mergedEntries.append(contentsOf: difference.entries)
if current.isPrimary {
currentSettings = LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: currentSettings.primaryComponent.languageCode, localization: Localization(version: difference.version, entries: mergedEntries), customPluralizationCode: currentSettings.primaryComponent.customPluralizationCode), secondaryComponent: currentSettings.secondaryComponent)
} else if let currentSecondary = currentSettings.secondaryComponent {
currentSettings = LocalizationSettings(primaryComponent: currentSettings.primaryComponent, secondaryComponent: LocalizationComponent(languageCode: currentSecondary.languageCode, localization: Localization(version: difference.version, entries: mergedEntries), customPluralizationCode: currentSecondary.customPluralizationCode))
}
} }
|> mapError { _ -> SynchronizeLocalizationUpdatesError in return .reset
} transaction.setPreferencesEntry(key: PreferencesKeys.localizationSettings, value: currentSettings)
|> switchToLatest return .fail(.done)
} }
|> mapError { _ -> SynchronizeLocalizationUpdatesError in
return .reset
}
|> switchToLatest
} }
}
return ((poll return ((poll
|> `catch` { error -> Signal<Void, Void> in |> `catch` { error -> Signal<Void, Void> in
@ -206,8 +228,8 @@ private func synchronizeLocalizationUpdates(transaction: Transaction, postbox: P
return .fail(Void()) return .fail(Void())
case .reset: case .reset:
return postbox.transaction { transaction -> Signal<Void, Void> in return postbox.transaction { transaction -> Signal<Void, Void> in
let (code, _, _) = getLocalization(transaction) let (primary, _) = getLocalization(transaction)
return downoadAndApplyLocalization(postbox: postbox, network: network, languageCode: code) return downloadAndApplyLocalization(postbox: postbox, network: network, languageCode: primary.code)
|> mapError { _ -> Void in |> mapError { _ -> Void in
return Void() return Void()
} }
@ -221,41 +243,61 @@ private func synchronizeLocalizationUpdates(transaction: Transaction, postbox: P
} }
func tryApplyingLanguageDifference(transaction: Transaction, langCode: String, difference: Api.LangPackDifference) -> Bool { func tryApplyingLanguageDifference(transaction: Transaction, langCode: String, difference: Api.LangPackDifference) -> Bool {
let (code, version, entries) = getLocalization(transaction) let (primary, secondary) = getLocalization(transaction)
switch difference { switch difference {
case let .langPackDifference(updatedCode, fromVersion, updatedVersion, strings): case let .langPackDifference(updatedCode, fromVersion, updatedVersion, strings):
if fromVersion == version && updatedCode == code { var current: (isPrimary: Bool, version: Int32, entries: [LocalizationEntry])?
var updatedEntries: [LocalizationEntry] = [] if updatedCode == primary.code {
current = (true, primary.version, primary.entries)
for string in strings { } else if let secondary = secondary, secondary.code == updatedCode {
switch string { current = (false, secondary.version, secondary.entries)
case let .langPackString(key, value): }
updatedEntries.append(.string(key: key, value: value)) guard let (isPrimary, version, entries) = current else {
case let .langPackStringPluralized(_, key, zeroValue, oneValue, twoValue, fewValue, manyValue, otherValue):
updatedEntries.append(.pluralizedString(key: key, zero: zeroValue, one: oneValue, two: twoValue, few: fewValue, many: manyValue, other: otherValue))
case let .langPackStringDeleted(key):
updatedEntries.append(.string(key: key, value: ""))
}
}
var updatedEntryKeys = Set<String>()
for entry in updatedEntries {
updatedEntryKeys.insert(entry.key)
}
var mergedEntries: [LocalizationEntry] = []
for entry in entries {
if !updatedEntryKeys.contains(entry.key) {
mergedEntries.append(entry)
}
}
mergedEntries.append(contentsOf: updatedEntries)
transaction.setPreferencesEntry(key: PreferencesKeys.localizationSettings, value: LocalizationSettings(languageCode: updatedCode, localization: Localization(version: updatedVersion, entries: mergedEntries)))
return true
} else {
return false return false
} }
guard fromVersion == version else {
return false
}
var updatedEntries: [LocalizationEntry] = []
for string in strings {
switch string {
case let .langPackString(key, value):
updatedEntries.append(.string(key: key, value: value))
case let .langPackStringPluralized(_, key, zeroValue, oneValue, twoValue, fewValue, manyValue, otherValue):
updatedEntries.append(.pluralizedString(key: key, zero: zeroValue, one: oneValue, two: twoValue, few: fewValue, many: manyValue, other: otherValue))
case let .langPackStringDeleted(key):
updatedEntries.append(.string(key: key, value: ""))
}
}
var updatedEntryKeys = Set<String>()
for entry in updatedEntries {
updatedEntryKeys.insert(entry.key)
}
var mergedEntries: [LocalizationEntry] = []
for entry in entries {
if !updatedEntryKeys.contains(entry.key) {
mergedEntries.append(entry)
}
}
mergedEntries.append(contentsOf: updatedEntries)
let currentSettings = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings ?? LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: "en", localization: Localization(version: 0, entries: []), customPluralizationCode: nil), secondaryComponent: nil)
var updatedSettings: LocalizationSettings
if isPrimary {
updatedSettings = LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: currentSettings.primaryComponent.languageCode, localization: Localization(version: updatedVersion, entries: mergedEntries), customPluralizationCode: currentSettings.primaryComponent.customPluralizationCode), secondaryComponent: currentSettings.secondaryComponent)
} else if let currentSecondary = currentSettings.secondaryComponent {
updatedSettings = LocalizationSettings(primaryComponent: currentSettings.primaryComponent, secondaryComponent: LocalizationComponent(languageCode: currentSecondary.languageCode, localization: Localization(version: updatedVersion, entries: mergedEntries), customPluralizationCode: currentSecondary.customPluralizationCode))
} else {
assertionFailure()
return false
}
transaction.setPreferencesEntry(key: PreferencesKeys.localizationSettings, value: updatedSettings)
return true
} }
} }

View File

@ -10,23 +10,23 @@ import Foundation
#endif #endif
public enum TwoStepVerificationConfiguration { public enum TwoStepVerificationConfiguration {
case notSet(pendingEmailPattern: String) case notSet(pendingEmail: TwoStepVerificationPendingEmail?)
case set(hint: String, hasRecoveryEmail: Bool, pendingEmailPattern: String, hasSecureValues: Bool) case set(hint: String, hasRecoveryEmail: Bool, pendingEmail: TwoStepVerificationPendingEmail?, hasSecureValues: Bool)
} }
public func twoStepVerificationConfiguration(account: Account) -> Signal<TwoStepVerificationConfiguration, NoError> { public func twoStepVerificationConfiguration(account: Account) -> Signal<TwoStepVerificationConfiguration, NoError> {
return account.network.request(Api.functions.account.getPassword()) return account.network.request(Api.functions.account.getPassword())
|> retryRequest |> retryRequest
|> map { result -> TwoStepVerificationConfiguration in |> map { result -> TwoStepVerificationConfiguration in
switch result { switch result {
case let .password(password): case let .password(password):
if password.currentAlgo != nil { if password.currentAlgo != nil {
return .set(hint: password.hint ?? "", hasRecoveryEmail: (password.flags & (1 << 0)) != 0, pendingEmailPattern: password.emailUnconfirmedPattern ?? "", hasSecureValues: (password.flags & (1 << 1)) != 0) return .set(hint: password.hint ?? "", hasRecoveryEmail: (password.flags & (1 << 0)) != 0, pendingEmail: password.emailUnconfirmedPattern.flatMap({ TwoStepVerificationPendingEmail(pattern: $0, codeLength: nil) }), hasSecureValues: (password.flags & (1 << 1)) != 0)
} else { } else {
return .notSet(pendingEmailPattern: password.emailUnconfirmedPattern ?? "") return .notSet(pendingEmail: password.emailUnconfirmedPattern.flatMap({ TwoStepVerificationPendingEmail(pattern: $0, codeLength: nil) }))
} }
}
} }
}
} }
public struct TwoStepVerificationSecureSecret { public struct TwoStepVerificationSecureSecret {
@ -97,6 +97,11 @@ public enum UpdateTwoStepVerificationPasswordError {
public struct TwoStepVerificationPendingEmail { public struct TwoStepVerificationPendingEmail {
public let pattern: String public let pattern: String
public let codeLength: Int32? public let codeLength: Int32?
public init(pattern: String, codeLength: Int32?) {
self.pattern = pattern
self.codeLength = codeLength
}
} }
public enum UpdateTwoStepVerificationPasswordResult { public enum UpdateTwoStepVerificationPasswordResult {