From 723528760d8e2dce4f40d40214ad5a7059eb954a Mon Sep 17 00:00:00 2001 From: naturecurly Date: Wed, 16 Jul 2025 20:30:55 +1000 Subject: [PATCH] Replace Hilt EntryPoint as Hilt does not support multiple EntryPoints with the same signature --- .../generator/ProviderDepsGenerator.kt | 69 ++++++++++++++----- .../generator/ProviderModuleGenerator.kt | 5 +- .../deck/compose/DeckDependencies.kt | 2 +- .../com/naturecurly/deck/compose/WharfImpl.kt | 46 ++++++------- .../naturecurly/deck/sample/MainActivity.kt | 1 + .../deck/sample/feature1/SecondaryScreen.kt | 54 +++++++++++++++ .../sample/feature1/SecondaryViewModel.kt | 53 ++++++++++++++ .../FeatureOneForSecondaryContainer.kt | 48 +++++++++++++ .../FeatureOneForSecondaryContainerUi.kt | 50 ++++++++++++++ 9 files changed, 280 insertions(+), 48 deletions(-) create mode 100644 sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryScreen.kt create mode 100644 sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryViewModel.kt create mode 100644 sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainer.kt create mode 100644 sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainerUi.kt diff --git a/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderDepsGenerator.kt b/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderDepsGenerator.kt index 43dff5a..bf1c709 100644 --- a/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderDepsGenerator.kt +++ b/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderDepsGenerator.kt @@ -34,24 +34,27 @@ import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.STAR import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.WildcardTypeName import com.squareup.kotlinpoet.asClassName import com.squareup.kotlinpoet.ksp.addOriginatingKSFile import com.squareup.kotlinpoet.ksp.writeTo -import dagger.hilt.EntryPoint -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent +import jakarta.inject.Inject import java.util.Locale import kotlin.reflect.KClass class ProviderDepsGenerator(private val codeGenerator: CodeGenerator) { - fun generate(originatingFile: KSFile, providerId: String, destinationPackageName: String): ClassName { + fun generate( + originatingFile: KSFile, + providerId: String, + destinationPackageName: String, + ): ClassName { val deckDependenciesInterfaceName = providerId.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ENGLISH) else it.toString() } + "DeckDependencies" - val deckQualifierAnnotation = getDeckQualifierAnnotation(providerId) // region Return Type val containerSetReturnType = Set::class.asClassName().parameterizedBy( @@ -76,39 +79,41 @@ class ProviderDepsGenerator(private val codeGenerator: CodeGenerator) { // endregion // region Functions - val functionProviderClass = FunSpec.builder("providerClass") + val providerClassPropertyName = "providerClass" + val functionProviderClass = FunSpec.builder(providerClassPropertyName) .addOriginatingKSFile(originatingFile) - .addAnnotation(deckQualifierAnnotation) .addModifiers(KModifier.OVERRIDE) - .addModifiers(KModifier.ABSTRACT) .returns(deckProviderKClassReturnType) + .addStatement("return $providerClassPropertyName") .build() - val functionContainer = FunSpec.builder("containers") + val containerPropertyName = "containers" + val functionContainer = FunSpec.builder(containerPropertyName) .addOriginatingKSFile(originatingFile) - .addAnnotation(deckQualifierAnnotation) .addModifiers(KModifier.OVERRIDE) - .addModifiers(KModifier.ABSTRACT) .returns(containerSetReturnType) + .addStatement("return $containerPropertyName") .build() + val containerUiPairPropertyName = "containerUiToContainerPairs" val functionContainerUiToContainerPairs = FunSpec.builder("containerUiToContainerPairs") .addOriginatingKSFile(originatingFile) - .addAnnotation(deckQualifierAnnotation) .addModifiers(KModifier.OVERRIDE) - .addModifiers(KModifier.ABSTRACT) .returns(containerUiToContainerPairsReturnType) + .addStatement("return $containerUiPairPropertyName") .build() // endregion val deckDependenciesInterfaceType = - TypeSpec.interfaceBuilder(deckDependenciesInterfaceName) + TypeSpec.classBuilder(deckDependenciesInterfaceName) .addOriginatingKSFile(originatingFile) - .addAnnotation(EntryPoint::class) - .addAnnotation( - AnnotationSpec.builder(InstallIn::class) - .addMember("%T::class", SingletonComponent::class) - .build(), + .constructor( + providerId, + mapOf( + providerClassPropertyName to deckProviderKClassReturnType, + containerPropertyName to containerSetReturnType, + containerUiPairPropertyName to containerUiToContainerPairsReturnType, + ), ) .addSuperinterface(deckDependenciesClassName) .addFunction(functionProviderClass) @@ -124,4 +129,30 @@ class ProviderDepsGenerator(private val codeGenerator: CodeGenerator) { .writeTo(codeGenerator, aggregating = false) return deckDependenciesInterfaceClassName } + + private fun TypeSpec.Builder.constructor( + providerId: String, + properties: Map, + ): TypeSpec.Builder { + val deckQualifierAnnotation = getDeckQualifierAnnotation(providerId) + + return this.primaryConstructor( + FunSpec.constructorBuilder() + .addAnnotation(Inject::class.asClassName()) + .apply { + properties.forEach { name, type -> + addParameter(name, type) + } + } + .build(), + ).addProperties( + properties.map { + PropertySpec.builder(it.key, it.value) + .initializer(it.key) + .addAnnotation(deckQualifierAnnotation) + .addModifiers(KModifier.PRIVATE) + .build() + }, + ) + } } diff --git a/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderModuleGenerator.kt b/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderModuleGenerator.kt index 84f3e8e..cdc35b2 100644 --- a/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderModuleGenerator.kt +++ b/codegen/src/main/java/com/naturecurly/deck/codegen/generator/ProviderModuleGenerator.kt @@ -68,8 +68,9 @@ class ProviderModuleGenerator(private val codeGenerator: CodeGenerator) { .addAnnotation(IntoMap::class) .addAnnotation(Provides::class) .addAnnotation(classKeyAnnotation) - .returns(deckDepsClassReturnType) - .addCode("return %T::class.java", providerDepsInterfaceClassName) + .addParameter("dependencies", providerDepsInterfaceClassName) + .returns(deckDependenciesClassName) + .addCode("return dependencies") .build() val functionProviderKClass = FunSpec.builder("provideDeckProviderKClass") diff --git a/compose/src/main/java/com/naturecurly/deck/compose/DeckDependencies.kt b/compose/src/main/java/com/naturecurly/deck/compose/DeckDependencies.kt index 9dcdc1c..2bcece6 100644 --- a/compose/src/main/java/com/naturecurly/deck/compose/DeckDependencies.kt +++ b/compose/src/main/java/com/naturecurly/deck/compose/DeckDependencies.kt @@ -33,7 +33,7 @@ import kotlin.reflect.KClass @EntryPoint @InstallIn(SingletonComponent::class) interface DeckDependenciesEntryPoint { - fun dependencies(): Map, @JvmSuppressWildcards Class> + fun dependencies(): Map, DeckDependencies> } interface DeckDependencies { diff --git a/compose/src/main/java/com/naturecurly/deck/compose/WharfImpl.kt b/compose/src/main/java/com/naturecurly/deck/compose/WharfImpl.kt index 4cfe387..3d0db0a 100644 --- a/compose/src/main/java/com/naturecurly/deck/compose/WharfImpl.kt +++ b/compose/src/main/java/com/naturecurly/deck/compose/WharfImpl.kt @@ -30,9 +30,7 @@ import dagger.hilt.EntryPoints import kotlin.reflect.KClass class WharfImpl : Wharf() { - private var application: Application? = null - - private val entryPoints: MutableMap, Class> = mutableMapOf() + private val entryPoints: MutableMap, DeckDependencies> = mutableMapOf() internal fun init(app: Application) { entryPoints.clear() @@ -45,7 +43,6 @@ class WharfImpl : Wharf() { DeckLog.e("WharfImpl initialization failed", it) return } - application = app } @Suppress("UNCHECKED_CAST") @@ -53,28 +50,25 @@ class WharfImpl : Wharf() { providerClass: KClass>, providerIdentity: Int, ) { - entryPoints[providerClass.java]?.let { dep -> - application?.let { app -> - val dependencies = EntryPoints.get(app, dep) - val containers = dependencies.containers() - deckEntry.addProvider(providerIdentity) - containers.forEach { - deckEntry.addContainer( - providerIdentity = providerIdentity, - containerClass = it::class, - container = it, - ) - } - dependencies.containerUiToContainerPairs().forEach { uiContainerPair -> - val containerUi = uiContainerPair.first - deckEntry.addContainerUi( - containerUiClass = containerUi::class, - containerClass = uiContainerPair.second, - containerUi = containerUi, - ) - deckEntry.getDeckContainer(containerUi::class)?.let { container -> - setContainerToContainerUi(containerUi, container) - } + entryPoints[providerClass.java]?.let { dependencies -> + val containers = dependencies.containers() + deckEntry.addProvider(providerIdentity) + containers.forEach { + deckEntry.addContainer( + providerIdentity = providerIdentity, + containerClass = it::class, + container = it, + ) + } + dependencies.containerUiToContainerPairs().forEach { uiContainerPair -> + val containerUi = uiContainerPair.first + deckEntry.addContainerUi( + containerUiClass = containerUi::class, + containerClass = uiContainerPair.second, + containerUi = containerUi, + ) + deckEntry.getDeckContainer(containerUi::class)?.let { container -> + setContainerToContainerUi(containerUi, container) } } } diff --git a/sample/app/src/main/java/com/naturecurly/deck/sample/MainActivity.kt b/sample/app/src/main/java/com/naturecurly/deck/sample/MainActivity.kt index f01136a..c26c803 100644 --- a/sample/app/src/main/java/com/naturecurly/deck/sample/MainActivity.kt +++ b/sample/app/src/main/java/com/naturecurly/deck/sample/MainActivity.kt @@ -36,6 +36,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() setContent { MainScreen() +// SecondaryScreen() } } } diff --git a/sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryScreen.kt b/sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryScreen.kt new file mode 100644 index 0000000..517b889 --- /dev/null +++ b/sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryScreen.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 naturecurly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.naturecurly.deck.sample.feature1 + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import com.naturecurly.deck.compose.Deck +import com.naturecurly.deck.compose.DeckScope +import com.naturecurly.deck.sample.designsystem.theme.DeckTheme + +@Composable +fun SecondaryScreen(viewModel: SecondaryViewModel = viewModel()) { + DeckTheme { + Deck(viewModel) { + Content() + } + } +} + +@Composable +private fun DeckScope.Content() { + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + Column(modifier = Modifier.padding(innerPadding)) { + Text("Secondary Screen") + Stub("FeatureOneForSecondary") + } + } +} diff --git a/sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryViewModel.kt b/sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryViewModel.kt new file mode 100644 index 0000000..281e635 --- /dev/null +++ b/sample/mainFeature/src/main/java/com/naturecurly/deck/sample/feature1/SecondaryViewModel.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 naturecurly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.naturecurly.deck.sample.feature1 + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.naturecurly.deck.ContainerEvent +import com.naturecurly.deck.DeckProvider +import com.naturecurly.deck.WharfAccess +import com.naturecurly.deck.annotations.Provider +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +@Provider("SecondaryScreen") +class SecondaryViewModel @Inject constructor(private val wharfAccess: WharfAccess) : + ViewModel(), + DeckProvider, + WharfAccess by wharfAccess { + init { + initDeckProvider(viewModelScope) + onDeckReady(viewModelScope, 7) + } + + override fun onContainerEvent(containerEvent: ContainerEvent) { + TODO("Not yet implemented") + } + + override fun onCleared() { + super.onCleared() + onDeckClear() + } +} diff --git a/sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainer.kt b/sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainer.kt new file mode 100644 index 0000000..3b29807 --- /dev/null +++ b/sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainer.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 naturecurly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.naturecurly.deck.sample.subfeatureone + +import com.naturecurly.deck.DeckContainer +import com.naturecurly.deck.annotations.Container +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import javax.inject.Inject + +@Container(bindTo = "SecondaryScreen") +class FeatureOneForSecondaryContainer @Inject constructor() : DeckContainer() { + private val _uiStateFlow = MutableStateFlow("") + lateinit var scope: CoroutineScope + override fun init(scope: CoroutineScope) { + this.scope = scope + } + + override fun onDataReady(scope: CoroutineScope, data: Int) { + _uiStateFlow.value = "FeatureOneForSecondary: $data" + } + + override val uiStateFlow: StateFlow = _uiStateFlow + + override fun onEvent(event: Event) { + } +} diff --git a/sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainerUi.kt b/sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainerUi.kt new file mode 100644 index 0000000..faad14e --- /dev/null +++ b/sample/subFeatureOne/src/main/java/com/naturecurly/deck/sample/subfeatureone/FeatureOneForSecondaryContainerUi.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 naturecurly + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.naturecurly.deck.sample.subfeatureone + +import androidx.compose.foundation.clickable +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.naturecurly.deck.annotations.ContainerUi +import com.naturecurly.deck.compose.DeckComposeContainerUi +import javax.inject.Inject + +@ContainerUi(bindTo = "SecondaryScreen") +class FeatureOneForSecondaryContainerUi @Inject constructor() : DeckComposeContainerUi() { + override val id: String + get() = "FeatureOneForSecondary" + + @Composable + override fun Content(modifier: Modifier) { + val uiState by container.uiStateFlow.collectAsStateWithLifecycle() + Text( + text = uiState, + modifier = modifier.clickable { + container.onEvent(Event.UpdateValueEvent) + }, + ) + } +}