Skip to content

Commit

Permalink
save API key in Keychain
Browse files Browse the repository at this point in the history
  • Loading branch information
ski-u committed Dec 17, 2024
1 parent 4c7d02f commit ba0ec5e
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 4 deletions.
79 changes: 79 additions & 0 deletions Features/.swiftpm/xcode/xcshareddata/xcschemes/Settings.xcscheme
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Settings"
BuildableName = "Settings"
BlueprintName = "Settings"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "SettingsTests"
BuildableName = "SettingsTests"
BlueprintName = "SettingsTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Settings"
BuildableName = "Settings"
BlueprintName = "Settings"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
1 change: 1 addition & 0 deletions Features/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ let package = Package(
name: "Settings",
dependencies: [
"APIClientLive",
"APIKeyClient",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
.product(name: "LicenseList", package: "LicenseList"),
]
Expand Down
11 changes: 10 additions & 1 deletion Features/Sources/Settings/APIKey/APIKeySetting.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import APIClient
import APIClientLive
import APIKeyClient
import ComposableArchitecture
import Models

Expand All @@ -22,20 +23,28 @@ public struct APIKeySetting {
}

public enum Action: Equatable {
case onAppear
case setAPIKeyInput(String)
case updateButtonTapped
}

@Dependency(\.apiKeyClient) private var apiKeyClient

public init() {}

public func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .onAppear:
state.apiKeyInput = apiKeyClient.getKey() ?? .init(rawValue: "")
return .none

case let .setAPIKeyInput(input):
state.apiKeyInput = .init(rawValue: input)
return .none

case .updateButtonTapped:
// TODO: Persist API key
apiKeyClient.setKey(state.apiKeyInput)
// TODO: remove this logic
APIClient.setAPIKey(state.apiKeyInput)
return .none
}
Expand Down
3 changes: 3 additions & 0 deletions Features/Sources/Settings/APIKey/APIKeySettingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ struct APIKeySettingView: View {
.disabled(!viewStore.isEdited)
}
}
.onAppear {
viewStore.send(.onAppear)
}
}

private var link: some View {
Expand Down
16 changes: 15 additions & 1 deletion Features/Sources/Settings/Settings/Settings.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import APIKeyClient
import ComposableArchitecture
import Models

@Reducer
public struct Settings {
Expand All @@ -9,19 +11,31 @@ public struct Settings {

@ObservableState
public struct State: Equatable {
var apiKey: APIKey
var path = StackState<Path.State>()

public init() {}
public init(
apiKey: APIKey = .init(rawValue: "")
) {
self.apiKey = apiKey
}
}

public enum Action {
case onAppear
case path(StackActionOf<Path>)
case popToRoot
}

@Dependency(\.apiKeyClient) private var apiKeyClient

public var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
state.apiKey = apiKeyClient.getKey() ?? .init(rawValue: "")
return .none

case let .path(action):
switch action {
case let .element(id: id, action: .apiKeySetting(.updateButtonTapped)):
Expand Down
5 changes: 4 additions & 1 deletion Features/Sources/Settings/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct SettingsView: View {

Spacer()

Text(APIClient.apiKey?.rawValue.masked ?? "None")
Text(store.apiKey.rawValue.masked ?? "None")
.foregroundStyle(Color.secondary)
}
}
Expand Down Expand Up @@ -77,6 +77,9 @@ public struct SettingsView: View {
APIKeySettingView(store: store)
}
}
.onAppear {
store.send(.onAppear)
}
}
}

Expand Down
38 changes: 38 additions & 0 deletions Features/Tests/SettingsTests/APIKeySettingTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import ComposableArchitecture
@testable import Settings
import Testing

@MainActor
struct APIKeySettingTests {
@Test
func setAPIKeyInput() async throws {
let store = TestStore(
initialState: APIKeySetting.State()
) {
APIKeySetting()
}

await store.send(.setAPIKeyInput("test")) {
$0.apiKeyInput = .init(rawValue: "test")
}
}

@Test
func onAppear() async throws {
let store = TestStore(
initialState: APIKeySetting.State()
) {
APIKeySetting()
}

store.dependencies.apiKeyClient.getKey = {
.init(rawValue: "test")
}

await store.send(.onAppear) {
$0.apiKeyInput = .init(rawValue: "test")
}
}

// TODO: add test case for `updateButtonTapped`
}
20 changes: 19 additions & 1 deletion Features/Tests/SettingsTests/SettingsTests.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import ComposableArchitecture
import Testing

@testable import Settings

@MainActor
struct SettingsTests {}
struct SettingsTests {
@Test
func onAppear() async throws {
let store = TestStore(
initialState: Settings.State()
) {
Settings()
}

store.dependencies.apiKeyClient.getKey = {
.init(rawValue: "test")
}

await store.send(.onAppear) {
$0.apiKey = .init(rawValue: "test")
}
}
}

0 comments on commit ba0ec5e

Please sign in to comment.