Update Ghostgram features

This commit is contained in:
ichmagmaus 812
2026-03-07 18:15:32 +01:00
parent 1a3303b059
commit 24a7ec39d9
902 changed files with 148302 additions and 62355 deletions
+19
View File
@@ -0,0 +1,19 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "SGSimpleSettings",
module_name = "SGSimpleSettings",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//Swiftgram/SGAppGroupIdentifier:SGAppGroupIdentifier",
"//Swiftgram/SGLogging:SGLogging",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,58 @@
//// A copy of Atomic from SwiftSignalKit
//import Foundation
//
//public enum AtomicWrapperLockError: Error {
// case isLocked
//}
//
//public final class AtomicWrapper<T> {
// private var lock: pthread_mutex_t
// private var value: T
//
// public init(value: T) {
// self.lock = pthread_mutex_t()
// self.value = value
//
// pthread_mutex_init(&self.lock, nil)
// }
//
// deinit {
// pthread_mutex_destroy(&self.lock)
// }
//
// public func with<R>(_ f: (T) -> R) -> R {
// pthread_mutex_lock(&self.lock)
// let result = f(self.value)
// pthread_mutex_unlock(&self.lock)
//
// return result
// }
//
// public func tryWith<R>(_ f: (T) -> R) throws -> R {
// if pthread_mutex_trylock(&self.lock) == 0 {
// let result = f(self.value)
// pthread_mutex_unlock(&self.lock)
// return result
// } else {
// throw AtomicWrapperLockError.isLocked
// }
// }
//
// public func modify(_ f: (T) -> T) -> T {
// pthread_mutex_lock(&self.lock)
// let result = f(self.value)
// self.value = result
// pthread_mutex_unlock(&self.lock)
//
// return result
// }
//
// public func swap(_ value: T) -> T {
// pthread_mutex_lock(&self.lock)
// let previous = self.value
// self.value = value
// pthread_mutex_unlock(&self.lock)
//
// return previous
// }
//}
@@ -0,0 +1,36 @@
//
// RWLock.swift
// SwiftConcurrentCollections
//
// Created by Pete Prokop on 09/02/2020.
// Copyright © 2020 Pete Prokop. All rights reserved.
//
import Foundation
public final class RWLock {
private var lock: pthread_rwlock_t
// MARK: Lifecycle
deinit {
pthread_rwlock_destroy(&lock)
}
public init() {
lock = pthread_rwlock_t()
pthread_rwlock_init(&lock, nil)
}
// MARK: Public
public func writeLock() {
pthread_rwlock_wrlock(&lock)
}
public func readLock() {
pthread_rwlock_rdlock(&lock)
}
public func unlock() {
pthread_rwlock_unlock(&lock)
}
}
@@ -0,0 +1,665 @@
import Foundation
import SGAppGroupIdentifier
import SGLogging
let APP_GROUP_IDENTIFIER = sgAppGroupIdentifier()
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)'.")
}
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 hideReactions
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 chatListLines
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
}
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.hideReactions.rawValue: false,
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.chatListLines.rawValue: ChatListLines.defaultValue.rawValue,
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,
Keys.customAppBadge.rawValue: "",
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
]
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,
]
@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.hideStories.rawValue)
public var hideStories: Bool
@UserDefault(key: Keys.warnOnStoriesOpen.rawValue)
public var warnOnStoriesOpen: Bool
@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)
@UserDefault(key: Keys.hideReactions.rawValue)
public var hideReactions: Bool
// @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.showProfileId.rawValue)
public var showProfileId: 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.chatListLines.rawValue)
public var chatListLines: String
@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.sendWithReturnKey.rawValue)
public var sendWithReturnKey: 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
@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
}
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
}
}
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`
}
}
@@ -0,0 +1,408 @@
import Foundation
public protocol AllowedUserDefaultTypes {}
/* // This one is more painful than helpful
extension Bool: AllowedUserDefaultTypes {}
extension String: AllowedUserDefaultTypes {}
extension Int: AllowedUserDefaultTypes {}
extension Int32: AllowedUserDefaultTypes {}
extension Double: AllowedUserDefaultTypes {}
extension Float: AllowedUserDefaultTypes {}
extension Data: AllowedUserDefaultTypes {}
extension URL: AllowedUserDefaultTypes {}
//extension Dictionary<String, Any>: AllowedUserDefaultTypes {}
extension Array: AllowedUserDefaultTypes where Element: AllowedUserDefaultTypes {}
*/
// Does not support Optional types due to caching
@propertyWrapper
public class UserDefault<T> /*where T: AllowedUserDefaultTypes*/ {
public let key: String
public let userDefaults: UserDefaults
private var cachedValue: T?
public init(key: String, userDefaults: UserDefaults = .standard) {
self.key = key
self.userDefaults = userDefaults
}
public var wrappedValue: T {
get {
#if DEBUG && false
SGtrace("UD.\(key)", what: "GET")
#endif
if let strongCachedValue = cachedValue {
#if DEBUG && false
SGtrace("UD", what: "CACHED \(key) \(strongCachedValue)")
#endif
return strongCachedValue
}
cachedValue = readFromUserDefaults()
#if DEBUG
SGtrace("UD.\(key)", what: "EXTRACTED: \(cachedValue!)")
#endif
return cachedValue!
}
set {
cachedValue = newValue
#if DEBUG
SGtrace("UD.\(key)", what: "CACHE UPDATED \(cachedValue!)")
#endif
userDefaults.set(newValue, forKey: key)
}
}
fileprivate func readFromUserDefaults() -> T {
switch T.self {
case is Bool.Type:
return (userDefaults.bool(forKey: key) as! T)
case is String.Type:
return (userDefaults.string(forKey: key) as! T)
case is Int64.Type:
return (Int64(exactly: userDefaults.integer(forKey: key)) as! T)
case is Int32.Type:
return (Int32(exactly: userDefaults.integer(forKey: key)) as! T)
case is Int.Type:
return (userDefaults.integer(forKey: key) as! T)
case is Double.Type:
return (userDefaults.double(forKey: key) as! T)
case is Float.Type:
return (userDefaults.float(forKey: key) as! T)
case is Data.Type:
return (userDefaults.data(forKey: key) as! T)
case is URL.Type:
return (userDefaults.url(forKey: key) as! T)
case is Array<String>.Type:
return (userDefaults.stringArray(forKey: key) as! T)
case is Array<Any>.Type:
return (userDefaults.array(forKey: key) as! T)
default:
fatalError("Unsupported UserDefault type \(T.self)")
// cachedValue = (userDefaults.object(forKey: key) as! T)
}
}
}
//public class AtomicUserDefault<T>: UserDefault<T> {
// private let atomicCachedValue: AtomicWrapper<T?> = AtomicWrapper(value: nil)
//
// public override var wrappedValue: T {
// get {
// return atomicCachedValue.modify({ value in
// if let strongValue = value {
// return strongValue
// }
// return self.readFromUserDefaults()
// })!
// }
// set {
// let _ = atomicCachedValue.modify({ _ in
// userDefaults.set(newValue, forKey: key)
// return newValue
// })
// }
// }
//}
// Based on ConcurrentDictionary.swift from https://github.com/peterprokop/SwiftConcurrentCollections
/// Thread-safe UserDefaults dictionary wrapper
/// - Important: Note that this is a `class`, i.e. reference (not value) type
/// - Important: Key can only be String type
public class UserDefaultsBackedDictionary<Key: Hashable, Value> {
public let userDefaultsKey: String
public let userDefaults: UserDefaults
private var container: [Key: Value]? = nil
private let rwlock = RWLock()
private let threadSafe: Bool
public var keys: [Key] {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "KEYS")
#endif
let result: [Key]
if threadSafe {
rwlock.readLock()
}
if container == nil {
container = userDefaultsContainer
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "EXTRACTED: \(container!)")
#endif
} else {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "FROM CACHE: \(container!)")
#endif
}
result = Array(container!.keys)
if threadSafe {
rwlock.unlock()
}
return result
}
public var values: [Value] {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "VALUES")
#endif
let result: [Value]
if threadSafe {
rwlock.readLock()
}
if container == nil {
container = userDefaultsContainer
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "EXTRACTED: \(container!)")
#endif
} else {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "FROM CACHE: \(container!)")
#endif
}
result = Array(container!.values)
if threadSafe {
rwlock.unlock()
}
return result
}
public init(userDefaultsKey: String, userDefaults: UserDefaults = .standard, threadSafe: Bool) {
self.userDefaultsKey = userDefaultsKey
self.userDefaults = userDefaults
self.threadSafe = threadSafe
}
/// Sets the value for key
///
/// - Parameters:
/// - value: The value to set for key
/// - key: The key to set value for
public func set(value: Value, forKey key: Key) {
if threadSafe {
rwlock.writeLock()
}
_set(value: value, forKey: key)
if threadSafe {
rwlock.unlock()
}
}
@discardableResult
public func remove(_ key: Key) -> Value? {
let result: Value?
if threadSafe {
rwlock.writeLock()
}
result = _remove(key)
if threadSafe {
rwlock.unlock()
}
return result
}
@discardableResult
public func removeValue(forKey: Key) -> Value? {
return self.remove(forKey)
}
public func contains(_ key: Key) -> Bool {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "CONTAINS")
#endif
let result: Bool
if threadSafe {
rwlock.readLock()
}
if container == nil {
container = userDefaultsContainer
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "EXTRACTED: \(container!)")
#endif
} else {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "FROM CACHE: \(container!)")
#endif
}
result = container!.index(forKey: key) != nil
if threadSafe {
rwlock.unlock()
}
return result
}
public func value(forKey key: Key) -> Value? {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "VALUE")
#endif
let result: Value?
if threadSafe {
rwlock.readLock()
}
if container == nil {
container = userDefaultsContainer
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "EXTRACTED: \(container!)")
#endif
} else {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "FROM CACHE: \(container!)")
#endif
}
result = container![key]
if threadSafe {
rwlock.unlock()
}
return result
}
public func mutateValue(forKey key: Key, mutation: (Value) -> Value) {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "MUTATE")
#endif
if threadSafe {
rwlock.writeLock()
}
if container == nil {
container = userDefaultsContainer
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "EXTRACTED: \(container!)")
#endif
} else {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "FROM CACHE: \(container!)")
#endif
}
if let value = container![key] {
container![key] = mutation(value)
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "UPDATING CACHE \(key): \(value), \(container!)")
#endif
userDefaultsContainer = container!
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "CACHE UPDATED \(key): \(value), \(container!)")
#endif
}
if threadSafe {
rwlock.unlock()
}
}
public var isEmpty: Bool {
return self.keys.isEmpty
}
// MARK: Subscript
public subscript(key: Key) -> Value? {
get {
return value(forKey: key)
}
set {
if threadSafe {
rwlock.writeLock()
}
defer {
if threadSafe {
rwlock.unlock()
}
}
guard let newValue = newValue else {
_remove(key)
return
}
_set(value: newValue, forKey: key)
}
}
// MARK: Private
@inline(__always)
private func _set(value: Value, forKey key: Key) {
if container == nil {
container = userDefaultsContainer
}
self.container![key] = value
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "UPDATING CACHE \(key): \(value), \(container!)")
#endif
userDefaultsContainer = container!
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "CACHE UPDATED \(key): \(value), \(container!)")
#endif
}
@inline(__always)
@discardableResult
private func _remove(_ key: Key) -> Value? {
if container == nil {
container = userDefaultsContainer
}
guard let index = container!.index(forKey: key) else { return nil }
let tuple = container!.remove(at: index)
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "UPDATING CACHE REMOVE \(key) \(container!)")
#endif
userDefaultsContainer = container!
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "CACHE UPDATED REMOVED \(key) \(container!)")
#endif
return tuple.value
}
private var userDefaultsContainer: [Key: Value] {
get {
return userDefaults.dictionary(forKey: userDefaultsKey) as! [Key: Value]
}
set {
userDefaults.set(newValue, forKey: userDefaultsKey)
}
}
public func drop() {
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "DROPPING")
#endif
if threadSafe {
rwlock.writeLock()
}
userDefaults.removeObject(forKey: userDefaultsKey)
container = userDefaultsContainer
#if DEBUG
SGtrace("UD.\(userDefaultsKey)\(threadSafe ? "-ts" : "")", what: "DROPPED: \(container!)")
#endif
if threadSafe {
rwlock.unlock()
}
}
}
#if DEBUG
private let queue = DispatchQueue(label: "app.swiftgram.ios.trace", qos: .utility)
public func SGtrace(_ domain: String, what: @autoclosure() -> String) {
let string = what()
var rawTime = time_t()
time(&rawTime)
var timeinfo = tm()
localtime_r(&rawTime, &timeinfo)
var curTime = timeval()
gettimeofday(&curTime, nil)
let seconds = Int(curTime.tv_sec % 60) // Extracting the current second
let microseconds = curTime.tv_usec // Full microsecond precision
queue.async {
let result = String(format: "[%@] %d-%d-%d %02d:%02d:%02d.%06d %@", arguments: [domain, Int(timeinfo.tm_year) + 1900, Int(timeinfo.tm_mon + 1), Int(timeinfo.tm_mday), Int(timeinfo.tm_hour), Int(timeinfo.tm_min), seconds, microseconds, string])
print(result)
}
}
#endif