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

Show Compose Contributor screen in iOS for prototype #9

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ private fun NavGraphBuilder.mainScreen(
) {
mainScreen(
windowSize = windowSize,
displayFeatures = displayFeatures,
mainNestedGraphStateHolder = KaigiAppMainNestedGraphStateHolder(),
mainNestedGraph = { mainNestedNavController, contentPadding ->
nestedSessionScreens(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class MainActivity : ComponentActivity() {
}

setContent {
val windowSize = calculateWindowSizeClass(this)
val windowSize = calculateWindowSizeClass()
val displayFeatures = calculateDisplayFeatures(this)
CompositionLocalProvider(
LocalClock provides clockProvider.clock(),
Expand Down
33 changes: 33 additions & 0 deletions app-ios/Sources/App/Container.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import shared

struct Container {
static let shared: Container = .init()

private let entryPoint: KmpEntryPoint
private init() {
entryPoint = .init()
class DummyAuthenticator: Authenticator {

func currentUser() async throws -> User? {
return User(idToken: "")
}


func signInAnonymously() async throws -> User? {
return User(idToken: "")
}

}
entryPoint.doInit(
remoteConfigApi: FakeRemoteConfigApi(),
authenticator: DummyAuthenticator()
)
}

func get<TypeProtocol, ReturnType>(type: TypeProtocol) -> ReturnType where TypeProtocol: Protocol {
guard let object = entryPoint.get(objCProtocol: type) as? ReturnType else {
fatalError("Not found instance for \(type)")
}
return object
}
}
50 changes: 44 additions & 6 deletions app-ios/Sources/App/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,55 @@ import SwiftUI
public struct ContentView: View {
public init() {}

@State var selection = 1
public var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")

TabView(selection: $selection) {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
.tabItem {
Label(
title: { Text("Label") },
icon: { Image(systemName: "42.circle") }
)
}


VStack {
ContributorComposeViewControllerWrapper()
}
.padding()
.tabItem {
Label(
title: { Text("KMP") },
icon: { Image(systemName: "42.circle") }
)
}

}
.padding()
}
}

struct ContributorComposeViewControllerWrapper: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
let container = Container.shared
let repository: ContributorsRepository = container.get(type: ContributorsRepository.self)
return DarwinContributorsKt.contributorViewController(
contributorsRepository: repository,
onContributorItemClick: {_ in}
)
}

func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
}


#Preview {
ContentView()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class KmpComposePlugin : Plugin<Project> {
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.dependencies.components.resources)
implementation(libs.library("rin"))
implementation(libs.library("composeNavigation"))
implementation(libs.library("composeMaterialWindowSize"))
implementation(libs.library("androidxLifecycleViewModel"))
implementation(libs.library("androidxLifecycleViewModelCompose"))
implementation(libs.library("androidxLifecycleCommon"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ class KmpPlugin : Plugin<Project> {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeLink>().configureEach {
notCompatibleWithConfigurationCache("Configuration chache not supported for a system property read at configuration time")
}
kotlin {
with(sourceSets) {
commonMain {
dependencies {
implementation(libs.findLibrary("kermit").get())
}
}
}
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface ComposeEffectErrorHandler {
val LocalComposeEffectErrorHandler = staticCompositionLocalOf<ComposeEffectErrorHandler> {
object : ComposeEffectErrorHandler {
override suspend fun emit(throwable: Throwable) {
throwable.printStackTrace()
}
}
}
Expand Down
1 change: 1 addition & 0 deletions core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ kotlin {
api(libs.androidxDatastoreDatastorePreferences)
implementation(libs.okIo)
implementation(libs.ktorClientCore)
implementation(libs.ktorClientLogging)
implementation(libs.ktorKotlinxSerialization)
implementation(libs.ktorContentNegotiation)
implementation(libs.kermit)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public class DefaultContributorsRepository(
refresh()
}
}
return contributorsStateFlow.value
return contributors
}

override suspend fun refresh() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import io.github.droidkaigi.confsched.model.SponsorsRepository
import io.github.droidkaigi.confsched.model.StaffRepository
import io.ktor.client.HttpClient
import io.ktor.client.engine.darwin.Darwin
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -49,6 +52,15 @@ public val dataModule: Module = module {
HttpClient(Darwin) {
engine {}
defaultKtorConfig(get(), get())
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
co.touchlab.kermit.Logger.d { message }
}
}

level = LogLevel.BODY
}
}
}
single {
Expand Down
6 changes: 1 addition & 5 deletions feature/contributors/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
plugins {
id("droidkaigi.primitive.kmp")
id("droidkaigi.primitive.kmp.android")
id("droidkaigi.primitive.kmp.android.hilt")
id("droidkaigi.primitive.kmp.ios")
id("droidkaigi.primitive.kmp.compose")
id("droidkaigi.convention.kmpfeature")
}

android.namespace = "io.github.droidkaigi.confsched.feature.contributors"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.testTag
import co.touchlab.kermit.Logger
import io.github.droidkaigi.confsched.compose.rememberEventEmitter
import io.github.droidkaigi.confsched.contributors.component.ContributorListItem
import io.github.droidkaigi.confsched.model.Contributor
Expand All @@ -33,7 +34,7 @@ const val ContributorsScreenTestTag = "ContributorsScreenTestTag"

data class ContributorsUiState(
val contributors: PersistentList<Contributor>,
val userMessageStateHolder: UserMessageStateHolder
val userMessageStateHolder: UserMessageStateHolder,
)

@Composable
Expand Down Expand Up @@ -71,6 +72,7 @@ fun ContributorsScreen(
onContributorItemClick: (url: String) -> Unit,
isTopAppBarHidden: Boolean,
) {
Logger.d { "ContributorsScreen: $uiState" }
val scrollBehavior =
if (!isTopAppBarHidden) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,66 @@
package io.github.droidkaigi.confsched.contributors

import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.interop.LocalUIViewController
import androidx.compose.ui.window.ComposeUIViewController
import app.cash.molecule.DisplayLinkClock
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import co.touchlab.kermit.Logger
import io.github.droidkaigi.confsched.compose.rememberEventEmitter
import io.github.droidkaigi.confsched.designsystem.theme.KaigiTheme
import io.github.droidkaigi.confsched.model.ContributorsRepository
import io.github.droidkaigi.confsched.model.compositionlocal.LocalRepositories
import io.github.droidkaigi.confsched.ui.SnackbarMessageEffect
import io.github.droidkaigi.confsched.ui.UserMessageStateHolderImpl
import platform.UIKit.UIViewController

@Suppress("UNUSED")
fun contributorViewController(
contributorsRepository: ContributorsRepository,
onContributorItemClick: (url: String) -> Unit,
): UIViewController = ComposeUIViewController {
val eventEmitter = rememberEventEmitter<ContributorsScreenEvent>()
val uiState = contributorsScreenPresenter(
events = eventEmitter,
)

val snackbarHostState = remember { SnackbarHostState() }

SnackbarMessageEffect(
snackbarHostState = snackbarHostState,
userMessageStateHolder = uiState.userMessageStateHolder,
)
val uiViewController = LocalUIViewController.current
LaunchedEffect(uiViewController) {
NavHost(rememberNavController(), startDestination = "root") {
composable("root") {
CompositionLocalProvider(
LocalRepositories provides mapOf(
ContributorsRepository::class to contributorsRepository,
)
) {
Logger.d { "contributorViewController" }
val eventEmitter = rememberEventEmitter<ContributorsScreenEvent>()

val uiState = contributorsScreenPresenter(
events = eventEmitter,
)

val snackbarHostState = remember { SnackbarHostState() }

SnackbarMessageEffect(
snackbarHostState = snackbarHostState,
userMessageStateHolder = uiState.userMessageStateHolder,
)
val uiViewController = LocalUIViewController.current
LaunchedEffect(uiViewController) {
// uiViewController
// TODO: How to know the destroy event of the ViewController?
// TODO: How to know the destroy event of the ViewController?
// viewModel.viewModelScope.cancel()
}
}

KaigiTheme {

KaigiTheme {
ContributorsScreen(
uiState = uiState,
snackbarHostState = snackbarHostState,
isTopAppBarHidden = true,
onBackClick = { /** no action for iOS side **/ },
onContributorItemClick = onContributorItemClick,
)

ContributorsScreen(
uiState = uiState,
snackbarHostState = snackbarHostState,
isTopAppBarHidden = true,
onBackClick = { /** no action for iOS side **/ },
onContributorItemClick = onContributorItemClick,
)
}
}
}
}
}
24 changes: 12 additions & 12 deletions feature/main/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
plugins {
id("droidkaigi.convention.androidfeature")
id("droidkaigi.convention.kmpfeature")
}

android.namespace = "io.github.droidkaigi.confsched.feature.main"

dependencies {
implementation(projects.core.designsystem)
implementation(projects.core.ui)
implementation(projects.core.model)
testImplementation(projects.core.testing)

implementation(libs.composeHiltNavigtation)
implementation(libs.composeMaterialWindowSize)
implementation(libs.composeMaterialIcon)
implementation(libs.composeNavigation)
implementation(libs.androidxWindow)
kotlin {
sourceSets {
commonMain {
dependencies {
implementation(compose.materialIconsExtended)
implementation(projects.core.model)
implementation(projects.core.designsystem)
implementation(projects.core.ui)
}
}
}
}
Loading