diff --git a/App/Previews/SettingsPreview/SettingsPreviewApp.swift b/App/Previews/SettingsPreview/SettingsPreviewApp.swift index dda79e7d..726fb60f 100644 --- a/App/Previews/SettingsPreview/SettingsPreviewApp.swift +++ b/App/Previews/SettingsPreview/SettingsPreviewApp.swift @@ -3,11 +3,10 @@ import AudioPlayerClient import ComposableStoreKit import ComposableUserNotifications import RemoteNotificationsClient -import ServerConfigClient +import ServerConfigPersistenceKey import SettingsFeature import Styleguide import SwiftUI -import UserDefaultsClient @main struct SettingsPreviewApp: App { diff --git a/App/iOS/App.swift b/App/iOS/App.swift index f51ae93e..4d30ad1f 100644 --- a/App/iOS/App.swift +++ b/App/iOS/App.swift @@ -7,7 +7,7 @@ import Build import ComposableArchitecture import DictionarySqliteClient import ServerConfig -import ServerConfigClient +import ServerConfigPersistenceKey import Styleguide import SwiftUI import UIApplicationClient @@ -25,7 +25,6 @@ final class AppDelegate: NSObject, UIApplicationDelegate { .appendingPathComponent("co.pointfree.Isowords") .appendingPathComponent("Isowords.sqlite3") ) - //$0.serverConfig = .live(apiClient: $0.apiClient, build: $0.build) } } @@ -74,14 +73,3 @@ struct IsowordsApp: App { extension AudioPlayerClient { static let liveValue = Self.live(bundles: [AppAudioLibrary.bundle, AppClipAudioLibrary.bundle]) } - -//extension ServerConfigClient { -// static func live(apiClient: ApiClient, build: Build) -> Self { -// .live( -// fetch: { -// try await apiClient -// .apiRequest(route: .config(build: build.number()), as: ServerConfig.self) -// } -// ) -// } -//} diff --git a/Package.swift b/Package.swift index 0a10e7e8..a7441d35 100644 --- a/Package.swift +++ b/Package.swift @@ -219,7 +219,7 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { .library(name: "OnboardingFeature", targets: ["OnboardingFeature"]), .library(name: "RemoteNotificationsClient", targets: ["RemoteNotificationsClient"]), .library(name: "SelectionSoundsCore", targets: ["SelectionSoundsCore"]), - .library(name: "ServerConfigClient", targets: ["ServerConfigClient"]), + .library(name: "ServerConfigPersistenceKey", targets: ["ServerConfigPersistenceKey"]), .library(name: "SettingsFeature", targets: ["SettingsFeature"]), .library(name: "SharedSwiftUIEnvironment", targets: ["SharedSwiftUIEnvironment"]), .library(name: "SoloFeature", targets: ["SoloFeature"]), @@ -231,7 +231,6 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { .library(name: "UserSettings", targets: ["UserSettings"]), .library(name: "UIApplicationClient", targets: ["UIApplicationClient"]), .library(name: "UpgradeInterstitialFeature", targets: ["UpgradeInterstitialFeature"]), - .library(name: "UserDefaultsClient", targets: ["UserDefaultsClient"]), .library(name: "VocabFeature", targets: ["VocabFeature"]), ]) package.targets.append(contentsOf: [ @@ -366,13 +365,12 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { dependencies: [ "ApiClient", "Build", - "ServerConfigClient", + "ServerConfigPersistenceKey", "SharedModels", "Styleguide", "SwiftUIHelpers", "TcaHelpers", "UIApplicationClient", - "UserDefaultsClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "Overture", package: "swift-overture"), ] @@ -519,7 +517,6 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { "FeedbackGeneratorClient", "OnboardingFeature", "SharedModels", - "UserDefaultsClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), @@ -611,7 +608,6 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { "SwiftUIHelpers", "TcaHelpers", "UpgradeInterstitialFeature", - "UserDefaultsClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), @@ -666,7 +662,7 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { "LeaderboardFeature", "LocalDatabaseClient", "MultiplayerFeature", - "ServerConfigClient", + "ServerConfigPersistenceKey", "SettingsFeature", "SharedModels", "SoloFeature", @@ -675,7 +671,6 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { "TcaHelpers", "UIApplicationClient", "UpgradeInterstitialFeature", - "UserDefaultsClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "Overture", package: "swift-overture"), ] @@ -798,7 +793,7 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { ] ), .target( - name: "ServerConfigClient", + name: "ServerConfigPersistenceKey", dependencies: [ "ServerConfig", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), @@ -815,13 +810,12 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { "ComposableUserNotifications", "LocalDatabaseClient", "RemoteNotificationsClient", - "ServerConfigClient", + "ServerConfigPersistenceKey", "StatsFeature", "Styleguide", "SwiftUIHelpers", "TcaHelpers", "UIApplicationClient", - "UserDefaultsClient", "UserSettings", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), @@ -896,7 +890,6 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { "OnboardingFeature", "SharedModels", "TcaHelpers", - "UserDefaultsClient", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), ] ), @@ -917,7 +910,7 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { name: "UpgradeInterstitialFeature", dependencies: [ "ComposableStoreKit", - "ServerConfigClient", + "ServerConfigPersistenceKey", "Styleguide", "SwiftUIHelpers", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), @@ -933,13 +926,6 @@ if ProcessInfo.processInfo.environment["TEST_SERVER"] == nil { ], exclude: ["__Snapshots__"] ), - .target( - name: "UserDefaultsClient", - dependencies: [ - .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "XCTestDynamicOverlay", package: "xctest-dynamic-overlay"), - ] - ), .target( name: "VocabFeature", dependencies: [ diff --git a/Sources/AppFeature/AppDelegate.swift b/Sources/AppFeature/AppDelegate.swift index 2a13a02b..de2d3289 100644 --- a/Sources/AppFeature/AppDelegate.swift +++ b/Sources/AppFeature/AppDelegate.swift @@ -21,7 +21,6 @@ public struct AppDelegateReducer { @Dependency(\.apiClient) var apiClient @Dependency(\.audioPlayer) var audioPlayer - //@Dependency(\.build.number) var buildNumber @Dependency(\.dictionary.load) var loadDictionary @Dependency(\.remoteNotifications.register) var registerForRemoteNotifications @Dependency(\.applicationClient.setUserInterfaceStyle) var setUserInterfaceStyle diff --git a/Sources/AppFeature/AppView.swift b/Sources/AppFeature/AppView.swift index 1af63da0..4b6de1c2 100644 --- a/Sources/AppFeature/AppView.swift +++ b/Sources/AppFeature/AppView.swift @@ -10,7 +10,7 @@ import OnboardingFeature import SharedModels import Styleguide import SwiftUI -import ServerConfigClient +import ServerConfigPersistenceKey @Reducer public struct AppReducer { @@ -28,7 +28,7 @@ public struct AppReducer { @Shared(.savedGames) var savedGames = SavedGamesState() @Shared(.hasShownFirstLaunchOnboarding) var hasShownFirstLaunchOnboarding = false @Shared(.installationTime) var installationTime = Date().timeIntervalSince1970 - @SharedReader(.serverConfigNew) var serverConfig = ServerConfig() + @SharedReader(.serverConfig) var serverConfig = ServerConfig() public init( appDelegate: AppDelegateReducer.State = AppDelegateReducer.State(), @@ -56,8 +56,6 @@ public struct AppReducer { @Dependency(\.mainRunLoop.now.date) var now @Dependency(\.dictionary.randomCubes) var randomCubes @Dependency(\.remoteNotifications) var remoteNotifications - //@Dependency(\.serverConfig.refresh) var refreshServerConfig - //@Dependency(\.userDefaults) var userDefaults @Dependency(\.userNotifications) var userNotifications public init() {} @@ -240,7 +238,7 @@ public struct AppReducer { remoteNotifications: self.remoteNotifications, userNotifications: self.userNotifications ) - async let refresh = serverConfig.persistence.reload() //.refreshServerConfig() + async let refresh = serverConfig.persistence.reload() _ = try await (register, refresh) } catch: { _, _ in } diff --git a/Sources/ChangelogFeature/ChangeView.swift b/Sources/ChangelogFeature/ChangeView.swift index 76d9bc0d..484c3fd1 100644 --- a/Sources/ChangelogFeature/ChangeView.swift +++ b/Sources/ChangelogFeature/ChangeView.swift @@ -1,6 +1,6 @@ import Build import ComposableArchitecture -import ServerConfigClient +import ServerConfigPersistenceKey import SwiftUI import Tagged diff --git a/Sources/ChangelogFeature/ChangelogView.swift b/Sources/ChangelogFeature/ChangelogView.swift index 3cd213ed..cfc352b5 100644 --- a/Sources/ChangelogFeature/ChangelogView.swift +++ b/Sources/ChangelogFeature/ChangelogView.swift @@ -1,7 +1,7 @@ import ApiClient import Build import ComposableArchitecture -import ServerConfigClient +import ServerConfigPersistenceKey import SharedModels import Styleguide import SwiftUI @@ -16,7 +16,7 @@ public struct ChangelogReducer { public var currentBuild: Build.Number public var isRequestInFlight: Bool public var isUpdateButtonVisible: Bool - @SharedReader(.serverConfigNew) var serverConfig = ServerConfig() + @SharedReader(.serverConfig) var serverConfig = ServerConfig() @Shared(.build) var build = Build() public init( @@ -48,9 +48,7 @@ public struct ChangelogReducer { } @Dependency(\.apiClient) var apiClient - //@Dependency(\.build.number) var buildNumber @Dependency(\.applicationClient.open) var openURL - //@Dependency(\.serverConfig) var serverConfig public init() {} @@ -185,8 +183,6 @@ public struct ChangelogView: View { return apiClient }() $0.applicationClient = .noop - //$0.build.number = { 98 } - //$0.serverConfig = .noop } ) .navigationStyle( diff --git a/Sources/ClientModels/AppStorage.swift b/Sources/ClientModels/AppStorage.swift index 0ce53b62..0f162a3c 100644 --- a/Sources/ClientModels/AppStorage.swift +++ b/Sources/ClientModels/AppStorage.swift @@ -1,55 +1,55 @@ import SwiftUI -extension AppStorageKey where Value == Bool { - public static let enableCubeShadow = Self(key: "enableCubeShadow", defaultValue: true) - public static let showSceneStatistics = Self(key: "showSceneStatistics", defaultValue: false) +extension _AppStorageKey where Value == Bool { + public static let enableCubeShadow = _AppStorageKey(key: "enableCubeShadow", defaultValue: true) + public static let showSceneStatistics = _AppStorageKey(key: "showSceneStatistics", defaultValue: false) } -public struct AppStorageKey { +public struct _AppStorageKey { public let key: String public let defaultValue: Value } extension AppStorage { - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Bool { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Bool { self.init(wrappedValue: key.defaultValue, key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Int { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Int { self.init(wrappedValue: key.defaultValue, key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Double { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Double { self.init(wrappedValue: key.defaultValue, key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == String { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == String { self.init(wrappedValue: key.defaultValue, key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Data { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Data { self.init(wrappedValue: key.defaultValue, key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value: RawRepresentable, Value.RawValue == Int { self.init(wrappedValue: key.defaultValue, key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value: RawRepresentable, Value.RawValue == String { self.init(wrappedValue: key.defaultValue, key.key, store: store) } } extension AppStorage where Value: ExpressibleByNilLiteral { - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Bool? { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Bool? { self.init(key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Int? { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Int? { self.init(key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Double? { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Double? { self.init(key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == String? { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == String? { self.init(key.key, store: store) } - public init(_ key: AppStorageKey, store: UserDefaults? = nil) where Value == Data? { + public init(_ key: _AppStorageKey, store: UserDefaults? = nil) where Value == Data? { self.init(key.key, store: store) } } diff --git a/Sources/ClientModels/AppStoragePersistenceKeys.swift b/Sources/ClientModels/AppStoragePersistenceKeys.swift new file mode 100644 index 00000000..c15f906a --- /dev/null +++ b/Sources/ClientModels/AppStoragePersistenceKeys.swift @@ -0,0 +1,22 @@ +import ComposableArchitecture + +extension PersistenceKey where Self == AppStorageKey { + public static var installationTime: Self { + appStorage("installationTimeKey") + } +} +extension PersistenceKey where Self == AppStorageKey { + public static var hasShownFirstLaunchOnboarding: Self { + AppStorageKey("hasShownFirstLaunchOnboardingKey") + } +} +extension PersistenceKey where Self == AppStorageKey { + public static var multiplayerOpensCount: Self { + AppStorageKey("multiplayerOpensCount") + } +} +extension PersistenceKey where Self == AppStorageKey { + public static var lastReviewRequest: Self { + AppStorageKey("last-review-request-timeinterval") + } +} diff --git a/Sources/CubeCore/CubeSceneView.swift b/Sources/CubeCore/CubeSceneView.swift index fcaa6cbd..2dbf6c6f 100644 --- a/Sources/CubeCore/CubeSceneView.swift +++ b/Sources/CubeCore/CubeSceneView.swift @@ -72,13 +72,15 @@ public class CubeSceneView: SCNView, UIGestureRecognizerDelegate { private let cameraNode = SCNNode() private var cancellables: Set = [] private let gameCubeNode = SCNNode() - @Published var isLowPowerEnabled = ProcessInfo.processInfo.isLowPowerModeEnabled private let light = SCNLight() private var motionManager: CMMotionManager? private var startingAttitude: Attitude? private let viewStore: ViewStore private var worldScale: Float = 1.0 + @Published var isLowPowerEnabled = ProcessInfo.processInfo.isLowPowerModeEnabled { + didSet { self.update() } + } var enableCubeShadow = true { didSet { self.update() } } @@ -113,13 +115,11 @@ public class CubeSceneView: SCNView, UIGestureRecognizerDelegate { gameCubeNode.scale = .init(worldScale, worldScale, worldScale) self.scene?.rootNode.addChildNode(self.gameCubeNode) - NotificationCenter.default.addObserver( - forName: .NSProcessInfoPowerStateDidChange, - object: nil, - queue: nil - ) { [weak self] _ in - self?.isLowPowerEnabled = ProcessInfo.processInfo.isLowPowerModeEnabled - } + NotificationCenter.default.publisher(for: .NSProcessInfoPowerStateDidChange) + .sink { [weak self] _ in + self?.isLowPowerEnabled = ProcessInfo.processInfo.isLowPowerModeEnabled + } + .store(in: &cancellables) self.viewStore.publisher.cubes .sink { cubes in diff --git a/Sources/DailyChallengeFeature/DailyChallengeView.swift b/Sources/DailyChallengeFeature/DailyChallengeView.swift index 87ef3a34..70706a28 100644 --- a/Sources/DailyChallengeFeature/DailyChallengeView.swift +++ b/Sources/DailyChallengeFeature/DailyChallengeView.swift @@ -120,11 +120,7 @@ public struct DailyChallengeReducer { await send( .startDailyChallengeResponse( Result { - try await startDailyChallengeAsync( - challenge, - apiClient: self.apiClient, - date: { self.now } - ) + try await startDailyChallenge(challenge) } ) ) diff --git a/Sources/DailyChallengeHelpers/DailyChallengeHelpers.swift b/Sources/DailyChallengeHelpers/DailyChallengeHelpers.swift index 38ec3aa2..a4222066 100644 --- a/Sources/DailyChallengeHelpers/DailyChallengeHelpers.swift +++ b/Sources/DailyChallengeHelpers/DailyChallengeHelpers.swift @@ -10,18 +10,17 @@ public enum DailyChallengeError: Error, Equatable { case couldNotFetch(nextStartsAt: Date) } -public func startDailyChallengeAsync( - _ challenge: FetchTodaysDailyChallengeResponse, - apiClient: ApiClient, - date: @escaping () -> Date +public func startDailyChallenge( + _ challenge: FetchTodaysDailyChallengeResponse ) async throws -> InProgressGame { + @Dependency(\.apiClient) var apiClient + @Dependency(\.date.now) var now + @Shared(.savedGames) var savedGames = SavedGamesState() + guard challenge.yourResult.rank == nil else { throw DailyChallengeError.alreadyPlayed(endsAt: challenge.dailyChallenge.endsAt) } - - @Shared(.savedGames) var savedGames = SavedGamesState() - guard challenge.dailyChallenge.gameMode == .unlimited, let game = savedGames.dailyChallengeUnlimited @@ -37,9 +36,8 @@ public func startDailyChallengeAsync( ), as: StartDailyChallengeResponse.self ), - date: date() + date: now ) - } catch { throw DailyChallengeError.couldNotFetch(nextStartsAt: challenge.dailyChallenge.endsAt) } diff --git a/Sources/DemoFeature/Demo.swift b/Sources/DemoFeature/Demo.swift index 84c6af37..4e595232 100644 --- a/Sources/DemoFeature/Demo.swift +++ b/Sources/DemoFeature/Demo.swift @@ -62,9 +62,7 @@ public struct Demo { $0.database = .noop $0.gameCenter = .noop $0.remoteNotifications = .noop - //$0.serverConfig = .noop $0.storeKit = .noop - //$0.userDefaults = .noop $0.userNotifications = .noop } } diff --git a/Sources/GameCore/GameCore.swift b/Sources/GameCore/GameCore.swift index 514a65e1..ba8353bb 100644 --- a/Sources/GameCore/GameCore.swift +++ b/Sources/GameCore/GameCore.swift @@ -165,8 +165,6 @@ public struct Game { @Dependency(\.gameCenter) var gameCenter @Dependency(\.mainQueue) var mainQueue @Dependency(\.mainRunLoop) var mainRunLoop - //@Dependency(\.serverConfig.config) var serverConfig - //@Dependency(\.userDefaults) var userDefaults public init() {} diff --git a/Sources/GameOverFeature/GameOverView.swift b/Sources/GameOverFeature/GameOverView.swift index b4d71d3c..ad85ebcb 100644 --- a/Sources/GameOverFeature/GameOverView.swift +++ b/Sources/GameOverFeature/GameOverView.swift @@ -13,7 +13,6 @@ import Styleguide import SwiftUI import SwiftUIHelpers import UpgradeInterstitialFeature -import UserDefaultsClient @Reducer public struct GameOver { @@ -131,8 +130,6 @@ public struct GameOver { @Dependency(\.dismissGame) var dismissGame @Dependency(\.mainRunLoop) var mainRunLoop @Dependency(\.storeKit.requestReview) var requestReview - //@Dependency(\.serverConfig.config) var serverConfig - //@Dependency(\.userDefaults) var userDefaults @Dependency(\.userNotifications.getNotificationSettings) var getUserNotificationSettings public init() {} @@ -185,11 +182,7 @@ public struct GameOver { await send( .startDailyChallengeResponse( Result { - try await startDailyChallengeAsync( - challenge, - apiClient: self.apiClient, - date: { self.mainRunLoop.now.date } - ) + try await startDailyChallenge(challenge) } ) ) diff --git a/Sources/HomeFeature/Home.swift b/Sources/HomeFeature/Home.swift index 856fa64c..baf6020b 100644 --- a/Sources/HomeFeature/Home.swift +++ b/Sources/HomeFeature/Home.swift @@ -8,12 +8,11 @@ import DeviceId import LeaderboardFeature import MultiplayerFeature import Overture -import ServerConfigClient +import ServerConfigPersistenceKey import SettingsFeature import SharedModels import SoloFeature import SwiftUI -import UserDefaultsClient import Build public struct ActiveMatchResponse: Equatable { @@ -44,7 +43,7 @@ public struct Home { public var weekInReview: FetchWeekInReviewResponse? @Shared(.installationTime) var installationTime = Date().timeIntervalSince1970 @Shared(.build) var build = Build() - @SharedReader(.serverConfigNew) var serverConfig = ServerConfig() + @SharedReader(.serverConfig) var serverConfig = ServerConfig() public var hasChangelog: Bool { self.serverConfig.newestBuild > self.build.number @@ -111,14 +110,11 @@ public struct Home { } @Dependency(\.apiClient) var apiClient - //@Dependency(\.build.number) var buildNumber @Dependency(\.deviceId) var deviceId @Dependency(\.gameCenter) var gameCenter @Dependency(\.mainRunLoop.now.date) var now @Dependency(\.audioPlayer.play) var playSound - //@Dependency(\.serverConfig) var serverConfig @Dependency(\.timeZone) var timeZone - //@Dependency(\.userDefaults) var userDefaults public init() {} @@ -298,7 +294,7 @@ public struct Home { ) await send(.authenticationResponse(currentPlayerEnvelope)) - @SharedReader(.serverConfigNew) var serverConfig = ServerConfig() + @SharedReader(.serverConfig) var serverConfig = ServerConfig() async let serverConfigResponse: Void = $serverConfig.persistence.reload() async let dailyChallengeResponse: Void = send( diff --git a/Sources/OnboardingFeature/OnboardingView.swift b/Sources/OnboardingFeature/OnboardingView.swift index 3b5b3e61..9df5b4f7 100644 --- a/Sources/OnboardingFeature/OnboardingView.swift +++ b/Sources/OnboardingFeature/OnboardingView.swift @@ -10,7 +10,6 @@ import SharedModels import Styleguide import SwiftUI import UIApplicationClient -import UserDefaultsClient import UserSettings @Reducer @@ -184,7 +183,6 @@ public struct Onboarding { @Dependency(\.dictionary) var dictionary @Dependency(\.feedbackGenerator) var feedbackGenerator @Dependency(\.mainQueue) var mainQueue - //@Dependency(\.userDefaults) var userDefaults public init() {} diff --git a/Sources/ServerConfigClient/ServerConfigPersistenceKey.swift b/Sources/ServerConfigClient/ServerConfigPersistenceKey.swift deleted file mode 100644 index cc2bce03..00000000 --- a/Sources/ServerConfigClient/ServerConfigPersistenceKey.swift +++ /dev/null @@ -1,91 +0,0 @@ -import ApiClient -import Build -import ComposableArchitecture -import Dependencies -import Foundation - -@_exported import ServerConfig - -@dynamicMemberLookup -public class ServerConfigClass: Equatable { - @Dependency(\.apiClient) var apiClient - @Shared(.build) var build = Build() - @Shared(.fileStorage(URL.documentsDirectory.appending(path: "server-config.json"))) - var config = ServerConfig() - - public init() {} - - public func refresh() async throws { - self.config = - try await apiClient - .apiRequest(route: .config(build: build.number), as: ServerConfig.self) - } - - public subscript(dynamicMember keyPath: KeyPath) -> Member { - self.config[keyPath: keyPath] - } - - public static func == (lhs: ServerConfigClass, rhs: ServerConfigClass) -> Bool { - lhs === rhs - } -} -extension PersistenceReaderKey where Self == InMemoryKey { - public static var serverConfig: Self { - inMemory("server-config") - } -} - -extension PersistenceReaderKey where Self == ServerConfigKey { - public static var serverConfigNew: Self { - ServerConfigKey() - } -} - -import Combine - -// @Shared(.api(route)) var response = ResponseEnvelope() -// @Shared(.leaderboards(sort:type)) - -public struct ServerConfigKey: PersistenceReaderKey, Hashable, Sendable { - @Dependency(\.apiClient) var apiClient - @Shared(.build) var build = Build() - @Shared(.fileStorage(URL.documentsDirectory.appending(path: "server-config.json"))) - var config = ServerConfig() - let (stream, continuation) = AsyncStream.makeStream() - - public init() {} - - public func reload() async { - continuation.yield() - } - - public func load(initialValue: ServerConfig?) -> ServerConfig? { - config - } - - public func subscribe( - initialValue: ServerConfig?, - didSet: @escaping (ServerConfig?) -> Void - ) -> Shared.Subscription { - let task = Task { - let config = try await apiClient - .apiRequest(route: .config(build: build.number), as: ServerConfig.self) - didSet(config) - for await _ in stream { - let config = try await apiClient - .apiRequest(route: .config(build: build.number), as: ServerConfig.self) - didSet(config) - } - } - // - return Shared.Subscription { - task.cancel() - } - } - - public static func == (lhs: ServerConfigKey, rhs: ServerConfigKey) -> Bool { - true - } - public func hash(into hasher: inout Hasher) { - } -} diff --git a/Sources/ServerConfigPersistenceKey/ServerConfigPersistenceKey.swift b/Sources/ServerConfigPersistenceKey/ServerConfigPersistenceKey.swift new file mode 100644 index 00000000..945e5c85 --- /dev/null +++ b/Sources/ServerConfigPersistenceKey/ServerConfigPersistenceKey.swift @@ -0,0 +1,60 @@ +import ApiClient +import Build +import ComposableArchitecture +import Dependencies +import Foundation +@_exported import ServerConfig + +extension PersistenceReaderKey where Self == ServerConfigKey { + public static var serverConfig: Self { + ServerConfigKey() + } +} + +public struct ServerConfigKey: PersistenceReaderKey, Hashable, Sendable { + @Dependency(\.apiClient) var apiClient + @Shared(.build) var build = Build() + @Shared(.fileStorage(.serverConfig)) var config = ServerConfig() + let (stream, continuation) = AsyncStream.makeStream() + + public init() {} + + public func reload() async { + continuation.yield() + } + + public func load(initialValue: ServerConfig?) -> ServerConfig? { + config + } + + public func subscribe( + initialValue: ServerConfig?, + didSet: @escaping (ServerConfig?) -> Void + ) -> Shared.Subscription { + let task = Task { + try await didSet( + apiClient + .apiRequest(route: .config(build: build.number), as: ServerConfig.self) + ) + for await _ in stream { + let config = + try await apiClient + .apiRequest(route: .config(build: build.number), as: ServerConfig.self) + didSet(config) + } + } + return Shared.Subscription { + task.cancel() + } + } + + public static func == (lhs: ServerConfigKey, rhs: ServerConfigKey) -> Bool { + true + } + public func hash(into hasher: inout Hasher) { + } +} + +extension URL { + fileprivate static let serverConfig = documentsDirectory.appending(path: "server-config.json") +} diff --git a/Sources/SettingsFeature/Settings.swift b/Sources/SettingsFeature/Settings.swift index b0eacf9a..9cdc8cac 100644 --- a/Sources/SettingsFeature/Settings.swift +++ b/Sources/SettingsFeature/Settings.swift @@ -9,7 +9,7 @@ import StatsFeature import StoreKit import UIApplicationClient import UserSettings -import ServerConfigClient +import ServerConfigPersistenceKey public struct DeveloperSettings: Equatable { public var currentBaseUrl: BaseUrl @@ -46,16 +46,16 @@ public struct Settings { @ObservableState public struct State: Equatable { @Presents public var alert: AlertState? + @Shared(.build) var build = Build() public var developer: DeveloperSettings public var fullGameProduct: Result? public var fullGamePurchasedAt: Date? public var isPurchasing: Bool public var isRestoring: Bool + @SharedReader(.serverConfig) var serverConfig = ServerConfig() public var stats: Stats.State public var userNotificationSettings: UserNotificationClient.Notification.Settings? @Shared(.userSettings) public var userSettings: UserSettings = UserSettings() - @Shared(.build) var build = Build() - @Shared(.serverConfig) var serverConfig = ServerConfigClass() public struct ProductError: Error, Equatable {} @@ -109,10 +109,8 @@ public struct Settings { @Dependency(\.apiClient) var apiClient @Dependency(\.applicationClient) var applicationClient @Dependency(\.audioPlayer) var audioPlayer - //@Dependency(\.build) var build @Dependency(\.mainQueue) var mainQueue @Dependency(\.remoteNotifications.register) var registerForRemoteNotifications - //@Dependency(\.serverConfig.config) var serverConfig @Dependency(\.storeKit) var storeKit @Dependency(\.userNotifications) var userNotifications diff --git a/Sources/SettingsFeature/SettingsView.swift b/Sources/SettingsFeature/SettingsView.swift index 77458782..dd96b67b 100644 --- a/Sources/SettingsFeature/SettingsView.swift +++ b/Sources/SettingsFeature/SettingsView.swift @@ -1,7 +1,7 @@ import Build import ComposableArchitecture import ComposableStoreKit -import ServerConfigClient +import ServerConfigPersistenceKey import StatsFeature import Styleguide import SwiftUI diff --git a/Sources/TrailerFeature/Trailer.swift b/Sources/TrailerFeature/Trailer.swift index df5e2ff2..d75b02d5 100644 --- a/Sources/TrailerFeature/Trailer.swift +++ b/Sources/TrailerFeature/Trailer.swift @@ -69,7 +69,6 @@ public struct Trailer { $0.remoteNotifications = .noop $0.serverConfig = .noop $0.storeKit = .noop - //$0.userDefaults = .noop $0.userNotifications = .noop } } diff --git a/Sources/UpgradeInterstitialFeature/UpgradeInterstitialView.swift b/Sources/UpgradeInterstitialFeature/UpgradeInterstitialView.swift index 8b861b2d..41c9f19c 100644 --- a/Sources/UpgradeInterstitialFeature/UpgradeInterstitialView.swift +++ b/Sources/UpgradeInterstitialFeature/UpgradeInterstitialView.swift @@ -1,6 +1,6 @@ import ComposableArchitecture import ComposableStoreKit -import ServerConfigClient +import ServerConfigPersistenceKey import StoreKit import Styleguide import SwiftUI @@ -21,8 +21,8 @@ public struct UpgradeInterstitial { public var isDismissable: Bool public var isPurchasing: Bool public var secondsPassedCount: Int + @SharedReader(.serverConfig) var serverConfig = ServerConfig() public var upgradeInterstitialDuration: Int - @Shared(.serverConfig) var serverConfig = ServerConfigClass() public init( fullGameProduct: StoreKitClient.Product? = nil, @@ -56,7 +56,6 @@ public struct UpgradeInterstitial { @Dependency(\.dismiss) var dismiss @Dependency(\.mainRunLoop) var mainRunLoop - //@Dependency(\.serverConfig.config) var serverConfig @Dependency(\.storeKit) var storeKit public init() {} @@ -300,14 +299,14 @@ public func shouldShowInterstitial( gamePlayedCount: Int, gameContext: GameContext ) -> Bool { - @Shared(.serverConfig) var serverConfig = ServerConfigClass() + @SharedReader(.serverConfig) var serverConfig = ServerConfig() let triggerCount = serverConfig.triggerCount(gameContext: gameContext) let triggerEvery = serverConfig.triggerEvery(gameContext: gameContext) return gamePlayedCount >= triggerCount && (gamePlayedCount - triggerCount) % triggerEvery == 0 } -extension ServerConfigClass { +extension ServerConfig { fileprivate func triggerCount(gameContext: GameContext) -> Int { switch gameContext { case .dailyChallenge: diff --git a/Sources/UserDefaultsClient/Interface.swift b/Sources/UserDefaultsClient/Interface.swift deleted file mode 100644 index 0a12bee2..00000000 --- a/Sources/UserDefaultsClient/Interface.swift +++ /dev/null @@ -1,72 +0,0 @@ -//import Dependencies -//import DependenciesMacros -//import Foundation -// -//extension DependencyValues { -// public var userDefaults: UserDefaultsClient { -// get { self[UserDefaultsClient.self] } -// set { self[UserDefaultsClient.self] = newValue } -// } -//} -// -//@DependencyClient -//public struct UserDefaultsClient { -// public var boolForKey: @Sendable (String) -> Bool = { _ in false } -// public var dataForKey: @Sendable (String) -> Data? -// public var doubleForKey: @Sendable (String) -> Double = { _ in 0 } -// public var integerForKey: @Sendable (String) -> Int = { _ in 0 } -// public var remove: @Sendable (String) async -> Void -// public var setBool: @Sendable (Bool, String) async -> Void -// public var setData: @Sendable (Data?, String) async -> Void -// public var setDouble: @Sendable (Double, String) async -> Void -// public var setInteger: @Sendable (Int, String) async -> Void -// -// public var hasShownFirstLaunchOnboarding: Bool { -// self.boolForKey(hasShownFirstLaunchOnboardingKey) -// } -// -// public func setHasShownFirstLaunchOnboarding(_ bool: Bool) async { -// await self.setBool(bool, hasShownFirstLaunchOnboardingKey) -// } -// -// public var installationTime: Double { -// self.doubleForKey(installationTimeKey) -// } -// -// public func setInstallationTime(_ double: Double) async { -// await self.setDouble(double, installationTimeKey) -// } -// -// public func incrementMultiplayerOpensCount() async -> Int { -// let incremented = self.integerForKey(multiplayerOpensCount) + 1 -// await self.setInteger(incremented, multiplayerOpensCount) -// return incremented -// } -//} -// -//let hasShownFirstLaunchOnboardingKey = "hasShownFirstLaunchOnboardingKey" -//let installationTimeKey = "installationTimeKey" -//let multiplayerOpensCount = "multiplayerOpensCount" - -import ComposableArchitecture - -extension PersistenceKey where Self == AppStorageKey { - public static var installationTime: Self { - appStorage("installationTimeKey") - } -} -extension PersistenceKey where Self == AppStorageKey { - public static var hasShownFirstLaunchOnboarding: Self { - AppStorageKey("hasShownFirstLaunchOnboardingKey") - } -} -extension PersistenceKey where Self == AppStorageKey { - public static var multiplayerOpensCount: Self { - AppStorageKey("multiplayerOpensCount") - } -} -extension PersistenceKey where Self == AppStorageKey { - public static var lastReviewRequest: Self { - AppStorageKey("last-review-request-timeinterval") - } -} diff --git a/Sources/UserDefaultsClient/LiveKey.swift b/Sources/UserDefaultsClient/LiveKey.swift deleted file mode 100644 index 7d621b02..00000000 --- a/Sources/UserDefaultsClient/LiveKey.swift +++ /dev/null @@ -1,19 +0,0 @@ -//import Dependencies -//import Foundation -// -//extension UserDefaultsClient: DependencyKey { -// public static let liveValue: Self = { -// let defaults = { UserDefaults(suiteName: "group.isowords")! } -// return Self( -// boolForKey: { defaults().bool(forKey: $0) }, -// dataForKey: { defaults().data(forKey: $0) }, -// doubleForKey: { defaults().double(forKey: $0) }, -// integerForKey: { defaults().integer(forKey: $0) }, -// remove: { defaults().removeObject(forKey: $0) }, -// setBool: { defaults().set($0, forKey: $1) }, -// setData: { defaults().set($0, forKey: $1) }, -// setDouble: { defaults().set($0, forKey: $1) }, -// setInteger: { defaults().set($0, forKey: $1) } -// ) -// }() -//} diff --git a/Sources/UserDefaultsClient/TestKey.swift b/Sources/UserDefaultsClient/TestKey.swift deleted file mode 100644 index 0e257660..00000000 --- a/Sources/UserDefaultsClient/TestKey.swift +++ /dev/null @@ -1,37 +0,0 @@ -//import Dependencies -//import Foundation -// -//extension UserDefaultsClient: TestDependencyKey { -// public static let previewValue = Self.noop -// public static let testValue = Self() -//} -// -//extension UserDefaultsClient { -// public static let noop = Self( -// boolForKey: { _ in false }, -// dataForKey: { _ in nil }, -// doubleForKey: { _ in 0 }, -// integerForKey: { _ in 0 }, -// remove: { _ in }, -// setBool: { _, _ in }, -// setData: { _, _ in }, -// setDouble: { _, _ in }, -// setInteger: { _, _ in } -// ) -// -// public mutating func override(bool: Bool, forKey key: String) { -// self.boolForKey = { [self] in $0 == key ? bool : self.boolForKey($0) } -// } -// -// public mutating func override(data: Data, forKey key: String) { -// self.dataForKey = { [self] in $0 == key ? data : self.dataForKey($0) } -// } -// -// public mutating func override(double: Double, forKey key: String) { -// self.doubleForKey = { [self] in $0 == key ? double : self.doubleForKey($0) } -// } -// -// public mutating func override(integer: Int, forKey key: String) { -// self.integerForKey = { [self] in $0 == key ? integer : self.integerForKey($0) } -// } -//} diff --git a/Tests/AppFeatureTests/Mocks/AppEnvironment.swift b/Tests/AppFeatureTests/Mocks/AppEnvironment.swift index fd9eaa11..bc128d06 100644 --- a/Tests/AppFeatureTests/Mocks/AppEnvironment.swift +++ b/Tests/AppFeatureTests/Mocks/AppEnvironment.swift @@ -4,7 +4,6 @@ import ComposableArchitecture import Foundation import Overture import SettingsFeature -import UserDefaultsClient extension DependencyValues { mutating func didFinishLaunching() { diff --git a/Tests/AppFeatureTests/PersistenceTests.swift b/Tests/AppFeatureTests/PersistenceTests.swift index f7f2628b..055ce2a0 100644 --- a/Tests/AppFeatureTests/PersistenceTests.swift +++ b/Tests/AppFeatureTests/PersistenceTests.swift @@ -17,7 +17,6 @@ import XCTest @testable import AppFeature @testable import GameCore @testable import SoloFeature -@testable import UserDefaultsClient class PersistenceTests: XCTestCase { @MainActor diff --git a/Tests/ChangelogFeatureTests/ChangelogFeatureTests.swift b/Tests/ChangelogFeatureTests/ChangelogFeatureTests.swift index 7a73a314..3d1ee3f2 100644 --- a/Tests/ChangelogFeatureTests/ChangelogFeatureTests.swift +++ b/Tests/ChangelogFeatureTests/ChangelogFeatureTests.swift @@ -4,7 +4,6 @@ import ServerConfig import XCTest @testable import ChangelogFeature -@testable import UserDefaultsClient class ChangelogFeatureTests: XCTestCase { @MainActor diff --git a/Tests/GameOverFeatureTests/GameOverFeatureTests.swift b/Tests/GameOverFeatureTests/GameOverFeatureTests.swift index 3b843628..4fd0eda8 100644 --- a/Tests/GameOverFeatureTests/GameOverFeatureTests.swift +++ b/Tests/GameOverFeatureTests/GameOverFeatureTests.swift @@ -10,7 +10,6 @@ import UpgradeInterstitialFeature import XCTest @testable import LocalDatabaseClient -@testable import UserDefaultsClient class GameOverFeatureTests: XCTestCase { @MainActor diff --git a/Tests/SettingsFeatureTests/SettingsFeatureTests.swift b/Tests/SettingsFeatureTests/SettingsFeatureTests.swift index 5ffb183f..c48991ba 100644 --- a/Tests/SettingsFeatureTests/SettingsFeatureTests.swift +++ b/Tests/SettingsFeatureTests/SettingsFeatureTests.swift @@ -6,7 +6,6 @@ import ComposableUserNotifications import Overture import SharedModels import TestHelpers -import UserDefaultsClient import UserNotifications import UserSettings import XCTest diff --git a/Tests/UpgradeInterstitialFeatureTests/UpgradeInterstitialFeatureTests.swift b/Tests/UpgradeInterstitialFeatureTests/UpgradeInterstitialFeatureTests.swift index 63a83f9a..ff521c67 100644 --- a/Tests/UpgradeInterstitialFeatureTests/UpgradeInterstitialFeatureTests.swift +++ b/Tests/UpgradeInterstitialFeatureTests/UpgradeInterstitialFeatureTests.swift @@ -8,7 +8,7 @@ import StoreKit import UpgradeInterstitialFeature import XCTest -@testable import ServerConfigClient +@testable import ServerConfigPersistenceKey class UpgradeInterstitialFeatureTests: XCTestCase { let scheduler = RunLoop.test