Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use molecule #665

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use molecule from SwiftUI
  • Loading branch information
hfhbd committed Nov 13, 2022
commit 00ccbbc576cfbcb60f0717131fcf5cd2dc72d303
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ import io.ktor.client.plugins.cookies.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.plugins.resources.*
import io.ktor.http.*
import io.ktor.http.URLProtocol.Companion.HTTPS
import io.ktor.serialization.kotlinx.json.*

fun IosContainer(
storage: CookiesStorage
) = IosContainer(
HTTPS,
"api.todo.softwork.app",
storage
)

fun IosContainer(
protocol: URLProtocol,
host: String,
Expand Down
1 change: 0 additions & 1 deletion desktop/src/main/kotlin/app/softwork/composetodo/main.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package app.softwork.composetodo

import androidx.compose.ui.window.*
import app.cash.sqldelight.db.*
import app.cash.sqldelight.driver.jdbc.sqlite.*
import app.softwork.composetodo.repository.*
import io.ktor.client.*
Expand Down
139 changes: 9 additions & 130 deletions iosApp/Shared/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,26 @@ import clients
import Combine

struct ContentView: View {
@ObservedObject var container: IosContainer

init(container: IosContainer) {
init(container: AppContainer) {
self._container = .init(initialValue: container)
self.isLoggedIn = APILoggedOut(client: container.client)
self.isLoggedIn = container.api.value as! API
}


@ObservedObject private var container: AppContainer
@State private var isLoggedIn: API

var body: some View {
if let isLoggedIn = isLoggedIn as? APILoggedOut {
if isLoggedIn is APILoggedOut {
TabView {
NavigationView {
let loginViewModel = container.loginViewModel(api: isLoggedIn)
Login(state: {
loginViewModel
})
Login(loginViewModel: container.loginViewModel)
.navigationTitle("Login")
}.tabItem {
Label("Login", systemImage: "person")
}

NavigationView {
Register(viewModel: container.registerViewModel(api: isLoggedIn))
Register(viewModel: container.registerViewModel())
.navigationTitle("Register")
}.tabItem {
Label("Register", systemImage: "person.badge.plus")
Expand All @@ -36,128 +32,11 @@ struct ContentView: View {
self.isLoggedIn = api
}
}
} else if let isLoggedIn = isLoggedIn as? APILoggedIn {
} else if isLoggedIn is APILoggedIn {
NavigationView {
Todos(viewModel: container.todoViewModel(api: isLoggedIn))
Todos(viewModel: container.todoViewModel())
.navigationTitle("Todos")
}
}
}
}

struct Login: View {
init(
state: @escaping () -> StateFlow,
updateUserName: @escaping (String) -> Void,
updatePassword: @escaping (String) -> Void
) {
self._stateFlow = StateObject(wrappedValue: {
SwiftStateFlow(flow: state())
}())
self.updateUserName = updateUserName
self.updatePassword = updatePassword
}
let updateUserName: (String) -> Void
let updatePassword: (String) -> Void

@StateObject private var stateFlow: SwiftStateFlow

var body: some View {
let state = stateFlow.value as! LoginViewModel.LoginState

return Form {
TextField("Username", text: Binding(get: {
state.userName
}, set: {
updateUserName(new: $0)
}))
SecureField("Password", text: Binding(get: {
state.userName
}, set: {
updatePassword(new: $0)
}))

if let error = state.error {
Text(error.reason)
}
}.toolbar {
Button("Login") {
viewModel.login()
}
.disabled(!state.enableLogin)
}.task {
for await _ in stateFlow.stream(LoginViewModel.LoginState.self) { }
}
}
}

struct Register: View {
init(viewModel: @autoclosure @escaping () -> RegisterViewModel) {
self._viewModel = StateObject(wrappedValue: viewModel())
}

@StateObject var viewModel: RegisterViewModel

@State private var disableRegister = true
@State private var error: Failure? = nil

var body: some View {
Form {
TextField("Username", text: viewModel.binding(\.username))

SecureField("Password", text: viewModel.binding(\.password))
SecureField("Password Again", text: viewModel.binding(\.passwordAgain))

TextField("First Name", text: viewModel.binding(\.firstName))
TextField("Last Name", text: viewModel.binding(\.lastName))
}.task {
for await newError in viewModel.error.stream(Failure?.self) {
self.error = newError
}
}.toolbar {
Button("Register") {
viewModel.register()
}.disabled(disableRegister)
.task {
for await newEnabled in viewModel.enableRegisterButton.stream(Bool.self) {
self.disableRegister = !newEnabled
}
}
}
}
}

extension Todo: Swift.Identifiable { }

class SwiftStateFlow: StateFlow, ObservableObject {
let flow: StateFlow

var value: Any? {
flow.value
}

var replayCache: [Any] { flow.replayCache }

func collect(collector: FlowCollector) async throws {
try await flow.collect(collector: SwiftFlowCollector(collector: collector, objectWillChange: objectWillChange))
}

init(flow: StateFlow) {
self.flow = flow
}

private class SwiftFlowCollector: FlowCollector {
func emit(value: Any?) async throws {
objectWillChange.send()
try await collector.emit(value: value)
}

let collector: FlowCollector
let objectWillChange: ObjectWillChangePublisher

init(collector: FlowCollector, objectWillChange: ObjectWillChangePublisher) {
self.collector = collector
self.objectWillChange = objectWillChange
}
}
}
74 changes: 74 additions & 0 deletions iosApp/Shared/Login.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// Login.swift
// composetodo (iOS)
//
// Created by Philip Wedemann on 13.11.22.
//

import SwiftUI
import clients

struct Login: View {
init(loginViewModel: @escaping () -> LoginViewModel) {
self._loginViewModel = StateObject(wrappedValue: loginViewModel())
}

@StateObject private var loginViewModel: LoginViewModel

var body: some View {
LoginView(state: loginViewModel.state(coroutineScope: loginViewModel.lifecycleScope, clock: Molecule_runtimeRecompositionClock.immediate), updateUserName: {
loginViewModel.updateUsername(new: $0)
}, updatePassword: {
loginViewModel.updatePassword(new: $0)
}, login: {
loginViewModel.login()
})
}
}

private struct LoginView: View {
init(
state: StateFlow,
updateUserName: @escaping (String) -> Void,
updatePassword: @escaping (String) -> Void,
login: @escaping () -> Void
) {
self.stateFlow = SwiftStateFlow(flow: state)
self.updateUsername = updateUserName
self.updatePassword = updatePassword
self.login = login
}
let updateUsername: (String) -> Void
let updatePassword: (String) -> Void
let login: () -> Void

@ObservedObject private var stateFlow: SwiftStateFlow

var body: some View {
let state = stateFlow.value as! LoginViewModel.LoginState

return Form {
TextField("Username", text: Binding(get: {
state.username
}, set: {
updateUsername($0)
}))
SecureField("Password", text: Binding(get: {
state.password
}, set: {
updatePassword($0)
}))

if let error = state.error {
Text(error.reason)
}
}.toolbar {
Button("Login") {
login()
}
.disabled(!state.enableLogin)
}.task {
for await _ in stateFlow.stream(LoginViewModel.LoginState.self) { }
}
}
}
46 changes: 46 additions & 0 deletions iosApp/Shared/Register.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// Register.swift
// composetodo (iOS)
//
// Created by Philip Wedemann on 13.11.22.
//

import SwiftUI
import clients

struct Register: View {
init(viewModel: @autoclosure @escaping () -> RegisterViewModel) {
self._viewModel = StateObject(wrappedValue: viewModel())
}

@StateObject var viewModel: RegisterViewModel

@State private var disableRegister = true
@State private var error: Failure? = nil

var body: some View {
Form {
TextField("Username", text: viewModel.binding(\.username))

SecureField("Password", text: viewModel.binding(\.password))
SecureField("Password Again", text: viewModel.binding(\.passwordAgain))

TextField("First Name", text: viewModel.binding(\.firstName))
TextField("Last Name", text: viewModel.binding(\.lastName))
}.task {
for await newError in viewModel.error.stream(Failure?.self) {
self.error = newError
}
}.toolbar {
Button("Register") {
viewModel.register()
}.disabled(disableRegister)
.task {
for await newEnabled in viewModel.enableRegisterButton.stream(Bool.self) {
self.disableRegister = !newEnabled
}
}
}
}
}

42 changes: 42 additions & 0 deletions iosApp/Shared/SwiftStateFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// SwiftStateFlow.swift
// composetodo (iOS)
//
// Created by Philip Wedemann on 13.11.22.
//

import clients

class SwiftStateFlow: StateFlow, ObservableObject {
let flow: StateFlow

var value: Any? {
flow.value
}

var replayCache: [Any] { flow.replayCache }

func collect(collector: FlowCollector) async throws {
try await flow.collect(collector: SwiftFlowCollector(collector: collector, objectWillChange: objectWillChange))
}

init(flow: StateFlow) {
self.flow = flow
}

private class SwiftFlowCollector: FlowCollector {
@MainActor
func emit(value: Any?) async throws {
objectWillChange.send()
try await collector.emit(value: value)
}

let collector: FlowCollector
let objectWillChange: ObjectWillChangePublisher

init(collector: FlowCollector, objectWillChange: ObjectWillChangePublisher) {
self.collector = collector
self.objectWillChange = objectWillChange
}
}
}
2 changes: 2 additions & 0 deletions iosApp/Shared/Todos.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct Todos: View {
}
}

extension Todo: Swift.Identifiable { }

struct Todos_Previews: PreviewProvider {
static var previews: some View {
Todos(viewModel: TodoViewModel.init(repo: TestRepo()))
Expand Down
30 changes: 30 additions & 0 deletions iosApp/Shared/UserDefaultStorage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// UserDefaultStorage.swift
// composetodo (iOS)
//
// Created by Philip Wedemann on 13.11.22.
//

import clients
import SwiftUI

actor UserDefaultStorage: NSObject, Ktor_client_coreCookiesStorage {
@AppStorage("refreshToken")
private var token: String?

func addCookie(requestUrl: Ktor_httpUrl, cookie: Ktor_httpCookie) async throws {
token = cookie.value
}

func get(requestUrl: Ktor_httpUrl) async throws -> [Ktor_httpCookie]? {
if let token {
return [Ktor_httpCookie(name: "SESSION", value: token, encoding: Ktor_httpCookieEncoding.uriEncoding, maxAge: 0, expires: nil, domain: nil, path: nil, secure: true, httpOnly: true, extensions: [:])]
} else {
return []
}
}

nonisolated func close() {

}
}
Loading