mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Sync experiment
This commit is contained in:
parent
c83a2364ac
commit
09861c72b7
2
BUCK
2
BUCK
@ -345,6 +345,8 @@ apple_binary(
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
"//submodules/Database/MessageHistoryReadStateTable:MessageHistoryReadStateTable",
|
||||
"//submodules/Database/MessageHistoryMetadataTable:MessageHistoryMetadataTable",
|
||||
"//submodules/Database/PreferencesTable:PreferencesTable",
|
||||
"//submodules/Database/PeerTable:PeerTable",
|
||||
"//submodules/sqlcipher:sqlcipher",
|
||||
],
|
||||
frameworks = [
|
||||
|
@ -3,6 +3,44 @@ load("//Config:utils.bzl",
|
||||
"dynamic_library_configs",
|
||||
)
|
||||
|
||||
text_section_items = [
|
||||
"__text",
|
||||
]
|
||||
|
||||
text_section_rename_linker_flags = []#["-Wl,-rename_section,__TEXT,%s,__MY_TEXT,%s" % (name, name) for name in text_section_items] + ["-Wl,-segprot,__MY_TEXT,rx,rx"]
|
||||
|
||||
data_section_items = [
|
||||
"__nl_symbol_ptr",
|
||||
"__la_symbol_ptr",
|
||||
"__mod_init_func",
|
||||
"__const",
|
||||
"__cfstring",
|
||||
"__objc_classlist",
|
||||
"__objc_nlclslist",
|
||||
"__objc_catlist",
|
||||
"__objc_nlcatlist",
|
||||
"__objc_protolist",
|
||||
"__objc_imageinfo",
|
||||
"__objc_const",
|
||||
"__objc_selrefs",
|
||||
"__objc_protorefs",
|
||||
"__objc_classrefs",
|
||||
"__objc_superrefs",
|
||||
"__objc_ivar",
|
||||
"__objc_data",
|
||||
"__data",
|
||||
"__thread_vars",
|
||||
"__thread_ptr",
|
||||
"__swift_hooks",
|
||||
"__thread_bss",
|
||||
"__bss",
|
||||
"__common",
|
||||
]
|
||||
|
||||
data_section_rename_linker_flags = ["-Wl,-rename_section,__DATA,%s,__MY_DATA,%s" % (name, name) for name in data_section_items] + ["-Wl,-segprot,__MY_DATA,rw,rw"]
|
||||
|
||||
section_rename_linker_flags = text_section_rename_linker_flags# + data_section_rename_linker_flags
|
||||
|
||||
def apple_lib(
|
||||
name,
|
||||
visibility = ["PUBLIC"],
|
||||
@ -71,6 +109,8 @@ def apple_lib(
|
||||
for framework in weak_frameworks:
|
||||
resolved_linker_flags = resolved_linker_flags + ["-Wl,-weak_framework,%s" % framework]
|
||||
|
||||
resolved_linker_flags = resolved_linker_flags + section_rename_linker_flags
|
||||
|
||||
native.apple_library(
|
||||
name = name + "",
|
||||
srcs = srcs,
|
||||
|
16
NotificationService/ApplicationSpecificSharedDataKeys.swift
Normal file
16
NotificationService/ApplicationSpecificSharedDataKeys.swift
Normal file
@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
import ValueBox
|
||||
|
||||
private func applicationSpecificSharedDataKey(_ value: Int32) -> ValueBoxKey {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: value + 1000)
|
||||
return key
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
||||
case inAppNotificationSettings = 0
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificSharedDataKeys {
|
||||
public static let inAppNotificationSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.inAppNotificationSettings.rawValue)
|
||||
}
|
90
NotificationService/InAppNotificationSettings.swift
Normal file
90
NotificationService/InAppNotificationSettings.swift
Normal file
@ -0,0 +1,90 @@
|
||||
import Foundation
|
||||
import PostboxCoding
|
||||
import PreferencesTable
|
||||
import MessageHistoryMetadataTable
|
||||
import PostboxDataTypes
|
||||
|
||||
public enum TotalUnreadCountDisplayStyle: Int32 {
|
||||
case filtered = 0
|
||||
|
||||
var category: ChatListTotalUnreadStateCategory {
|
||||
switch self {
|
||||
case .filtered:
|
||||
return .filtered
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum TotalUnreadCountDisplayCategory: Int32 {
|
||||
case chats = 0
|
||||
case messages = 1
|
||||
|
||||
var statsType: ChatListTotalUnreadStateStats {
|
||||
switch self {
|
||||
case .chats:
|
||||
return .chats
|
||||
case .messages:
|
||||
return .messages
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct InAppNotificationSettings: PreferencesEntry, Equatable {
|
||||
public var playSounds: Bool
|
||||
public var vibrate: Bool
|
||||
public var displayPreviews: Bool
|
||||
public var totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle
|
||||
public var totalUnreadCountDisplayCategory: TotalUnreadCountDisplayCategory
|
||||
public var totalUnreadCountIncludeTags: PeerSummaryCounterTags
|
||||
public var displayNameOnLockscreen: Bool
|
||||
public var displayNotificationsFromAllAccounts: Bool
|
||||
|
||||
public static var defaultSettings: InAppNotificationSettings {
|
||||
return InAppNotificationSettings(playSounds: true, vibrate: false, displayPreviews: true, totalUnreadCountDisplayStyle: .filtered, totalUnreadCountDisplayCategory: .messages, totalUnreadCountIncludeTags: [.regularChatsAndPrivateGroups], displayNameOnLockscreen: true, displayNotificationsFromAllAccounts: true)
|
||||
}
|
||||
|
||||
public init(playSounds: Bool, vibrate: Bool, displayPreviews: Bool, totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: TotalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: PeerSummaryCounterTags, displayNameOnLockscreen: Bool, displayNotificationsFromAllAccounts: Bool) {
|
||||
self.playSounds = playSounds
|
||||
self.vibrate = vibrate
|
||||
self.displayPreviews = displayPreviews
|
||||
self.totalUnreadCountDisplayStyle = totalUnreadCountDisplayStyle
|
||||
self.totalUnreadCountDisplayCategory = totalUnreadCountDisplayCategory
|
||||
self.totalUnreadCountIncludeTags = totalUnreadCountIncludeTags
|
||||
self.displayNameOnLockscreen = displayNameOnLockscreen
|
||||
self.displayNotificationsFromAllAccounts = displayNotificationsFromAllAccounts
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.playSounds = decoder.decodeInt32ForKey("s", orElse: 0) != 0
|
||||
self.vibrate = decoder.decodeInt32ForKey("v", orElse: 0) != 0
|
||||
self.displayPreviews = decoder.decodeInt32ForKey("p", orElse: 0) != 0
|
||||
self.totalUnreadCountDisplayStyle = TotalUnreadCountDisplayStyle(rawValue: decoder.decodeInt32ForKey("cds", orElse: 0)) ?? .filtered
|
||||
self.totalUnreadCountDisplayCategory = TotalUnreadCountDisplayCategory(rawValue: decoder.decodeInt32ForKey("totalUnreadCountDisplayCategory", orElse: 1)) ?? .messages
|
||||
if let value = decoder.decodeOptionalInt32ForKey("totalUnreadCountIncludeTags") {
|
||||
self.totalUnreadCountIncludeTags = PeerSummaryCounterTags(rawValue: value)
|
||||
} else {
|
||||
self.totalUnreadCountIncludeTags = [.regularChatsAndPrivateGroups]
|
||||
}
|
||||
self.displayNameOnLockscreen = decoder.decodeInt32ForKey("displayNameOnLockscreen", orElse: 1) != 0
|
||||
self.displayNotificationsFromAllAccounts = decoder.decodeInt32ForKey("displayNotificationsFromAllAccounts", orElse: 1) != 0
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.playSounds ? 1 : 0, forKey: "s")
|
||||
encoder.encodeInt32(self.vibrate ? 1 : 0, forKey: "v")
|
||||
encoder.encodeInt32(self.displayPreviews ? 1 : 0, forKey: "p")
|
||||
encoder.encodeInt32(self.totalUnreadCountDisplayStyle.rawValue, forKey: "cds")
|
||||
encoder.encodeInt32(self.totalUnreadCountDisplayCategory.rawValue, forKey: "totalUnreadCountDisplayCategory")
|
||||
encoder.encodeInt32(self.totalUnreadCountIncludeTags.rawValue, forKey: "totalUnreadCountIncludeTags")
|
||||
encoder.encodeInt32(self.displayNameOnLockscreen ? 1 : 0, forKey: "displayNameOnLockscreen")
|
||||
encoder.encodeInt32(self.displayNotificationsFromAllAccounts ? 1 : 0, forKey: "displayNotificationsFromAllAccounts")
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
if let to = to as? InAppNotificationSettings {
|
||||
return self == to
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
20
NotificationService/Namespaces.swift
Normal file
20
NotificationService/Namespaces.swift
Normal file
@ -0,0 +1,20 @@
|
||||
import PostboxDataTypes
|
||||
|
||||
extension PeerSummaryCounterTags {
|
||||
static let regularChatsAndPrivateGroups = PeerSummaryCounterTags(rawValue: 1 << 0)
|
||||
static let publicGroups = PeerSummaryCounterTags(rawValue: 1 << 1)
|
||||
static let channels = PeerSummaryCounterTags(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
struct Namespaces {
|
||||
struct Message {
|
||||
static let Cloud: Int32 = 0
|
||||
}
|
||||
|
||||
struct Peer {
|
||||
static let CloudUser: Int32 = 0
|
||||
static let CloudGroup: Int32 = 1
|
||||
static let CloudChannel: Int32 = 2
|
||||
static let SecretChat: Int32 = 3
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
#import "Sync.h"
|
||||
//#import <sqlcipher/sqlcipher.h>
|
||||
|
@ -1,9 +1,17 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import ValueBox
|
||||
import PostboxDataTypes
|
||||
import MessageHistoryReadStateTable
|
||||
import MessageHistoryMetadataTable
|
||||
import PostboxDataTypes
|
||||
import PreferencesTable
|
||||
import PeerTable
|
||||
import PostboxCoding
|
||||
|
||||
private let registeredTypes: Void = {
|
||||
declareEncodable(InAppNotificationSettings.self, f: InAppNotificationSettings.init(decoder:))
|
||||
declareEncodable(TelegramChannel.self, f: TelegramChannel.init(decoder:))
|
||||
}()
|
||||
|
||||
private func accountRecordIdPathName(_ id: Int64) -> String {
|
||||
return "account-\(UInt64(bitPattern: id))"
|
||||
@ -15,40 +23,49 @@ private final class ValueBoxLoggerImpl: ValueBoxLogger {
|
||||
}
|
||||
}
|
||||
|
||||
private extension PeerSummaryCounterTags {
|
||||
static let regularChatsAndPrivateGroups = PeerSummaryCounterTags(rawValue: 1 << 0)
|
||||
static let publicGroups = PeerSummaryCounterTags(rawValue: 1 << 1)
|
||||
static let channels = PeerSummaryCounterTags(rawValue: 1 << 2)
|
||||
}
|
||||
|
||||
private struct Namespaces {
|
||||
struct Message {
|
||||
static let Cloud: Int32 = 0
|
||||
}
|
||||
|
||||
struct Peer {
|
||||
static let CloudUser: Int32 = 0
|
||||
static let CloudGroup: Int32 = 1
|
||||
static let CloudChannel: Int32 = 2
|
||||
static let SecretChat: Int32 = 3
|
||||
}
|
||||
}
|
||||
|
||||
final class SyncProviderImpl {
|
||||
func addIncomingMessage(withRootPath rootPath: String, accountId: Int64, encryptionParameters: DeviceSpecificEncryptionParameters, peerId: Int64, messageId: Int32, completion: @escaping (Int32) -> Void) {
|
||||
Queue.mainQueue().async {
|
||||
let _ = registeredTypes
|
||||
|
||||
let sharedBasePath = rootPath + "/accounts-metadata"
|
||||
let basePath = rootPath + "/" + accountRecordIdPathName(accountId) + "/postbox"
|
||||
|
||||
let sharedValueBox = SqliteValueBox(basePath: sharedBasePath + "/db", queue: Queue.mainQueue(), logger: ValueBoxLoggerImpl(), encryptionParameters: nil, disableCache: true, upgradeProgress: { _ in
|
||||
})
|
||||
|
||||
let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: Queue.mainQueue(), logger: ValueBoxLoggerImpl(), encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: encryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: encryptionParameters.salt)!), disableCache: true, upgradeProgress: { _ in
|
||||
})
|
||||
|
||||
let metadataTable = MessageHistoryMetadataTable(valueBox: valueBox, table: MessageHistoryMetadataTable.tableSpec(10))
|
||||
let readStateTable = MessageHistoryReadStateTable(valueBox: valueBox, table: MessageHistoryReadStateTable.tableSpec(14), defaultMessageNamespaceReadStates: [:])
|
||||
let peerTable = PeerTable(valueBox: valueBox, table: PeerTable.tableSpec(2), reverseAssociatedTable: nil)
|
||||
|
||||
let preferencesTable = PreferencesTable(valueBox: sharedValueBox, table: PreferencesTable.tableSpec(2))
|
||||
|
||||
let peerId = PeerId(peerId)
|
||||
|
||||
let initialCombinedState = readStateTable.getCombinedState(peerId)
|
||||
let (combinedState, _) = readStateTable.addIncomingMessages(peerId, indices: Set([MessageIndex(id: MessageId(peerId: peerId, namespace: 0, id: messageId), timestamp: 1)]))
|
||||
|
||||
let combinedState = initialCombinedState.flatMap { state -> CombinedPeerReadState in
|
||||
var state = state
|
||||
for i in 0 ..< state.states.count {
|
||||
if state.states[i].0 == Namespaces.Message.Cloud {
|
||||
switch state.states[i].1 {
|
||||
case .idBased(let maxIncomingReadId, let maxOutgoingReadId, var maxKnownId, var count, let markedUnread):
|
||||
if messageId > maxIncomingReadId {
|
||||
count += 1
|
||||
}
|
||||
maxKnownId = max(maxKnownId, messageId)
|
||||
state.states[i] = (state.states[i].0, .idBased(maxIncomingReadId: maxIncomingReadId, maxOutgoingReadId: maxOutgoingReadId, maxKnownId: maxKnownId, count: count, markedUnread: markedUnread))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
if let combinedState = combinedState {
|
||||
let initialCount = initialCombinedState?.count ?? 0
|
||||
let updatedCount = combinedState.count
|
||||
@ -56,7 +73,20 @@ final class SyncProviderImpl {
|
||||
|
||||
let tag: PeerSummaryCounterTags
|
||||
if peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
tag = .channels
|
||||
if let channel = peerTable.get(peerId) as? TelegramChannel {
|
||||
switch channel.info {
|
||||
case .broadcast:
|
||||
tag = .channels
|
||||
case .group:
|
||||
if channel.username != nil {
|
||||
tag = .publicGroups
|
||||
} else {
|
||||
tag = .regularChatsAndPrivateGroups
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tag = .channels
|
||||
}
|
||||
} else {
|
||||
tag = .regularChatsAndPrivateGroups
|
||||
}
|
||||
@ -79,7 +109,9 @@ final class SyncProviderImpl {
|
||||
totalUnreadState.filteredCounters[tag] = counters
|
||||
}
|
||||
|
||||
totalCount = totalUnreadState.count(for: .filtered, in: .messages, with: [.channels, .publicGroups, .regularChatsAndPrivateGroups])
|
||||
let inAppSettings = preferencesTable.get(key: ApplicationSpecificSharedDataKeys.inAppNotificationSettings) as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings
|
||||
|
||||
totalCount = totalUnreadState.count(for: inAppSettings.totalUnreadCountDisplayStyle.category, in: inAppSettings.totalUnreadCountDisplayCategory.statsType, with: inAppSettings.totalUnreadCountIncludeTags)
|
||||
metadataTable.setChatListTotalUnreadState(totalUnreadState)
|
||||
metadataTable.setShouldReindexUnreadCounts(value: true)
|
||||
|
||||
|
38
NotificationService/TelegramChannel.swift
Normal file
38
NotificationService/TelegramChannel.swift
Normal file
@ -0,0 +1,38 @@
|
||||
import PostboxDataTypes
|
||||
import PostboxCoding
|
||||
|
||||
public enum TelegramChannelInfo: Int32 {
|
||||
case broadcast = 0
|
||||
case group = 1
|
||||
}
|
||||
|
||||
public final class TelegramChannel: Peer {
|
||||
public let id: PeerId
|
||||
public let username: String?
|
||||
public let info: TelegramChannelInfo
|
||||
|
||||
public let associatedPeerId: PeerId? = nil
|
||||
public let notificationSettingsPeerId: PeerId? = nil
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.id = PeerId(decoder.decodeInt64ForKey("i", orElse: 0))
|
||||
self.username = decoder.decodeOptionalStringForKey("un")
|
||||
self.info = TelegramChannelInfo(rawValue: decoder.decodeInt32ForKey("i.t", orElse: 0)) ?? .broadcast
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
public func isEqual(_ other: Peer) -> Bool {
|
||||
guard let other = other as? TelegramChannel else {
|
||||
return false
|
||||
}
|
||||
|
||||
if self.username != other.username {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -4824,6 +4824,8 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Wallet.Send.ConfirmationConfirm" = "Confirm";
|
||||
"Wallet.Send.Send" = "Send";
|
||||
"Wallet.Settings.Title" = "Wallet Settings";
|
||||
"Wallet.Settings.Configuration" = "Configuration";
|
||||
"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\nWallets are located in the TON Blockchain, which is not controlled by Telegram. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty.";
|
||||
"Wallet.Intro.NotNow" = "Not Now";
|
||||
@ -4908,11 +4910,15 @@ Any member of this group will be able to see messages in the channel.";
|
||||
"Wallet.Send.SendAnyway" = "Send Anyway";
|
||||
"Wallet.Receive.CreateInvoice" = "Create Invoice";
|
||||
"Wallet.Receive.CreateInvoiceInfo" = "You can specify 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" = "Confguration";
|
||||
"Wallet.Configuration.Apply" = "Save";
|
||||
|
||||
"Wallet.CreateInvoice.Title" = "Create Invoice";
|
||||
|
||||
"Wallet.Navigation.Close" = "Close";
|
||||
|
@ -42,19 +42,6 @@
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).ton</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ton</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
|
@ -228,9 +228,11 @@ private let records = Atomic<[WalletStateRecord]>(value: [])
|
||||
|
||||
private final class WalletStorageInterfaceImpl: WalletStorageInterface {
|
||||
private let storage: FileBackedStorage
|
||||
private let configurationStorage: FileBackedStorage
|
||||
|
||||
init(path: String) {
|
||||
init(path: String, configurationPath: String) {
|
||||
self.storage = FileBackedStorage(path: path)
|
||||
self.configurationStorage = FileBackedStorage(path: configurationPath)
|
||||
}
|
||||
|
||||
func watchWalletRecords() -> Signal<[WalletStateRecord], NoError> {
|
||||
@ -278,6 +280,35 @@ private final class WalletStorageInterfaceImpl: WalletStorageInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func customWalletConfiguration() -> Signal<CustomWalletConfiguration?, NoError> {
|
||||
return self.configurationStorage.watch()
|
||||
|> map { data -> CustomWalletConfiguration? in
|
||||
guard let data = data, !data.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
return try JSONDecoder().decode(CustomWalletConfiguration.self, from: data)
|
||||
} catch let error {
|
||||
print("Error deserializing data: \(error)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateCustomWalletConfiguration(_ value: CustomWalletConfiguration?) {
|
||||
do {
|
||||
if let value = value {
|
||||
let updatedData = try JSONEncoder().encode(value)
|
||||
let _ = self.configurationStorage.set(data: updatedData).start()
|
||||
} else {
|
||||
let _ = self.configurationStorage.set(data: Data()).start()
|
||||
}
|
||||
} catch let error {
|
||||
print("Error serializing data: \(error)")
|
||||
let _ = self.configurationStorage.set(data: Data()).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
|
||||
@ -287,6 +318,8 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
||||
let presentationData: WalletPresentationData
|
||||
let window: Window1
|
||||
|
||||
let supportsCustomConfigurations: Bool = true
|
||||
|
||||
private var currentImagePickerCompletion: ((UIImage) -> Void)?
|
||||
|
||||
var inForeground: Signal<Bool, NoError> {
|
||||
@ -360,17 +393,17 @@ private final class WalletContextImpl: NSObject, WalletContext, UIImagePickerCon
|
||||
picker.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
init(basePath: String, config: String, blockchainName: String, navigationBarTheme: NavigationBarTheme, window: Window1) {
|
||||
init(basePath: String, storage: WalletStorageInterfaceImpl, config: String, blockchainName: String, navigationBarTheme: NavigationBarTheme, window: Window1) {
|
||||
let _ = try? FileManager.default.createDirectory(at: URL(fileURLWithPath: basePath + "/keys"), withIntermediateDirectories: true, attributes: nil)
|
||||
self.storage = storage
|
||||
|
||||
self.storage = WalletStorageInterfaceImpl(path: basePath + "/data")
|
||||
self.window = window
|
||||
|
||||
self.tonInstance = TonInstance(
|
||||
basePath: basePath + "/keys",
|
||||
config: config,
|
||||
blockchainName: blockchainName,
|
||||
proxy: nil /*TonProxyImpl()*/
|
||||
proxy: nil
|
||||
)
|
||||
|
||||
let baseAppBundleId = Bundle.main.bundleIdentifier!
|
||||
@ -555,40 +588,74 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
print("Starting with \(documentsPath)")
|
||||
#endif
|
||||
|
||||
let updatedConfigSignal: Signal<String, NoError> = Signal { subscriber in
|
||||
let downloadTask = URLSession.shared.downloadTask(with: URL(string: "https://test.ton.org/ton-lite-client-test1.config.json")!, completionHandler: { location, _, error in
|
||||
if let location = location, let data = try? Data(contentsOf: location), let string = String(data: data, encoding: .utf8) {
|
||||
subscriber.putNext(string)
|
||||
subscriber.putCompletion()
|
||||
}
|
||||
})
|
||||
downloadTask.resume()
|
||||
let storage = WalletStorageInterfaceImpl(path: documentsPath + "/data", configurationPath: documentsPath + "/customConfiguration")
|
||||
|
||||
return ActionDisposable {
|
||||
let configSignal = downloadFile(url: URL(string: "https://test.ton.org/ton-lite-client-test1.config.json")!)
|
||||
|> mapToSignal { data -> Signal<String, DownloadFileError> in
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
return .single(string)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
|> retry(retryOnError: { error in
|
||||
if case .network = error {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}, delayIncrement: 0.2, maxDelay: 5.0, maxRetries: 1000, onQueue: Queue.concurrentDefaultQueue())
|
||||
|> `catch` { _ -> Signal<String, NoError> in
|
||||
return .complete()
|
||||
}
|
||||
|
||||
let updatedConfig = Promise<String>()
|
||||
updatedConfig.set(updatedConfigSignal)
|
||||
let updatedRemoteConfig = Promise<String>()
|
||||
updatedRemoteConfig.set(configSignal)
|
||||
|
||||
let configPath = documentsPath + "/config"
|
||||
var initialConfig: Signal<String, NoError> = .complete()
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: configPath)), let string = String(data: data, encoding: .utf8) {
|
||||
initialConfig = .single(string)
|
||||
} else {
|
||||
initialConfig = updatedConfig.get() |> take(1)
|
||||
initialConfig = updatedRemoteConfig.get() |> take(1)
|
||||
}
|
||||
|
||||
let _ = (initialConfig
|
||||
let resolvedInitialConfig = combineLatest(queue: .mainQueue(),
|
||||
initialConfig,
|
||||
storage.customWalletConfiguration() |> take(1)
|
||||
)
|
||||
|> map { initialConfig, customConfig -> String in
|
||||
if let customConfig = customConfig {
|
||||
switch customConfig {
|
||||
case let .string(string):
|
||||
return string
|
||||
}
|
||||
} else {
|
||||
return initialConfig
|
||||
}
|
||||
}
|
||||
|
||||
let _ = (resolvedInitialConfig
|
||||
|> deliverOnMainQueue).start(next: { initialConfig in
|
||||
let walletContext = WalletContextImpl(basePath: documentsPath, config: initialConfig, blockchainName: "testnet", navigationBarTheme: navigationBarTheme, window: mainWindow)
|
||||
let walletContext = WalletContextImpl(basePath: documentsPath, storage: storage, config: initialConfig, blockchainName: "testnet", navigationBarTheme: navigationBarTheme, window: mainWindow)
|
||||
self.walletContext = walletContext
|
||||
|
||||
let _ = (updatedConfig.get()
|
||||
|> deliverOnMainQueue).start(next: { config in
|
||||
if config != initialConfig {
|
||||
let _ = walletContext.tonInstance.updateConfig(config: config, blockchainName: "testnet").start()
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
updatedRemoteConfig.get(),
|
||||
storage.customWalletConfiguration()
|
||||
)
|
||||
|> map { initialConfig, customConfig -> String in
|
||||
if let customConfig = customConfig {
|
||||
switch customConfig {
|
||||
case let .string(string):
|
||||
return string
|
||||
}
|
||||
} else {
|
||||
return initialConfig
|
||||
}
|
||||
}
|
||||
|> distinctUntilChanged).start(next: { config in
|
||||
let _ = walletContext.tonInstance.updateConfig(config: config, blockchainName: "testnet").start()
|
||||
})
|
||||
|
||||
let _ = (combineLatest(queue: .mainQueue(),
|
||||
@ -639,3 +706,29 @@ final class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private enum DownloadFileError {
|
||||
case network
|
||||
}
|
||||
|
||||
private func downloadFile(url: URL) -> Signal<Data, DownloadFileError> {
|
||||
return Signal { subscriber in
|
||||
let completed = Atomic<Bool>(value: false)
|
||||
let downloadTask = URLSession.shared.downloadTask(with: url, completionHandler: { location, _, error in
|
||||
let _ = completed.swap(true)
|
||||
if let location = location, let data = try? Data(contentsOf: location) {
|
||||
subscriber.putNext(data)
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
subscriber.putError(.network)
|
||||
}
|
||||
})
|
||||
downloadTask.resume()
|
||||
|
||||
return ActionDisposable {
|
||||
if !completed.with({ $0 }) {
|
||||
downloadTask.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@
|
||||
"Wallet.Info.TransactionTo" = "to";
|
||||
"Wallet.Info.TransactionFrom" = "from";
|
||||
"Wallet.Info.Updating" = "updating";
|
||||
"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fee";
|
||||
"Wallet.Info.TransactionBlockchainFee" = "%@ blockchain fees";
|
||||
"Wallet.Info.TransactionPendingHeader" = "Pending";
|
||||
"Wallet.Qr.ScanCode" = "Scan QR Code";
|
||||
"Wallet.Qr.Title" = "QR Code";
|
||||
@ -58,6 +58,8 @@
|
||||
"Wallet.Send.ConfirmationConfirm" = "Confirm";
|
||||
"Wallet.Send.Send" = "Send";
|
||||
"Wallet.Settings.Title" = "Wallet Settings";
|
||||
"Wallet.Settings.Configuration" = "Configuration";
|
||||
"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\nWallets are located in the TON Blockchain, which is not controlled by Telegram. If you want a wallet to be deleted, simply transfer all the grams from it and leave it empty.";
|
||||
"Wallet.Intro.NotNow" = "Not Now";
|
||||
@ -142,6 +144,8 @@
|
||||
"Wallet.Send.SendAnyway" = "Send Anyway";
|
||||
"Wallet.Receive.CreateInvoice" = "Create Invoice";
|
||||
"Wallet.Receive.CreateInvoiceInfo" = "You can specify amount and purpose of the payment to save the sender some time.";
|
||||
"Wallet.Configuration.Title" = "Confguration";
|
||||
"Wallet.Configuration.Apply" = "Save";
|
||||
"Wallet.CreateInvoice.Title" = "Create Invoice";
|
||||
"Wallet.Navigation.Close" = "Close";
|
||||
"Wallet.Navigation.Back" = "Back";
|
||||
@ -192,3 +196,4 @@
|
||||
"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.";
|
||||
|
@ -1284,8 +1284,6 @@ public final class ChatListNode: ListView {
|
||||
var filter = true
|
||||
if let inAppNotificationSettings = transaction.getSharedData(ApplicationSpecificSharedDataKeys.inAppNotificationSettings) as? InAppNotificationSettings {
|
||||
switch inAppNotificationSettings.totalUnreadCountDisplayStyle {
|
||||
case .raw:
|
||||
filter = false
|
||||
case .filtered:
|
||||
filter = true
|
||||
}
|
||||
|
@ -17,16 +17,6 @@ private enum MetadataPrefix: Int8 {
|
||||
case PeerHistoryInitialized = 9
|
||||
}
|
||||
|
||||
public enum ChatListTotalUnreadStateCategory: Int32 {
|
||||
case filtered = 0
|
||||
case raw = 1
|
||||
}
|
||||
|
||||
public enum ChatListTotalUnreadStateStats: Int32 {
|
||||
case messages = 0
|
||||
case chats = 1
|
||||
}
|
||||
|
||||
private struct InitializedChatListKey: Hashable {
|
||||
let groupId: PeerGroupId
|
||||
}
|
||||
|
17
submodules/Database/PeerTable/BUCK
Normal file
17
submodules/Database/PeerTable/BUCK
Normal file
@ -0,0 +1,17 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "PeerTable",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/Table:Table",
|
||||
"//submodules/Database/PostboxCoding:PostboxCoding",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
],
|
||||
)
|
100
submodules/Database/PeerTable/Sources/PeerTable.swift
Normal file
100
submodules/Database/PeerTable/Sources/PeerTable.swift
Normal file
@ -0,0 +1,100 @@
|
||||
import Foundation
|
||||
import ValueBox
|
||||
import PostboxDataTypes
|
||||
import Table
|
||||
import PostboxCoding
|
||||
|
||||
public protocol ReverseAssociatedPeerTable {
|
||||
func removeReverseAssociation(target: PeerId, from: PeerId)
|
||||
func addReverseAssociation(target: PeerId, from: PeerId)
|
||||
}
|
||||
|
||||
public final class PeerTable: Table {
|
||||
public static func tableSpec(_ id: Int32) -> ValueBoxTable {
|
||||
return ValueBoxTable(id: id, keyType: .int64, compactValuesOnCreation: false)
|
||||
}
|
||||
|
||||
private let reverseAssociatedTable: ReverseAssociatedPeerTable?
|
||||
|
||||
private let sharedEncoder = PostboxEncoder()
|
||||
private let sharedKey = ValueBoxKey(length: 8)
|
||||
|
||||
private var cachedPeers: [PeerId: Peer] = [:]
|
||||
private var updatedInitialPeers: [PeerId: Peer?] = [:]
|
||||
|
||||
public init(valueBox: ValueBox, table: ValueBoxTable, reverseAssociatedTable: ReverseAssociatedPeerTable?) {
|
||||
self.reverseAssociatedTable = reverseAssociatedTable
|
||||
|
||||
super.init(valueBox: valueBox, table: table)
|
||||
}
|
||||
|
||||
private func key(_ id: PeerId) -> ValueBoxKey {
|
||||
self.sharedKey.setInt64(0, value: id.toInt64())
|
||||
return self.sharedKey
|
||||
}
|
||||
|
||||
public func set(_ peer: Peer) {
|
||||
let previous = self.get(peer.id)
|
||||
self.cachedPeers[peer.id] = peer
|
||||
if self.updatedInitialPeers[peer.id] == nil {
|
||||
self.updatedInitialPeers[peer.id] = previous
|
||||
}
|
||||
}
|
||||
|
||||
public func get(_ id: PeerId) -> Peer? {
|
||||
if let peer = self.cachedPeers[id] {
|
||||
return peer
|
||||
}
|
||||
if let value = self.valueBox.get(self.table, key: self.key(id)) {
|
||||
if let peer = PostboxDecoder(buffer: value).decodeRootObject() as? Peer {
|
||||
self.cachedPeers[id] = peer
|
||||
return peer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
override public func clearMemoryCache() {
|
||||
self.cachedPeers.removeAll()
|
||||
assert(self.updatedInitialPeers.isEmpty)
|
||||
}
|
||||
|
||||
public func transactionUpdatedPeers() -> [(Peer?, Peer)] {
|
||||
var result: [(Peer?, Peer)] = []
|
||||
for (peerId, initialPeer) in self.updatedInitialPeers {
|
||||
if let peer = self.get(peerId) {
|
||||
result.append((initialPeer, peer))
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override public func beforeCommit() {
|
||||
if !self.updatedInitialPeers.isEmpty {
|
||||
for (peerId, previousPeer) in self.updatedInitialPeers {
|
||||
if let peer = self.cachedPeers[peerId] {
|
||||
self.sharedEncoder.reset()
|
||||
self.sharedEncoder.encodeRootObject(peer)
|
||||
|
||||
self.valueBox.set(self.table, key: self.key(peerId), value: self.sharedEncoder.readBufferNoCopy())
|
||||
|
||||
let previousAssociation = previousPeer?.associatedPeerId
|
||||
if previousAssociation != peer.associatedPeerId {
|
||||
if let previousAssociation = previousAssociation {
|
||||
self.reverseAssociatedTable?.removeReverseAssociation(target: previousAssociation, from: peerId)
|
||||
}
|
||||
if let associatedPeerId = peer.associatedPeerId {
|
||||
self.reverseAssociatedTable?.addReverseAssociation(target: associatedPeerId, from: peerId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
self.updatedInitialPeers.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
|
||||
public protocol MediaResourceId {
|
||||
var uniqueId: String { get }
|
||||
var hashValue: Int { get }
|
||||
func isEqual(to: MediaResourceId) -> Bool
|
||||
}
|
||||
|
||||
public struct WrappedMediaResourceId: Hashable {
|
||||
public let id: MediaResourceId
|
||||
|
||||
public init(_ id: MediaResourceId) {
|
||||
self.id = id
|
||||
}
|
||||
|
||||
public static func ==(lhs: WrappedMediaResourceId, rhs: WrappedMediaResourceId) -> Bool {
|
||||
return lhs.id.isEqual(to: rhs.id)
|
||||
}
|
||||
|
||||
public var hashValue: Int {
|
||||
return self.id.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
public func anyHashableFromMediaResourceId(_ id: MediaResourceId) -> AnyHashable {
|
||||
return AnyHashable(WrappedMediaResourceId(id))
|
||||
}
|
11
submodules/Database/PostboxDataTypes/Sources/Peer.swift
Normal file
11
submodules/Database/PostboxDataTypes/Sources/Peer.swift
Normal file
@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
import Buffers
|
||||
import PostboxCoding
|
||||
|
||||
public protocol Peer: class, PostboxCoding {
|
||||
var id: PeerId { get }
|
||||
var associatedPeerId: PeerId? { get }
|
||||
var notificationSettingsPeerId: PeerId? { get }
|
||||
|
||||
func isEqual(_ other: Peer) -> Bool
|
||||
}
|
17
submodules/Database/PreferencesTable/BUCK
Normal file
17
submodules/Database/PreferencesTable/BUCK
Normal file
@ -0,0 +1,17 @@
|
||||
load("//Config:buck_rule_macros.bzl", "static_library")
|
||||
|
||||
static_library(
|
||||
name = "PreferencesTable",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/Database/ValueBox:ValueBox",
|
||||
"//submodules/Database/Table:Table",
|
||||
"//submodules/Database/PostboxCoding:PostboxCoding",
|
||||
"//submodules/Database/PostboxDataTypes:PostboxDataTypes",
|
||||
],
|
||||
frameworks = [
|
||||
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
|
||||
],
|
||||
)
|
@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
import PostboxCoding
|
||||
import PostboxDataTypes
|
||||
|
||||
public protocol PreferencesEntry: PostboxCoding {
|
||||
var relatedResources: [MediaResourceId] { get }
|
||||
|
||||
func isEqual(to: PreferencesEntry) -> Bool
|
||||
}
|
||||
|
||||
public extension PreferencesEntry {
|
||||
var relatedResources: [MediaResourceId] {
|
||||
return []
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
import Foundation
|
||||
import ValueBox
|
||||
import Table
|
||||
import PostboxCoding
|
||||
|
||||
public enum PreferencesOperation {
|
||||
case update(ValueBoxKey, PreferencesEntry?)
|
||||
}
|
||||
|
||||
private struct CachedEntry {
|
||||
let entry: PreferencesEntry?
|
||||
}
|
||||
|
||||
public final class PreferencesTable: Table {
|
||||
private var cachedEntries: [ValueBoxKey: CachedEntry] = [:]
|
||||
private var updatedEntryKeys = Set<ValueBoxKey>()
|
||||
|
||||
public static func tableSpec(_ id: Int32) -> ValueBoxTable {
|
||||
return ValueBoxTable(id: id, keyType: .binary, compactValuesOnCreation: false)
|
||||
}
|
||||
|
||||
public func enumerateEntries(_ f: (PreferencesEntry) -> Bool) {
|
||||
self.valueBox.scan(self.table, values: { _, value in
|
||||
if let object = PostboxDecoder(buffer: value).decodeRootObject() as? PreferencesEntry {
|
||||
return f(object)
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func get(key: ValueBoxKey) -> PreferencesEntry? {
|
||||
if let cached = self.cachedEntries[key] {
|
||||
return cached.entry
|
||||
} else {
|
||||
if let value = self.valueBox.get(self.table, key: key), let object = PostboxDecoder(buffer: value).decodeRootObject() as? PreferencesEntry {
|
||||
self.cachedEntries[key] = CachedEntry(entry: object)
|
||||
return object
|
||||
} else {
|
||||
self.cachedEntries[key] = CachedEntry(entry: nil)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func set(key: ValueBoxKey, value: PreferencesEntry?, operations: inout [PreferencesOperation]) {
|
||||
self.cachedEntries[key] = CachedEntry(entry: value)
|
||||
updatedEntryKeys.insert(key)
|
||||
operations.append(.update(key, value))
|
||||
}
|
||||
|
||||
override public func clearMemoryCache() {
|
||||
assert(self.updatedEntryKeys.isEmpty)
|
||||
}
|
||||
|
||||
override public func beforeCommit() {
|
||||
if !self.updatedEntryKeys.isEmpty {
|
||||
for key in self.updatedEntryKeys {
|
||||
if let value = self.cachedEntries[key]?.entry {
|
||||
let encoder = PostboxEncoder()
|
||||
encoder.encodeRootObject(value)
|
||||
withExtendedLifetime(encoder, {
|
||||
self.valueBox.set(self.table, key: key, value: encoder.readBufferNoCopy())
|
||||
})
|
||||
} else {
|
||||
self.valueBox.remove(self.table, key: key, secure: false)
|
||||
}
|
||||
}
|
||||
|
||||
self.updatedEntryKeys.removeAll()
|
||||
}
|
||||
}
|
||||
}
|
@ -4,14 +4,10 @@ import SwiftSignalKit
|
||||
public protocol StatusBarHost {
|
||||
var statusBarFrame: CGRect { get }
|
||||
var statusBarStyle: UIStatusBarStyle { get set }
|
||||
var statusBarWindow: UIView? { get }
|
||||
var statusBarView: UIView? { get }
|
||||
|
||||
var keyboardWindow: UIWindow? { get }
|
||||
var keyboardView: UIView? { get }
|
||||
|
||||
var handleVolumeControl: Signal<Bool, NoError> { get }
|
||||
|
||||
var isApplicationInForeground: Bool { get }
|
||||
|
||||
func setStatusBarStyle(_ style: UIStatusBarStyle, animated: Bool)
|
||||
|
@ -1,303 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
private struct MappedStatusBar {
|
||||
let style: StatusBarStyle
|
||||
let frame: CGRect
|
||||
let statusBar: StatusBar?
|
||||
}
|
||||
|
||||
private struct MappedStatusBarSurface {
|
||||
let statusBars: [MappedStatusBar]
|
||||
let surface: StatusBarSurface
|
||||
}
|
||||
|
||||
private func mapStatusBar(_ statusBar: StatusBar, forceInCall: Bool) -> MappedStatusBar {
|
||||
let frame = CGRect(origin: statusBar.view.convert(CGPoint(), to: nil), size: statusBar.frame.size)
|
||||
let resolvedStyle: StatusBarStyle
|
||||
switch statusBar.statusBarStyle {
|
||||
case .Black, .White:
|
||||
if forceInCall {
|
||||
resolvedStyle = .White
|
||||
} else {
|
||||
resolvedStyle = statusBar.statusBarStyle
|
||||
}
|
||||
default:
|
||||
resolvedStyle = statusBar.statusBarStyle
|
||||
}
|
||||
return MappedStatusBar(style: resolvedStyle, frame: frame, statusBar: statusBar)
|
||||
}
|
||||
|
||||
private func mappedSurface(_ surface: StatusBarSurface, forceInCall: Bool) -> MappedStatusBarSurface {
|
||||
var statusBars: [MappedStatusBar] = []
|
||||
for statusBar in surface.statusBars {
|
||||
if statusBar.statusBarStyle != .Ignore {
|
||||
statusBars.append(mapStatusBar(statusBar, forceInCall: forceInCall))
|
||||
}
|
||||
}
|
||||
return MappedStatusBarSurface(statusBars: statusBars, surface: surface)
|
||||
}
|
||||
|
||||
private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface, forceInCall: Bool) -> MappedStatusBarSurface {
|
||||
if surface.statusBars.count > 1 {
|
||||
for i in 1 ..< surface.statusBars.count {
|
||||
if (!forceInCall && surface.statusBars[i].style != surface.statusBars[i - 1].style) || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat.ulpOfOne {
|
||||
return surface
|
||||
}
|
||||
if let lhsStatusBar = surface.statusBars[i - 1].statusBar, let rhsStatusBar = surface.statusBars[i].statusBar , !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) {
|
||||
return surface
|
||||
}
|
||||
}
|
||||
let size = statusBarSize
|
||||
return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: forceInCall ? .White : surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface)
|
||||
} else {
|
||||
return surface
|
||||
}
|
||||
}
|
||||
|
||||
private func displayHiddenAnimation() -> CAAnimation {
|
||||
let animation = CABasicAnimation(keyPath: "transform.translation.y")
|
||||
animation.fromValue = NSNumber(value: Float(-40.0))
|
||||
animation.toValue = NSNumber(value: Float(-40.0))
|
||||
animation.fillMode = .both
|
||||
animation.duration = 1.0
|
||||
animation.speed = 0.0
|
||||
animation.isAdditive = true
|
||||
animation.isRemovedOnCompletion = false
|
||||
|
||||
return animation
|
||||
}
|
||||
|
||||
class StatusBarManager {
|
||||
private var host: StatusBarHost
|
||||
private let volumeControlStatusBar: VolumeControlStatusBar
|
||||
private let volumeControlStatusBarNode: VolumeControlStatusBarNode
|
||||
|
||||
private var surfaces: [StatusBarSurface] = []
|
||||
private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool)?
|
||||
|
||||
var inCallNavigate: (() -> Void)?
|
||||
|
||||
private var volumeTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(host: StatusBarHost, volumeControlStatusBar: VolumeControlStatusBar, volumeControlStatusBarNode: VolumeControlStatusBarNode) {
|
||||
self.host = host
|
||||
self.volumeControlStatusBar = volumeControlStatusBar
|
||||
self.volumeControlStatusBarNode = volumeControlStatusBarNode
|
||||
self.volumeControlStatusBarNode.isHidden = true
|
||||
|
||||
self.volumeControlStatusBar.valueChanged = { [weak self] previous, updated in
|
||||
if let strongSelf = self {
|
||||
strongSelf.startVolumeTimer()
|
||||
strongSelf.volumeControlStatusBarNode.updateValue(from: CGFloat(previous), to: CGFloat(updated))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startVolumeTimer() {
|
||||
self.volumeTimer?.invalidate()
|
||||
let timer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in
|
||||
self?.endVolumeTimer()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.volumeTimer = timer
|
||||
timer.start()
|
||||
if self.volumeControlStatusBarNode.isHidden {
|
||||
self.volumeControlStatusBarNode.isHidden = false
|
||||
self.volumeControlStatusBarNode.alpha = 1.0
|
||||
self.volumeControlStatusBarNode.allowsGroupOpacity = true
|
||||
self.volumeControlStatusBarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.18, completion: { [weak self] _ in
|
||||
self?.volumeControlStatusBarNode.allowsGroupOpacity = false
|
||||
})
|
||||
}
|
||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
private func endVolumeTimer() {
|
||||
self.volumeControlStatusBarNode.alpha = 0.0
|
||||
self.volumeControlStatusBarNode.allowsGroupOpacity = true
|
||||
self.volumeControlStatusBarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { [weak self] completed in
|
||||
if let strongSelf = self, completed {
|
||||
strongSelf.volumeControlStatusBarNode.isHidden = true
|
||||
strongSelf.volumeControlStatusBarNode.allowsGroupOpacity = false
|
||||
}
|
||||
})
|
||||
self.volumeTimer = nil
|
||||
if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams {
|
||||
self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) {
|
||||
let previousSurfaces = self.surfaces
|
||||
self.surfaces = surfaces
|
||||
self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, alphaTransition: .immediate)
|
||||
}
|
||||
|
||||
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, alphaTransition: ContainedViewLayoutTransition) {
|
||||
let statusBarFrame = self.host.statusBarFrame
|
||||
guard let statusBarView = self.host.statusBarView else {
|
||||
return
|
||||
}
|
||||
|
||||
self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows)
|
||||
|
||||
if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) {
|
||||
self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil)
|
||||
}
|
||||
|
||||
var mappedSurfaces: [MappedStatusBarSurface] = []
|
||||
var mapIndex = 0
|
||||
var doNotOptimize = false
|
||||
for surface in self.surfaces {
|
||||
inner: for statusBar in surface.statusBars {
|
||||
if statusBar.statusBarStyle == .Hide {
|
||||
doNotOptimize = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
|
||||
let mapped = mappedSurface(surface, forceInCall: forceInCallStatusBarText != nil)
|
||||
|
||||
if doNotOptimize {
|
||||
mappedSurfaces.append(mapped)
|
||||
} else {
|
||||
mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped, forceInCall: forceInCallStatusBarText != nil))
|
||||
}
|
||||
mapIndex += 1
|
||||
}
|
||||
|
||||
var reduceSurfaces = true
|
||||
var reduceSurfacesStatusBarStyleAndAlpha: (StatusBarStyle, CGFloat)?
|
||||
var reduceIndex = 0
|
||||
outer: for surface in mappedSurfaces {
|
||||
for mappedStatusBar in surface.statusBars {
|
||||
if reduceIndex == 0 && mappedStatusBar.style == .Hide {
|
||||
reduceSurfaces = false
|
||||
break outer
|
||||
}
|
||||
if mappedStatusBar.frame.origin.equalTo(CGPoint()) {
|
||||
let statusBarAlpha = mappedStatusBar.statusBar?.alpha ?? 1.0
|
||||
if let reduceSurfacesStatusBarStyleAndAlpha = reduceSurfacesStatusBarStyleAndAlpha {
|
||||
if mappedStatusBar.style != reduceSurfacesStatusBarStyleAndAlpha.0 {
|
||||
reduceSurfaces = false
|
||||
break outer
|
||||
}
|
||||
if !statusBarAlpha.isEqual(to: reduceSurfacesStatusBarStyleAndAlpha.1) {
|
||||
reduceSurfaces = false
|
||||
break outer
|
||||
}
|
||||
} else {
|
||||
reduceSurfacesStatusBarStyleAndAlpha = (mappedStatusBar.style, statusBarAlpha)
|
||||
}
|
||||
}
|
||||
}
|
||||
reduceIndex += 1
|
||||
}
|
||||
|
||||
if reduceSurfaces {
|
||||
outer: for surface in mappedSurfaces {
|
||||
for mappedStatusBar in surface.statusBars {
|
||||
if mappedStatusBar.frame.origin.equalTo(CGPoint()) {
|
||||
if let statusBar = mappedStatusBar.statusBar , !statusBar.layer.hasPositionOrOpacityAnimations() {
|
||||
mappedSurfaces = [MappedStatusBarSurface(statusBars: [mappedStatusBar], surface: surface.surface)]
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var visibleStatusBars: [StatusBar] = []
|
||||
|
||||
var globalStatusBar: (StatusBarStyle, CGFloat, CGFloat)?
|
||||
|
||||
var coveredIdentity = false
|
||||
var statusBarIndex = 0
|
||||
for i in 0 ..< mappedSurfaces.count {
|
||||
for mappedStatusBar in mappedSurfaces[i].statusBars {
|
||||
if let statusBar = mappedStatusBar.statusBar {
|
||||
if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() && !statusBar.offsetNode.layer.hasPositionAnimations() {
|
||||
if !coveredIdentity {
|
||||
if statusBar.statusBarStyle != .Hide {
|
||||
if statusBar.offsetNode.frame.origin.equalTo(CGPoint()) {
|
||||
coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha)
|
||||
}
|
||||
if statusBarIndex == 0 && globalStatusBar == nil {
|
||||
globalStatusBar = (mappedStatusBar.style, statusBar.alpha, statusBar.offsetNode.frame.origin.y)
|
||||
} else {
|
||||
visibleStatusBars.append(statusBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
visibleStatusBars.append(statusBar)
|
||||
}
|
||||
} else {
|
||||
if !coveredIdentity {
|
||||
coveredIdentity = true
|
||||
if statusBarIndex == 0 && globalStatusBar == nil {
|
||||
globalStatusBar = (mappedStatusBar.style, 1.0, 0.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
statusBarIndex += 1
|
||||
}
|
||||
}
|
||||
|
||||
for surface in previousSurfaces {
|
||||
for statusBar in surface.statusBars {
|
||||
if !visibleStatusBars.contains(where: {$0 === statusBar}) {
|
||||
statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for surface in self.surfaces {
|
||||
for statusBar in surface.statusBars {
|
||||
statusBar.inCallNavigate = self.inCallNavigate
|
||||
if !visibleStatusBars.contains(where: {$0 === statusBar}) {
|
||||
statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for statusBar in visibleStatusBars {
|
||||
statusBar.updateState(statusBar: statusBarView, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated)
|
||||
}
|
||||
|
||||
if self.volumeTimer != nil {
|
||||
globalStatusBar?.1 = 0.0
|
||||
}
|
||||
var isDark = true
|
||||
if let globalStatusBar = globalStatusBar {
|
||||
isDark = globalStatusBar.0.systemStyle == UIStatusBarStyle.lightContent
|
||||
}
|
||||
self.volumeControlStatusBarNode.isDark = isDark
|
||||
|
||||
/*if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows {
|
||||
let statusBarStyle: UIStatusBarStyle
|
||||
if forceInCallStatusBarText != nil {
|
||||
statusBarStyle = .lightContent
|
||||
} else {
|
||||
statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent
|
||||
}
|
||||
if self.host.statusBarStyle != statusBarStyle {
|
||||
self.host.statusBarStyle = statusBarStyle
|
||||
}
|
||||
if let statusBarWindow = self.host.statusBarWindow {
|
||||
alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1)
|
||||
var statusBarBounds = statusBarWindow.bounds
|
||||
if !statusBarBounds.origin.y.isEqual(to: globalStatusBar.2) {
|
||||
statusBarBounds.origin.y = globalStatusBar.2
|
||||
statusBarWindow.bounds = statusBarBounds
|
||||
}
|
||||
}
|
||||
} else {
|
||||
statusBarView.alpha = 0.0
|
||||
}*/
|
||||
}
|
||||
}
|
@ -332,10 +332,6 @@ public class Window1 {
|
||||
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
|
||||
self.hostView = hostView
|
||||
|
||||
//self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false))
|
||||
//self.volumeControlStatusBarNode = VolumeControlStatusBarNode()
|
||||
//self.volumeControlStatusBarNode.isHidden = true
|
||||
|
||||
let boundsSize = self.hostView.eventView.bounds.size
|
||||
self.deviceMetrics = DeviceMetrics(screenSize: UIScreen.main.bounds.size, statusBarHeight: statusBarHost?.statusBarFrame.height ?? defaultStatusBarHeight, onScreenNavigationHeight: self.hostView.onScreenNavigationHeight)
|
||||
|
||||
@ -343,12 +339,10 @@ public class Window1 {
|
||||
let statusBarHeight: CGFloat
|
||||
if let statusBarHost = statusBarHost {
|
||||
statusBarHeight = statusBarHost.statusBarFrame.size.height
|
||||
//self.statusBarManager = StatusBarManager(host: statusBarHost, volumeControlStatusBar: self.volumeControlStatusBar, volumeControlStatusBarNode: self.volumeControlStatusBarNode)
|
||||
self.keyboardManager = KeyboardManager(host: statusBarHost)
|
||||
self.keyboardViewManager = KeyboardViewManager(host: statusBarHost)
|
||||
} else {
|
||||
statusBarHeight = self.deviceMetrics.statusBarHeight
|
||||
//self.statusBarManager = nil
|
||||
self.keyboardManager = nil
|
||||
self.keyboardViewManager = nil
|
||||
}
|
||||
|
@ -78,12 +78,6 @@ typedef struct {
|
||||
const char bit_build[16];
|
||||
} bit_info_t;
|
||||
|
||||
static bit_info_t hockeyapp_library_info __attribute__((section("__TEXT,__bit_ios,regular,no_dead_strip"))) = {
|
||||
.info_version = 1,
|
||||
.bit_version = BITHOCKEY_C_VERSION,
|
||||
.bit_build = BITHOCKEY_C_BUILD
|
||||
};
|
||||
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
@ -485,7 +479,7 @@ NSString *bit_screenSize(void){
|
||||
}
|
||||
|
||||
NSString *bit_sdkVersion(void){
|
||||
return [NSString stringWithFormat:@"ios:%@", [NSString stringWithUTF8String:hockeyapp_library_info.bit_version]];
|
||||
return [NSString stringWithFormat:@"ios:%@", [NSString stringWithUTF8String:BITHOCKEY_C_VERSION]];
|
||||
}
|
||||
|
||||
NSString *bit_appVersion(void){
|
||||
|
@ -46,12 +46,6 @@ typedef struct {
|
||||
const char hockey_build[16];
|
||||
} bitstadium_info_t;
|
||||
|
||||
static bitstadium_info_t bitstadium_library_info __attribute__((section("__TEXT,__bit_hockey,regular,no_dead_strip"))) = {
|
||||
.info_version = 1,
|
||||
.hockey_version = BITHOCKEY_C_VERSION,
|
||||
.hockey_build = BITHOCKEY_C_BUILD
|
||||
};
|
||||
|
||||
#if HOCKEYSDK_FEATURE_CRASH_REPORTER
|
||||
#import "BITCrashManagerPrivate.h"
|
||||
#endif /* HOCKEYSDK_FEATURE_CRASH_REPORTER */
|
||||
@ -528,11 +522,11 @@ static bitstadium_info_t bitstadium_library_info __attribute__((section("__TEXT,
|
||||
|
||||
|
||||
- (NSString *)version {
|
||||
return (NSString *)[NSString stringWithUTF8String:bitstadium_library_info.hockey_version];
|
||||
return (NSString *)[NSString stringWithUTF8String:BITHOCKEY_C_VERSION];
|
||||
}
|
||||
|
||||
- (NSString *)build {
|
||||
return (NSString *)[NSString stringWithUTF8String:bitstadium_library_info.hockey_build];
|
||||
return (NSString *)[NSString stringWithUTF8String:BITHOCKEY_C_BUILD];
|
||||
}
|
||||
|
||||
|
||||
|
@ -43,7 +43,6 @@ private final class NotificationsAndSoundsArguments {
|
||||
let updateInAppPreviews: (Bool) -> Void
|
||||
|
||||
let updateDisplayNameOnLockscreen: (Bool) -> Void
|
||||
let updateTotalUnreadCountStyle: (Bool) -> Void
|
||||
let updateIncludeTag: (PeerSummaryCounterTags, Bool) -> Void
|
||||
let updateTotalUnreadCountCategory: (Bool) -> Void
|
||||
|
||||
@ -57,7 +56,7 @@ private final class NotificationsAndSoundsArguments {
|
||||
|
||||
let updateNotificationsFromAllAccounts: (Bool) -> Void
|
||||
|
||||
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, authorizeNotifications: @escaping () -> Void, suppressWarning: @escaping () -> Void, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateTotalUnreadCountStyle: @escaping (Bool) -> Void, updateIncludeTag: @escaping (PeerSummaryCounterTags, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void, updateJoinedNotifications: @escaping (Bool) -> Void, updateNotificationsFromAllAccounts: @escaping (Bool) -> Void) {
|
||||
init(context: AccountContext, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping(ViewController)->Void, soundSelectionDisposable: MetaDisposable, authorizeNotifications: @escaping () -> Void, suppressWarning: @escaping () -> Void, updateMessageAlerts: @escaping (Bool) -> Void, updateMessagePreviews: @escaping (Bool) -> Void, updateMessageSound: @escaping (PeerMessageSound) -> Void, updateGroupAlerts: @escaping (Bool) -> Void, updateGroupPreviews: @escaping (Bool) -> Void, updateGroupSound: @escaping (PeerMessageSound) -> Void, updateChannelAlerts: @escaping (Bool) -> Void, updateChannelPreviews: @escaping (Bool) -> Void, updateChannelSound: @escaping (PeerMessageSound) -> Void, updateInAppSounds: @escaping (Bool) -> Void, updateInAppVibration: @escaping (Bool) -> Void, updateInAppPreviews: @escaping (Bool) -> Void, updateDisplayNameOnLockscreen: @escaping (Bool) -> Void, updateIncludeTag: @escaping (PeerSummaryCounterTags, Bool) -> Void, updateTotalUnreadCountCategory: @escaping (Bool) -> Void, resetNotifications: @escaping () -> Void, updatedExceptionMode: @escaping(NotificationExceptionMode) -> Void, openAppSettings: @escaping () -> Void, updateJoinedNotifications: @escaping (Bool) -> Void, updateNotificationsFromAllAccounts: @escaping (Bool) -> Void) {
|
||||
self.context = context
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
@ -77,7 +76,6 @@ private final class NotificationsAndSoundsArguments {
|
||||
self.updateInAppVibration = updateInAppVibration
|
||||
self.updateInAppPreviews = updateInAppPreviews
|
||||
self.updateDisplayNameOnLockscreen = updateDisplayNameOnLockscreen
|
||||
self.updateTotalUnreadCountStyle = updateTotalUnreadCountStyle
|
||||
self.updateIncludeTag = updateIncludeTag
|
||||
self.updateTotalUnreadCountCategory = updateTotalUnreadCountCategory
|
||||
self.resetNotifications = resetNotifications
|
||||
@ -113,7 +111,6 @@ public enum NotificationsAndSoundsEntryTag: ItemListItemTag {
|
||||
case inAppVibrate
|
||||
case inAppPreviews
|
||||
case displayNamesOnLockscreen
|
||||
case unreadCountStyle
|
||||
case includePublicGroups
|
||||
case includeChannels
|
||||
case unreadCountCategory
|
||||
@ -168,7 +165,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
case displayNamesOnLockscreenInfo(PresentationTheme, String)
|
||||
|
||||
case badgeHeader(PresentationTheme, String)
|
||||
case unreadCountStyle(PresentationTheme, String, Bool)
|
||||
case includePublicGroups(PresentationTheme, String, Bool)
|
||||
case includeChannels(PresentationTheme, String, Bool)
|
||||
case unreadCountCategory(PresentationTheme, String, Bool)
|
||||
@ -196,7 +192,7 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
return NotificationsAndSoundsSection.inApp.rawValue
|
||||
case .displayNamesOnLockscreen, .displayNamesOnLockscreenInfo:
|
||||
return NotificationsAndSoundsSection.displayNamesOnLockscreen.rawValue
|
||||
case .badgeHeader, .unreadCountStyle, .includePublicGroups, .includeChannels, .unreadCountCategory, .unreadCountCategoryInfo:
|
||||
case .badgeHeader, .includePublicGroups, .includeChannels, .unreadCountCategory, .unreadCountCategoryInfo:
|
||||
return NotificationsAndSoundsSection.badge.rawValue
|
||||
case .joinedNotifications, .joinedNotificationsInfo:
|
||||
return NotificationsAndSoundsSection.joinedNotifications.rawValue
|
||||
@ -267,8 +263,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
return 28
|
||||
case .badgeHeader:
|
||||
return 29
|
||||
case .unreadCountStyle:
|
||||
return 30
|
||||
case .includePublicGroups:
|
||||
return 31
|
||||
case .includeChannels:
|
||||
@ -312,8 +306,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
return NotificationsAndSoundsEntryTag.inAppPreviews
|
||||
case .displayNamesOnLockscreen:
|
||||
return NotificationsAndSoundsEntryTag.displayNamesOnLockscreen
|
||||
case .unreadCountStyle:
|
||||
return NotificationsAndSoundsEntryTag.unreadCountStyle
|
||||
case .includePublicGroups:
|
||||
return NotificationsAndSoundsEntryTag.includePublicGroups
|
||||
case .includeChannels:
|
||||
@ -511,12 +503,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .unreadCountStyle(lhsTheme, lhsText, lhsValue):
|
||||
if case let .unreadCountStyle(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .includePublicGroups(lhsTheme, lhsText, lhsValue):
|
||||
if case let .includePublicGroups(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
|
||||
return true
|
||||
@ -690,10 +676,6 @@ private enum NotificationsAndSoundsEntry: ItemListNodeEntry {
|
||||
})
|
||||
case let .badgeHeader(theme, text):
|
||||
return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section)
|
||||
case let .unreadCountStyle(theme, text, value):
|
||||
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateTotalUnreadCountStyle(updatedValue)
|
||||
}, tag: self.tag)
|
||||
case let .includePublicGroups(theme, text, value):
|
||||
return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .blocks, updated: { updatedValue in
|
||||
arguments.updateIncludeTag(.publicGroups, updatedValue)
|
||||
@ -797,7 +779,6 @@ private func notificationsAndSoundsEntries(authorizationStatus: AccessType, warn
|
||||
entries.append(.displayNamesOnLockscreenInfo(presentationData.theme, presentationData.strings.Notifications_DisplayNamesOnLockScreenInfoWithLink))
|
||||
|
||||
entries.append(.badgeHeader(presentationData.theme, presentationData.strings.Notifications_Badge.uppercased()))
|
||||
entries.append(.unreadCountStyle(presentationData.theme, presentationData.strings.Notifications_Badge_IncludeMutedChats, inAppSettings.totalUnreadCountDisplayStyle == .raw))
|
||||
entries.append(.includePublicGroups(presentationData.theme, presentationData.strings.Notifications_Badge_IncludePublicGroups, inAppSettings.totalUnreadCountIncludeTags.contains(.publicGroups)))
|
||||
entries.append(.includeChannels(presentationData.theme, presentationData.strings.Notifications_Badge_IncludeChannels, inAppSettings.totalUnreadCountIncludeTags.contains(.channels)))
|
||||
entries.append(.unreadCountCategory(presentationData.theme, presentationData.strings.Notifications_Badge_CountUnreadMessages, inAppSettings.totalUnreadCountDisplayCategory == .messages))
|
||||
@ -928,12 +909,6 @@ public func notificationsAndSoundsController(context: AccountContext, exceptions
|
||||
settings.displayNameOnLockscreen = value
|
||||
return settings
|
||||
}).start()
|
||||
}, updateTotalUnreadCountStyle: { value in
|
||||
let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
settings.totalUnreadCountDisplayStyle = value ? .raw : .filtered
|
||||
return settings
|
||||
}).start()
|
||||
}, updateIncludeTag: { tag, value in
|
||||
let _ = updateInAppNotificationSettingsInteractively(accountManager: context.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
|
@ -410,9 +410,6 @@ private func notificationSearchableItems(context: AccountContext, settings: Glob
|
||||
SettingsSearchableItem(id: .notifications(16), title: strings.Notifications_DisplayNamesOnLockScreen, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_DisplayNamesOnLockScreen), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds], present: { context, _, present in
|
||||
presentNotificationSettings(context, present, .displayNamesOnLockscreen)
|
||||
}),
|
||||
SettingsSearchableItem(id: .notifications(17), title: strings.Notifications_Badge_IncludeMutedChats, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedChats), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge], present: { context, _, present in
|
||||
presentNotificationSettings(context, present, .unreadCountStyle)
|
||||
}),
|
||||
SettingsSearchableItem(id: .notifications(18), title: strings.Notifications_Badge_IncludePublicGroups, alternate: synonyms(strings.SettingsSearch_Synonyms_Notifications_BadgeIncludeMutedPublicGroups), icon: icon, breadcrumbs: [strings.Settings_NotificationsAndSounds, strings.Notifications_Badge], present: { context, _, present in
|
||||
presentNotificationSettings(context, present, .includePublicGroups)
|
||||
}),
|
||||
|
@ -221,7 +221,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
private var droppedCall = false
|
||||
private var dropCallKitCallTimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
|
||||
init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), initialState: CallSession?, internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, currentNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>) {
|
||||
self.account = account
|
||||
self.audioSession = audioSession
|
||||
self.callSessionManager = callSessionManager
|
||||
@ -236,7 +236,15 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
self.ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: currentNetworkType, updatedNetworkType: updatedNetworkType, serializedData: serializedData, dataSaving: dataSaving, derivedState: derivedState)
|
||||
|
||||
var didReceiveAudioOutputs = false
|
||||
self.sessionStateDisposable = (callSessionManager.callState(internalId: internalId)
|
||||
|
||||
var callSessionState: Signal<CallSession, NoError> = .complete()
|
||||
if let initialState = initialState {
|
||||
callSessionState = .single(initialState)
|
||||
}
|
||||
callSessionState = callSessionState
|
||||
|> then(callSessionManager.callState(internalId: internalId))
|
||||
|
||||
self.sessionStateDisposable = (callSessionState
|
||||
|> deliverOnMainQueue).start(next: { [weak self] sessionState in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateSessionState(sessionState: sessionState, callContextState: strongSelf.callContextState, reception: strongSelf.reception, audioSessionControl: strongSelf.audioSessionControl)
|
||||
@ -402,7 +410,7 @@ public final class PresentationCallImpl: PresentationCall {
|
||||
switch sessionState.state {
|
||||
case .ringing:
|
||||
presentationState = .ringing
|
||||
if let _ = audioSessionControl, previous == nil || previousControl == nil {
|
||||
if previous == nil || previousControl == nil {
|
||||
if !self.reportedIncomingCall {
|
||||
self.reportedIncomingCall = true
|
||||
self.callKitIntegration?.reportIncomingCall(uuid: self.internalId, handle: "\(self.peerId.id)", displayTitle: self.peer?.debugDisplayTitle ?? "Unknown", completion: { [weak self] error in
|
||||
|
@ -223,6 +223,51 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
self.callSettingsDisposable?.dispose()
|
||||
}
|
||||
|
||||
public func injectRingingStateSynchronously(account: Account, ringingState: CallSessionRingingState, callSession: CallSession) {
|
||||
if self.currentCall != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var data: (PreferencesView, AccountSharedDataView, Peer?)?
|
||||
let _ = combineLatest(
|
||||
account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState])
|
||||
|> take(1),
|
||||
accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings])
|
||||
|> take(1),
|
||||
account.postbox.transaction { transaction -> Peer? in
|
||||
return transaction.getPeer(ringingState.peerId)
|
||||
}
|
||||
).start(next: { preferences, sharedData, peer in
|
||||
data = (preferences, sharedData, peer)
|
||||
semaphore.signal()
|
||||
})
|
||||
semaphore.wait()
|
||||
|
||||
if let (preferences, sharedData, maybePeer) = data, let peer = maybePeer {
|
||||
let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue
|
||||
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
|
||||
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
|
||||
|
||||
let enableCallKit = true
|
||||
|
||||
let call = PresentationCallImpl(account: account, audioSession: self.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: self.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: self.getDeviceAccessData, initialState: callSession, internalId: ringingState.id, peerId: ringingState.peerId, isOutgoing: false, peer: peer, proxyServer: self.proxyServer, currentNetworkType: .none, updatedNetworkType: account.networkType)
|
||||
self.currentCall = call
|
||||
self.currentCallPromise.set(.single(call))
|
||||
self.hasActiveCallsPromise.set(true)
|
||||
self.removeCurrentCallDisposable.set((call.canBeRemoved
|
||||
|> deliverOnMainQueue).start(next: { [weak self, weak call] value in
|
||||
if value, let strongSelf = self, let call = call {
|
||||
if strongSelf.currentCall === call {
|
||||
strongSelf.currentCall = nil
|
||||
strongSelf.currentCallPromise.set(.single(nil))
|
||||
strongSelf.hasActiveCallsPromise.set(false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) {
|
||||
if let firstState = ringingStates.first {
|
||||
if self.currentCall == nil {
|
||||
@ -236,7 +281,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
|
||||
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
|
||||
|
||||
let call = PresentationCallImpl(account: firstState.0, audioSession: strongSelf.audioSession, callSessionManager: firstState.0.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, peer: firstState.1, proxyServer: strongSelf.proxyServer, currentNetworkType: firstState.4, updatedNetworkType: firstState.0.networkType)
|
||||
let call = PresentationCallImpl(account: firstState.0, audioSession: strongSelf.audioSession, callSessionManager: firstState.0.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, peer: firstState.1, proxyServer: strongSelf.proxyServer, currentNetworkType: firstState.4, updatedNetworkType: firstState.0.networkType)
|
||||
strongSelf.currentCall = call
|
||||
strongSelf.currentCallPromise.set(.single(call))
|
||||
strongSelf.hasActiveCallsPromise.set(true)
|
||||
@ -366,7 +411,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default
|
||||
let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings
|
||||
|
||||
let call = PresentationCallImpl(account: account, audioSession: strongSelf.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: account.networkType)
|
||||
let call = PresentationCallImpl(account: account, audioSession: strongSelf.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, currentNetworkType: currentNetworkType, updatedNetworkType: account.networkType)
|
||||
strongSelf.currentCall = call
|
||||
strongSelf.currentCallPromise.set(.single(call))
|
||||
strongSelf.hasActiveCallsPromise.set(true)
|
||||
|
@ -640,7 +640,7 @@ public final class AccountStateManager {
|
||||
}
|
||||
if !events.updatedCalls.isEmpty {
|
||||
for call in events.updatedCalls {
|
||||
strongSelf.callSessionManager.updateSession(call)
|
||||
strongSelf.callSessionManager.updateSession(call, completion: { _ in })
|
||||
}
|
||||
}
|
||||
if !events.isContactUpdates.isEmpty {
|
||||
@ -989,6 +989,38 @@ public final class AccountStateManager {
|
||||
subscriber(updatedPeersNearby)
|
||||
}
|
||||
}
|
||||
|
||||
public func processIncomingCallUpdate(data: Data, completion: @escaping ((CallSessionRingingState, CallSession)?) -> Void) {
|
||||
var rawData = data
|
||||
let reader = BufferReader(Buffer(data: data))
|
||||
if let signature = reader.readInt32(), signature == 0x3072cfa1 {
|
||||
if let compressedData = parseBytes(reader) {
|
||||
if let decompressedData = MTGzip.decompress(compressedData.makeData()) {
|
||||
rawData = decompressedData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let updates = Api.parse(Buffer(data: rawData)) as? Api.Updates {
|
||||
switch updates {
|
||||
case let .updates(updates, users, chats, date, seq):
|
||||
for update in updates {
|
||||
switch update {
|
||||
case let .updatePhoneCall(phoneCall):
|
||||
self.callSessionManager.updateSession(phoneCall, completion: { result in
|
||||
completion(result)
|
||||
})
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func messagesForNotification(transaction: Transaction, id: MessageId, alwaysReturnMessage: Bool) -> (messages: [Message], notify: Bool, sound: PeerMessageSound, displayContents: Bool) {
|
||||
|
@ -348,9 +348,9 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
}
|
||||
|
||||
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data) {
|
||||
private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data) -> CallSessionInternalId? {
|
||||
if self.contextIdByStableId[stableId] != nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
let bBytes = malloc(256)!
|
||||
@ -365,6 +365,9 @@ private final class CallSessionManagerContext {
|
||||
self.contextIdByStableId[stableId] = internalId
|
||||
self.contextUpdated(internalId: internalId)
|
||||
self.ringingStatesUpdated()
|
||||
return internalId
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -497,7 +500,9 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
}
|
||||
|
||||
func updateSession(_ call: Api.PhoneCall) {
|
||||
func updateSession(_ call: Api.PhoneCall, completion: @escaping ((CallSessionRingingState, CallSession)?) -> Void) {
|
||||
var resultRingingState: (CallSessionRingingState, CallSession)?
|
||||
|
||||
switch call {
|
||||
case .phoneCallEmpty:
|
||||
break
|
||||
@ -532,7 +537,7 @@ private final class CallSessionManagerContext {
|
||||
context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in
|
||||
if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state {
|
||||
if let updatedCall = updatedCall {
|
||||
strongSelf.updateSession(updatedCall)
|
||||
strongSelf.updateSession(updatedCall, completion: { _ in })
|
||||
} else {
|
||||
strongSelf.drop(internalId: internalId, reason: .disconnect)
|
||||
}
|
||||
@ -634,7 +639,22 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, _):
|
||||
if self.contextIdByStableId[id] == nil {
|
||||
self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData())
|
||||
let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: adminId), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData())
|
||||
if let internalId = internalId {
|
||||
var resultRingingStateValue: CallSessionRingingState?
|
||||
for ringingState in self.ringingStatesValue() {
|
||||
if ringingState.id == internalId {
|
||||
resultRingingStateValue = ringingState
|
||||
break
|
||||
}
|
||||
}
|
||||
if let context = self.contexts[internalId] {
|
||||
let callSession = CallSession(id: internalId, isOutgoing: context.isOutgoing, state: CallSessionState(context))
|
||||
if let resultRingingStateValue = resultRingingStateValue {
|
||||
resultRingingState = (resultRingingStateValue, callSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate):
|
||||
if let internalId = self.contextIdByStableId[id] {
|
||||
@ -653,6 +673,8 @@ private final class CallSessionManagerContext {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
completion(resultRingingState)
|
||||
}
|
||||
|
||||
private func makeSessionEncryptionKey(config: SecretChatEncryptionConfig, gAHash: Data, b: Data, gA: Data) -> (key: Data, keyId: Int64, keyVisualHash: Data)? {
|
||||
@ -744,9 +766,9 @@ public final class CallSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
func updateSession(_ call: Api.PhoneCall) {
|
||||
func updateSession(_ call: Api.PhoneCall, completion: @escaping ((CallSessionRingingState, CallSession)?) -> Void) {
|
||||
self.withContext { context in
|
||||
context.updateSession(call)
|
||||
context.updateSession(call, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -981,38 +1003,38 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update
|
||||
mappedReason = .phoneCallDiscardReasonMissed
|
||||
}
|
||||
return network.request(Api.functions.phone.discardCall(flags: 0, peer: Api.InputPhoneCall.inputPhoneCall(id: stableId, accessHash: accessHash), duration: duration, reason: mappedReason, connectionId: 0))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<(Bool, Bool), NoError> in
|
||||
var reportRating: Bool = false
|
||||
var sendDebugLogs: Bool = false
|
||||
if let updates = updates {
|
||||
switch updates {
|
||||
case .updates(let updates, _, _, _, _):
|
||||
for update in updates {
|
||||
switch update {
|
||||
case .updatePhoneCall(let phoneCall):
|
||||
switch phoneCall {
|
||||
case.phoneCallDiscarded(let values):
|
||||
reportRating = (values.flags & (1 << 2)) != 0
|
||||
sendDebugLogs = (values.flags & (1 << 3)) != 0
|
||||
default:
|
||||
break
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<(Bool, Bool), NoError> in
|
||||
var reportRating: Bool = false
|
||||
var sendDebugLogs: Bool = false
|
||||
if let updates = updates {
|
||||
switch updates {
|
||||
case .updates(let updates, _, _, _, _):
|
||||
for update in updates {
|
||||
switch update {
|
||||
case .updatePhoneCall(let phoneCall):
|
||||
switch phoneCall {
|
||||
case.phoneCallDiscarded(let values):
|
||||
reportRating = (values.flags & (1 << 2)) != 0
|
||||
sendDebugLogs = (values.flags & (1 << 3)) != 0
|
||||
default:
|
||||
break
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
addUpdates(updates)
|
||||
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return .single((reportRating, sendDebugLogs))
|
||||
|
||||
addUpdates(updates)
|
||||
|
||||
}
|
||||
return .single((reportRating, sendDebugLogs))
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -48,9 +48,6 @@ private func encodeText(_ string: String, _ key: Int) -> String {
|
||||
return result
|
||||
}
|
||||
|
||||
private let statusBarRootViewClass: AnyClass = NSClassFromString("UIStatusBar")!
|
||||
private let statusBarPlaceholderClass: AnyClass? = NSClassFromString("UIStatusBar_Placeholder")
|
||||
private let cutoutStatusBarForegroundClass: AnyClass? = NSClassFromString("_UIStatusBar")
|
||||
private let keyboardViewClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuIptuWjfx", -1))!
|
||||
private let keyboardViewContainerClass: AnyClass? = NSClassFromString(encodeText("VJJoqvuTfuDpoubjofsWjfx", -1))!
|
||||
|
||||
@ -93,33 +90,6 @@ private class ApplicationStatusBarHost: StatusBarHost {
|
||||
self.application.setStatusBarHidden(value, with: animated ? .fade : .none)
|
||||
}
|
||||
|
||||
var statusBarWindow: UIView? {
|
||||
return self.application.value(forKey: "statusBarWindow") as? UIView
|
||||
}
|
||||
|
||||
var statusBarView: UIView? {
|
||||
guard let containerView = self.statusBarWindow?.subviews.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if containerView.isKind(of: statusBarRootViewClass) {
|
||||
return containerView
|
||||
}
|
||||
if let statusBarPlaceholderClass = statusBarPlaceholderClass {
|
||||
if containerView.isKind(of: statusBarPlaceholderClass) {
|
||||
return containerView
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for subview in containerView.subviews {
|
||||
if let cutoutStatusBarForegroundClass = cutoutStatusBarForegroundClass, subview.isKind(of: cutoutStatusBarForegroundClass) {
|
||||
return subview
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var keyboardWindow: UIWindow? {
|
||||
guard let keyboardWindowClass = keyboardWindowClass else {
|
||||
return nil
|
||||
@ -149,10 +119,6 @@ private class ApplicationStatusBarHost: StatusBarHost {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var handleVolumeControl: Signal<Bool, NoError> {
|
||||
return MediaManagerImpl.globalAudioSession.isPlaybackActive()
|
||||
}
|
||||
}
|
||||
|
||||
private func legacyDocumentsPath() -> String {
|
||||
@ -1460,16 +1426,84 @@ final class SharedApplicationContext {
|
||||
|
||||
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
|
||||
if #available(iOS 9.0, *) {
|
||||
let _ = (self.sharedContextPromise.get()
|
||||
guard var encryptedPayload = payload.dictionaryPayload["p"] as? String else {
|
||||
return
|
||||
}
|
||||
encryptedPayload = encryptedPayload.replacingOccurrences(of: "-", with: "+")
|
||||
encryptedPayload = encryptedPayload.replacingOccurrences(of: "_", with: "/")
|
||||
while encryptedPayload.count % 4 != 0 {
|
||||
encryptedPayload.append("=")
|
||||
}
|
||||
guard let data = Data(base64Encoded: encryptedPayload) else {
|
||||
return
|
||||
}
|
||||
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var accountAndDecryptedPayload: (Account, Data)?
|
||||
|
||||
var sharedApplicationContextValue: SharedApplicationContext?
|
||||
|
||||
let accountsAndKeys = self.sharedContextPromise.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
|
||||
|> mapToSignal { sharedApplicationContext -> Signal<[(Account, MasterNotificationKey)], NoError> in
|
||||
sharedApplicationContextValue = sharedApplicationContext
|
||||
|
||||
return sharedApplicationContext.sharedContext.activeAccounts
|
||||
|> take(1)
|
||||
|> mapToSignal { activeAccounts -> Signal<[(Account, MasterNotificationKey)], NoError> in
|
||||
return combineLatest(activeAccounts.accounts.map { account -> Signal<(Account, MasterNotificationKey), NoError> in
|
||||
return masterNotificationsKey(account: account.1, ignoreDisabled: true)
|
||||
|> map { key -> (Account, MasterNotificationKey) in
|
||||
return (account.1, key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let _ = accountsAndKeys.start(next: { accountsAndKeys in
|
||||
for (account, key) in accountsAndKeys {
|
||||
if let decryptedData = decryptedNotificationPayload(key: key, data: data) {
|
||||
accountAndDecryptedPayload = (account, decryptedData)
|
||||
break
|
||||
}
|
||||
}
|
||||
semaphore.signal()
|
||||
})
|
||||
semaphore.wait()
|
||||
|
||||
if let sharedApplicationContextValue = sharedApplicationContextValue, let (account, decryptedData) = accountAndDecryptedPayload {
|
||||
if let decryptedDict = (try? JSONSerialization.jsonObject(with: decryptedData, options: [])) as? [AnyHashable: Any] {
|
||||
if var updateString = decryptedDict["updates"] as? String {
|
||||
updateString = updateString.replacingOccurrences(of: "-", with: "+")
|
||||
updateString = updateString.replacingOccurrences(of: "_", with: "/")
|
||||
while updateString.count % 4 != 0 {
|
||||
updateString.append("=")
|
||||
}
|
||||
if let updateData = Data(base64Encoded: updateString) {
|
||||
var result: (CallSessionRingingState, CallSession)?
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
account.stateManager.processIncomingCallUpdate(data: updateData, completion: { ringingState in
|
||||
result = ringingState
|
||||
semaphore.signal()
|
||||
})
|
||||
semaphore.wait()
|
||||
|
||||
if let (ringingState, callSession) = result {
|
||||
(sharedApplicationContextValue.sharedContext.callManager as? PresentationCallManagerImpl)?.injectRingingStateSynchronously(account: account, ringingState: ringingState, callSession: callSession)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*.start(next: { sharedApplicationContext in
|
||||
sharedApplicationContext.wakeupManager.allowBackgroundTimeExtension(timeout: 4.0)
|
||||
|
||||
if case PKPushType.voIP = type {
|
||||
Logger.shared.log("App \(self.episodeId)", "pushRegistry payload: \(payload.dictionaryPayload)")
|
||||
sharedApplicationContext.notificationManager.addNotification(payload.dictionaryPayload)
|
||||
}
|
||||
})
|
||||
})*/
|
||||
}
|
||||
}
|
||||
|
||||
|
Binary file not shown.
@ -662,7 +662,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
let settings = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.inAppNotificationSettings])
|
||||
|> map { sharedData -> (allAccounts: Bool, includeMuted: Bool) in
|
||||
let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings
|
||||
return (settings.displayNotificationsFromAllAccounts, settings.totalUnreadCountDisplayStyle == .raw)
|
||||
return (settings.displayNotificationsFromAllAccounts, false)
|
||||
}
|
||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||
if lhs.allAccounts != rhs.allAccounts {
|
||||
|
@ -4,14 +4,11 @@ import SwiftSignalKit
|
||||
|
||||
public enum TotalUnreadCountDisplayStyle: Int32 {
|
||||
case filtered = 0
|
||||
case raw = 1
|
||||
|
||||
var category: ChatListTotalUnreadStateCategory {
|
||||
switch self {
|
||||
case .filtered:
|
||||
return .filtered
|
||||
case .raw:
|
||||
return .raw
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -41,7 +38,7 @@ public struct InAppNotificationSettings: PreferencesEntry, Equatable {
|
||||
public var displayNotificationsFromAllAccounts: Bool
|
||||
|
||||
public static var defaultSettings: InAppNotificationSettings {
|
||||
return InAppNotificationSettings(playSounds: true, vibrate: false, displayPreviews: true, totalUnreadCountDisplayStyle: .raw, totalUnreadCountDisplayCategory: .messages, totalUnreadCountIncludeTags: [.regularChatsAndPrivateGroups], displayNameOnLockscreen: true, displayNotificationsFromAllAccounts: true)
|
||||
return InAppNotificationSettings(playSounds: true, vibrate: false, displayPreviews: true, totalUnreadCountDisplayStyle: .filtered, totalUnreadCountDisplayCategory: .messages, totalUnreadCountIncludeTags: [.regularChatsAndPrivateGroups], displayNameOnLockscreen: true, displayNotificationsFromAllAccounts: true)
|
||||
}
|
||||
|
||||
public init(playSounds: Bool, vibrate: Bool, displayPreviews: Bool, totalUnreadCountDisplayStyle: TotalUnreadCountDisplayStyle, totalUnreadCountDisplayCategory: TotalUnreadCountDisplayCategory, totalUnreadCountIncludeTags: PeerSummaryCounterTags, displayNameOnLockscreen: Bool, displayNotificationsFromAllAccounts: Bool) {
|
||||
@ -59,7 +56,7 @@ public struct InAppNotificationSettings: PreferencesEntry, Equatable {
|
||||
self.playSounds = decoder.decodeInt32ForKey("s", orElse: 0) != 0
|
||||
self.vibrate = decoder.decodeInt32ForKey("v", orElse: 0) != 0
|
||||
self.displayPreviews = decoder.decodeInt32ForKey("p", orElse: 0) != 0
|
||||
self.totalUnreadCountDisplayStyle = TotalUnreadCountDisplayStyle(rawValue: decoder.decodeInt32ForKey("tds", orElse: 1)) ?? .raw
|
||||
self.totalUnreadCountDisplayStyle = TotalUnreadCountDisplayStyle(rawValue: decoder.decodeInt32ForKey("cds", orElse: 0)) ?? .filtered
|
||||
self.totalUnreadCountDisplayCategory = TotalUnreadCountDisplayCategory(rawValue: decoder.decodeInt32ForKey("totalUnreadCountDisplayCategory", orElse: 1)) ?? .messages
|
||||
if let value = decoder.decodeOptionalInt32ForKey("totalUnreadCountIncludeTags") {
|
||||
self.totalUnreadCountIncludeTags = PeerSummaryCounterTags(rawValue: value)
|
||||
@ -74,7 +71,7 @@ public struct InAppNotificationSettings: PreferencesEntry, Equatable {
|
||||
encoder.encodeInt32(self.playSounds ? 1 : 0, forKey: "s")
|
||||
encoder.encodeInt32(self.vibrate ? 1 : 0, forKey: "v")
|
||||
encoder.encodeInt32(self.displayPreviews ? 1 : 0, forKey: "p")
|
||||
encoder.encodeInt32(self.totalUnreadCountDisplayStyle.rawValue, forKey: "tds")
|
||||
encoder.encodeInt32(self.totalUnreadCountDisplayStyle.rawValue, forKey: "cds")
|
||||
encoder.encodeInt32(self.totalUnreadCountDisplayCategory.rawValue, forKey: "totalUnreadCountDisplayCategory")
|
||||
encoder.encodeInt32(self.totalUnreadCountIncludeTags.rawValue, forKey: "totalUnreadCountIncludeTags")
|
||||
encoder.encodeInt32(self.displayNameOnLockscreen ? 1 : 0, forKey: "displayNameOnLockscreen")
|
||||
|
@ -15,8 +15,6 @@ public func renderedTotalUnreadCount(inAppNotificationSettings: InAppNotificatio
|
||||
public func renderedTotalUnreadCount(inAppSettings: InAppNotificationSettings, totalUnreadState: ChatListTotalUnreadState) -> (Int32, RenderedTotalUnreadCountType) {
|
||||
let type: RenderedTotalUnreadCountType
|
||||
switch inAppSettings.totalUnreadCountDisplayStyle {
|
||||
case .raw:
|
||||
type = .raw
|
||||
case .filtered:
|
||||
type = .filtered
|
||||
}
|
||||
@ -42,8 +40,6 @@ public func renderedTotalUnreadCount(accountManager: AccountManager, postbox: Po
|
||||
}
|
||||
let type: RenderedTotalUnreadCountType
|
||||
switch inAppSettings.totalUnreadCountDisplayStyle {
|
||||
case .raw:
|
||||
type = .raw
|
||||
case .filtered:
|
||||
type = .filtered
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface TON : NSObject
|
||||
|
||||
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests;
|
||||
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests syncStateUpdated:(void (^)(float))syncStateUpdated;
|
||||
|
||||
- (SSignal *)updateConfig:(NSString *)config blockchainName:(NSString *)blockchainName;
|
||||
|
||||
|
@ -242,7 +242,7 @@ typedef enum {
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests {
|
||||
- (instancetype)initWithKeystoreDirectory:(NSString *)keystoreDirectory config:(NSString *)config blockchainName:(NSString *)blockchainName performExternalRequest:(void (^)(TONExternalRequest * _Nonnull))performExternalRequest enableExternalRequests:(bool)enableExternalRequests syncStateUpdated:(void (^)(float))syncStateUpdated {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_queue = [SQueue mainQueue];
|
||||
@ -291,7 +291,23 @@ typedef enum {
|
||||
}]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}/* else if (response.object->get_id() == tonlib_api::updateSyncState::ID) {
|
||||
auto result = tonlib_api::move_object_as<tonlib_api::updateSyncState>(response.object);
|
||||
if (result.sync_state_->get_id() == tonlib_api::syncStateInProgress::ID) {
|
||||
auto syncStateInProgress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(result.sync_state_);
|
||||
int32_t currentDelta = syncStateInProgress->current_seqno_ - syncStateInProgress->from_seqno_;
|
||||
int32_t fullDelta = syncStateInProgress->to_seqno_ - syncStateInProgress->from_seqno_;
|
||||
if (currentDelta > 0 && fullDelta > 0) {
|
||||
float progress = ((float)currentDelta) / ((float)fullDelta);
|
||||
syncStateUpdated(progress);
|
||||
} else {
|
||||
syncStateUpdated(0.0f);
|
||||
}
|
||||
} else if (result.sync_state_->get_id() == tonlib_api::syncStateDone::ID) {
|
||||
syncStateUpdated(1.0f);
|
||||
}
|
||||
return
|
||||
}*/
|
||||
NSNumber *requestId = @(response.id);
|
||||
[requestHandlersLock lock];
|
||||
TONRequestHandler *handler = requestHandlers[requestId];
|
||||
|
@ -55,6 +55,7 @@ private final class TonInstanceImpl {
|
||||
private let blockchainName: String
|
||||
private let proxy: TonNetworkProxy?
|
||||
private var instance: TON?
|
||||
private let syncStateProgress = ValuePromise<Float>(0.0)
|
||||
|
||||
init(queue: Queue, basePath: String, config: String, blockchainName: String, proxy: TonNetworkProxy?) {
|
||||
self.queue = queue
|
||||
@ -70,6 +71,7 @@ private final class TonInstanceImpl {
|
||||
instance = current
|
||||
} else {
|
||||
let proxy = self.proxy
|
||||
let syncStateProgress = self.syncStateProgress
|
||||
instance = TON(keystoreDirectory: self.basePath + "/ton-keystore", config: self.config, blockchainName: self.blockchainName, performExternalRequest: { request in
|
||||
if let proxy = proxy {
|
||||
let _ = proxy.request(data: request.data, timeout: 20.0, completion: { result in
|
||||
@ -83,7 +85,9 @@ private final class TonInstanceImpl {
|
||||
} else {
|
||||
request.onResult(nil, "NETWORK_DISABLED")
|
||||
}
|
||||
}, enableExternalRequests: proxy != nil)
|
||||
}, enableExternalRequests: proxy != nil, syncStateUpdated: { progress in
|
||||
syncStateProgress.set(progress)
|
||||
})
|
||||
self.instance = instance
|
||||
}
|
||||
f(instance)
|
||||
@ -1019,8 +1023,39 @@ private func getWalletTransactionsOnce(address: String, previousId: WalletTransa
|
||||
}
|
||||
}
|
||||
|
||||
public enum CustomWalletConfigurationDecodingError: Error {
|
||||
case generic
|
||||
}
|
||||
|
||||
public enum CustomWalletConfiguration: Codable {
|
||||
enum Key: CodingKey {
|
||||
case string
|
||||
}
|
||||
|
||||
case string(String)
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: Key.self)
|
||||
if let string = try? container.decode(String.self, forKey: .string) {
|
||||
self = .string(string)
|
||||
} else {
|
||||
throw CustomWalletConfigurationDecodingError.generic
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = try encoder.container(keyedBy: Key.self)
|
||||
switch self {
|
||||
case let .string(string):
|
||||
try container.encode(string, forKey: .string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public protocol WalletStorageInterface {
|
||||
func watchWalletRecords() -> Signal<[WalletStateRecord], NoError>
|
||||
func getWalletRecords() -> Signal<[WalletStateRecord], NoError>
|
||||
func updateWalletRecords(_ f: @escaping ([WalletStateRecord]) -> [WalletStateRecord]) -> Signal<[WalletStateRecord], NoError>
|
||||
func customWalletConfiguration() -> Signal<CustomWalletConfiguration?, NoError>
|
||||
func updateCustomWalletConfiguration(_ value: CustomWalletConfiguration?)
|
||||
}
|
||||
|
Binary file not shown.
187
submodules/WalletUI/Sources/WalletConfgurationScreen.swift
Normal file
187
submodules/WalletUI/Sources/WalletConfgurationScreen.swift
Normal file
@ -0,0 +1,187 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AppBundle
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import OverlayStatusController
|
||||
import WalletCore
|
||||
|
||||
private final class WalletConfigurationScreenArguments {
|
||||
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void
|
||||
let dismissInput: () -> Void
|
||||
|
||||
init(updateState: @escaping ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void, dismissInput: @escaping () -> Void) {
|
||||
self.updateState = updateState
|
||||
self.dismissInput = dismissInput
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletConfigurationScreenSection: Int32 {
|
||||
case configString
|
||||
}
|
||||
|
||||
private enum WalletConfigurationScreenEntryTag: ItemListItemTag {
|
||||
case configStringText
|
||||
|
||||
func isEqual(to other: ItemListItemTag) -> Bool {
|
||||
if let other = other as? WalletConfigurationScreenEntryTag {
|
||||
return self == other
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletConfigurationScreenEntry: ItemListNodeEntry, Equatable {
|
||||
case configString(WalletTheme, String, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .configString:
|
||||
return WalletConfigurationScreenSection.configString.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .configString:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
static func <(lhs: WalletConfigurationScreenEntry, rhs: WalletConfigurationScreenEntry) -> Bool {
|
||||
return lhs.stableId < rhs.stableId
|
||||
}
|
||||
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! WalletConfigurationScreenArguments
|
||||
switch self {
|
||||
case let .configString(theme, placeholder, text):
|
||||
return ItemListMultilineInputItem(theme: theme, text: text, placeholder: placeholder, maxLength: nil, sectionId: self.section, style: .blocks, capitalization: false, autocorrection: false, returnKeyType: .done, minimalHeight: nil, textUpdated: { value in
|
||||
arguments.updateState { state in
|
||||
var state = state
|
||||
state.configString = value
|
||||
return state
|
||||
}
|
||||
}, shouldUpdateText: { _ in
|
||||
return true
|
||||
}, processPaste: nil, updatedFocus: nil, tag: WalletConfigurationScreenEntryTag.configStringText, action: nil, inlineAction: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct WalletConfigurationScreenState: Equatable {
|
||||
var configString: String
|
||||
|
||||
var isEmpty: Bool {
|
||||
return self.configString.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
private func walletConfigurationScreenEntries(presentationData: WalletPresentationData, state: WalletConfigurationScreenState) -> [WalletConfigurationScreenEntry] {
|
||||
var entries: [WalletConfigurationScreenEntry] = []
|
||||
|
||||
entries.append(.configString(presentationData.theme, "", state.configString))
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
protocol WalletConfigurationScreen {
|
||||
}
|
||||
|
||||
private final class WalletConfigurationScreenImpl: ItemListController, WalletConfigurationScreen {
|
||||
override func preferredContentSizeForLayout(_ layout: ContainerViewLayout) -> CGSize? {
|
||||
return CGSize(width: layout.size.width, height: layout.size.height - 174.0)
|
||||
}
|
||||
}
|
||||
|
||||
func walletConfigurationScreen(context: WalletContext, currentConfiguration: CustomWalletConfiguration?) -> ViewController {
|
||||
var configString = ""
|
||||
if let currentConfiguration = currentConfiguration {
|
||||
switch currentConfiguration {
|
||||
case let .string(string):
|
||||
configString = string
|
||||
}
|
||||
}
|
||||
let initialState = WalletConfigurationScreenState(configString: configString)
|
||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||
let stateValue = Atomic(value: initialState)
|
||||
let updateState: ((WalletConfigurationScreenState) -> WalletConfigurationScreenState) -> Void = { f in
|
||||
statePromise.set(stateValue.modify { f($0) })
|
||||
}
|
||||
|
||||
var presentControllerImpl: ((ViewController, Any?) -> Void)?
|
||||
var pushImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var dismissInputImpl: (() -> Void)?
|
||||
var ensureItemVisibleImpl: ((WalletConfigurationScreenEntryTag, Bool) -> Void)?
|
||||
|
||||
weak var currentStatusController: ViewController?
|
||||
let arguments = WalletConfigurationScreenArguments(updateState: { f in
|
||||
updateState(f)
|
||||
}, dismissInput: {
|
||||
dismissInputImpl?()
|
||||
})
|
||||
|
||||
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let rightNavigationButton = ItemListNavigationButton(content: .text(presentationData.strings.Wallet_Configuration_Apply), style: .bold, enabled: !state.isEmpty, action: {
|
||||
let state = stateValue.with { $0 }
|
||||
let configuration: CustomWalletConfiguration?
|
||||
if state.configString.isEmpty {
|
||||
configuration = nil
|
||||
} else {
|
||||
configuration = .string(state.configString)
|
||||
}
|
||||
context.storage.updateCustomWalletConfiguration(configuration)
|
||||
dismissImpl?()
|
||||
})
|
||||
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_Configuration_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletConfigurationScreenEntries(presentationData: presentationData, state: state), style: .blocks, focusItemTag: nil, ensureVisibleItemTag: nil, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
||||
let controller = WalletConfigurationScreenImpl(theme: context.presentationData.theme, strings: context.presentationData.strings, updatedPresentationData: .single((context.presentationData.theme, context.presentationData.strings)), state: signal, tabBarItem: nil)
|
||||
controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
||||
controller.experimentalSnapScrollToItem = true
|
||||
controller.didAppear = { _ in
|
||||
}
|
||||
presentControllerImpl = { [weak controller] c, a in
|
||||
controller?.present(c, in: .window(.root), with: a)
|
||||
}
|
||||
pushImpl = { [weak controller] c in
|
||||
controller?.push(c)
|
||||
}
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
let _ = controller?.dismiss()
|
||||
}
|
||||
dismissInputImpl = { [weak controller] in
|
||||
controller?.view.endEditing(true)
|
||||
}
|
||||
ensureItemVisibleImpl = { [weak controller] targetTag, animated in
|
||||
controller?.afterLayout({
|
||||
guard let controller = controller else {
|
||||
return
|
||||
}
|
||||
var resultItemNode: ListViewItemNode?
|
||||
let state = stateValue.with({ $0 })
|
||||
let _ = controller.frameForItemNode({ itemNode in
|
||||
if let itemNode = itemNode as? ItemListItemNode {
|
||||
if let tag = itemNode.tag, tag.isEqual(to: targetTag) {
|
||||
resultItemNode = itemNode as? ListViewItemNode
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if let resultItemNode = resultItemNode {
|
||||
controller.ensureItemNodeVisible(resultItemNode, animated: animated)
|
||||
}
|
||||
})
|
||||
}
|
||||
return controller
|
||||
}
|
@ -13,6 +13,8 @@ public protocol WalletContext {
|
||||
var keychain: TonKeychain { get }
|
||||
var presentationData: WalletPresentationData { get }
|
||||
|
||||
var supportsCustomConfigurations: Bool { get }
|
||||
|
||||
var inForeground: Signal<Bool, NoError> { get }
|
||||
|
||||
func getServerSalt() -> Signal<Data, WalletContextGetServerSaltError>
|
||||
|
@ -9,27 +9,33 @@ import OverlayStatusController
|
||||
import WalletCore
|
||||
|
||||
private final class WalletSettingsControllerArguments {
|
||||
let openConfiguration: () -> Void
|
||||
let exportWallet: () -> Void
|
||||
let deleteWallet: () -> Void
|
||||
|
||||
init(exportWallet: @escaping () -> Void, deleteWallet: @escaping () -> Void) {
|
||||
init(openConfiguration: @escaping () -> Void, exportWallet: @escaping () -> Void, deleteWallet: @escaping () -> Void) {
|
||||
self.openConfiguration = openConfiguration
|
||||
self.exportWallet = exportWallet
|
||||
self.deleteWallet = deleteWallet
|
||||
}
|
||||
}
|
||||
|
||||
private enum WalletSettingsSection: Int32 {
|
||||
case configuration
|
||||
case exportWallet
|
||||
case deleteWallet
|
||||
}
|
||||
|
||||
private enum WalletSettingsEntry: ItemListNodeEntry {
|
||||
case configuration(WalletTheme, String)
|
||||
case exportWallet(WalletTheme, String)
|
||||
case deleteWallet(WalletTheme, String)
|
||||
case deleteWalletInfo(WalletTheme, String)
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .configuration:
|
||||
return WalletSettingsSection.configuration.rawValue
|
||||
case .exportWallet:
|
||||
return WalletSettingsSection.exportWallet.rawValue
|
||||
case .deleteWallet, .deleteWalletInfo:
|
||||
@ -39,12 +45,14 @@ private enum WalletSettingsEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int32 {
|
||||
switch self {
|
||||
case .exportWallet:
|
||||
case .configuration:
|
||||
return 0
|
||||
case .deleteWallet:
|
||||
case .exportWallet:
|
||||
return 1
|
||||
case .deleteWalletInfo:
|
||||
case .deleteWallet:
|
||||
return 2
|
||||
case .deleteWalletInfo:
|
||||
return 3
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +63,10 @@ private enum WalletSettingsEntry: ItemListNodeEntry {
|
||||
func item(_ arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! WalletSettingsControllerArguments
|
||||
switch self {
|
||||
case let .configuration(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.openConfiguration()
|
||||
})
|
||||
case let .exportWallet(theme, text):
|
||||
return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
arguments.exportWallet()
|
||||
@ -72,14 +84,16 @@ private enum WalletSettingsEntry: ItemListNodeEntry {
|
||||
private struct WalletSettingsControllerState: Equatable {
|
||||
}
|
||||
|
||||
private func walletSettingsControllerEntries(presentationData: WalletPresentationData, state: WalletSettingsControllerState) -> [WalletSettingsEntry] {
|
||||
private func walletSettingsControllerEntries(presentationData: WalletPresentationData, state: WalletSettingsControllerState, supportsCustomConfigurations: Bool) -> [WalletSettingsEntry] {
|
||||
var entries: [WalletSettingsEntry] = []
|
||||
|
||||
entries.append(.exportWallet(presentationData.theme, "Export Wallet"))
|
||||
if supportsCustomConfigurations {
|
||||
entries.append(.configuration(presentationData.theme, presentationData.strings.Wallet_Settings_Configuration))
|
||||
}
|
||||
entries.append(.exportWallet(presentationData.theme, presentationData.strings.Wallet_Settings_BackupWallet))
|
||||
entries.append(.deleteWallet(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWallet))
|
||||
entries.append(.deleteWalletInfo(presentationData.theme, presentationData.strings.Wallet_Settings_DeleteWalletInfo))
|
||||
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
@ -96,7 +110,13 @@ public func walletSettingsController(context: WalletContext, walletInfo: WalletI
|
||||
|
||||
var replaceAllWalletControllersImpl: ((ViewController) -> Void)?
|
||||
|
||||
let arguments = WalletSettingsControllerArguments(exportWallet: {
|
||||
let arguments = WalletSettingsControllerArguments(openConfiguration: {
|
||||
let _ = (context.storage.customWalletConfiguration()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { configuration in
|
||||
pushControllerImpl?(walletConfigurationScreen(context: context, currentConfiguration: configuration))
|
||||
})
|
||||
}, exportWallet: {
|
||||
let presentationData = context.presentationData
|
||||
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: nil))
|
||||
presentControllerImpl?(controller, nil)
|
||||
@ -145,7 +165,7 @@ public func walletSettingsController(context: WalletContext, walletInfo: WalletI
|
||||
let signal = combineLatest(queue: .mainQueue(), .single(context.presentationData), statePromise.get())
|
||||
|> map { presentationData, state -> (ItemListControllerState, (ItemListNodeState, Any)) in
|
||||
let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Wallet_Settings_Title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Wallet_Navigation_Back), animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletSettingsControllerEntries(presentationData: presentationData, state: state), style: .blocks, animateChanges: false)
|
||||
let listState = ItemListNodeState(entries: walletSettingsControllerEntries(presentationData: presentationData, state: state, supportsCustomConfigurations: context.supportsCustomConfigurations), style: .blocks, animateChanges: false)
|
||||
|
||||
return (controllerState, (listState, arguments))
|
||||
}
|
||||
|
@ -199,228 +199,235 @@ public final class WalletStrings: Equatable {
|
||||
public var Wallet_Receive_ShareUrlInfo: String { return self._s[7]! }
|
||||
public var Wallet_RestoreFailed_Title: String { return self._s[8]! }
|
||||
public var Wallet_TransactionInfo_CopyAddress: String { return self._s[10]! }
|
||||
public var Wallet_Send_NetworkErrorTitle: String { return self._s[11]! }
|
||||
public var Wallet_Month_ShortJune: String { return self._s[12]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[13]! }
|
||||
public var Wallet_Created_Title: String { return self._s[14]! }
|
||||
public var Wallet_Info_YourBalance: String { return self._s[15]! }
|
||||
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[16]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[17]! }
|
||||
public var Wallet_Settings_BackupWallet: String { return self._s[11]! }
|
||||
public var Wallet_Send_NetworkErrorTitle: String { return self._s[12]! }
|
||||
public var Wallet_Month_ShortJune: String { return self._s[13]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeInfo: String { return self._s[14]! }
|
||||
public var Wallet_Created_Title: String { return self._s[15]! }
|
||||
public var Wallet_Info_YourBalance: String { return self._s[16]! }
|
||||
public var Wallet_TransactionInfo_CommentHeader: String { return self._s[17]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeHeader: String { return self._s[18]! }
|
||||
public func Wallet_Time_PreciseDate_m3(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[18]!, self._r[18]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[19]!, self._r[19]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_WordImport_IncorrectText: String { return self._s[19]! }
|
||||
public var Wallet_Month_GenJanuary: String { return self._s[20]! }
|
||||
public var Wallet_Receive_ShareAddress: String { return self._s[21]! }
|
||||
public var Wallet_WordImport_Title: String { return self._s[22]! }
|
||||
public var Wallet_TransactionInfo_Title: String { return self._s[23]! }
|
||||
public var Wallet_Words_NotDoneText: String { return self._s[25]! }
|
||||
public var Wallet_RestoreFailed_EnterWords: String { return self._s[26]! }
|
||||
public var Wallet_WordImport_Text: String { return self._s[27]! }
|
||||
public var Wallet_RestoreFailed_Text: String { return self._s[29]! }
|
||||
public var Wallet_TransactionInfo_NoAddress: String { return self._s[30]! }
|
||||
public var Wallet_Navigation_Back: String { return self._s[31]! }
|
||||
public var Wallet_Intro_Terms: String { return self._s[32]! }
|
||||
public var Wallet_WordImport_IncorrectText: String { return self._s[20]! }
|
||||
public var Wallet_Month_GenJanuary: String { return self._s[21]! }
|
||||
public var Wallet_Receive_ShareAddress: String { return self._s[22]! }
|
||||
public var Wallet_WordImport_Title: String { return self._s[23]! }
|
||||
public var Wallet_TransactionInfo_Title: String { return self._s[24]! }
|
||||
public var Wallet_Words_NotDoneText: String { return self._s[26]! }
|
||||
public var Wallet_RestoreFailed_EnterWords: String { return self._s[27]! }
|
||||
public var Wallet_WordImport_Text: String { return self._s[28]! }
|
||||
public var Wallet_RestoreFailed_Text: String { return self._s[30]! }
|
||||
public var Wallet_TransactionInfo_NoAddress: String { return self._s[31]! }
|
||||
public var Wallet_Navigation_Back: String { return self._s[32]! }
|
||||
public var Wallet_Intro_Terms: String { return self._s[33]! }
|
||||
public func Wallet_Send_Balance(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[33]!, self._r[33]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[34]!, self._r[34]!, [_0])
|
||||
}
|
||||
public func Wallet_Time_PreciseDate_m8(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[34]!, self._r[34]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[35]!, self._r[35]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[35]! }
|
||||
public var Wallet_TransactionInfo_AddressCopied: String { return self._s[36]! }
|
||||
public func Wallet_Info_TransactionDateHeaderYear(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[36]!, self._r[36]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[37]!, self._r[37]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_NetworkErrorText: String { return self._s[37]! }
|
||||
public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[38]! }
|
||||
public var Wallet_Intro_ImportExisting: String { return self._s[39]! }
|
||||
public var Wallet_Receive_CommentInfo: String { return self._s[40]! }
|
||||
public var Wallet_WordCheck_Continue: String { return self._s[41]! }
|
||||
public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[42]! }
|
||||
public var Wallet_Completed_Text: String { return self._s[43]! }
|
||||
public var Wallet_WordCheck_IncorrectHeader: String { return self._s[45]! }
|
||||
public var Wallet_Receive_Title: String { return self._s[46]! }
|
||||
public var Wallet_Info_WalletCreated: String { return self._s[47]! }
|
||||
public var Wallet_Navigation_Cancel: String { return self._s[48]! }
|
||||
public var Wallet_CreateInvoice_Title: String { return self._s[49]! }
|
||||
public var Wallet_Send_NetworkErrorText: String { return self._s[38]! }
|
||||
public var Wallet_VoiceOver_Editing_ClearText: String { return self._s[39]! }
|
||||
public var Wallet_Intro_ImportExisting: String { return self._s[40]! }
|
||||
public var Wallet_Receive_CommentInfo: String { return self._s[41]! }
|
||||
public var Wallet_WordCheck_Continue: String { return self._s[42]! }
|
||||
public var Wallet_Receive_InvoiceUrlCopied: String { return self._s[43]! }
|
||||
public var Wallet_Completed_Text: String { return self._s[44]! }
|
||||
public var Wallet_WordCheck_IncorrectHeader: String { return self._s[46]! }
|
||||
public var Wallet_Receive_Title: String { return self._s[47]! }
|
||||
public var Wallet_Info_WalletCreated: String { return self._s[48]! }
|
||||
public var Wallet_Navigation_Cancel: String { return self._s[49]! }
|
||||
public var Wallet_CreateInvoice_Title: String { return self._s[50]! }
|
||||
public func Wallet_WordCheck_Text(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[50]!, self._r[50]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[51]!, self._r[51]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_TransactionInfo_SenderHeader: String { return self._s[51]! }
|
||||
public var Wallet_TransactionInfo_SenderHeader: String { return self._s[52]! }
|
||||
public func Wallet_Time_PreciseDate_m4(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[52]!, self._r[52]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[53]!, self._r[53]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_GenAugust: String { return self._s[53]! }
|
||||
public var Wallet_Info_UnknownTransaction: String { return self._s[54]! }
|
||||
public var Wallet_Receive_CreateInvoice: String { return self._s[55]! }
|
||||
public var Wallet_Month_GenSeptember: String { return self._s[56]! }
|
||||
public var Wallet_Month_GenJuly: String { return self._s[57]! }
|
||||
public var Wallet_Receive_AddressHeader: String { return self._s[58]! }
|
||||
public var Wallet_Send_AmountText: String { return self._s[59]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[60]! }
|
||||
public var Wallet_Month_GenAugust: String { return self._s[54]! }
|
||||
public var Wallet_Info_UnknownTransaction: String { return self._s[55]! }
|
||||
public var Wallet_Receive_CreateInvoice: String { return self._s[56]! }
|
||||
public var Wallet_Month_GenSeptember: String { return self._s[57]! }
|
||||
public var Wallet_Month_GenJuly: String { return self._s[58]! }
|
||||
public var Wallet_Receive_AddressHeader: String { return self._s[59]! }
|
||||
public var Wallet_Send_AmountText: String { return self._s[60]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Text: String { return self._s[61]! }
|
||||
public func Wallet_Time_PreciseDate_m12(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[61]!, self._r[61]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[62]!, self._r[62]!, [_1, _2, _3])
|
||||
}
|
||||
public func Wallet_Updated_TodayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[62]!, self._r[62]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[63]!, self._r[63]!, [_0])
|
||||
}
|
||||
public var Wallet_Words_Title: String { return self._s[64]! }
|
||||
public var Wallet_Month_ShortMay: String { return self._s[65]! }
|
||||
public var Wallet_WordCheck_Title: String { return self._s[66]! }
|
||||
public var Wallet_Words_NotDoneResponse: String { return self._s[67]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[68]! }
|
||||
public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[69]! }
|
||||
public var Wallet_Configuration_Title: String { return self._s[65]! }
|
||||
public var Wallet_Words_Title: String { return self._s[66]! }
|
||||
public var Wallet_Month_ShortMay: String { return self._s[67]! }
|
||||
public var Wallet_WordCheck_Title: String { return self._s[68]! }
|
||||
public var Wallet_Words_NotDoneResponse: String { return self._s[69]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsText: String { return self._s[70]! }
|
||||
public var Wallet_Receive_CreateInvoiceInfo: String { return self._s[71]! }
|
||||
public func Wallet_Time_PreciseDate_m9(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[70]!, self._r[70]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[72]!, self._r[72]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Info_Address: String { return self._s[71]! }
|
||||
public var Wallet_Intro_CreateWallet: String { return self._s[72]! }
|
||||
public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[73]! }
|
||||
public var Wallet_Info_Address: String { return self._s[73]! }
|
||||
public var Wallet_Intro_CreateWallet: String { return self._s[74]! }
|
||||
public var Wallet_SecureStorageChanged_PasscodeText: String { return self._s[75]! }
|
||||
public func Wallet_SecureStorageReset_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[74]!, self._r[74]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[76]!, self._r[76]!, [_0])
|
||||
}
|
||||
public var Wallet_Send_SendAnyway: String { return self._s[75]! }
|
||||
public var Wallet_UnknownError: String { return self._s[76]! }
|
||||
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[77]! }
|
||||
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[79]! }
|
||||
public var Wallet_Words_NotDoneOk: String { return self._s[80]! }
|
||||
public var Wallet_Intro_Title: String { return self._s[81]! }
|
||||
public var Wallet_Info_Receive: String { return self._s[82]! }
|
||||
public var Wallet_Completed_ViewWallet: String { return self._s[83]! }
|
||||
public var Wallet_Month_ShortJuly: String { return self._s[84]! }
|
||||
public var Wallet_Month_ShortApril: String { return self._s[85]! }
|
||||
public var Wallet_Send_SendAnyway: String { return self._s[77]! }
|
||||
public var Wallet_UnknownError: String { return self._s[78]! }
|
||||
public var Wallet_SecureStorageChanged_ImportWallet: String { return self._s[79]! }
|
||||
public var Wallet_SecureStorageChanged_CreateWallet: String { return self._s[81]! }
|
||||
public var Wallet_Words_NotDoneOk: String { return self._s[82]! }
|
||||
public var Wallet_Intro_Title: String { return self._s[83]! }
|
||||
public var Wallet_Info_Receive: String { return self._s[84]! }
|
||||
public var Wallet_Completed_ViewWallet: String { return self._s[85]! }
|
||||
public var Wallet_Month_ShortJuly: String { return self._s[86]! }
|
||||
public var Wallet_Month_ShortApril: String { return self._s[87]! }
|
||||
public func Wallet_Info_TransactionDateHeader(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[86]!, self._r[86]!, [_1, _2])
|
||||
return formatWithArgumentRanges(self._s[88]!, self._r[88]!, [_1, _2])
|
||||
}
|
||||
public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[87]! }
|
||||
public var Wallet_Receive_ShareInvoiceUrl: String { return self._s[89]! }
|
||||
public func Wallet_Time_PreciseDate_m10(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[88]!, self._r[88]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[90]!, self._r[90]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_UninitializedText: String { return self._s[90]! }
|
||||
public var Wallet_Send_UninitializedText: String { return self._s[92]! }
|
||||
public func Wallet_Sent_Text(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[91]!, self._r[91]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[93]!, self._r[93]!, [_0])
|
||||
}
|
||||
public var Wallet_Month_GenNovember: String { return self._s[92]! }
|
||||
public var Wallet_Month_GenNovember: String { return self._s[94]! }
|
||||
public func Wallet_Time_PreciseDate_m5(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[93]!, self._r[93]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[95]!, self._r[95]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_GenApril: String { return self._s[94]! }
|
||||
public var Wallet_Month_ShortMarch: String { return self._s[95]! }
|
||||
public var Wallet_Month_GenFebruary: String { return self._s[96]! }
|
||||
public var Wallet_Qr_ScanCode: String { return self._s[97]! }
|
||||
public var Wallet_Receive_AddressCopied: String { return self._s[98]! }
|
||||
public var Wallet_Send_UninitializedTitle: String { return self._s[99]! }
|
||||
public var Wallet_Send_Send: String { return self._s[100]! }
|
||||
public var Wallet_Info_RefreshErrorTitle: String { return self._s[101]! }
|
||||
public var Wallet_Month_GenJune: String { return self._s[102]! }
|
||||
public var Wallet_Send_AddressHeader: String { return self._s[103]! }
|
||||
public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[104]! }
|
||||
public var Wallet_Send_Confirmation: String { return self._s[105]! }
|
||||
public var Wallet_Completed_Title: String { return self._s[106]! }
|
||||
public var Wallet_Alert_OK: String { return self._s[107]! }
|
||||
public var Wallet_Settings_DeleteWallet: String { return self._s[108]! }
|
||||
public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[109]! }
|
||||
public var Wallet_Month_ShortSeptember: String { return self._s[110]! }
|
||||
public var Wallet_Info_TransactionTo: String { return self._s[111]! }
|
||||
public var Wallet_Send_ConfirmationConfirm: String { return self._s[112]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[113]! }
|
||||
public var Wallet_Receive_AmountText: String { return self._s[114]! }
|
||||
public var Wallet_Receive_CopyAddress: String { return self._s[115]! }
|
||||
public var Wallet_Intro_Text: String { return self._s[117]! }
|
||||
public var Wallet_Month_GenApril: String { return self._s[96]! }
|
||||
public var Wallet_Month_ShortMarch: String { return self._s[97]! }
|
||||
public var Wallet_Month_GenFebruary: String { return self._s[98]! }
|
||||
public var Wallet_Qr_ScanCode: String { return self._s[99]! }
|
||||
public var Wallet_Receive_AddressCopied: String { return self._s[100]! }
|
||||
public var Wallet_Send_UninitializedTitle: String { return self._s[101]! }
|
||||
public var Wallet_Send_Send: String { return self._s[102]! }
|
||||
public var Wallet_Info_RefreshErrorTitle: String { return self._s[103]! }
|
||||
public var Wallet_Month_GenJune: String { return self._s[104]! }
|
||||
public var Wallet_Send_AddressHeader: String { return self._s[105]! }
|
||||
public var Wallet_SecureStorageReset_BiometryTouchId: String { return self._s[106]! }
|
||||
public var Wallet_Send_Confirmation: String { return self._s[107]! }
|
||||
public var Wallet_Completed_Title: String { return self._s[108]! }
|
||||
public var Wallet_Alert_OK: String { return self._s[109]! }
|
||||
public var Wallet_Settings_DeleteWallet: String { return self._s[110]! }
|
||||
public var Wallet_SecureStorageReset_PasscodeText: String { return self._s[111]! }
|
||||
public var Wallet_Month_ShortSeptember: String { return self._s[112]! }
|
||||
public var Wallet_Info_TransactionTo: String { return self._s[113]! }
|
||||
public var Wallet_Send_ConfirmationConfirm: String { return self._s[114]! }
|
||||
public var Wallet_TransactionInfo_OtherFeeInfo: String { return self._s[115]! }
|
||||
public var Wallet_Receive_AmountText: String { return self._s[116]! }
|
||||
public var Wallet_Receive_CopyAddress: String { return self._s[117]! }
|
||||
public var Wallet_Intro_Text: String { return self._s[119]! }
|
||||
public var Wallet_Configuration_Apply: String { return self._s[120]! }
|
||||
public func Wallet_SecureStorageChanged_BiometryText(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[118]!, self._r[118]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[121]!, self._r[121]!, [_0])
|
||||
}
|
||||
public var Wallet_TransactionInfo_FeeInfoURL: String { return self._s[119]! }
|
||||
public var Wallet_TransactionInfo_FeeInfoURL: String { return self._s[122]! }
|
||||
public func Wallet_Time_PreciseDate_m1(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[120]!, self._r[120]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[123]!, self._r[123]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_RestoreFailed_CreateWallet: String { return self._s[121]! }
|
||||
public var Wallet_Weekday_Yesterday: String { return self._s[122]! }
|
||||
public var Wallet_Receive_AmountHeader: String { return self._s[123]! }
|
||||
public var Wallet_Month_ShortFebruary: String { return self._s[124]! }
|
||||
public var Wallet_Alert_Cancel: String { return self._s[125]! }
|
||||
public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[126]! }
|
||||
public var Wallet_Info_TransactionFrom: String { return self._s[127]! }
|
||||
public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[128]! }
|
||||
public var Wallet_Words_NotDoneTitle: String { return self._s[129]! }
|
||||
public var Wallet_Month_ShortOctober: String { return self._s[130]! }
|
||||
public var Wallet_Month_GenMay: String { return self._s[131]! }
|
||||
public var Wallet_Intro_CreateErrorTitle: String { return self._s[132]! }
|
||||
public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[133]! }
|
||||
public var Wallet_Month_ShortJanuary: String { return self._s[134]! }
|
||||
public var Wallet_Month_GenMarch: String { return self._s[135]! }
|
||||
public var Wallet_Sending_Text: String { return self._s[136]! }
|
||||
public var Wallet_Month_GenOctober: String { return self._s[137]! }
|
||||
public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[138]! }
|
||||
public var Wallet_ContextMenuCopy: String { return self._s[139]! }
|
||||
public var Wallet_RestoreFailed_CreateWallet: String { return self._s[124]! }
|
||||
public var Wallet_Weekday_Yesterday: String { return self._s[125]! }
|
||||
public var Wallet_Receive_AmountHeader: String { return self._s[126]! }
|
||||
public var Wallet_Month_ShortFebruary: String { return self._s[127]! }
|
||||
public var Wallet_Alert_Cancel: String { return self._s[128]! }
|
||||
public var Wallet_TransactionInfo_RecipientHeader: String { return self._s[129]! }
|
||||
public var Wallet_Info_TransactionFrom: String { return self._s[130]! }
|
||||
public var Wallet_Send_ErrorDecryptionFailed: String { return self._s[131]! }
|
||||
public var Wallet_Words_NotDoneTitle: String { return self._s[132]! }
|
||||
public var Wallet_Month_ShortOctober: String { return self._s[133]! }
|
||||
public var Wallet_Month_GenMay: String { return self._s[134]! }
|
||||
public var Wallet_Intro_CreateErrorTitle: String { return self._s[135]! }
|
||||
public var Wallet_SecureStorageReset_BiometryFaceId: String { return self._s[136]! }
|
||||
public var Wallet_Month_ShortJanuary: String { return self._s[137]! }
|
||||
public var Wallet_Month_GenMarch: String { return self._s[138]! }
|
||||
public var Wallet_Sending_Text: String { return self._s[139]! }
|
||||
public var Wallet_Month_GenOctober: String { return self._s[140]! }
|
||||
public var Wallet_Receive_CopyInvoiceUrl: String { return self._s[141]! }
|
||||
public var Wallet_ContextMenuCopy: String { return self._s[142]! }
|
||||
public func Wallet_Time_PreciseDate_m6(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[140]!, self._r[140]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[143]!, self._r[143]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Info_Updating: String { return self._s[142]! }
|
||||
public var Wallet_Intro_TermsUrl: String { return self._s[143]! }
|
||||
public var Wallet_Created_ExportErrorTitle: String { return self._s[144]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[145]! }
|
||||
public var Wallet_Sending_Title: String { return self._s[146]! }
|
||||
public var Wallet_Navigation_Done: String { return self._s[147]! }
|
||||
public var Wallet_Settings_Title: String { return self._s[148]! }
|
||||
public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[149]! }
|
||||
public var Wallet_Weekday_Today: String { return self._s[151]! }
|
||||
public var Wallet_Month_ShortDecember: String { return self._s[152]! }
|
||||
public var Wallet_Words_Text: String { return self._s[153]! }
|
||||
public var Wallet_WordCheck_ViewWords: String { return self._s[154]! }
|
||||
public var Wallet_Send_AddressInfo: String { return self._s[155]! }
|
||||
public var Wallet_Info_Updating: String { return self._s[145]! }
|
||||
public var Wallet_Intro_TermsUrl: String { return self._s[146]! }
|
||||
public var Wallet_Created_ExportErrorTitle: String { return self._s[147]! }
|
||||
public var Wallet_SecureStorageNotAvailable_Title: String { return self._s[148]! }
|
||||
public var Wallet_Sending_Title: String { return self._s[149]! }
|
||||
public var Wallet_Navigation_Done: String { return self._s[150]! }
|
||||
public var Wallet_Settings_Title: String { return self._s[151]! }
|
||||
public func Wallet_Receive_ShareInvoiceUrlInfo(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[152]!, self._r[152]!, [_0])
|
||||
}
|
||||
public var Wallet_Info_RefreshErrorNetworkText: String { return self._s[153]! }
|
||||
public var Wallet_Weekday_Today: String { return self._s[155]! }
|
||||
public var Wallet_Month_ShortDecember: String { return self._s[156]! }
|
||||
public var Wallet_Words_Text: String { return self._s[157]! }
|
||||
public var Wallet_WordCheck_ViewWords: String { return self._s[158]! }
|
||||
public var Wallet_Send_AddressInfo: String { return self._s[159]! }
|
||||
public func Wallet_Updated_AtDate(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[156]!, self._r[156]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[160]!, self._r[160]!, [_0])
|
||||
}
|
||||
public var Wallet_Intro_NotNow: String { return self._s[157]! }
|
||||
public var Wallet_Navigation_Close: String { return self._s[158]! }
|
||||
public var Wallet_Month_GenDecember: String { return self._s[160]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[161]! }
|
||||
public var Wallet_WordImport_IncorrectTitle: String { return self._s[162]! }
|
||||
public var Wallet_Send_AddressText: String { return self._s[163]! }
|
||||
public var Wallet_Receive_AmountInfo: String { return self._s[164]! }
|
||||
public var Wallet_Intro_NotNow: String { return self._s[161]! }
|
||||
public var Wallet_Navigation_Close: String { return self._s[162]! }
|
||||
public var Wallet_Month_GenDecember: String { return self._s[164]! }
|
||||
public var Wallet_Send_ErrorNotEnoughFundsTitle: String { return self._s[165]! }
|
||||
public var Wallet_WordImport_IncorrectTitle: String { return self._s[166]! }
|
||||
public var Wallet_Send_AddressText: String { return self._s[167]! }
|
||||
public var Wallet_Receive_AmountInfo: String { return self._s[168]! }
|
||||
public func Wallet_Time_PreciseDate_m2(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[165]!, self._r[165]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[169]!, self._r[169]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Month_ShortAugust: String { return self._s[166]! }
|
||||
public var Wallet_Qr_Title: String { return self._s[167]! }
|
||||
public var Wallet_WordCheck_TryAgain: String { return self._s[168]! }
|
||||
public var Wallet_Info_TransactionPendingHeader: String { return self._s[169]! }
|
||||
public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[170]! }
|
||||
public var Wallet_Created_Text: String { return self._s[171]! }
|
||||
public var Wallet_Created_Proceed: String { return self._s[172]! }
|
||||
public var Wallet_Words_Done: String { return self._s[173]! }
|
||||
public var Wallet_WordImport_Continue: String { return self._s[174]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[175]! }
|
||||
public var Wallet_WordImport_CanNotRemember: String { return self._s[176]! }
|
||||
public var Wallet_Month_ShortAugust: String { return self._s[170]! }
|
||||
public var Wallet_Qr_Title: String { return self._s[171]! }
|
||||
public var Wallet_Settings_Configuration: String { return self._s[172]! }
|
||||
public var Wallet_WordCheck_TryAgain: String { return self._s[173]! }
|
||||
public var Wallet_Info_TransactionPendingHeader: String { return self._s[174]! }
|
||||
public var Wallet_Receive_InvoiceUrlHeader: String { return self._s[175]! }
|
||||
public var Wallet_Created_Text: String { return self._s[176]! }
|
||||
public var Wallet_Created_Proceed: String { return self._s[177]! }
|
||||
public var Wallet_Words_Done: String { return self._s[178]! }
|
||||
public var Wallet_WordImport_Continue: String { return self._s[179]! }
|
||||
public var Wallet_TransactionInfo_StorageFeeHeader: String { return self._s[180]! }
|
||||
public var Wallet_WordImport_CanNotRemember: String { return self._s[181]! }
|
||||
public func Wallet_Time_PreciseDate_m11(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[177]!, self._r[177]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[182]!, self._r[182]!, [_1, _2, _3])
|
||||
}
|
||||
public func Wallet_Send_ConfirmationText(_ _1: String, _ _2: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[178]!, self._r[178]!, [_1, _2])
|
||||
return formatWithArgumentRanges(self._s[183]!, self._r[183]!, [_1, _2])
|
||||
}
|
||||
public var Wallet_Created_ExportErrorText: String { return self._s[180]! }
|
||||
public var Wallet_Created_ExportErrorText: String { return self._s[185]! }
|
||||
public func Wallet_Updated_YesterdayAt(_ _0: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[181]!, self._r[181]!, [_0])
|
||||
return formatWithArgumentRanges(self._s[186]!, self._r[186]!, [_0])
|
||||
}
|
||||
public var Wallet_Settings_DeleteWalletInfo: String { return self._s[182]! }
|
||||
public var Wallet_Intro_CreateErrorText: String { return self._s[183]! }
|
||||
public var Wallet_Sent_ViewWallet: String { return self._s[184]! }
|
||||
public var Wallet_Send_ErrorInvalidAddress: String { return self._s[185]! }
|
||||
public var Wallet_Settings_DeleteWalletInfo: String { return self._s[187]! }
|
||||
public var Wallet_Intro_CreateErrorText: String { return self._s[188]! }
|
||||
public var Wallet_Sent_ViewWallet: String { return self._s[189]! }
|
||||
public var Wallet_Send_ErrorInvalidAddress: String { return self._s[190]! }
|
||||
public func Wallet_Time_PreciseDate_m7(_ _1: String, _ _2: String, _ _3: String) -> (String, [(Int, NSRange)]) {
|
||||
return formatWithArgumentRanges(self._s[186]!, self._r[186]!, [_1, _2, _3])
|
||||
return formatWithArgumentRanges(self._s[191]!, self._r[191]!, [_1, _2, _3])
|
||||
}
|
||||
public var Wallet_Send_Title: String { return self._s[187]! }
|
||||
public var Wallet_Info_RefreshErrorText: String { return self._s[188]! }
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[189]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[190]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[191]! }
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
public var Wallet_Send_Title: String { return self._s[192]! }
|
||||
public var Wallet_Info_RefreshErrorText: String { return self._s[193]! }
|
||||
public var Wallet_SecureStorageReset_Title: String { return self._s[194]! }
|
||||
public var Wallet_Receive_CommentHeader: String { return self._s[195]! }
|
||||
public var Wallet_Info_ReceiveGrams: String { return self._s[196]! }
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
}
|
||||
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
|
||||
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
|
||||
let form = getPluralizationForm(self.lc, value)
|
||||
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
|
||||
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user