Skip to content
This repository was archived by the owner on Mar 10, 2025. It is now read-only.
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
29 changes: 25 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ three steps:
1. Set up build.gradle
2. Create the catalog activity and app classes
3. Setup `AndroidManifest.xml`
4. [Optional] provide sourcecode, documentation and bug report URLs

In the app module's `build.gradle` include the framework dependencies, Hilt and KAPT plugins:

Expand Down Expand Up @@ -107,6 +108,22 @@ Finally, don't forget to declare them in the `AndroidManifest.xml`:
</application>
```

The framework provides quick access to the sourcecode, documentation or bug report of a sample in
the default UI toolbar.

To enable it, provide the base URLs and use the `@Sample` annotation parameters

```xml
<resources>
<string name="app_name">app-catalog</string>
<string name="source_base_url">https://github.com/google/casa-android/tree/main/app-catalog/samples/%1$s</string>
<string name="documentation_base_url">https://github.com/google/casa-android</string>
<string name="bug_report_url">https://github.com/google/casa-android/issues/new?assignees=&amp;labels=&amp;template=bug_report.md&amp;title=%1$s</string>
</resources>
```

> **Note:** the resources can take one optional string argument by adding %1$s.

### Create sample modules

Place any new samples under the samples folder and for each new module add the framework
Expand Down Expand Up @@ -140,12 +157,16 @@ Then create as many entry points as desired by annotating any composable functio
fragment with the `@Sample` annotation:

```kotlin
@Sample(name = "Compose sample", "Shows how to add a compose target in the catalog")
@Sample(
name = "Compose sample",
description = "Shows how to add a compose target in the catalog",
documentation = "https://github.com/google/casa-android#create-sample-modules"
)
@Composable
fun ComposeSample() {
Box(Modifier.fillMaxSize()) {
Text(text = "Hi, I am a compose sample target!")
}
Box(Modifier.fillMaxSize()) {
Text(text = "Hi, I am a compose sample target!")
}
}
```

Expand Down
3 changes: 3 additions & 0 deletions app-catalog/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@

<resources>
<string name="app_name">app-catalog</string>
<string name="source_base_url">https://github.com/google/casa-android/tree/main/app-catalog/samples/%1$s</string>
<string name="documentation_base_url">https://github.com/google/casa-android</string>
<string name="bug_report_url">https://github.com/google/casa-android/issues/new?assignees=&amp;labels=&amp;template=bug_report.md&amp;title=%1$s</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.google.android.catalog.framework.annotations.Sample

@Sample(name = "Compose sample", "Shows how to add a compose target in the catalog")
@Sample(
name = "Compose sample",
description = "Shows how to add a compose target in the catalog",
documentation = "https://github.com/google/casa-android#create-sample-modules"
)
@Composable
fun ComposeSample() {
Box(Modifier.fillMaxSize()) {
Expand Down
4 changes: 4 additions & 0 deletions framework/annotations/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package com.google.android.catalog.framework.annotations {

@kotlin.annotation.MustBeDocumented @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface Sample {
method public abstract String description();
method public abstract String documentation();
method public abstract String name();
method public abstract String sourcePath();
property public abstract String description;
property public abstract String documentation;
property public abstract String name;
property public abstract String sourcePath;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,23 @@

package com.google.android.catalog.framework.annotations

/**
* Add this annotation to a parameterless `@Compose` function, fragment or activity classes to
* create a new sample entry-point that will be automatically included in the Catalog app.
*
* @param name a name to use when displaying the sample
* @param description a description to use when displaying the sample
* @param documentation an optional documentation link, it can be absolute or relative to the
* provided `documentation_base_url` resource ID.
* @param sourcePath an optional source code path, it can be absolute or relative to the
* provided `source_base_url` resource ID.
*/
@MustBeDocumented
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class Sample(val name: String, val description: String)
annotation class Sample(
val name: String,
val description: String,
val documentation: String = "",
val sourcePath: String = "",
)
12 changes: 9 additions & 3 deletions framework/base/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@
package com.google.android.catalog.framework.base {

public final class CatalogSample {
ctor public CatalogSample(String name, String description, String path, com.google.android.catalog.framework.base.CatalogTarget target);
ctor public CatalogSample(String name, String description, String documentation, String sourcePath, String path, com.google.android.catalog.framework.base.CatalogTarget target);
method public String component1();
method public String component2();
method public String component3();
method public com.google.android.catalog.framework.base.CatalogTarget component4();
method public com.google.android.catalog.framework.base.CatalogSample copy(String name, String description, String path, com.google.android.catalog.framework.base.CatalogTarget target);
method public String component4();
method public String component5();
method public com.google.android.catalog.framework.base.CatalogTarget component6();
method public com.google.android.catalog.framework.base.CatalogSample copy(String name, String description, String documentation, String sourcePath, String path, com.google.android.catalog.framework.base.CatalogTarget target);
method public String getDescription();
method public String getDocumentation();
method public String getName();
method public String getPath();
method public String getSourcePath();
method public com.google.android.catalog.framework.base.CatalogTarget getTarget();
property public final String description;
property public final String documentation;
property public final String name;
property public final String path;
property public final String sourcePath;
property public final com.google.android.catalog.framework.base.CatalogTarget target;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package com.google.android.catalog.framework.base
data class CatalogSample(
val name: String,
val description: String,
val documentation: String,
val sourcePath: String,
val path: String,
val target: CatalogTarget
)
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class SampleProcessor(

@OptIn(KspExperimental::class)
private fun createModule(functionSample: KSDeclaration, target: String) {
val samplePath = getSamplePath(functionSample)
val filePath = getRelativeFilePath(functionSample)
val sample = functionSample.getAnnotationsByType(Sample::class).first()
val packageName = functionSample.packageName.asString()
val sampleFile = functionSample.simpleName.asString()
Expand All @@ -121,7 +121,9 @@ class SampleProcessor(
samplePackage = packageName,
sampleName = sample.name,
sampleDescription = sample.description,
samplePath = samplePath,
sampleDocs = sample.documentation,
sampleSource = sample.sourcePath.ifBlank { filePath },
samplePath = filePath.substringBefore("/src"),
sampleTarget = target
).toByteArray()
)
Expand All @@ -131,9 +133,9 @@ class SampleProcessor(
private fun KSDeclaration.toFullPath() =
"${packageName.asString()}.${simpleName.asString()}"

private fun getSamplePath(declaration: KSDeclaration): String {
private fun getRelativeFilePath(declaration: KSDeclaration): String {
val path = (declaration.location as? FileLocation)?.filePath.orEmpty()
return path.substringAfterLast("/samples/").substringBefore("/src")
return path.substringAfterLast("/samples/")
}
}

Expand All @@ -142,6 +144,8 @@ private fun sampleTemplate(
samplePackage: String,
sampleName: String,
sampleDescription: String,
sampleDocs: String,
sampleSource: String,
samplePath: String,
sampleTarget: String
) = """
Expand All @@ -165,6 +169,8 @@ class ${sampleFile}Module {
return CatalogSample(
"$sampleName",
"$sampleDescription",
"$sampleDocs",
"$sampleSource",
"$samplePath",
$sampleTarget
)
Expand Down
4 changes: 4 additions & 0 deletions framework/ui/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ package com.google.android.catalog.framework.ui.components {
public final class CardItemKt {
}

public final class CatalogTopAppBarKt {
method @androidx.compose.runtime.Composable public static void CatalogTopAppBar(optional com.google.android.catalog.framework.base.CatalogSample? selectedSample, optional kotlin.jvm.functions.Function0<kotlin.Unit> onSearch, optional kotlin.jvm.functions.Function0<kotlin.Unit> onBackClick);
}

public final class FilterTabRowKt {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@
package com.google.android.catalog.framework.ui

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.fragment.app.FragmentManager
Expand All @@ -29,6 +34,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.catalog.framework.base.CatalogSample
import com.google.android.catalog.framework.base.CatalogTarget
import com.google.android.catalog.framework.ui.components.CatalogTopAppBar
import com.google.android.catalog.framework.ui.components.FragmentContainer

private const val HOME_DESTINATION = "home"
Expand All @@ -50,28 +56,39 @@ fun CatalogNavigation(samples: Set<CatalogSample>, fragmentManager: FragmentMana

// Add all the samples
samples.forEach { sample ->
addTargets(sample, fragmentManager)
addTargets(sample, fragmentManager) {
navController.popBackStack()
}
}
}
}

private fun NavGraphBuilder.addTargets(sample: CatalogSample, fragmentManager: FragmentManager) {
@OptIn(ExperimentalMaterial3Api::class)
private fun NavGraphBuilder.addTargets(
sample: CatalogSample,
fragmentManager: FragmentManager,
onBackClick: () -> Unit
) {
when (val target = sample.target) {
is CatalogTarget.TargetComposable -> {
composable(sample.name) {
target.composable()
SampleScaffold(sample = sample, onBackClick = onBackClick) {
target.composable()
}
}
}

is CatalogTarget.TargetFragment -> {
composable(sample.name) {
FragmentContainer(
modifier = Modifier.fillMaxSize(),
fragmentManager = fragmentManager,
commit = { id ->
add(id, target.targetClass.java.newInstance())
}
)
SampleScaffold(sample = sample, onBackClick = onBackClick) {
FragmentContainer(
modifier = Modifier.fillMaxSize(),
fragmentManager = fragmentManager,
commit = { id ->
add(id, target.targetClass.java.newInstance())
}
)
}
}
}

Expand All @@ -83,3 +100,19 @@ private fun NavGraphBuilder.addTargets(sample: CatalogSample, fragmentManager: F
}
}
}

@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun SampleScaffold(
sample: CatalogSample,
onBackClick: () -> Unit,
content: @Composable BoxScope.() -> Unit
) {
Scaffold(topBar = {
CatalogTopAppBar(
selectedSample = sample, onBackClick = onBackClick
)
}) {
Box(modifier = Modifier.padding(it), content = content)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,8 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
Expand All @@ -37,11 +31,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.google.android.catalog.framework.base.CatalogSample
import com.google.android.catalog.framework.ui.components.CardItem
import com.google.android.catalog.framework.ui.components.CatalogTopAppBar
import com.google.android.catalog.framework.ui.components.FilterTabRow
import com.google.android.catalog.framework.ui.components.SearchTopAppBar

Expand Down Expand Up @@ -88,23 +81,7 @@ internal fun CatalogScreen(
}
)
} else {
TopAppBar(
title = {
Text(
text = stringResource(id = R.string.app_name),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
actions = {
IconButton(onClick = { searchState = !searchState }) {
Icon(
imageVector = Icons.Rounded.Search,
contentDescription = "Search button"
)
}
},
)
CatalogTopAppBar(onSearch = { searchState = true })
}
}
) { paddingValues ->
Expand Down
Loading