diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index e37043cf79..7af9207365 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -276,11 +276,12 @@ Follow these steps to install and configure the plugin and templates: 1. Install the AS plugin for generating modules : [Generate Module from Template](https://plugins.jetbrains.com/plugin/13586-generate-module-from-template) -2. Import file templates in AS : +2. Run the script `tools/templates/generate_templates.sh` to generate the template zip file +3. Import file templates in AS : - Navigate to File/Manage IDE Settings/Import Settings - - Pick the `tools/templates/file_templates.zip` files + - Pick the `tmp/file_templates.zip` files - Click on OK -3. Configure generate-module-from-template plugin : +4. Configure generate-module-from-template plugin : - Navigate to AS/Settings/Tools/Module Template Settings - Click on + / Import From File - Pick the `tools/templates/FeatureModule.json` diff --git a/features/invitelist/impl/build.gradle.kts b/features/invitelist/impl/build.gradle.kts index d8fb25585f..110eb7946e 100644 --- a/features/invitelist/impl/build.gradle.kts +++ b/features/invitelist/impl/build.gradle.kts @@ -14,8 +14,6 @@ * limitations under the License. */ -// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed -@Suppress("DSL_SCOPE_VIOLATION") plugins { id("io.element.android-compose-library") alias(libs.plugins.anvil) diff --git a/tools/templates/file_templates.zip b/tools/templates/file_templates.zip deleted file mode 100644 index 7352ac3074..0000000000 Binary files a/tools/templates/file_templates.zip and /dev/null differ diff --git a/tools/templates/files/IntelliJ IDEA Global Settings b/tools/templates/files/IntelliJ IDEA Global Settings new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/templates/files/fileTemplates/Template Module Feature Build Gradle API.kts b/tools/templates/files/fileTemplates/Template Module Feature Build Gradle API.kts new file mode 100644 index 0000000000..5c72896315 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Module Feature Build Gradle API.kts @@ -0,0 +1,11 @@ +plugins { + id("io.element.android-library") +} + +android { + namespace = "io.element.android.features.${MODULE_NAME}.api" +} + +dependencies { + implementation(projects.libraries.architecture) +} diff --git a/tools/templates/files/fileTemplates/Template Module Feature Build Gradle Impl.kts b/tools/templates/files/fileTemplates/Template Module Feature Build Gradle Impl.kts new file mode 100644 index 0000000000..6b40a1d06f --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Module Feature Build Gradle Impl.kts @@ -0,0 +1,34 @@ +plugins { + id("io.element.android-compose-library") + alias(libs.plugins.anvil) + alias(libs.plugins.ksp) + id("kotlin-parcelize") +} + +android { + namespace = "io.element.android.features.${MODULE_NAME}.impl" +} + +anvil { + generateDaggerFactories.set(true) +} + +dependencies { + implementation(projects.anvilannotations) + anvil(projects.anvilcodegen) + api(projects.features.${MODULE_NAME}.api) + implementation(projects.libraries.core) + implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrixui) + implementation(projects.libraries.designsystem) + + testImplementation(libs.test.junit) + testImplementation(libs.coroutines.test) + testImplementation(libs.molecule.runtime) + testImplementation(libs.test.truth) + testImplementation(libs.test.turbine) + testImplementation(projects.libraries.matrix.test) + + ksp(libs.showkase.processor) +} diff --git a/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt b/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt new file mode 100644 index 0000000000..6297ec4e24 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Module Feature Entry Point API.kt @@ -0,0 +1,20 @@ +package io.element.android.features.${MODULE_NAME}.api + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint + +interface ${FEATURE_NAME}EntryPoint : FeatureEntryPoint { + + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + // Add your callbacks + } +} diff --git a/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt b/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt new file mode 100644 index 0000000000..adfd142ae5 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Module Feature Entry Point Flow Impl.kt @@ -0,0 +1,30 @@ +package io.element.android.features.${MODULE_NAME}.impl + +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.${MODULE_NAME}.api.${FEATURE_NAME}EntryPoint +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class Default${FEATURE_NAME}EntryPoint @Inject constructor() : ${FEATURE_NAME}EntryPoint { + + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): ${FEATURE_NAME}EntryPoint.NodeBuilder { + val plugins = ArrayList() + + return object : ${FEATURE_NAME}EntryPoint.NodeBuilder { + + override fun callback(callback: ${FEATURE_NAME}EntryPoint.Callback): ${FEATURE_NAME}EntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode<${FEATURE_NAME}FlowNode>(buildContext, plugins) + } + } + } +} diff --git a/tools/templates/files/fileTemplates/Template Module Feature Node Flow Impl.kt b/tools/templates/files/fileTemplates/Template Module Feature Node Flow Impl.kt new file mode 100644 index 0000000000..d08d67ae38 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Module Feature Node Flow Impl.kt @@ -0,0 +1,57 @@ +package io.element.android.features.${MODULE_NAME}.impl + +import android.os.Parcelable +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.composable.Children +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.operation.push +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.architecture.BackstackNode +import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler +import io.element.android.libraries.architecture.createNode +import io.element.android.libraries.di.AppScope +import kotlinx.parcelize.Parcelize + +// CHANGE THE SCOPE +@ContributesNode(AppScope::class) +class ${FEATURE_NAME}FlowNode @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : BackstackNode<${FEATURE_NAME}FlowNode.NavTarget>( + backstack = BackStack( + initialElement = NavTarget.Root, + savedStateMap = buildContext.savedStateMap, + ), + buildContext = buildContext, + plugins = plugins, +) { + + sealed interface NavTarget : Parcelable { + @Parcelize + object Root : NavTarget + } + + override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { + return when (navTarget) { + NavTarget.Root -> { + //Give your root node or completely delete this FlowNode if you have only one node. + createNode<>(buildContext) + } + } + } + + @Composable + override fun View(modifier: Modifier) { + Children( + navModel = backstack, + modifier = modifier, + transitionHandler = rememberDefaultTransitionHandler(), + ) + } +} diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt new file mode 100644 index 0000000000..aa44bc4269 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt @@ -0,0 +1,22 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class ${NAME}Presenter @Inject constructor() : Presenter<${NAME}State> { + + @Composable + override fun present(): ${NAME}State { + + fun handleEvents(event: ${NAME}Events) { + when (event) { + ${NAME}Events.MyEvent -> Unit + } + } + + return ${NAME}State( + eventSink = ::handleEvents + ) + } +} diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.0.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.0.kt new file mode 100644 index 0000000000..26372fc970 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.0.kt @@ -0,0 +1,15 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider + +open class ${NAME}StateProvider : PreviewParameterProvider<${NAME}State> { + override val values: Sequence<${NAME}State> + get() = sequenceOf( + a${NAME}State(), + // Add other states here + ) +} + +fun a${NAME}State() = ${NAME}State( + eventSink = {} +) diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.1.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.1.kt new file mode 100644 index 0000000000..0c997351f0 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.1.kt @@ -0,0 +1,29 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import io.element.android.anvilannotations.ContributesNode +import io.element.android.libraries.di.AppScope + +// CHANGE THE SCOPE +@ContributesNode(AppScope::class) +class ${NAME}Node @AssistedInject constructor( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, + private val presenter: ${NAME}Presenter, +) : Node(buildContext, plugins = plugins) { + + @Composable + override fun View(modifier: Modifier) { + val state = presenter.present() + ${NAME}View( + state = state, + modifier = modifier + ) + } +} diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.2.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.2.kt new file mode 100644 index 0000000000..0080f8d905 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.2.kt @@ -0,0 +1,35 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end + +import androidx.compose.foundation.layout.Box +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.theme.components.Text + +@Composable +fun ${NAME}View( + state: ${NAME}State, + modifier: Modifier = Modifier, +) { + Box(modifier, contentAlignment = Alignment.Center) { + Text( + "${NAME} feature view", + color = MaterialTheme.colorScheme.primary, + ) + } +} + +@PreviewsDayNight +@Composable +internal fun ${NAME}ViewPreview( + @PreviewParameter(${NAME}StateProvider::class) state: ${NAME}State +) = ElementPreview { + ${NAME}View( + state = state, + ) +} diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt new file mode 100644 index 0000000000..3fcdd7f219 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.3.kt @@ -0,0 +1,7 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end + +// TODO add your ui models. Remove the eventSink if you don't have events. +// Do not use default value, so no member get forgotten in the presenters. +data class ${NAME}State( + val eventSink: (${NAME}Events) -> Unit +) diff --git a/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt new file mode 100644 index 0000000000..4b47069304 --- /dev/null +++ b/tools/templates/files/fileTemplates/Template Presentation Classes.kt.child.4.kt @@ -0,0 +1,6 @@ +#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME}#end + +// TODO Add your events or remove the file completely if no events +sealed interface ${NAME}Events { + data object MyEvent: ${NAME}Events +} diff --git a/tools/templates/files/options/file.template.settings.xml b/tools/templates/files/options/file.template.settings.xml new file mode 100644 index 0000000000..c7d26d1fb7 --- /dev/null +++ b/tools/templates/files/options/file.template.settings.xml @@ -0,0 +1,18 @@ + + + +