You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionUtilitiesKit/General/Feature.swift

257 lines
9.3 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
//
// stringlint:disable
import Foundation
public final class Features {
public static let legacyGroupDepricationDate: Date = Date.distantFuture // TODO: [GROUPS REBUILD] Set this date
public static let legacyGroupDepricationUrl: String = "https://getsession.org/groups"
}
public extension FeatureStorage {
static let animationsEnabled: FeatureConfig<Bool> = Dependencies.create(
identifier: "animationsEnabled",
defaultOption: true
)
static let showStringKeys: FeatureConfig<Bool> = Dependencies.create(
identifier: "showStringKeys"
)
static let forceOffline: FeatureConfig<Bool> = Dependencies.create(
identifier: "forceOffline"
)
static let debugDisappearingMessageDurations: FeatureConfig<Bool> = Dependencies.create(
identifier: "debugDisappearingMessageDurations"
)
static let updatedDisappearingMessages: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedDisappearingMessages",
automaticChangeBehaviour: Feature<Bool>.ChangeBehaviour(
value: true,
condition: .after(timestamp: 1710284400)
)
)
static let updatedGroups: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroups",
defaultOption: true,
automaticChangeBehaviour: Feature<Bool>.ChangeBehaviour(
value: true,
condition: .after(timestamp: Features.legacyGroupDepricationDate.timeIntervalSince1970)
)
)
static let legacyGroupsDeprecated: FeatureConfig<Bool> = Dependencies.create(
identifier: "legacyGroupsDeprecated",
automaticChangeBehaviour: Feature<Bool>.ChangeBehaviour(
value: true,
condition: .after(timestamp: Features.legacyGroupDepricationDate.timeIntervalSince1970)
)
)
static let updatedGroupsDisableAutoApprove: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsDisableAutoApprove"
)
static let updatedGroupsRemoveMessagesOnKick: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsRemoveMessagesOnKick"
)
static let updatedGroupsAllowHistoricAccessOnInvite: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsAllowHistoricAccessOnInvite"
)
static let updatedGroupsAllowDisplayPicture: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsAllowDisplayPicture"
)
static let updatedGroupsAllowDescriptionEditing: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsAllowDescriptionEditing"
)
static let updatedGroupsAllowPromotions: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsAllowPromotions"
)
static let updatedGroupsAllowInviteById: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsAllowInviteById"
)
static let updatedGroupsDeleteBeforeNow: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsDeleteBeforeNow"
)
static let updatedGroupsDeleteAttachmentsBeforeNow: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedGroupsDeleteAttachmentsBeforeNow"
)
}
// MARK: - FeatureOption
public protocol FeatureOption: RawRepresentable, CaseIterable, Equatable where RawValue == Int {
static var defaultOption: Self { get }
var isValidOption: Bool { get }
var title: String { get }
var subtitle: String? { get }
}
public extension FeatureOption {
var isValidOption: Bool { true }
}
// MARK: - FeatureType
public protocol FeatureType {}
// MARK: - Feature<T>
public struct Feature<T: FeatureOption>: FeatureType {
public struct ChangeBehaviour {
let value: T
let condition: ChangeCondition
}
public indirect enum ChangeCondition {
case after(timestamp: TimeInterval)
case afterFork(hard: Int, soft: Int)
case either(ChangeCondition, ChangeCondition)
case both(ChangeCondition, ChangeCondition)
static func after(date: Date) -> ChangeCondition { return .after(timestamp: date.timeIntervalSince1970) }
}
private let identifier: String
public let options: [T]
public let defaultOption: T
public let automaticChangeBehaviour: ChangeBehaviour?
// MARK: - Initialization
public init(
identifier: String,
options: [T],
defaultOption: T,
automaticChangeBehaviour: ChangeBehaviour? = nil
) {
guard T.self == Bool.self || !options.appending(defaultOption).contains(where: { $0.rawValue == 0 }) else {
preconditionFailure("A rawValue of '0' is a protected value (it indicates unset)")
}
self.identifier = identifier
self.options = options
self.defaultOption = defaultOption
self.automaticChangeBehaviour = automaticChangeBehaviour
}
// MARK: - Functions
internal func currentValue(using dependencies: Dependencies) -> T {
let maybeSelectedOption: T? = {
// `Int` defaults to `0` and `Bool` defaults to `false` so rather than those (in case we want
// a default value that isn't `0` or `false` which might be considered valid cases) we check
// if an entry exists and return `nil` if not before retrieving an `Int` representation of
// the value and converting to the desired type
guard dependencies[defaults: .appGroup].object(forKey: identifier) != nil else { return nil }
let selectedOption: Int = dependencies[defaults: .appGroup].integer(forKey: identifier)
return T(rawValue: selectedOption)
}()
/// If we have an explicitly set `selectedOption` then we should use that, otherwise we should check if any of the
/// `automaticChangeBehaviour` conditions have been met, and if so use the specified value
guard let selectedOption: T = maybeSelectedOption, selectedOption.isValidOption else {
func automaticChangeConditionMet(_ condition: ChangeCondition) -> Bool {
switch condition {
case .after(let timestamp): return (dependencies.dateNow.timeIntervalSince1970 >= timestamp)
case .afterFork(let hard, let soft):
let currentHardFork: Int = dependencies[defaults: .standard, key: .hardfork]
let currentSoftFork: Int = dependencies[defaults: .standard, key: .softfork]
let currentVersion: Version = Version(major: currentHardFork, minor: currentSoftFork, patch: 0)
let requiredVersion: Version = Version(major: hard, minor: soft, patch: 0)
return (requiredVersion >= currentVersion)
case .either(let firstCondition, let secondCondition):
return (
automaticChangeConditionMet(firstCondition) ||
automaticChangeConditionMet(secondCondition)
)
case .both(let firstCondition, let secondCondition):
return (
automaticChangeConditionMet(firstCondition) &&
automaticChangeConditionMet(secondCondition)
)
}
}
/// If the change conditions have been met then use the automatic value, otherwise use the default value
guard
let automaticChangeBehaviour: ChangeBehaviour = automaticChangeBehaviour,
automaticChangeConditionMet(automaticChangeBehaviour.condition)
else { return defaultOption }
return automaticChangeBehaviour.value
}
/// We had an explicitly selected option so return that
return selectedOption
}
internal func setValue(to updatedValue: T?, using dependencies: Dependencies) {
dependencies[defaults: .appGroup].set(updatedValue?.rawValue, forKey: identifier)
}
}
// MARK: - Convenience
public struct FeatureValue<R> {
private let valueGenerator: (Dependencies) -> R
public init<F: FeatureOption>(feature: FeatureConfig<F>, _ valueGenerator: @escaping (F) -> R) {
self.valueGenerator = { [feature] dependencies in
valueGenerator(dependencies[feature: feature])
}
}
// MARK: - Functions
Merge remote-tracking branch 'origin/feature/swift-package-manager' into feature/groups-rebuild # Conflicts: # Podfile # Podfile.lock # Session.xcodeproj/project.pbxproj # Session/Calls/Call Management/SessionCall.swift # Session/Calls/Call Management/SessionCallManager.swift # Session/Calls/CallVC.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/ConversationVC.swift # Session/Conversations/ConversationViewModel.swift # Session/Conversations/Message Cells/Content Views/MediaAlbumView.swift # Session/Conversations/Settings/ThreadSettingsViewModel.swift # Session/Emoji/Emoji+Available.swift # Session/Home/GlobalSearch/GlobalSearchViewController.swift # Session/Home/HomeVC.swift # Session/Home/HomeViewModel.swift # Session/Home/New Conversation/NewDMVC.swift # Session/Media Viewing & Editing/DocumentTitleViewController.swift # Session/Media Viewing & Editing/GIFs/GifPickerCell.swift # Session/Media Viewing & Editing/GIFs/GifPickerViewController.swift # Session/Media Viewing & Editing/ImagePickerController.swift # Session/Media Viewing & Editing/MediaTileViewController.swift # Session/Media Viewing & Editing/PhotoCapture.swift # Session/Media Viewing & Editing/PhotoCaptureViewController.swift # Session/Media Viewing & Editing/PhotoLibrary.swift # Session/Media Viewing & Editing/SendMediaNavigationController.swift # Session/Meta/AppDelegate.swift # Session/Meta/AppEnvironment.swift # Session/Meta/MainAppContext.swift # Session/Meta/SessionApp.swift # Session/Notifications/NotificationPresenter.swift # Session/Notifications/PushRegistrationManager.swift # Session/Notifications/SyncPushTokensJob.swift # Session/Notifications/UserNotificationsAdaptee.swift # Session/Onboarding/LandingVC.swift # Session/Onboarding/LinkDeviceVC.swift # Session/Onboarding/Onboarding.swift # Session/Onboarding/RegisterVC.swift # Session/Onboarding/RestoreVC.swift # Session/Settings/HelpViewModel.swift # Session/Settings/NukeDataModal.swift # Session/Shared/FullConversationCell.swift # Session/Shared/OWSBezierPathView.m # Session/Utilities/BackgroundPoller.swift # Session/Utilities/MockDataGenerator.swift # SessionMessagingKit/Configuration.swift # SessionMessagingKit/Crypto/Crypto+SessionMessagingKit.swift # SessionMessagingKit/Database/Migrations/_004_RemoveLegacyYDB.swift # SessionMessagingKit/Database/Migrations/_014_GenerateInitialUserConfigDumps.swift # SessionMessagingKit/Database/Migrations/_015_BlockCommunityMessageRequests.swift # SessionMessagingKit/Database/Migrations/_018_DisappearingMessagesConfiguration.swift # SessionMessagingKit/Database/Models/Attachment.swift # SessionMessagingKit/Database/Models/DisappearingMessageConfiguration.swift # SessionMessagingKit/Database/Models/Interaction.swift # SessionMessagingKit/Database/Models/Profile.swift # SessionMessagingKit/Database/Models/SessionThread.swift # SessionMessagingKit/File Server/FileServerAPI.swift # SessionMessagingKit/Jobs/AttachmentDownloadJob.swift # SessionMessagingKit/Jobs/CheckForAppUpdatesJob.swift # SessionMessagingKit/Jobs/DisappearingMessagesJob.swift # SessionMessagingKit/Jobs/FailedMessageSendsJob.swift # SessionMessagingKit/Jobs/MessageSendJob.swift # SessionMessagingKit/Jobs/Types/GroupLeavingJob.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+Contacts.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+ConvoInfoVolatile.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+Shared.swift # SessionMessagingKit/LibSession/Config Handling/LibSession+UserGroups.swift # SessionMessagingKit/LibSession/LibSession+SessionMessagingKit.swift # SessionMessagingKit/Messages/Message.swift # SessionMessagingKit/Open Groups/Crypto/Crypto+OpenGroupAPI.swift # SessionMessagingKit/Open Groups/Models/SOGSMessage.swift # SessionMessagingKit/Open Groups/OpenGroupAPI.swift # SessionMessagingKit/Open Groups/OpenGroupManager.swift # SessionMessagingKit/Sending & Receiving/Attachments/SignalAttachment.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+ExpirationTimers.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+LegacyClosedGroups.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageReceiver+VisibleMessages.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+LegacyClosedGroups.swift # SessionMessagingKit/Sending & Receiving/MessageReceiver.swift # SessionMessagingKit/Sending & Receiving/MessageSender+Convenience.swift # SessionMessagingKit/Sending & Receiving/MessageSender.swift # SessionMessagingKit/Sending & Receiving/Notifications/Models/SubscribeRequest.swift # SessionMessagingKit/Sending & Receiving/Notifications/Models/UnsubscribeRequest.swift # SessionMessagingKit/Sending & Receiving/Notifications/PushNotificationAPI.swift # SessionMessagingKit/Sending & Receiving/Pollers/CurrentUserPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/GroupPoller.swift # SessionMessagingKit/Sending & Receiving/Pollers/OpenGroupAPI+Poller.swift # SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift # SessionMessagingKit/Utilities/ProfileManager.swift # SessionMessagingKitTests/Jobs/MessageSendJobSpec.swift # SessionMessagingKitTests/LibSession/LibSessionSpec.swift # SessionMessagingKitTests/LibSession/LibSessionUtilSpec.swift # SessionMessagingKitTests/Open Groups/Models/SOGSMessageSpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupAPISpec.swift # SessionMessagingKitTests/Open Groups/OpenGroupManagerSpec.swift # SessionMessagingKitTests/Utilities/CryptoSMKSpec.swift # SessionMessagingKitTests/_TestUtilities/MockOGMCache.swift # SessionNotificationServiceExtension/NSENotificationPresenter.swift # SessionNotificationServiceExtension/NotificationServiceExtension.swift # SessionShareExtension/ShareAppExtensionContext.swift # SessionShareExtension/ShareNavController.swift # SessionShareExtension/ThreadPickerVC.swift # SessionSnodeKit/Crypto/Crypto+SessionSnodeKit.swift # SessionSnodeKit/Models/DeleteAllBeforeResponse.swift # SessionSnodeKit/Models/DeleteAllMessagesResponse.swift # SessionSnodeKit/Models/DeleteMessagesResponse.swift # SessionSnodeKit/Models/RevokeSubkeyRequest.swift # SessionSnodeKit/Models/RevokeSubkeyResponse.swift # SessionSnodeKit/Models/SendMessageResponse.swift # SessionSnodeKit/Models/SnodeAuthenticatedRequestBody.swift # SessionSnodeKit/Models/UpdateExpiryAllResponse.swift # SessionSnodeKit/Models/UpdateExpiryResponse.swift # SessionSnodeKit/Networking/SnodeAPI.swift # SessionTests/Conversations/Settings/ThreadDisappearingMessagesViewModelSpec.swift # SessionTests/Conversations/Settings/ThreadSettingsViewModelSpec.swift # SessionTests/Database/DatabaseSpec.swift # SessionTests/Settings/NotificationContentViewModelSpec.swift # SessionUIKit/Components/ToastController.swift # SessionUIKit/Style Guide/Values.swift # SessionUtilitiesKit/Crypto/Crypto+SessionUtilitiesKit.swift # SessionUtilitiesKit/Crypto/Crypto.swift # SessionUtilitiesKit/Database/Models/Identity.swift # SessionUtilitiesKit/Database/Models/Job.swift # SessionUtilitiesKit/Database/Storage.swift # SessionUtilitiesKit/Database/Types/Migration.swift # SessionUtilitiesKit/General/AppContext.swift # SessionUtilitiesKit/General/Data+Utilities.swift # SessionUtilitiesKit/General/Logging.swift # SessionUtilitiesKit/General/SNUserDefaults.swift # SessionUtilitiesKit/General/String+Trimming.swift # SessionUtilitiesKit/General/String+Utilities.swift # SessionUtilitiesKit/General/TimestampUtils.swift # SessionUtilitiesKit/General/UIEdgeInsets.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift # SessionUtilitiesKit/LibSession/LibSessionError.swift # SessionUtilitiesKit/Media/DataSource.swift # SessionUtilitiesKit/Meta/SessionUtilitiesKit.h # SessionUtilitiesKit/Networking/NetworkType.swift # SessionUtilitiesKit/Networking/ProxiedContentDownloader.swift # SessionUtilitiesKit/Utilities/BackgroundTaskManager.swift # SessionUtilitiesKit/Utilities/BencodeResponse.swift # SessionUtilitiesKit/Utilities/CExceptionHelper.mm # SessionUtilitiesKit/Utilities/FileManagerType.swift # SessionUtilitiesKit/Utilities/KeychainStorageType.swift # SessionUtilitiesKitTests/Database/Models/IdentitySpec.swift # SessionUtilitiesKitTests/Database/Utilities/PersistableRecordUtilitiesSpec.swift # SessionUtilitiesKitTests/General/SessionIdSpec.swift # SessionUtilitiesKitTests/JobRunner/JobRunnerSpec.swift # SignalUtilitiesKit/Configuration.swift # SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalInputAccessoryView.swift # SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentApprovalViewController.swift # SignalUtilitiesKit/Media Viewing & Editing/Attachment Approval/AttachmentTextToolbar.swift # SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorCropViewController.swift # SignalUtilitiesKit/Media Viewing & Editing/Image Editing/ImageEditorModel.swift # SignalUtilitiesKit/Media Viewing & Editing/MediaMessageView.swift # SignalUtilitiesKit/Meta/SignalUtilitiesKit.h # SignalUtilitiesKit/Shared View Controllers/OWSViewController.swift # SignalUtilitiesKit/Shared Views/CircleView.swift # SignalUtilitiesKit/Shared Views/TappableView.swift # SignalUtilitiesKit/Utilities/AppSetup.swift # SignalUtilitiesKit/Utilities/Bench.swift # SignalUtilitiesKit/Utilities/NoopNotificationsManager.swift # _SharedTestUtilities/CommonMockedExtensions.swift # _SharedTestUtilities/MockCrypto.swift # _SharedTestUtilities/Mocked.swift # _SharedTestUtilities/SynchronousStorage.swift
10 months ago
public func value(using dependencies: Dependencies) -> R {
return valueGenerator(dependencies)
}
}
// MARK: - Bool FeatureOption
extension Bool: @retroactive CaseIterable {}
extension Bool: @retroactive RawRepresentable {}
extension Bool: FeatureOption {
public static let allCases: [Bool] = [false, true]
// MARK: - Initialization
public var rawValue: Int { return (self ? 1 : 0) }
public init?(rawValue: Int) {
self = (rawValue != 0)
}
// MARK: - Feature Option
public static var defaultOption: Bool = false
public var title: String {
return (self ? "Enabled" : "Disabled")
}
public var subtitle: String? {
return (self ? "Enabled" : "Disabled")
}
}