Update Ghostgram features

This commit is contained in:
ichmagmaus 812
2026-03-07 18:15:32 +01:00
parent 32b0dddcd9
commit 13a43b9068
902 changed files with 148302 additions and 62355 deletions
@@ -1,3 +1,6 @@
import SGStrings
import SGSimpleSettings
import Foundation
import SwiftSignalKit
import UIKit
@@ -156,6 +159,8 @@ public final class AccountContextImpl: AccountContext {
return self._countriesConfiguration.get()
}
public var watchManager: WatchManager?
private var storedPassword: (String, CFAbsoluteTime, SwiftSignalKit.Timer)?
private var limitsConfigurationDisposable: Disposable?
private var contentSettingsDisposable: Disposable?
@@ -830,6 +835,8 @@ public final class AccountContextImpl: AccountContext {
}
public func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void) {
// MARK: Swiftgram
let makeCall = {
guard let callResult = self.sharedContext.callManager?.requestCall(context: self, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false) else {
return
}
@@ -897,6 +904,19 @@ public final class AccountContextImpl: AccountContext {
} else {
completion()
}
// MARK: Swiftgram
}
if SGSimpleSettings.shared.confirmCalls {
let presentationData = self.sharedContext.currentPresentationData.with { $0 }
self.sharedContext.mainWindow?.present(textAlertController(context: self, title: nil, text: isVideo ? i18n("CallConfirmation.Video.Title", presentationData.strings.baseLanguageCode) : i18n("CallConfirmation.Audio.Title", presentationData.strings.baseLanguageCode), actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_No, action: {}), TextAlertAction(type: .destructiveAction, title: presentationData.strings.Common_Yes, action: { [weak self] in
guard let _ = self else {
return
}
makeCall()
})]), on: .root)
} else {
makeCall()
}
}
}
+321 -9
View File
@@ -1,5 +1,20 @@
// MARK: Swiftgram
import StoreKit
import SGIAP
import SGAPI
import SGDeviceToken
import SGAPIToken
import SGActionRequestHandlerSanitizer
import SGGHSettings
import SGAPIWebSettings
import SGLogging
import SGStrings
import SGSimpleSettings
import SGStatus
import SGSwiftSignalKit
import UIKit
@preconcurrency import SwiftSignalKit
import SwiftSignalKit
import Display
import TelegramCore
import UserNotifications
@@ -19,6 +34,7 @@ import OverlayStatusController
import UndoUI
import LegacyUI
import PassportUI
import WatchBridge
import SettingsUI
import AppBundle
import UrlHandling
@@ -41,8 +57,10 @@ import MediaEditor
import TelegramUIDeclareEncodables
import ContextMenuScreen
import MetalEngine
import RecaptchaEnterpriseSDK
import RecaptchaEnterprise
import NavigationBarImpl
import ContextUI
import ContextControllerImpl
#if canImport(AppCenter)
import AppCenter
@@ -150,6 +168,22 @@ private func legacyDocumentsPath() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/legacy"
}
private func getContextForUserId(context: AccountContext, userId: Int64) -> Signal<AccountContext, NoError> {
return context.sharedContext.activeAccountContexts
|> take(1)
|> mapToSignal { primary, accounts, _ -> Signal<AccountContext, NoError> in
if let primary, primary.account.peerId.id._internalGetInt64Value() == userId {
return .single(primary)
}
if let match = accounts.first(where: { account in
account.1.account.peerId.id._internalGetInt64Value() == userId
})?.1 {
return .single(match)
}
return .complete()
}
}
protocol SupportedStartCallIntent {
@available(iOS 10.0, *)
var contacts: [INPerson]? { get }
@@ -228,6 +262,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
let hasActiveAudioSession = Promise<Bool>(false)
private let sharedContextPromise = Promise<SharedApplicationContext>()
private let watchCommunicationManagerPromise = Promise<WatchCommunicationManager?>()
private var accountManager: AccountManager<TelegramAccountManagerTypes>?
private var accountManagerState: AccountManagerState?
@@ -589,6 +624,12 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
let rootPath = rootPathForBasePath(appGroupUrl.path)
// MARK: Swiftgram
if UserDefaults.standard.bool(forKey: "sg_db_hard_reset") {
self.window?.makeKeyAndVisible()
sgHardReset(dataPath: rootPath, present: self.mainWindow?.presentNative)
return true
}
performAppGroupUpgrades(appGroupPath: appGroupUrl.path, rootPath: rootPath)
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
@@ -882,6 +923,31 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
icons.append(PresentationAppIcon(name: "PremiumTurbo", imageName: "PremiumTurbo", isPremium: true))
icons.append(PresentationAppIcon(name: "PremiumBlack", imageName: "PremiumBlack", isPremium: true))
// MARK: Swiftgram
icons = [
PresentationAppIcon(name: "SGDefault", imageName: "SGDefault", isDefault: true),
PresentationAppIcon(name: "SGBlack", imageName: "SGBlack"),
PresentationAppIcon(name: "SGLegacy", imageName: "SGLegacy"),
PresentationAppIcon(name: "SGInverted", imageName: "SGInverted"),
PresentationAppIcon(name: "SGWhite", imageName: "SGWhite"),
PresentationAppIcon(name: "SGNight", imageName: "SGNight"),
PresentationAppIcon(name: "SGSky", imageName: "SGSky"),
PresentationAppIcon(name: "SGTitanium", imageName: "SGTitanium"),
PresentationAppIcon(name: "SGPro", imageName: "SGPro", isSGPro: true),
PresentationAppIcon(name: "SGDay", imageName: "SGDay", isSGPro: true),
PresentationAppIcon(name: "SGGold", imageName: "SGGold", isSGPro: true),
SGSimpleSettings.shared.duckyAppIconAvailable ? PresentationAppIcon(name: "SGDucky", imageName: "SGDucky", isSGPro: true) : PresentationAppIcon(name: "", imageName: ""), // Empty
PresentationAppIcon(name: "SGNeon", imageName: "SGNeon"),
PresentationAppIcon(name: "SGNeonBlue", imageName: "SGNeonBlue"),
PresentationAppIcon(name: "SGGlass", imageName: "SGGlass"),
PresentationAppIcon(name: "SGSparkling", imageName: "SGSparkling"),
]
if Bundle.main.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" {
icons.append(PresentationAppIcon(name: "SGBeta", imageName: "SGBeta"))
}
return icons
} else {
return []
@@ -1052,7 +1118,26 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
return .single(nil)
}
}
let watchTasks = self.context.get()
|> mapToSignal { context -> Signal<AccountRecordId?, NoError> in
if let context = context, let watchManager = context.context.watchManager {
let accountId = context.context.account.id
let runningTasks: Signal<WatchRunningTasks?, NoError> = .single(nil)
|> then(watchManager.runningTasks)
return runningTasks
|> distinctUntilChanged
|> map { value -> AccountRecordId? in
if let value = value, value.running {
return accountId
} else {
return nil
}
}
|> distinctUntilChanged
} else {
return .single(nil)
}
}
let wakeupManager = SharedWakeupManager(beginBackgroundTask: { name, expiration in
let id = application.beginBackgroundTask(withName: name, expirationHandler: expiration)
Logger.shared.log("App \(self.episodeId)", "Begin background task \(name): \(id)")
@@ -1064,7 +1149,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
application.endBackgroundTask(id)
}, backgroundTimeRemaining: { application.backgroundTimeRemaining }, acquireIdleExtension: {
return applicationBindings.pushIdleTimerExtension()
}, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: liveLocationPolling, watchTasks: .single(nil), inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: self.hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
}, activeAccounts: sharedContext.activeAccountContexts |> map { ($0.0?.account, $0.1.map { ($0.0, $0.1.account) }) }, liveLocationPolling: liveLocationPolling, watchTasks: watchTasks /* MARK: Swiftgram */, inForeground: applicationBindings.applicationInForeground, hasActiveAudioSession: self.hasActiveAudioSession.get(), notificationManager: notificationManager, mediaManager: sharedContext.mediaManager, callManager: sharedContext.callManager, accountUserInterfaceInUse: { id in
return sharedContext.accountUserInterfaceInUse(id)
})
let sharedApplicationContext = SharedApplicationContext(sharedContext: sharedContext, notificationManager: notificationManager, wakeupManager: wakeupManager)
@@ -1075,13 +1160,16 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
}
self.sharedContextPromise.set(sharedContextSignal
|> mapToSignal { sharedApplicationContext, loggingSettings -> Signal<SharedApplicationContext, NoError> in
|> mapToSignal { value -> Signal<SharedApplicationContext, NoError> in
let (sharedApplicationContext, loggingSettings) = value
Logger.shared.logToFile = loggingSettings.logToFile
Logger.shared.logToConsole = loggingSettings.logToConsole
Logger.shared.redactSensitiveData = loggingSettings.redactSensitiveData
return .single(sharedApplicationContext)
})
let watchManagerArgumentsPromise = Promise<WatchManagerArguments?>()
self.context.set(self.sharedContextPromise.get()
|> deliverOnMainQueue
@@ -1096,7 +1184,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
return true
})
|> mapToSignal { context -> Signal<(AccountContext, CallListSettings)?, NoError> in
|> mapToSignal { (context: AccountContext?) -> Signal<(AccountContext, CallListSettings)?, NoError> in
return sharedApplicationContext.sharedContext.accountManager.transaction { transaction -> CallListSettings? in
return transaction.getSharedData(ApplicationSpecificSharedDataKeys.callListSettings)?.get(CallListSettings.self)
}
@@ -1118,9 +1206,9 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
}
|> deliverOnMainQueue
|> map { accountAndSettings -> AuthorizedApplicationContext? in
|> map { (accountAndSettings: (AccountContext, CallListSettings)?) -> AuthorizedApplicationContext? in
return accountAndSettings.flatMap { context, callListSettings in
return AuthorizedApplicationContext(sharedApplicationContext: sharedApplicationContext, mainWindow: self.mainWindow, context: context as! AccountContextImpl, accountManager: sharedApplicationContext.sharedContext.accountManager, showCallsTab: callListSettings.showTab, reinitializedNotificationSettings: {
return AuthorizedApplicationContext(sharedApplicationContext: sharedApplicationContext, mainWindow: self.mainWindow, watchManagerArguments: watchManagerArgumentsPromise.get(), context: context as! AccountContextImpl, accountManager: sharedApplicationContext.sharedContext.accountManager, showContactsTab: callListSettings.showContactsTab, showCallsTab: callListSettings.showTab, reinitializedNotificationSettings: {
let _ = (self.context.get()
|> take(1)
|> deliverOnMainQueue).start(next: { context in
@@ -1197,6 +1285,8 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
var network: Network?
if let context = context {
network = context.context.account.network
// MARK: Swiftgram
sgDBResetIfNeeded(databasePath: context.context.sharedContext.accountManager.basePath + "/db", present: self.mainWindow?.presentNative)
}
Logger.shared.log("App \(self.episodeId)", "received context \(String(describing: context)) account \(String(describing: context?.context.account.id)) network \(String(describing: network))")
@@ -1242,6 +1332,21 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
self.registerForNotifications(context: context.context, authorize: authorizeNotifications)
self.resetIntentsIfNeeded(context: context.context)
// MARK: Swiftgram
updateSGWebSettingsInteractivelly(context: context.context)
updateSGGHSettingsInteractivelly(context: context.context)
let _ = (context.context.sharedContext.presentationData.start(next: { presentationData in
SGLocalizationManager.shared.downloadLocale(presentationData.strings.baseLanguageCode)
}))
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
@@ -1311,6 +1416,12 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
authContextReadyDisposable.set(nil)
}
}))
// MARK: Swiftgram
if #available(iOS 13.0, *) {
self.setupIAP()
}
let logoutDataSignal: Signal<(AccountManager, Set<PeerId>), NoError> = self.sharedContextPromise.get()
@@ -1346,6 +1457,20 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}).start()
}))
self.watchCommunicationManagerPromise.set(watchCommunicationManager(context: self.context.get() |> flatMap { WatchCommunicationManagerContext(context: $0.context) }, allowBackgroundTimeExtension: { timeout in
let _ = (self.sharedContextPromise.get()
|> take(1)).start(next: { sharedContext in
sharedContext.wakeupManager.allowBackgroundTimeExtension(timeout: timeout)
})
}))
let _ = self.watchCommunicationManagerPromise.get().start(next: { manager in
if let manager = manager {
watchManagerArgumentsPromise.set(.single(manager.arguments))
} else {
watchManagerArgumentsPromise.set(.single(nil))
}
})
self.resetBadge()
if #available(iOS 9.1, *) {
@@ -1392,7 +1517,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
if let url = launchOptions?[.url] {
if let url = url as? URL, url.scheme == "tg" || url.scheme == buildConfig.appSpecificUrlScheme {
self.openUrlWhenReady(url: url)
self.openUrlWhenReady(url: sgActionRequestHandlerSanitizer(url))
} else if let urlString = url as? String, urlString.lowercased().hasPrefix("tg:") || urlString.lowercased().hasPrefix("\(buildConfig.appSpecificUrlScheme):"), let url = URL(string: urlString) {
self.openUrlWhenReady(url: url)
}
@@ -1892,6 +2017,10 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
func runForegroundTasks() {
var sgTasksLaunched: Bool = false
let _ = (self.sharedContextPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { sharedApplicationContext in
@@ -1899,6 +2028,12 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|> take(1)
|> deliverOnMainQueue).start(next: { activeAccounts in
for (_, context, _) in activeAccounts.accounts {
// MARK: Swiftgram
if !sgTasksLaunched {
updateSGWebSettingsInteractivelly(context: context)
updateSGGHSettingsInteractivelly(context: context)
sgTasksLaunched = true
}
(context.downloadedMediaStoreManager as? DownloadedMediaStoreManagerImpl)?.runTasks()
}
})
@@ -2414,6 +2549,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
}
}
|> deliverOnMainQueue).start(next: { sharedContext, context, authContext in
let url = sgActionRequestHandlerSanitizer(url)
if let authContext = authContext, let confirmationCode = parseConfirmationCodeUrl(sharedContext: sharedContext, url: url) {
authContext.rootController.applyConfirmationCode(confirmationCode)
} else if let context = context {
@@ -3211,3 +3347,179 @@ final class UpdateSettings: Codable, Equatable {
return lhs.url == rhs.url
}
}
// MARK: Swiftgram
@available(iOS 13.0, *)
extension AppDelegate {
func setupIAP() {
NotificationCenter.default.addObserver(forName: .SGIAPHelperPurchaseNotification, object: nil, queue: nil) { [weak self] notification in
SGLogger.shared.log("SGIAP", "Got SGIAPHelperPurchaseNotification")
guard let strongSelf = self else { return }
if let transactions = notification.object as? [SKPaymentTransaction] {
let _ = (strongSelf.context.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak strongSelf] context in
guard let veryStrongSelf = strongSelf else {
SGLogger.shared.log("SGIAP", "Finishing transactions \(transactions.map({ $0.transactionIdentifier ?? "nil" }).joined(separator: ", "))")
let defaultPaymentQueue = SKPaymentQueue.default()
for transaction in transactions {
defaultPaymentQueue.finishTransaction(transaction)
}
return
}
guard let context = context else {
SGLogger.shared.log("SGIAP", "Empty app context (how?)")
SGLogger.shared.log("SGIAP", "Finishing transactions \(transactions.map({ $0.transactionIdentifier ?? "nil" }).joined(separator: ", "))")
let defaultPaymentQueue = SKPaymentQueue.default()
for transaction in transactions {
defaultPaymentQueue.finishTransaction(transaction)
}
return
}
SGLogger.shared.log("SGIAP", "Got context for SGIAPHelperPurchaseNotification")
let _ = Task {
await veryStrongSelf.sendReceiptForVerification(primaryContext: context.context)
await veryStrongSelf.fetchSGStatus(primaryContext: context.context)
SGLogger.shared.log("SGIAP", "Finishing transactions \(transactions.map({ $0.transactionIdentifier ?? "nil" }).joined(separator: ", "))")
let defaultPaymentQueue = SKPaymentQueue.default()
for transaction in transactions {
defaultPaymentQueue.finishTransaction(transaction)
}
}
})
} else {
SGLogger.shared.log("SGIAP", "Wrong object in SGIAPHelperPurchaseNotification")
#if DEBUG
preconditionFailure("Wrong object in SGIAPHelperPurchaseNotification")
#endif
}
}
}
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()
}
var primaryContext = try? 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: \(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 let error as SignalCompleted {
let _ = error
} catch {
SGLogger.shared.log("SGIAP", "Error: \(error)")
}
}
}
func fetchSGStatus(primaryContext: AccountContext) async {
// TODO(swiftgram): Stuck on getting shouldKeepConnection
// Perhaps, we can drop on some timeout?
// let currentShouldKeepConnection = await (primaryContext.account.network.shouldKeepConnection.get() |> take(1) |> deliverOnMainQueue).awaitable()
guard !primaryContext.account.testingEnvironment else {
return
}
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))
}
// MARK: Swiftgram
let sgIqtpQueryString = makeIqtpQuery("s")
//
let iqtpResponse = try? await sgIqtpQuery(engine: primaryContext.engine, query: sgIqtpQueryString).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))
// }
DispatchQueue.main.async {
NotificationCenter.default.post(name: .SGIAPHelperValidationErrorNotification, object: nil, userInfo: ["error": "PayWall.ValidationError.TryAgain"])
}
return
}
SGLogger.shared.log("SGIAP", "Got IQTP response: \(iqtpResponse)")
let _ = try? await updateSGStatusInteractively(accountManager: primaryContext.sharedContext.accountManager, { value in
var value = value
let newStatus: Int64
if let status = Int64(iqtpResponse.value) {
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 {
let stringUserId = String(userId)
if SGSimpleSettings.shared.primaryUserId != stringUserId {
SGLogger.shared.log("SGIAP", "Setting new primary user id: \(userId)")
SGSimpleSettings.shared.primaryUserId = stringUserId
}
} else {
SGLogger.shared.log("SGIAP", "Status expired")
DispatchQueue.main.async {
NotificationCenter.default.post(name: .SGIAPHelperValidationErrorNotification, object: nil, userInfo: ["error": "PayWall.ValidationError.Expired"])
}
}
value.status = newStatus
} else {
SGLogger.shared.log("SGIAP", "Status \(value.status) for \(userId) hasn't changed")
if newStatus < 2 {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .SGIAPHelperValidationErrorNotification, object: nil, userInfo: ["error": "PayWall.ValidationError.TryAgain"])
}
}
}
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 SGSimpleSettings
import Foundation
import UIKit
import Intents
@@ -20,6 +22,7 @@ import TelegramPermissionsUI
import PasscodeUI
import ImageBlur
import FastBlur
import WatchBridge
import SettingsUI
import AppLock
import AccountUtils
@@ -151,11 +154,12 @@ final class AuthorizedApplicationContext {
private var applicationInForegroundDisposable: Disposable?
private var showContactsTab: Bool
private var showCallsTab: Bool
private var showCallsTabDisposable: Disposable?
private var enablePostboxTransactionsDiposable: Disposable?
init(sharedApplicationContext: SharedApplicationContext, mainWindow: Window1, context: AccountContextImpl, accountManager: AccountManager<TelegramAccountManagerTypes>, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) {
init(sharedApplicationContext: SharedApplicationContext, mainWindow: Window1, watchManagerArguments: Signal<WatchManagerArguments?, NoError>, context: AccountContextImpl, accountManager: AccountManager<TelegramAccountManagerTypes>, showContactsTab: Bool, showCallsTab: Bool, reinitializedNotificationSettings: @escaping () -> Void) {
self.sharedApplicationContext = sharedApplicationContext
setupLegacyComponents(context: context)
@@ -166,6 +170,8 @@ final class AuthorizedApplicationContext {
self.context = context
self.showContactsTab = showContactsTab
self.showCallsTab = showCallsTab
self.notificationController = NotificationContainerController(context: context)
@@ -249,7 +255,7 @@ final class AuthorizedApplicationContext {
}
if self.rootController.rootTabController == nil {
self.rootController.addRootControllers(showCallsTab: self.showCallsTab)
self.rootController.addRootControllers(hidePhoneInSettings: SGSimpleSettings.shared.hidePhoneInSettings, showContactsTab: self.showContactsTab, showCallsTab: self.showCallsTab)
}
if let tabsController = self.rootController.viewControllers.first as? TabBarController, !tabsController.controllers.isEmpty, tabsController.selectedIndex >= 0 {
let controller = tabsController.controllers[tabsController.selectedIndex]
@@ -782,22 +788,81 @@ final class AuthorizedApplicationContext {
})
let showCallsTabSignal = context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.callListSettings])
|> map { sharedData -> Bool in
var value = CallListSettings.defaultSettings.showTab
|> map { sharedData -> (Bool, Bool) in
var showCallsTabValue = CallListSettings.defaultSettings.showTab
var showContactsTabValue = CallListSettings.defaultSettings.showContactsTab
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.callListSettings]?.get(CallListSettings.self) {
value = settings.showTab
showCallsTabValue = settings.showTab
showContactsTabValue = settings.showContactsTab
}
return value
return (showContactsTabValue, showCallsTabValue)
}
self.showCallsTabDisposable = (showCallsTabSignal |> deliverOnMainQueue).start(next: { [weak self] value in
self.showCallsTabDisposable = (showCallsTabSignal |> deliverOnMainQueue).start(next: { [weak self] values in
if let strongSelf = self {
if strongSelf.showCallsTab != value {
strongSelf.showCallsTab = value
strongSelf.rootController.updateRootControllers(showCallsTab: value)
let (showContactsTabValue, showCallsTabValue) = values
var needControllersUpdate = false
if strongSelf.showCallsTab != showCallsTabValue {
needControllersUpdate = true
strongSelf.showCallsTab = showCallsTabValue
}
if strongSelf.showContactsTab != showContactsTabValue {
needControllersUpdate = true
strongSelf.showContactsTab = showContactsTabValue
}
if needControllersUpdate {
strongSelf.rootController.updateRootControllers(showContactsTab: showContactsTabValue, showCallsTab: showCallsTabValue)
}
}
})
let _ = (watchManagerArguments
|> deliverOnMainQueue).start(next: { [weak self] arguments in
guard let strongSelf = self else {
return
}
let watchManager = WatchManagerImpl(arguments: arguments)
strongSelf.context.watchManager = watchManager
strongSelf.watchNavigateToMessageDisposable.set((strongSelf.context.sharedContext.applicationBindings.applicationInForeground |> mapToSignal({ applicationInForeground -> Signal<(Bool, MessageId), NoError> in
return watchManager.navigateToMessageRequested
|> map { messageId in
return (applicationInForeground, messageId)
}
|> deliverOnMainQueue
})).start(next: { [weak self] applicationInForeground, messageId in
if let strongSelf = self {
if applicationInForeground {
var chatIsVisible = false
if let controller = strongSelf.rootController.viewControllers.last as? ChatControllerImpl, case .peer(messageId.peerId) = controller.chatLocation {
chatIsVisible = true
}
let navigateToMessage = {
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|> deliverOnMainQueue).start(next: { peer in
guard let peer = peer else {
return
}
strongSelf.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: strongSelf.rootController, context: strongSelf.context, chatLocation: .peer(peer), subject: .message(id: .id(messageId), highlight: ChatControllerSubject.MessageHighlight(quote: nil), timecode: nil, setupReply: false)))
})
}
if chatIsVisible {
navigateToMessage()
} else {
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let controller = textAlertController(context: strongSelf.context, title: presentationData.strings.WatchRemote_AlertTitle, text: presentationData.strings.WatchRemote_AlertText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.WatchRemote_AlertOpen, action:navigateToMessage)])
(strongSelf.rootController.viewControllers.last as? ViewController)?.present(controller, in: .window(.root))
}
} else {
//strongSelf.notificationManager.presentWatchContinuityNotification(context: strongSelf.context, messageId: messageId)
}
}
}))
})
self.rootController.setForceInCallStatusBar((self.context.sharedContext as! SharedAccountContextImpl).currentCallStatusBarNode)
if let groupCallController = self.context.sharedContext.currentGroupCallController as? VoiceChatController {
if let overlayController = groupCallController.currentOverlayController {
@@ -2008,7 +2008,7 @@ extension ChatControllerImpl {
}
}
}))
}, forwardSelectedMessages: { [weak self] in
}, forwardSelectedMessages: { [weak self] _ in
if let strongSelf = self {
strongSelf.commitPurposefulAction()
if let forwardMessageIdsSet = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds {
@@ -2023,7 +2023,7 @@ extension ChatControllerImpl {
strongSelf.forwardMessages(messageIds: forwardMessageIds, options: strongSelf.presentationInterfaceState.interfaceState.forwardOptionsState, resetCurrent: true)
}
}
}, forwardMessages: { [weak self] messages in
}, forwardMessages: { [weak self] messages, action in
if let strongSelf = self, !messages.isEmpty {
guard !strongSelf.presentAccountFrozenInfoIfNeeded(delay: true) else {
return
@@ -2031,7 +2031,16 @@ extension ChatControllerImpl {
strongSelf.commitPurposefulAction()
let forwardMessageIds = messages.map { $0.id }.sorted()
strongSelf.forwardMessages(messageIds: forwardMessageIds)
switch action {
case "forwardMessagesToCloud":
strongSelf.forwardMessagesToCloud(messageIds: forwardMessageIds, removeNames: false, openCloud: false)
case "forwardMessagesWithNoNames":
strongSelf.forwardMessages(messageIds: forwardMessageIds, options: ChatInterfaceForwardOptionsState(hideNames: true, hideCaptions: false, unhideNamesOnCaptionChange: false))
case "forwardMessagesToCloudWithNoNamesAndOpen":
strongSelf.forwardMessagesToCloud(messageIds: forwardMessageIds, removeNames: true, openCloud: true)
default:
strongSelf.forwardMessages(messageIds: forwardMessageIds)
}
}
}, updateForwardOptionsState: { [weak self] f in
if let strongSelf = self {
@@ -1,3 +1,7 @@
// MARK: Swiftgram
import SGSimpleSettings
import TextFormat
import TranslateUI
import Foundation
import UIKit
import AsyncDisplayKit
@@ -84,6 +88,47 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
return
}
// MARK: Swiftgram
let outgoingMessageTranslateToLang = SGSimpleSettings.shared.outgoingLanguageTranslation[SGSimpleSettings.makeOutgoingLanguageTranslationKey(accountId: selfController.context.account.peerId.id._internalGetInt64Value(), peerId: peer.id.id._internalGetInt64Value())] ?? selfController.contentData?.state.predictedChatLanguage
let sgTranslationContext: (outgoingMessageTranslateToLang: String?, translate: (() -> Void)?, changeTranslationLanguage: (() -> ())?) = (outgoingMessageTranslateToLang: outgoingMessageTranslateToLang, translate: { [weak selfController] in
guard let selfController else { return }
let textToTranslate = selfController.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string
let textEntities = selfController.presentationInterfaceState.interfaceState.synchronizeableInputState?.entities ?? []
if let outgoingMessageTranslateToLang = outgoingMessageTranslateToLang {
let _ = (selfController.context.engine.messages.translate(text: textToTranslate, toLang: outgoingMessageTranslateToLang, entities: textEntities) |> deliverOnMainQueue).start(next: { [weak selfController] translatedTextAndEntities in
guard let selfController, let translatedTextAndEntities else { return }
let newInputText = chatInputStateStringWithAppliedEntities(translatedTextAndEntities.0, entities: translatedTextAndEntities.1)
let newTextInputState = ChatTextInputState(inputText: newInputText, selectionRange: 0 ..< newInputText.length)
selfController.updateChatPresentationInterfaceState(interactive: true, { state in
return state.updatedInterfaceState { interfaceState in
return interfaceState.withUpdatedEffectiveInputState(newTextInputState)
}
})
})
}
}, changeTranslationLanguage: { [weak selfController] in
guard let selfController else { return }
let controller = languageSelectionController(context: selfController.context, forceTheme: selfController.presentationData.theme, fromLanguage: "", toLanguage: selfController.presentationInterfaceState.translationState?.fromLang ?? "", completion: { _, toLang in
guard let peerId = selfController.chatLocation.peerId else {
return
}
var langCode = toLang
if langCode == "nb" {
langCode = "no"
} else if langCode == "pt-br" {
langCode = "pt"
}
if !toLang.isEmpty {
SGSimpleSettings.shared.outgoingLanguageTranslation[SGSimpleSettings.makeOutgoingLanguageTranslationKey(accountId: selfController.context.account.peerId.id._internalGetInt64Value(), peerId: peerId.id._internalGetInt64Value())] = langCode
}
chatMessageDisplaySendMessageOptions(selfController: selfController, node: node, gesture: gesture)
})
controller.navigationPresentation = .modal
selfController.push(controller)
})
if let editMessage = selfController.presentationInterfaceState.interfaceState.editMessage {
if editMessages.isEmpty {
return
@@ -122,6 +167,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
}
let controller = makeChatSendMessageActionSheetController(
sgTranslationContext: sgTranslationContext,
initialData: initialData,
context: selfController.context,
updatedPresentationData: selfController.updatedPresentationData,
@@ -213,6 +259,7 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no
}
let controller = makeChatSendMessageActionSheetController(
sgTranslationContext: sgTranslationContext,
initialData: initialData,
context: selfController.context,
updatedPresentationData: selfController.updatedPresentationData,
File diff suppressed because it is too large Load Diff
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import TelegramPresentationData
import AccountContext
@@ -136,6 +137,10 @@ extension ChatControllerImpl {
var voiceMessagesAvailable: Bool = true
var requestsState: PeerInvitationImportersState?
var dismissedInvitationRequests: [Int64]?
// MARK: Swiftgram
var predictedChatLanguage: String?
var customEmojiAvailable: Bool = false
var threadData: ChatPresentationInterfaceState.ThreadData?
var forumTopicData: ChatPresentationInterfaceState.ThreadData?
@@ -163,6 +168,9 @@ extension ChatControllerImpl {
private var premiumGiftSuggestionDisposable: Disposable?
private var translationStateDisposable: Disposable?
// MARK: Swiftgram
private var chatLanguagePredictionDisposable: Disposable?
private let isPeerInfoReady = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isChatLocationInfoReady = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isCachedDataReady = ValuePromise<Bool>(false, ignoreRepeated: true)
@@ -893,8 +901,7 @@ extension ChatControllerImpl {
peerVerification = cachedChannelData.verification
}
}
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
copyProtectionEnabled = peer.isCopyProtectionEnabled
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
@@ -983,7 +990,9 @@ extension ChatControllerImpl {
}
var adMessage = adMessage
if let peer = peerView.peers[peerView.peerId] as? TelegramUser, peer.botInfo != nil {
if MiscSettingsManager.shared.shouldBlockAds {
adMessage = nil
} else if let peer = peerView.peers[peerView.peerId] as? TelegramUser, peer.botInfo != nil {
} else {
adMessage = nil
}
@@ -1028,7 +1037,7 @@ extension ChatControllerImpl {
} else {
isRegularChat = true
}
if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack {
if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack, !SGSimpleSettings.shared.disableScrollToNextChannel {
if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 {
let nextPeerId = customChatNavigationStack[index + 1]
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
@@ -1072,7 +1081,7 @@ extension ChatControllerImpl {
}
})
}
} else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil {
} else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil, !SGSimpleSettings.shared.disableScrollToNextChannel {
//TODO:loc optimize
let accountPeerId = context.account.peerId
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
@@ -1372,8 +1381,7 @@ extension ChatControllerImpl {
var alwaysShowGiftButton = false
var disallowedGifts: TelegramDisallowedGifts?
if let peer = peerView.peers[peerView.peerId] {
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
copyProtectionEnabled = MiscSettingsManager.shared.shouldBypassCopyProtection ? false : peer.isCopyProtectionEnabled
copyProtectionEnabled = peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
if case let .known(value) = cachedData.businessIntro {
@@ -1701,7 +1709,7 @@ extension ChatControllerImpl {
strongSelf.state.alwaysShowGiftButton = alwaysShowGiftButton
strongSelf.state.disallowedGifts = disallowedGifts
if let replyThreadId, let channel = renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, strongSelf.nextChannelToReadDisposable == nil {
if let replyThreadId, let channel = renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, strongSelf.nextChannelToReadDisposable == nil, !SGSimpleSettings.shared.disableScrollToNextTopic {
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
context.engine.peers.getNextUnreadForumTopic(peerId: channel.id, topicId: Int32(clamping: replyThreadId)),
ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager)
@@ -2112,7 +2120,8 @@ extension ChatControllerImpl {
if counterAndTimestamp.0 >= 3 {
maybeSuggestPremium = true
}
if (isPremium || maybeSuggestPremium || hasAutoTranslate) && !isHidden {
let sgTranslationAvailable = SGSimpleSettings.shared.translationBackendEnum != .default
if (isPremium || maybeSuggestPremium || hasAutoTranslate || sgTranslationAvailable) && !isHidden {
return chatTranslationState(context: context, peerId: peerId, threadId: chatLocation.threadId)
|> map { translationState -> ChatPresentationTranslationState? in
if let translationState, !translationState.fromLang.isEmpty && (translationState.fromLang != baseLanguageCode || translationState.isEnabled) {
@@ -2137,6 +2146,27 @@ extension ChatControllerImpl {
strongSelf.onUpdated?(previousState)
})
// MARK: Swiftgram
self.chatLanguagePredictionDisposable = (
chatTranslationState(context: context, peerId: peerId, threadId: chatLocation.threadId)
|> map { translationState -> ChatPresentationTranslationState? in
if let translationState, !translationState.fromLang.isEmpty {
return ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? baseLanguageCode)
} else {
return nil
}
}
|> distinctUntilChanged
|> deliverOnMainQueue).startStrict(next: { [weak self] translationState in
if let strongSelf = self, let translationState = translationState, strongSelf.state.predictedChatLanguage == nil {
let previousState = strongSelf.state
strongSelf.state.predictedChatLanguage = translationState.fromLang
strongSelf.onUpdated?(previousState)
}
})
}
let premiumGiftOptions: Signal<[CachedPremiumGiftOption], NoError> = .single([])
@@ -2400,6 +2430,8 @@ extension ChatControllerImpl {
self.cachedDataDisposable?.dispose()
self.premiumGiftSuggestionDisposable?.dispose()
self.translationStateDisposable?.dispose()
// MARK: Swiftgram
self.chatLanguagePredictionDisposable?.dispose()
self.inviteRequestsDisposable?.dispose()
}
}
@@ -15,7 +15,165 @@ import ReactionSelectionNode
import TopMessageReactions
import ChatMessagePaymentAlertController
enum ForwardEnqueueMessagesResult {
case messages([EnqueueMessage])
case unsupported
}
extension ChatControllerImpl {
private func forwardOptionsAttributes(_ options: ChatInterfaceForwardOptionsState?) -> [MessageAttribute] {
return [
ForwardOptionsMessageAttribute(
hideNames: options?.hideNames == true,
hideCaptions: options?.hideCaptions == true
)
]
}
private func shouldUsePlainCopyForward(for message: Message) -> Bool {
return MiscSettingsManager.shared.shouldBypassCopyProtection && message.isCopyProtectedIgnoringBypass()
}
private func forwardMessagesNeedPlainCopy(_ messages: [Message]) -> Bool {
return messages.contains(where: self.shouldUsePlainCopyForward(for:))
}
private func plainCopyForwardMediaReference(for message: Message) -> AnyMediaReference? {
var supportedMedia: Media?
for media in message.media {
switch media {
case is TelegramMediaAction, is TelegramMediaPoll, is TelegramMediaTodo, is TelegramMediaDice, is TelegramMediaPaidContent, is TelegramMediaExpiredContent, is TelegramMediaStory, is TelegramMediaInvoice:
return nil
case is TelegramMediaWebpage:
continue
case is TelegramMediaImage, is TelegramMediaFile:
if supportedMedia != nil {
return nil
}
supportedMedia = media
case is TelegramMediaMap, is TelegramMediaContact:
if supportedMedia != nil {
return nil
}
supportedMedia = media
default:
return nil
}
}
guard let supportedMedia else {
return nil
}
if supportedMedia is TelegramMediaImage || supportedMedia is TelegramMediaFile {
return .message(message: MessageReference(message), media: supportedMedia)
} else {
return .standalone(media: supportedMedia)
}
}
private func plainCopyForwardMessage(
_ message: Message,
options: ChatInterfaceForwardOptionsState?,
threadId: Int64?,
localGroupingKey: Int64?
) -> EnqueueMessage? {
if message.id.peerId.namespace == Namespaces.Peer.SecretChat || message.containsSecretMedia || message.minAutoremoveOrClearTimeout == viewOnceTimeout {
return nil
}
let mediaReference = self.plainCopyForwardMediaReference(for: message)
if !message.media.isEmpty && mediaReference == nil {
return nil
}
var text = message.text
var entities = message.textEntitiesAttribute?.entities ?? []
if options?.hideCaptions == true, mediaReference != nil {
text = ""
entities = []
}
if mediaReference == nil && text.isEmpty {
return nil
}
var attributes: [MessageAttribute] = []
if !entities.isEmpty {
attributes.append(TextEntitiesMessageAttribute(entities: entities))
}
for attribute in message.attributes {
if attribute is MediaSpoilerMessageAttribute || attribute is InvertMediaMessageAttribute {
attributes.append(attribute)
}
}
return .message(
text: text,
attributes: attributes,
inlineStickers: [:],
mediaReference: mediaReference,
threadId: threadId,
replyToMessageId: nil,
replyToStoryId: nil,
localGroupingKey: localGroupingKey,
correlationId: nil,
bubbleUpEmojiOrStickersets: []
)
}
func buildForwardEnqueueMessages(
from messages: [Message],
options: ChatInterfaceForwardOptionsState?,
threadId: Int64?
) -> ForwardEnqueueMessagesResult {
let forwardAttributes = self.forwardOptionsAttributes(options)
var groupingKeyMap: [Int64: Int64] = [:]
var result: [EnqueueMessage] = []
for message in messages {
if self.shouldUsePlainCopyForward(for: message) {
let localGroupingKey: Int64?
if let groupingKey = message.groupingKey {
if let existingKey = groupingKeyMap[groupingKey] {
localGroupingKey = existingKey
} else {
let newKey = Int64.random(in: Int64.min ... Int64.max)
groupingKeyMap[groupingKey] = newKey
localGroupingKey = newKey
}
} else {
localGroupingKey = nil
}
guard let plainCopyMessage = self.plainCopyForwardMessage(message, options: options, threadId: threadId, localGroupingKey: localGroupingKey) else {
return .unsupported
}
result.append(plainCopyMessage)
} else {
result.append(.forward(source: message.id, threadId: threadId, grouping: .auto, attributes: forwardAttributes, correlationId: nil))
}
}
return .messages(result)
}
func presentUnsupportedProtectedForwardAlert(in controller: ViewController?) {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let text = "This protected message type can't be copied yet."
let alert = textAlertController(
context: self.context,
title: nil,
text: text,
actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]
)
if let controller {
controller.present(alert, in: .window(.root))
} else {
self.present(alert, in: .window(.root))
}
}
func forwardMessages(messageIds: [MessageId], options: ChatInterfaceForwardOptionsState? = nil, resetCurrent: Bool = false) {
let _ = (self.context.engine.data.get(EngineDataMap(
messageIds.map(TelegramEngine.EngineData.Item.Messages.Message.init)
@@ -53,7 +211,7 @@ extension ChatControllerImpl {
var attemptSelectionImpl: ((EnginePeer, ChatListDisabledPeerReason) -> Void)?
let controller = self.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: self.context, updatedPresentationData: self.updatedPresentationData, filter: filter, hasFilters: true, attemptSelection: { peer, _, reason in
attemptSelectionImpl?(peer, reason)
}, multipleSelection: true, forwardedMessageIds: messages.map { $0.id }, selectForumThreads: true))
}, multipleSelection: true, forwardedMessageIds: messages.map { $0.id }, initialForwardOptionsState: options, selectForumThreads: true))
let context = self.context
attemptSelectionImpl = { [weak self, weak controller] peer, reason in
guard let strongSelf = self, let controller = controller else {
@@ -153,12 +311,13 @@ extension ChatControllerImpl {
}
}
var attributes: [MessageAttribute] = []
attributes.append(ForwardOptionsMessageAttribute(hideNames: forwardOptions?.hideNames == true, hideCaptions: forwardOptions?.hideCaptions == true))
result.append(contentsOf: messages.map { message -> EnqueueMessage in
return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: attributes, correlationId: nil)
})
switch strongSelf.buildForwardEnqueueMessages(from: messages, options: forwardOptions, threadId: nil) {
case let .messages(forwardMessages):
result.append(contentsOf: forwardMessages)
case .unsupported:
strongSelf.presentUnsupportedProtectedForwardAlert(in: strongController)
return
}
let commit: ([EnqueueMessage]) -> Void = { result in
guard let strongSelf = self else {
@@ -392,10 +551,17 @@ extension ChatControllerImpl {
}
var correlationIds: [Int64] = []
let mappedMessages = messages.map { message -> EnqueueMessage in
let correlationId = Int64.random(in: Int64.min ... Int64.max)
correlationIds.append(correlationId)
return .forward(source: message.id, threadId: nil, grouping: .auto, attributes: [], correlationId: correlationId)
let mappedMessages: [EnqueueMessage]
switch strongSelf.buildForwardEnqueueMessages(from: messages, options: options, threadId: nil) {
case let .messages(builtMessages):
mappedMessages = builtMessages.map { message in
let correlationId = Int64.random(in: Int64.min ... Int64.max)
correlationIds.append(correlationId)
return message.withUpdatedCorrelationId(correlationId)
}
case .unsupported:
strongSelf.presentUnsupportedProtectedForwardAlert(in: strongController)
return
}
let _ = (reactionItems
@@ -448,6 +614,33 @@ extension ChatControllerImpl {
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
strongController.dismiss()
} else {
if strongSelf.forwardMessagesNeedPlainCopy(messages) {
let forwardOptions = options ?? ChatInterfaceForwardOptionsState(hideNames: !hasNotOwnMessages, hideCaptions: false, unhideNamesOnCaptionChange: false)
let mappedMessages: [EnqueueMessage]
switch strongSelf.buildForwardEnqueueMessages(from: messages, options: forwardOptions, threadId: threadId) {
case let .messages(builtMessages):
mappedMessages = builtMessages
case .unsupported:
strongSelf.presentUnsupportedProtectedForwardAlert(in: strongController)
return
}
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: mappedMessages)
|> deliverOnMainQueue).startStandalone()
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
var peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
peerName = peerName.replacingOccurrences(of: "**", with: "")
let text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).string : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).string
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: false, text: text), elevatedLayout: false, position: .bottom, animateInAsReplacement: true, action: { _ in
return false
}), in: .current)
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: true, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
strongController.dismiss()
return
}
if let navigationController = strongSelf.navigationController as? NavigationController {
for controller in navigationController.viewControllers {
if let maybeChat = controller as? ChatControllerImpl {
@@ -1,4 +1,5 @@
import Foundation
import SGSimpleSettings
import UIKit
import AsyncDisplayKit
import Postbox
@@ -129,7 +130,6 @@ class HistoryNodeContainer: ASDisplayNode {
var isSecret: Bool {
didSet {
if self.isSecret != oldValue {
// MISC: Bypass screenshot protection if enabled
let shouldDisable = self.isSecret && !MiscSettingsManager.shared.shouldBypassScreenshotProtection
setLayerDisableScreenshots(self.layer, shouldDisable)
}
@@ -146,7 +146,6 @@ class HistoryNodeContainer: ASDisplayNode {
super.init()
if self.isSecret {
// MISC: Bypass screenshot protection if enabled
let shouldDisable = self.isSecret && !MiscSettingsManager.shared.shouldBypassScreenshotProtection
setLayerDisableScreenshots(self.layer, shouldDisable)
}
@@ -1109,8 +1108,8 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
statusBar.updateStatusBarStyle(.White, animated: true)
} else {
let statusBarStyle: StatusBarStyle
if let isDark = self.backgroundNode.isDark {
if isDark {
if let contentStats = self.backgroundNode.contentStats {
if contentStats.isDark {
statusBarStyle = .White
} else {
statusBarStyle = .Black
@@ -1129,7 +1128,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
// GHOSTGRAM: Bypass screenshot protection if enabled in Misc settings
let effectiveCopyProtection = MiscSettingsManager.shared.shouldBypassScreenshotProtection ? false : self.chatPresentationInterfaceState.copyProtectionEnabled
let isSecret = effectiveCopyProtection || self.chatLocation.peerId?.namespace == Namespaces.Peer.SecretChat || self.chatLocation.peerId?.isVerificationCodes == true
if self.historyNodeContainer.isSecret != isSecret {
@@ -1644,6 +1642,10 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
dismissedFloatingTopicsPanel = floatingTopicsPanel
}
if floatingTopicsPanelInsets.top != 0.0 {
floatingTopicsPanelInsets.top += 8.0
}
var isSidebarOpen = false
if let floatingTopicsPanel = self.floatingTopicsPanel {
isSidebarOpen = floatingTopicsPanel.component.location == .side
@@ -1704,8 +1706,21 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
var dismissedAccessoryPanelNode: AccessoryPanelNode?
var dismissedInputContextPanelNode: ChatInputContextPanelNode?
var dismissedOverlayContextPanelNode: ChatInputContextPanelNode?
let inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, chatControllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction)
// MARK: Swiftgram
var inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, chatControllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction)
if SGSimpleSettings.shared.hideChannelBottomButton {
// We still need the panel for messages multi-select or search. Likely can break in future.
if self.chatPresentationInterfaceState.interfaceState.selectionState != nil || self.chatPresentationInterfaceState.search != nil {
self.inputPanelBackgroundNode.isHidden = false
} else if (inputPanelNodes.primary != nil || inputPanelNodes.secondary != nil) {
// So there should be some panel, but user don't want it. Let's check if our logic will hide it
inputPanelNodes = inputPanelForChatPresentationIntefaceState(self.chatPresentationInterfaceState, context: self.context, currentPanel: self.inputPanelNode, currentSecondaryPanel: self.secondaryInputPanelNode, textInputPanelNode: self.textInputPanelNode, chatControllerInteraction: self.controllerInteraction, interfaceInteraction: self.interfaceInteraction)
if inputPanelNodes.primary == nil && inputPanelNodes.secondary == nil {
// Looks like we're eligible to hide the panel, let's remove safe area fill as well
self.inputPanelBackgroundNode.isHidden = true
}
}
}
let inputPanelBottomInset = max(insets.bottom, inputPanelBottomInsetTerm)
@@ -1931,7 +1946,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
updateExtraNavigationBarBackgroundHeight(0.0, 0.0, nil, transition)
var sidePanelTopInset: CGFloat = insets.top + 4.0
var sidePanelTopInset: CGFloat = insets.top - 4.0
let contentBounds = CGRect(x: 0.0, y: 0.0, width: layout.size.width - wrappingInsets.left - wrappingInsets.right, height: layout.size.height - wrappingInsets.top - wrappingInsets.bottom)
@@ -2241,24 +2256,38 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if let current = self.bottomBackgroundEdgeEffectNode {
bottomBackgroundEdgeEffectNode = current
let edgeEffectAlpha: CGFloat
if case .image = self.chatPresentationInterfaceState.chatWallpaper {
edgeEffectAlpha = 0.7
} else if case .color = self.chatPresentationInterfaceState.chatWallpaper {
edgeEffectAlpha = 0.85
} else {
if let value = self.backgroundNode.makeEdgeEffectNode() {
bottomBackgroundEdgeEffectNode = value
self.bottomBackgroundEdgeEffectNode = value
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
edgeEffectAlpha = 0.75
}
var bottomBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if self.historyNode.rotated {
if let current = self.bottomBackgroundEdgeEffectNode {
bottomBackgroundEdgeEffectNode = current
} else {
if let value = self.backgroundNode.makeEdgeEffectNode() {
bottomBackgroundEdgeEffectNode = value
self.bottomBackgroundEdgeEffectNode = value
value.isUserInteractionEnabled = false
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
}
}
}
if let bottomBackgroundEdgeEffectNode {
var blurFrame = inputBackgroundFrame
blurFrame.origin.y -= 18.0
blurFrame.origin.y -= 20.0
blurFrame.size.height = max(100.0, layout.size.height - blurFrame.origin.y)
transition.updateFrame(node: bottomBackgroundEdgeEffectNode, frame: blurFrame)
bottomBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .bottom, size: 100.0),
edge: WallpaperEdgeEffectEdge(edge: .bottom, size: min(60.0, blurFrame.height)),
alpha: edgeEffectAlpha,
blur: false,
containerSize: wallpaperBounds.size,
transition: transition
@@ -2275,7 +2304,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
var contentBottomInset: CGFloat = inputPanelsHeight + 11.0 + inputPanelsInset
var contentBottomInset: CGFloat = inputPanelsHeight + inputPanelsInset
if previewing {
} else {
contentBottomInset += 11.0
}
if let scrollContainerNode = self.scrollContainerNode {
transition.updateFrame(node: scrollContainerNode, frame: CGRect(origin: CGPoint(), size: layout.size))
@@ -2307,7 +2340,6 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
if let containerNode = self.containerNode {
contentBottomInset += 8.0
let containerNodeFrame = CGRect(origin: CGPoint(x: wrappingInsets.left, y: wrappingInsets.top), size: CGSize(width: contentBounds.size.width, height: contentBounds.size.height - containerInsets.bottom - inputPanelsHeight - 8.0))
transition.updateFrame(node: containerNode, frame: containerNodeFrame)
@@ -2322,7 +2354,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
overlayNavigationBar.updateLayout(size: barFrame.size, transition: transition)
}
var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top + 6.0, right: containerInsets.left)
var listInsets = UIEdgeInsets(top: containerInsets.bottom + contentBottomInset, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left)
let listScrollIndicatorInsets = UIEdgeInsets(top: containerInsets.bottom + inputPanelsHeight, left: containerInsets.right, bottom: containerInsets.top, right: containerInsets.left)
var childContentInsets: UIEdgeInsets = containerInsets
@@ -2446,23 +2478,28 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
})
var topBackgroundEdgeEffectNode: WallpaperEdgeEffectNode?
if let current = self.topBackgroundEdgeEffectNode {
topBackgroundEdgeEffectNode = current
} else {
if let value = self.backgroundNode.makeEdgeEffectNode() {
topBackgroundEdgeEffectNode = value
self.topBackgroundEdgeEffectNode = value
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
if self.historyNode.rotated {
if let current = self.topBackgroundEdgeEffectNode {
topBackgroundEdgeEffectNode = current
} else {
if let value = self.backgroundNode.makeEdgeEffectNode() {
topBackgroundEdgeEffectNode = value
self.topBackgroundEdgeEffectNode = value
value.isUserInteractionEnabled = false
self.historyNodeContainer.view.superview?.insertSubview(value.view, aboveSubview: self.historyNodeContainer.view)
}
}
}
if let topBackgroundEdgeEffectNode {
var blurFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(100.0, listInsets.bottom + 10.0)))
blurFrame.origin.y = listInsets.bottom + 10.0 - blurFrame.height
let topExtent: CGFloat = 34.0
var blurFrame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: max(100.0, listInsets.bottom + topExtent)))
blurFrame.origin.y = listInsets.bottom + topExtent - blurFrame.height
transition.updateFrame(node: topBackgroundEdgeEffectNode, frame: blurFrame)
topBackgroundEdgeEffectNode.update(
rect: blurFrame,
edge: WallpaperEdgeEffectEdge(edge: .top, size: 100.0),
blur: false,
edge: WallpaperEdgeEffectEdge(edge: .top, size: 80.0),
alpha: edgeEffectAlpha,
blur: true,
containerSize: wallpaperBounds.size,
transition: transition
)
@@ -2524,6 +2561,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
transition.updateFrame(node: self.inputPanelBackgroundNode, frame: apparentInputBackgroundFrame, beginWithCurrentState: true)
if let headerPanelsComponentView = self.headerPanelsView?.view, let headerPanelsSize {
sidePanelTopInset += 8.0
let headerPanelsFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: sidePanelTopInset), size: headerPanelsSize)
var headerPanelsTransition = ComponentTransition(transition)
if headerPanelsComponentView.superview == nil {
@@ -4602,12 +4640,13 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
}
// GHOSTGRAM: When send delay is active, scheduled messages
// don't trigger history view update, so clear input immediately.
if SendDelayManager.shared.isEnabled {
self.collapseInput()
self.ignoreUpdateHeight = true
textInputPanelNode.text = ""
if let textInputPanelNode = self.inputPanelNode as? ChatTextInputPanelNode {
textInputPanelNode.text = ""
}
self.requestUpdateChatInterfaceState(.immediate, overrideThreadId == nil, { state in
var state = state
state = state.withUpdatedReplyMessageSubject(nil)
@@ -4657,7 +4696,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
}
var targetThreadId: Int64?
if self.chatLocation.threadId == nil, let user = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, user.isForum {
if self.chatLocation.threadId == nil, let user = self.chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.hasForum), botInfo.flags.contains(.forumManagedByUser) {
if let message = messages.first {
switch message {
case let .message(_, _, _, _, _, replyToMessageId, _, _, _, _):
@@ -647,7 +647,7 @@ func chatHistoryEntriesForView(
}
}
if !dynamicAdMessages.isEmpty {
if !MiscSettingsManager.shared.shouldBlockAds && !dynamicAdMessages.isEmpty {
assert(entries.sorted() == entries)
for message in dynamicAdMessages {
entries.append(.MessageEntry(message, presentationData, false, nil, .none, ChatMessageEntryAttributes(rank: nil, isContact: false, contentTypeHint: .generic, updatingMedia: nil, isPlaying: false, isCentered: false, authorStoryStats: nil, displayContinueThreadFooter: false)))
@@ -655,7 +655,7 @@ func chatHistoryEntriesForView(
entries.sort()
}
if view.laterId == nil && !view.isLoading {
if !MiscSettingsManager.shared.shouldBlockAds && view.laterId == nil && !view.isLoading {
if !entries.isEmpty, case let .MessageEntry(lastMessage, _, _, _, _, _) = entries[entries.count - 1], let message = adMessage {
var nextAdMessageId: Int32 = 10000
let updatedMessage = Message(
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import UIKit
import Postbox
@@ -339,6 +340,7 @@ final class ChatHistoryTransactionOpaqueState {
}
private func extractAssociatedData(
translationSettings: TranslationSettings,
chatLocation: ChatLocation,
view: MessageHistoryView,
automaticDownloadNetworkType: MediaAutoDownloadNetworkType,
@@ -421,7 +423,43 @@ private func extractAssociatedData(
automaticDownloadPeerId = message.peerId
}
return ChatMessageItemAssociatedData(automaticDownloadPeerType: automaticMediaDownloadPeerType, automaticDownloadPeerId: automaticDownloadPeerId, automaticDownloadNetworkType: automaticDownloadNetworkType, preferredStoryHighQuality: preferredStoryHighQuality, isRecentActions: false, subject: subject, contactsPeerIds: contactsPeerIds, channelDiscussionGroup: channelDiscussionGroup, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, currentlyPlayingMessageId: currentlyPlayingMessageId, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction, areStarReactionsEnabled: areStarReactionsEnabled, isPremium: isPremium, accountPeer: accountPeer, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, topicAuthorId: topicAuthorId, hasBots: hasBots, translateToLanguage: translateToLanguage, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: isInline, showSensitiveContent: showSensitiveContent, isSuspiciousPeer: isSuspiciousPeer)
return ChatMessageItemAssociatedData(
automaticDownloadPeerType: automaticMediaDownloadPeerType,
automaticDownloadPeerId: automaticDownloadPeerId,
automaticDownloadNetworkType: automaticDownloadNetworkType,
preferredStoryHighQuality: preferredStoryHighQuality,
isRecentActions: false,
subject: subject,
contactsPeerIds: contactsPeerIds,
channelDiscussionGroup: channelDiscussionGroup,
animatedEmojiStickers: animatedEmojiStickers,
additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers,
forcedResourceStatus: nil,
currentlyPlayingMessageId: currentlyPlayingMessageId,
isCopyProtectionEnabled: isCopyProtectionEnabled,
availableReactions: availableReactions,
availableMessageEffects: availableMessageEffects,
savedMessageTags: savedMessageTags,
defaultReaction: defaultReaction,
areStarReactionsEnabled: areStarReactionsEnabled,
isPremium: isPremium,
accountPeer: accountPeer,
forceInlineReactions: false,
alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton,
topicAuthorId: topicAuthorId,
hasBots: hasBots,
translationSettings: translationSettings,
translateToLanguage: translateToLanguage,
maxReadStoryId: maxReadStoryId,
recommendedChannels: recommendedChannels,
audioTranscriptionTrial: audioTranscriptionTrial,
chatThemes: chatThemes,
deviceContactsNumbers: deviceContactsNumbers,
isStandalone: false,
isInline: isInline,
showSensitiveContent: showSensitiveContent,
isSuspiciousPeer: isSuspiciousPeer
)
}
private extension ChatHistoryLocationInput {
@@ -791,6 +829,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
self.messageTransitionNode = messageTransitionNode
self.mode = mode
if SGSimpleSettings.shared.disableSnapDeletionEffect { self.allowDustEffect = false }
if let data = context.currentAppConfiguration.with({ $0 }).data {
if let _ = data["ios_killswitch_disable_unread_alignment"] {
self.enableUnreadAlignment = false
@@ -1816,6 +1855,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
var measure_isFirstTime = true
let messageViewQueue = Queue.mainQueue()
let historyViewTransitionDisposable = (combineLatest(queue: messageViewQueue,
self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) |> take(1),
historyViewUpdate |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_historyViewUpdate"),
self.chatPresentationDataPromise.get() |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_chatPresentationData"),
selectedMessages |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_selectedMessages"),
@@ -1841,7 +1881,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
chatThemes |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_chatThemes"),
deviceContactsNumbers |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_deviceContactsNumbers"),
contentSettings |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_contentSettings")
) |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_firstChatHistoryTransition")).startStrict(next: { [weak self] update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in
) |> debug_measureTimeToFirstEvent(label: "chatHistoryNode_firstChatHistoryTransition")).startStrict(next: { [weak self] sharedData, /* MARK: Swiftgram */ update, chatPresentationData, selectedMessages, updatingMedia, networkType, preferredStoryHighQuality, animatedEmojiStickers, additionalAnimatedEmojiStickers, customChannelDiscussionReadState, customThreadOutgoingReadState, availableReactions, availableMessageEffects, savedMessageTags, defaultReaction, accountPeer, suggestAudioTranscription, promises, topicAuthorId, translationState, maxReadStoryId, recommendedChannels, audioTranscriptionTrial, chatThemes, deviceContactsNumbers, contentSettings in
let (historyAppearsCleared, pendingUnpinnedAllMessages, pendingRemovedMessages, currentlyPlayingMessageIdAndType, scrollToMessageId, chatHasBots, allAdMessages) = promises
if measure_isFirstTime {
@@ -1852,6 +1892,13 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
#endif
}
let translationSettings: TranslationSettings
if let current = sharedData.entries[ApplicationSpecificSharedDataKeys.translationSettings]?.get(TranslationSettings.self) {
translationSettings = current
} else {
translationSettings = TranslationSettings.defaultSettings
}
func applyHole() {
Queue.mainQueue().async {
if let strongSelf = self {
@@ -2056,23 +2103,24 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
}
}
}
// GHOSTGRAM: Bypass copy protection if enabled in Misc settings
if MiscSettingsManager.shared.shouldBypassCopyProtection {
isCopyProtectionEnabled = false
}
let alwaysDisplayTranscribeButton = ChatMessageItemAssociatedData.DisplayTranscribeButton(
canBeDisplayed: suggestAudioTranscription.0 < 2,
displayForNotConsumed: suggestAudioTranscription.1,
providedByGroupBoost: audioTranscriptionProvidedByBoost
)
var translateToLanguage: (fromLang: String, toLang: String)?
if let translationState, (isPremium || autoTranslate) && translationState.isEnabled {
var languageCode = translationState.toLang ?? chatPresentationData.strings.baseLanguageCode
// MARK: Swiftgram
// var translateToLanguage: (fromLang: String, toLang: String)?
// if let translationState, (isPremium || autoTranslate) && translationState.isEnabled {
var languageCode = translationState?.toLang ?? chatPresentationData.strings.baseLanguageCode
let rawSuffix = "-raw"
if languageCode.hasSuffix(rawSuffix) {
languageCode = String(languageCode.dropLast(rawSuffix.count))
}
languageCode = normalizeTranslationLanguage(languageCode)
var translateToLanguage: (fromLang: String, toLang: String)?
let sgTranslationAvailable = SGSimpleSettings.shared.translationBackendEnum != .default
if let translationState, (isPremium || autoTranslate || sgTranslationAvailable) && translationState.isEnabled {
translateToLanguage = (normalizeTranslationLanguage(translationState.fromLang), normalizeTranslationLanguage(languageCode))
}
@@ -2081,7 +2129,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
isSuspiciousPeer = true
}
let associatedData = extractAssociatedData(chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction.0, areStarReactionsEnabled: defaultReaction.1, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage?.toLang, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: contentSettings.ignoreContentRestrictionReasons.contains("sensitive"), isSuspiciousPeer: isSuspiciousPeer)
let associatedData = extractAssociatedData(translationSettings: translationSettings, /* MARK: Swiftgram */ chatLocation: chatLocation, view: view, automaticDownloadNetworkType: networkType, preferredStoryHighQuality: preferredStoryHighQuality, animatedEmojiStickers: animatedEmojiStickers, additionalAnimatedEmojiStickers: additionalAnimatedEmojiStickers, subject: subject, currentlyPlayingMessageId: currentlyPlayingMessageIdAndType?.0, isCopyProtectionEnabled: isCopyProtectionEnabled, availableReactions: availableReactions, availableMessageEffects: availableMessageEffects, savedMessageTags: savedMessageTags, defaultReaction: defaultReaction.0, areStarReactionsEnabled: defaultReaction.1, isPremium: isPremium, alwaysDisplayTranscribeButton: alwaysDisplayTranscribeButton, accountPeer: accountPeer, topicAuthorId: topicAuthorId, hasBots: chatHasBots, translateToLanguage: translateToLanguage?.toLang, maxReadStoryId: maxReadStoryId, recommendedChannels: recommendedChannels, audioTranscriptionTrial: audioTranscriptionTrial, chatThemes: chatThemes, deviceContactsNumbers: deviceContactsNumbers, isInline: !rotated, showSensitiveContent: contentSettings.ignoreContentRestrictionReasons.contains("sensitive"), isSuspiciousPeer: isSuspiciousPeer)
var includeEmbeddedSavedChatInfo = false
if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId, !rotated {
@@ -2106,8 +2154,7 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
selectedMessages: selectedMessages,
presentationData: chatPresentationData,
historyAppearsCleared: historyAppearsCleared,
// GHOSTGRAM: Keep view-once media visible if bypass is enabled
skipViewOnceMedia: MiscSettingsManager.shared.shouldDisableViewOnceAutoDelete ? false : (mode != .bubbles),
skipViewOnceMedia: mode != .bubbles,
pendingUnpinnedAllMessages: pendingUnpinnedAllMessages,
pendingRemovedMessages: pendingRemovedMessages,
associatedData: associatedData,
@@ -2115,9 +2162,8 @@ public final class ChatHistoryListNodeImpl: ListView, ChatHistoryNode, ChatHisto
customChannelDiscussionReadState: customChannelDiscussionReadState,
customThreadOutgoingReadState: customThreadOutgoingReadState,
cachedData: data.cachedData,
// GHOSTGRAM: Block ads if enabled in Misc settings
adMessage: MiscSettingsManager.shared.shouldBlockAds ? nil : allAdMessages.fixed,
dynamicAdMessages: MiscSettingsManager.shared.shouldBlockAds ? [] : allAdMessages.opportunistic
adMessage: allAdMessages.fixed,
dynamicAdMessages: allAdMessages.opportunistic
)
let lastHeaderId = filteredEntries.last.flatMap { listMessageDateHeaderId(timestamp: $0.index.timestamp) } ?? 0
let processedView = ChatHistoryView(originalView: view, filteredEntries: filteredEntries, associatedData: associatedData, lastHeaderId: lastHeaderId, id: id, locationInput: update.2, ignoreMessagesInTimestampRange: update.3, ignoreMessageIds: update.4)
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import UIKit
import TelegramCore
@@ -261,7 +262,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
}
} else {
stickersAreEmoji = stickersAreEmoji || hasForward
if stickersEnabled {
if stickersEnabled, !SGSimpleSettings.shared.forceEmojiTab {
accessoryItems.append(.input(isEnabled: true, inputMode: stickersAreEmoji ? .emoji : .stickers))
} else {
accessoryItems.append(.input(isEnabled: true, inputMode: .emoji))
File diff suppressed because it is too large Load Diff
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import UIKit
import Display
@@ -494,8 +495,8 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
}
if currentTranslateToLanguageUpdated || messageUpdated, let message = interfaceState.pinnedMessage?.message {
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage?.toLang {
} else if let translateToLanguage {
if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage?.toLang || translation.toLang.hasPrefix("\(translateToLanguage?.toLang ?? "")-") /* MARK: Swiftgram */ {
} else if let translateToLanguage {
self.translationDisposable.set(translateMessageIds(context: self.context, messageIds: [message.id], fromLang: translateToLanguage.fromLang, toLang: translateToLanguage.toLang).startStrict())
}
}
@@ -18,6 +18,17 @@ import TranslateUI
import TelegramUIPreferences
import TelegramNotices
import PremiumUI
import SGSimpleSettings
private func sgChatTranslationAvailable(isPremium: Bool, peer: Peer?) -> Bool {
if isPremium || SGSimpleSettings.shared.translationBackendEnum != .default {
return true
}
if let channel = peer as? TelegramChannel, channel.flags.contains(.autoTranslateEnabled) {
return true
}
return false
}
final class ChatTranslationPanelNode: ASDisplayNode {
private let context: AccountContext
@@ -183,11 +194,7 @@ final class ChatTranslationPanelNode: ASDisplayNode {
@objc private func closePressed() {
let isPremium = self.chatInterfaceState?.isPremium ?? false
var translationAvailable = isPremium
if let channel = self.chatInterfaceState?.renderedPeer?.chatMainPeer as? TelegramChannel, channel.flags.contains(.autoTranslateEnabled) {
translationAvailable = true
}
let translationAvailable = sgChatTranslationAvailable(isPremium: isPremium, peer: self.chatInterfaceState?.renderedPeer?.chatMainPeer)
if translationAvailable {
self.interfaceInteraction?.hideTranslationPanel()
@@ -202,11 +209,7 @@ final class ChatTranslationPanelNode: ASDisplayNode {
}
let isPremium = self.chatInterfaceState?.isPremium ?? false
var translationAvailable = isPremium
if let channel = self.chatInterfaceState?.renderedPeer?.chatMainPeer as? TelegramChannel, channel.flags.contains(.autoTranslateEnabled) {
translationAvailable = true
}
let translationAvailable = sgChatTranslationAvailable(isPremium: isPremium, peer: self.chatInterfaceState?.renderedPeer?.chatMainPeer)
if translationAvailable {
self.interfaceInteraction?.toggleTranslation(translationState.isEnabled ? .original : .translated)
@@ -1,4 +1,5 @@
import Foundation
import SGSimpleSettings
import Display
import AsyncDisplayKit
import Postbox
@@ -757,7 +757,7 @@ func openResolvedUrlImpl(
case let .settings(section):
dismissInput()
switch section {
case .theme:
case .legacy(.theme):
if let navigationController = navigationController {
let controller = themeSettingsController(context: context)
controller.navigationPresentation = .modal
@@ -768,7 +768,7 @@ func openResolvedUrlImpl(
navigationController.setViewControllers(controllers, animated: true)
}
case .devices:
case .legacy(.devices):
if let navigationController = navigationController {
let activeSessions = deferred { () -> Signal<(ActiveSessionsContext, Int, WebSessionsContext), NoError> in
let activeSessionsContext = context.engine.privacy.activeSessions()
@@ -798,15 +798,13 @@ func openResolvedUrlImpl(
navigationController.setViewControllers(controllers, animated: true)
})
}
case .autoremoveMessages:
case .legacy(.autoremoveMessages):
let _ = (context.engine.privacy.requestAccountPrivacySettings()
|> take(1)
|> deliverOnMainQueue).start(next: { settings in
navigationController?.pushViewController(globalAutoremoveScreen(context: context, initialValue: settings.messageAutoremoveTimeout ?? 0, updated: { _ in }), animated: true)
})
case .twoStepAuth:
break
case .enableLog:
case .legacy(.enableLog):
if let navigationController = navigationController {
let _ = updateLoggingSettings(accountManager: context.sharedContext.accountManager, {
$0.withUpdatedLogToFile(true)
@@ -819,7 +817,7 @@ func openResolvedUrlImpl(
navigationController.setViewControllers(controllers, animated: true)
}
}
case .phonePrivacy:
case .legacy(.phonePrivacy):
let privacySignal = context.engine.privacy.requestAccountPrivacySettings()
let _ = (privacySignal
|> deliverOnMainQueue).start(next: { info in
@@ -831,11 +829,13 @@ func openResolvedUrlImpl(
navigationController.pushViewController(controller)
}
})
case .loginEmail:
case .legacy(.loginEmail):
if let navigationController {
let controller = loginEmailSetupController(context: context, blocking: false, emailPattern: nil, navigationController: navigationController, completion: {}, dismiss: {})
navigationController.pushViewController(controller)
}
case .path(_):
break
}
case let .premiumOffer(reference):
dismissInput()
@@ -851,7 +851,7 @@ func openResolvedUrlImpl(
}
case let .starsTopup(amount, purpose):
dismissInput()
if let starsContext = context.starsContext {
if let starsContext = context.starsContext, let amount {
let proceed = {
let controller = context.sharedContext.makeStarsPurchaseScreen(context: context, starsContext: starsContext, options: [], purpose: .topUp(requiredStars: amount, purpose: purpose), targetPeerId: nil, customTheme: nil, completion: { _ in })
if let navigationController = navigationController {
@@ -1480,8 +1480,8 @@ func openResolvedUrlImpl(
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
}
})
case let .collectible(gift):
if let gift {
case let .collectible(collectible):
if case let .gift(gift) = collectible {
var dismissedImpl: (() -> Void)?
if let storyProgressPauseContext = contentContext as? StoryProgressPauseContext {
let updateExternalController = storyProgressPauseContext.update
@@ -1628,5 +1628,17 @@ func openResolvedUrlImpl(
navigationController?.pushViewController(controller)
}
}
case let .unknownDeepLink(path):
present(textAlertController(context: context, updatedPresentationData: updatedPresentationData, title: nil, text: path, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil)
case let .oauth(url):
context.sharedContext.openExternalUrl(context: context, urlContext: urlContext, url: url, forceExternal: true, presentationData: presentationData, navigationController: navigationController, dismissInput: dismissInput)
case .chats(_):
dismissInput()
case .compose(_):
dismissInput()
case .postStory(_):
dismissInput()
case .contacts(_):
dismissInput()
}
}
File diff suppressed because it is too large Load Diff
@@ -1,3 +1,10 @@
// MARK: Swiftgram
import SGIAP
import SGPayWall
import SGProUI
import SGSimpleSettings
import SGStatus
//
import Foundation
import UIKit
import AsyncDisplayKit
@@ -95,6 +102,7 @@ import PasskeysScreen
import GiftDemoScreen
import ChatTextLinkEditUI
import CocoonInfoScreen
import GiftCraftScreen
private final class AccountUserInterfaceInUseContext {
let subscribers = Bag<(Bool) -> Void>()
@@ -254,6 +262,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
public let currentMediaDisplaySettings: Atomic<MediaDisplaySettings>
private var mediaDisplaySettingsDisposable: Disposable?
public let currentChatSettings: Atomic<ChatSettings>
private var chatSettingsDisposable: Disposable?
public let currentStickerSettings: Atomic<StickerSettings>
private var stickerSettingsDisposable: Disposable?
@@ -264,6 +275,14 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return self.immediateExperimentalUISettingsValue.with { $0 }
}
private var experimentalUISettingsDisposable: Disposable?
// MARK: Swiftgram
private var immediateSGStatusValue = Atomic<SGStatus>(value: SGStatus.default)
public var immediateSGStatus: SGStatus {
return self.immediateSGStatusValue.with { $0 }
}
private var sgStatusDisposable: Disposable?
public var SGIAP: SGIAPManager?
public var presentGlobalController: (ViewController, Any?) -> Void = { _, _ in }
public var presentCrossfadeController: () -> Void = {}
@@ -338,6 +357,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.currentMediaDisplaySettings = Atomic(value: initialPresentationDataAndSettings.mediaDisplaySettings)
self.currentStickerSettings = Atomic(value: initialPresentationDataAndSettings.stickerSettings)
self.currentInAppNotificationSettings = Atomic(value: initialPresentationDataAndSettings.inAppNotificationSettings)
self.currentChatSettings = Atomic(value: ChatSettings.defaultSettings)
if automaticEnergyUsageShouldBeOnNow(settings: self.currentAutomaticMediaDownloadSettings) {
self.energyUsageSettings = EnergyUsageSettings.powerSavingDefault
@@ -484,6 +504,15 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
})
self.chatSettingsDisposable = (self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.chatSettings])
|> deliverOnMainQueue).start(next: { [weak self] sharedData in
if let strongSelf = self {
if let settings = sharedData.entries[ApplicationSpecificSharedDataKeys.chatSettings]?.get(ChatSettings.self) {
let _ = strongSelf.currentChatSettings.swap(settings)
}
}
})
let immediateExperimentalUISettingsValue = self.immediateExperimentalUISettingsValue
let _ = immediateExperimentalUISettingsValue.swap(initialPresentationDataAndSettings.experimentalUISettings)
@@ -498,6 +527,18 @@ public final class SharedAccountContextImpl: SharedAccountContext {
GlassBackgroundView.useCustomGlassImpl = settings.fakeGlass
}
})
// 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)
SGSimpleSettings.shared.ephemeralStatus = settings.status
SGSimpleSettings.shared.status = settings.status
}
})
self.initSGIAP(isMainApp: applicationBindings.isMainApp)
//
let _ = self.contactDataManager?.personNameDisplayOrder().start(next: { order in
let _ = updateContactSettingsInteractively(accountManager: accountManager, { settings in
@@ -1077,6 +1118,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.inAppNotificationSettingsDisposable?.dispose()
self.mediaInputSettingsDisposable?.dispose()
self.mediaDisplaySettingsDisposable?.dispose()
self.chatSettingsDisposable?.dispose()
self.stickerSettingsDisposable?.dispose()
self.callDisposable?.dispose()
self.groupCallDisposable?.dispose()
self.callStateDisposable?.dispose()
@@ -1084,8 +1127,20 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.callPeerDisposable?.dispose()
}
// MARK: Swiftgram
var didPerformSGUISettingsMigration = false
//
// MARK: Swiftgram
func sgPrimaryAccountContextForMigration() -> AccountContext? {
return self.activeAccountsValue?.primary
}
//
private var didPerformAccountSettingsImport = false
private func performAccountSettingsImportIfNecessary() {
// MARK: Swiftgram
self.performSGUISettingsMigrationIfNecessary()
//
if self.didPerformAccountSettingsImport {
return
}
@@ -3767,7 +3822,24 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
public func makeStarsTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, source: BotPaymentInvoiceSource, extendedMedia: [TelegramExtendedMedia], inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, completion: @escaping (Bool) -> Void) -> ViewController {
return StarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: source, extendedMedia: extendedMedia, inputData: inputData, completion: completion)
return StarsTransferScreen(context: context, starsContext: starsContext, invoice: invoice, source: source, extendedMedia: extendedMedia, inputData: inputData, navigateToPeer: { [weak self] peer in
guard let self else {
return
}
if let infoController = self.makePeerInfoController(
context: context,
updatedPresentationData: nil,
peer: peer._asPeer(),
mode: .generic,
avatarInitiallyExpanded: peer.smallProfileImage != nil,
fromChat: false,
requestsContext: nil
) {
if let navigationController = self.mainWindow?.viewController as? NavigationController {
navigationController.pushViewController(infoController)
}
}
}, completion: completion)
}
public func makeStarsSubscriptionTransferScreen(context: AccountContext, starsContext: StarsContext, invoice: TelegramMediaInvoice, link: String, inputData: Signal<(StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)?, NoError>, navigateToPeer: @escaping (EnginePeer) -> Void) -> ViewController {
@@ -3851,7 +3923,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftViewScreen(context: context, subject: .wearPreview(gift, attributes))
}
public func makeGiftUpgradePreviewScreen(context: AccountContext, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController {
public func makeGiftUpgradePreviewScreen(context: AccountContext, gift: StarGift.Gift, attributes: [StarGift.UniqueGift.Attribute], peerName: String) -> ViewController {
return GiftViewScreen(context: context, subject: .upgradePreview(attributes, peerName))
}
@@ -3875,7 +3947,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return giftOfferAlertController(context: context, updatedPresentationData: updatedPresentationData, gift: gift, peer: peer, amount: amount, commit: commit)
}
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
public func makeGiftUpgradeVariantsScreen(context: AccountContext, gift: StarGift, crafted: Bool, attributes: [StarGift.UniqueGift.Attribute], selectedAttributes: [StarGift.UniqueGift.Attribute]?, focusedAttribute: StarGift.UniqueGift.Attribute?) -> ViewController {
return GiftUpgradeVariantsScreen(context: context, gift: gift, attributes: attributes, selectedAttributes: selectedAttributes, focusedAttribute: focusedAttribute)
}
@@ -3883,6 +3955,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
return GiftAuctionWearPreviewScreen(context: context, auctionContext: auctionContext, attributes: attributes, completion: completion)
}
public func makeGiftCraftScreen(context: AccountContext, gift: StarGift.UniqueGift, profileGiftsContext: ProfileGiftsContext?) -> ViewController {
return GiftCraftScreen(context: context, gift: gift, profileGiftsContext: profileGiftsContext)
}
public func makeGiftDemoScreen(context: AccountContext) -> ViewController {
return GiftDemoScreen(context: context)
}
@@ -4094,8 +4170,23 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
var switchToMediaTarget: PeerInfoSwitchToMediaTarget?
switch mode {
case let .media(kind, index):
let mappedKind: PeerInfoSwitchToMediaTarget.Kind
switch kind {
case .photoVideo:
mappedKind = .photoVideo
case .file:
mappedKind = .file
}
switchToMediaTarget = PeerInfoSwitchToMediaTarget(kind: mappedKind, messageIndex: index)
default:
break
}
if let _ = peer as? TelegramGroup {
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget)
} else if let _ = peer as? TelegramChannel {
var forumTopicThread: ChatReplyThreadMessage?
var switchToRecommendedChannels = false
@@ -4118,7 +4209,7 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
default:
break
}
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder)
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], forumTopicThread: forumTopicThread, switchToRecommendedChannels: switchToRecommendedChannels, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget)
} else if peer is TelegramUser {
var nearbyPeerDistance: Int32?
var reactionSourceMessageId: MessageId?
@@ -4171,9 +4262,9 @@ private func peerInfoControllerImpl(context: AccountContext, updatedPresentation
default:
break
}
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder)
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nearbyPeerDistance, reactionSourceMessageId: reactionSourceMessageId, callMessages: callMessages, isMyProfile: isMyProfile, hintGroupInCommon: hintGroupInCommon, forumTopicThread: forumTopicThread, sharedMediaFromForumTopic: sharedMediaFromForumTopic, switchToGiftsTarget: switchToGiftsTarget, switchToGroupsInCommon: switchToGroupsInCommon, switchToStoryFolder: switchToStoryFolder, switchToMediaTarget: switchToMediaTarget)
} else if peer is TelegramSecretChat {
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [])
return PeerInfoScreenImpl(context: context, updatedPresentationData: updatedPresentationData, peerId: peer.id, avatarInitiallyExpanded: avatarInitiallyExpanded, isOpenedFromChat: isOpenedFromChat, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], switchToMediaTarget: switchToMediaTarget)
}
return nil
}
@@ -4184,3 +4275,64 @@ private func useFlatModalCallsPresentation(context: AccountContext) -> Bool {
}
return true
}
// MARK: Swiftgram
extension SharedAccountContextImpl {
func initSGIAP(isMainApp: Bool) {
if isMainApp {
self.SGIAP = SGIAPManager()
} else {
self.SGIAP = nil
}
}
public func makeSGProController(context: AccountContext) -> ViewController {
let controller = sgProController(context: context)
return controller
}
public func makeSGPayWallController(context: AccountContext) -> ViewController? {
guard #available(iOS 13.0, *) else {
return nil
}
guard let sgIAP = self.SGIAP else {
return nil
}
let statusSignal = self.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.sgStatus])
|> map { sharedData -> Int64 in
let sgStatus = sharedData.entries[ApplicationSpecificSharedDataKeys.sgStatus]?.get(SGStatus.self) ?? SGStatus.default
return sgStatus.status
}
let proController = self.makeSGProController(context: context)
let sgWebSettings = context.currentAppConfiguration.with { $0 }.sgWebSettings
let presentationData = self.currentPresentationData.with { $0 }
var payWallController: ViewController? = nil
let openUrl: ((String, Bool) -> Void) = { [weak self, weak context] url, forceExternal in
guard let strongSelf = self, let strongContext = context, let strongPayWallController = payWallController else {
return
}
let navigationController = strongPayWallController.navigationController as? NavigationController
Queue.mainQueue().async {
strongSelf.openExternalUrl(context: strongContext, urlContext: .generic, url: url, forceExternal: forceExternal, presentationData: presentationData, navigationController: navigationController, dismissInput: {})
}
}
var supportUrl: String? = nil
if let supportUrlString = sgWebSettings.global.proSupportUrl, !supportUrlString.isEmpty, let data = Data(base64Encoded: supportUrlString), let decodedString = String(data: data, encoding: .utf8) {
supportUrl = decodedString
}
payWallController = sgPayWallController(statusSignal: statusSignal, replacementController: proController, presentationData: presentationData, SGIAPManager: sgIAP, openUrl: openUrl, paymentsEnabled: sgWebSettings.global.paymentsEnabled, canBuyInBeta: sgWebSettings.user.canBuyInBeta, openAppStorePage: self.applicationBindings.openAppStorePage, proSupportUrl: supportUrl)
return payWallController
}
public func makeSGUpdateIOSController() -> ViewController {
let presentationData = self.currentPresentationData.with { $0 }
let controller = textAlertController(sharedContext: self, title: nil, text: "Common.UpdateOS".i18n(presentationData.strings.baseLanguageCode), actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {
})])
return controller
}
}
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import UIKit
import Display
@@ -41,7 +42,7 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh
init(context: AccountContext) {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, preferredGlassType: .default, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: .standard(.default), chatLocation: .peer(id: context.account.peerId), subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: true)
self.emptyNode = ChatEmptyNode(context: context, interaction: nil)
@@ -54,7 +55,8 @@ private class DetailsChatPlaceholderNode: ASDisplayNode, NavigationDetailsPlaceh
func updatePresentationData(_ presentationData: PresentationData) {
self.presentationData = presentationData
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(.default), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
let preferredGlassType = self.presentationInterfaceState.preferredGlassType
self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, preferredGlassType: preferredGlassType, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: self.presentationInterfaceState.limitsConfiguration, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: self.presentationInterfaceState.accountPeerId, mode: .standard(.default), chatLocation: self.presentationInterfaceState.chatLocation, subject: nil, peerNearbyData: nil, greetingData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil, hasActiveGroupCall: false, threadData: nil, isGeneralThreadClosed: nil, replyMessage: nil, accountPeerColor: nil, businessIntro: nil)
self.wallpaperBackgroundNode.update(wallpaper: presentationData.chatWallpaper, animated: false)
}
@@ -156,10 +158,18 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
return self.chatListController
}
public func getSettingsController() -> ViewController? {
return self.accountSettingsController
}
public func getPrivacySettings() -> Promise<AccountPrivacySettings?>? {
return self.accountSettingsController?.privacySettings
}
public func getTwoStepAuthData() -> Promise<TwoStepAuthData?>? {
return self.accountSettingsController?.twoStepAuthData
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
let needsRootWallpaperBackgroundNode: Bool
if case .regular = layout.metrics.widthClass {
@@ -186,7 +196,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
super.containerLayoutUpdated(layout, transition: transition)
}
public func addRootControllers(showCallsTab: Bool) {
public func addRootControllers(hidePhoneInSettings: Bool, showContactsTab: Bool, showCallsTab: Bool) {
let tabBarController = TabBarControllerImpl(theme: self.presentationData.theme, strings: self.presentationData.strings)
tabBarController.navigationPresentation = .master
let chatListController = self.context.sharedContext.makeChatListController(context: self.context, location: .chatList(groupId: .root), controlsHistoryPreload: true, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: !GlobalExperimentalSettings.isAppStoreBuild)
@@ -201,7 +211,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
contactsController.switchToChatsController = { [weak self] in
self?.openChatsController(activateSearch: false)
}
controllers.append(contactsController)
// MARK: Swiftgram
if showContactsTab {
controllers.append(contactsController)
}
if showCallsTab {
controllers.append(callListController)
@@ -217,7 +230,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
sharedContext.switchingData = (nil, nil, nil)
}
let accountSettingsController = PeerInfoScreenImpl(context: self.context, updatedPresentationData: nil, peerId: self.context.account.peerId, avatarInitiallyExpanded: false, isOpenedFromChat: false, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], isSettings: true)
let accountSettingsController = PeerInfoScreenImpl(hidePhoneInSettings: hidePhoneInSettings, context: self.context, updatedPresentationData: nil, peerId: self.context.account.peerId, avatarInitiallyExpanded: false, isOpenedFromChat: false, nearbyPeerDistance: nil, reactionSourceMessageId: nil, callMessages: [], isSettings: true)
accountSettingsController.tabBarItemDebugTapAction = { [weak self] in
guard let strongSelf = self else {
return
@@ -237,12 +250,14 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
self.pushViewController(tabBarController, animated: false)
}
public func updateRootControllers(showCallsTab: Bool) {
public func updateRootControllers(showContactsTab: Bool, showCallsTab: Bool) {
guard let rootTabController = self.rootTabController as? TabBarControllerImpl else {
return
}
var controllers: [ViewController] = []
controllers.append(self.contactsController!)
if showContactsTab {
controllers.append(self.contactsController!)
}
if showCallsTab {
controllers.append(self.callListController!)
}
@@ -300,7 +315,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
@discardableResult
public func openStoryCamera(customTarget: Stories.PendingTarget?, resumeLiveStream: Bool, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
public func openStoryCamera(mode: StoryCameraMode, customTarget: Stories.PendingTarget?, resumeLiveStream: Bool, transitionIn: StoryCameraTransitionIn?, transitionedIn: @escaping () -> Void, transitionOut: @escaping (Stories.PendingTarget?, Bool) -> StoryCameraTransitionOut?) -> StoryCameraTransitionInCoordinator? {
guard let controller = self.viewControllers.last as? ViewController else {
return nil
}
@@ -675,7 +690,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
defer {
TempBox.shared.dispose(tempFile)
}
if let imageData = compressImageToJPEG(image, quality: 0.7, tempFilePath: tempFile.path) {
if let imageData = compressImageToJPEG(image, quality: Float(SGSimpleSettings.shared.outgoingPhotoQuality) / 100.0, tempFilePath: tempFile.path) {
media = .image(dimensions: dimensions, data: imageData, stickers: result.stickers)
}
case let .video(content, firstFrameImage, values, duration, dimensions):
@@ -698,7 +713,7 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
defer {
TempBox.shared.dispose(tempFile)
}
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: 0.6, tempFilePath: tempFile.path) }
let imageData = firstFrameImage.flatMap { compressImageToJPEG($0, quality: Float(SGSimpleSettings.shared.outgoingPhotoQuality) / 100.0, tempFilePath: tempFile.path) }
let firstFrameFile = imageData.flatMap { data -> TempBoxFile? in
let file = TempBox.shared.tempFile(fileName: "image.jpg")
if let _ = try? data.write(to: URL(fileURLWithPath: file.path)) {
@@ -758,7 +773,31 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
}
}
public func openSettings() {
public func openChats() {
guard let rootTabController = self.rootTabController else {
return
}
self.popToRoot(animated: false)
if let index = rootTabController.controllers.firstIndex(where: { $0 is ChatListController }) {
rootTabController.selectedIndex = index
}
}
public func openContacts() {
guard let rootTabController = self.rootTabController else {
return
}
self.popToRoot(animated: false)
if let index = rootTabController.controllers.firstIndex(where: { $0 is ContactsController }) {
rootTabController.selectedIndex = index
}
}
public func openSettings(edit: Bool) {
guard let rootTabController = self.rootTabController else {
return
}
@@ -768,6 +807,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
if let index = rootTabController.controllers.firstIndex(where: { $0 is PeerInfoScreenImpl }) {
rootTabController.selectedIndex = index
}
if edit {
self.accountSettingsController?.activateEdit()
}
}
public func openBirthdaySetup() {
@@ -784,6 +827,10 @@ public final class TelegramRootController: NavigationController, TelegramRootCon
accountSettingsController.openAvatars()
}
}
public func startNewCall() {
self.callListController?.tabBarActivateSearch()
}
}
#if SWIFT_PACKAGE
@@ -1,3 +1,4 @@
import SGSimpleSettings
import Foundation
import UIKit
import TelegramCore
@@ -169,7 +170,8 @@ public func transformOutgoingMessageMedia(postbox: Postbox, network: Network, me
defer {
TempBox.shared.dispose(tempFile)
}
if let fullImage = UIImage(contentsOfFile: data.path), let smallestImage = generateScaledImage(image: fullImage, size: smallestSize, scale: 1.0), let smallestData = compressImageToJPEG(smallestImage, quality: 0.7, tempFilePath: tempFile.path) {
// MARK: Swiftgram
if let fullImage = UIImage(contentsOfFile: data.path), let smallestImage = generateScaledImage(image: fullImage, size: smallestSize, scale: 1.0), let smallestData = compressImageToJPEG(smallestImage, quality: Float(SGSimpleSettings.shared.outgoingPhotoQuality) / 100.0, tempFilePath: tempFile.path) {
var representations = image.representations
let thumbnailResource = LocalFileMediaResource(fileId: Int64.random(in: Int64.min ... Int64.max))
@@ -0,0 +1,25 @@
import Foundation
import SwiftSignalKit
import TelegramCore
import AccountContext
import WatchBridge
public final class WatchManagerImpl: WatchManager {
private let arguments: WatchManagerArguments?
public init(arguments: WatchManagerArguments?) {
self.arguments = arguments
}
public var watchAppInstalled: Signal<Bool, NoError> {
return self.arguments?.appInstalled ?? .single(false)
}
public var navigateToMessageRequested: Signal<EngineMessage.Id, NoError> {
return self.arguments?.navigateToMessageRequested ?? .never()
}
public var runningTasks: Signal<WatchRunningTasks?, NoError> {
return self.arguments?.runningTasks ?? .single(nil)
}
}