├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── compiler.xml ├── kotlinc.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── ktlint.xml ├── misc.xml ├── gradle.xml └── jarRepositories.xml ├── docs ├── static │ ├── .nojekyll │ └── img │ │ ├── ogp.png │ │ ├── favicon.ico │ │ ├── docs │ │ ├── group.png │ │ ├── start.png │ │ ├── binding.png │ │ ├── fragment.png │ │ ├── android-theme.png │ │ ├── android-view.png │ │ ├── binding-match.png │ │ ├── binding-size.png │ │ ├── compose-theme.png │ │ ├── fragment-update.png │ │ ├── jetpack-compose.png │ │ ├── android-view-match.png │ │ ├── android-view-size.png │ │ ├── android-view-livedata.gif │ │ └── jetpack-compose-state.gif │ │ ├── header-img.png │ │ ├── tutorial │ │ ├── localeDropdown.png │ │ └── docsVersionDropdown.png │ │ ├── logo.svg │ │ └── logo-clip.svg ├── babel.config.js ├── docs │ ├── main │ │ ├── _category_.json │ │ ├── extensions │ │ │ ├── _category_.json │ │ │ ├── page-saver.mdx │ │ │ ├── compose-theme.mdx │ │ │ └── android-theme.mdx │ │ └── jetpack-compose.mdx │ └── getting-started.md ├── .gitignore ├── src │ ├── widgets │ │ └── Preview │ │ │ ├── index.module.css │ │ │ └── index.tsx │ ├── pages │ │ ├── header.module.css │ │ └── Header.js │ └── css │ │ └── custom.css ├── sidebars.js ├── README.md ├── package.json └── docusaurus.config.js ├── katalog ├── .gitignore ├── consumer-rules.pro ├── src │ ├── test │ │ ├── resources │ │ │ └── robolectric.properties │ │ └── kotlin │ │ │ └── com │ │ │ └── moriatsushi │ │ │ └── katalog │ │ │ ├── domain │ │ │ └── DummyKatalog.kt │ │ │ └── compose │ │ │ └── DummyKatalogViewModel.kt │ └── main │ │ ├── kotlin │ │ ├── com │ │ │ └── moriatsushi │ │ │ │ └── katalog │ │ │ │ ├── dsl │ │ │ │ ├── Group.kt │ │ │ │ ├── GroupScope.kt │ │ │ │ ├── Types.kt │ │ │ │ └── ViewDefinitionScope.kt │ │ │ │ ├── compose │ │ │ │ ├── util │ │ │ │ │ ├── Urls.kt │ │ │ │ │ ├── Browser.kt │ │ │ │ │ └── LazyListStateExt.kt │ │ │ │ ├── res │ │ │ │ │ ├── Dimens.kt │ │ │ │ │ └── Colors.kt │ │ │ │ ├── navigation │ │ │ │ │ ├── NavDestination.kt │ │ │ │ │ ├── DiscoveryDestination.kt │ │ │ │ │ ├── MainDestination.kt │ │ │ │ │ ├── NavState.kt │ │ │ │ │ ├── ExtNavStateImpl.kt │ │ │ │ │ ├── NavController.kt │ │ │ │ │ └── NavRoot.kt │ │ │ │ ├── widget │ │ │ │ │ ├── ClickMask.kt │ │ │ │ │ ├── CatalogItem.kt │ │ │ │ │ ├── Dissolve.kt │ │ │ │ │ ├── Empty.kt │ │ │ │ │ ├── KatalogTopAppBar.kt │ │ │ │ │ ├── ErrorMessage.kt │ │ │ │ │ └── CatalogItemList.kt │ │ │ │ ├── page │ │ │ │ │ ├── TopPage.kt │ │ │ │ │ ├── GroupPage.kt │ │ │ │ │ ├── MainPage.kt │ │ │ │ │ └── PreviewPage.kt │ │ │ │ └── KatalogViewModel.kt │ │ │ │ ├── ext │ │ │ │ ├── ExtWrapperScope.kt │ │ │ │ ├── ExtensionBuilder.kt │ │ │ │ ├── ExtNavState.kt │ │ │ │ ├── Types.kt │ │ │ │ └── KatalogExt.kt │ │ │ │ ├── util │ │ │ │ └── UUIDWrapper.kt │ │ │ │ ├── domain │ │ │ │ ├── Errors.kt │ │ │ │ ├── ExtWrapperScopeImpl.kt │ │ │ │ ├── KatalogExtImpl.kt │ │ │ │ ├── KatalogDefinition.kt │ │ │ │ ├── Extensions.kt │ │ │ │ ├── CatalogItem.kt │ │ │ │ ├── KatalogContainer.kt │ │ │ │ ├── ExtensionBuilderImpl.kt │ │ │ │ ├── Katalog.kt │ │ │ │ └── GroupScopeImpl.kt │ │ │ │ ├── KatalogActivity.kt │ │ │ │ └── Entry.kt │ │ └── jp │ │ │ └── co │ │ │ └── cyberagent │ │ │ └── katalog │ │ │ ├── dsl │ │ │ ├── Group.kt │ │ │ ├── GroupScope.kt │ │ │ ├── ViewDefinitionScope.kt │ │ │ └── Types.kt │ │ │ ├── ext │ │ │ ├── KatalogExt.kt │ │ │ ├── ExtNavState.kt │ │ │ ├── ExtWrapperScope.kt │ │ │ ├── ExtensionBuilder.kt │ │ │ └── Types.kt │ │ │ ├── KatalogActivity.kt │ │ │ └── Entry.kt │ │ └── AndroidManifest.xml ├── gradle.properties ├── proguard-rules.pro └── build.gradle.kts ├── extensions ├── pagesaver │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── test │ │ │ ├── resources │ │ │ │ └── robolectric.properties │ │ │ └── kotlin │ │ │ │ └── com │ │ │ │ └── moriatsushi │ │ │ │ └── katalog │ │ │ │ └── ext │ │ │ │ └── pagesaver │ │ │ │ └── internal │ │ │ │ ├── DummyLocalStorage.kt │ │ │ │ ├── DummyExtNavState.kt │ │ │ │ └── PageSaverTest.kt │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ ├── com │ │ │ └── moriatsushi │ │ │ │ └── katalog │ │ │ │ └── ext │ │ │ │ └── pagesaver │ │ │ │ ├── PageSaverExt.kt │ │ │ │ └── internal │ │ │ │ ├── BackStackMapper.kt │ │ │ │ ├── PageSaverExt.kt │ │ │ │ ├── PageStore.kt │ │ │ │ ├── LocalStorage.kt │ │ │ │ └── PageSaver.kt │ │ │ └── jp │ │ │ └── co │ │ │ └── cyberagent │ │ │ └── katalog │ │ │ └── ext │ │ │ └── pagesaver │ │ │ └── PageSaverExt.kt │ ├── gradle.properties │ ├── proguard-rules.pro │ └── build.gradle.kts ├── theme │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── kotlin │ │ │ ├── com │ │ │ └── moriatsushi │ │ │ │ └── katalog │ │ │ │ └── ext │ │ │ │ └── theme │ │ │ │ ├── ThemeDefinition.kt │ │ │ │ ├── ThemeExt.kt │ │ │ │ └── internal │ │ │ │ └── ThemeExt.kt │ │ │ └── jp │ │ │ └── co │ │ │ └── cyberagent │ │ │ └── katalog │ │ │ └── ext │ │ │ └── theme │ │ │ ├── ThemeDefinition.kt │ │ │ └── ThemeExt.kt │ ├── gradle.properties │ ├── proguard-rules.pro │ └── build.gradle.kts └── androidtheme │ ├── consumer-rules.pro │ ├── .gitignore │ ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ ├── com │ │ └── moriatsushi │ │ │ └── katalog │ │ │ └── ext │ │ │ └── androidtheme │ │ │ ├── AndroidThemeExt.kt │ │ │ └── internal │ │ │ ├── AndroidThemeExt.kt │ │ │ ├── ContextTheme.kt │ │ │ └── Background.kt │ │ └── jp │ │ └── co │ │ └── cyberagent │ │ └── katalog │ │ └── ext │ │ └── androidtheme │ │ └── AndroidThemeExt.kt │ ├── gradle.properties │ ├── proguard-rules.pro │ └── build.gradle.kts ├── katalog-androidview ├── consumer-rules.pro ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── kotlin │ │ ├── com │ │ └── moriatsushi │ │ │ └── katalog │ │ │ ├── androidview │ │ │ ├── util │ │ │ │ ├── ContextExt.kt │ │ │ │ └── LifecycleOwner.kt │ │ │ └── mapper │ │ │ │ ├── ViewToCompose.kt │ │ │ │ ├── BindingToCompose.kt │ │ │ │ ├── ViewDefinitionScope.kt │ │ │ │ └── FragmentToCompose.kt │ │ │ └── dsl │ │ │ ├── BindingExt.kt │ │ │ ├── FragmentExt.kt │ │ │ └── ViewExt.kt │ │ └── jp │ │ └── co │ │ └── cyberagent │ │ └── katalog │ │ └── dsl │ │ ├── FragmentExt.kt │ │ ├── BindingExt.kt │ │ └── ViewExt.kt ├── gradle.properties ├── proguard-rules.pro └── build.gradle.kts ├── samples └── androidapp │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ └── main │ │ ├── ic_launcher-playstore.png │ │ ├── res │ │ ├── values │ │ │ ├── ic_launcher_background.xml │ │ │ └── type.xml │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable │ │ │ ├── ic_add_24.xml │ │ │ ├── ic_menu_24.xml │ │ │ ├── ic_star_24.xml │ │ │ ├── ic_favorite_24.xml │ │ │ ├── ic_search_24.xml │ │ │ └── ic_launcher_foreground.xml │ │ ├── menu │ │ │ ├── bottom_app_bar.xml │ │ │ ├── bottom_navigation_menu.xml │ │ │ └── top_app_bar.xml │ │ └── layout │ │ │ ├── material_app_bars_top.xml │ │ │ ├── material_app_bars_bottom.xml │ │ │ ├── material_app_bars_top_prominent.xml │ │ │ └── fragment_sample.xml │ │ ├── kotlin │ │ └── com │ │ │ └── moriatsushi │ │ │ └── katalog │ │ │ └── androidsample │ │ │ ├── compose │ │ │ └── material │ │ │ │ ├── Text.kt │ │ │ │ ├── Card.kt │ │ │ │ ├── TextButton.kt │ │ │ │ ├── OutlinedButton.kt │ │ │ │ ├── Icon.kt │ │ │ │ ├── Surface.kt │ │ │ │ ├── IconButton.kt │ │ │ │ ├── FloatingActionButton.kt │ │ │ │ ├── Switch.kt │ │ │ │ ├── Checkbox.kt │ │ │ │ ├── OutlinedTextField.kt │ │ │ │ ├── IconToggleButton.kt │ │ │ │ ├── Button.kt │ │ │ │ ├── BottomAppBar.kt │ │ │ │ ├── ExtendedFloatingActionButton.kt │ │ │ │ ├── BottomNavigation.kt │ │ │ │ ├── TopAppBar.kt │ │ │ │ ├── TabRow.kt │ │ │ │ ├── Slider.kt │ │ │ │ ├── LinearProgressIndicator.kt │ │ │ │ ├── CircularProgressIndicator.kt │ │ │ │ ├── DropdownMenu.kt │ │ │ │ ├── TriStateCheckbox.kt │ │ │ │ ├── Scaffold.kt │ │ │ │ └── RadioButton.kt │ │ │ ├── fragment │ │ │ ├── FragmentGroup.kt │ │ │ └── SampleFragment.kt │ │ │ ├── SampleTheme.kt │ │ │ ├── view │ │ │ └── material │ │ │ │ ├── BottomNavigation.kt │ │ │ │ └── ViewMaterialGroup.kt │ │ │ └── SampleApp.kt │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── renovate.json ├── .editorconfig ├── .github ├── ci-gradle.properties ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── test_docs.yml │ ├── deploy_docs.yml │ └── build.yml ├── .gitignore ├── settings.gradle.kts └── gradle.properties /.idea/.name: -------------------------------------------------------------------------------- 1 | Katalog -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /katalog/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /katalog/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/pagesaver/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /extensions/theme/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /extensions/theme/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /katalog-androidview/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/androidapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /samples/androidapp/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/androidtheme/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/pagesaver/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /katalog-androidview/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /extensions/androidtheme/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /katalog/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | instrumentedPackages=androidx.loader.content 2 | -------------------------------------------------------------------------------- /docs/static/img/ogp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/ogp.png -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /extensions/pagesaver/src/test/resources/robolectric.properties: -------------------------------------------------------------------------------- 1 | instrumentedPackages=androidx.loader.content 2 | -------------------------------------------------------------------------------- /docs/static/img/docs/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/group.png -------------------------------------------------------------------------------- /docs/static/img/docs/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/start.png -------------------------------------------------------------------------------- /docs/static/img/header-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/header-img.png -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/main/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Documantation", 3 | "position": 2, 4 | "collapsed": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/static/img/docs/binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/binding.png -------------------------------------------------------------------------------- /docs/static/img/docs/fragment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/fragment.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/docs/main/extensions/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Extensions", 3 | "position": 7, 4 | "collapsed": true 5 | } 6 | -------------------------------------------------------------------------------- /docs/static/img/docs/android-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/android-theme.png -------------------------------------------------------------------------------- /docs/static/img/docs/android-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/android-view.png -------------------------------------------------------------------------------- /docs/static/img/docs/binding-match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/binding-match.png -------------------------------------------------------------------------------- /docs/static/img/docs/binding-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/binding-size.png -------------------------------------------------------------------------------- /docs/static/img/docs/compose-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/compose-theme.png -------------------------------------------------------------------------------- /docs/static/img/docs/fragment-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/fragment-update.png -------------------------------------------------------------------------------- /docs/static/img/docs/jetpack-compose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/jetpack-compose.png -------------------------------------------------------------------------------- /docs/static/img/docs/android-view-match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/android-view-match.png -------------------------------------------------------------------------------- /docs/static/img/docs/android-view-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/android-view-size.png -------------------------------------------------------------------------------- /docs/static/img/tutorial/localeDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/tutorial/localeDropdown.png -------------------------------------------------------------------------------- /docs/static/img/docs/android-view-livedata.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/android-view-livedata.gif -------------------------------------------------------------------------------- /docs/static/img/docs/jetpack-compose-state.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/docs/jetpack-compose-state.gif -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /docs/static/img/tutorial/docsVersionDropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/docs/static/img/tutorial/docsVersionDropdown.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | indent_style = space 8 | -------------------------------------------------------------------------------- /katalog/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=katalog 2 | POM_NAME=katalog 3 | POM_DESCRIPTION=A UI Catalog Library made with Jetpack Compose 4 | POM_PACKAGING=aar 5 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mori-atsushi/katalog/HEAD/samples/androidapp/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /extensions/theme/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /extensions/androidtheme/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /extensions/theme/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=katalog-ext-theme 2 | POM_NAME=katalog-ext-theme 3 | POM_DESCRIPTION=A UI Catalog Library made with Jetpack Compose 4 | POM_PACKAGING=aar 5 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /katalog-androidview/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=katalog-androidview 2 | POM_NAME=katalog-androidview 3 | POM_DESCRIPTION=A UI Catalog Library made with Jetpack Compose 4 | POM_PACKAGING=aar 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /extensions/pagesaver/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=katalog-ext-pagesaver 2 | POM_NAME=katalog-ext-pagesaver 3 | POM_DESCRIPTION=A UI Catalog Library made with Jetpack Compose 4 | POM_PACKAGING=aar 5 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /extensions/androidtheme/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_ARTIFACT_ID=katalog-ext-androidtheme 2 | POM_NAME=katalog-ext-androidtheme 3 | POM_DESCRIPTION=A UI Catalog Library made with Jetpack Compose 4 | POM_PACKAGING=aar 5 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/dsl/Group.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | public class Group( 4 | internal val name: String, 5 | internal val definition: GroupDefinition, 6 | ) 7 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/util/Urls.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.util 2 | 3 | internal object Urls { 4 | const val documents = "https://mori-atsushi.github.io/katalog/" 5 | } 6 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/ext/ExtWrapperScope.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext 2 | 3 | @ExperimentalKatalogExtApi 4 | public interface ExtWrapperScope { 5 | public val navState: ExtNavState 6 | } 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/res/Dimens.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.res 2 | 3 | import androidx.compose.ui.unit.dp 4 | 5 | internal val defaultPadding = 16.dp 6 | internal val defaultCornerRadius = 10.dp 7 | -------------------------------------------------------------------------------- /extensions/theme/src/main/kotlin/com/moriatsushi/katalog/ext/theme/ThemeDefinition.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.theme 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | public typealias ThemeDefinition = @Composable (@Composable () -> Unit) -> Unit 6 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/util/UUIDWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.util 2 | 3 | import java.util.UUID 4 | 5 | internal object UUIDWrapper { 6 | fun getString(): String { 7 | return UUID.randomUUID().toString() 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/ci-gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=false 2 | org.gradle.parallel=true 3 | org.gradle.workers.max=2 4 | org.gradle.jvmargs=-Xmx2g 5 | 6 | # kotlin 7 | kotlin.compiler.execution.strategy=in-process 8 | kotlin.native.ignoreDisabledTargets=true 9 | 10 | # other 11 | warningsAsErrors=true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | build/ 12 | captures/ 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/NavDestination.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | @Stable 6 | internal interface NavDestination { 7 | val childNavController: NavController<*>? get() = null 8 | } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/Errors.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | internal class NotRegisteredException : 4 | IllegalStateException("require call registerKatalog") 5 | 6 | internal class AlreadyRegisteredException : 7 | IllegalStateException("Cannot call registerKatalog more than once.") 8 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/dsl/Group.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "Group", 7 | "com.moriatsushi.katalog.dsl.Group", 8 | ), 9 | ) 10 | public typealias Group = com.moriatsushi.katalog.dsl.Group 11 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Text.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Text 4 | import androidx.compose.runtime.Composable 5 | 6 | @Composable 7 | fun SampleText() { 8 | Text("Sample Text") 9 | } 10 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/dsl/GroupScope.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | @CatalogDslMarker 4 | public interface GroupScope { 5 | public fun group(name: String, definition: GroupDefinition) 6 | public fun group(vararg group: Group) 7 | public fun compose(name: String, definition: ComposeDefinition) 8 | } 9 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/dsl/Types.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | public typealias GroupDefinition = GroupScope.() -> Unit 6 | public typealias ComposeDefinition = @Composable () -> Unit 7 | 8 | @DslMarker 9 | internal annotation class CatalogDslMarker 10 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/dsl/GroupScope.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "GroupScope", 7 | "com.moriatsushi.katalog.dsl.GroupScope", 8 | ), 9 | ) 10 | public typealias GroupScope = com.moriatsushi.katalog.dsl.GroupScope 11 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/ext/KatalogExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "KatalogExt", 7 | "com.moriatsushi.katalog.ext.KatalogExt", 8 | ), 9 | ) 10 | public typealias KatalogExt = com.moriatsushi.katalog.ext.KatalogExt 11 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/fragment/FragmentGroup.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.fragment 2 | 3 | import com.moriatsushi.katalog.dsl.fragment 4 | import com.moriatsushi.katalog.group 5 | 6 | val fragmentGroup = group("Fragment") { 7 | fragment { 8 | SampleFragment() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /extensions/theme/src/main/kotlin/com/moriatsushi/katalog/ext/theme/ThemeExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.theme 2 | 3 | import com.moriatsushi.katalog.ext.KatalogExt 4 | import com.moriatsushi.katalog.ext.theme.internal.createThemeExt 5 | 6 | @Suppress("FunctionName") 7 | public fun ThemeExt(theme: ThemeDefinition): KatalogExt { 8 | return createThemeExt(theme) 9 | } 10 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/ext/ExtensionBuilder.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext 2 | 3 | @ExperimentalKatalogExtApi 4 | public interface ExtensionBuilder { 5 | public fun setComponentWrapper(wrapper: ExtComponentWrapper): ExtensionBuilder 6 | public fun setRootWrapper(wrapper: ExtRootWrapper): ExtensionBuilder 7 | public fun build(): KatalogExt 8 | } 9 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/com/moriatsushi/katalog/ext/pagesaver/PageSaverExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver 2 | 3 | import com.moriatsushi.katalog.ext.KatalogExt 4 | import com.moriatsushi.katalog.ext.pagesaver.internal.createPageSaverExt 5 | 6 | @Suppress("FunctionName") 7 | public fun PageSaverExt(): KatalogExt { 8 | return createPageSaverExt() 9 | } 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/ext/ExtNavState.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "ExtNavState", 7 | "com.moriatsushi.katalog.ext.ExtNavState", 8 | ), 9 | ) 10 | @ExperimentalKatalogExtApi 11 | public typealias ExtNavState = com.moriatsushi.katalog.ext.ExtNavState 12 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/dsl/ViewDefinitionScope.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "ViewDefinitionScope", 7 | "com.moriatsushi.katalog.dsl.ViewDefinitionScope", 8 | ), 9 | ) 10 | public typealias ViewDefinitionScope = com.moriatsushi.katalog.dsl.ViewDefinitionScope 11 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/ktlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/dsl/ViewDefinitionScope.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import androidx.lifecycle.LifecycleOwner 6 | 7 | public interface ViewDefinitionScope { 8 | public val context: Context 9 | public val activity: Activity 10 | public val lifecycleOwner: LifecycleOwner 11 | } 12 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Card.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Card 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun SampleCard() { 9 | Card { 10 | Text("Card Content") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/util/Browser.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | 7 | internal fun openBrowser(context: Context, url: String) { 8 | val uri = Uri.parse(url) 9 | val intent = Intent(Intent.ACTION_VIEW, uri) 10 | context.startActivity(intent) 11 | } 12 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/ext/ExtWrapperScope.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "ExtWrapperScope", 7 | "com.moriatsushi.katalog.ext.ExtWrapperScope", 8 | ), 9 | ) 10 | @ExperimentalKatalogExtApi 11 | public typealias ExtWrapperScope = com.moriatsushi.katalog.ext.ExtWrapperScope 12 | -------------------------------------------------------------------------------- /extensions/theme/src/main/kotlin/jp/co/cyberagent/katalog/ext/theme/ThemeDefinition.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext.theme 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "ThemeDefinition", 7 | "com.moriatsushi.katalog.ext.theme.ThemeDefinition", 8 | ), 9 | ) 10 | public typealias ThemeDefinition = 11 | com.moriatsushi.katalog.ext.theme.ThemeDefinition 12 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/ext/ExtensionBuilder.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "ExtensionBuilder", 7 | "com.moriatsushi.katalog.ext.ExtensionBuilder", 8 | ), 9 | ) 10 | @ExperimentalKatalogExtApi 11 | public typealias ExtensionBuilder = com.moriatsushi.katalog.ext.ExtensionBuilder 12 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/DiscoveryDestination.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import com.moriatsushi.katalog.domain.CatalogItem 4 | 5 | internal sealed class DiscoveryDestination : NavDestination { 6 | object Top : DiscoveryDestination() 7 | 8 | data class Group( 9 | val group: CatalogItem.Group, 10 | ) : DiscoveryDestination() 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/widgets/Preview/index.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | flex-wrap: wrap; 4 | align-items: flex-start; 5 | gap: 10px; 6 | margin-bottom: 20px; 7 | } 8 | 9 | .code { 10 | flex-grow: 1; 11 | flex-shrink: 0; 12 | width: 320px; 13 | } 14 | 15 | .img { 16 | flex-grow: 0; 17 | width: 320px; 18 | border: 1px solid black; 19 | height: auto; 20 | display: block; 21 | } 22 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/ext/ExtNavState.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | @ExperimentalKatalogExtApi 6 | @Stable 7 | public interface ExtNavState { 8 | public val current: String 9 | public val backStack: List 10 | 11 | public fun navigateTo(destination: String): Boolean 12 | public fun restore(backStack: List): Boolean 13 | } 14 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/drawable/ic_add_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/ExtWrapperScopeImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 4 | import com.moriatsushi.katalog.ext.ExtNavState 5 | import com.moriatsushi.katalog.ext.ExtWrapperScope 6 | 7 | @OptIn(ExperimentalKatalogExtApi::class) 8 | internal class ExtWrapperScopeImpl( 9 | override val navState: ExtNavState, 10 | ) : ExtWrapperScope 11 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/TextButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Text 4 | import androidx.compose.material.TextButton 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun SampleTextButton() { 9 | TextButton(onClick = { /* Do something! */ }) { 10 | Text("Text Button") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/drawable/ic_menu_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /katalog/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea or an feature 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Background 10 | Please describe your current problems or what want to do. 11 | If you have tried an alternative, pease describe as well. 12 | 13 | ## Solution 14 | Please describe the solution you'd like. 15 | A clear and concise description of what you want, using codes or images as necessary. 16 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/OutlinedButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.OutlinedButton 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun SampleOutlinedButton() { 9 | OutlinedButton(onClick = { /* Do something! */ }) { 10 | Text("Outlined Button") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/widgets/Preview/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.module.css'; 3 | 4 | export const Preview = ({children, imageUrl, width, height}) => ( 5 |
6 |
7 | {children} 8 |
9 | 16 |
17 | ); 18 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/drawable/ic_star_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /extensions/androidtheme/src/main/kotlin/com/moriatsushi/katalog/ext/androidtheme/AndroidThemeExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.androidtheme 2 | 3 | import androidx.annotation.StyleRes 4 | import com.moriatsushi.katalog.ext.KatalogExt 5 | import com.moriatsushi.katalog.ext.androidtheme.internal.createAndroidThemeExt 6 | 7 | @Suppress("FunctionName") 8 | public fun AndroidThemeExt( 9 | @StyleRes themeResId: Int, 10 | ): KatalogExt { 11 | return createAndroidThemeExt(themeResId) 12 | } 13 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/test/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/DummyLocalStorage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | internal class DummyLocalStorage( 4 | private val map: MutableMap = mutableMapOf(), 5 | ) : LocalStorage { 6 | override fun putString(key: String, value: String?) { 7 | map[key] = value 8 | } 9 | 10 | override fun getString(key: String): String? { 11 | return map[key] as? String 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Icon.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Icon 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.Call 6 | import androidx.compose.runtime.Composable 7 | 8 | @Composable 9 | fun SampleIcon() { 10 | Icon( 11 | imageVector = Icons.Default.Call, 12 | contentDescription = "call", 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/MainDestination.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import com.moriatsushi.katalog.domain.CatalogItem 4 | 5 | internal sealed class MainDestination : NavDestination { 6 | data class Discovery( 7 | override val childNavController: NavController, 8 | ) : MainDestination() 9 | 10 | data class Preview( 11 | val component: CatalogItem.Component, 12 | ) : MainDestination() 13 | } 14 | -------------------------------------------------------------------------------- /extensions/theme/src/main/kotlin/jp/co/cyberagent/katalog/ext/theme/ThemeExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext.theme 2 | 3 | import jp.co.cyberagent.katalog.ext.KatalogExt 4 | 5 | @Deprecated( 6 | "The package name has changed.", 7 | ReplaceWith( 8 | "ThemeExt(theme)", 9 | "com.moriatsushi.katalog.ext.theme.ThemeExt", 10 | ), 11 | ) 12 | @Suppress("FunctionName") 13 | public fun ThemeExt(theme: ThemeDefinition): KatalogExt { 14 | return com.moriatsushi.katalog.ext.theme.ThemeExt(theme) 15 | } 16 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/jp/co/cyberagent/katalog/ext/pagesaver/PageSaverExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext.pagesaver 2 | 3 | import jp.co.cyberagent.katalog.ext.KatalogExt 4 | 5 | @Deprecated( 6 | "The package name has changed.", 7 | ReplaceWith( 8 | "PageSaverExt()", 9 | "com.moriatsushi.katalog.ext.pagesaver.PageSaverExt", 10 | ), 11 | ) 12 | @Suppress("FunctionName") 13 | public fun PageSaverExt(): KatalogExt { 14 | return com.moriatsushi.katalog.ext.pagesaver.PageSaverExt() 15 | } 16 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Surface.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.Surface 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | 8 | @Composable 9 | fun SampleSurface() { 10 | Surface( 11 | color = MaterialTheme.colors.background, 12 | ) { 13 | Text("Text color is `onBackground`") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/values/type.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bag or unexpected behavior. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | --- 8 | 9 | ## What happened? 10 | A clear and concise description of what the bug is. 11 | 12 | ## To Reproduce Steps 13 | Steps to reproduce the behavior: 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | Please attach screenshots or logs if you have. 20 | 21 | ## Expected behavior 22 | A clear and concise description of what you expected to happen. 23 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/KatalogActivity.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.moriatsushi.katalog.compose.App 7 | 8 | public class KatalogActivity : AppCompatActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | setContent { 12 | App( 13 | window = window, 14 | ) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/KatalogExtImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 4 | import com.moriatsushi.katalog.ext.ExtComponentWrapper 5 | import com.moriatsushi.katalog.ext.ExtRootWrapper 6 | import com.moriatsushi.katalog.ext.KatalogExt 7 | 8 | @ExperimentalKatalogExtApi 9 | internal class KatalogExtImpl( 10 | override val name: String, 11 | override val componentWrapper: ExtComponentWrapper?, 12 | override val rootWrapper: ExtRootWrapper?, 13 | ) : KatalogExt 14 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/BackStackMapper.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import kotlinx.serialization.decodeFromString 4 | import kotlinx.serialization.encodeToString 5 | import kotlinx.serialization.json.Json 6 | 7 | internal object BackStackMapper { 8 | fun toString(backStack: List): String { 9 | return Json.encodeToString(backStack) 10 | } 11 | 12 | fun fromString(value: String): List { 13 | return Json.decodeFromString(value) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/ClickMask.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.Modifier 6 | import androidx.compose.ui.input.pointer.pointerInput 7 | 8 | @Composable 9 | internal fun ClickMask( 10 | modifier: Modifier = Modifier, 11 | enabled: Boolean = true, 12 | ) { 13 | if (!enabled) return 14 | Box( 15 | modifier = modifier 16 | .pointerInput(Unit) { }, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/drawable/ic_favorite_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | rootProject.name = "Katalog" 18 | include(":katalog") 19 | include(":katalog-androidview") 20 | include(":extensions:theme") 21 | include(":extensions:androidtheme") 22 | include(":extensions:pagesaver") 23 | include(":samples:androidapp") 24 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/IconButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Icon 4 | import androidx.compose.material.IconButton 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.filled.Favorite 7 | import androidx.compose.runtime.Composable 8 | 9 | @Composable 10 | fun SampleIconButton() { 11 | IconButton(onClick = { /* doSomething() */ }) { 12 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/PageSaverExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 4 | import com.moriatsushi.katalog.ext.KatalogExt 5 | 6 | @OptIn(ExperimentalKatalogExtApi::class) 7 | internal fun createPageSaverExt(): KatalogExt { 8 | val builder = KatalogExt.Builder("PageSaver") 9 | builder.setRootWrapper { content -> 10 | PageSaver( 11 | navState = navState, 12 | content = content, 13 | ) 14 | } 15 | return builder.build() 16 | } 17 | -------------------------------------------------------------------------------- /katalog/src/test/kotlin/com/moriatsushi/katalog/domain/DummyKatalog.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.dsl.GroupDefinition 4 | import com.moriatsushi.katalog.ext.KatalogExt 5 | 6 | internal fun dummyKatalog( 7 | title: String = "", 8 | extensions: List = emptyList(), 9 | groupDefinition: GroupDefinition = {}, 10 | ): Katalog { 11 | val container = KatalogContainer() 12 | container.register( 13 | title = title, 14 | extensions = extensions, 15 | groupDefinition = groupDefinition, 16 | ) 17 | return container.create() 18 | } 19 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/util/LazyListStateExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.util 2 | 3 | import androidx.compose.foundation.lazy.LazyListState 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.State 6 | import androidx.compose.runtime.derivedStateOf 7 | import androidx.compose.runtime.remember 8 | 9 | @Composable 10 | internal fun LazyListState.rememberIsTop(): State { 11 | return remember { 12 | derivedStateOf { 13 | this.firstVisibleItemIndex == 0 && 14 | this.firstVisibleItemScrollOffset == 0 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/dsl/Types.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "GroupDefinition", 7 | "com.moriatsushi.katalog.dsl.GroupDefinition", 8 | ), 9 | ) 10 | public typealias GroupDefinition = com.moriatsushi.katalog.dsl.GroupDefinition 11 | 12 | @Deprecated( 13 | "The package name has changed.", 14 | ReplaceWith( 15 | "ComposeDefinition", 16 | "com.moriatsushi.katalog.dsl.ComposeDefinition", 17 | ), 18 | ) 19 | public typealias ComposeDefinition = com.moriatsushi.katalog.dsl.ComposeDefinition 20 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/drawable/ic_search_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/FloatingActionButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.FloatingActionButton 4 | import androidx.compose.material.Icon 5 | import androidx.compose.material.icons.Icons 6 | import androidx.compose.material.icons.filled.Favorite 7 | import androidx.compose.runtime.Composable 8 | 9 | @Composable 10 | fun SampleFloatingActionButton() { 11 | FloatingActionButton(onClick = { /*do something*/ }) { 12 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /extensions/androidtheme/src/main/kotlin/jp/co/cyberagent/katalog/ext/androidtheme/AndroidThemeExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext.androidtheme 2 | 3 | import androidx.annotation.StyleRes 4 | import jp.co.cyberagent.katalog.ext.KatalogExt 5 | 6 | @Deprecated( 7 | "The package name has changed.", 8 | ReplaceWith( 9 | "AndroidThemeExt(themeResId)", 10 | "com.moriatsushi.katalog.ext.androidtheme.AndroidThemeExt", 11 | ), 12 | ) 13 | @Suppress("FunctionName") 14 | public fun AndroidThemeExt( 15 | @StyleRes themeResId: Int, 16 | ): KatalogExt { 17 | return com.moriatsushi.katalog.ext.androidtheme.AndroidThemeExt(themeResId) 18 | } 19 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/SampleTheme.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | @Composable 10 | fun SampleTheme( 11 | content: @Composable () -> Unit, 12 | ) { 13 | val darkTheme = isSystemInDarkTheme() 14 | MaterialTheme( 15 | colors = if (darkTheme) darkColors() else lightColors(), 16 | content = content, 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Switch.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Switch 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | 10 | @Composable 11 | fun SampleSwitch() { 12 | var checkedState by remember { mutableStateOf(true) } 13 | Switch( 14 | checked = checkedState, 15 | onCheckedChange = { checkedState = it }, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Checkbox.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Checkbox 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.setValue 9 | 10 | @Composable 11 | fun SampleCheckbox() { 12 | var checkedState by remember { mutableStateOf(true) } 13 | Checkbox( 14 | checked = checkedState, 15 | onCheckedChange = { checkedState = it }, 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /docs/src/pages/header.module.css: -------------------------------------------------------------------------------- 1 | .headerContainer { 2 | align-items: center; 3 | padding: 60px 0; 4 | } 5 | 6 | .headerTitle { 7 | margin: 0 0 60px; 8 | } 9 | 10 | .headerTitle h1 { 11 | font-size: 50px; 12 | } 13 | 14 | .headerTitle p { 15 | margin: 20px 0 60px; 16 | } 17 | 18 | .button { 19 | display: inline-block; 20 | background: var(--ifm-color-primary); 21 | border-radius: 1000px; 22 | border: none; 23 | width: 100%; 24 | font-size: 24px; 25 | color: #FFFFFF; 26 | transition: opacity 0.2s; 27 | } 28 | 29 | @media (min-width: 440px) { 30 | .button { 31 | width: auto; 32 | } 33 | } 34 | 35 | .button:hover { 36 | color: #FFFFFF; 37 | opacity: 0.8; 38 | } 39 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/KatalogDefinition.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.dsl.GroupDefinition 4 | import com.moriatsushi.katalog.ext.KatalogExt 5 | 6 | internal data class KatalogDefinition( 7 | val title: String, 8 | val extensions: List, 9 | val groupDefinition: GroupDefinition, 10 | ) { 11 | fun build(): Katalog { 12 | val groupScope = GroupScopeImpl() 13 | groupScope.groupDefinition() 14 | return Katalog( 15 | title = title, 16 | items = groupScope.items, 17 | extensions = Extensions(extensions), 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/test_docs.yml: -------------------------------------------------------------------------------- 1 | name: "test docs" 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - 'docs/**' 7 | 8 | jobs: 9 | documentation-test: 10 | name: Test deployment 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Setup Node 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: 18 19 | cache: yarn 20 | cache-dependency-path: docs/yarn.lock 21 | 22 | - name: Install dependencies 23 | run: yarn install --frozen-lockfile 24 | working-directory: ./docs 25 | 26 | - name: Build website 27 | run: yarn build 28 | working-directory: ./docs 29 | -------------------------------------------------------------------------------- /katalog/src/test/kotlin/com/moriatsushi/katalog/compose/DummyKatalogViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose 2 | 3 | import com.moriatsushi.katalog.domain.KatalogContainer 4 | import com.moriatsushi.katalog.dsl.GroupDefinition 5 | import com.moriatsushi.katalog.ext.KatalogExt 6 | 7 | internal fun dummyKatalogViewModel( 8 | title: String = "", 9 | extensions: List = emptyList(), 10 | groupDefinition: GroupDefinition = {}, 11 | ): KatalogViewModel { 12 | val container = KatalogContainer() 13 | container.register( 14 | title = title, 15 | extensions = extensions, 16 | groupDefinition = groupDefinition, 17 | ) 18 | return KatalogViewModel(container) 19 | } 20 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/menu/bottom_app_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 13 | 18 | 19 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/KatalogActivity.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog 2 | 3 | import android.os.Bundle 4 | import androidx.activity.compose.setContent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import com.moriatsushi.katalog.compose.App 7 | 8 | @Deprecated( 9 | "The package name has changed.", 10 | ReplaceWith("KatalogActivity", "com.moriatsushi.katalog.KatalogActivity"), 11 | ) 12 | public class KatalogActivity : AppCompatActivity() { 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | setContent { 16 | App( 17 | window = window, 18 | ) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | module.exports = { 13 | // By default, Docusaurus generates a sidebar from the docs folder structure 14 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 15 | 16 | // But you can create a sidebar manually 17 | /* 18 | tutorialSidebar: [ 19 | { 20 | type: 'category', 21 | label: 'Tutorial', 22 | items: ['hello'], 23 | }, 24 | ], 25 | */ 26 | }; 27 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/Entry.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog 2 | 3 | import com.moriatsushi.katalog.domain.KatalogContainer 4 | import com.moriatsushi.katalog.dsl.Group 5 | import com.moriatsushi.katalog.dsl.GroupDefinition 6 | import com.moriatsushi.katalog.ext.KatalogExt 7 | 8 | private const val DEFAULT_TITLE = "Katalog" 9 | 10 | public fun registerKatalog( 11 | title: String = DEFAULT_TITLE, 12 | extensions: List = emptyList(), 13 | groupDefinition: GroupDefinition, 14 | ) { 15 | KatalogContainer.instance.register(title, extensions, groupDefinition) 16 | } 17 | 18 | public fun group(name: String, definition: GroupDefinition): Group { 19 | return Group(name, definition) 20 | } 21 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import androidx.compose.runtime.Stable 4 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 5 | import com.moriatsushi.katalog.ext.ExtComponentWrapper 6 | import com.moriatsushi.katalog.ext.ExtRootWrapper 7 | import com.moriatsushi.katalog.ext.KatalogExt 8 | 9 | @OptIn(ExperimentalKatalogExtApi::class) 10 | @Stable 11 | internal data class Extensions( 12 | val list: List, 13 | ) { 14 | val rootWrappers: List = 15 | list.mapNotNull { it.rootWrapper } 16 | 17 | val componentWrappers: List = 18 | list.mapNotNull { it.componentWrapper } 19 | } 20 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/fragment/SampleFragment.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import com.moriatsushi.katalog.androidsample.databinding.FragmentSampleBinding 9 | 10 | class SampleFragment : Fragment() { 11 | override fun onCreateView( 12 | inflater: LayoutInflater, 13 | container: ViewGroup?, 14 | savedInstanceState: Bundle?, 15 | ): View { 16 | val binding = FragmentSampleBinding.inflate(inflater, container, false) 17 | return binding.root 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/androidview/util/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidview.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.ContextWrapper 6 | import androidx.fragment.app.FragmentActivity 7 | import androidx.fragment.app.FragmentManager 8 | 9 | internal inline fun Context.findActivity(): T? { 10 | var context = this 11 | while (context is ContextWrapper) { 12 | if (context is T) return context 13 | context = context.baseContext 14 | } 15 | return null 16 | } 17 | 18 | internal fun Context.findFragmentManager(): FragmentManager? { 19 | return findActivity()?.supportFragmentManager 20 | } 21 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/CatalogItem.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Stable 5 | 6 | @Stable 7 | internal sealed class CatalogItem { 8 | abstract val identifier: CatalogItemIdentifier 9 | 10 | data class Group( 11 | override val identifier: CatalogItemIdentifier, 12 | val items: List, 13 | ) : CatalogItem() 14 | 15 | data class Component( 16 | override val identifier: CatalogItemIdentifier, 17 | val definition: @Composable () -> Unit, 18 | ) : CatalogItem() 19 | 20 | val name: String 21 | get() = identifier.name 22 | 23 | val id: String 24 | get() = identifier.id 25 | } 26 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/ext/Types.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | @ExperimentalKatalogExtApi 6 | public typealias ExtComponentWrapper = 7 | @Composable ExtWrapperScope.(content: @Composable () -> Unit) -> Unit 8 | 9 | @ExperimentalKatalogExtApi 10 | public typealias ExtRootWrapper = 11 | @Composable ExtWrapperScope.(content: @Composable () -> Unit) -> Unit 12 | 13 | @RequiresOptIn(message = "This is an experimental katalog extension API.") 14 | @Target( 15 | AnnotationTarget.CLASS, 16 | AnnotationTarget.FUNCTION, 17 | AnnotationTarget.PROPERTY, 18 | AnnotationTarget.TYPEALIAS, 19 | ) 20 | @Retention(AnnotationRetention.BINARY) 21 | public annotation class ExperimentalKatalogExtApi 22 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/menu/bottom_navigation_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 15 | 20 | 21 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/NavState.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import androidx.compose.runtime.Immutable 4 | import com.moriatsushi.katalog.util.UUIDWrapper 5 | 6 | @Immutable 7 | internal data class NavState( 8 | val destination: T, 9 | val index: Int, 10 | private val uuid: String, 11 | ) { 12 | companion object { 13 | fun of( 14 | destination: T, 15 | index: Int, 16 | ): NavState { 17 | val uuid = UUIDWrapper.getString() 18 | return NavState( 19 | destination, 20 | index, 21 | uuid, 22 | ) 23 | } 24 | } 25 | 26 | val key: String = "$index-$uuid" 27 | } 28 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/OutlinedTextField.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.OutlinedTextField 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.saveable.rememberSaveable 9 | import androidx.compose.runtime.setValue 10 | 11 | @Composable 12 | fun SampleOutlinedTextField() { 13 | var text by rememberSaveable { mutableStateOf("") } 14 | 15 | OutlinedTextField( 16 | value = text, 17 | onValueChange = { text = it }, 18 | label = { Text("Label") }, 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/ext/KatalogExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext 2 | 3 | import androidx.compose.runtime.Stable 4 | import com.moriatsushi.katalog.domain.ExtensionBuilderImpl 5 | 6 | @Stable 7 | public interface KatalogExt { 8 | public companion object { 9 | @ExperimentalKatalogExtApi 10 | @Suppress("FunctionName") 11 | public fun Builder(name: String): ExtensionBuilder = 12 | ExtensionBuilderImpl(name) 13 | } 14 | 15 | @ExperimentalKatalogExtApi 16 | public val name: String 17 | 18 | @ExperimentalKatalogExtApi 19 | public val componentWrapper: ExtComponentWrapper? 20 | get() = null 21 | 22 | @ExperimentalKatalogExtApi 23 | public val rootWrapper: ExtRootWrapper? 24 | get() = null 25 | } 26 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/androidview/mapper/ViewToCompose.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidview.mapper 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.viewinterop.AndroidView 7 | import com.moriatsushi.katalog.dsl.ViewDefinition 8 | 9 | @Composable 10 | internal fun ViewToCompose( 11 | layoutParams: ViewGroup.LayoutParams? = null, 12 | definition: ViewDefinition, 13 | ) { 14 | val scope = rememberViewDefinitionScope() 15 | AndroidView( 16 | factory = { 17 | val view = definition.invoke(scope) 18 | if (layoutParams != null) { 19 | view.layoutParams = layoutParams 20 | } 21 | view 22 | }, 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /katalog/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /extensions/androidtheme/src/main/kotlin/com/moriatsushi/katalog/ext/androidtheme/internal/AndroidThemeExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.androidtheme.internal 2 | 3 | import androidx.annotation.StyleRes 4 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 5 | import com.moriatsushi.katalog.ext.KatalogExt 6 | 7 | @OptIn(ExperimentalKatalogExtApi::class) 8 | internal fun createAndroidThemeExt( 9 | @StyleRes themeResId: Int, 10 | ): KatalogExt { 11 | val builder = KatalogExt.Builder("AndroidTheme") 12 | builder.setRootWrapper { content -> 13 | ContextTheme( 14 | themeResId = themeResId, 15 | content = content, 16 | ) 17 | } 18 | builder.setComponentWrapper { content -> 19 | Background { 20 | content() 21 | } 22 | } 23 | return builder.build() 24 | } 25 | -------------------------------------------------------------------------------- /samples/androidapp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /extensions/theme/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /extensions/androidtheme/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /extensions/pagesaver/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /extensions/theme/src/main/kotlin/com/moriatsushi/katalog/ext/theme/internal/ThemeExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.theme.internal 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.Surface 5 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 6 | import com.moriatsushi.katalog.ext.KatalogExt 7 | import com.moriatsushi.katalog.ext.theme.ThemeDefinition 8 | 9 | @OptIn(ExperimentalKatalogExtApi::class) 10 | internal fun createThemeExt( 11 | theme: ThemeDefinition, 12 | ): KatalogExt { 13 | val builder = KatalogExt.Builder("Theme") 14 | builder.setComponentWrapper { content -> 15 | theme { 16 | Surface( 17 | color = MaterialTheme.colors.background, 18 | content = content, 19 | ) 20 | } 21 | } 22 | return builder.build() 23 | } 24 | -------------------------------------------------------------------------------- /katalog-androidview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /.github/workflows/deploy_docs.yml: -------------------------------------------------------------------------------- 1 | name: "deploy docs" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | deploy_docs: 8 | name: Deploy Docs 9 | runs-on: ubuntu-latest 10 | defaults: 11 | run: 12 | working-directory: docs 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 18 20 | cache: yarn 21 | cache-dependency-path: docs/yarn.lock 22 | 23 | - name: git config 24 | run: | 25 | git config --global user.email "action@github.com" 26 | git config --global user.name "GitHub Action" 27 | 28 | - name: Install dependencies 29 | run: yarn install --frozen-lockfile 30 | 31 | - name: Deploy 32 | run: yarn deploy 33 | env: 34 | GIT_USER: ${{ github.actor }}:${{ github.token }} 35 | -------------------------------------------------------------------------------- /extensions/androidtheme/src/main/kotlin/com/moriatsushi/katalog/ext/androidtheme/internal/ContextTheme.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.androidtheme.internal 2 | 3 | import android.content.ContextWrapper 4 | import androidx.annotation.StyleRes 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.CompositionLocalProvider 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.ui.platform.LocalContext 9 | 10 | @Composable 11 | internal fun ContextTheme( 12 | @StyleRes themeResId: Int, 13 | content: @Composable () -> Unit, 14 | ) { 15 | val originalContext = LocalContext.current 16 | val themedContext = remember(originalContext) { 17 | ContextWrapper(originalContext).apply { 18 | setTheme(themeResId) 19 | } 20 | } 21 | CompositionLocalProvider( 22 | LocalContext provides themedContext, 23 | content = content, 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/menu/top_app_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 12 | 13 | 19 | 20 | 25 | 26 | -------------------------------------------------------------------------------- /docs/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /* stylelint-disable docusaurus/copyright-header */ 2 | /** 3 | * Any CSS included here will be global. The classic template 4 | * bundles Infima by default. Infima is a CSS framework designed to 5 | * work well for content-centric websites. 6 | */ 7 | 8 | /* You can override the default Infima variables here. */ 9 | :root { 10 | --ifm-color-primary: #4aab79; 11 | --ifm-color-primary-dark: #439a6d; 12 | --ifm-color-primary-darker: #3f9167; 13 | --ifm-color-primary-darkest: #347855; 14 | --ifm-color-primary-light: #57b685; 15 | --ifm-color-primary-lighter: #60ba8b; 16 | --ifm-color-primary-lightest: #79c59e; 17 | --ifm-code-font-size: 95%; 18 | } 19 | 20 | .docusaurus-highlight-code-line { 21 | background-color: rgba(0, 0, 0, 0.1); 22 | display: block; 23 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 24 | padding: 0 var(--ifm-pre-padding); 25 | } 26 | 27 | html[data-theme='dark'] .docusaurus-highlight-code-line { 28 | background-color: rgba(0, 0, 0, 0.3); 29 | } 30 | -------------------------------------------------------------------------------- /docs/src/pages/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | import styles from './header.module.css'; 5 | 6 | import LogoSvg from '@site/static/img/logo-clip.svg'; 7 | import HeaderImg from '@site/static/img/header-img.png'; 8 | 9 | export default function TopHeader() { 10 | return ( 11 |
12 |
13 |
14 | 15 |

Katalog

16 |

A UI Catalog Library made with Jetpack Compose

17 | 20 | Getting Started 21 | 22 |
23 |
24 | 25 |
26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /docs/static/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/KatalogContainer.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.dsl.GroupDefinition 4 | import com.moriatsushi.katalog.ext.KatalogExt 5 | 6 | internal class KatalogContainer { 7 | companion object { 8 | val instance: KatalogContainer = KatalogContainer() 9 | } 10 | 11 | private var definition: KatalogDefinition? = null 12 | 13 | fun register( 14 | title: String, 15 | extensions: List, 16 | groupDefinition: GroupDefinition, 17 | ) { 18 | if (definition != null) { 19 | throw AlreadyRegisteredException() 20 | } 21 | this.definition = KatalogDefinition( 22 | title = title, 23 | extensions = extensions, 24 | groupDefinition = groupDefinition, 25 | ) 26 | } 27 | 28 | fun create(): Katalog { 29 | val definition = definition ?: throw NotRegisteredException() 30 | return definition.build() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/CatalogItem.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.foundation.border 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.shape.RoundedCornerShape 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.draw.clip 10 | import androidx.compose.ui.unit.dp 11 | import com.moriatsushi.katalog.compose.res.defaultCornerRadius 12 | 13 | @Composable 14 | internal fun CatalogItemWrapper( 15 | modifier: Modifier = Modifier, 16 | content: @Composable () -> Unit, 17 | ) { 18 | val shape = RoundedCornerShape(defaultCornerRadius) 19 | Box( 20 | modifier 21 | .clip(shape) 22 | .border( 23 | width = 1.5.dp, 24 | color = MaterialTheme.colors.surface, 25 | shape = shape, 26 | ), 27 | ) { 28 | content() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/androidview/mapper/BindingToCompose.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidview.mapper 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.compose.runtime.Composable 6 | import androidx.databinding.ViewDataBinding 7 | import androidx.viewbinding.ViewBinding 8 | import com.moriatsushi.katalog.dsl.BindingFactoryDefinition 9 | import com.moriatsushi.katalog.dsl.BindingUpdateDefinition 10 | 11 | @Composable 12 | internal fun BindingToCompose( 13 | factory: BindingFactoryDefinition, 14 | layoutParams: ViewGroup.LayoutParams? = null, 15 | update: BindingUpdateDefinition = {}, 16 | ) { 17 | ViewToCompose(layoutParams) { 18 | val inflater = LayoutInflater.from(context) 19 | val binding = factory.invoke(inflater, null, false) 20 | update.invoke(this, binding) 21 | if (binding is ViewDataBinding) { 22 | binding.executePendingBindings() 23 | } 24 | binding.root 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/test/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/DummyExtNavState.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import androidx.compose.runtime.derivedStateOf 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateListOf 6 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 7 | import com.moriatsushi.katalog.ext.ExtNavState 8 | 9 | @ExperimentalKatalogExtApi 10 | internal class DummyExtNavState : ExtNavState { 11 | override val current: String by derivedStateOf { 12 | backStack.last() 13 | } 14 | private val _backStack = mutableStateListOf("/") 15 | override val backStack: List by derivedStateOf { 16 | _backStack.toList() 17 | } 18 | 19 | override fun navigateTo(destination: String): Boolean { 20 | _backStack.add(destination) 21 | return true 22 | } 23 | 24 | override fun restore(backStack: List): Boolean { 25 | _backStack.clear() 26 | _backStack.addAll(backStack) 27 | return true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/view/material/BottomNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.view.material 2 | 3 | import android.content.Context 4 | import com.google.android.material.bottomnavigation.BottomNavigationView 5 | import com.moriatsushi.katalog.androidsample.R 6 | 7 | fun getSampleBottomNavigation(context: Context): BottomNavigationView { 8 | val view = BottomNavigationView(context) 9 | view.inflateMenu(R.menu.bottom_navigation_menu) 10 | return view 11 | } 12 | 13 | fun getSampleBottomNavigationWithBadges(context: Context): BottomNavigationView { 14 | val view = BottomNavigationView(context) 15 | view.inflateMenu(R.menu.bottom_navigation_menu) 16 | view.getOrCreateBadge(R.id.page_1).also { 17 | it.isVisible = true 18 | } 19 | view.getOrCreateBadge(R.id.page_2).also { 20 | it.isVisible = true 21 | it.number = 99 22 | } 23 | 24 | view.getOrCreateBadge(R.id.page_3).also { 25 | it.isVisible = true 26 | it.number = 1000 27 | } 28 | return view 29 | } 30 | -------------------------------------------------------------------------------- /docs/static/img/logo-clip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/IconToggleButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.animation.animateColorAsState 4 | import androidx.compose.material.Icon 5 | import androidx.compose.material.IconToggleButton 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.Favorite 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.runtime.mutableStateOf 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.runtime.setValue 13 | import androidx.compose.ui.graphics.Color 14 | 15 | @Composable 16 | fun SampleIconToggleButton() { 17 | var checked by remember { mutableStateOf(false) } 18 | 19 | IconToggleButton(checked = checked, onCheckedChange = { checked = it }) { 20 | val tint by animateColorAsState(if (checked) Color(0xFFEC407A) else Color(0xFFB0BEC5)) 21 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description", tint = tint) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Button.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.material.Button 6 | import androidx.compose.material.ButtonDefaults 7 | import androidx.compose.material.Icon 8 | import androidx.compose.material.Text 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.Favorite 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | 14 | @Composable 15 | fun SampleButton() { 16 | Button(onClick = { }) { 17 | Text("Button") 18 | } 19 | } 20 | 21 | @Composable 22 | fun ButtonWithIcon() { 23 | Button(onClick = { }) { 24 | Icon( 25 | Icons.Filled.Favorite, 26 | contentDescription = null, 27 | modifier = Modifier.size(ButtonDefaults.IconSize), 28 | ) 29 | Spacer(Modifier.size(ButtonDefaults.IconSpacing)) 30 | Text("Like") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /docs/docs/main/extensions/page-saver.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | import { Preview } from "/src/widgets/Preview"; 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | # Page Saver 9 | There is an extension that restores the previously opened page upon restart. 10 | 11 | ## Setup 12 | Add the `katalog-ext-pagesaver` package. 13 | 14 | ```kotlin 15 | dependencies { 16 | implementation("com.moriatsushi.katalog:katalog:`LATEST_VERSION`") 17 | implementation("com.moriatsushi.katalog:katalog-ext-pagesaver:`LATEST_VERSION`") 18 | } 19 | ``` 20 | 21 | ## Usage 22 | Specify `PageSaverExt` when calling `registerKatalog`. 23 | 24 | ```kotlin {4} 25 | registerKatalog( 26 | title = "Android Sample", 27 | extensions = listOf( 28 | PageSaverExt() // add this line 29 | ) 30 | ) { 31 | compose("Text") { 32 | Text(text = "Hello, World") 33 | } 34 | } 35 | ``` 36 | 37 | When you launch the UI Catalog, previously opened pages are automatically restored. 38 | At the first startup or if the page does not exist, the TOP page will be launched. 39 | 40 | This will help you quickly see the UI changes. 41 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids" 15 | }, 16 | "dependencies": { 17 | "@docusaurus/core": "2.4.1", 18 | "@docusaurus/preset-classic": "2.4.1", 19 | "@mdx-js/react": "1.6.22", 20 | "@svgr/webpack": "8.0.1", 21 | "clsx": "1.2.1", 22 | "file-loader": "6.2.0", 23 | "prism-react-renderer": "1.3.5", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "url-loader": "4.1.1" 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.5%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/ext/Types.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.ext 2 | 3 | @Deprecated( 4 | "The package name has changed.", 5 | ReplaceWith( 6 | "ExtComponentWrapper", 7 | "com.moriatsushi.katalog.ext.ExtComponentWrapper", 8 | ), 9 | ) 10 | @ExperimentalKatalogExtApi 11 | public typealias ExtComponentWrapper = com.moriatsushi.katalog.ext.ExtComponentWrapper 12 | 13 | @Deprecated( 14 | "The package name has changed.", 15 | ReplaceWith( 16 | "ExtRootWrapper", 17 | "com.moriatsushi.katalog.ext.ExtRootWrapper", 18 | ), 19 | ) 20 | @ExperimentalKatalogExtApi 21 | public typealias ExtRootWrapper = com.moriatsushi.katalog.ext.ExtRootWrapper 22 | 23 | @Deprecated( 24 | "The package name has changed.", 25 | ReplaceWith( 26 | "ExperimentalKatalogExtApi", 27 | "com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi", 28 | ), 29 | ) 30 | // https://youtrack.jetbrains.com/issue/KT-56715 31 | @Suppress("OPT_IN_MARKER_CAN_ONLY_BE_USED_AS_ANNOTATION_OR_ARGUMENT_IN_OPT_IN") 32 | public typealias ExperimentalKatalogExtApi = com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 33 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/BottomAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.material.BottomAppBar 5 | import androidx.compose.material.Icon 6 | import androidx.compose.material.IconButton 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.Favorite 9 | import androidx.compose.material.icons.filled.Menu 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | 13 | @Composable 14 | fun SampleBottomAppBar() { 15 | BottomAppBar { 16 | IconButton(onClick = { }) { 17 | Icon(Icons.Filled.Menu, contentDescription = "Localized description") 18 | } 19 | Spacer(Modifier.weight(1f, true)) 20 | IconButton(onClick = { }) { 21 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description") 22 | } 23 | IconButton(onClick = { }) { 24 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/res/Colors.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.res 2 | 3 | import androidx.compose.material.Colors 4 | import androidx.compose.material.darkColors 5 | import androidx.compose.material.lightColors 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.graphics.Color 8 | 9 | @Composable 10 | internal fun materialColors( 11 | darkTheme: Boolean, 12 | ): Colors { 13 | return if (darkTheme) darkColor else lightColor 14 | } 15 | 16 | private val lightColorBg = Color.White 17 | private val lightColorTxt = Color(0xFF000000) 18 | private val lightUpperColorBg = Color(0xFFeeeeee) 19 | private val darkColorBg = Color(0xFF121212) 20 | private val darkColorTxt = Color.White 21 | private val darkUpperColorBg = Color(0xFF333333) 22 | 23 | private val lightColor = lightColors( 24 | surface = lightUpperColorBg, 25 | onSurface = lightColorTxt, 26 | background = lightColorBg, 27 | onBackground = lightColorTxt, 28 | ) 29 | private val darkColor = darkColors( 30 | surface = darkUpperColorBg, 31 | onSurface = darkColorTxt, 32 | background = darkColorBg, 33 | onBackground = darkColorTxt, 34 | ) 35 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/ExtendedFloatingActionButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.fillMaxWidth 4 | import androidx.compose.material.ExtendedFloatingActionButton 5 | import androidx.compose.material.Icon 6 | import androidx.compose.material.Text 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.Favorite 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | 12 | @Composable 13 | fun SampleExtendedFloatingActionButton() { 14 | ExtendedFloatingActionButton( 15 | icon = { Icon(Icons.Filled.Favorite, contentDescription = null) }, 16 | text = { Text("ADD TO BASKET") }, 17 | onClick = { /*do something*/ }, 18 | ) 19 | } 20 | 21 | @Composable 22 | fun SampleExtendedFloatingActionButtonFullWidth() { 23 | ExtendedFloatingActionButton( 24 | icon = { Icon(Icons.Filled.Favorite, contentDescription = null) }, 25 | text = { Text("FLUID FAB") }, 26 | onClick = { /*do something*/ }, 27 | modifier = Modifier.fillMaxWidth(), 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/Dissolve.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.animation.AnimatedContent 4 | import androidx.compose.animation.ContentTransform 5 | import androidx.compose.animation.core.snap 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.animation.fadeIn 8 | import androidx.compose.animation.fadeOut 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | 12 | private const val ANIMATION_DURATION = 300 13 | 14 | @Composable 15 | internal fun Dissolve( 16 | targetState: T, 17 | modifier: Modifier = Modifier, 18 | content: @Composable (T) -> Unit, 19 | ) { 20 | AnimatedContent( 21 | targetState = targetState, 22 | modifier = modifier, 23 | transitionSpec = { 24 | ContentTransform( 25 | targetContentEnter = fadeIn( 26 | animationSpec = tween(ANIMATION_DURATION), 27 | ), 28 | initialContentExit = fadeOut( 29 | animationSpec = snap(ANIMATION_DURATION), 30 | ), 31 | ) 32 | }, 33 | ) { 34 | content(it) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/jp/co/cyberagent/katalog/Entry.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog 2 | 3 | import com.moriatsushi.katalog.dsl.Group 4 | import jp.co.cyberagent.katalog.dsl.GroupDefinition 5 | import jp.co.cyberagent.katalog.ext.KatalogExt 6 | 7 | private const val DEFAULT_TITLE = "Katalog" 8 | 9 | @Deprecated( 10 | "The package name has changed.", 11 | ReplaceWith( 12 | "registerKatalog(title, extensions, groupDefinition)", 13 | "com.moriatsushi.katalog.registerKatalog", 14 | ), 15 | ) 16 | public fun registerKatalog( 17 | title: String = DEFAULT_TITLE, 18 | extensions: List = emptyList(), 19 | groupDefinition: GroupDefinition, 20 | ) { 21 | com.moriatsushi.katalog.registerKatalog( 22 | title = title, 23 | extensions = extensions, 24 | groupDefinition = groupDefinition, 25 | ) 26 | } 27 | 28 | @Deprecated( 29 | "The package name has changed.", 30 | ReplaceWith( 31 | "group(name, definition)", 32 | "com.moriatsushi.katalog.group", 33 | ), 34 | ) 35 | public fun group(name: String, definition: GroupDefinition): Group { 36 | return com.moriatsushi.katalog.group( 37 | name = name, 38 | definition = definition, 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/PageStore.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | 6 | internal class PageStore( 7 | private val localStorage: LocalStorage, 8 | ) { 9 | companion object { 10 | private const val BACK_STACK_KEY = "back_stack" 11 | } 12 | 13 | fun update(backStack: List) { 14 | val string = BackStackMapper.toString(backStack) 15 | localStorage.putString(BACK_STACK_KEY, string) 16 | } 17 | 18 | fun read(): List? { 19 | val backStack = try { 20 | val string = localStorage.getString(BACK_STACK_KEY) 21 | if (string != null) { 22 | BackStackMapper.fromString(string) 23 | } else { 24 | null 25 | } 26 | } catch (e: Throwable) { 27 | return null 28 | } 29 | return backStack 30 | } 31 | } 32 | 33 | @Composable 34 | internal fun rememberPageStore( 35 | localStorage: LocalStorage = rememberLocalStorage(), 36 | ): PageStore { 37 | return remember(localStorage) { 38 | PageStore(localStorage) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/ExtensionBuilderImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 4 | import com.moriatsushi.katalog.ext.ExtComponentWrapper 5 | import com.moriatsushi.katalog.ext.ExtRootWrapper 6 | import com.moriatsushi.katalog.ext.ExtensionBuilder 7 | import com.moriatsushi.katalog.ext.KatalogExt 8 | 9 | @ExperimentalKatalogExtApi 10 | internal class ExtensionBuilderImpl( 11 | private val name: String, 12 | ) : ExtensionBuilder { 13 | private var componentWrapper: ExtComponentWrapper? = null 14 | private var rootWrapper: ExtRootWrapper? = null 15 | 16 | override fun setComponentWrapper( 17 | wrapper: ExtComponentWrapper, 18 | ): ExtensionBuilder { 19 | this.componentWrapper = wrapper 20 | return this 21 | } 22 | 23 | override fun setRootWrapper( 24 | wrapper: ExtRootWrapper, 25 | ): ExtensionBuilder { 26 | this.rootWrapper = wrapper 27 | return this 28 | } 29 | 30 | override fun build(): KatalogExt { 31 | return KatalogExtImpl( 32 | name = name, 33 | componentWrapper = componentWrapper, 34 | rootWrapper = rootWrapper, 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/BottomNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.BottomNavigation 4 | import androidx.compose.material.BottomNavigationItem 5 | import androidx.compose.material.Icon 6 | import androidx.compose.material.Text 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.Favorite 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.getValue 11 | import androidx.compose.runtime.mutableStateOf 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.runtime.setValue 14 | 15 | @Composable 16 | fun SampleBottomNavigation() { 17 | var selectedItem by remember { mutableStateOf(0) } 18 | val items = listOf("Songs", "Artists", "Playlists") 19 | 20 | BottomNavigation { 21 | items.forEachIndexed { index, item -> 22 | BottomNavigationItem( 23 | icon = { Icon(Icons.Filled.Favorite, contentDescription = null) }, 24 | label = { Text(item) }, 25 | selected = selectedItem == index, 26 | onClick = { selectedItem = index }, 27 | ) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 26 | 27 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/LocalStorage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import android.content.Context 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.ui.platform.LocalContext 7 | 8 | private const val PACKAGE_NAME = "com.moriatsushi.katalog.ext.pagesaver" 9 | 10 | internal interface LocalStorage { 11 | fun putString(key: String, value: String?) 12 | fun getString(key: String): String? 13 | } 14 | 15 | private class LocalStorageImpl( 16 | context: Context, 17 | ) : LocalStorage { 18 | private val sharedPreference = 19 | context.getSharedPreferences(PACKAGE_NAME, Context.MODE_PRIVATE) 20 | 21 | override fun putString(key: String, value: String?) { 22 | sharedPreference.edit() 23 | .putString(key, value) 24 | .apply() 25 | } 26 | 27 | override fun getString(key: String): String? { 28 | return sharedPreference.getString(key, null) 29 | } 30 | } 31 | 32 | @Composable 33 | internal fun rememberLocalStorage( 34 | context: Context = LocalContext.current, 35 | ): LocalStorage { 36 | return remember(context) { 37 | LocalStorageImpl(context) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/SampleApp.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample 2 | 3 | import android.app.Application 4 | import com.google.android.material.R as MaterialR 5 | import com.moriatsushi.katalog.androidsample.compose.material.composeMaterialGroup 6 | import com.moriatsushi.katalog.androidsample.fragment.fragmentGroup 7 | import com.moriatsushi.katalog.androidsample.view.material.viewMaterialGroup 8 | import com.moriatsushi.katalog.ext.androidtheme.AndroidThemeExt 9 | import com.moriatsushi.katalog.ext.pagesaver.PageSaverExt 10 | import com.moriatsushi.katalog.ext.theme.ThemeExt 11 | import com.moriatsushi.katalog.registerKatalog 12 | 13 | class SampleApp : Application() { 14 | override fun onCreate() { 15 | super.onCreate() 16 | 17 | registerKatalog( 18 | title = "Android Sample", 19 | extensions = listOf( 20 | AndroidThemeExt(MaterialR.style.Theme_MaterialComponents_DayNight_NoActionBar), 21 | ThemeExt { SampleTheme(it) }, 22 | PageSaverExt(), 23 | ), 24 | ) { 25 | group( 26 | composeMaterialGroup, 27 | viewMaterialGroup, 28 | fragmentGroup, 29 | ) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/androidview/mapper/ViewDefinitionScope.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidview.mapper 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.remember 7 | import androidx.compose.ui.platform.LocalContext 8 | import androidx.lifecycle.LifecycleOwner 9 | import com.moriatsushi.katalog.androidview.util.findActivity 10 | import com.moriatsushi.katalog.androidview.util.rememberLifecycleOwner 11 | import com.moriatsushi.katalog.dsl.ViewDefinitionScope 12 | 13 | @Composable 14 | internal fun rememberViewDefinitionScope(): ViewDefinitionScope { 15 | val context = LocalContext.current 16 | val lifecycleOwner = rememberLifecycleOwner() 17 | return remember(context, lifecycleOwner) { 18 | val activity = context.findActivity() ?: error("not found activity") 19 | ViewDefinitionScopeImpl( 20 | context = context, 21 | activity = activity, 22 | lifecycleOwner = lifecycleOwner, 23 | ) 24 | } 25 | } 26 | 27 | private class ViewDefinitionScopeImpl( 28 | override val context: Context, 29 | override val activity: Activity, 30 | override val lifecycleOwner: LifecycleOwner, 31 | ) : ViewDefinitionScope 32 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | 13 | 16 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/docs/main/jetpack-compose.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | import { Preview } from "/src/widgets/Preview" 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | # Jetpack Compose 9 | 10 | ## Examples 11 | 12 | 16 | 17 | To add a `Composable`, use the `compose` method. 18 | 19 | ```kotlin 20 | compose("Text") { 21 | Text(text = "Hello, World") 22 | } 23 | ``` 24 | 25 | 26 | 27 | 31 | 32 | You can use state in the `compose`. 33 | 34 | ```kotlin 35 | compose("MyComponent") { 36 | var boolean by remember { 37 | mutableStateOf(true) 38 | } 39 | Column( 40 | horizontalAlignment = Alignment.CenterHorizontally 41 | ) { 42 | Text( 43 | text = if (boolean) "Hello" else "World" 44 | ) 45 | Button( 46 | onClick = { boolean = !boolean } 47 | ) { 48 | Text(text = "Toggle") 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | 55 | 56 | ## Parameters 57 | 58 | name|description 59 | :--|:-- 60 | name|The UI Component name. 61 | definition|Composable function. 62 | -------------------------------------------------------------------------------- /extensions/androidtheme/src/main/kotlin/com/moriatsushi/katalog/ext/androidtheme/internal/Background.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.androidtheme.internal 2 | 3 | import android.util.TypedValue 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.core.content.ContextCompat 13 | 14 | @Composable 15 | internal fun Background(content: @Composable () -> Unit) { 16 | val context = LocalContext.current 17 | val color = remember(context) { 18 | val typedValue = TypedValue() 19 | context.theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true) 20 | when { 21 | typedValue.data != 0 -> typedValue.data 22 | typedValue.resourceId != 0 -> ContextCompat.getColor(context, typedValue.resourceId) 23 | else -> 0 24 | } 25 | } 26 | Box( 27 | modifier = Modifier 28 | .fillMaxSize() 29 | .background(color = Color(color)), 30 | ) { 31 | content() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/dsl/BindingExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.viewbinding.ViewBinding 6 | import com.moriatsushi.katalog.androidview.mapper.BindingToCompose 7 | import kotlin.reflect.KClass 8 | 9 | public typealias BindingFactoryDefinition = (LayoutInflater, ViewGroup?, Boolean) -> T 10 | public typealias BindingUpdateDefinition = ViewDefinitionScope.(T) -> Unit 11 | 12 | public inline fun GroupScope.binding( 13 | noinline factory: BindingFactoryDefinition, 14 | name: String? = null, 15 | layoutParams: ViewGroup.LayoutParams = WRAP_WIDTH_WRAP_HEIGHT, 16 | noinline update: BindingUpdateDefinition = {}, 17 | ) { 18 | binding(T::class, factory, name, layoutParams, update) 19 | } 20 | 21 | @PublishedApi 22 | internal fun GroupScope.binding( 23 | clazz: KClass, 24 | factory: BindingFactoryDefinition, 25 | name: String?, 26 | layoutParams: ViewGroup.LayoutParams = WRAP_WIDTH_WRAP_HEIGHT, 27 | update: BindingUpdateDefinition = {}, 28 | ) { 29 | val displayName = name ?: clazz.simpleName ?: "" 30 | compose(displayName) { 31 | BindingToCompose(factory, layoutParams, update) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/TopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.Icon 4 | import androidx.compose.material.IconButton 5 | import androidx.compose.material.Text 6 | import androidx.compose.material.TopAppBar 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.Favorite 9 | import androidx.compose.material.icons.filled.Menu 10 | import androidx.compose.runtime.Composable 11 | 12 | @Composable 13 | fun SampleTopAppBar() { 14 | TopAppBar( 15 | title = { Text("Simple TopAppBar") }, 16 | navigationIcon = { 17 | IconButton(onClick = { /* doSomething() */ }) { 18 | Icon(Icons.Filled.Menu, contentDescription = null) 19 | } 20 | }, 21 | actions = { 22 | // RowScope here, so these icons will be placed horizontally 23 | IconButton(onClick = { /* doSomething() */ }) { 24 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description") 25 | } 26 | IconButton(onClick = { /* doSomething() */ }) { 27 | Icon(Icons.Filled.Favorite, contentDescription = "Localized description") 28 | } 29 | }, 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 11 | 12 | 13 | 15 | 16 | 17 | 28 | 29 | -------------------------------------------------------------------------------- /docs/docs/main/extensions/compose-theme.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import { Preview } from "/src/widgets/Preview"; 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | # Compose Theme 9 | There is an extension to change the Compose Theme. 10 | 11 | ## Setup 12 | Add the `katalog-ext-theme` package. 13 | 14 | ```kotlin 15 | dependencies { 16 | implementation("com.moriatsushi.katalog:katalog:`LATEST_VERSION`") 17 | implementation("com.moriatsushi.katalog:katalog-ext-theme:`LATEST_VERSION`") 18 | } 19 | ``` 20 | 21 | ## Examples 22 | 26 | 27 | You can set the Material Theme when calling `registerKatalog`. 28 | The `background` color will be applied automatically. 29 | Don't forget to set the `content`. 30 | 31 | ```kotlin {4-12} 32 | registerKatalog( 33 | title = "Android Sample", 34 | extensions = listOf( 35 | ThemeExt { content -> 36 | MaterialTheme( 37 | colors = MaterialTheme.colors.copy( 38 | background = Color.Red 39 | ), 40 | content = content 41 | ) 42 | } 43 | ) 44 | ) { 45 | compose("Text") { 46 | Text(text = "Hello, World") 47 | } 48 | } 49 | ``` 50 | 51 | 52 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/Katalog.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | @Stable 6 | internal data class Katalog( 7 | val title: String, 8 | val items: List, 9 | val extensions: Extensions, 10 | ) { 11 | fun findItemById(id: String, ignoreCount: Boolean = false): CatalogItem? { 12 | val identifier = CatalogItemIdentifier.ofOrNull(id) ?: return null 13 | val items = identifier.parents.fold(items) { acc, parent -> 14 | val result = findItem(acc, parent, ignoreCount) 15 | if (result is CatalogItem.Group) { 16 | result.items 17 | } else { 18 | return null 19 | } 20 | } 21 | return findItem(items, identifier, ignoreCount) 22 | } 23 | 24 | private fun findItem( 25 | items: List, 26 | identifier: CatalogItemIdentifier, 27 | ignoreCount: Boolean, 28 | ): CatalogItem? { 29 | val result = items.find { 30 | it.identifier.name == identifier.name && it.identifier.count == identifier.count 31 | } 32 | if (result != null || !ignoreCount) { 33 | return result 34 | } 35 | return items.find { 36 | it.identifier.name == identifier.name 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/dsl/FragmentExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | import android.view.ViewGroup 4 | import androidx.fragment.app.Fragment 5 | import com.moriatsushi.katalog.androidview.mapper.FragmentToCompose 6 | import kotlin.reflect.KClass 7 | 8 | public typealias FragmentDefinition = ViewDefinitionScope.() -> T 9 | public typealias FragmentOnCreateListener = ViewDefinitionScope.(fragment: T) -> Unit 10 | 11 | public inline fun GroupScope.fragment( 12 | name: String? = null, 13 | layoutParams: ViewGroup.LayoutParams = MATCH_WIDTH_MATCH_HEIGHT, 14 | noinline onCreateView: FragmentOnCreateListener = {}, 15 | noinline definition: FragmentDefinition, 16 | ) { 17 | fragment(T::class, name, layoutParams, onCreateView, definition) 18 | } 19 | 20 | @PublishedApi 21 | internal fun GroupScope.fragment( 22 | clazz: KClass, 23 | name: String? = null, 24 | layoutParams: ViewGroup.LayoutParams = MATCH_WIDTH_MATCH_HEIGHT, 25 | onCreateView: FragmentOnCreateListener, 26 | definition: FragmentDefinition, 27 | ) { 28 | val displayName = name ?: clazz.simpleName ?: "" 29 | compose(displayName) { 30 | FragmentToCompose( 31 | layoutParams = layoutParams, 32 | onCreateView = onCreateView, 33 | definition = definition, 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/layout/material_app_bars_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/TabRow.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.Tab 6 | import androidx.compose.material.TabRow 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.runtime.mutableStateOf 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.runtime.setValue 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | 16 | @Composable 17 | fun SampleTabRow() { 18 | var state by remember { mutableStateOf(0) } 19 | val titles = listOf("TAB 1", "TAB 2", "TAB 3 WITH LOTS OF TEXT") 20 | Column { 21 | TabRow(selectedTabIndex = state) { 22 | titles.forEachIndexed { index, title -> 23 | Tab( 24 | text = { Text(title) }, 25 | selected = state == index, 26 | onClick = { state = index }, 27 | ) 28 | } 29 | } 30 | Text( 31 | modifier = Modifier.align(Alignment.CenterHorizontally), 32 | text = "Text tab ${state + 1} selected", 33 | style = MaterialTheme.typography.body1, 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/jp/co/cyberagent/katalog/dsl/FragmentExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | import android.view.ViewGroup 4 | import androidx.fragment.app.Fragment 5 | import com.moriatsushi.katalog.dsl.fragment as actualFragment 6 | 7 | @Deprecated( 8 | "The package name has changed.", 9 | ReplaceWith( 10 | "FragmentDefinition", 11 | "com.moriatsushi.katalog.dsl.FragmentDefinition", 12 | ), 13 | ) 14 | public typealias FragmentDefinition = 15 | com.moriatsushi.katalog.dsl.FragmentDefinition 16 | 17 | @Deprecated( 18 | "The package name has changed.", 19 | ReplaceWith( 20 | "FragmentOnCreateListener", 21 | "com.moriatsushi.katalog.dsl.FragmentOnCreateListener", 22 | ), 23 | ) 24 | public typealias FragmentOnCreateListener = 25 | com.moriatsushi.katalog.dsl.FragmentOnCreateListener 26 | 27 | @Deprecated( 28 | "The package name has changed.", 29 | ReplaceWith( 30 | "fragment(name, layoutParams, onCreateView, definition)", 31 | "com.moriatsushi.katalog.dsl.fragment", 32 | ), 33 | ) 34 | public inline fun GroupScope.fragment( 35 | name: String? = null, 36 | layoutParams: ViewGroup.LayoutParams = MATCH_WIDTH_MATCH_HEIGHT, 37 | noinline onCreateView: FragmentOnCreateListener = {}, 38 | noinline definition: FragmentDefinition, 39 | ) { 40 | actualFragment(name, layoutParams, onCreateView, definition) 41 | } 42 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Slider.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.material.MaterialTheme 4 | import androidx.compose.material.Slider 5 | import androidx.compose.material.SliderDefaults 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.getValue 9 | import androidx.compose.runtime.mutableStateOf 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.runtime.setValue 12 | 13 | @Composable 14 | fun SampleSlider() { 15 | var sliderPosition by remember { mutableStateOf(0f) } 16 | Text(text = sliderPosition.toString()) 17 | Slider(value = sliderPosition, onValueChange = { sliderPosition = it }) 18 | } 19 | 20 | @Composable 21 | fun SampleSliderWithSteps() { 22 | var sliderPosition by remember { mutableStateOf(0f) } 23 | Text(text = sliderPosition.toString()) 24 | Slider( 25 | value = sliderPosition, 26 | onValueChange = { sliderPosition = it }, 27 | valueRange = 0f..100f, 28 | onValueChangeFinished = { 29 | // launch some business logic update with the state you hold 30 | // viewModel.updateSelectedSliderValue(sliderPosition) 31 | }, 32 | steps = 5, 33 | colors = SliderDefaults.colors( 34 | thumbColor = MaterialTheme.colors.secondary, 35 | activeTrackColor = MaterialTheme.colors.secondary, 36 | ), 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/jp/co/cyberagent/katalog/dsl/BindingExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | import android.view.ViewGroup 4 | import androidx.viewbinding.ViewBinding 5 | import com.moriatsushi.katalog.dsl.binding as actualBinding 6 | 7 | @Deprecated( 8 | "The package name has changed.", 9 | ReplaceWith( 10 | "BindingFactoryDefinition", 11 | "com.moriatsushi.katalog.dsl.BindingFactoryDefinition", 12 | ), 13 | ) 14 | public typealias BindingFactoryDefinition = 15 | com.moriatsushi.katalog.dsl.BindingFactoryDefinition 16 | 17 | @Deprecated( 18 | "The package name has changed.", 19 | ReplaceWith( 20 | "BindingFactoryDefinition", 21 | "com.moriatsushi.katalog.dsl.BindingFactoryDefinition", 22 | ), 23 | ) 24 | public typealias BindingUpdateDefinition = 25 | com.moriatsushi.katalog.dsl.BindingUpdateDefinition 26 | 27 | @Deprecated( 28 | "The package name has changed.", 29 | ReplaceWith( 30 | "binding(factory, name, layoutParams, update)", 31 | "com.moriatsushi.katalog.dsl.binding", 32 | ), 33 | ) 34 | public inline fun GroupScope.binding( 35 | noinline factory: BindingFactoryDefinition, 36 | name: String? = null, 37 | layoutParams: ViewGroup.LayoutParams = WRAP_WIDTH_WRAP_HEIGHT, 38 | noinline update: BindingUpdateDefinition = {}, 39 | ) { 40 | actualBinding( 41 | factory = factory, 42 | name = name, 43 | layoutParams = layoutParams, 44 | update = update, 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/ExtNavStateImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.remember 7 | import com.moriatsushi.katalog.domain.Katalog 8 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 9 | import com.moriatsushi.katalog.ext.ExtNavState 10 | 11 | @ExperimentalKatalogExtApi 12 | internal class ExtNavStateImpl( 13 | private val navController: NavController, 14 | private val katalog: Katalog, 15 | ) : ExtNavState { 16 | override val current: String by derivedStateOf { 17 | backStack.last() 18 | } 19 | override val backStack: List by derivedStateOf { 20 | navController.idBackStack 21 | } 22 | 23 | override fun navigateTo(destination: String): Boolean { 24 | return navController.navigateTo(katalog, destination) 25 | } 26 | 27 | override fun restore(backStack: List): Boolean { 28 | return navController.restore(katalog, backStack) 29 | } 30 | } 31 | 32 | @ExperimentalKatalogExtApi 33 | @Composable 34 | internal fun rememberExtNavState( 35 | navController: NavController, 36 | katalog: Katalog, 37 | ): ExtNavState { 38 | return remember(navController, katalog) { 39 | ExtNavStateImpl( 40 | navController = navController, 41 | katalog = katalog, 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | paths-ignore: 9 | - 'docs/**' 10 | 11 | concurrency: 12 | group: build-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 60 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v3 23 | 24 | - name: Validate Gradle Wrapper 25 | uses: gradle/wrapper-validation-action@v1 26 | 27 | - name: Copy CI gradle.properties 28 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties 29 | 30 | - name: Set up JDK 17 31 | uses: actions/setup-java@v3 32 | with: 33 | distribution: 'zulu' 34 | java-version: 17 35 | 36 | - name: Setup Gradle 37 | uses: gradle/gradle-build-action@v2 38 | with: 39 | gradle-home-cache-cleanup: true 40 | cache-read-only: ${{ github.ref != 'refs/heads/main' }} 41 | 42 | - name: Check spotless 43 | run: ./gradlew spotlessCheck --no-configuration-cache --stacktrace 44 | 45 | - name: Assemble main outputs for all the variants. 46 | run: ./gradlew assemble --stacktrace 47 | 48 | - name: Run local tests 49 | run: ./gradlew test --stacktrace 50 | 51 | - name: Upload test reports 52 | if: always() 53 | uses: actions/upload-artifact@v3 54 | with: 55 | name: test-reports 56 | path: '**/build/reports/tests' 57 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/layout/material_app_bars_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/LinearProgressIndicator.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.animation.core.animateFloatAsState 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.requiredHeight 7 | import androidx.compose.material.LinearProgressIndicator 8 | import androidx.compose.material.OutlinedButton 9 | import androidx.compose.material.ProgressIndicatorDefaults 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.runtime.mutableStateOf 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.runtime.setValue 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.unit.dp 19 | 20 | @Composable 21 | fun SampleLinearProgressIndicator() { 22 | var progress by remember { mutableStateOf(0.1f) } 23 | val animatedProgress by animateFloatAsState( 24 | targetValue = progress, 25 | animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, 26 | ) 27 | 28 | Column(horizontalAlignment = Alignment.CenterHorizontally) { 29 | LinearProgressIndicator(progress = animatedProgress) 30 | Spacer(Modifier.requiredHeight(30.dp)) 31 | OutlinedButton( 32 | onClick = { 33 | if (progress < 1f) progress += 0.1f 34 | }, 35 | ) { 36 | Text("Increase") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/layout/material_app_bars_top_prominent.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 19 | 20 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/CircularProgressIndicator.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.animation.core.animateFloatAsState 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.requiredHeight 7 | import androidx.compose.material.CircularProgressIndicator 8 | import androidx.compose.material.OutlinedButton 9 | import androidx.compose.material.ProgressIndicatorDefaults 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.runtime.mutableStateOf 14 | import androidx.compose.runtime.remember 15 | import androidx.compose.runtime.setValue 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.unit.dp 19 | 20 | @Composable 21 | fun SampleCircularProgressIndicator() { 22 | var progress by remember { mutableStateOf(0.1f) } 23 | val animatedProgress by animateFloatAsState( 24 | targetValue = progress, 25 | animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, 26 | ) 27 | 28 | Column(horizontalAlignment = Alignment.CenterHorizontally) { 29 | CircularProgressIndicator(progress = animatedProgress) 30 | Spacer(Modifier.requiredHeight(30.dp)) 31 | OutlinedButton( 32 | onClick = { 33 | if (progress < 1f) progress += 0.1f 34 | }, 35 | ) { 36 | Text("Increase") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/page/TopPage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.page 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.foundation.lazy.rememberLazyListState 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Surface 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.LaunchedEffect 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.ui.Modifier 11 | import com.moriatsushi.katalog.compose.util.rememberIsTop 12 | import com.moriatsushi.katalog.compose.widget.CatalogItemList 13 | import com.moriatsushi.katalog.domain.CatalogItem 14 | import com.moriatsushi.katalog.domain.Katalog 15 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 16 | import com.moriatsushi.katalog.ext.ExtNavState 17 | 18 | @OptIn(ExperimentalKatalogExtApi::class) 19 | @Composable 20 | internal fun TopPage( 21 | katalog: Katalog, 22 | extNavState: ExtNavState, 23 | onChangeIsTop: (isTop: Boolean) -> Unit = {}, 24 | onClickItem: (item: CatalogItem) -> Unit, 25 | ) { 26 | val lazyListState = rememberLazyListState() 27 | val isTop by lazyListState.rememberIsTop() 28 | LaunchedEffect(isTop) { 29 | onChangeIsTop(isTop) 30 | } 31 | Surface( 32 | modifier = Modifier.fillMaxSize(), 33 | color = MaterialTheme.colors.background, 34 | ) { 35 | CatalogItemList( 36 | list = katalog.items, 37 | extensions = katalog.extensions, 38 | extNavState = extNavState, 39 | onClick = onClickItem, 40 | lazyListState = lazyListState, 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/dsl/ViewExt.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.dsl 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import com.moriatsushi.katalog.androidview.mapper.ViewToCompose 6 | import kotlin.reflect.KClass 7 | 8 | public typealias ViewDefinition = ViewDefinitionScope.() -> T 9 | 10 | public val WRAP_WIDTH_WRAP_HEIGHT: ViewGroup.LayoutParams = ViewGroup.LayoutParams( 11 | ViewGroup.LayoutParams.WRAP_CONTENT, 12 | ViewGroup.LayoutParams.WRAP_CONTENT, 13 | ) 14 | public val WRAP_WIDTH_MATCH_HEIGHT: ViewGroup.LayoutParams = ViewGroup.LayoutParams( 15 | ViewGroup.LayoutParams.WRAP_CONTENT, 16 | ViewGroup.LayoutParams.MATCH_PARENT, 17 | ) 18 | public val MATCH_WIDTH_WRAP_HEIGHT: ViewGroup.LayoutParams = ViewGroup.LayoutParams( 19 | ViewGroup.LayoutParams.MATCH_PARENT, 20 | ViewGroup.LayoutParams.WRAP_CONTENT, 21 | ) 22 | public val MATCH_WIDTH_MATCH_HEIGHT: ViewGroup.LayoutParams = ViewGroup.LayoutParams( 23 | ViewGroup.LayoutParams.MATCH_PARENT, 24 | ViewGroup.LayoutParams.MATCH_PARENT, 25 | ) 26 | 27 | public inline fun GroupScope.view( 28 | name: String? = null, 29 | layoutParams: ViewGroup.LayoutParams = WRAP_WIDTH_WRAP_HEIGHT, 30 | noinline definition: ViewDefinition, 31 | ) { 32 | view(T::class, name, layoutParams, definition) 33 | } 34 | 35 | @PublishedApi 36 | internal fun GroupScope.view( 37 | clazz: KClass, 38 | name: String?, 39 | layoutParams: ViewGroup.LayoutParams, 40 | definition: ViewDefinition, 41 | ) { 42 | val displayName = name ?: clazz.simpleName ?: "" 43 | compose(displayName) { 44 | ViewToCompose(layoutParams, definition) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/docs/main/extensions/android-theme.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | import { Preview } from "/src/widgets/Preview"; 5 | import useBaseUrl from '@docusaurus/useBaseUrl'; 6 | 7 | # Android Theme 8 | There is an extension to change the Android Theme for Android View, DataBinding/ViewBinding, Fragment. 9 | 10 | ## Setup 11 | Add the `katalog-ext-androidtheme` package. 12 | The `katalog-androidview` package is required to use Android View, DataBinding/ViewBinding and Fragment too. 13 | 14 | ```kotlin 15 | dependencies { 16 | implementation("com.moriatsushi.katalog:katalog:`LATEST_VERSION`") 17 | implementation("com.moriatsushi.katalog:katalog-androidview:`LATEST_VERSION`") 18 | implementation("com.moriatsushi.katalog:katalog-ext-androidtheme:`LATEST_VERSION`") 19 | } 20 | ``` 21 | 22 | ## Examples 23 | 27 | 28 | You can set the Android Theme when calling `registerKatalog`. 29 | The background color will be the value specified by `android:colorBackground`. 30 | 31 | ```xml title="res/values/styles.xml" 32 | 33 | 34 | 35 | 38 | 39 | ``` 40 | ```kotlin {4} 41 | registerKatalog( 42 | title = "Android Sample", 43 | extensions = listOf( 44 | AndroidThemeExt(R.style.MyTheme) // add this line 45 | ) 46 | ) { 47 | view("Text View") { 48 | TextView(context).apply { 49 | text = "Hello, World" 50 | } 51 | } 52 | } 53 | ``` 54 | 55 | 56 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/Empty.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.material.Icon 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.filled.Construction 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.text.style.TextAlign 16 | import androidx.compose.ui.unit.dp 17 | import androidx.compose.ui.unit.sp 18 | import com.moriatsushi.katalog.compose.res.defaultPadding 19 | 20 | @Composable 21 | internal fun Empty() { 22 | Column( 23 | modifier = Modifier 24 | .fillMaxWidth() 25 | .padding(horizontal = defaultPadding, vertical = 24.dp), 26 | horizontalAlignment = Alignment.CenterHorizontally, 27 | ) { 28 | Icon( 29 | imageVector = Icons.Filled.Construction, 30 | contentDescription = null, 31 | modifier = Modifier 32 | .padding(bottom = 16.dp) 33 | .size(36.dp), 34 | tint = MaterialTheme.colors.onBackground.copy(alpha = 0.6F), 35 | ) 36 | Text( 37 | text = "No Component Yet", 38 | textAlign = TextAlign.Center, 39 | color = MaterialTheme.colors.onBackground.copy(alpha = 0.4F), 40 | fontSize = 16.sp, 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/page/GroupPage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.page 2 | 3 | import androidx.compose.foundation.layout.fillMaxSize 4 | import androidx.compose.foundation.lazy.rememberLazyListState 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Surface 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.LaunchedEffect 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.ui.Modifier 11 | import com.moriatsushi.katalog.compose.util.rememberIsTop 12 | import com.moriatsushi.katalog.compose.widget.CatalogItemList 13 | import com.moriatsushi.katalog.domain.CatalogItem 14 | import com.moriatsushi.katalog.domain.Katalog 15 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 16 | import com.moriatsushi.katalog.ext.ExtNavState 17 | 18 | @ExperimentalKatalogExtApi 19 | @Composable 20 | internal fun GroupPage( 21 | katalog: Katalog, 22 | group: CatalogItem.Group, 23 | extNavState: ExtNavState, 24 | onChangeIsTop: (isTop: Boolean) -> Unit = {}, 25 | onClickItem: (item: CatalogItem) -> Unit, 26 | ) { 27 | val extensions = katalog.extensions 28 | val lazyListState = rememberLazyListState() 29 | val isTop by lazyListState.rememberIsTop() 30 | LaunchedEffect(isTop) { 31 | onChangeIsTop(isTop) 32 | } 33 | Surface( 34 | modifier = Modifier.fillMaxSize(), 35 | color = MaterialTheme.colors.background, 36 | ) { 37 | CatalogItemList( 38 | list = group.items, 39 | extensions = extensions, 40 | extNavState = extNavState, 41 | onClick = onClickItem, 42 | lazyListState = lazyListState, 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /extensions/theme/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | defaultConfig { 10 | minSdk = 21 11 | 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildFeatures { 15 | compose = true 16 | } 17 | compileOptions { 18 | sourceCompatibility = JavaVersion.VERSION_17 19 | targetCompatibility = JavaVersion.VERSION_17 20 | } 21 | kotlinOptions { 22 | jvmTarget = "17" 23 | freeCompilerArgs = freeCompilerArgs + listOf( 24 | "-Xexplicit-api=strict", 25 | "-Xopt-in=kotlin.RequiresOptIn" 26 | ) 27 | } 28 | composeOptions { 29 | kotlinCompilerExtensionVersion = 30 | libs.versions.androidx.compose.compiler.get() 31 | } 32 | sourceSets { 33 | getByName("main").java.srcDir("src/main/kotlin") 34 | getByName("test").java.srcDir("src/test/kotlin") 35 | getByName("androidTest").java.srcDir("src/androidTest/kotlin") 36 | } 37 | namespace = "com.moriatsushi.ext.theme" 38 | } 39 | 40 | dependencies { 41 | implementation(kotlin("stdlib")) 42 | implementation(project(":katalog")) 43 | 44 | implementation(libs.androidx.compose.ui) 45 | implementation(libs.androidx.compose.ui.tooling) 46 | implementation(libs.androidx.compose.foundation) 47 | implementation(libs.androidx.compose.material) 48 | 49 | testImplementation(libs.androidx.test.core) 50 | testImplementation(libs.androidx.test.runner) 51 | testImplementation(libs.androidx.test.rules) 52 | testImplementation(libs.androidx.test.ext.junit) 53 | testImplementation(libs.androidx.test.ext.truth) 54 | } 55 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/NavController.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import androidx.compose.runtime.Stable 4 | import androidx.compose.runtime.derivedStateOf 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateListOf 7 | 8 | @Stable 9 | internal class NavController( 10 | startDestination: T, 11 | ) { 12 | private val _backStack = mutableStateListOf(NavState.of(startDestination, 0)) 13 | val backStack: List> = _backStack 14 | 15 | val current: NavState by derivedStateOf { 16 | _backStack.last() 17 | } 18 | 19 | val isTop: Boolean by derivedStateOf { 20 | if (_backStack.size > 1) return@derivedStateOf false 21 | val childNavController = current.destination.childNavController 22 | childNavController == null || childNavController.isTop 23 | } 24 | 25 | fun push(destination: T) { 26 | val state = NavState.of(destination, _backStack.size) 27 | _backStack.add(state) 28 | } 29 | 30 | fun restore(backStack: List) { 31 | _backStack.clear() 32 | backStack.forEach { 33 | val state = NavState.of(it, _backStack.size) 34 | _backStack.add(state) 35 | } 36 | } 37 | 38 | fun back(): Boolean { 39 | if (isTop) return false 40 | 41 | val childNavController = current.destination.childNavController 42 | if (childNavController != null && childNavController.back()) { 43 | return true 44 | } 45 | 46 | _backStack.removeLast() 47 | return true 48 | } 49 | 50 | fun hasState(state: NavState): Boolean { 51 | return _backStack.any { 52 | it.key == state.key 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/view/material/ViewMaterialGroup.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.view.material 2 | 3 | import com.moriatsushi.katalog.androidsample.databinding.MaterialAppBarsBottomBinding 4 | import com.moriatsushi.katalog.androidsample.databinding.MaterialAppBarsTopBinding 5 | import com.moriatsushi.katalog.androidsample.databinding.MaterialAppBarsTopProminentBinding 6 | import com.moriatsushi.katalog.dsl.MATCH_WIDTH_MATCH_HEIGHT 7 | import com.moriatsushi.katalog.dsl.MATCH_WIDTH_WRAP_HEIGHT 8 | import com.moriatsushi.katalog.dsl.binding 9 | import com.moriatsushi.katalog.dsl.view 10 | import com.moriatsushi.katalog.group 11 | 12 | val viewMaterialGroup = group("View Material") { 13 | binding( 14 | factory = MaterialAppBarsBottomBinding::inflate, 15 | name = "App bars: bottom", 16 | layoutParams = MATCH_WIDTH_MATCH_HEIGHT, 17 | ) 18 | group("App bars: top") { 19 | binding( 20 | factory = MaterialAppBarsTopBinding::inflate, 21 | name = "App bars: top", 22 | layoutParams = MATCH_WIDTH_MATCH_HEIGHT, 23 | ) 24 | binding( 25 | factory = MaterialAppBarsTopProminentBinding::inflate, 26 | name = "App bars: top - prominent", 27 | layoutParams = MATCH_WIDTH_MATCH_HEIGHT, 28 | ) 29 | } 30 | group("Bottom navigation") { 31 | view( 32 | name = "Bottom navigation", 33 | layoutParams = MATCH_WIDTH_WRAP_HEIGHT, 34 | ) { 35 | getSampleBottomNavigation(context) 36 | } 37 | view( 38 | name = "Bottom navigation with badges", 39 | layoutParams = MATCH_WIDTH_WRAP_HEIGHT, 40 | ) { 41 | getSampleBottomNavigationWithBadges(context) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /extensions/androidtheme/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | defaultConfig { 10 | minSdk = 21 11 | 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildFeatures { 15 | compose = true 16 | } 17 | compileOptions { 18 | sourceCompatibility = JavaVersion.VERSION_17 19 | targetCompatibility = JavaVersion.VERSION_17 20 | } 21 | kotlinOptions { 22 | jvmTarget = "17" 23 | freeCompilerArgs = freeCompilerArgs + listOf( 24 | "-Xexplicit-api=strict", 25 | "-Xopt-in=kotlin.RequiresOptIn" 26 | ) 27 | } 28 | composeOptions { 29 | kotlinCompilerExtensionVersion = 30 | libs.versions.androidx.compose.compiler.get() 31 | } 32 | sourceSets { 33 | getByName("main").java.srcDir("src/main/kotlin") 34 | getByName("test").java.srcDir("src/test/kotlin") 35 | getByName("androidTest").java.srcDir("src/androidTest/kotlin") 36 | } 37 | namespace = "com.moriatsushi.katalog.ext.androidtheme" 38 | } 39 | 40 | dependencies { 41 | implementation(kotlin("stdlib")) 42 | implementation(project(":katalog")) 43 | 44 | implementation(libs.androidx.core) 45 | 46 | implementation(libs.androidx.compose.ui) 47 | implementation(libs.androidx.compose.ui.tooling) 48 | implementation(libs.androidx.compose.foundation) 49 | implementation(libs.androidx.compose.material) 50 | 51 | testImplementation(libs.androidx.test.core) 52 | testImplementation(libs.androidx.test.runner) 53 | testImplementation(libs.androidx.test.rules) 54 | testImplementation(libs.androidx.test.ext.junit) 55 | testImplementation(libs.androidx.test.ext.truth) 56 | } 57 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/page/MainPage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.page 2 | 3 | import androidx.compose.runtime.Composable 4 | import com.moriatsushi.katalog.compose.navigation.MainDestination 5 | import com.moriatsushi.katalog.compose.navigation.NavAnimation 6 | import com.moriatsushi.katalog.compose.navigation.NavController 7 | import com.moriatsushi.katalog.compose.navigation.NavRoot 8 | import com.moriatsushi.katalog.domain.CatalogItem 9 | import com.moriatsushi.katalog.domain.Katalog 10 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 11 | import com.moriatsushi.katalog.ext.ExtNavState 12 | 13 | @ExperimentalKatalogExtApi 14 | @Composable 15 | internal fun MainPage( 16 | katalog: Katalog, 17 | navController: NavController, 18 | extNavState: ExtNavState, 19 | onClickItem: (item: CatalogItem) -> Unit, 20 | onClickBack: () -> Unit, 21 | ) { 22 | NavRoot( 23 | navController = navController, 24 | transitionSpec = NavAnimation.createUpSpec(), 25 | ) { state -> 26 | when (state.destination) { 27 | is MainDestination.Discovery -> { 28 | DiscoveryPage( 29 | katalog = katalog, 30 | navController = state.destination.childNavController, 31 | extNavState = extNavState, 32 | onClickItem = onClickItem, 33 | isTopPage = navController.isTop, 34 | onClickBack = onClickBack, 35 | ) 36 | } 37 | is MainDestination.Preview -> { 38 | PreviewPage( 39 | component = state.destination.component, 40 | extensions = katalog.extensions, 41 | extNavState = extNavState, 42 | onClickClose = onClickBack, 43 | ) 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/KatalogViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.moriatsushi.katalog.compose.navigation.MainDestination 6 | import com.moriatsushi.katalog.compose.navigation.NavController 7 | import com.moriatsushi.katalog.compose.navigation.createMainNavController 8 | import com.moriatsushi.katalog.compose.navigation.navigateTo 9 | import com.moriatsushi.katalog.domain.CatalogItem 10 | import com.moriatsushi.katalog.domain.Katalog 11 | import com.moriatsushi.katalog.domain.KatalogContainer 12 | import com.moriatsushi.katalog.domain.NotRegisteredException 13 | import kotlinx.coroutines.Dispatchers 14 | import kotlinx.coroutines.flow.MutableStateFlow 15 | import kotlinx.coroutines.flow.StateFlow 16 | import kotlinx.coroutines.launch 17 | 18 | internal class KatalogViewModel( 19 | private val container: KatalogContainer = KatalogContainer.instance, 20 | ) : ViewModel() { 21 | private val _katalog = MutableStateFlow(null) 22 | val katalog: StateFlow = _katalog 23 | 24 | private val _errorMessage = MutableStateFlow(null) 25 | val errorMessage: StateFlow = _errorMessage 26 | 27 | val navController: NavController = 28 | createMainNavController() 29 | 30 | init { 31 | viewModelScope.launch(Dispatchers.Default) { 32 | try { 33 | val katalog = container.create() 34 | _katalog.value = katalog 35 | } catch (e: NotRegisteredException) { 36 | _errorMessage.value = "Please call registerKatalog method." 37 | } catch (e: Throwable) { 38 | _errorMessage.value = "Unknown error: $e" 39 | } 40 | } 41 | } 42 | 43 | fun handleClick(item: CatalogItem) { 44 | navController.navigateTo(item) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/KatalogTopAppBar.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.animation.fadeIn 5 | import androidx.compose.animation.fadeOut 6 | import androidx.compose.foundation.layout.Box 7 | import androidx.compose.material.Divider 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.Text 10 | import androidx.compose.material.TopAppBar 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.text.style.TextOverflow 15 | import androidx.compose.ui.unit.dp 16 | 17 | @Composable 18 | internal fun KatalogTopAppBar( 19 | title: String, 20 | isVisibleDivider: Boolean = true, 21 | navigationIcon: @Composable (() -> Unit)? = null, 22 | ) { 23 | Box { 24 | Dissolve( 25 | targetState = (title to navigationIcon), 26 | ) { (title, navigationIcon) -> 27 | TopAppBar( 28 | title = { 29 | Text( 30 | text = title, 31 | maxLines = 1, 32 | overflow = TextOverflow.Ellipsis, 33 | ) 34 | }, 35 | elevation = 0.dp, 36 | backgroundColor = MaterialTheme.colors.background, 37 | contentColor = MaterialTheme.colors.onBackground, 38 | navigationIcon = navigationIcon, 39 | ) 40 | } 41 | AnimatedVisibility( 42 | visible = isVisibleDivider, 43 | modifier = Modifier.align(Alignment.BottomCenter), 44 | enter = fadeIn(), 45 | exit = fadeOut(), 46 | ) { 47 | Divider( 48 | color = MaterialTheme.colors.surface, 49 | ) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | 21 | SONATYPE_HOST=S01 22 | RELEASE_SIGNING_ENABLED=true 23 | GROUP=com.moriatsushi.katalog 24 | VERSION_NAME=1.2.2 25 | 26 | POM_NAME=Katalog 27 | POM_DESCRIPTION=A UI Catalog Library made with Jetpack Compose 28 | POM_INCEPTION_YEAR=2021 29 | POM_URL=https://github.com/mori-atsushi/katalog/ 30 | 31 | POM_LICENSE_NAME=The Apache Software License, Version 2.0 32 | POM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt 33 | POM_LICENSE_DIST=repo 34 | 35 | POM_SCM_URL=https://github.com/mori-atsushi/katalog/ 36 | POM_SCM_CONNECTION=scm:git:git://github.com/mori-atsushi/katalog.git 37 | POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/mori-atsushi/katalog.git 38 | 39 | POM_DEVELOPER_ID=mori-atsushi 40 | POM_DEVELOPER_NAME=Mori Atsushi 41 | POM_DEVELOPER_URL=https://github.com/mori-atsushi/ 42 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/main/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/PageSaver.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.Immutable 5 | import androidx.compose.runtime.LaunchedEffect 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.remember 9 | import androidx.compose.runtime.setValue 10 | import androidx.compose.runtime.snapshotFlow 11 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 12 | import com.moriatsushi.katalog.ext.ExtNavState 13 | import kotlinx.coroutines.flow.launchIn 14 | import kotlinx.coroutines.flow.onEach 15 | 16 | @ExperimentalKatalogExtApi 17 | @Composable 18 | internal fun PageSaver( 19 | navState: ExtNavState, 20 | pageStore: PageStore = rememberPageStore(), 21 | content: @Composable () -> Unit, 22 | ) { 23 | val state = rememberPageSagerState( 24 | navState = navState, 25 | pageStore = pageStore, 26 | ) 27 | 28 | if (state.isInitialized) { 29 | content() 30 | } 31 | } 32 | 33 | @Immutable 34 | private data class PageSagerState( 35 | val isInitialized: Boolean, 36 | ) 37 | 38 | @ExperimentalKatalogExtApi 39 | @Composable 40 | private fun rememberPageSagerState( 41 | navState: ExtNavState, 42 | pageStore: PageStore = rememberPageStore(), 43 | ): PageSagerState { 44 | var isInitialized by remember { mutableStateOf(false) } 45 | 46 | LaunchedEffect(Unit) { 47 | val backStack = pageStore.read() 48 | if (backStack != null && navState.backStack != backStack) { 49 | navState.restore(backStack) 50 | } 51 | 52 | snapshotFlow { navState.backStack } 53 | .onEach { pageStore.update(navState.backStack) } 54 | .launchIn(this) 55 | 56 | isInitialized = true 57 | } 58 | 59 | return PageSagerState(isInitialized) 60 | } 61 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/DropdownMenu.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxSize 5 | import androidx.compose.foundation.layout.wrapContentSize 6 | import androidx.compose.material.Divider 7 | import androidx.compose.material.DropdownMenu 8 | import androidx.compose.material.DropdownMenuItem 9 | import androidx.compose.material.Icon 10 | import androidx.compose.material.IconButton 11 | import androidx.compose.material.Text 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.filled.MoreVert 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.runtime.getValue 16 | import androidx.compose.runtime.mutableStateOf 17 | import androidx.compose.runtime.remember 18 | import androidx.compose.runtime.setValue 19 | import androidx.compose.ui.Alignment 20 | import androidx.compose.ui.Modifier 21 | 22 | @Composable 23 | fun SampleDropdownMenu() { 24 | var expanded by remember { mutableStateOf(false) } 25 | 26 | Box(modifier = Modifier.fillMaxSize().wrapContentSize(Alignment.TopStart)) { 27 | IconButton(onClick = { expanded = true }) { 28 | Icon(Icons.Default.MoreVert, contentDescription = "Localized description") 29 | } 30 | DropdownMenu( 31 | expanded = expanded, 32 | onDismissRequest = { expanded = false }, 33 | ) { 34 | DropdownMenuItem(onClick = { /* Handle refresh! */ }) { 35 | Text("Refresh") 36 | } 37 | DropdownMenuItem(onClick = { /* Handle settings! */ }) { 38 | Text("Settings") 39 | } 40 | Divider() 41 | DropdownMenuItem(onClick = { /* Handle send feedback! */ }) { 42 | Text("Send Feedback") 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /extensions/pagesaver/src/test/kotlin/com/moriatsushi/katalog/ext/pagesaver/internal/PageSaverTest.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.ext.pagesaver.internal 2 | 3 | import androidx.compose.ui.test.junit4.createComposeRule 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import com.google.common.truth.Truth.assertThat 6 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 7 | import org.junit.Rule 8 | import org.junit.Test 9 | import org.junit.runner.RunWith 10 | 11 | @ExperimentalKatalogExtApi 12 | @RunWith(AndroidJUnit4::class) 13 | internal class PageSaverTest { 14 | @get:Rule 15 | val composeTestRule = createComposeRule() 16 | 17 | @Test 18 | fun save() { 19 | val navState = DummyExtNavState() 20 | val pageStore = PageStore( 21 | DummyLocalStorage(), 22 | ) 23 | assertThat(pageStore.read()).isNull() 24 | 25 | composeTestRule.setContent { 26 | PageSaver( 27 | navState = navState, 28 | pageStore = pageStore, 29 | ) {} 30 | } 31 | composeTestRule.waitForIdle() 32 | assertThat(pageStore.read()).isEqualTo(listOf("/")) 33 | 34 | navState.navigateTo("/Group") 35 | navState.navigateTo("/Group/Item") 36 | 37 | composeTestRule.waitForIdle() 38 | assertThat(pageStore.read()).isEqualTo(listOf("/", "/Group", "/Group/Item")) 39 | } 40 | 41 | @Test 42 | fun restore() { 43 | val navState = DummyExtNavState() 44 | val pageStore = PageStore( 45 | DummyLocalStorage(), 46 | ) 47 | pageStore.update(listOf("/", "/Group", "/Group/Item")) 48 | assertThat(navState.backStack).isEqualTo(listOf("/")) 49 | 50 | composeTestRule.setContent { 51 | PageSaver( 52 | navState = navState, 53 | pageStore = pageStore, 54 | ) {} 55 | } 56 | composeTestRule.waitForIdle() 57 | assertThat(navState.backStack).isEqualTo(listOf("/", "/Group", "/Group/Item")) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /extensions/pagesaver/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("com.vanniktech.maven.publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | defaultConfig { 10 | minSdk = 21 11 | 12 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 13 | } 14 | buildFeatures { 15 | compose = true 16 | } 17 | compileOptions { 18 | sourceCompatibility = JavaVersion.VERSION_17 19 | targetCompatibility = JavaVersion.VERSION_17 20 | } 21 | kotlinOptions { 22 | jvmTarget = "17" 23 | freeCompilerArgs = freeCompilerArgs + listOf( 24 | "-Xexplicit-api=strict", 25 | "-Xopt-in=kotlin.RequiresOptIn" 26 | ) 27 | } 28 | composeOptions { 29 | kotlinCompilerExtensionVersion = 30 | libs.versions.androidx.compose.compiler.get() 31 | } 32 | sourceSets { 33 | getByName("main").java.srcDir("src/main/kotlin") 34 | getByName("test").java.srcDir("src/test/kotlin") 35 | getByName("androidTest").java.srcDir("src/androidTest/kotlin") 36 | } 37 | testOptions { 38 | unitTests { 39 | isIncludeAndroidResources = true 40 | } 41 | } 42 | namespace = "com.moriatsushi.katalog.ext.pagesaver" 43 | } 44 | 45 | dependencies { 46 | implementation(kotlin("stdlib")) 47 | implementation(project(":katalog")) 48 | 49 | implementation(libs.kotlinx.serialization.json) 50 | implementation(libs.androidx.compose.foundation) 51 | implementation(libs.androidx.compose.foundation) 52 | 53 | testImplementation(libs.androidx.test.core) 54 | testImplementation(libs.androidx.test.runner) 55 | testImplementation(libs.androidx.test.rules) 56 | testImplementation(libs.androidx.test.ext.junit) 57 | testImplementation(libs.androidx.test.ext.truth) 58 | 59 | testImplementation(libs.androidx.compose.ui.test) 60 | testImplementation(libs.androidx.compose.ui.test.manifest) 61 | testImplementation(libs.robolectric) 62 | } 63 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/TriStateCheckbox.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.Checkbox 6 | import androidx.compose.material.CheckboxDefaults 7 | import androidx.compose.material.MaterialTheme 8 | import androidx.compose.material.TriStateCheckbox 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.runtime.mutableStateOf 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.state.ToggleableState 14 | import androidx.compose.ui.unit.dp 15 | 16 | @Composable 17 | fun SampleTriStateCheckbox() { 18 | Column { 19 | // define dependent checkboxes states 20 | val (state, onStateChange) = remember { mutableStateOf(true) } 21 | val (state2, onStateChange2) = remember { mutableStateOf(true) } 22 | 23 | // TriStateCheckbox state reflects state of dependent checkboxes 24 | val parentState = remember(state, state2) { 25 | if (state && state2) { 26 | ToggleableState.On 27 | } else if (!state && !state2) { 28 | ToggleableState.Off 29 | } else { 30 | ToggleableState.Indeterminate 31 | } 32 | } 33 | // click on TriStateCheckbox can set state for dependent checkboxes 34 | val onParentClick = { 35 | val s = parentState != ToggleableState.On 36 | onStateChange(s) 37 | onStateChange2(s) 38 | } 39 | 40 | TriStateCheckbox( 41 | state = parentState, 42 | onClick = onParentClick, 43 | colors = CheckboxDefaults.colors( 44 | checkedColor = MaterialTheme.colors.primary, 45 | ), 46 | ) 47 | Column(Modifier.padding(10.dp, 0.dp, 0.dp, 0.dp)) { 48 | Checkbox(state, onStateChange) 49 | Checkbox(state2, onStateChange2) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /katalog-androidview/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | id("kotlin-android") 4 | id("kotlin-kapt") 5 | id("com.vanniktech.maven.publish") 6 | } 7 | 8 | android { 9 | compileSdk = 34 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | } 16 | buildFeatures { 17 | dataBinding = true 18 | viewBinding = true 19 | compose = true 20 | } 21 | compileOptions { 22 | sourceCompatibility = JavaVersion.VERSION_17 23 | targetCompatibility = JavaVersion.VERSION_17 24 | } 25 | kotlinOptions { 26 | jvmTarget = "17" 27 | freeCompilerArgs = freeCompilerArgs + listOf( 28 | "-Xexplicit-api=strict" 29 | ) 30 | } 31 | composeOptions { 32 | kotlinCompilerExtensionVersion = 33 | libs.versions.androidx.compose.compiler.get() 34 | } 35 | sourceSets { 36 | getByName("main").java.srcDir("src/main/kotlin") 37 | getByName("test").java.srcDir("src/test/kotlin") 38 | getByName("androidTest").java.srcDir("src/androidTest/kotlin") 39 | } 40 | namespace = "com.moriatsushi.katalog.androidview" 41 | } 42 | 43 | dependencies { 44 | implementation(project(":katalog")) 45 | 46 | implementation(kotlin("stdlib")) 47 | 48 | implementation(libs.androidx.core) 49 | implementation(libs.androidx.activity) 50 | implementation(libs.androidx.fragment) 51 | 52 | implementation(libs.androidx.compose.ui) 53 | implementation(libs.androidx.compose.ui.tooling) 54 | implementation(libs.androidx.compose.foundation) 55 | implementation(libs.androidx.compose.material) 56 | 57 | implementation(libs.androidx.lifecycle.runtime) 58 | implementation(libs.androidx.lifecycle.viewmodel.compose) 59 | 60 | implementation(libs.androidx.annotation) 61 | 62 | testImplementation(libs.androidx.test.core) 63 | testImplementation(libs.androidx.test.runner) 64 | testImplementation(libs.androidx.test.rules) 65 | testImplementation(libs.androidx.test.ext.junit) 66 | testImplementation(libs.androidx.test.ext.truth) 67 | } 68 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/page/PreviewPage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.page 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.material.Icon 7 | import androidx.compose.material.IconButton 8 | import androidx.compose.material.MaterialTheme 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.Close 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import com.moriatsushi.katalog.compose.widget.KatalogTopAppBar 14 | import com.moriatsushi.katalog.compose.widget.Preview 15 | import com.moriatsushi.katalog.domain.CatalogItem 16 | import com.moriatsushi.katalog.domain.Extensions 17 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 18 | import com.moriatsushi.katalog.ext.ExtNavState 19 | 20 | @ExperimentalKatalogExtApi 21 | @Composable 22 | internal fun PreviewPage( 23 | component: CatalogItem.Component, 24 | extensions: Extensions, 25 | extNavState: ExtNavState, 26 | onClickClose: () -> Unit, 27 | ) { 28 | Column( 29 | modifier = Modifier 30 | .fillMaxSize() 31 | .background(MaterialTheme.colors.background), 32 | ) { 33 | PreviewTopAppBar( 34 | name = component.name, 35 | onClickClose = onClickClose, 36 | ) 37 | Preview( 38 | extensions = extensions, 39 | extNavState = extNavState, 40 | modifier = Modifier.fillMaxSize(), 41 | clickable = true, 42 | definition = component.definition, 43 | ) 44 | } 45 | } 46 | 47 | @Composable 48 | private fun PreviewTopAppBar( 49 | name: String, 50 | onClickClose: () -> Unit, 51 | ) { 52 | KatalogTopAppBar( 53 | title = name, 54 | navigationIcon = { 55 | IconButton(onClick = onClickClose) { 56 | Icon( 57 | imageVector = Icons.Filled.Close, 58 | contentDescription = "close", 59 | ) 60 | } 61 | }, 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/androidview/util/LifecycleOwner.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidview.util 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.DisposableEffect 5 | import androidx.compose.runtime.remember 6 | import androidx.compose.ui.platform.LocalLifecycleOwner 7 | import androidx.lifecycle.Lifecycle 8 | import androidx.lifecycle.LifecycleEventObserver 9 | import androidx.lifecycle.LifecycleOwner 10 | import androidx.lifecycle.LifecycleRegistry 11 | 12 | @Composable 13 | internal fun rememberLifecycleOwner(): LifecycleOwner { 14 | val hostLifecycleOwner = LocalLifecycleOwner.current 15 | val lifecycleOwner = remember(hostLifecycleOwner) { 16 | ComposeLifecycleOwner(hostLifecycleOwner) 17 | } 18 | DisposableEffect(lifecycleOwner) { 19 | lifecycleOwner.onEnter() 20 | onDispose { 21 | lifecycleOwner.onDispose() 22 | } 23 | } 24 | return lifecycleOwner 25 | } 26 | 27 | private class ComposeLifecycleOwner( 28 | private val hostLifecycleOwner: LifecycleOwner, 29 | ) : LifecycleOwner { 30 | private val registry = LifecycleRegistry(this) 31 | private var maxLifecycleState = Lifecycle.State.INITIALIZED 32 | 33 | override val lifecycle: Lifecycle 34 | get() = registry 35 | 36 | init { 37 | hostLifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver { 38 | override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { 39 | updateState() 40 | } 41 | }) 42 | } 43 | 44 | private fun updateState() { 45 | val hostLifecycleState = hostLifecycleOwner.lifecycle.currentState 46 | if (hostLifecycleState.ordinal < maxLifecycleState.ordinal) { 47 | registry.currentState = hostLifecycleState 48 | } else { 49 | registry.currentState = maxLifecycleState 50 | } 51 | } 52 | 53 | fun onEnter() { 54 | maxLifecycleState = Lifecycle.State.RESUMED 55 | updateState() 56 | } 57 | 58 | fun onDispose() { 59 | maxLifecycleState = Lifecycle.State.DESTROYED 60 | updateState() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/domain/GroupScopeImpl.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.domain 2 | 3 | import com.moriatsushi.katalog.dsl.ComposeDefinition 4 | import com.moriatsushi.katalog.dsl.Group 5 | import com.moriatsushi.katalog.dsl.GroupDefinition 6 | import com.moriatsushi.katalog.dsl.GroupScope 7 | 8 | internal class GroupScopeImpl( 9 | private val parentIdentifier: CatalogItemIdentifier? = null, 10 | ) : GroupScope { 11 | private var _items = mutableListOf() 12 | val items: List get() = _items 13 | 14 | override fun group(name: String, definition: GroupDefinition) { 15 | val item = dslToModel( 16 | name = name, 17 | definition = definition, 18 | ) 19 | _items.add(item) 20 | } 21 | 22 | override fun group(vararg group: Group) { 23 | group.forEach { 24 | group(it.name, it.definition) 25 | } 26 | } 27 | 28 | override fun compose( 29 | name: String, 30 | definition: ComposeDefinition, 31 | ) { 32 | val item = dslToModel(name, definition) 33 | _items.add(item) 34 | } 35 | 36 | private fun dslToModel( 37 | name: String, 38 | definition: GroupDefinition, 39 | ): CatalogItem.Group { 40 | val identifier = createIdentifier(name) 41 | val groupScope = GroupScopeImpl(identifier) 42 | definition.invoke(groupScope) 43 | return CatalogItem.Group( 44 | identifier = identifier, 45 | items = groupScope.items, 46 | ) 47 | } 48 | 49 | private fun dslToModel( 50 | name: String, 51 | definition: ComposeDefinition, 52 | ): CatalogItem.Component { 53 | val identifier = createIdentifier(name) 54 | return CatalogItem.Component( 55 | identifier = identifier, 56 | definition = definition, 57 | ) 58 | } 59 | 60 | private fun createIdentifier(name: String): CatalogItemIdentifier { 61 | return CatalogItemIdentifier.of( 62 | parent = parentIdentifier, 63 | name = name, 64 | count = items.count { 65 | it.name == name 66 | }, 67 | ) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /docs/docs/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | import { Preview } from '/src/widgets/Preview'; 6 | import useBaseUrl from '@docusaurus/useBaseUrl'; 7 | 8 | # Getting Started 9 | ## step1: Add the dependency 10 | 11 | Add Maven Central repository to your `build.gradle`. 12 | 13 | ```kotlin 14 | repositories { 15 | mavenCentral() 16 | } 17 | ``` 18 | 19 | Add the package dependencies to your `build.gradle`. 20 | 21 | ```kotlin 22 | dependencies { 23 | implementation("com.moriatsushi.katalog:katalog:1.2.2") 24 | } 25 | ``` 26 | 27 | ## step2: Register the UI component 28 | 29 | 33 | 34 | Just run the `registerKatalog` function in your application. 35 | To register a `Composable`, use the `compose` function. 36 | 37 | ```kotlin 38 | class MyApplication : Application() { 39 | override fun onCreate() { 40 | super.onCreate() 41 | 42 | registerKatalog( 43 | title = "My App Catalog" 44 | ) { 45 | compose("UI Component") { 46 | Text(text = "Hello, World") 47 | } 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | 54 | 55 | 59 | 60 | You can use the `group` function to group components. 61 | 62 | ```kotlin 63 | registerKatalog( 64 | title = "My App Catalog" 65 | ) { 66 | group("Group 1") { 67 | compose("UI Component") { 68 | /* ... */ 69 | } 70 | } 71 | 72 | group("Group 2") { 73 | /* ... */ 74 | } 75 | } 76 | ``` 77 | 78 | The `group` can also be assigned to a variable. 79 | 80 | ```kotlin 81 | val group1 = group("Group 1") { 82 | /* ... */ 83 | } 84 | val group2 = group("Group 2") { 85 | /* ... */ 86 | } 87 | registerKatalog { 88 | title = "My App Catalog" 89 | group(group1, group2) 90 | } 91 | ``` 92 | 93 | 94 | 95 | ## step3: Start Catalog Activity 96 | 97 | Start `KatalogActivity` from your debug menu. 98 | 99 | ```kotlin 100 | KatalogActivity.start(activity) 101 | ``` 102 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/jp/co/cyberagent/katalog/dsl/ViewExt.kt: -------------------------------------------------------------------------------- 1 | package jp.co.cyberagent.katalog.dsl 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import com.moriatsushi.katalog.dsl.view as actualView 6 | 7 | @Deprecated( 8 | "The package name has changed.", 9 | ReplaceWith( 10 | "ViewDefinition", 11 | "com.moriatsushi.katalog.dsl.ViewDefinition", 12 | ), 13 | ) 14 | public typealias ViewDefinition = com.moriatsushi.katalog.dsl.ViewDefinition 15 | 16 | @Deprecated( 17 | "The package name has changed.", 18 | ReplaceWith( 19 | "WRAP_WIDTH_WRAP_HEIGHT", 20 | "com.moriatsushi.katalog.dsl.WRAP_WIDTH_WRAP_HEIGHT", 21 | ), 22 | ) 23 | public val WRAP_WIDTH_WRAP_HEIGHT: ViewGroup.LayoutParams = 24 | com.moriatsushi.katalog.dsl.WRAP_WIDTH_WRAP_HEIGHT 25 | 26 | @Deprecated( 27 | "The package name has changed.", 28 | ReplaceWith( 29 | "WRAP_WIDTH_MATCH_HEIGHT", 30 | "com.moriatsushi.katalog.dsl.WRAP_WIDTH_MATCH_HEIGHT", 31 | ), 32 | ) 33 | public val WRAP_WIDTH_MATCH_HEIGHT: ViewGroup.LayoutParams = 34 | com.moriatsushi.katalog.dsl.WRAP_WIDTH_MATCH_HEIGHT 35 | 36 | @Deprecated( 37 | "The package name has changed.", 38 | ReplaceWith( 39 | "MATCH_WIDTH_WRAP_HEIGHT", 40 | "com.moriatsushi.katalog.dsl.MATCH_WIDTH_WRAP_HEIGHT", 41 | ), 42 | ) 43 | public val MATCH_WIDTH_WRAP_HEIGHT: ViewGroup.LayoutParams = 44 | com.moriatsushi.katalog.dsl.MATCH_WIDTH_WRAP_HEIGHT 45 | 46 | @Deprecated( 47 | "The package name has changed.", 48 | ReplaceWith( 49 | "MATCH_WIDTH_MATCH_HEIGHT", 50 | "com.moriatsushi.katalog.dsl.MATCH_WIDTH_MATCH_HEIGHT", 51 | ), 52 | ) 53 | public val MATCH_WIDTH_MATCH_HEIGHT: ViewGroup.LayoutParams = 54 | com.moriatsushi.katalog.dsl.MATCH_WIDTH_MATCH_HEIGHT 55 | 56 | @Deprecated( 57 | "The package name has changed.", 58 | ReplaceWith( 59 | "view(name, layoutParams, definition)", 60 | "com.moriatsushi.katalog.dsl.view", 61 | ), 62 | ) 63 | public inline fun GroupScope.view( 64 | name: String? = null, 65 | layoutParams: ViewGroup.LayoutParams = WRAP_WIDTH_WRAP_HEIGHT, 66 | noinline definition: ViewDefinition, 67 | ) { 68 | actualView(name, layoutParams, definition) 69 | } 70 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/ErrorMessage.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.foundation.layout.Arrangement 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material.Button 9 | import androidx.compose.material.Icon 10 | import androidx.compose.material.MaterialTheme 11 | import androidx.compose.material.Text 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.filled.Error 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.platform.LocalContext 18 | import androidx.compose.ui.text.style.TextAlign 19 | import androidx.compose.ui.unit.dp 20 | import androidx.compose.ui.unit.sp 21 | import com.moriatsushi.katalog.compose.res.defaultPadding 22 | import com.moriatsushi.katalog.compose.util.Urls 23 | import com.moriatsushi.katalog.compose.util.openBrowser 24 | 25 | @Composable 26 | internal fun ErrorMessage(text: String) { 27 | val context = LocalContext.current 28 | 29 | Column( 30 | modifier = Modifier 31 | .fillMaxSize() 32 | .padding(horizontal = defaultPadding, vertical = 24.dp), 33 | horizontalAlignment = Alignment.CenterHorizontally, 34 | verticalArrangement = Arrangement.Center, 35 | ) { 36 | Icon( 37 | imageVector = Icons.Filled.Error, 38 | contentDescription = null, 39 | modifier = Modifier 40 | .padding(bottom = 16.dp) 41 | .size(36.dp), 42 | tint = MaterialTheme.colors.onBackground.copy(alpha = 0.6F), 43 | ) 44 | Text( 45 | text = text, 46 | modifier = Modifier 47 | .padding(bottom = 32.dp), 48 | textAlign = TextAlign.Center, 49 | color = MaterialTheme.colors.onBackground.copy(alpha = 0.4F), 50 | fontSize = 16.sp, 51 | ) 52 | Button(onClick = { 53 | openBrowser(context, Urls.documents) 54 | }) { 55 | Text(text = "Documents") 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/widget/CatalogItemList.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.widget 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.foundation.lazy.LazyColumn 8 | import androidx.compose.foundation.lazy.LazyListState 9 | import androidx.compose.foundation.lazy.items 10 | import androidx.compose.foundation.lazy.rememberLazyListState 11 | import androidx.compose.material.Icon 12 | import androidx.compose.material.MaterialTheme 13 | import androidx.compose.material.icons.Icons 14 | import androidx.compose.material.icons.filled.Circle 15 | import androidx.compose.runtime.Composable 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.unit.dp 19 | import com.moriatsushi.katalog.domain.CatalogItem 20 | import com.moriatsushi.katalog.domain.Extensions 21 | import com.moriatsushi.katalog.ext.ExperimentalKatalogExtApi 22 | import com.moriatsushi.katalog.ext.ExtNavState 23 | 24 | @ExperimentalKatalogExtApi 25 | @Composable 26 | internal fun CatalogItemList( 27 | list: List, 28 | extensions: Extensions, 29 | extNavState: ExtNavState, 30 | onClick: (item: CatalogItem) -> Unit, 31 | lazyListState: LazyListState = rememberLazyListState(), 32 | ) { 33 | if (list.isEmpty()) { 34 | Empty() 35 | return 36 | } 37 | 38 | LazyColumn( 39 | state = lazyListState, 40 | ) { 41 | items(list) { 42 | CatalogItemRow( 43 | item = it, 44 | extensions = extensions, 45 | extNavState = extNavState, 46 | onClick = onClick, 47 | ) 48 | } 49 | item { 50 | LastItem() 51 | } 52 | } 53 | } 54 | 55 | @Composable 56 | private fun LastItem() { 57 | Box( 58 | modifier = Modifier 59 | .fillMaxWidth() 60 | .padding(top = 16.dp, bottom = 32.dp), 61 | contentAlignment = Alignment.Center, 62 | ) { 63 | Icon( 64 | modifier = Modifier.size(5.dp), 65 | imageVector = Icons.Filled.Circle, 66 | contentDescription = null, 67 | tint = MaterialTheme.colors.onBackground.copy(alpha = 0.3F), 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /samples/androidapp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("kotlin-kapt") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "com.moriatsushi.katalog.android_sample" 12 | minSdk = 21 13 | targetSdk = 33 14 | versionCode = 1 15 | versionName = "1.0.0" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | buildFeatures { 20 | dataBinding = true 21 | viewBinding = true 22 | compose = true 23 | } 24 | buildTypes { 25 | getByName("release") { 26 | isMinifyEnabled = false 27 | proguardFiles( 28 | getDefaultProguardFile("proguard-android-optimize.txt"), 29 | "proguard-rules.pro" 30 | ) 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | kotlinOptions { 38 | jvmTarget = "17" 39 | } 40 | composeOptions { 41 | kotlinCompilerExtensionVersion = 42 | libs.versions.androidx.compose.compiler.get() 43 | } 44 | sourceSets { 45 | getByName("main").java.srcDir("src/main/kotlin") 46 | getByName("test").java.srcDir("src/test/kotlin") 47 | getByName("androidTest").java.srcDir("src/androidTest/kotlin") 48 | } 49 | namespace = "com.moriatsushi.katalog.androidsample" 50 | } 51 | 52 | dependencies { 53 | implementation(project(":katalog")) 54 | implementation(project(":katalog-androidview")) 55 | implementation(project(":extensions:androidtheme")) 56 | implementation(project(":extensions:theme")) 57 | implementation(project(":extensions:pagesaver")) 58 | 59 | implementation(kotlin("stdlib")) 60 | implementation(libs.kotlinx.coroutines.android) 61 | 62 | implementation(libs.androidx.activity) 63 | implementation(libs.androidx.activity.compose) 64 | implementation(libs.androidx.fragment) 65 | 66 | implementation(libs.androidx.compose.ui) 67 | implementation(libs.androidx.compose.ui.tooling) 68 | implementation(libs.androidx.compose.foundation) 69 | implementation(libs.androidx.compose.material) 70 | implementation(libs.androidx.compose.material.icons.core) 71 | implementation(libs.androidx.compose.material.icons.extended) 72 | 73 | implementation(libs.material) 74 | } 75 | -------------------------------------------------------------------------------- /katalog/src/main/kotlin/com/moriatsushi/katalog/compose/navigation/NavRoot.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.compose.navigation 2 | 3 | import androidx.compose.animation.AnimatedContent 4 | import androidx.compose.animation.AnimatedContentTransitionScope 5 | import androidx.compose.animation.ContentTransform 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.DisposableEffect 9 | import androidx.compose.runtime.saveable.SaveableStateHolder 10 | import androidx.compose.runtime.saveable.rememberSaveableStateHolder 11 | import androidx.compose.ui.Modifier 12 | import com.moriatsushi.katalog.compose.widget.ClickMask 13 | 14 | @Composable 15 | internal fun NavRoot( 16 | navController: NavController, 17 | transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform = 18 | NavAnimation.createSlideSpec(), 19 | component: @Composable (NavState) -> Unit, 20 | ) { 21 | val saveableStateHolder = rememberSaveableStateHolder() 22 | 23 | AnimatedPage( 24 | targetState = navController.current, 25 | transitionSpec = transitionSpec, 26 | ) { 27 | NavChild( 28 | navController = navController, 29 | state = it, 30 | saveableStateHolder = saveableStateHolder, 31 | component = component, 32 | ) 33 | } 34 | } 35 | 36 | @Composable 37 | private fun NavChild( 38 | navController: NavController, 39 | state: NavState, 40 | saveableStateHolder: SaveableStateHolder, 41 | component: @Composable (NavState) -> Unit, 42 | ) { 43 | DisposableEffect(state) { 44 | onDispose { 45 | if (!navController.hasState(state)) { 46 | saveableStateHolder.removeState(state.key) 47 | } 48 | } 49 | } 50 | saveableStateHolder.SaveableStateProvider(state.key) { 51 | component(state) 52 | } 53 | } 54 | 55 | @Composable 56 | private fun AnimatedPage( 57 | targetState: NavState, 58 | transitionSpec: AnimatedContentTransitionScope>.() -> ContentTransform, 59 | content: @Composable (state: NavState) -> Unit, 60 | ) { 61 | AnimatedContent( 62 | targetState = targetState, 63 | transitionSpec = transitionSpec, 64 | ) { 65 | content(it) 66 | ClickMask( 67 | modifier = Modifier.fillMaxSize(), 68 | enabled = it != targetState, 69 | ) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/Scaffold.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.fillMaxWidth 5 | import androidx.compose.foundation.layout.height 6 | import androidx.compose.foundation.lazy.LazyColumn 7 | import androidx.compose.material.ExtendedFloatingActionButton 8 | import androidx.compose.material.FabPosition 9 | import androidx.compose.material.Icon 10 | import androidx.compose.material.IconButton 11 | import androidx.compose.material.Scaffold 12 | import androidx.compose.material.Text 13 | import androidx.compose.material.TopAppBar 14 | import androidx.compose.material.icons.Icons 15 | import androidx.compose.material.icons.filled.Menu 16 | import androidx.compose.material.rememberScaffoldState 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.rememberCoroutineScope 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.unit.dp 21 | import kotlinx.coroutines.launch 22 | 23 | @Composable 24 | fun SampleScaffold() { 25 | val scaffoldState = rememberScaffoldState() 26 | val scope = rememberCoroutineScope() 27 | Scaffold( 28 | scaffoldState = scaffoldState, 29 | drawerContent = { Text("Drawer content") }, 30 | topBar = { 31 | TopAppBar( 32 | title = { Text("Simple Scaffold Screen") }, 33 | navigationIcon = { 34 | IconButton( 35 | onClick = { 36 | scope.launch { scaffoldState.drawerState.open() } 37 | }, 38 | ) { 39 | Icon(Icons.Filled.Menu, contentDescription = "Localized description") 40 | } 41 | }, 42 | ) 43 | }, 44 | floatingActionButtonPosition = FabPosition.End, 45 | floatingActionButton = { 46 | ExtendedFloatingActionButton( 47 | text = { Text("Inc") }, 48 | onClick = { /* fab click handler */ }, 49 | ) 50 | }, 51 | content = { innerPadding -> 52 | LazyColumn(contentPadding = innerPadding) { 53 | items(count = 100) { 54 | Box( 55 | Modifier 56 | .fillMaxWidth() 57 | .height(50.dp), 58 | ) 59 | } 60 | } 61 | }, 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /katalog/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.publish) 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | 13 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | buildFeatures { 16 | compose = true 17 | } 18 | compileOptions { 19 | sourceCompatibility = JavaVersion.VERSION_17 20 | targetCompatibility = JavaVersion.VERSION_17 21 | } 22 | kotlinOptions { 23 | jvmTarget = "17" 24 | freeCompilerArgs = freeCompilerArgs + listOf( 25 | "-Xexplicit-api=strict", 26 | "-Xopt-in=kotlin.RequiresOptIn" 27 | ) 28 | } 29 | composeOptions { 30 | kotlinCompilerExtensionVersion = 31 | libs.versions.androidx.compose.compiler.get() 32 | } 33 | sourceSets { 34 | getByName("main").java.srcDir("src/main/kotlin") 35 | getByName("test").java.srcDir("src/test/kotlin") 36 | getByName("androidTest").java.srcDir("src/androidTest/kotlin") 37 | } 38 | testOptions { 39 | unitTests { 40 | isIncludeAndroidResources = true 41 | } 42 | } 43 | namespace = "com.moriatsushi.katalog" 44 | } 45 | 46 | dependencies { 47 | implementation(kotlin("stdlib")) 48 | implementation(libs.kotlinx.coroutines.android) 49 | 50 | implementation(libs.androidx.core) 51 | implementation(libs.androidx.activity) 52 | implementation(libs.androidx.activity.compose) 53 | 54 | implementation(libs.androidx.compose.ui) 55 | implementation(libs.androidx.compose.ui.tooling) 56 | implementation(libs.androidx.compose.foundation) 57 | implementation(libs.androidx.compose.material) 58 | implementation(libs.androidx.compose.material.icons.core) 59 | implementation(libs.androidx.compose.material.icons.extended) 60 | 61 | implementation(libs.androidx.lifecycle.viewmodel) 62 | implementation(libs.androidx.lifecycle.viewmodel.compose) 63 | 64 | implementation(libs.androidx.annotation) 65 | 66 | implementation(libs.material) 67 | 68 | testImplementation(libs.androidx.test.core) 69 | testImplementation(libs.androidx.test.runner) 70 | testImplementation(libs.androidx.test.rules) 71 | testImplementation(libs.androidx.test.ext.junit) 72 | testImplementation(libs.androidx.test.ext.truth) 73 | 74 | testImplementation(libs.kotlinx.coroutines.test) 75 | testImplementation(libs.androidx.compose.ui.test) 76 | testImplementation(libs.androidx.compose.ui.test.manifest) 77 | testImplementation(libs.robolectric) 78 | } 79 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/res/layout/fragment_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | 21 | 22 | 29 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | 49 | 50 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /samples/androidapp/src/main/kotlin/com/moriatsushi/katalog/androidsample/compose/material/RadioButton.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidsample.compose.material 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.foundation.selection.selectable 9 | import androidx.compose.foundation.selection.selectableGroup 10 | import androidx.compose.material.MaterialTheme 11 | import androidx.compose.material.RadioButton 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.runtime.mutableStateOf 16 | import androidx.compose.runtime.remember 17 | import androidx.compose.runtime.setValue 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.semantics.Role 21 | import androidx.compose.ui.unit.dp 22 | 23 | @Composable 24 | fun SampleRadioButton() { 25 | // We have two radio buttons and only one can be selected 26 | var state by remember { mutableStateOf(true) } 27 | Row(Modifier.selectableGroup()) { 28 | RadioButton( 29 | selected = state, 30 | onClick = { state = true }, 31 | ) 32 | RadioButton( 33 | selected = !state, 34 | onClick = { state = false }, 35 | ) 36 | } 37 | } 38 | 39 | @Composable 40 | fun SampleRadioButtonWithLabels() { 41 | val radioOptions = listOf("Calls", "Missed", "Friends") 42 | val (selectedOption, onOptionSelected) = remember { mutableStateOf(radioOptions[0]) } 43 | Column(Modifier.selectableGroup()) { 44 | radioOptions.forEach { text -> 45 | Row( 46 | Modifier 47 | .fillMaxWidth() 48 | .height(56.dp) 49 | .selectable( 50 | selected = (text == selectedOption), 51 | onClick = { onOptionSelected(text) }, 52 | role = Role.RadioButton, 53 | ) 54 | .padding(horizontal = 16.dp), 55 | verticalAlignment = Alignment.CenterVertically, 56 | ) { 57 | RadioButton( 58 | selected = (text == selectedOption), 59 | onClick = null, // null recommended for accessibility with screenreaders 60 | ) 61 | Text( 62 | text = text, 63 | style = MaterialTheme.typography.body1.merge(), 64 | modifier = Modifier.padding(start = 16.dp), 65 | ) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /katalog-androidview/src/main/kotlin/com/moriatsushi/katalog/androidview/mapper/FragmentToCompose.kt: -------------------------------------------------------------------------------- 1 | package com.moriatsushi.katalog.androidview.mapper 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.runtime.DisposableEffect 7 | import androidx.compose.runtime.State 8 | import androidx.compose.runtime.getValue 9 | import androidx.compose.runtime.mutableStateOf 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.ui.platform.LocalContext 12 | import androidx.compose.ui.viewinterop.AndroidView 13 | import androidx.fragment.app.Fragment 14 | import androidx.fragment.app.FragmentContainerView 15 | import androidx.fragment.app.commitNow 16 | import com.moriatsushi.katalog.androidview.util.findFragmentManager 17 | import com.moriatsushi.katalog.dsl.FragmentDefinition 18 | import com.moriatsushi.katalog.dsl.FragmentOnCreateListener 19 | import java.util.UUID 20 | 21 | @Composable 22 | internal fun FragmentToCompose( 23 | layoutParams: ViewGroup.LayoutParams? = null, 24 | onCreateView: FragmentOnCreateListener, 25 | definition: FragmentDefinition, 26 | ) { 27 | val view by fragmentViewState( 28 | definition = definition, 29 | onCreateView = onCreateView, 30 | ) 31 | view?.let { 32 | FragmentContainerView( 33 | layoutParams = layoutParams, 34 | view = it, 35 | ) 36 | } 37 | } 38 | 39 | @Composable 40 | private fun fragmentViewState( 41 | definition: FragmentDefinition, 42 | onCreateView: FragmentOnCreateListener, 43 | ): State { 44 | val context = LocalContext.current 45 | val fragmentManager = remember(context) { 46 | context.findFragmentManager() ?: error("not found FragmentManager") 47 | } 48 | val view = remember { mutableStateOf(null) } 49 | val scope = rememberViewDefinitionScope() 50 | DisposableEffect(definition) { 51 | val tag = UUID.randomUUID().toString() 52 | val fragment = definition.invoke(scope) 53 | fragmentManager.commitNow(true) { 54 | add(fragment, tag) 55 | } 56 | view.value = fragment.view 57 | onCreateView.invoke(scope, fragment) 58 | onDispose { 59 | fragmentManager.commitNow(true) { 60 | remove(fragment) 61 | } 62 | } 63 | } 64 | return view 65 | } 66 | 67 | @Composable 68 | private fun FragmentContainerView( 69 | layoutParams: ViewGroup.LayoutParams? = null, 70 | view: View, 71 | ) { 72 | AndroidView( 73 | factory = { 74 | val container = FragmentContainerView(it) 75 | if (layoutParams != null) { 76 | container.layoutParams = layoutParams 77 | } 78 | container.addView(view) 79 | container 80 | }, 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /docs/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | const lightCodeTheme = require('prism-react-renderer/themes/github'); 2 | const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 3 | 4 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 5 | module.exports = { 6 | title: 'Katalog - A UI Catalog Library made with Jetpack Compose', 7 | tagline: 'A UI Catalog Library made with Jetpack Compose', 8 | url: 'https://mori-atsushi.github.io', 9 | baseUrl: '/katalog/', 10 | onBrokenLinks: 'throw', 11 | onBrokenMarkdownLinks: 'warn', 12 | favicon: 'img/favicon.ico', 13 | organizationName: 'mori-atsushi', 14 | projectName: 'katalog', 15 | trailingSlash: false, 16 | themeConfig: { 17 | image: 'img/ogp.png', 18 | navbar: { 19 | title: 'Katalog', 20 | logo: { 21 | alt: 'Katalog Logo', 22 | src: 'img/logo.svg', 23 | }, 24 | items: [ 25 | { 26 | to: 'docs/getting-started', 27 | position: 'left', 28 | label: 'Getting Started', 29 | }, 30 | { 31 | to: 'docs/main/register-and-start', 32 | activeBasePath: 'docs/main', 33 | position: 'left', 34 | label: 'Documentation', 35 | }, 36 | { 37 | href: 'https://github.com/mori-atsushi/katalog', 38 | label: 'GitHub', 39 | position: 'right', 40 | }, 41 | ], 42 | }, 43 | footer: { 44 | style: 'dark', 45 | links: [ 46 | { 47 | title: 'Docs', 48 | items: [ 49 | { 50 | label: 'Getting Started', 51 | to: '/docs/getting-started', 52 | }, 53 | { 54 | label: 'Documentation', 55 | to: '/docs/main/register-and-start', 56 | }, 57 | { 58 | label: 'Extensions', 59 | to: '/docs/main/extensions/compose-theme', 60 | }, 61 | ], 62 | }, 63 | { 64 | title: 'More', 65 | items: [ 66 | { 67 | label: 'GitHub', 68 | href: 'https://github.com/mori-atsushi/katalog', 69 | }, 70 | ], 71 | }, 72 | ], 73 | copyright: `Copyright © ${new Date().getFullYear()} Katalog. Built with Docusaurus.`, 74 | }, 75 | prism: { 76 | theme: lightCodeTheme, 77 | darkTheme: darkCodeTheme, 78 | additionalLanguages: ['kotlin'], 79 | }, 80 | }, 81 | presets: [ 82 | [ 83 | '@docusaurus/preset-classic', 84 | { 85 | docs: { 86 | sidebarPath: require.resolve('./sidebars.js'), 87 | // Please change this to your repo. 88 | editUrl: 89 | 'https://github.com/mori-atsushi/katalog/edit/main/docs/', 90 | }, 91 | theme: { 92 | customCss: require.resolve('./src/css/custom.css'), 93 | }, 94 | }, 95 | ], 96 | ], 97 | }; 98 | --------------------------------------------------------------------------------