mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
WIP IAPs
This commit is contained in:
parent
88f2f4d299
commit
82aa7f0857
@ -143,3 +143,46 @@ public func getSGAPIRegDate(token: String, deviceToken: String, userId: Int64) -
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func postSGReceipt(token: String, deviceToken: String, encodedReceiptData: Data) -> Signal<Void, SGAPIError> {
|
||||
return Signal { subscriber in
|
||||
|
||||
let url = URL(string: buildApiUrl("validate"))!
|
||||
let headers = [
|
||||
SG_API_AUTHORIZATION_HEADER: "Token \(token)",
|
||||
SG_API_DEVICE_TOKEN_HEADER: deviceToken
|
||||
]
|
||||
let completed = Atomic<Bool>(value: false)
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
headers.forEach { key, value in
|
||||
request.addValue(value, forHTTPHeaderField: key)
|
||||
}
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = encodedReceiptData
|
||||
|
||||
let dataSignal = requestsCustom(request: request).start(next: { data, urlResponse in
|
||||
let _ = completed.swap(true)
|
||||
|
||||
if let httpResponse = urlResponse as? HTTPURLResponse {
|
||||
switch httpResponse.statusCode {
|
||||
case 200...299:
|
||||
subscriber.putCompletion()
|
||||
default:
|
||||
subscriber.putError(.generic("Error posting Receipt: \(httpResponse.statusCode). Response: \(String(data: data, encoding: .utf8) ?? "")"))
|
||||
}
|
||||
} else {
|
||||
subscriber.putError(.generic("Not an HTTP response: \(String(describing: urlResponse))"))
|
||||
}
|
||||
}, error: { error in
|
||||
subscriber.putError(.generic("Error posting Receipt: \(String(describing: error))"))
|
||||
})
|
||||
|
||||
return ActionDisposable {
|
||||
if !completed.with({ $0 }) {
|
||||
dataSignal.dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ public struct SGConfig: Codable {
|
||||
public var apiUrl: String = "https://api.swiftgram.app"
|
||||
public var webappUrl: String = "https://my.swiftgram.app"
|
||||
public var botUsername: String = "SwiftgramBot"
|
||||
public var iaps: [String] = []
|
||||
}
|
||||
|
||||
private func parseSGConfig(_ jsonString: String) -> SGConfig {
|
||||
|
@ -839,6 +839,7 @@ private enum SGDebugActions: String {
|
||||
case flexing
|
||||
case fileManager
|
||||
case clearRegDateCache
|
||||
case debugIAP
|
||||
}
|
||||
|
||||
private enum SGDebugToggles: String {
|
||||
@ -862,6 +863,7 @@ private func SGDebugControllerEntries(presentationData: PresentationData) -> [SG
|
||||
#if DEBUG
|
||||
entries.append(.action(id: id.count, section: .base, actionType: .flexing, text: "FLEX", kind: .generic))
|
||||
entries.append(.action(id: id.count, section: .base, actionType: .fileManager, text: "FileManager", kind: .generic))
|
||||
entries.append(.action(id: id.count, section: .base, actionType: .debugIAP, text: "Buy", kind: .generic))
|
||||
#endif
|
||||
|
||||
if SGSimpleSettings.shared.b {
|
||||
@ -1020,6 +1022,10 @@ public func sgDebugController(context: AccountContext) -> ViewController {
|
||||
nil)
|
||||
}
|
||||
#endif
|
||||
case .debugIAP:
|
||||
#if DEBUG
|
||||
preconditionFailure("IAP")
|
||||
#endif
|
||||
}
|
||||
})
|
||||
|
||||
|
20
Swiftgram/SGIAP/BUILD
Normal file
20
Swiftgram/SGIAP/BUILD
Normal file
@ -0,0 +1,20 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "SGIAP",
|
||||
module_name = "SGIAP",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//Swiftgram/SGLogging:SGLogging",
|
||||
"//Swiftgram/SGConfig:SGConfig",
|
||||
"//submodules/AppBundle:AppBundle"
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
328
Swiftgram/SGIAP/Sources/SGIAP.swift
Normal file
328
Swiftgram/SGIAP/Sources/SGIAP.swift
Normal file
@ -0,0 +1,328 @@
|
||||
import StoreKit
|
||||
import SGConfig
|
||||
import SGLogging
|
||||
import AppBundle
|
||||
|
||||
private final class CurrencyFormatterEntry {
|
||||
public let symbol: String
|
||||
public let thousandsSeparator: String
|
||||
public let decimalSeparator: String
|
||||
public let symbolOnLeft: Bool
|
||||
public let spaceBetweenAmountAndSymbol: Bool
|
||||
public let decimalDigits: Int
|
||||
|
||||
public init(symbol: String, thousandsSeparator: String, decimalSeparator: String, symbolOnLeft: Bool, spaceBetweenAmountAndSymbol: Bool, decimalDigits: Int) {
|
||||
self.symbol = symbol
|
||||
self.thousandsSeparator = thousandsSeparator
|
||||
self.decimalSeparator = decimalSeparator
|
||||
self.symbolOnLeft = symbolOnLeft
|
||||
self.spaceBetweenAmountAndSymbol = spaceBetweenAmountAndSymbol
|
||||
self.decimalDigits = decimalDigits
|
||||
}
|
||||
}
|
||||
|
||||
private func getCurrencyExp(currency: String) -> Int {
|
||||
switch currency {
|
||||
case "CLF":
|
||||
return 4
|
||||
case "BHD", "IQD", "JOD", "KWD", "LYD", "OMR", "TND":
|
||||
return 3
|
||||
case "BIF", "BYR", "CLP", "CVE", "DJF", "GNF", "ISK", "JPY", "KMF", "KRW", "MGA", "PYG", "RWF", "UGX", "UYI", "VND", "VUV", "XAF", "XOF", "XPF":
|
||||
return 0
|
||||
case "MRO":
|
||||
return 1
|
||||
default:
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
private func loadCurrencyFormatterEntries() -> [String: CurrencyFormatterEntry] {
|
||||
guard let filePath = getAppBundle().path(forResource: "currencies", ofType: "json") else {
|
||||
return [:]
|
||||
}
|
||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
||||
return [:]
|
||||
}
|
||||
|
||||
guard let object = try? JSONSerialization.jsonObject(with: data, options: []), let dict = object as? [String: AnyObject] else {
|
||||
return [:]
|
||||
}
|
||||
|
||||
var result: [String: CurrencyFormatterEntry] = [:]
|
||||
|
||||
for (code, contents) in dict {
|
||||
if let contentsDict = contents as? [String: AnyObject] {
|
||||
let entry = CurrencyFormatterEntry(
|
||||
symbol: contentsDict["symbol"] as! String,
|
||||
thousandsSeparator: contentsDict["thousandsSeparator"] as! String,
|
||||
decimalSeparator: contentsDict["decimalSeparator"] as! String,
|
||||
symbolOnLeft: (contentsDict["symbolOnLeft"] as! NSNumber).boolValue,
|
||||
spaceBetweenAmountAndSymbol: (contentsDict["spaceBetweenAmountAndSymbol"] as! NSNumber).boolValue,
|
||||
decimalDigits: getCurrencyExp(currency: code.uppercased())
|
||||
)
|
||||
result[code] = entry
|
||||
result[code.lowercased()] = entry
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private let currencyFormatterEntries = loadCurrencyFormatterEntries()
|
||||
|
||||
private func fractionalValueToCurrencyAmount(value: Double, currency: String) -> Int64? {
|
||||
guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
|
||||
return nil
|
||||
}
|
||||
var factor: Double = 1.0
|
||||
for _ in 0 ..< entry.decimalDigits {
|
||||
factor *= 10.0
|
||||
}
|
||||
if value > Double(Int64.max) / factor {
|
||||
return nil
|
||||
} else {
|
||||
return Int64(value * factor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public extension Notification.Name {
|
||||
static let SGIAPHelperPurchaseNotification = Notification.Name("SGIAPPurchaseNotification")
|
||||
static let SGIAPHelperErrorNotification = Notification.Name("SGIAPErrorNotification")
|
||||
}
|
||||
|
||||
public final class SGIAP: NSObject {
|
||||
private var productRequest: SKProductsRequest?
|
||||
private var productsRequestCompletion: (([SKProduct]) -> Void)?
|
||||
private var purchaseCompletion: ((Bool, Error?) -> Void)?
|
||||
|
||||
|
||||
public final class SGProduct: Equatable {
|
||||
private lazy var numberFormatter: NumberFormatter = {
|
||||
let numberFormatter = NumberFormatter()
|
||||
numberFormatter.numberStyle = .currency
|
||||
numberFormatter.locale = self.skProduct.priceLocale
|
||||
return numberFormatter
|
||||
}()
|
||||
|
||||
let skProduct: SKProduct
|
||||
|
||||
init(skProduct: SKProduct) {
|
||||
self.skProduct = skProduct
|
||||
}
|
||||
|
||||
public var id: String {
|
||||
return self.skProduct.productIdentifier
|
||||
}
|
||||
|
||||
public var isSubscription: Bool {
|
||||
if #available(iOS 12.0, *) {
|
||||
return self.skProduct.subscriptionGroupIdentifier != nil
|
||||
} else {
|
||||
return self.skProduct.subscriptionPeriod != nil
|
||||
}
|
||||
}
|
||||
|
||||
public var price: String {
|
||||
return self.numberFormatter.string(from: self.skProduct.price) ?? ""
|
||||
}
|
||||
|
||||
public func pricePerMonth(_ monthsCount: Int) -> String {
|
||||
let price = self.skProduct.price.dividing(by: NSDecimalNumber(value: monthsCount)).round(2)
|
||||
return self.numberFormatter.string(from: price) ?? ""
|
||||
}
|
||||
|
||||
public func defaultPrice(_ value: NSDecimalNumber, monthsCount: Int) -> String {
|
||||
let price = value.multiplying(by: NSDecimalNumber(value: monthsCount)).round(2)
|
||||
let prettierPrice = price
|
||||
.multiplying(by: NSDecimalNumber(value: 2))
|
||||
.rounding(accordingToBehavior:
|
||||
NSDecimalNumberHandler(
|
||||
roundingMode: .up,
|
||||
scale: Int16(0),
|
||||
raiseOnExactness: false,
|
||||
raiseOnOverflow: false,
|
||||
raiseOnUnderflow: false,
|
||||
raiseOnDivideByZero: false
|
||||
)
|
||||
)
|
||||
.dividing(by: NSDecimalNumber(value: 2))
|
||||
.subtracting(NSDecimalNumber(value: 0.01))
|
||||
return self.numberFormatter.string(from: prettierPrice) ?? ""
|
||||
}
|
||||
|
||||
public func multipliedPrice(count: Int) -> String {
|
||||
let price = self.skProduct.price.multiplying(by: NSDecimalNumber(value: count)).round(2)
|
||||
let prettierPrice = price
|
||||
.multiplying(by: NSDecimalNumber(value: 2))
|
||||
.rounding(accordingToBehavior:
|
||||
NSDecimalNumberHandler(
|
||||
roundingMode: .up,
|
||||
scale: Int16(0),
|
||||
raiseOnExactness: false,
|
||||
raiseOnOverflow: false,
|
||||
raiseOnUnderflow: false,
|
||||
raiseOnDivideByZero: false
|
||||
)
|
||||
)
|
||||
.dividing(by: NSDecimalNumber(value: 2))
|
||||
.subtracting(NSDecimalNumber(value: 0.01))
|
||||
return self.numberFormatter.string(from: prettierPrice) ?? ""
|
||||
}
|
||||
|
||||
public var priceValue: NSDecimalNumber {
|
||||
return self.skProduct.price
|
||||
}
|
||||
|
||||
public var priceCurrencyAndAmount: (currency: String, amount: Int64) {
|
||||
if let currencyCode = self.numberFormatter.currencyCode,
|
||||
let amount = fractionalValueToCurrencyAmount(value: self.priceValue.doubleValue, currency: currencyCode) {
|
||||
return (currencyCode, amount)
|
||||
} else {
|
||||
return ("", 0)
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: SGProduct, rhs: SGProduct) -> Bool {
|
||||
if lhs.id != rhs.id {
|
||||
return false
|
||||
}
|
||||
if lhs.isSubscription != rhs.isSubscription {
|
||||
return false
|
||||
}
|
||||
if lhs.priceValue != rhs.priceValue {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public init(foo: Bool = false) {
|
||||
super.init()
|
||||
|
||||
SKPaymentQueue.default().add(self)
|
||||
self.requestProducts()
|
||||
}
|
||||
|
||||
deinit {
|
||||
SKPaymentQueue.default().remove(self)
|
||||
}
|
||||
|
||||
var canMakePayments: Bool {
|
||||
return SKPaymentQueue.canMakePayments()
|
||||
}
|
||||
|
||||
public func buyProduct(_ product: SKProduct) {
|
||||
SGLogger.shared.log("SGIAP", "Buying \(product.productIdentifier)...")
|
||||
let payment = SKPayment(product: product)
|
||||
SKPaymentQueue.default().add(payment)
|
||||
}
|
||||
|
||||
private func requestProducts() {
|
||||
SGLogger.shared.log("SGIAP", "Requesting products...")
|
||||
let productRequest = SKProductsRequest(productIdentifiers: Set(SG_CONFIG.iaps))
|
||||
|
||||
productRequest.delegate = self
|
||||
productRequest.start()
|
||||
|
||||
self.productRequest = productRequest
|
||||
}
|
||||
|
||||
public func restorePurchases(completion: @escaping (RestoreState) -> Void) {
|
||||
SGLogger.shared.log("SGIAP", "Restoring purchases...")
|
||||
|
||||
let paymentQueue = SKPaymentQueue.default()
|
||||
paymentQueue.restoreCompletedTransactions()
|
||||
}
|
||||
|
||||
public enum RestoreState {
|
||||
case succeed(Bool)
|
||||
case failed
|
||||
}
|
||||
}
|
||||
|
||||
extension SGIAP: SKProductsRequestDelegate {
|
||||
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
|
||||
self.productRequest = nil
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let products = response.products
|
||||
SGLogger.shared.log("SGIAP", "Received products \(products.map({ $0.productIdentifier }).joined(separator: ", "))")
|
||||
}
|
||||
}
|
||||
|
||||
public func request(_ request: SKRequest, didFailWithError error: Error) {
|
||||
SGLogger.shared.log("SGIAP", "Failed to load list of products. Error \(error.localizedDescription)")
|
||||
self.productRequest = nil
|
||||
}
|
||||
}
|
||||
|
||||
extension SGIAP: SKPaymentTransactionObserver {
|
||||
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
||||
SGLogger.shared.log("SGIAP", "paymentQueue transactions \(transactions.count)")
|
||||
for transaction in transactions {
|
||||
SGLogger.shared.log("SGIAP", "Transaction state for \(transaction.payment.productIdentifier): \(transaction.transactionState)")
|
||||
switch transaction.transactionState {
|
||||
case .purchased, .restored:
|
||||
NotificationCenter.default.post(name: .SGIAPHelperPurchaseNotification, object: transaction)
|
||||
case .purchasing, .deferred:
|
||||
break
|
||||
case .failed:
|
||||
if let transactionError = transaction.error as NSError?,
|
||||
let localizedDescription = transaction.error?.localizedDescription,
|
||||
transactionError.code != SKError.paymentCancelled.rawValue {
|
||||
SGLogger.shared.log("SGIAP", "Transaction Error: \(localizedDescription)")
|
||||
}
|
||||
NotificationCenter.default.post(name: .SGIAPHelperErrorNotification, object: transaction)
|
||||
default:
|
||||
SGLogger.shared.log("SGIAP", "Unknown transaction state \(transaction.transactionState). Finishing transaction.")
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension NSDecimalNumber {
|
||||
func round(_ decimals: Int) -> NSDecimalNumber {
|
||||
return self.rounding(accordingToBehavior:
|
||||
NSDecimalNumberHandler(roundingMode: .down,
|
||||
scale: Int16(decimals),
|
||||
raiseOnExactness: false,
|
||||
raiseOnOverflow: false,
|
||||
raiseOnUnderflow: false,
|
||||
raiseOnDivideByZero: false))
|
||||
}
|
||||
|
||||
func prettyPrice() -> NSDecimalNumber {
|
||||
return self.multiplying(by: NSDecimalNumber(value: 2))
|
||||
.rounding(accordingToBehavior:
|
||||
NSDecimalNumberHandler(
|
||||
roundingMode: .plain,
|
||||
scale: Int16(0),
|
||||
raiseOnExactness: false,
|
||||
raiseOnOverflow: false,
|
||||
raiseOnUnderflow: false,
|
||||
raiseOnDivideByZero: false
|
||||
)
|
||||
)
|
||||
.dividing(by: NSDecimalNumber(value: 2))
|
||||
.subtracting(NSDecimalNumber(value: 0.01))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getPurchaceReceiptData() -> Data? {
|
||||
var receiptData: Data?
|
||||
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
|
||||
do {
|
||||
receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
|
||||
} catch {
|
||||
SGLogger.shared.log("SGIAP", "Couldn't read receipt data with error: \(error.localizedDescription)")
|
||||
}
|
||||
} else {
|
||||
SGLogger.shared.log("SGIAP", "Couldn't find receipt path")
|
||||
}
|
||||
return receiptData
|
||||
}
|
@ -23,22 +23,23 @@ public func makeIqtpQuery(_ api: Int, _ method: String, _ args: [String] = []) -
|
||||
}
|
||||
|
||||
public func sgIqtpQuery(engine: TelegramEngine, query: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<SGIQTPResponse?, NoError> {
|
||||
let queryId = arc4random()
|
||||
#if DEBUG
|
||||
SGLogger.shared.log("SGIQTP", "Query: \(query)")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Query: \(query)")
|
||||
#else
|
||||
SGLogger.shared.log("SGIQTP", "Query")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Query")
|
||||
#endif
|
||||
return engine.peers.resolvePeerByName(name: SG_CONFIG.botUsername, referrer: nil)
|
||||
return engine.peers.resolvePeerByName(forIQTP: true, name: SG_CONFIG.botUsername, referrer: nil)
|
||||
|> mapToSignal { result -> Signal<EnginePeer?, NoError> in
|
||||
guard case let .result(result) = result else {
|
||||
SGLogger.shared.log("SGIQTP", "Failed to resolve peer")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Failed to resolve peer \(SG_CONFIG.botUsername)")
|
||||
return .complete()
|
||||
}
|
||||
return .single(result)
|
||||
}
|
||||
|> mapToSignal { peer -> Signal<ChatContextResultCollection?, NoError> in
|
||||
guard let peer = peer else {
|
||||
SGLogger.shared.log("SGIQTP", "Empty peer")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Empty peer")
|
||||
return .single(nil)
|
||||
}
|
||||
return engine.messages.requestChatContextResults(botId: peer.id, peerId: engine.account.peerId, query: query, offset: "", incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|
||||
@ -46,13 +47,13 @@ public func sgIqtpQuery(engine: TelegramEngine, query: String, incompleteResults
|
||||
return results?.results
|
||||
}
|
||||
|> `catch` { error -> Signal<ChatContextResultCollection?, NoError> in
|
||||
SGLogger.shared.log("SGIQTP", "Failed to request inline results")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Failed to request inline results")
|
||||
return .single(nil)
|
||||
}
|
||||
}
|
||||
|> map { contextResult -> SGIQTPResponse? in
|
||||
guard let contextResult, let firstResult = contextResult.results.first else {
|
||||
SGLogger.shared.log("SGIQTP", "Empty inline result")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Empty inline result")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -70,7 +71,7 @@ public func sgIqtpQuery(engine: TelegramEngine, query: String, incompleteResults
|
||||
description: firstResult.description,
|
||||
text: t
|
||||
)
|
||||
SGLogger.shared.log("SGIQTP", "Response: \(response)")
|
||||
SGLogger.shared.log("SGIQTP", "[\(queryId)] Response: \(response)")
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,7 @@ public class SGSimpleSettings {
|
||||
case inputToolbar
|
||||
case pinnedMessageNotifications
|
||||
case mentionsAndRepliesNotifications
|
||||
case primaryUserId
|
||||
}
|
||||
|
||||
public enum DownloadSpeedBoostValues: String, CaseIterable {
|
||||
@ -236,7 +237,8 @@ public class SGSimpleSettings {
|
||||
Keys.confirmCalls.rawValue: true,
|
||||
Keys.videoPIPSwipeDirection.rawValue: VideoPIPSwipeDirection.up.rawValue,
|
||||
Keys.messageFilterKeywords.rawValue: [],
|
||||
Keys.inputToolbar.rawValue: false
|
||||
Keys.inputToolbar.rawValue: false,
|
||||
Keys.primaryUserId.rawValue: ""
|
||||
]
|
||||
|
||||
public static let groupDefaultValues: [String: Any] = [
|
||||
@ -437,6 +439,9 @@ public class SGSimpleSettings {
|
||||
|
||||
@UserDefault(key: Keys.mentionsAndRepliesNotifications.rawValue, userDefaults: UserDefaults(suiteName: appGroupIdentifier) ?? .standard)
|
||||
public var mentionsAndRepliesNotifications: String
|
||||
|
||||
@UserDefault(key: Keys.primaryUserId.rawValue)
|
||||
public var primaryUserId: String
|
||||
}
|
||||
|
||||
extension SGSimpleSettings {
|
||||
|
9
Swiftgram/SGStatus/BUILD
Normal file
9
Swiftgram/SGStatus/BUILD
Normal file
@ -0,0 +1,9 @@
|
||||
filegroup(
|
||||
name = "SGStatus",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
41
Swiftgram/SGStatus/Sources/SGStatus.swift
Normal file
41
Swiftgram/SGStatus/Sources/SGStatus.swift
Normal file
@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import TelegramCore
|
||||
|
||||
public struct SGStatus: Equatable, Codable {
|
||||
public var status: Int64
|
||||
|
||||
public static var `default`: SGStatus {
|
||||
return SGStatus(status: 1)
|
||||
}
|
||||
|
||||
public init(status: Int64) {
|
||||
self.status = status
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
self.status = try container.decodeIfPresent(Int64.self, forKey: "status") ?? 1
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: StringCodingKey.self)
|
||||
|
||||
try container.encodeIfPresent(self.status, forKey: "status")
|
||||
}
|
||||
}
|
||||
|
||||
public func updateSGStatusInteractively(accountManager: AccountManager<TelegramAccountManagerTypes>, _ f: @escaping (SGStatus) -> SGStatus) -> Signal<Void, NoError> {
|
||||
return accountManager.transaction { transaction -> Void in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.sgStatus, { entry in
|
||||
let currentSettings: SGStatus
|
||||
if let entry = entry?.get(SGStatus.self) {
|
||||
currentSettings = entry
|
||||
} else {
|
||||
currentSettings = SGStatus.default
|
||||
}
|
||||
return SharedPreferencesEntry(f(currentSettings))
|
||||
})
|
||||
}
|
||||
}
|
@ -27,7 +27,17 @@ public func ignoreSignalErrors<T, E>(onError: ((E) -> Void)? = nil) -> (Signal<T
|
||||
}
|
||||
}
|
||||
|
||||
extension Signal where E: Error {
|
||||
// Wrapper for non-Error types
|
||||
public struct SignalError<E>: Error {
|
||||
public let error: E
|
||||
|
||||
public init(_ error: E) {
|
||||
self.error = error
|
||||
}
|
||||
}
|
||||
|
||||
// Extension for Signals with Error types
|
||||
extension Signal {
|
||||
@available(iOS 13.0, *)
|
||||
public func awaitable() async throws -> T {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
@ -38,7 +48,11 @@ extension Signal where E: Error {
|
||||
disposable?.dispose()
|
||||
},
|
||||
error: { error in
|
||||
continuation.resume(throwing: error)
|
||||
if let error = error as? Error {
|
||||
continuation.resume(throwing: error)
|
||||
} else {
|
||||
continuation.resume(throwing: SignalError(error))
|
||||
}
|
||||
disposable?.dispose()
|
||||
},
|
||||
completed: {
|
||||
@ -49,6 +63,7 @@ extension Signal where E: Error {
|
||||
}
|
||||
}
|
||||
|
||||
// Extension for Signals with NoError
|
||||
extension Signal where E == NoError {
|
||||
@available(iOS 13.0, *)
|
||||
public func awaitable() async -> T {
|
||||
@ -71,6 +86,7 @@ extension Signal where E == NoError {
|
||||
}
|
||||
}
|
||||
|
||||
// Extension for general Signal types - AsyncStream support
|
||||
extension Signal {
|
||||
@available(iOS 13.0, *)
|
||||
public func awaitableStream() -> AsyncStream<T> {
|
||||
@ -94,7 +110,7 @@ extension Signal {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extension for NoError Signal types - AsyncStream support
|
||||
extension Signal where E == NoError {
|
||||
@available(iOS 13.0, *)
|
||||
public func awaitableStream() -> AsyncStream<T> {
|
||||
|
@ -933,6 +933,8 @@ public protocol SharedAccountContext: AnyObject {
|
||||
var automaticMediaDownloadSettings: Signal<MediaAutoDownloadSettings, NoError> { get }
|
||||
var currentAutodownloadSettings: Atomic<AutodownloadSettings> { get }
|
||||
var immediateExperimentalUISettings: ExperimentalUISettings { get }
|
||||
// MARK: Swiftgram
|
||||
var immediateSGStatus: SGStatus { get }
|
||||
var currentInAppNotificationSettings: Atomic<InAppNotificationSettings> { get }
|
||||
var currentMediaInputSettings: Atomic<MediaInputSettings> { get }
|
||||
var currentStickerSettings: Atomic<StickerSettings> { get }
|
||||
|
@ -52,3 +52,18 @@ public func activeAccountsAndPeers(context: AccountContext, includePrimary: Bool
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Swiftgram
|
||||
public func getContextForUserId(context: AccountContext, userId: Int64) -> Signal<AccountContext?, NoError> {
|
||||
if context.account.peerId.id._internalGetInt64Value() == userId {
|
||||
return .single(context)
|
||||
}
|
||||
return context.sharedContext.activeAccountContexts
|
||||
|> take(1)
|
||||
|> map { _, activeAccounts, _ -> AccountContext? in
|
||||
if let account = activeAccounts.first(where: { $0.1.account.peerId.id._internalGetInt64Value() == userId }) {
|
||||
return account.1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -236,12 +236,12 @@ public final class InAppPurchaseManager: NSObject {
|
||||
|
||||
super.init()
|
||||
|
||||
SKPaymentQueue.default().add(self)
|
||||
// SKPaymentQueue.default().add(self) // MARK: Swiftgram
|
||||
self.requestProducts()
|
||||
}
|
||||
|
||||
deinit {
|
||||
SKPaymentQueue.default().remove(self)
|
||||
// SKPaymentQueue.default().remove(self) // MARK: Swiftgram
|
||||
}
|
||||
|
||||
var canMakePayments: Bool {
|
||||
@ -249,6 +249,7 @@ public final class InAppPurchaseManager: NSObject {
|
||||
}
|
||||
|
||||
private func requestProducts() {
|
||||
if ({ return true }()) { return } // MARK: Swiftgram
|
||||
Logger.shared.log("InAppPurchaseManager", "Requesting products")
|
||||
let productRequest = SKProductsRequest(productIdentifiers: Set(productIdentifiers))
|
||||
productRequest.delegate = self
|
||||
@ -296,7 +297,7 @@ public final class InAppPurchaseManager: NSObject {
|
||||
let payment = SKMutablePayment(product: product.skProduct)
|
||||
payment.applicationUsername = accountPeerId
|
||||
payment.quantity = Int(quantity)
|
||||
SKPaymentQueue.default().add(payment)
|
||||
// SKPaymentQueue.default().add(payment) // MARK: Swiftgram
|
||||
|
||||
let productIdentifier = payment.productIdentifier
|
||||
let signal = Signal<PurchaseState, PurchaseError> { subscriber in
|
||||
|
@ -31,6 +31,7 @@ public let SGLocalizations: [LocalizationInfo] = [
|
||||
LocalizationInfo(languageCode: "zhcncc", baseLanguageCode: "zh-hans-raw", customPluralizationCode: "zh", title: "Chinese (Simplified) zhcncc", localizedTitle: "简体中文 (聪聪) - 已更完", isOfficial: true, totalStringCount: 7160, translatedStringCount: 7144, platformUrl: "https://translations.telegram.org/zhcncc/"),
|
||||
LocalizationInfo(languageCode: "taiwan", baseLanguageCode: "zh-hant-raw", customPluralizationCode: "zh", title: "Chinese (zh-Hant-TW) @zh_Hant_TW", localizedTitle: "正體中文", isOfficial: true, totalStringCount: 7160, translatedStringCount: 3761, platformUrl: "https://translations.telegram.org/taiwan/"),
|
||||
LocalizationInfo(languageCode: "hongkong", baseLanguageCode: "zh-hant-raw", customPluralizationCode: "zh", title: "Chinese (Hong Kong)", localizedTitle: "中文(香港)", isOfficial: true, totalStringCount: 7358, translatedStringCount: 6083, platformUrl: "https://translations.telegram.org/hongkong/"),
|
||||
// TODO(swiftgram): Japanese beta
|
||||
// baseLanguageCode is actually nil, since it's an "official" beta language
|
||||
LocalizationInfo(languageCode: "vi-raw", baseLanguageCode: "vi-raw", customPluralizationCode: "vi", title: "Vietnamese", localizedTitle: "Tiếng Việt (beta)", isOfficial: true, totalStringCount: 7160, translatedStringCount: 3795, platformUrl: "https://translations.telegram.org/vi/"),
|
||||
LocalizationInfo(languageCode: "hi-raw", baseLanguageCode: "hi-raw", customPluralizationCode: "hi", title: "Hindi", localizedTitle: "हिन्दी (beta)", isOfficial: true, totalStringCount: 7358, translatedStringCount: 992, platformUrl: "https://translations.telegram.org/hi/"),
|
||||
|
@ -52,7 +52,7 @@ public struct RequestChatContextResultsResult {
|
||||
}
|
||||
}
|
||||
|
||||
func _internal_requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
func _internal_requestChatContextResults(forIQTP: Bool = false, account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
return account.postbox.transaction { transaction -> (bot: Peer, peer: Peer)? in
|
||||
if let bot = transaction.getPeer(botId), let peer = transaction.getPeer(peerId) {
|
||||
return (bot, peer)
|
||||
@ -77,7 +77,7 @@ func _internal_requestChatContextResults(account: Account, botId: PeerId, peerId
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> in
|
||||
return account.postbox.transaction(ignoreDisabled: forIQTP) { transaction -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> in
|
||||
var staleResult: RequestChatContextResultsResult?
|
||||
|
||||
if offset.isEmpty && location == nil {
|
||||
@ -127,6 +127,7 @@ func _internal_requestChatContextResults(account: Account, botId: PeerId, peerId
|
||||
return ChatContextResultCollection(apiResults: result, botId: bot.id, peerId: peerId, query: query, geoPoint: location)
|
||||
}
|
||||
|> mapError { error -> RequestChatContextResultsError in
|
||||
print("error.errorDescription", error.errorDescription ?? "")
|
||||
if error.errorDescription == "BOT_INLINE_GEO_REQUIRED" {
|
||||
return .locationRequired
|
||||
} else {
|
||||
@ -138,7 +139,7 @@ func _internal_requestChatContextResults(account: Account, botId: PeerId, peerId
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
return account.postbox.transaction { transaction -> RequestChatContextResultsResult? in
|
||||
return account.postbox.transaction(ignoreDisabled: forIQTP) { transaction -> RequestChatContextResultsResult? in
|
||||
if result.cacheTimeout > 10, offset.isEmpty && location == nil {
|
||||
if let resultData = try? JSONEncoder().encode(result) {
|
||||
let requestData = RequestData(version: requestVersion, botId: botId, peerId: peerId, query: query)
|
||||
|
@ -361,8 +361,8 @@ public extension TelegramEngine {
|
||||
return _internal_updateStarsReactionIsAnonymous(account: self.account, messageId: id, isAnonymous: isAnonymous)
|
||||
}
|
||||
|
||||
public func requestChatContextResults(botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
return _internal_requestChatContextResults(account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|
||||
public func requestChatContextResults(forIQTP: Bool = false, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String, incompleteResults: Bool = false, staleCachedResults: Bool = false) -> Signal<RequestChatContextResultsResult?, RequestChatContextResultsError> {
|
||||
return _internal_requestChatContextResults(forIQTP: Bool = false, account: self.account, botId: botId, peerId: peerId, query: query, location: location, offset: offset, incompleteResults: incompleteResults, staleCachedResults: staleCachedResults)
|
||||
}
|
||||
|
||||
public func removeRecentlyUsedHashtag(string: String) -> Signal<Void, NoError> {
|
||||
|
@ -179,7 +179,7 @@ public extension TelegramEngine {
|
||||
return _internal_inactiveChannelList(network: self.account.network)
|
||||
}
|
||||
|
||||
public func resolvePeerByName(name: String, referrer: String?, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<ResolvePeerResult, NoError> {
|
||||
public func resolvePeerByName(forIQTP: Bool = false, name: String, referrer: String?, ageLimit: Int32 = 2 * 60 * 60 * 24) -> Signal<ResolvePeerResult, NoError> {
|
||||
return _internal_resolvePeerByName(account: self.account, name: name, referrer: referrer, ageLimit: ageLimit)
|
||||
|> mapToSignal { result -> Signal<ResolvePeerResult, NoError> in
|
||||
switch result {
|
||||
@ -189,7 +189,7 @@ public extension TelegramEngine {
|
||||
guard let peerId = peerId else {
|
||||
return .single(.result(nil))
|
||||
}
|
||||
return self.account.postbox.transaction { transaction -> ResolvePeerResult in
|
||||
return self.account.postbox.transaction(ignoreDisabled: forIQTP) { transaction -> ResolvePeerResult in
|
||||
return .result(transaction.getPeer(peerId).flatMap(EnginePeer.init))
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ sgdeps = [
|
||||
"//Swiftgram/SGDeviceToken:SGDeviceToken",
|
||||
"//Swiftgram/SGDebugUI:SGDebugUI",
|
||||
"//Swiftgram/SGInputToolbar:SGInputToolbar",
|
||||
"//Swiftgram/SGIAP:SGIAP",
|
||||
# "//Swiftgram/SGContentAnalysis:SGContentAnalysis"
|
||||
]
|
||||
|
||||
|
@ -1,4 +1,10 @@
|
||||
// MARK: Swiftgram
|
||||
import StoreKit
|
||||
import SGIAP
|
||||
import SGAPI
|
||||
import SGDeviceToken
|
||||
import SGAPIToken
|
||||
|
||||
import SGActionRequestHandlerSanitizer
|
||||
import SGAPIWebSettings
|
||||
import SGLogging
|
||||
@ -1265,11 +1271,14 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
let _ = (context.context.sharedContext.presentationData.start(next: { presentationData in
|
||||
SGLocalizationManager.shared.downloadLocale(presentationData.strings.baseLanguageCode)
|
||||
}))
|
||||
let _ = sgIqtpQuery(engine: context.context.engine, query: makeIqtpQuery(0, "b")).start(next: { response in
|
||||
guard let response else { return }
|
||||
SGLogger.shared.log("IQTP", "Response: \(response)")
|
||||
SGSimpleSettings.shared.b = response.description == "1"
|
||||
})
|
||||
if #available(iOS 13.0, *) {
|
||||
let _ = Task {
|
||||
let primaryContext = await self.getPrimaryContext(anyContext: context.context)
|
||||
SGLogger.shared.log("SGIAP", "Verifying Status \(primaryContext.sharedContext.immediateSGStatus.status) for: \(primaryContext.account.peerId.id._internalGetInt64Value())")
|
||||
let _ = await self.fetchSGStatus(primaryContext: primaryContext)
|
||||
}
|
||||
}
|
||||
|
||||
}))
|
||||
} else {
|
||||
self.mainWindow.viewController = nil
|
||||
@ -3051,3 +3060,136 @@ private func getMemoryConsumption() -> Int {
|
||||
}
|
||||
return Int(info.phys_footprint)
|
||||
}
|
||||
|
||||
// MARK: Swiftgram
|
||||
@available(iOS 13.0, *)
|
||||
extension AppDelegate {
|
||||
|
||||
func setupIAP() {
|
||||
NotificationCenter.default.addObserver(forName: .SGIAPHelperPurchaseNotification, object: nil, queue: nil) { [weak self] notification in
|
||||
guard let strongSelf = self else { return }
|
||||
if let transaction = notification.object as? SKPaymentTransaction {
|
||||
let _ = (strongSelf.context.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { context in
|
||||
guard let context = context else {
|
||||
SGLogger.shared.log("SGIAP", "Empty app context (how?)")
|
||||
|
||||
SGLogger.shared.log("SGIAP", "Finishing transaction")
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
return
|
||||
}
|
||||
let _ = Task {
|
||||
await strongSelf.sendReceiptForVerification(primaryContext: context.context)
|
||||
await strongSelf.fetchSGStatus(primaryContext: context.context)
|
||||
SGLogger.shared.log("SGIAP", "Finishing transaction")
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
SGLogger.shared.log("SGIAP", "Wrong object in SGIAPHelperPurchaseNotification")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getPrimaryContext(anyContext context: AccountContext, fallbackToCurrent: Bool = false) async -> AccountContext {
|
||||
var primaryUserId: Int64 = Int64(SGSimpleSettings.shared.primaryUserId) ?? 0
|
||||
if primaryUserId == 0 {
|
||||
primaryUserId = context.account.peerId.id._internalGetInt64Value()
|
||||
SGLogger.shared.log("SGIAP", "Setting new primary user id: \(primaryUserId)")
|
||||
SGSimpleSettings.shared.primaryUserId = String(primaryUserId)
|
||||
}
|
||||
|
||||
var primaryContext = await getContextForUserId(context: context, userId: primaryUserId).awaitable()
|
||||
if let primaryContext = primaryContext {
|
||||
SGLogger.shared.log("SGIAP", "Got primary context for user id: \(primaryContext.account.peerId.id._internalGetInt64Value())")
|
||||
return primaryContext
|
||||
} else {
|
||||
primaryContext = context
|
||||
let newPrimaryUserId = context.account.peerId.id._internalGetInt64Value()
|
||||
SGLogger.shared.log("SGIAP", "Primary context for user id \(primaryUserId) is nil! Falling back to current context with user id: \(context.account.peerId.id._internalGetInt64Value())")
|
||||
SGLogger.shared.log("SGIAP", "Setting new primary user id: \(primaryUserId)")
|
||||
SGSimpleSettings.shared.primaryUserId = String(newPrimaryUserId)
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
func sendReceiptForVerification(primaryContext: AccountContext) async {
|
||||
guard let receiptData = getPurchaceReceiptData() else {
|
||||
return
|
||||
}
|
||||
|
||||
let encodedReceiptData = receiptData.base64EncodedData(options: [])
|
||||
|
||||
var deviceToken: String?
|
||||
var apiToken: String?
|
||||
do {
|
||||
async let deviceTokenTask = getDeviceToken().awaitable()
|
||||
async let apiTokenTask = getSGApiToken(context: primaryContext).awaitable()
|
||||
|
||||
(deviceToken, apiToken) = try await (deviceTokenTask, apiTokenTask)
|
||||
} catch {
|
||||
SGLogger.shared.log("SGIAP", "Error getting device token or API token: \(error)")
|
||||
return
|
||||
}
|
||||
|
||||
if let deviceToken, let apiToken {
|
||||
do {
|
||||
let _ = try await postSGReceipt(token: apiToken,
|
||||
deviceToken: deviceToken,
|
||||
encodedReceiptData: encodedReceiptData).awaitable()
|
||||
} catch {
|
||||
SGLogger.shared.log("SGIAP", "Error: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchSGStatus(primaryContext: AccountContext) async {
|
||||
// let currentShouldKeepConnection = await (primaryContext.account.network.shouldKeepConnection.get() |> take(1) |> deliverOnMainQueue).awaitable()
|
||||
let currentShouldKeepConnection = false
|
||||
let userId = primaryContext.account.peerId.id._internalGetInt64Value()
|
||||
// SGLogger.shared.log("SGIAP", "User id \(userId) currently keeps connection: \(currentShouldKeepConnection)")
|
||||
if !currentShouldKeepConnection {
|
||||
SGLogger.shared.log("SGIAP", "Asking user id \(userId) to keep connection: true")
|
||||
primaryContext.account.network.shouldKeepConnection.set(.single(true))
|
||||
}
|
||||
let iqtpResponse = await sgIqtpQuery(engine: primaryContext.engine, query: makeIqtpQuery(0, "s")).awaitable()
|
||||
guard let iqtpResponse = iqtpResponse else {
|
||||
SGLogger.shared.log("SGIAP", "IQTP response is nil!")
|
||||
// if !currentShouldKeepConnection {
|
||||
// SGLogger.shared.log("SGIAP", "Setting user id \(userId) keep connection back to false")
|
||||
// primaryContext.account.network.shouldKeepConnection.set(.single(false))
|
||||
// }
|
||||
return
|
||||
}
|
||||
SGLogger.shared.log("SGIAP", "Got IQTP response: \(iqtpResponse)")
|
||||
let _ = await updateSGStatusInteractively(accountManager: primaryContext.sharedContext.accountManager, { value in
|
||||
var value = value
|
||||
|
||||
let newStatus: Int64
|
||||
if let description = iqtpResponse.description, let status = Int64(description) {
|
||||
newStatus = status
|
||||
} else {
|
||||
SGLogger.shared.log("SGIAP", "Can't parse IQTP response into status!")
|
||||
newStatus = value.status // unparseable
|
||||
}
|
||||
|
||||
let userId = primaryContext.account.peerId.id._internalGetInt64Value()
|
||||
if value.status != newStatus {
|
||||
SGLogger.shared.log("SGIAP", "Updating \(userId) status \(value.status) -> \(newStatus)")
|
||||
if newStatus > 1 {
|
||||
SGSimpleSettings.shared.primaryUserId = String(userId)
|
||||
}
|
||||
value.status = newStatus
|
||||
} else {
|
||||
SGLogger.shared.log("SGIAP", "Status \(value.status) for \(userId) hasn't changed")
|
||||
}
|
||||
return value
|
||||
}).awaitable()
|
||||
|
||||
// if !currentShouldKeepConnection {
|
||||
// SGLogger.shared.log("SGIAP", "Setting user id \(userId) keep connection back to false")
|
||||
// primaryContext.account.network.shouldKeepConnection.set(.single(false))
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
// MARK: Swiftgram
|
||||
import SGIAP
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
@ -240,6 +242,13 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
return self.immediateExperimentalUISettingsValue.with { $0 }
|
||||
}
|
||||
private var experimentalUISettingsDisposable: Disposable?
|
||||
|
||||
private var immediateSGStatusValue = Atomic<SGStatus>(value: SGStatus.default)
|
||||
public var immediateSGStatus: SGStatus {
|
||||
return self.immediateSGStatusValue.with { $0 }
|
||||
}
|
||||
private var sgStatusDisposable: Disposable?
|
||||
|
||||
|
||||
public var presentGlobalController: (ViewController, Any?) -> Void = { _, _ in }
|
||||
public var presentCrossfadeController: () -> Void = {}
|
||||
@ -468,6 +477,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
let _ = immediateExperimentalUISettingsValue.swap(settings)
|
||||
}
|
||||
})
|
||||
// MARK: Swiftgram
|
||||
let immediateSGStatusValue = self.immediateSGStatusValue
|
||||
self.sgStatusDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.sgStatus])
|
||||
|> deliverOnMainQueue).start(next: { sharedData in
|
||||
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.sgStatus]?.get(SGStatus.self) {
|
||||
let _ = immediateSGStatusValue.swap(settings)
|
||||
}
|
||||
})
|
||||
|
||||
let _ = self.contactDataManager?.personNameDisplayOrder().start(next: { order in
|
||||
let _ = updateContactSettingsInteractively(accountManager: accountManager, { settings in
|
||||
@ -3020,3 +3037,11 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Swiftgram
|
||||
//public extension SharedAccountContextImpl {
|
||||
// public func initSGIAP() {
|
||||
// self.sgIAP = SGIAP
|
||||
// }
|
||||
//}
|
||||
|
@ -1,9 +1,13 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
sgsrcs = [
|
||||
"//Swiftgram/SGStatus:SGStatus"
|
||||
]
|
||||
|
||||
swift_library(
|
||||
name = "TelegramUIPreferences",
|
||||
module_name = "TelegramUIPreferences",
|
||||
srcs = glob([
|
||||
srcs = sgsrcs + glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
|
@ -21,6 +21,8 @@ public struct ApplicationSpecificPreferencesKeys {
|
||||
}
|
||||
|
||||
private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
||||
// MARK: Swiftgram
|
||||
case sgStatus = 999
|
||||
case inAppNotificationSettings = 0
|
||||
case presentationPasscodeSettings = 1
|
||||
case automaticMediaDownloadSettings = 2
|
||||
@ -45,6 +47,8 @@ private enum ApplicationSpecificSharedDataKeyValues: Int32 {
|
||||
}
|
||||
|
||||
public struct ApplicationSpecificSharedDataKeys {
|
||||
// MARK: Swiftgram
|
||||
public static let sgStatus = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.sgStatus.rawValue)
|
||||
public static let inAppNotificationSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.inAppNotificationSettings.rawValue)
|
||||
public static let presentationPasscodeSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.presentationPasscodeSettings.rawValue)
|
||||
public static let automaticMediaDownloadSettings = applicationSpecificSharedDataKey(ApplicationSpecificSharedDataKeyValues.automaticMediaDownloadSettings.rawValue)
|
||||
|
Loading…
x
Reference in New Issue
Block a user