mirror of
https://github.com/GLEGram/GLEGram-iOS.git
synced 2026-07-09 07:37:51 +02:00
4647310322
Based on Swiftgram 12.5 (Telegram iOS 12.5). All GLEGram features ported and organized in GLEGram/ folder. Features: Ghost Mode, Saved Deleted Messages, Content Protection Bypass, Font Replacement, Fake Profile, Chat Export, Plugin System, and more. See CHANGELOG_12.5.md for full details.
1390 lines
53 KiB
Swift
1390 lines
53 KiB
Swift
import Foundation
|
|
import SGAppGroupIdentifier
|
|
import SGLogging
|
|
|
|
let APP_GROUP_IDENTIFIER = sgAppGroupIdentifier()
|
|
|
|
/// Lightweight file-backed key-value store (replaces NSUserDefaults for sensitive keys).
|
|
private class SGFileStore {
|
|
static let shared = SGFileStore()
|
|
|
|
private var data: [String: Any] = [:]
|
|
private let filePath: String
|
|
private let lock = NSLock()
|
|
|
|
private init() {
|
|
let docs = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first ?? NSTemporaryDirectory()
|
|
filePath = docs + "/sg_private_settings.plist"
|
|
if let dict = NSDictionary(contentsOfFile: filePath) as? [String: Any] {
|
|
data = dict
|
|
}
|
|
}
|
|
|
|
func double(forKey key: String, default defaultValue: Double) -> Double {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return data[key] as? Double ?? defaultValue
|
|
}
|
|
|
|
func int32(forKey key: String, default defaultValue: Int32) -> Int32 {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
if let v = data[key] as? Int { return Int32(v) }
|
|
if let v = data[key] as? Int32 { return v }
|
|
return defaultValue
|
|
}
|
|
|
|
func set(_ value: Any, forKey key: String) {
|
|
lock.lock()
|
|
data[key] = value
|
|
(data as NSDictionary).write(toFile: filePath, atomically: true)
|
|
lock.unlock()
|
|
}
|
|
}
|
|
|
|
public class SGSimpleSettings {
|
|
|
|
public static let shared = SGSimpleSettings()
|
|
|
|
private init() {
|
|
setDefaultValues()
|
|
migrate()
|
|
preCacheValues()
|
|
}
|
|
|
|
private func setDefaultValues() {
|
|
UserDefaults.standard.register(defaults: SGSimpleSettings.defaultValues)
|
|
// Just in case group defaults will be nil
|
|
UserDefaults.standard.register(defaults: SGSimpleSettings.groupDefaultValues)
|
|
if let groupUserDefaults = UserDefaults(suiteName: APP_GROUP_IDENTIFIER) {
|
|
groupUserDefaults.register(defaults: SGSimpleSettings.groupDefaultValues)
|
|
}
|
|
}
|
|
|
|
private func migrate() {
|
|
let showRepostToStoryMigrationKey = "migrated_\(Keys.showRepostToStory.rawValue)"
|
|
if let groupUserDefaults = UserDefaults(suiteName: APP_GROUP_IDENTIFIER) {
|
|
if !groupUserDefaults.bool(forKey: showRepostToStoryMigrationKey) {
|
|
self.showRepostToStoryV2 = self.showRepostToStory
|
|
groupUserDefaults.set(true, forKey: showRepostToStoryMigrationKey)
|
|
SGLogger.shared.log("SGSimpleSettings", "Migrated showRepostToStory. \(self.showRepostToStory) -> \(self.showRepostToStoryV2)")
|
|
}
|
|
} else {
|
|
SGLogger.shared.log("SGSimpleSettings", "Unable to migrate showRepostToStory. Shared UserDefaults suite is not available for '\(APP_GROUP_IDENTIFIER)'.")
|
|
}
|
|
|
|
// MARK: AppBadge default migration
|
|
// Older builds used an empty value which resulted in the classic badge being shown.
|
|
if self.customAppBadge.isEmpty || self.customAppBadge == "Components/AppBadge" {
|
|
self.customAppBadge = "SkyAppBadge"
|
|
}
|
|
|
|
let chatListLinesMigrationKey = "migrated_\(Keys.chatListLines.rawValue)"
|
|
if !UserDefaults.standard.bool(forKey: chatListLinesMigrationKey) {
|
|
let legacyCompactMessagePreviewKey = "compactMessagePreview"
|
|
if UserDefaults.standard.object(forKey: legacyCompactMessagePreviewKey) != nil {
|
|
if UserDefaults.standard.bool(forKey: legacyCompactMessagePreviewKey) {
|
|
self.chatListLines = ChatListLines.one.rawValue
|
|
}
|
|
UserDefaults.standard.removeObject(forKey: legacyCompactMessagePreviewKey)
|
|
SGLogger.shared.log("SGSimpleSettings", "Migrated compactMessagePreview -> chatListLines. \(self.chatListLines)")
|
|
}
|
|
UserDefaults.standard.set(true, forKey: chatListLinesMigrationKey)
|
|
}
|
|
}
|
|
|
|
private func preCacheValues() {
|
|
// let dispatchGroup = DispatchGroup()
|
|
|
|
let tasks = [
|
|
// { let _ = self.allChatsFolderPositionOverride },
|
|
{ let _ = self.tabBarSearchEnabled },
|
|
{ let _ = self.allChatsHidden },
|
|
{ let _ = self.hideTabBar },
|
|
{ let _ = self.bottomTabStyle },
|
|
{ let _ = self.compactChatList },
|
|
{ let _ = self.chatListLines },
|
|
{ let _ = self.compactFolderNames },
|
|
{ let _ = self.disableSwipeToRecordStory },
|
|
{ let _ = self.rememberLastFolder },
|
|
{ let _ = self.quickTranslateButton },
|
|
{ let _ = self.stickerSize },
|
|
{ let _ = self.stickerTimestamp },
|
|
{ let _ = self.hideReactions },
|
|
{ let _ = self.disableGalleryCamera },
|
|
{ let _ = self.disableSendAsButton },
|
|
{ let _ = self.disableSnapDeletionEffect },
|
|
{ let _ = self.startTelescopeWithRearCam },
|
|
{ let _ = self.hideRecordingButton },
|
|
{ let _ = self.inputToolbar },
|
|
{ let _ = self.dismissedSGSuggestions },
|
|
{ let _ = self.customAppBadge }
|
|
]
|
|
|
|
tasks.forEach { task in
|
|
DispatchQueue.global(qos: .background).async(/*group: dispatchGroup*/) {
|
|
task()
|
|
}
|
|
}
|
|
|
|
// dispatchGroup.notify(queue: DispatchQueue.main) {}
|
|
}
|
|
|
|
public func synchronizeShared() {
|
|
if let groupUserDefaults = UserDefaults(suiteName: APP_GROUP_IDENTIFIER) {
|
|
groupUserDefaults.synchronize()
|
|
}
|
|
}
|
|
|
|
public enum Keys: String, CaseIterable {
|
|
case hidePhoneInSettings
|
|
case showTabNames
|
|
case startTelescopeWithRearCam
|
|
case accountColorsSaturation
|
|
case uploadSpeedBoost
|
|
case downloadSpeedBoost
|
|
case bottomTabStyle
|
|
case rememberLastFolder
|
|
case lastAccountFolders
|
|
case localDNSForProxyHost
|
|
case sendLargePhotos
|
|
case outgoingPhotoQuality
|
|
case storyStealthMode
|
|
case canUseStealthMode
|
|
case disableSwipeToRecordStory
|
|
case quickTranslateButton
|
|
case outgoingLanguageTranslation
|
|
case showRepostToStory
|
|
case showRepostToStoryV2
|
|
case contextShowSelectFromUser
|
|
case contextShowSaveToCloud
|
|
case contextShowRestrict
|
|
// case contextShowBan
|
|
case contextShowHideForwardName
|
|
case contextShowReport
|
|
case contextShowReply
|
|
case contextShowPin
|
|
case contextShowSaveMedia
|
|
case contextShowMessageReplies
|
|
case contextShowJson
|
|
case disableScrollToNextChannel
|
|
case disableScrollToNextTopic
|
|
case disableChatSwipeOptions
|
|
case disableDeleteChatSwipeOption
|
|
case disableGalleryCamera
|
|
case disableGalleryCameraPreview
|
|
case disableSendAsButton
|
|
case disableSnapDeletionEffect
|
|
case stickerSize
|
|
case stickerTimestamp
|
|
case hideRecordingButton
|
|
case hideTabBar
|
|
case showDC
|
|
case showCreationDate
|
|
case showRegDate
|
|
case regDateCache
|
|
case compactChatList
|
|
case compactFolderNames
|
|
case allChatsTitleLengthOverride
|
|
// case allChatsFolderPositionOverride
|
|
case allChatsHidden
|
|
case defaultEmojisFirst
|
|
case messageDoubleTapActionOutgoing
|
|
case wideChannelPosts
|
|
case forceEmojiTab
|
|
case forceBuiltInMic
|
|
case secondsInMessages
|
|
case hideChannelBottomButton
|
|
case forceSystemSharing
|
|
case confirmCalls
|
|
case videoPIPSwipeDirection
|
|
case legacyNotificationsFix
|
|
case messageFilterKeywords
|
|
case inputToolbar
|
|
case pinnedMessageNotifications
|
|
case mentionsAndRepliesNotifications
|
|
case primaryUserId
|
|
case status
|
|
case dismissedSGSuggestions
|
|
case duckyAppIconAvailable
|
|
case transcriptionBackend
|
|
case translationBackend
|
|
case customAppBadge
|
|
case canUseNY
|
|
case nyStyle
|
|
case wideTabBar
|
|
case tabBarSearchEnabled
|
|
case hideStories
|
|
case warnOnStoriesOpen
|
|
case showProfileId
|
|
case sendWithReturnKey
|
|
case chatListLines
|
|
case showDeletedMessages
|
|
case saveEditHistory
|
|
// MARK: Saved Deleted Messages (AyuGram-style)
|
|
case saveDeletedMessagesMedia
|
|
case saveDeletedMessagesReactions
|
|
case saveDeletedMessagesForBots
|
|
// Ghost Mode settings
|
|
case ghostModeMessageSendDelaySeconds
|
|
case disableOnlineStatus
|
|
case disableTypingStatus
|
|
case disableRecordingVideoStatus
|
|
case disableUploadingVideoStatus
|
|
case disableVCMessageRecordingStatus
|
|
case disableVCMessageUploadingStatus
|
|
case disableUploadingPhotoStatus
|
|
case disableUploadingFileStatus
|
|
case disableChoosingLocationStatus
|
|
case disableChoosingContactStatus
|
|
case disablePlayingGameStatus
|
|
case disableRecordingRoundVideoStatus
|
|
case disableUploadingRoundVideoStatus
|
|
case disableSpeakingInGroupCallStatus
|
|
case disableChoosingStickerStatus
|
|
case disableEmojiInteractionStatus
|
|
case disableEmojiAcknowledgementStatus
|
|
case disableMessageReadReceipt
|
|
case disableStoryReadReceipt
|
|
case disableAllAds
|
|
case hideProxySponsor
|
|
case enableSavingProtectedContent
|
|
case disableScreenshotDetection
|
|
case enableSavingSelfDestructingMessages
|
|
case disableSecretChatBlurOnScreenshot
|
|
case enableLocalPremium
|
|
case scrollToTopButtonEnabled
|
|
case fakeLocationEnabled
|
|
case keepRemovedChannels
|
|
case enableVideoToCircleOrVoice
|
|
case enableTelescope
|
|
// Font replacement (A-Font style)
|
|
case enableFontReplacement
|
|
case fontReplacementName
|
|
case fontReplacementBoldName
|
|
case fontReplacementFilePath
|
|
case fontReplacementBoldFilePath
|
|
case enableLocalMessageEditing
|
|
case disableCompactNumbers
|
|
case disableZalgoText
|
|
// Оформление
|
|
case unlimitedFavoriteStickers
|
|
// Запись времени в сети
|
|
case enableOnlineStatusRecording
|
|
case onlineStatusRecordingIntervalMinutes
|
|
case savedOnlineStatusByPeerId
|
|
case addMusicFromDeviceToProfile
|
|
case hideReactions
|
|
case pluginSystemEnabled
|
|
case installedPluginsJson
|
|
case chatExportEnabled
|
|
case profileCoverMediaPath
|
|
case profileCoverIsVideo
|
|
case emojiDownloaderEnabled
|
|
case feelRichEnabled
|
|
case feelRichStarsAmount
|
|
case giftIdEnabled
|
|
case fakeProfileEnabled
|
|
case fakeProfileTargetUserId
|
|
case fakeProfileFirstName
|
|
case fakeProfileLastName
|
|
case fakeProfileUsername
|
|
case fakeProfilePhone
|
|
case fakeProfileId
|
|
case fakeProfilePremium
|
|
case fakeProfileVerified
|
|
case fakeProfileScam
|
|
case fakeProfileFake
|
|
case fakeProfileSupport
|
|
case fakeProfileBot
|
|
case currentAccountPeerId
|
|
case notificationMutedAccountRecordIds
|
|
case gatedFeatureKeys
|
|
case unlockedFeatureKeys
|
|
}
|
|
|
|
public enum DownloadSpeedBoostValues: String, CaseIterable {
|
|
case none
|
|
case medium
|
|
case maximum
|
|
}
|
|
|
|
public enum BottomTabStyleValues: String, CaseIterable {
|
|
case telegram
|
|
case ios
|
|
}
|
|
|
|
public enum AllChatsTitleLengthOverride: String, CaseIterable {
|
|
case none
|
|
case short
|
|
case long
|
|
}
|
|
|
|
public enum AllChatsFolderPositionOverride: String, CaseIterable {
|
|
case none
|
|
case last
|
|
case hidden
|
|
}
|
|
|
|
public enum ChatListLines: String, CaseIterable {
|
|
case three = "3"
|
|
case two = "2"
|
|
case one = "1"
|
|
|
|
public static let defaultValue: ChatListLines = .three
|
|
}
|
|
|
|
public enum MessageDoubleTapAction: String, CaseIterable {
|
|
case `default`
|
|
case none
|
|
case edit
|
|
}
|
|
|
|
public enum VideoPIPSwipeDirection: String, CaseIterable {
|
|
case up
|
|
case down
|
|
case none
|
|
}
|
|
|
|
public enum TranscriptionBackend: String, CaseIterable {
|
|
case `default`
|
|
case apple
|
|
}
|
|
|
|
public enum TranslationBackend: String, CaseIterable {
|
|
case `default`
|
|
case gtranslate
|
|
case system
|
|
// Make sure to update TranslationConfiguration
|
|
}
|
|
|
|
public enum PinnedMessageNotificationsSettings: String, CaseIterable {
|
|
case `default`
|
|
case silenced
|
|
case disabled
|
|
}
|
|
|
|
public enum MentionsAndRepliesNotificationsSettings: String, CaseIterable {
|
|
case `default`
|
|
case silenced
|
|
case disabled
|
|
}
|
|
|
|
public enum NYStyle: String, CaseIterable {
|
|
case `default`
|
|
case snow
|
|
case lightning
|
|
}
|
|
|
|
public static let defaultValues: [String: Any] = [
|
|
Keys.hidePhoneInSettings.rawValue: true,
|
|
Keys.showTabNames.rawValue: true,
|
|
Keys.startTelescopeWithRearCam.rawValue: false,
|
|
Keys.accountColorsSaturation.rawValue: 100,
|
|
Keys.uploadSpeedBoost.rawValue: false,
|
|
Keys.downloadSpeedBoost.rawValue: DownloadSpeedBoostValues.none.rawValue,
|
|
Keys.rememberLastFolder.rawValue: false,
|
|
Keys.bottomTabStyle.rawValue: BottomTabStyleValues.telegram.rawValue,
|
|
Keys.lastAccountFolders.rawValue: [:],
|
|
Keys.localDNSForProxyHost.rawValue: false,
|
|
Keys.sendLargePhotos.rawValue: false,
|
|
Keys.outgoingPhotoQuality.rawValue: 70,
|
|
Keys.storyStealthMode.rawValue: false,
|
|
Keys.canUseStealthMode.rawValue: true,
|
|
Keys.disableSwipeToRecordStory.rawValue: false,
|
|
Keys.quickTranslateButton.rawValue: false,
|
|
Keys.outgoingLanguageTranslation.rawValue: [:],
|
|
Keys.showRepostToStory.rawValue: true,
|
|
Keys.contextShowSelectFromUser.rawValue: true,
|
|
Keys.contextShowSaveToCloud.rawValue: true,
|
|
Keys.contextShowRestrict.rawValue: true,
|
|
// Keys.contextShowBan.rawValue: true,
|
|
Keys.contextShowHideForwardName.rawValue: true,
|
|
Keys.contextShowReport.rawValue: true,
|
|
Keys.contextShowReply.rawValue: true,
|
|
Keys.contextShowPin.rawValue: true,
|
|
Keys.contextShowSaveMedia.rawValue: true,
|
|
Keys.contextShowMessageReplies.rawValue: true,
|
|
Keys.contextShowJson.rawValue: false,
|
|
Keys.disableScrollToNextChannel.rawValue: false,
|
|
Keys.disableScrollToNextTopic.rawValue: false,
|
|
Keys.disableChatSwipeOptions.rawValue: false,
|
|
Keys.disableDeleteChatSwipeOption.rawValue: false,
|
|
Keys.disableGalleryCamera.rawValue: false,
|
|
Keys.disableGalleryCameraPreview.rawValue: false,
|
|
Keys.disableSendAsButton.rawValue: false,
|
|
Keys.disableSnapDeletionEffect.rawValue: false,
|
|
Keys.stickerSize.rawValue: 100,
|
|
Keys.stickerTimestamp.rawValue: true,
|
|
Keys.hideRecordingButton.rawValue: false,
|
|
Keys.hideTabBar.rawValue: false,
|
|
Keys.showDC.rawValue: false,
|
|
Keys.showCreationDate.rawValue: true,
|
|
Keys.showRegDate.rawValue: true,
|
|
Keys.regDateCache.rawValue: [:],
|
|
Keys.compactChatList.rawValue: false,
|
|
Keys.compactFolderNames.rawValue: false,
|
|
Keys.allChatsTitleLengthOverride.rawValue: AllChatsTitleLengthOverride.none.rawValue,
|
|
// Keys.allChatsFolderPositionOverride.rawValue: AllChatsFolderPositionOverride.none.rawValue
|
|
Keys.allChatsHidden.rawValue: false,
|
|
Keys.defaultEmojisFirst.rawValue: false,
|
|
Keys.messageDoubleTapActionOutgoing.rawValue: MessageDoubleTapAction.default.rawValue,
|
|
Keys.wideChannelPosts.rawValue: false,
|
|
Keys.forceEmojiTab.rawValue: false,
|
|
Keys.hideChannelBottomButton.rawValue: false,
|
|
Keys.secondsInMessages.rawValue: false,
|
|
Keys.forceSystemSharing.rawValue: false,
|
|
Keys.confirmCalls.rawValue: true,
|
|
Keys.videoPIPSwipeDirection.rawValue: VideoPIPSwipeDirection.up.rawValue,
|
|
Keys.messageFilterKeywords.rawValue: [],
|
|
Keys.inputToolbar.rawValue: false,
|
|
Keys.primaryUserId.rawValue: "",
|
|
Keys.dismissedSGSuggestions.rawValue: [],
|
|
Keys.duckyAppIconAvailable.rawValue: true,
|
|
Keys.transcriptionBackend.rawValue: TranscriptionBackend.default.rawValue,
|
|
Keys.translationBackend.rawValue: TranslationBackend.default.rawValue,
|
|
// Default app badge (GLEGram Dark Purple)
|
|
Keys.customAppBadge.rawValue: "SkyAppBadge",
|
|
Keys.canUseNY.rawValue: false,
|
|
Keys.nyStyle.rawValue: NYStyle.default.rawValue,
|
|
Keys.wideTabBar.rawValue: false,
|
|
Keys.tabBarSearchEnabled.rawValue: true,
|
|
Keys.hideStories.rawValue: false,
|
|
Keys.warnOnStoriesOpen.rawValue: false,
|
|
Keys.showProfileId.rawValue: true,
|
|
Keys.sendWithReturnKey.rawValue: false,
|
|
Keys.chatListLines.rawValue: ChatListLines.defaultValue.rawValue,
|
|
Keys.showDeletedMessages.rawValue: true,
|
|
Keys.saveEditHistory.rawValue: true,
|
|
// Saved Deleted Messages defaults (AyuGram-style)
|
|
Keys.saveDeletedMessagesMedia.rawValue: true,
|
|
Keys.saveDeletedMessagesReactions.rawValue: true,
|
|
Keys.saveDeletedMessagesForBots.rawValue: true,
|
|
// Ghost Mode defaults
|
|
Keys.ghostModeMessageSendDelaySeconds.rawValue: 0,
|
|
Keys.disableOnlineStatus.rawValue: false,
|
|
Keys.disableTypingStatus.rawValue: false,
|
|
Keys.disableRecordingVideoStatus.rawValue: false,
|
|
Keys.disableUploadingVideoStatus.rawValue: false,
|
|
Keys.disableVCMessageRecordingStatus.rawValue: false,
|
|
Keys.disableVCMessageUploadingStatus.rawValue: false,
|
|
Keys.disableUploadingPhotoStatus.rawValue: false,
|
|
Keys.disableUploadingFileStatus.rawValue: false,
|
|
Keys.disableChoosingLocationStatus.rawValue: false,
|
|
Keys.disableChoosingContactStatus.rawValue: false,
|
|
Keys.disablePlayingGameStatus.rawValue: false,
|
|
Keys.disableRecordingRoundVideoStatus.rawValue: false,
|
|
Keys.disableUploadingRoundVideoStatus.rawValue: false,
|
|
Keys.disableSpeakingInGroupCallStatus.rawValue: false,
|
|
Keys.disableChoosingStickerStatus.rawValue: false,
|
|
Keys.disableEmojiInteractionStatus.rawValue: false,
|
|
Keys.disableEmojiAcknowledgementStatus.rawValue: false,
|
|
Keys.disableMessageReadReceipt.rawValue: false,
|
|
Keys.disableStoryReadReceipt.rawValue: false,
|
|
Keys.disableAllAds.rawValue: false,
|
|
Keys.hideProxySponsor.rawValue: false,
|
|
Keys.enableSavingProtectedContent.rawValue: false,
|
|
Keys.disableScreenshotDetection.rawValue: false,
|
|
Keys.enableSavingSelfDestructingMessages.rawValue: false,
|
|
Keys.disableSecretChatBlurOnScreenshot.rawValue: false,
|
|
Keys.enableLocalPremium.rawValue: false,
|
|
Keys.scrollToTopButtonEnabled.rawValue: true,
|
|
Keys.fakeLocationEnabled.rawValue: false,
|
|
Keys.keepRemovedChannels.rawValue: false,
|
|
Keys.enableVideoToCircleOrVoice.rawValue: false,
|
|
Keys.enableTelescope.rawValue: false,
|
|
Keys.enableFontReplacement.rawValue: false,
|
|
Keys.fontReplacementName.rawValue: "",
|
|
Keys.fontReplacementBoldName.rawValue: "",
|
|
Keys.fontReplacementFilePath.rawValue: "",
|
|
Keys.fontReplacementBoldFilePath.rawValue: "",
|
|
Keys.enableLocalMessageEditing.rawValue: false,
|
|
Keys.disableCompactNumbers.rawValue: false,
|
|
Keys.disableZalgoText.rawValue: false,
|
|
Keys.unlimitedFavoriteStickers.rawValue: true,
|
|
Keys.enableOnlineStatusRecording.rawValue: false,
|
|
Keys.onlineStatusRecordingIntervalMinutes.rawValue: 5,
|
|
Keys.savedOnlineStatusByPeerId.rawValue: "{}",
|
|
Keys.addMusicFromDeviceToProfile.rawValue: false,
|
|
Keys.hideReactions.rawValue: false,
|
|
Keys.pluginSystemEnabled.rawValue: false,
|
|
Keys.installedPluginsJson.rawValue: "[]",
|
|
Keys.chatExportEnabled.rawValue: false,
|
|
Keys.profileCoverMediaPath.rawValue: "",
|
|
Keys.profileCoverIsVideo.rawValue: false,
|
|
Keys.emojiDownloaderEnabled.rawValue: false,
|
|
Keys.feelRichEnabled.rawValue: false,
|
|
Keys.feelRichStarsAmount.rawValue: "1000",
|
|
Keys.giftIdEnabled.rawValue: false,
|
|
Keys.fakeProfileEnabled.rawValue: false,
|
|
Keys.fakeProfileTargetUserId.rawValue: "",
|
|
Keys.fakeProfileFirstName.rawValue: "",
|
|
Keys.fakeProfileLastName.rawValue: "",
|
|
Keys.fakeProfileUsername.rawValue: "",
|
|
Keys.fakeProfilePhone.rawValue: "",
|
|
Keys.fakeProfileId.rawValue: "",
|
|
Keys.fakeProfilePremium.rawValue: false,
|
|
Keys.fakeProfileVerified.rawValue: false,
|
|
Keys.fakeProfileScam.rawValue: false,
|
|
Keys.fakeProfileFake.rawValue: false,
|
|
Keys.fakeProfileSupport.rawValue: false,
|
|
Keys.fakeProfileBot.rawValue: false,
|
|
Keys.currentAccountPeerId.rawValue: "",
|
|
Keys.gatedFeatureKeys.rawValue: [] as [String],
|
|
Keys.unlockedFeatureKeys.rawValue: [] as [String]
|
|
]
|
|
|
|
public static let groupDefaultValues: [String: Any] = [
|
|
Keys.legacyNotificationsFix.rawValue: false,
|
|
Keys.pinnedMessageNotifications.rawValue: PinnedMessageNotificationsSettings.default.rawValue,
|
|
Keys.mentionsAndRepliesNotifications.rawValue: MentionsAndRepliesNotificationsSettings.default.rawValue,
|
|
Keys.status.rawValue: 1,
|
|
Keys.showRepostToStoryV2.rawValue: true,
|
|
Keys.notificationMutedAccountRecordIds.rawValue: [] as [String],
|
|
]
|
|
|
|
@UserDefault(key: Keys.hidePhoneInSettings.rawValue)
|
|
public var hidePhoneInSettings: Bool
|
|
|
|
@UserDefault(key: Keys.showTabNames.rawValue)
|
|
public var showTabNames: Bool
|
|
|
|
@UserDefault(key: Keys.startTelescopeWithRearCam.rawValue)
|
|
public var startTelescopeWithRearCam: Bool
|
|
|
|
@UserDefault(key: Keys.accountColorsSaturation.rawValue)
|
|
public var accountColorsSaturation: Int32
|
|
|
|
@UserDefault(key: Keys.uploadSpeedBoost.rawValue)
|
|
public var uploadSpeedBoost: Bool
|
|
|
|
@UserDefault(key: Keys.downloadSpeedBoost.rawValue)
|
|
public var downloadSpeedBoost: String
|
|
|
|
@UserDefault(key: Keys.rememberLastFolder.rawValue)
|
|
public var rememberLastFolder: Bool
|
|
|
|
// Disabled while Telegram is migrating to Glass
|
|
// @UserDefault(key: Keys.bottomTabStyle.rawValue)
|
|
public var bottomTabStyle: String {
|
|
set {}
|
|
get {
|
|
return BottomTabStyleValues.ios.rawValue
|
|
}
|
|
}
|
|
|
|
public var lastAccountFolders = UserDefaultsBackedDictionary<String, Int32>(userDefaultsKey: Keys.lastAccountFolders.rawValue, threadSafe: false)
|
|
|
|
@UserDefault(key: Keys.localDNSForProxyHost.rawValue)
|
|
public var localDNSForProxyHost: Bool
|
|
|
|
@UserDefault(key: Keys.sendLargePhotos.rawValue)
|
|
public var sendLargePhotos: Bool
|
|
|
|
@UserDefault(key: Keys.outgoingPhotoQuality.rawValue)
|
|
public var outgoingPhotoQuality: Int32
|
|
|
|
@UserDefault(key: Keys.storyStealthMode.rawValue)
|
|
public var storyStealthMode: Bool
|
|
|
|
@UserDefault(key: Keys.canUseStealthMode.rawValue)
|
|
public var canUseStealthMode: Bool
|
|
|
|
@UserDefault(key: Keys.disableSwipeToRecordStory.rawValue)
|
|
public var disableSwipeToRecordStory: Bool
|
|
|
|
@UserDefault(key: Keys.quickTranslateButton.rawValue)
|
|
public var quickTranslateButton: Bool
|
|
|
|
public var outgoingLanguageTranslation = UserDefaultsBackedDictionary<String, String>(userDefaultsKey: Keys.outgoingLanguageTranslation.rawValue, threadSafe: false)
|
|
|
|
// @available(*, deprecated, message: "Use showRepostToStoryV2 instead")
|
|
@UserDefault(key: Keys.showRepostToStory.rawValue)
|
|
public var showRepostToStory: Bool
|
|
|
|
@UserDefault(key: Keys.showRepostToStoryV2.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
|
|
public var showRepostToStoryV2: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowRestrict.rawValue)
|
|
public var contextShowRestrict: Bool
|
|
|
|
/*@UserDefault(key: Keys.contextShowBan.rawValue)
|
|
public var contextShowBan: Bool*/
|
|
|
|
@UserDefault(key: Keys.contextShowSelectFromUser.rawValue)
|
|
public var contextShowSelectFromUser: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowSaveToCloud.rawValue)
|
|
public var contextShowSaveToCloud: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowHideForwardName.rawValue)
|
|
public var contextShowHideForwardName: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowReport.rawValue)
|
|
public var contextShowReport: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowReply.rawValue)
|
|
public var contextShowReply: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowPin.rawValue)
|
|
public var contextShowPin: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowSaveMedia.rawValue)
|
|
public var contextShowSaveMedia: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowMessageReplies.rawValue)
|
|
public var contextShowMessageReplies: Bool
|
|
|
|
@UserDefault(key: Keys.contextShowJson.rawValue)
|
|
public var contextShowJson: Bool
|
|
|
|
@UserDefault(key: Keys.disableScrollToNextChannel.rawValue)
|
|
public var disableScrollToNextChannel: Bool
|
|
|
|
@UserDefault(key: Keys.disableScrollToNextTopic.rawValue)
|
|
public var disableScrollToNextTopic: Bool
|
|
|
|
@UserDefault(key: Keys.disableChatSwipeOptions.rawValue)
|
|
public var disableChatSwipeOptions: Bool
|
|
|
|
@UserDefault(key: Keys.disableDeleteChatSwipeOption.rawValue)
|
|
public var disableDeleteChatSwipeOption: Bool
|
|
|
|
@UserDefault(key: Keys.disableGalleryCamera.rawValue)
|
|
public var disableGalleryCamera: Bool
|
|
|
|
@UserDefault(key: Keys.disableGalleryCameraPreview.rawValue)
|
|
public var disableGalleryCameraPreview: Bool
|
|
|
|
@UserDefault(key: Keys.disableSendAsButton.rawValue)
|
|
public var disableSendAsButton: Bool
|
|
|
|
@UserDefault(key: Keys.disableSnapDeletionEffect.rawValue)
|
|
public var disableSnapDeletionEffect: Bool
|
|
|
|
@UserDefault(key: Keys.stickerSize.rawValue)
|
|
public var stickerSize: Int32
|
|
|
|
@UserDefault(key: Keys.stickerTimestamp.rawValue)
|
|
public var stickerTimestamp: Bool
|
|
|
|
@UserDefault(key: Keys.hideRecordingButton.rawValue)
|
|
public var hideRecordingButton: Bool
|
|
|
|
@UserDefault(key: Keys.hideTabBar.rawValue)
|
|
public var hideTabBar: Bool
|
|
|
|
@UserDefault(key: Keys.showDC.rawValue)
|
|
public var showDC: Bool
|
|
|
|
@UserDefault(key: Keys.showCreationDate.rawValue)
|
|
public var showCreationDate: Bool
|
|
|
|
@UserDefault(key: Keys.showRegDate.rawValue)
|
|
public var showRegDate: Bool
|
|
|
|
public var regDateCache = UserDefaultsBackedDictionary<String, Data>(userDefaultsKey: Keys.regDateCache.rawValue, threadSafe: false)
|
|
|
|
@UserDefault(key: Keys.compactChatList.rawValue)
|
|
public var compactChatList: Bool
|
|
|
|
@UserDefault(key: Keys.compactFolderNames.rawValue)
|
|
public var compactFolderNames: Bool
|
|
|
|
@UserDefault(key: Keys.allChatsTitleLengthOverride.rawValue)
|
|
public var allChatsTitleLengthOverride: String
|
|
//
|
|
// @UserDefault(key: Keys.allChatsFolderPositionOverride.rawValue)
|
|
// public var allChatsFolderPositionOverride: String
|
|
@UserDefault(key: Keys.allChatsHidden.rawValue)
|
|
public var allChatsHidden: Bool
|
|
|
|
@UserDefault(key: Keys.defaultEmojisFirst.rawValue)
|
|
public var defaultEmojisFirst: Bool
|
|
|
|
@UserDefault(key: Keys.messageDoubleTapActionOutgoing.rawValue)
|
|
public var messageDoubleTapActionOutgoing: String
|
|
|
|
@UserDefault(key: Keys.wideChannelPosts.rawValue)
|
|
public var wideChannelPosts: Bool
|
|
|
|
@UserDefault(key: Keys.forceEmojiTab.rawValue)
|
|
public var forceEmojiTab: Bool
|
|
|
|
@UserDefault(key: Keys.forceBuiltInMic.rawValue)
|
|
public var forceBuiltInMic: Bool
|
|
|
|
@UserDefault(key: Keys.secondsInMessages.rawValue)
|
|
public var secondsInMessages: Bool
|
|
|
|
@UserDefault(key: Keys.hideChannelBottomButton.rawValue)
|
|
public var hideChannelBottomButton: Bool
|
|
|
|
@UserDefault(key: Keys.forceSystemSharing.rawValue)
|
|
public var forceSystemSharing: Bool
|
|
|
|
@UserDefault(key: Keys.confirmCalls.rawValue)
|
|
public var confirmCalls: Bool
|
|
|
|
@UserDefault(key: Keys.videoPIPSwipeDirection.rawValue)
|
|
public var videoPIPSwipeDirection: String
|
|
|
|
@UserDefault(key: Keys.legacyNotificationsFix.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
|
|
public var legacyNotificationsFix: Bool
|
|
|
|
@UserDefault(key: Keys.status.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
|
|
public var status: Int64
|
|
|
|
public var ephemeralStatus: Int64 = 1
|
|
|
|
@UserDefault(key: Keys.messageFilterKeywords.rawValue)
|
|
public var messageFilterKeywords: [String]
|
|
|
|
@UserDefault(key: Keys.inputToolbar.rawValue)
|
|
public var inputToolbar: Bool
|
|
|
|
@UserDefault(key: Keys.pinnedMessageNotifications.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
|
|
public var pinnedMessageNotifications: String
|
|
|
|
@UserDefault(key: Keys.mentionsAndRepliesNotifications.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
|
|
public var mentionsAndRepliesNotifications: String
|
|
|
|
/// Account record IDs (as strings) for which push notifications are muted.
|
|
@UserDefault(key: Keys.notificationMutedAccountRecordIds.rawValue, userDefaults: UserDefaults(suiteName: APP_GROUP_IDENTIFIER) ?? .standard)
|
|
public var notificationMutedAccountRecordIds: [String]
|
|
|
|
public func isAccountNotificationMuted(recordId: Int64) -> Bool {
|
|
return notificationMutedAccountRecordIds.contains(String(recordId))
|
|
}
|
|
|
|
public func setAccountNotificationMuted(recordId: Int64, muted: Bool) {
|
|
let key = String(recordId)
|
|
var current = notificationMutedAccountRecordIds
|
|
if muted {
|
|
if !current.contains(key) {
|
|
current.append(key)
|
|
}
|
|
} else {
|
|
current.removeAll { $0 == key }
|
|
}
|
|
notificationMutedAccountRecordIds = current
|
|
}
|
|
|
|
// MARK: - Gated Features
|
|
|
|
@UserDefault(key: Keys.gatedFeatureKeys.rawValue)
|
|
public var gatedFeatureKeys: [String]
|
|
|
|
@UserDefault(key: Keys.unlockedFeatureKeys.rawValue)
|
|
public var unlockedFeatureKeys: [String]
|
|
|
|
/// A gated feature is visible only if it's been unlocked (or if it's not gated at all).
|
|
public func isFeatureVisible(_ settingKey: String) -> Bool {
|
|
if !gatedFeatureKeys.contains(settingKey) { return true }
|
|
return unlockedFeatureKeys.contains(settingKey)
|
|
}
|
|
|
|
public func unlockFeature(_ settingKey: String) {
|
|
var current = unlockedFeatureKeys
|
|
if !current.contains(settingKey) {
|
|
current.append(settingKey)
|
|
unlockedFeatureKeys = current
|
|
}
|
|
}
|
|
|
|
/// Deeplink path → feature key mapping (cached from server)
|
|
private static let gatedDeeplinkMapKey = "sg_gatedDeeplinkMap"
|
|
|
|
public var gatedDeeplinkMap: [String: String] {
|
|
get { UserDefaults.standard.dictionary(forKey: Self.gatedDeeplinkMapKey) as? [String: String] ?? [:] }
|
|
set { UserDefaults.standard.set(newValue, forKey: Self.gatedDeeplinkMapKey) }
|
|
}
|
|
|
|
/// Update gated features from server response
|
|
public func updateGatedFeatures(_ features: [(key: String, deeplinkPath: String)]) {
|
|
gatedFeatureKeys = features.map { $0.key }
|
|
var map: [String: String] = [:]
|
|
for f in features { map[f.deeplinkPath] = f.key }
|
|
gatedDeeplinkMap = map
|
|
}
|
|
|
|
/// Handle deeplink path. For single paths, unlocks locally and returns the key.
|
|
/// For group paths (not in map), returns nil — server will handle and return keys.
|
|
public func handleFeatureDeeplink(_ path: String) -> String? {
|
|
guard let key = gatedDeeplinkMap[path] else {
|
|
// Group path (e.g. "unlock-all", "ghost-mode") — not in local map.
|
|
// Server will resolve and return unlocked keys.
|
|
return nil
|
|
}
|
|
unlockFeature(key)
|
|
return key
|
|
}
|
|
|
|
/// Unlock multiple features at once (used for group deeplinks after server response).
|
|
public func unlockFeatures(_ keys: [String]) {
|
|
var current = unlockedFeatureKeys
|
|
for key in keys {
|
|
if !current.contains(key) {
|
|
current.append(key)
|
|
}
|
|
}
|
|
unlockedFeatureKeys = current
|
|
}
|
|
|
|
@UserDefault(key: Keys.primaryUserId.rawValue)
|
|
public var primaryUserId: String
|
|
|
|
@UserDefault(key: Keys.dismissedSGSuggestions.rawValue)
|
|
public var dismissedSGSuggestions: [String]
|
|
|
|
@UserDefault(key: Keys.duckyAppIconAvailable.rawValue)
|
|
public var duckyAppIconAvailable: Bool
|
|
|
|
@UserDefault(key: Keys.transcriptionBackend.rawValue)
|
|
public var transcriptionBackend: String
|
|
|
|
@UserDefault(key: Keys.translationBackend.rawValue)
|
|
public var translationBackend: String
|
|
|
|
@UserDefault(key: Keys.customAppBadge.rawValue)
|
|
public var customAppBadge: String
|
|
|
|
@UserDefault(key: Keys.canUseNY.rawValue)
|
|
public var canUseNY: Bool
|
|
|
|
@UserDefault(key: Keys.nyStyle.rawValue)
|
|
public var nyStyle: String
|
|
|
|
@UserDefault(key: Keys.wideTabBar.rawValue)
|
|
public var wideTabBar: Bool
|
|
|
|
@UserDefault(key: Keys.tabBarSearchEnabled.rawValue)
|
|
public var tabBarSearchEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.hideStories.rawValue)
|
|
public var hideStories: Bool
|
|
|
|
@UserDefault(key: Keys.warnOnStoriesOpen.rawValue)
|
|
public var warnOnStoriesOpen: Bool
|
|
|
|
@UserDefault(key: Keys.showProfileId.rawValue)
|
|
public var showProfileId: Bool
|
|
|
|
@UserDefault(key: Keys.chatListLines.rawValue)
|
|
public var chatListLines: String
|
|
|
|
@UserDefault(key: Keys.sendWithReturnKey.rawValue)
|
|
public var sendWithReturnKey: Bool
|
|
|
|
@UserDefault(key: Keys.showDeletedMessages.rawValue)
|
|
public var showDeletedMessages: Bool
|
|
|
|
@UserDefault(key: Keys.saveEditHistory.rawValue)
|
|
public var saveEditHistory: Bool
|
|
|
|
// MARK: Saved Deleted Messages (AyuGram-style)
|
|
@UserDefault(key: Keys.saveDeletedMessagesMedia.rawValue)
|
|
public var saveDeletedMessagesMedia: Bool
|
|
|
|
@UserDefault(key: Keys.saveDeletedMessagesReactions.rawValue)
|
|
public var saveDeletedMessagesReactions: Bool
|
|
|
|
@UserDefault(key: Keys.saveDeletedMessagesForBots.rawValue)
|
|
public var saveDeletedMessagesForBots: Bool
|
|
|
|
// Ghost Mode settings
|
|
/// 0 = off, 12 / 30 / 45 = delay in seconds
|
|
@UserDefault(key: Keys.ghostModeMessageSendDelaySeconds.rawValue)
|
|
public var ghostModeMessageSendDelaySeconds: Int32
|
|
|
|
@UserDefault(key: Keys.disableOnlineStatus.rawValue)
|
|
public var disableOnlineStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableTypingStatus.rawValue)
|
|
public var disableTypingStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableRecordingVideoStatus.rawValue)
|
|
public var disableRecordingVideoStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableUploadingVideoStatus.rawValue)
|
|
public var disableUploadingVideoStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableVCMessageRecordingStatus.rawValue)
|
|
public var disableVCMessageRecordingStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableVCMessageUploadingStatus.rawValue)
|
|
public var disableVCMessageUploadingStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableUploadingPhotoStatus.rawValue)
|
|
public var disableUploadingPhotoStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableUploadingFileStatus.rawValue)
|
|
public var disableUploadingFileStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableChoosingLocationStatus.rawValue)
|
|
public var disableChoosingLocationStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableChoosingContactStatus.rawValue)
|
|
public var disableChoosingContactStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disablePlayingGameStatus.rawValue)
|
|
public var disablePlayingGameStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableRecordingRoundVideoStatus.rawValue)
|
|
public var disableRecordingRoundVideoStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableUploadingRoundVideoStatus.rawValue)
|
|
public var disableUploadingRoundVideoStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableSpeakingInGroupCallStatus.rawValue)
|
|
public var disableSpeakingInGroupCallStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableChoosingStickerStatus.rawValue)
|
|
public var disableChoosingStickerStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableEmojiInteractionStatus.rawValue)
|
|
public var disableEmojiInteractionStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableEmojiAcknowledgementStatus.rawValue)
|
|
public var disableEmojiAcknowledgementStatus: Bool
|
|
|
|
@UserDefault(key: Keys.disableMessageReadReceipt.rawValue)
|
|
public var disableMessageReadReceipt: Bool
|
|
|
|
/// Peer IDs (as "namespace:id") to whom read receipts ARE sent (whitelist). Empty = send to all.
|
|
public var messageReadReceiptsSendToPeerIds: Set<String> {
|
|
get {
|
|
if let data = UserDefaults.standard.data(forKey: "messageReadReceiptsSendToPeerIds"),
|
|
let array = try? JSONDecoder().decode([String].self, from: data) {
|
|
return Set(array)
|
|
}
|
|
return []
|
|
}
|
|
set {
|
|
if let data = try? JSONEncoder().encode(Array(newValue)) {
|
|
UserDefaults.standard.set(data, forKey: "messageReadReceiptsSendToPeerIds")
|
|
synchronizeShared()
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Check if read receipts should be blocked for this peer (when disableMessageReadReceipt is false).
|
|
/// Whitelist: empty list = send to no one; non-empty = send only to peers in list.
|
|
public func shouldBlockReadReceiptFor(peerIdNamespace: Int32, peerIdId: Int64) -> Bool {
|
|
let list = messageReadReceiptsSendToPeerIds
|
|
if list.isEmpty { return true }
|
|
let key = "\(peerIdNamespace):\(peerIdId)"
|
|
return !list.contains(key)
|
|
}
|
|
|
|
@UserDefault(key: Keys.disableStoryReadReceipt.rawValue)
|
|
public var disableStoryReadReceipt: Bool
|
|
|
|
@UserDefault(key: Keys.disableAllAds.rawValue)
|
|
public var disableAllAds: Bool
|
|
|
|
@UserDefault(key: Keys.hideProxySponsor.rawValue)
|
|
public var hideProxySponsor: Bool
|
|
|
|
@UserDefault(key: Keys.enableSavingProtectedContent.rawValue)
|
|
public var enableSavingProtectedContent: Bool
|
|
|
|
@UserDefault(key: Keys.enableSavingSelfDestructingMessages.rawValue)
|
|
public var enableSavingSelfDestructingMessages: Bool
|
|
|
|
@UserDefault(key: Keys.disableSecretChatBlurOnScreenshot.rawValue)
|
|
public var disableSecretChatBlurOnScreenshot: Bool
|
|
|
|
@UserDefault(key: Keys.enableLocalPremium.rawValue)
|
|
public var enableLocalPremium: Bool
|
|
|
|
@UserDefault(key: Keys.disableScreenshotDetection.rawValue)
|
|
public var disableScreenshotDetection: Bool
|
|
|
|
@UserDefault(key: Keys.scrollToTopButtonEnabled.rawValue)
|
|
public var scrollToTopButtonEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.fakeLocationEnabled.rawValue)
|
|
public var fakeLocationEnabled: Bool
|
|
|
|
public var fakeLatitude: Double {
|
|
get { SGFileStore.shared.double(forKey: "fakeLatitude", default: 0.0) }
|
|
set { SGFileStore.shared.set(newValue, forKey: "fakeLatitude") }
|
|
}
|
|
|
|
public var fakeLongitude: Double {
|
|
get { SGFileStore.shared.double(forKey: "fakeLongitude", default: 0.0) }
|
|
set { SGFileStore.shared.set(newValue, forKey: "fakeLongitude") }
|
|
}
|
|
|
|
@UserDefault(key: Keys.keepRemovedChannels.rawValue)
|
|
public var keepRemovedChannels: Bool
|
|
|
|
@UserDefault(key: Keys.enableVideoToCircleOrVoice.rawValue)
|
|
public var enableVideoToCircleOrVoice: Bool
|
|
|
|
@UserDefault(key: Keys.enableTelescope.rawValue)
|
|
public var enableTelescope: Bool
|
|
|
|
/// Font replacement (A-Font style): enable, main font name, bold font name, size multiplier (100 = 1.0)
|
|
@UserDefault(key: Keys.enableFontReplacement.rawValue)
|
|
public var enableFontReplacement: Bool
|
|
|
|
@UserDefault(key: Keys.fontReplacementName.rawValue)
|
|
public var fontReplacementName: String
|
|
|
|
@UserDefault(key: Keys.fontReplacementBoldName.rawValue)
|
|
public var fontReplacementBoldName: String
|
|
|
|
/// Persistent path to copied main font file (so it survives app restart)
|
|
@UserDefault(key: Keys.fontReplacementFilePath.rawValue)
|
|
public var fontReplacementFilePath: String
|
|
|
|
/// Persistent path to copied bold font file
|
|
@UserDefault(key: Keys.fontReplacementBoldFilePath.rawValue)
|
|
public var fontReplacementBoldFilePath: String
|
|
|
|
public var fontReplacementSizeMultiplier: Int32 {
|
|
get { SGFileStore.shared.int32(forKey: "fontReplacementSizeMultiplier", default: 100) }
|
|
set { SGFileStore.shared.set(Int(newValue), forKey: "fontReplacementSizeMultiplier") }
|
|
}
|
|
|
|
@UserDefault(key: Keys.enableLocalMessageEditing.rawValue)
|
|
public var enableLocalMessageEditing: Bool
|
|
|
|
@UserDefault(key: Keys.disableCompactNumbers.rawValue)
|
|
public var disableCompactNumbers: Bool
|
|
|
|
@UserDefault(key: Keys.disableZalgoText.rawValue)
|
|
public var disableZalgoText: Bool
|
|
|
|
@UserDefault(key: Keys.unlimitedFavoriteStickers.rawValue)
|
|
public var unlimitedFavoriteStickers: Bool
|
|
|
|
@UserDefault(key: Keys.enableOnlineStatusRecording.rawValue)
|
|
public var enableOnlineStatusRecording: Bool
|
|
|
|
@UserDefault(key: Keys.onlineStatusRecordingIntervalMinutes.rawValue)
|
|
public var onlineStatusRecordingIntervalMinutes: Int32
|
|
|
|
@UserDefault(key: Keys.savedOnlineStatusByPeerId.rawValue)
|
|
public var savedOnlineStatusByPeerId: String
|
|
|
|
@UserDefault(key: Keys.addMusicFromDeviceToProfile.rawValue)
|
|
public var addMusicFromDeviceToProfile: Bool
|
|
|
|
@UserDefault(key: Keys.hideReactions.rawValue)
|
|
public var hideReactions: Bool
|
|
|
|
@UserDefault(key: Keys.pluginSystemEnabled.rawValue)
|
|
public var pluginSystemEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.installedPluginsJson.rawValue)
|
|
public var installedPluginsJson: String
|
|
|
|
@UserDefault(key: Keys.chatExportEnabled.rawValue)
|
|
public var chatExportEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.profileCoverMediaPath.rawValue)
|
|
public var profileCoverMediaPath: String
|
|
|
|
@UserDefault(key: Keys.profileCoverIsVideo.rawValue)
|
|
public var profileCoverIsVideo: Bool
|
|
|
|
@UserDefault(key: Keys.emojiDownloaderEnabled.rawValue)
|
|
public var emojiDownloaderEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.feelRichEnabled.rawValue)
|
|
public var feelRichEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.feelRichStarsAmount.rawValue)
|
|
public var feelRichStarsAmount: String
|
|
|
|
@UserDefault(key: Keys.giftIdEnabled.rawValue)
|
|
public var giftIdEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileEnabled.rawValue)
|
|
public var fakeProfileEnabled: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileTargetUserId.rawValue)
|
|
public var fakeProfileTargetUserId: String
|
|
|
|
@UserDefault(key: Keys.fakeProfileFirstName.rawValue)
|
|
public var fakeProfileFirstName: String
|
|
|
|
@UserDefault(key: Keys.fakeProfileLastName.rawValue)
|
|
public var fakeProfileLastName: String
|
|
|
|
@UserDefault(key: Keys.fakeProfileUsername.rawValue)
|
|
public var fakeProfileUsername: String
|
|
|
|
@UserDefault(key: Keys.fakeProfilePhone.rawValue)
|
|
public var fakeProfilePhone: String
|
|
|
|
@UserDefault(key: Keys.fakeProfileId.rawValue)
|
|
public var fakeProfileId: String
|
|
|
|
@UserDefault(key: Keys.fakeProfilePremium.rawValue)
|
|
public var fakeProfilePremium: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileVerified.rawValue)
|
|
public var fakeProfileVerified: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileScam.rawValue)
|
|
public var fakeProfileScam: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileFake.rawValue)
|
|
public var fakeProfileFake: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileSupport.rawValue)
|
|
public var fakeProfileSupport: Bool
|
|
|
|
@UserDefault(key: Keys.fakeProfileBot.rawValue)
|
|
public var fakeProfileBot: Bool
|
|
|
|
@UserDefault(key: Keys.currentAccountPeerId.rawValue)
|
|
public var currentAccountPeerId: String
|
|
|
|
/// Whether fake profile overlay should apply for this peer id (current account or target user).
|
|
public func shouldApplyFakeProfile(peerId: Int64) -> Bool {
|
|
guard fakeProfileEnabled else { return false }
|
|
let target: String = fakeProfileTargetUserId.isEmpty ? currentAccountPeerId : fakeProfileTargetUserId
|
|
guard let targetNum = Int64(target) else { return false }
|
|
return peerId == targetNum
|
|
}
|
|
|
|
/// Display value for first name when fake profile is active.
|
|
public func displayFirstName(peerId: Int64, real: String?) -> String {
|
|
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileFirstName.isEmpty ? fakeProfileFirstName : (real ?? "")
|
|
}
|
|
|
|
/// Display value for last name when fake profile is active.
|
|
public func displayLastName(peerId: Int64, real: String?) -> String {
|
|
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileLastName.isEmpty ? fakeProfileLastName : (real ?? "")
|
|
}
|
|
|
|
/// Display value for username (without @) when fake profile is active.
|
|
public func displayUsername(peerId: Int64, real: String?) -> String {
|
|
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileUsername.isEmpty ? fakeProfileUsername : (real ?? "")
|
|
}
|
|
|
|
/// Display value for phone when fake profile is active.
|
|
public func displayPhone(peerId: Int64, real: String?) -> String {
|
|
shouldApplyFakeProfile(peerId: peerId) && !fakeProfilePhone.isEmpty ? fakeProfilePhone : (real ?? "")
|
|
}
|
|
|
|
/// Display value for user id string when fake profile is active.
|
|
public func displayId(peerId: Int64, real: String?) -> String {
|
|
shouldApplyFakeProfile(peerId: peerId) && !fakeProfileId.isEmpty ? fakeProfileId : (real ?? "")
|
|
}
|
|
|
|
/// Saved "last seen" timestamps per peer (for online status recording). Key: peerId as Int64, value: timestamp.
|
|
public var savedOnlineStatusByPeerIdDict: [Int64: Int32] {
|
|
get {
|
|
guard let data = savedOnlineStatusByPeerId.data(using: .utf8),
|
|
let dict = try? JSONDecoder().decode([String: Int32].self, from: data) else {
|
|
return [:]
|
|
}
|
|
var result: [Int64: Int32] = [:]
|
|
for (k, v) in dict where Int64(k) != nil {
|
|
result[Int64(k)!] = v
|
|
}
|
|
return result
|
|
}
|
|
set {
|
|
let dict = Dictionary(uniqueKeysWithValues: newValue.map { ("\($0.key)", $0.value) })
|
|
if let data = try? JSONEncoder().encode(dict),
|
|
let string = String(data: data, encoding: .utf8) {
|
|
savedOnlineStatusByPeerId = string
|
|
synchronizeShared()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func getSavedOnlineStatusTimestamp(peerId: Int64) -> Int32? {
|
|
return savedOnlineStatusByPeerIdDict[peerId]
|
|
}
|
|
|
|
public static let onlineStatusTimestampDidChangeNotification = Notification.Name("SGOnlineStatusTimestampDidChange")
|
|
|
|
public func setSavedOnlineStatusTimestamp(peerId: Int64, timestamp: Int32) {
|
|
var dict = savedOnlineStatusByPeerIdDict
|
|
dict[peerId] = timestamp
|
|
savedOnlineStatusByPeerIdDict = dict
|
|
DispatchQueue.main.async {
|
|
NotificationCenter.default.post(name: SGSimpleSettings.onlineStatusTimestampDidChangeNotification, object: nil, userInfo: ["peerId": peerId])
|
|
}
|
|
}
|
|
|
|
/// Strip Zalgo / combining characters from string (for display when disableZalgoText is on).
|
|
public static func stripZalgo(_ string: String) -> String {
|
|
return string.filter { char in
|
|
!char.unicodeScalars.contains(where: { (scalar: Unicode.Scalar) in
|
|
let cat = scalar.properties.generalCategory
|
|
return cat == .nonspacingMark || cat == .spacingMark || cat == .enclosingMark
|
|
})
|
|
}
|
|
}
|
|
|
|
// MARK: Swiftgram - Keep Removed Channels
|
|
public var removedChannels: [Int64] {
|
|
get {
|
|
if let data = UserDefaults.standard.data(forKey: "removedChannels"),
|
|
let array = try? JSONDecoder().decode([Int64].self, from: data) {
|
|
return array
|
|
}
|
|
return []
|
|
}
|
|
set {
|
|
if let data = try? JSONEncoder().encode(newValue) {
|
|
UserDefaults.standard.set(data, forKey: "removedChannels")
|
|
synchronizeShared()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func isChannelRemoved(_ peerId: Int64) -> Bool {
|
|
return keepRemovedChannels && removedChannels.contains(peerId)
|
|
}
|
|
|
|
public func markChannelAsRemoved(_ peerId: Int64) {
|
|
if !removedChannels.contains(peerId) {
|
|
removedChannels.append(peerId)
|
|
}
|
|
}
|
|
|
|
public func unmarkChannelAsRemoved(_ peerId: Int64) {
|
|
removedChannels.removeAll { $0 == peerId }
|
|
}
|
|
|
|
// MARK: Swiftgram - Keep Removed User Chats
|
|
public var removedUserChats: [Int64] {
|
|
get {
|
|
if let data = UserDefaults.standard.data(forKey: "removedUserChats"),
|
|
let array = try? JSONDecoder().decode([Int64].self, from: data) {
|
|
return array
|
|
}
|
|
return []
|
|
}
|
|
set {
|
|
if let data = try? JSONEncoder().encode(newValue) {
|
|
UserDefaults.standard.set(data, forKey: "removedUserChats")
|
|
synchronizeShared()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func isUserChatRemoved(_ peerId: Int64) -> Bool {
|
|
return keepRemovedChannels && removedUserChats.contains(peerId)
|
|
}
|
|
|
|
public func markUserChatAsRemoved(_ peerId: Int64) {
|
|
if !removedUserChats.contains(peerId) {
|
|
removedUserChats.append(peerId)
|
|
}
|
|
}
|
|
|
|
public func unmarkUserChatAsRemoved(_ peerId: Int64) {
|
|
removedUserChats.removeAll { $0 == peerId }
|
|
}
|
|
}
|
|
|
|
extension SGSimpleSettings {
|
|
public var isStealthModeEnabled: Bool {
|
|
return storyStealthMode && canUseStealthMode
|
|
}
|
|
|
|
public static func makeOutgoingLanguageTranslationKey(accountId: Int64, peerId: Int64) -> String {
|
|
return "\(accountId):\(peerId)"
|
|
}
|
|
}
|
|
|
|
extension SGSimpleSettings {
|
|
public var translationBackendEnum: SGSimpleSettings.TranslationBackend {
|
|
return TranslationBackend(rawValue: translationBackend) ?? .default
|
|
}
|
|
|
|
public var transcriptionBackendEnum: SGSimpleSettings.TranscriptionBackend {
|
|
return TranscriptionBackend(rawValue: transcriptionBackend) ?? .default
|
|
}
|
|
}
|
|
|
|
extension SGSimpleSettings {
|
|
public var isNYEnabled: Bool {
|
|
return canUseNY && NYStyle(rawValue: nyStyle) != .default
|
|
}
|
|
|
|
/// Check if a peer should be treated as premium, considering local premium setting
|
|
/// - Parameters:
|
|
/// - peerId: The peer ID to check
|
|
/// - accountPeerId: The current account's peer ID
|
|
/// - isPremium: The actual premium status from Telegram
|
|
/// - Returns: True if the peer should be treated as premium (either has real premium or has local premium enabled for current user)
|
|
public func isPremium(peerId: Int64, accountPeerId: Int64, isPremium: Bool) -> Bool {
|
|
if isPremium {
|
|
return true
|
|
}
|
|
// Local premium only applies to the current user
|
|
if self.enableLocalPremium && peerId == accountPeerId {
|
|
return true
|
|
}
|
|
// Fake profile: show premium badge for the substituted profile when enabled
|
|
if self.shouldApplyFakeProfile(peerId: peerId) && self.fakeProfilePremium {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
}
|
|
|
|
public func getSGDownloadPartSize(_ default: Int64, fileSize: Int64?) -> Int64 {
|
|
let currentDownloadSetting = SGSimpleSettings.shared.downloadSpeedBoost
|
|
// Increasing chunk size for small files make it worse in terms of overall download performance
|
|
let smallFileSizeThreshold = 1 * 1024 * 1024 // 1 MB
|
|
switch (currentDownloadSetting) {
|
|
case SGSimpleSettings.DownloadSpeedBoostValues.medium.rawValue:
|
|
if let fileSize, fileSize <= smallFileSizeThreshold {
|
|
return `default`
|
|
}
|
|
return 512 * 1024
|
|
case SGSimpleSettings.DownloadSpeedBoostValues.maximum.rawValue:
|
|
if let fileSize, fileSize <= smallFileSizeThreshold {
|
|
return `default`
|
|
}
|
|
return 1024 * 1024
|
|
default:
|
|
return `default`
|
|
}
|
|
}
|
|
|
|
public func getSGMaxPendingParts(_ default: Int) -> Int {
|
|
let currentDownloadSetting = SGSimpleSettings.shared.downloadSpeedBoost
|
|
switch (currentDownloadSetting) {
|
|
case SGSimpleSettings.DownloadSpeedBoostValues.medium.rawValue:
|
|
return 8
|
|
case SGSimpleSettings.DownloadSpeedBoostValues.maximum.rawValue:
|
|
return 12
|
|
default:
|
|
return `default`
|
|
}
|
|
}
|
|
|
|
public func sgUseShortAllChatsTitle(_ default: Bool) -> Bool {
|
|
let currentOverride = SGSimpleSettings.shared.allChatsTitleLengthOverride
|
|
switch (currentOverride) {
|
|
case SGSimpleSettings.AllChatsTitleLengthOverride.short.rawValue:
|
|
return true
|
|
case SGSimpleSettings.AllChatsTitleLengthOverride.long.rawValue:
|
|
return false
|
|
default:
|
|
return `default`
|
|
}
|
|
}
|
|
|
|
public extension Notification.Name {
|
|
/// Posted when "Hide Proxy Sponsor" is toggled so the chat list can refresh.
|
|
static let sgHideProxySponsorDidChange = Notification.Name("SGHideProxySponsorDidChange")
|
|
/// Posted when a badge image finishes downloading so the UI can re-render.
|
|
static let sgBadgeImageDidCache = Notification.Name("SGBadgeImageDidCache")
|
|
}
|