├── .github └── workflows │ └── android.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── app │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── jailedbird │ │ │ └── app │ │ │ └── App.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── font │ │ ├── google_sans.xml │ │ └── google_sans_medium.xml │ │ ├── mipmap │ │ └── ic_launcher.png │ │ ├── values-night │ │ └── colors.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── font_certs.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── cn │ └── jailedbird │ └── app │ └── ExampleUnitTest.kt ├── build-logic ├── README.md ├── convention │ ├── build.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── AndroidApplicationComposeConventionPlugin.kt │ │ ├── AndroidApplicationConventionPlugin.kt │ │ ├── AndroidApplicationFirebaseConventionPlugin.kt │ │ ├── AndroidApplicationFlavorsConventionPlugin.kt │ │ ├── AndroidApplicationJacocoConventionPlugin.kt │ │ ├── AndroidFeatureConventionPlugin.kt │ │ ├── AndroidHiltConventionPlugin.kt │ │ ├── AndroidLibraryComposeConventionPlugin.kt │ │ ├── AndroidLibraryConventionPlugin.kt │ │ ├── AndroidLibraryJacocoConventionPlugin.kt │ │ ├── AndroidLintConventionPlugin.kt │ │ ├── AndroidRoomConventionPlugin.kt │ │ ├── AndroidTestConventionPlugin.kt │ │ ├── JvmLibraryConventionPlugin.kt │ │ └── com │ │ └── google │ │ └── samples │ │ └── apps │ │ └── nowinandroid │ │ ├── AndroidCompose.kt │ │ ├── AndroidInstrumentedTests.kt │ │ ├── Badging.kt │ │ ├── GradleManagedDevices.kt │ │ ├── Jacoco.kt │ │ ├── KotlinAndroid.kt │ │ ├── NiaBuildType.kt │ │ ├── NiaFlavor.kt │ │ ├── PrintTestApks.kt │ │ └── ProjectExtensions.kt ├── gradle.properties └── settings.gradle.kts ├── build.gradle.kts ├── core ├── common │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── core │ │ │ └── common │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── core │ │ │ └── common │ │ │ ├── base │ │ │ └── fragment │ │ │ │ └── BaseViewBindingNavigationFragment.kt │ │ │ ├── expose │ │ │ └── TestJavaLibraryExpose.kt │ │ │ └── utils │ │ │ ├── CommonExt.kt │ │ │ └── DebouncingUtils.java │ │ └── test │ │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── core │ │ └── common │ │ └── ExampleUnitTest.kt ├── resource │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── core │ │ │ └── resource │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── res │ │ │ ├── font │ │ │ ├── google_sans.xml │ │ │ └── google_sans_medium.xml │ │ │ └── values │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── font_certs.xml │ │ │ ├── ids.xml │ │ │ ├── styles.xml │ │ │ └── themes.xml │ │ └── test │ │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── core │ │ └── resource │ │ └── ExampleUnitTest.kt └── settings │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── core │ │ └── settings │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── core │ │ └── settings │ │ ├── Android.kt │ │ └── Settings.kt │ └── test │ └── java │ └── cn │ └── jailedbird │ └── core │ └── settings │ └── ExampleUnitTest.kt ├── doc └── blog.md ├── feature ├── about │ ├── .gitignore │ ├── build.gradle.kts │ ├── build_gradle_template_expose │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── jcn │ │ │ └── jailedbird │ │ │ └── feature │ │ │ └── about │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── feature │ │ │ └── about │ │ │ ├── AboutActivity.kt │ │ │ ├── di │ │ │ └── AboutModule.kt │ │ │ ├── expose │ │ │ ├── AboutEntity.kt │ │ │ └── AboutExpose.kt │ │ │ └── exposeimpl │ │ │ └── AboutExposeImpl.kt │ │ └── test │ │ └── java │ │ └── cn │ │ └── jcn │ │ └── jailedbird │ │ └── feature │ │ └── about │ │ └── ExampleUnitTest.kt ├── benchmark │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── feature │ │ │ └── benchmark │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── feature │ │ │ └── benchmark │ │ │ ├── BenchMark.kt │ │ │ └── expose │ │ │ ├── expose │ │ │ ├── expose1 │ │ │ └── expose2 │ │ └── test │ │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── feature │ │ └── benchmark │ │ └── ExampleUnitTest.kt ├── search │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── libs │ │ └── lib.jar │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── jailedbird │ │ │ └── feature │ │ │ └── search │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── cn │ │ │ │ └── jailedbird │ │ │ │ └── feature │ │ │ │ └── search │ │ │ │ ├── adapter │ │ │ │ └── AppListTwoTypeAdapter.kt │ │ │ │ ├── data │ │ │ │ ├── AppDao.kt │ │ │ │ ├── AppDatabase.kt │ │ │ │ ├── AppRepository.kt │ │ │ │ └── entity │ │ │ │ │ └── AppModel.kt │ │ │ │ ├── di │ │ │ │ ├── DatabaseModule.kt │ │ │ │ └── SearchModule.kt │ │ │ │ ├── dialog │ │ │ │ ├── AppListPopWindow.kt │ │ │ │ ├── AppSettingsPopWindow.kt │ │ │ │ └── BaseSimplePopUp.kt │ │ │ │ ├── expose │ │ │ │ └── SearchExpose.kt │ │ │ │ ├── exposeimpl │ │ │ │ └── SearchExpose.kt │ │ │ │ ├── main │ │ │ │ ├── SearchActivity.kt │ │ │ │ └── SearchViewModel.kt │ │ │ │ └── utils │ │ │ │ ├── Constants.kt │ │ │ │ ├── Ext.kt │ │ │ │ └── PackageUtils.kt │ │ └── res │ │ │ ├── drawable │ │ │ ├── ic_android.xml │ │ │ ├── ic_baseline_arrow_back_ios_24.xml │ │ │ ├── ic_baseline_more_vert_24.xml │ │ │ ├── ic_baseline_search_24.xml │ │ │ ├── ic_round_first_item_4dp.xml │ │ │ └── ic_round_gray_10dp.xml │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ ├── item_app_list.xml │ │ │ ├── item_app_list_first.xml │ │ │ ├── pop_up_app_list.xml │ │ │ └── pop_up_app_setting.xml │ │ │ └── values │ │ │ └── ids.xml │ │ └── test │ │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── feature │ │ └── search │ │ └── ExampleUnitTest.kt └── settings │ ├── .gitignore │ ├── build.gradle.kts │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── feature │ │ └── settings │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── cn │ │ │ └── jailedbird │ │ │ └── feature │ │ │ └── settings │ │ │ ├── SettingFragment.kt │ │ │ ├── SettingsActivity.kt │ │ │ ├── di │ │ │ └── SettingModule.kt │ │ │ ├── expose │ │ │ └── SettingExpose.kt │ │ │ └── exposeimpl │ │ │ └── SettingExposeImpl.kt │ └── res │ │ ├── layout │ │ ├── activity_settings.xml │ │ └── fragment_setting_main.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── cn │ └── jailedbird │ └── feature │ └── settings │ └── ExampleUnitTest.kt ├── gradle.properties ├── gradle ├── expose │ ├── build_gradle_template_android │ ├── build_gradle_template_expose │ ├── build_gradle_template_java │ ├── expose.gradle.kts │ └── expose.md ├── expose_gradle │ ├── build_gradle_template_android │ ├── build_gradle_template_expose │ ├── build_gradle_template_java │ ├── expose.gradle │ └── expose.md ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── moduleexpose.jks ├── settings.gradle.kts └── subproj └── GradleSample ├── .gitignore ├── app ├── .gitignore ├── build.gradle ├── build_gradle_template_expose ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── myapplication │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── myapplication │ │ │ ├── FirstFragment.kt │ │ │ ├── MainActivity.kt │ │ │ ├── SecondFragment.kt │ │ │ └── expose │ │ │ └── a.kt │ └── res │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── content_main.xml │ │ ├── fragment_first.xml │ │ └── fragment_second.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ └── nav_graph.xml │ │ ├── values-land │ │ └── dimens.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values-v23 │ │ └── themes.xml │ │ ├── values-w1240dp │ │ └── dimens.xml │ │ ├── values-w600dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── example │ └── myapplication │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle ├── expose_gradle │ ├── build_gradle_template_android │ ├── build_gradle_template_expose │ ├── build_gradle_template_java │ └── expose.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── cn │ └── jailedbird │ └── lib │ └── MyClass.kt ├── mylibrary ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── cn │ │ └── jailedbird │ │ └── mylibrary │ │ └── ExampleInstrumentedTest.kt │ ├── main │ └── AndroidManifest.xml │ └── test │ └── java │ └── cn │ └── jailedbird │ └── mylibrary │ └── ExampleUnitTest.kt └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-java@v3 15 | with: 16 | distribution: temurin 17 | java-version: 11 18 | - uses: gradle/gradle-build-action@v2 19 | with: 20 | gradle-version: 8.0.0 21 | arguments: assembleRelease 22 | - uses: r0adkll/sign-android-release@v1 23 | id: sign_app 24 | with: 25 | releaseDirectory: app/build/outputs/apk/release 26 | signingKeyBase64: ${{ secrets.SIGNING_KEY }} 27 | alias: ${{ secrets.ALIAS }} 28 | keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }} 29 | keyPassword: ${{ secrets.KEY_PASSWORD }} 30 | - run: mv ${{steps.sign_app.outputs.signedReleaseFile}} SmartAppSearch_$GITHUB_REF_NAME.apk 31 | - uses: ncipollo/release-action@v1 32 | with: 33 | artifacts: "*.apk" 34 | token: ${{ github.token }} 35 | generateReleaseNotes: true 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /.idea/* 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | app/release/output-metadata.json 18 | app/release/app-release.apk 19 | **/build/ 20 | *_api/ 21 | *_expose/ 22 | app/pro/release/app-pro-release.apk 23 | app/pro/release/output-metadata.json 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModuleExpose 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/JailedBird/ModuleExpose.svg)](https://github.com/JailedBird/ModuleExpose/stargazers) [![GitHub forks](https://img.shields.io/github/forks/JailedBird/ModuleExpose.svg)](https://github.com/JailedBird/ModuleExpose/network/members) [![GitHub issues](https://img.shields.io/github/issues/JailedBird/ModuleExpose.svg)](https://github.com/JailedBird/ModuleExpose/issues) [![GitHub license](https://img.shields.io/github/license/JailedBird/ModuleExpose.svg)](https://github.com/JailedBird/ModuleExpose/blob/master/LICENSE) 4 | 5 | 安卓模块化最重要点就是:如何优雅的实现模块间通信;而模块之间通信往往需要获取相同的实体类或接口,导致部分涉及模块通信的实体类和接口被迫下沉到基础模块,造成 *基础模块膨胀、模块代码分散、不便维护* 等问题; 6 | 7 | 8 | 9 | ## 快速接入 10 | 11 | 以kts版本为例展示,gradle版本、详细用法请参考对应维基文档; 12 | 13 | 方式1:克隆本项目,将本项目 gradle/expose目录文件拷贝到工程的gradle/expose即可; 14 | 15 | 方式2:考虑到克隆项目比较麻烦,提供命令行操作方式如下: 16 | 17 | - 在项目gradle目录下创建expose目录,将命令行工作目录切换到gradle/expose 18 | 19 | - 在gradle/expose下执行curl命令,分别下载 核心脚本expose.gradle.kts、Android Library配置模板、Java Library配置模板 20 | 21 | ``` 22 | curl -O https://raw.githubusercontent.com/JailedBird/ModuleExpose/main/gradle/expose/expose.gradle.kts 23 | curl -O https://raw.githubusercontent.com/JailedBird/ModuleExpose/main/gradle/expose/build_gradle_template_android 24 | curl -O https://raw.githubusercontent.com/JailedBird/ModuleExpose/main/gradle/expose/build_gradle_template_java 25 | ``` 26 | 27 | PS:两个模板文件需要根据项目实际情况进行定制; 28 | 29 | 30 | 31 | ## 简单介绍 32 | 33 | **ModuleExpose方案(简称模块暴露),是将模块(module)内部的这部分代码暴露出来并自动生成新的暴露模块(module_expose);** 34 | 35 | 不同于手动形式的代码下沉,本方案是直接将module中需要暴露的代码完整拷贝到module_expose模块,而module_expose模块的生成和配置是由脚本自动完成,并保证编译时两者代码的完全同步; 36 | 37 | 38 | 39 | 最终,工程中包含如下几类核心模块: 40 | 41 | - 基础模块:基础代码封装,可供任何业务模块使用; 42 | 43 | - 业务模块:包含业务功能,业务模块可以依赖基础模块,但无法依赖其他业务模块; 44 | - 暴露模块:由脚本基于业务模块或基础模块自动拷贝生成,业务模块可(compileOnly)依赖其他暴露模块; 45 | 46 | 示例如图: 47 | 48 | ![image-20231206141629690](https://zhaojunchen-1259455842.cos.ap-nanjing.myqcloud.com//imgimage-20231206141629690.png) 49 | 50 | 注意这种方案并非原创,原创出处如下: 51 | 52 | > 思路原创:[微信Android模块化架构重构实践](https://mp.weixin.qq.com/s/6Q818XA5FaHd7jJMFBG60w) 53 | > 54 | > 项目原创: [github/tyhjh/module_api](https://github.com/tyhjh/module_api) 55 | 56 | 57 | 58 | ## 工程架构 59 | 60 | 示例工程简介: 61 | 62 | - 基于nio重写脚本,并同时支持kts脚本和groovy脚本,详见维基文档; 63 | - 基于性能的考量,对暴露规则和生成方式进行改进,详见维基文档; 64 | - 综合优秀技术栈,优雅实现 模块化示例工程: 65 | - 结合 [now in android](https://github.com/android/nowinandroid) 项目编译脚本系统,实现快速生成和配置统一的模块 66 | - 结合最新ksp版本Hilt依赖注入框架,实现基于暴露接口的模块化解耦方案 67 | - 完整实现支持拼音的安卓App搜索启动器,包含Room等 Jetpack主流组件 68 | 69 | 70 | 71 | ## 工程文档 72 | 73 | 详细维基文档:[JailedBird/ModuleExpose/wiki](https://github.com/JailedBird/ModuleExpose/wiki) 74 | 75 | - [1、模块暴露](https://github.com/JailedBird/ModuleExpose/wiki/1、模块暴露) 76 | - [2、接入方式](https://github.com/JailedBird/ModuleExpose/wiki/2、接入方式) 77 | - [3、依赖注入](https://github.com/JailedBird/ModuleExpose/wiki/3、依赖注入) 78 | - [4、性能测试](https://github.com/JailedBird/ModuleExpose/wiki/4、性能测试) 79 | 80 | 81 | 82 | 如果本方案对大家有帮助,欢迎点亮项目star支持作者😘 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.application) 3 | alias(libs.plugins.nowinandroid.android.application.flavors) 4 | alias(libs.plugins.nowinandroid.android.hilt) 5 | alias(libs.plugins.nowinandroid.android.room) 6 | } 7 | 8 | android { 9 | namespace = "cn.jailedbird.app" 10 | 11 | buildFeatures { 12 | viewBinding = true 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation(projects.core.resource) 18 | implementation(projects.core.common) 19 | implementation(projects.core.settings) 20 | implementation(projects.feature.search) 21 | implementation(projects.feature.settings) 22 | implementation(projects.feature.about) 23 | 24 | // // Hilt https://developer.android.com/training/dependency-injection/hilt-android 25 | 26 | // // https://square.github.io/leakcanary/getting_started/ 27 | debugImplementation(libs.leakcanary) 28 | 29 | testImplementation(libs.junit4) 30 | androidTestImplementation(libs.junit) 31 | androidTestImplementation(libs.androidx.test.espresso.core) 32 | } -------------------------------------------------------------------------------- /app/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 -------------------------------------------------------------------------------- /app/src/androidTest/java/cn/jailedbird/app/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.app 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.smartappsearch", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/cn/jailedbird/app/App.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.app 2 | 3 | import android.app.Application 4 | import android.content.BroadcastReceiver 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.content.IntentFilter 8 | import cn.jailedbird.core.common.utils.log 9 | import cn.jailedbird.core.settings.Settings 10 | import cn.jailedbird.feature.search.data.AppRepository 11 | import cn.jailedbird.feature.search.data.entity.AppModel 12 | import dagger.hilt.android.HiltAndroidApp 13 | import kotlinx.coroutines.DelicateCoroutinesApi 14 | import kotlinx.coroutines.Dispatchers 15 | import kotlinx.coroutines.GlobalScope 16 | import kotlinx.coroutines.launch 17 | import javax.inject.Inject 18 | 19 | 20 | @HiltAndroidApp 21 | class App : Application() { 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | "App create".log() 26 | listenApkChange() 27 | Settings.init(this) 28 | } 29 | 30 | @Inject 31 | lateinit var appRepository: AppRepository 32 | 33 | /** Dynamic broadcast for apk install and uninstall 34 | * [StackOverflow](https://stackoverflow.com/questions/7470314/receiving-package-install-and-uninstall-events)*/ 35 | private fun listenApkChange() { 36 | val broadcastReceiver = object : BroadcastReceiver() { 37 | override fun onReceive(context: Context?, intent: Intent?) { 38 | @OptIn(DelicateCoroutinesApi::class) 39 | GlobalScope.launch(Dispatchers.IO) { 40 | appRepository.refreshAppModelTable( 41 | AppModel.updateMeta( 42 | this@App, 43 | appRepository.getApps() 44 | ) 45 | ) 46 | } 47 | } 48 | } 49 | 50 | val intentFilter = IntentFilter().apply { 51 | addAction(Intent.ACTION_PACKAGE_ADDED) 52 | addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED) 53 | addDataScheme("package") 54 | } 55 | 56 | registerReceiver(broadcastReceiver, intentFilter) 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/font/google_sans.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/font/google_sans_medium.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/app/src/main/res/mipmap/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | @color/carolina_blue 19 | @color/cornflower_blue 20 | @color/black 21 | 22 | #2EFFFFFF 23 | #2a2b2e 24 | 25 | 26 | #2EFFFFFF 27 | #B3000000 28 | 29 | 30 | @color/transparent 31 | 32 | 33 | #263238 34 | @color/event_card_header_background_dark 35 | #1fffffff 36 | 37 | 0.27 38 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SmartAppSearch 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @color/deep_sky_blue 5 | @color/warm_blue 6 | @color/white 7 | 8 | #39000000 9 | #F8F9FA 10 | 11 | 12 | #B3FFFFFF 13 | #40000000 14 | 15 | 16 | @color/system_ui_scrim_black 17 | 18 | @color/system_ui_scrim_black 19 | 20 | 21 | #e8f0fe 22 | @color/event_card_header_background_light 23 | #1f000000> 24 | 25 | 26 | #999 27 | #e6e6e6 28 | #33000000 29 | 30 | #f8f9fa 31 | #252729 32 | 33 | 0.18 34 | 35 | 36 | @color/deep_sky_blue 37 | @color/teal 38 | @color/bright_light_blue 39 | @color/sun_yellow 40 | @color/bright_orange 41 | 42 | 43 | 44 | #1a73e8 45 | #574ddd 46 | #8ab4f8 47 | #669DF6 48 | 49 | #069f86 50 | #27e5fd 51 | #fcd230 52 | #ff6c00 53 | 54 | #fff 55 | #000 56 | #0fff 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SmartAppSearch 3 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/test/java/cn/jailedbird/app/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.app 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build-logic/README.md: -------------------------------------------------------------------------------- 1 | # Convention Plugins 2 | 3 | The `build-logic` folder defines project-specific convention plugins, used to keep a single 4 | source of truth for common module configurations. 5 | 6 | This approach is heavily based on 7 | [https://developer.squareup.com/blog/herding-elephants/](https://developer.squareup.com/blog/herding-elephants/) 8 | and 9 | [https://github.com/jjohannes/idiomatic-gradle](https://github.com/jjohannes/idiomatic-gradle). 10 | 11 | By setting up convention plugins in `build-logic`, we can avoid duplicated build script setup, 12 | messy `subproject` configurations, without the pitfalls of the `buildSrc` directory. 13 | 14 | `build-logic` is an included build, as configured in the root 15 | [`settings.gradle.kts`](../settings.gradle.kts). 16 | 17 | Inside `build-logic` is a `convention` module, which defines a set of plugins that all normal 18 | modules can use to configure themselves. 19 | 20 | `build-logic` also includes a set of `Kotlin` files used to share logic between plugins themselves, 21 | which is most useful for configuring Android components (libraries vs applications) with shared 22 | code. 23 | 24 | These plugins are *additive* and *composable*, and try to only accomplish a single responsibility. 25 | Modules can then pick and choose the configurations they need. 26 | If there is one-off logic for a module without shared code, it's preferable to define that directly 27 | in the module's `build.gradle`, as opposed to creating a convention plugin with module-specific 28 | setup. 29 | 30 | Current list of convention plugins: 31 | 32 | - [`nowinandroid.android.application`](convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt), 33 | [`nowinandroid.android.library`](convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt), 34 | [`nowinandroid.android.test`](convention/src/main/kotlin/AndroidTestConventionPlugin.kt): 35 | Configures common Android and Kotlin options. 36 | - [`nowinandroid.android.application.compose`](convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt), 37 | [`nowinandroid.android.library.compose`](convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt): 38 | Configures Jetpack Compose options 39 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationComposeConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.dsl.ApplicationExtension 18 | import com.google.samples.apps.nowinandroid.configureAndroidCompose 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.kotlin.dsl.getByType 22 | 23 | class AndroidApplicationComposeConventionPlugin : Plugin { 24 | override fun apply(target: Project) { 25 | with(target) { 26 | pluginManager.apply("com.android.application") 27 | // Screenshot Tests 28 | pluginManager.apply("io.github.takahirom.roborazzi") 29 | 30 | val extension = extensions.getByType() 31 | configureAndroidCompose(extension) 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.dsl.ApplicationExtension 18 | import com.google.samples.apps.nowinandroid.configureGradleManagedDevices 19 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension 20 | import com.android.build.gradle.BaseExtension 21 | import com.google.samples.apps.nowinandroid.configureBadgingTasks 22 | import com.google.samples.apps.nowinandroid.configureKotlinAndroid 23 | import com.google.samples.apps.nowinandroid.configurePrintApksTask 24 | import org.gradle.api.Plugin 25 | import org.gradle.api.Project 26 | import org.gradle.kotlin.dsl.configure 27 | import org.gradle.kotlin.dsl.getByType 28 | 29 | class AndroidApplicationConventionPlugin : Plugin { 30 | override fun apply(target: Project) { 31 | with(target) { 32 | with(pluginManager) { 33 | apply("com.android.application") 34 | apply("org.jetbrains.kotlin.android") 35 | apply("nowinandroid.android.lint") 36 | } 37 | 38 | extensions.configure { 39 | configureKotlinAndroid(this) 40 | defaultConfig.targetSdk = 34 41 | configureGradleManagedDevices(this) 42 | } 43 | extensions.configure { 44 | configurePrintApksTask(this) 45 | configureBadgingTasks(extensions.getByType(), this) 46 | } 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationFirebaseConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.dsl.ApplicationExtension 18 | import com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension 19 | import com.google.samples.apps.nowinandroid.libs 20 | import org.gradle.api.Plugin 21 | import org.gradle.api.Project 22 | import org.gradle.kotlin.dsl.configure 23 | import org.gradle.kotlin.dsl.dependencies 24 | 25 | class AndroidApplicationFirebaseConventionPlugin : Plugin { 26 | override fun apply(target: Project) { 27 | with(target) { 28 | with(pluginManager) { 29 | apply("com.google.gms.google-services") 30 | apply("com.google.firebase.firebase-perf") 31 | apply("com.google.firebase.crashlytics") 32 | } 33 | 34 | dependencies { 35 | val bom = libs.findLibrary("firebase-bom").get() 36 | add("implementation", platform(bom)) 37 | "implementation"(libs.findLibrary("firebase.analytics").get()) 38 | "implementation"(libs.findLibrary("firebase.performance").get()) 39 | "implementation"(libs.findLibrary("firebase.crashlytics").get()) 40 | } 41 | 42 | extensions.configure { 43 | buildTypes.configureEach { 44 | // Disable the Crashlytics mapping file upload. This feature should only be 45 | // enabled if a Firebase backend is available and configured in 46 | // google-services.json. 47 | configure { 48 | mappingFileUploadEnabled = false 49 | } 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationFlavorsConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.dsl.ApplicationExtension 18 | import com.google.samples.apps.nowinandroid.configureFlavors 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.kotlin.dsl.configure 22 | 23 | class AndroidApplicationFlavorsConventionPlugin : Plugin { 24 | override fun apply(target: Project) { 25 | with(target) { 26 | extensions.configure { 27 | configureFlavors(this) 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidApplicationJacocoConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension 18 | import com.google.samples.apps.nowinandroid.configureJacoco 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.kotlin.dsl.getByType 22 | 23 | class AndroidApplicationJacocoConventionPlugin : Plugin { 24 | override fun apply(target: Project) { 25 | with(target) { 26 | with(pluginManager) { 27 | apply("org.gradle.jacoco") 28 | apply("com.android.application") 29 | } 30 | val extension = extensions.getByType() 31 | configureJacoco(extension) 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidFeatureConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.gradle.LibraryExtension 18 | import com.google.samples.apps.nowinandroid.configureGradleManagedDevices 19 | import com.google.samples.apps.nowinandroid.libs 20 | import org.gradle.api.Plugin 21 | import org.gradle.api.Project 22 | import org.gradle.kotlin.dsl.configure 23 | import org.gradle.kotlin.dsl.dependencies 24 | import org.gradle.kotlin.dsl.kotlin 25 | 26 | class AndroidFeatureConventionPlugin : Plugin { 27 | override fun apply(target: Project) { 28 | with(target) { 29 | pluginManager.apply { 30 | apply("nowinandroid.android.library") 31 | apply("nowinandroid.android.hilt") 32 | } 33 | extensions.configure { 34 | defaultConfig { 35 | testInstrumentationRunner = 36 | "com.google.samples.apps.nowinandroid.core.testing.NiaTestRunner" 37 | } 38 | configureGradleManagedDevices(this) 39 | } 40 | 41 | dependencies { 42 | add("implementation", project(":core:model")) 43 | add("implementation", project(":core:ui")) 44 | add("implementation", project(":core:designsystem")) 45 | add("implementation", project(":core:data")) 46 | add("implementation", project(":core:common")) 47 | add("implementation", project(":core:domain")) 48 | add("implementation", project(":core:analytics")) 49 | 50 | add("testImplementation", kotlin("test")) 51 | add("testImplementation", project(":core:testing")) 52 | add("androidTestImplementation", kotlin("test")) 53 | add("androidTestImplementation", project(":core:testing")) 54 | 55 | add("implementation", libs.findLibrary("coil.kt").get()) 56 | add("implementation", libs.findLibrary("coil.kt.compose").get()) 57 | 58 | add("implementation", libs.findLibrary("androidx.hilt.navigation.compose").get()) 59 | add("implementation", libs.findLibrary("androidx.lifecycle.runtimeCompose").get()) 60 | add("implementation", libs.findLibrary("androidx.lifecycle.viewModelCompose").get()) 61 | 62 | add("implementation", libs.findLibrary("kotlinx.coroutines.android").get()) 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidHiltConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.variant.AndroidComponentsExtension 18 | import com.android.build.gradle.internal.tasks.databinding.DataBindingGenBaseClassesTask 19 | import com.google.samples.apps.nowinandroid.libs 20 | import org.gradle.api.Plugin 21 | import org.gradle.api.Project 22 | import org.gradle.configurationcache.extensions.capitalized 23 | import org.gradle.kotlin.dsl.dependencies 24 | import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool 25 | 26 | class AndroidHiltConventionPlugin : Plugin { 27 | override fun apply(project: Project) { 28 | with(project) { 29 | with(pluginManager) { 30 | apply("com.google.devtools.ksp") 31 | apply("dagger.hilt.android.plugin") 32 | } 33 | 34 | dependencies { 35 | "implementation"(libs.findLibrary("hilt.android").get()) 36 | "ksp"(libs.findLibrary("hilt.compiler").get()) 37 | "kspAndroidTest"(libs.findLibrary("hilt.compiler").get()) 38 | "kspTest"(libs.findLibrary("hilt.compiler").get()) 39 | } 40 | // Fix: [Ksp] InjectProcessingStep was unable to process 'test' because 'error.NonExistentClass' could not be resolved. 41 | // https://github.com/google/dagger/issues/4097#issuecomment-1763781846 42 | // Note, This is a temporary fix and needs to wait for the official official fix 43 | val androidComponents = 44 | project.extensions.getByType(AndroidComponentsExtension::class.java) 45 | // Fix: when a module do not have dataBindingGenBaseClasses will crash 46 | /** 47 | * [org.gradle.api.NamedDomainObjectCollection.findByName] to replace [org.gradle.api.NamedDomainObjectCollection.getByName] 48 | * */ 49 | androidComponents.onVariants { variant -> 50 | afterEvaluate { 51 | project.tasks.getByName("ksp" + variant.name.capitalized() + "Kotlin") { 52 | val dataBindingTask = 53 | project.tasks.findByName("dataBindingGenBaseClasses" + variant.name.capitalized()) as? DataBindingGenBaseClassesTask 54 | if (dataBindingTask != null) { 55 | (this as AbstractKotlinCompileTool<*>).setSource(dataBindingTask.sourceOutFolder) 56 | } 57 | } 58 | } 59 | } 60 | } 61 | 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidLibraryComposeConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.gradle.LibraryExtension 18 | import com.google.samples.apps.nowinandroid.configureAndroidCompose 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.kotlin.dsl.dependencies 22 | import org.gradle.kotlin.dsl.getByType 23 | import org.gradle.kotlin.dsl.kotlin 24 | 25 | class AndroidLibraryComposeConventionPlugin : Plugin { 26 | override fun apply(target: Project) { 27 | with(target) { 28 | pluginManager.apply("com.android.library") 29 | // Screenshot Tests 30 | pluginManager.apply("io.github.takahirom.roborazzi") 31 | 32 | val extension = extensions.getByType() 33 | configureAndroidCompose(extension) 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 18 | import com.android.build.gradle.LibraryExtension 19 | import com.google.samples.apps.nowinandroid.configureFlavors 20 | import com.google.samples.apps.nowinandroid.configureGradleManagedDevices 21 | import com.google.samples.apps.nowinandroid.configureKotlinAndroid 22 | import com.google.samples.apps.nowinandroid.configurePrintApksTask 23 | import com.google.samples.apps.nowinandroid.disableUnnecessaryAndroidTests 24 | import org.gradle.api.Plugin 25 | import org.gradle.api.Project 26 | import org.gradle.kotlin.dsl.configure 27 | import org.gradle.kotlin.dsl.dependencies 28 | import org.gradle.kotlin.dsl.kotlin 29 | 30 | class AndroidLibraryConventionPlugin : Plugin { 31 | override fun apply(target: Project) { 32 | with(target) { 33 | with(pluginManager) { 34 | apply("com.android.library") 35 | apply("org.jetbrains.kotlin.android") 36 | apply("nowinandroid.android.lint") 37 | } 38 | 39 | extensions.configure { 40 | configureKotlinAndroid(this) 41 | defaultConfig.targetSdk = 34 42 | configureFlavors(this) 43 | configureGradleManagedDevices(this) 44 | } 45 | extensions.configure { 46 | configurePrintApksTask(this) 47 | disableUnnecessaryAndroidTests(target) 48 | } 49 | dependencies { 50 | add("testImplementation", kotlin("test")) 51 | // add("testImplementation", project(":core:testing")) 52 | add("androidTestImplementation", kotlin("test")) 53 | // add("androidTestImplementation", project(":core:testing")) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidLibraryJacocoConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 18 | import com.google.samples.apps.nowinandroid.configureJacoco 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.kotlin.dsl.getByType 22 | 23 | class AndroidLibraryJacocoConventionPlugin : Plugin { 24 | override fun apply(target: Project) { 25 | with(target) { 26 | with(pluginManager) { 27 | apply("org.gradle.jacoco") 28 | apply("com.android.library") 29 | } 30 | val extension = extensions.getByType() 31 | configureJacoco(extension) 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidLintConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.api.dsl.ApplicationExtension 18 | import com.android.build.api.dsl.LibraryExtension 19 | import com.android.build.api.dsl.Lint 20 | import org.gradle.api.Plugin 21 | import org.gradle.api.Project 22 | import org.gradle.kotlin.dsl.configure 23 | 24 | class AndroidLintConventionPlugin : Plugin { 25 | override fun apply(target: Project) { 26 | with(target) { 27 | when { 28 | pluginManager.hasPlugin("com.android.application") -> 29 | configure { lint(Lint::configure) } 30 | 31 | pluginManager.hasPlugin("com.android.library") -> 32 | configure { lint(Lint::configure) } 33 | 34 | else -> { 35 | pluginManager.apply("com.android.lint") 36 | configure(Lint::configure) 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | private fun Lint.configure() { 44 | xmlReport = true 45 | checkDependencies = true 46 | } 47 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.google.devtools.ksp.gradle.KspExtension 18 | import com.google.samples.apps.nowinandroid.libs 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.api.tasks.InputDirectory 22 | import org.gradle.api.tasks.PathSensitive 23 | import org.gradle.api.tasks.PathSensitivity 24 | import org.gradle.kotlin.dsl.configure 25 | import org.gradle.kotlin.dsl.dependencies 26 | import org.gradle.process.CommandLineArgumentProvider 27 | import java.io.File 28 | 29 | class AndroidRoomConventionPlugin : Plugin { 30 | 31 | override fun apply(target: Project) { 32 | with(target) { 33 | pluginManager.apply("com.google.devtools.ksp") 34 | 35 | extensions.configure { 36 | // The schemas directory contains a schema file for each version of the Room database. 37 | // This is required to enable Room auto migrations. 38 | // See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration. 39 | // arg(RoomSchemaArgProvider(File(projectDir, "schemas"))) 40 | } 41 | 42 | dependencies { 43 | add("implementation", libs.findLibrary("room.runtime").get()) 44 | add("implementation", libs.findLibrary("room.ktx").get()) 45 | add("ksp", libs.findLibrary("room.compiler").get()) 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * https://issuetracker.google.com/issues/132245929 52 | * [Export schemas](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas) 53 | */ 54 | class RoomSchemaArgProvider( 55 | @get:InputDirectory 56 | @get:PathSensitive(PathSensitivity.RELATIVE) 57 | val schemaDir: File, 58 | ) : CommandLineArgumentProvider { 59 | override fun asArguments() = listOf("room.schemaLocation=${schemaDir.path}") 60 | } 61 | } -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/AndroidTestConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.android.build.gradle.TestExtension 18 | import com.google.samples.apps.nowinandroid.configureGradleManagedDevices 19 | import com.google.samples.apps.nowinandroid.configureKotlinAndroid 20 | import org.gradle.api.Plugin 21 | import org.gradle.api.Project 22 | import org.gradle.kotlin.dsl.configure 23 | 24 | class AndroidTestConventionPlugin : Plugin { 25 | override fun apply(target: Project) { 26 | with(target) { 27 | with(pluginManager) { 28 | apply("com.android.test") 29 | apply("org.jetbrains.kotlin.android") 30 | } 31 | 32 | extensions.configure { 33 | configureKotlinAndroid(this) 34 | defaultConfig.targetSdk = 34 35 | configureGradleManagedDevices(this) 36 | } 37 | } 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/JvmLibraryConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import com.google.samples.apps.nowinandroid.configureKotlinJvm 18 | import org.gradle.api.Plugin 19 | import org.gradle.api.Project 20 | 21 | class JvmLibraryConventionPlugin : Plugin { 22 | override fun apply(target: Project) { 23 | with(target) { 24 | with(pluginManager) { 25 | apply("org.jetbrains.kotlin.jvm") 26 | apply("nowinandroid.android.lint") 27 | } 28 | configureKotlinJvm() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/AndroidInstrumentedTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.apps.nowinandroid 18 | 19 | import com.android.build.api.variant.LibraryAndroidComponentsExtension 20 | import org.gradle.api.Project 21 | 22 | /** 23 | * Disable unnecessary Android instrumented tests for the [project] if there is no `androidTest` folder. 24 | * Otherwise, these projects would be compiled, packaged, installed and ran only to end-up with the following message: 25 | * 26 | * > Starting 0 tests on AVD 27 | * 28 | * Note: this could be improved by checking other potential sourceSets based on buildTypes and flavors. 29 | */ 30 | internal fun LibraryAndroidComponentsExtension.disableUnnecessaryAndroidTests( 31 | project: Project, 32 | ) = beforeVariants { 33 | it.enableAndroidTest = it.enableAndroidTest 34 | && project.projectDir.resolve("src/androidTest").exists() 35 | } 36 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/GradleManagedDevices.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.apps.nowinandroid 18 | 19 | import com.android.build.api.dsl.CommonExtension 20 | import com.android.build.api.dsl.ManagedVirtualDevice 21 | import org.gradle.kotlin.dsl.get 22 | import org.gradle.kotlin.dsl.invoke 23 | 24 | /** 25 | * Configure project for Gradle managed devices 26 | */ 27 | internal fun configureGradleManagedDevices( 28 | commonExtension: CommonExtension<*, *, *, *, *>, 29 | ) { 30 | val pixel4 = DeviceConfig("Pixel 4", 30, "aosp-atd") 31 | val pixel6 = DeviceConfig("Pixel 6", 31, "aosp") 32 | val pixelC = DeviceConfig("Pixel C", 30, "aosp-atd") 33 | 34 | val allDevices = listOf(pixel4, pixel6, pixelC) 35 | val ciDevices = listOf(pixel4, pixelC) 36 | 37 | commonExtension.testOptions { 38 | managedDevices { 39 | devices { 40 | allDevices.forEach { deviceConfig -> 41 | maybeCreate(deviceConfig.taskName, ManagedVirtualDevice::class.java).apply { 42 | device = deviceConfig.device 43 | apiLevel = deviceConfig.apiLevel 44 | systemImageSource = deviceConfig.systemImageSource 45 | } 46 | } 47 | } 48 | groups { 49 | maybeCreate("ci").apply { 50 | ciDevices.forEach { deviceConfig -> 51 | targetDevices.add(devices[deviceConfig.taskName]) 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | private data class DeviceConfig( 60 | val device: String, 61 | val apiLevel: Int, 62 | val systemImageSource: String, 63 | ) { 64 | val taskName = buildString { 65 | append(device.lowercase().replace(" ", "")) 66 | append("api") 67 | append(apiLevel.toString()) 68 | append(systemImageSource.replace("-", "")) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/Jacoco.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.apps.nowinandroid 18 | 19 | import com.android.build.api.variant.AndroidComponentsExtension 20 | import org.gradle.api.Project 21 | import org.gradle.api.tasks.testing.Test 22 | import org.gradle.kotlin.dsl.configure 23 | import org.gradle.kotlin.dsl.register 24 | import org.gradle.kotlin.dsl.withType 25 | import org.gradle.testing.jacoco.plugins.JacocoPluginExtension 26 | import org.gradle.testing.jacoco.plugins.JacocoTaskExtension 27 | import org.gradle.testing.jacoco.tasks.JacocoReport 28 | import java.util.Locale 29 | 30 | private val coverageExclusions = listOf( 31 | // Android 32 | "**/R.class", 33 | "**/R\$*.class", 34 | "**/BuildConfig.*", 35 | "**/Manifest*.*" 36 | ) 37 | 38 | private fun String.capitalize() = replaceFirstChar { 39 | if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() 40 | } 41 | 42 | internal fun Project.configureJacoco( 43 | androidComponentsExtension: AndroidComponentsExtension<*, *, *>, 44 | ) { 45 | configure { 46 | toolVersion = libs.findVersion("jacoco").get().toString() 47 | } 48 | 49 | val jacocoTestReport = tasks.create("jacocoTestReport") 50 | 51 | androidComponentsExtension.onVariants { variant -> 52 | val testTaskName = "test${variant.name.capitalize()}UnitTest" 53 | val buildDir = layout.buildDirectory.get().asFile 54 | val reportTask = tasks.register("jacoco${testTaskName.capitalize()}Report", JacocoReport::class) { 55 | dependsOn(testTaskName) 56 | 57 | reports { 58 | xml.required.set(true) 59 | html.required.set(true) 60 | } 61 | 62 | classDirectories.setFrom( 63 | fileTree("$buildDir/tmp/kotlin-classes/${variant.name}") { 64 | exclude(coverageExclusions) 65 | } 66 | ) 67 | 68 | sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin")) 69 | executionData.setFrom(file("$buildDir/jacoco/$testTaskName.exec")) 70 | } 71 | 72 | jacocoTestReport.dependsOn(reportTask) 73 | } 74 | 75 | tasks.withType().configureEach { 76 | configure { 77 | // Required for JaCoCo + Robolectric 78 | // https://github.com/robolectric/robolectric/issues/2230 79 | // TODO: Consider removing if not we don't add Robolectric 80 | isIncludeNoLocationClasses = true 81 | 82 | // Required for JDK 11 with the above 83 | // https://github.com/gradle/gradle/issues/5184#issuecomment-391982009 84 | excludes = listOf("jdk.internal.*") 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/KotlinAndroid.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.apps.nowinandroid 18 | 19 | import com.android.build.api.dsl.CommonExtension 20 | import org.gradle.api.JavaVersion 21 | import org.gradle.api.Project 22 | import org.gradle.api.plugins.JavaPluginExtension 23 | import org.gradle.kotlin.dsl.configure 24 | import org.gradle.kotlin.dsl.dependencies 25 | import org.gradle.kotlin.dsl.provideDelegate 26 | import org.gradle.kotlin.dsl.withType 27 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 28 | 29 | /** 30 | * Configure base Kotlin with Android options 31 | */ 32 | internal fun Project.configureKotlinAndroid( 33 | commonExtension: CommonExtension<*, *, *, *, *>, 34 | ) { 35 | commonExtension.apply { 36 | compileSdk = 34 37 | 38 | defaultConfig { 39 | minSdk = 24 40 | } 41 | 42 | compileOptions { 43 | // Up to Java 11 APIs are available through desugaring 44 | // https://developer.android.com/studio/write/java11-minimal-support-table 45 | sourceCompatibility = JavaVersion.VERSION_11 46 | targetCompatibility = JavaVersion.VERSION_11 47 | isCoreLibraryDesugaringEnabled = true 48 | } 49 | } 50 | 51 | configureKotlin() 52 | 53 | dependencies { 54 | add("coreLibraryDesugaring", libs.findLibrary("android.desugarJdkLibs").get()) 55 | } 56 | } 57 | 58 | /** 59 | * Configure base Kotlin options for JVM (non-Android) 60 | */ 61 | internal fun Project.configureKotlinJvm() { 62 | extensions.configure { 63 | // Up to Java 11 APIs are available through desugaring 64 | // https://developer.android.com/studio/write/java11-minimal-support-table 65 | sourceCompatibility = JavaVersion.VERSION_11 66 | targetCompatibility = JavaVersion.VERSION_11 67 | } 68 | 69 | configureKotlin() 70 | } 71 | 72 | /** 73 | * Configure base Kotlin options 74 | */ 75 | private fun Project.configureKotlin() { 76 | // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947 77 | tasks.withType().configureEach { 78 | kotlinOptions { 79 | // Set JVM target to 11 80 | jvmTarget = JavaVersion.VERSION_11.toString() 81 | // Treat all Kotlin warnings as errors (disabled by default) 82 | // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties 83 | val warningsAsErrors: String? by project 84 | allWarningsAsErrors = warningsAsErrors.toBoolean() 85 | freeCompilerArgs = freeCompilerArgs + listOf( 86 | "-opt-in=kotlin.RequiresOptIn", 87 | // Enable experimental coroutines APIs, including Flow 88 | "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", 89 | "-opt-in=kotlinx.coroutines.FlowPreview", 90 | ) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaBuildType.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.apps.nowinandroid 18 | 19 | /** 20 | * This is shared between :app and :benchmarks module to provide configurations type safety. 21 | */ 22 | enum class NiaBuildType(val applicationIdSuffix: String? = null) { 23 | DEBUG(".debug"), 24 | RELEASE, 25 | BENCHMARK(".benchmark") 26 | } 27 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/NiaFlavor.kt: -------------------------------------------------------------------------------- 1 | package com.google.samples.apps.nowinandroid 2 | 3 | import com.android.build.api.dsl.ApplicationExtension 4 | import com.android.build.api.dsl.ApplicationProductFlavor 5 | import com.android.build.api.dsl.CommonExtension 6 | import com.android.build.api.dsl.ProductFlavor 7 | 8 | @Suppress("EnumEntryName") 9 | enum class FlavorDimension { 10 | contentType 11 | } 12 | 13 | // The content for the app can either come from local static data which is useful for demo 14 | // purposes, or from a production backend server which supplies up-to-date, real content. 15 | // These two product flavors reflect this behaviour. 16 | @Suppress("EnumEntryName") 17 | enum class NiaFlavor(val dimension: FlavorDimension, val applicationIdSuffix: String? = null) { 18 | tst(FlavorDimension.contentType, applicationIdSuffix = ".tst"), 19 | pro(FlavorDimension.contentType) 20 | } 21 | 22 | fun configureFlavors( 23 | commonExtension: CommonExtension<*, *, *, *, *>, 24 | flavorConfigurationBlock: ProductFlavor.(flavor: NiaFlavor) -> Unit = {} 25 | ) { 26 | commonExtension.apply { 27 | flavorDimensions += FlavorDimension.contentType.name 28 | productFlavors { 29 | NiaFlavor.values().forEach { 30 | create(it.name) { 31 | dimension = it.dimension.name 32 | flavorConfigurationBlock(this, it) 33 | if (this@apply is ApplicationExtension && this is ApplicationProductFlavor) { 34 | if (it.applicationIdSuffix != null) { 35 | applicationIdSuffix = it.applicationIdSuffix 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /build-logic/convention/src/main/kotlin/com/google/samples/apps/nowinandroid/ProjectExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.samples.apps.nowinandroid 18 | 19 | import org.gradle.api.Project 20 | import org.gradle.api.artifacts.VersionCatalog 21 | import org.gradle.api.artifacts.VersionCatalogsExtension 22 | import org.gradle.kotlin.dsl.getByType 23 | 24 | val Project.libs 25 | get(): VersionCatalog = extensions.getByType().named("libs") 26 | -------------------------------------------------------------------------------- /build-logic/gradle.properties: -------------------------------------------------------------------------------- 1 | # Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534 2 | org.gradle.parallel=true 3 | org.gradle.caching=true 4 | org.gradle.configureondemand=true 5 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | dependencyResolutionManagement { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | versionCatalogs { 23 | create("libs") { 24 | from(files("../gradle/libs.versions.toml")) 25 | } 26 | } 27 | } 28 | 29 | rootProject.name = "build-logic" 30 | include(":convention") 31 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | } 9 | 10 | // Lists all plugins used throughout the project without applying them. 11 | plugins { 12 | alias(libs.plugins.android.application) apply false 13 | alias(libs.plugins.kotlin.jvm) apply false 14 | alias(libs.plugins.kotlin.serialization) apply false 15 | alias(libs.plugins.firebase.crashlytics) apply false 16 | alias(libs.plugins.firebase.perf) apply false 17 | alias(libs.plugins.gms) apply false 18 | alias(libs.plugins.hilt) apply false 19 | alias(libs.plugins.ksp) apply false 20 | alias(libs.plugins.roborazzi) apply false 21 | alias(libs.plugins.secrets) apply false 22 | alias(libs.plugins.android.library) apply false 23 | alias(libs.plugins.org.jetbrains.kotlin.android) apply false 24 | } 25 | 26 | task("clean").dependsOn("module_expose_clean") 27 | // This task is used for delete xx_expose module 28 | tasks.register("module_expose_clean"){ 29 | doLast { 30 | println("execute clean expose") 31 | subprojects.forEach{ project-> 32 | // Please exclude these project that you don't want to delete 33 | if(project.name.endsWith("_expose")){ 34 | println("ModuleExpose: delete ${project.path}") 35 | project.projectDir.deleteRecursively() 36 | } 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /core/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | } 4 | 5 | android { 6 | namespace = "cn.jailedbird.core.common" 7 | 8 | buildFeatures { 9 | viewBinding = true 10 | } 11 | } 12 | 13 | dependencies { 14 | implementation(libs.androidx.core.ktx) 15 | implementation(libs.androidx.appcompat) 16 | api(libs.appContext) 17 | implementation(libs.material) 18 | testImplementation(libs.junit4) 19 | androidTestImplementation(libs.junit) 20 | androidTestImplementation(libs.androidx.test.espresso.core) 21 | } -------------------------------------------------------------------------------- /core/common/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/core/common/consumer-rules.pro -------------------------------------------------------------------------------- /core/common/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. 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 -------------------------------------------------------------------------------- /core/common/src/androidTest/java/cn/jailedbird/core/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.common 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.core.common.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/common/src/main/java/cn/jailedbird/core/common/expose/TestJavaLibraryExpose.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.common.expose 2 | 3 | interface TestJavaLibraryExpose { 4 | fun testJavaLibraryExpose() 5 | } -------------------------------------------------------------------------------- /core/common/src/main/java/cn/jailedbird/core/common/utils/CommonExt.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused", "UnusedReceiverParameter") 2 | 3 | package cn.jailedbird.core.common.utils 4 | 5 | import android.app.Activity 6 | import android.content.Context 7 | import android.content.res.Configuration 8 | import android.content.res.Resources 9 | import android.os.Handler 10 | import android.os.Looper 11 | import android.util.Log 12 | import android.view.View 13 | import android.widget.Toast 14 | import androidx.core.view.WindowCompat 15 | import androidx.core.view.WindowInsetsCompat 16 | import net.vrallev.android.context.AppContext 17 | 18 | private val applicationContext = AppContext.getApplication() 19 | fun String?.toast() { 20 | val s = this 21 | if (!s.isNullOrEmpty()) { 22 | if (Looper.myLooper() != Looper.getMainLooper()) { 23 | Handler(Looper.getMainLooper()).post { 24 | Toast.makeText(applicationContext, s, Toast.LENGTH_SHORT).show() 25 | } 26 | } else { 27 | Toast.makeText(applicationContext, s, Toast.LENGTH_SHORT).show() 28 | } 29 | } 30 | } 31 | 32 | fun Any?.log() { 33 | val s = this?.toString() ?: return 34 | if (s.isNotEmpty()) { 35 | Log.d("log", s) 36 | } 37 | } 38 | 39 | fun Context.finishProcess() { 40 | android.os.Process.killProcess(android.os.Process.myPid()) 41 | } 42 | 43 | fun Int.toPx(): Float { 44 | val dpValue = this 45 | val scale = Resources.getSystem().displayMetrics.density 46 | return (dpValue * scale + 0.5f) 47 | } 48 | 49 | /** 50 | * Avoid view's fast-click 51 | * */ 52 | inline fun View.setDebouncingClick( 53 | @Suppress("UNUSED_PARAMETER") duration: Long = 1000L, 54 | crossinline block: (view: View) -> Unit 55 | ) { 56 | setOnClickListener { 57 | if (DebouncingUtils.isValid(it)) { 58 | block.invoke(this) 59 | } 60 | } 61 | } 62 | 63 | fun Context.hideKeyboard() { 64 | if (this is Activity) { 65 | val window = this.window 66 | WindowCompat.getInsetsController(window, window.decorView) 67 | .hide(WindowInsetsCompat.Type.ime()) 68 | } 69 | } 70 | 71 | fun Context.showKeyboard() { 72 | if (this is Activity) { 73 | val window = this.window 74 | WindowCompat.getInsetsController(window, window.decorView) 75 | .show(WindowInsetsCompat.Type.ime()) 76 | } 77 | } 78 | 79 | /** 80 | * @return the system theme is light theme 81 | * 82 | * more dark mode [doc](https://developer.android.com/guide/topics/ui/look-and-feel/darktheme?hl=zh-cn#kotlin) 83 | * */ 84 | private fun Context.isLightSystemTheme(): Boolean { 85 | val context = this 86 | val uiConfig = context.resources.configuration.uiMode 87 | return when (uiConfig and Configuration.UI_MODE_NIGHT_MASK) { 88 | // Night mode is not active, we're using the light theme 89 | Configuration.UI_MODE_NIGHT_NO -> { 90 | true 91 | } 92 | // Night mode is active, we're using dark theme 93 | Configuration.UI_MODE_NIGHT_YES -> { 94 | false 95 | } 96 | // Default light theme 97 | else -> { 98 | true 99 | } 100 | } 101 | } 102 | 103 | fun Long.timer(label: String, withToast: Boolean = false): Long { 104 | val startTime = this 105 | val spend: Long = ((System.nanoTime() - startTime) / 1000_000) 106 | "$label cost $spend ms".apply { 107 | if (withToast) { 108 | this.toast() 109 | } 110 | this.log() 111 | } 112 | return spend 113 | } 114 | 115 | 116 | -------------------------------------------------------------------------------- /core/common/src/main/java/cn/jailedbird/core/common/utils/DebouncingUtils.java: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.common.utils; 2 | 3 | 4 | import android.os.SystemClock; 5 | import android.text.TextUtils; 6 | import android.view.View; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import java.util.Iterator; 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | *
16 |  *     author: Blankj
17 |  *     blog  : blankj
18 |  *     time  : 2020/09/01
19 |  *     desc  : utils about debouncing
20 |  * 
21 | */ 22 | @SuppressWarnings("SpellCheckingInspection") 23 | public class DebouncingUtils { 24 | 25 | private static final int CACHE_SIZE = 64; 26 | private static final Map KEY_MILLIS_MAP = new ConcurrentHashMap<>(CACHE_SIZE); 27 | private static final long DEBOUNCING_DEFAULT_VALUE = 1000; 28 | 29 | private DebouncingUtils() { 30 | throw new UnsupportedOperationException("u can't instantiate me..."); 31 | } 32 | 33 | /** 34 | * Return whether the view is not in a jitter state. 35 | * 36 | * @param view The view. 37 | * @return {@code true}: yes
{@code false}: no 38 | */ 39 | public static boolean isValid(@NonNull final View view) { 40 | return isValid(view, DEBOUNCING_DEFAULT_VALUE); 41 | } 42 | 43 | /** 44 | * Return whether the view is not in a jitter state. 45 | * 46 | * @param view The view. 47 | * @param duration The duration. 48 | * @return {@code true}: yes
{@code false}: no 49 | */ 50 | public static boolean isValid(@NonNull final View view, final long duration) { 51 | return isValid(String.valueOf(view.hashCode()), duration); 52 | } 53 | 54 | /** 55 | * Return whether the key is not in a jitter state. 56 | * 57 | * @param key The key. 58 | * @param duration The duration. 59 | * @return {@code true}: yes
{@code false}: no 60 | */ 61 | public static boolean isValid(@NonNull String key, final long duration) { 62 | if (TextUtils.isEmpty(key)) { 63 | throw new IllegalArgumentException("The key is null."); 64 | } 65 | if (duration < 0) { 66 | throw new IllegalArgumentException("The duration is less than 0."); 67 | } 68 | long curTime = SystemClock.elapsedRealtime(); 69 | clearIfNecessary(curTime); 70 | Long validTime = KEY_MILLIS_MAP.get(key); 71 | if (validTime == null || curTime >= validTime) { 72 | KEY_MILLIS_MAP.put(key, curTime + duration); 73 | return true; 74 | } 75 | return false; 76 | } 77 | 78 | private static void clearIfNecessary(long curTime) { 79 | if (KEY_MILLIS_MAP.size() < CACHE_SIZE) return; 80 | for (Iterator> it = KEY_MILLIS_MAP.entrySet().iterator(); it.hasNext(); ) { 81 | Map.Entry entry = it.next(); 82 | Long validTime = entry.getValue(); 83 | if (curTime >= validTime) { 84 | it.remove(); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /core/common/src/test/java/cn/jailedbird/core/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.common 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/resource/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/resource/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | } 4 | 5 | android { 6 | namespace = "cn.jailedbird.core.resource" 7 | } 8 | 9 | dependencies { 10 | 11 | implementation(libs.androidx.core.ktx) 12 | implementation(libs.androidx.appcompat) 13 | implementation(libs.material) 14 | testImplementation(libs.junit4) 15 | androidTestImplementation(libs.junit) 16 | androidTestImplementation(libs.androidx.test.espresso.core) 17 | } -------------------------------------------------------------------------------- /core/resource/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/core/resource/consumer-rules.pro -------------------------------------------------------------------------------- /core/resource/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. 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 -------------------------------------------------------------------------------- /core/resource/src/androidTest/java/cn/jailedbird/core/resource/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.resource 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.core.resource.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/resource/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/resource/src/main/res/font/google_sans.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /core/resource/src/main/res/font/google_sans_medium.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /core/resource/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /core/resource/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @color/deep_sky_blue 5 | @color/warm_blue 6 | @color/white 7 | 8 | #39000000 9 | #F8F9FA 10 | 11 | 12 | #B3FFFFFF 13 | #40000000 14 | 15 | 16 | @color/system_ui_scrim_black 17 | 18 | @color/system_ui_scrim_black 19 | 20 | 21 | #e8f0fe 22 | @color/event_card_header_background_light 23 | #1f000000> 24 | 25 | 26 | #999 27 | #e6e6e6 28 | #33000000 29 | 30 | #f8f9fa 31 | #252729 32 | 33 | 0.18 34 | 35 | 36 | @color/deep_sky_blue 37 | @color/teal 38 | @color/bright_light_blue 39 | @color/sun_yellow 40 | @color/bright_orange 41 | 42 | 43 | 44 | #1a73e8 45 | #574ddd 46 | #8ab4f8 47 | #669DF6 48 | 49 | #069f86 50 | #27e5fd 51 | #fcd230 52 | #ff6c00 53 | 54 | #fff 55 | #000 56 | #0fff 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /core/resource/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /core/resource/src/test/java/cn/jailedbird/core/resource/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.resource 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /core/settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /core/settings/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | } 4 | 5 | android { 6 | namespace = "cn.jailedbird.core.settings" 7 | } 8 | 9 | dependencies { 10 | implementation(projects.core.resource) 11 | implementation(libs.androidx.core.ktx) 12 | implementation(libs.androidx.appcompat) 13 | implementation(libs.material) 14 | testImplementation(libs.junit4) 15 | androidTestImplementation(libs.junit) 16 | androidTestImplementation(libs.androidx.test.espresso.core) 17 | } -------------------------------------------------------------------------------- /core/settings/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/core/settings/consumer-rules.pro -------------------------------------------------------------------------------- /core/settings/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. 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 -------------------------------------------------------------------------------- /core/settings/src/androidTest/java/cn/jailedbird/core/settings/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.settings 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.core.settings.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /core/settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /core/settings/src/main/java/cn/jailedbird/core/settings/Android.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.settings 2 | 3 | import android.os.Build 4 | 5 | object Android { 6 | val sdk: Int 7 | get() = Build.VERSION.SDK_INT 8 | 9 | val name: String 10 | get() = "Android ${Build.VERSION.RELEASE}" 11 | 12 | val platforms = Build.SUPPORTED_ABIS.toSet() 13 | 14 | val primaryPlatform: String? 15 | get() = Build.SUPPORTED_64_BIT_ABIS?.firstOrNull() 16 | ?: Build.SUPPORTED_32_BIT_ABIS?.firstOrNull() 17 | 18 | fun sdk(sdk: Int): Boolean { 19 | return Build.VERSION.SDK_INT >= sdk 20 | } 21 | 22 | object PackageManager { 23 | // GET_SIGNATURES should always present for getPackageArchiveInfo 24 | val signaturesFlag: Int 25 | get() = (if (sdk(28)) android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES else 0) or 26 | @Suppress("DEPRECATION") android.content.pm.PackageManager.GET_SIGNATURES 27 | } 28 | } -------------------------------------------------------------------------------- /core/settings/src/test/java/cn/jailedbird/core/settings/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.core.settings 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /feature/about/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/about/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | alias(libs.plugins.nowinandroid.android.room) 4 | alias(libs.plugins.nowinandroid.android.hilt) 5 | id("kotlin-parcelize") 6 | } 7 | 8 | android { 9 | namespace = "cn.jailedbird.feature.about" 10 | 11 | buildFeatures { 12 | viewBinding = true 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation(libs.androidx.core.ktx) 18 | implementation(libs.androidx.appcompat) 19 | implementation(libs.androidx.navigation.fragment.ktx) 20 | implementation(libs.androidx.navigation.ui.ktx) 21 | implementation(libs.material) 22 | implementation(libs.edgeutils) 23 | 24 | implementation(projects.core.settings) 25 | implementation(projects.core.common) 26 | 27 | compileOnly(projects.feature.searchExpose) 28 | 29 | testImplementation(libs.junit4) 30 | androidTestImplementation(libs.junit) 31 | androidTestImplementation(libs.androidx.test.espresso.core) 32 | } -------------------------------------------------------------------------------- /feature/about/build_gradle_template_expose: -------------------------------------------------------------------------------- 1 | // generate by :feature:about build_gradle_template_expose 2 | plugins { 3 | alias(libs.plugins.nowinandroid.android.library) 4 | id("kotlin-parcelize") 5 | } 6 | 7 | android { 8 | namespace = "cn.jailedbird.module.about_expose" 9 | } 10 | 11 | dependencies { 12 | implementation(libs.androidx.core.ktx) 13 | implementation(libs.androidx.appcompat) 14 | } -------------------------------------------------------------------------------- /feature/about/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/feature/about/consumer-rules.pro -------------------------------------------------------------------------------- /feature/about/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. 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 -------------------------------------------------------------------------------- /feature/about/src/androidTest/java/cn/jcn/jailedbird/feature/about/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jcn.jailedbird.feature.about 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jcn.jailedbird.feature.about.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feature/about/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /feature/about/src/main/java/cn/jailedbird/feature/about/AboutActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.about 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import cn.jailedbird.core.common.utils.toast 7 | import cn.jailedbird.feature.about.expose.AboutEntity 8 | 9 | /** 10 | * Open content with browser, to do replace it with WebView 11 | * */ 12 | class AboutActivity /*: AppCompatActivity()*/ { 13 | companion object { 14 | @JvmStatic 15 | fun start(context: Context, aboutEntity: AboutEntity) { 16 | val intent = Intent(Intent.ACTION_VIEW, Uri.parse(aboutEntity.url)) 17 | aboutEntity.title.toast() 18 | context.startActivity(intent) 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /feature/about/src/main/java/cn/jailedbird/feature/about/di/AboutModule.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.about.di 2 | 3 | import cn.jailedbird.feature.about.expose.AboutExpose 4 | import cn.jailedbird.feature.about.exposeimpl.AboutExposeImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @InstallIn(SingletonComponent::class) 12 | @Module 13 | abstract class AboutModule { 14 | @Singleton 15 | @Binds 16 | abstract fun bindAboutExpose(aboutExposeImpl: AboutExposeImpl): AboutExpose 17 | } -------------------------------------------------------------------------------- /feature/about/src/main/java/cn/jailedbird/feature/about/expose/AboutEntity.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.about.expose 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | /** 7 | * @Parcelize will be exposed in module_expose, so ensure add id("kotlin-parcelize") in build_gradle_template_expose 8 | * to make compile process successful 9 | * */ 10 | @Parcelize 11 | data class AboutEntity(val title: String, val url: String) : Parcelable 12 | -------------------------------------------------------------------------------- /feature/about/src/main/java/cn/jailedbird/feature/about/expose/AboutExpose.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.about.expose 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import cn.jailedbird.feature.about.expose.AboutEntity 5 | 6 | interface AboutExpose { 7 | /** 8 | * Ensure start as AppCompatActivity, rather than Context, so please implementation(libs.androidx.appcompat) 9 | * in build_gradle_template_expose 10 | * */ 11 | fun openAboutActivity(starter: AppCompatActivity, aboutEntity: AboutEntity) 12 | } -------------------------------------------------------------------------------- /feature/about/src/main/java/cn/jailedbird/feature/about/exposeimpl/AboutExposeImpl.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.about.exposeimpl 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import cn.jailedbird.feature.about.AboutActivity 5 | import cn.jailedbird.feature.about.expose.AboutEntity 6 | import cn.jailedbird.feature.about.expose.AboutExpose 7 | import javax.inject.Inject 8 | 9 | class AboutExposeImpl @Inject constructor() : AboutExpose { 10 | 11 | /** 12 | * @param starter 传递context即可 但是这里为了演示 build_gradle_template_expose 自定义依赖的功能,特意如此设置 13 | * @param aboutEntity AboutEntity需要模块启用 id("kotlin-parcelize") 默认模板也无法做法 同样需要 build_gradle_template_expose实现自定义 14 | * */ 15 | override fun openAboutActivity(starter: AppCompatActivity, aboutEntity: AboutEntity) { 16 | AboutActivity.start(starter, aboutEntity) 17 | } 18 | } -------------------------------------------------------------------------------- /feature/about/src/test/java/cn/jcn/jailedbird/feature/about/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jcn.jailedbird.feature.about 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /feature/benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/benchmark/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | } 4 | 5 | android { 6 | namespace = "cn.jailedbird.feature.benchmark" 7 | } 8 | 9 | dependencies{ 10 | implementation(libs.androidx.core.ktx) 11 | implementation(libs.androidx.appcompat) 12 | implementation(libs.material) 13 | testImplementation(libs.junit4) 14 | androidTestImplementation(libs.junit) 15 | androidTestImplementation(libs.androidx.test.espresso.core) 16 | } -------------------------------------------------------------------------------- /feature/benchmark/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/feature/benchmark/consumer-rules.pro -------------------------------------------------------------------------------- /feature/benchmark/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. 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 -------------------------------------------------------------------------------- /feature/benchmark/src/androidTest/java/cn/jailedbird/feature/benchmark/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.benchmark 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.feature.benchmark.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feature/benchmark/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /feature/benchmark/src/main/java/cn/jailedbird/feature/benchmark/BenchMark.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.benchmark 2 | 3 | object BenchMark { 4 | /*This module is used for test expose.gradle.kts performance*/ 5 | } -------------------------------------------------------------------------------- /feature/benchmark/src/test/java/cn/jailedbird/feature/benchmark/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.benchmark 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /feature/search/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/search/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | alias(libs.plugins.nowinandroid.android.room) 4 | alias(libs.plugins.nowinandroid.android.hilt) 5 | } 6 | 7 | android { 8 | namespace = "cn.jailedbird.feature.search" 9 | 10 | buildFeatures { 11 | viewBinding = true 12 | buildConfig = true 13 | } 14 | } 15 | 16 | 17 | dependencies { 18 | implementation(fileTree("dir" to "libs", "include" to listOf("*.jar"))) 19 | implementation(projects.core.settings) 20 | implementation(projects.core.common) 21 | 22 | // implementation(libs.androidx.activity.compose) 23 | implementation(libs.androidx.activity) 24 | implementation(libs.androidx.appcompat) 25 | implementation(libs.androidx.core.ktx) 26 | implementation(libs.androidx.core.splashscreen) 27 | // implementation(libs.androidx.compose.runtime) 28 | // implementation(libs.androidx.lifecycle.runtimeCompose) 29 | // implementation(libs.androidx.compose.runtime.tracing) 30 | // implementation(libs.androidx.compose.material3.windowSizeClass) 31 | // implementation(libs.androidx.hilt.navigation.compose) 32 | // implementation(libs.androidx.navigation.compose) 33 | // implementation(libs.androidx.window.manager) 34 | // implementation(libs.androidx.profileinstaller) 35 | // implementation(libs.kotlinx.coroutines.guava) 36 | implementation(libs.coil.kt) 37 | 38 | implementation(libs.androidx.navigation.fragment.ktx) 39 | implementation(libs.androidx.navigation.ui.ktx) 40 | 41 | implementation(libs.edgeutils) 42 | implementation(libs.recyclerview) 43 | compileOnly(projects.feature.settingsExpose) 44 | compileOnly(projects.feature.aboutExpose) 45 | 46 | implementation(libs.androidx.core.ktx) 47 | implementation(libs.androidx.appcompat) 48 | implementation(libs.material) 49 | testImplementation(libs.junit4) 50 | androidTestImplementation(libs.junit) 51 | androidTestImplementation(libs.androidx.test.espresso.core) 52 | } -------------------------------------------------------------------------------- /feature/search/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/feature/search/consumer-rules.pro -------------------------------------------------------------------------------- /feature/search/libs/lib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/feature/search/libs/lib.jar -------------------------------------------------------------------------------- /feature/search/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. 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 -------------------------------------------------------------------------------- /feature/search/src/androidTest/java/cn/jailedbird/feature/search/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.feature.search.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feature/search/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/data/AppDao.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.data 2 | 3 | import androidx.room.* 4 | import cn.jailedbird.feature.search.data.entity.AppModel 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | @Dao 8 | interface AppDao { 9 | @Query("SELECT * FROM apps ORDER BY count DESC,timestamp DESC, appName, appNamePinyin") 10 | fun getAppsFlow(): Flow> 11 | 12 | @Query("SELECT * FROM apps") 13 | suspend fun getApps(): List 14 | 15 | @Query("SELECT * FROM apps WHERE appName=:appName AND appPackageName=:appPackageName LIMIT 1") 16 | suspend fun queryAppModel(appPackageName: String, appName: String): AppModel? 17 | 18 | @Query("UPDATE apps SET count=:count WHERE appName=:appName AND appPackageName=:appPackageName") 19 | suspend fun replaceAppModel(appPackageName: String, appName: String, count: Int) 20 | 21 | @Query("DELETE FROM apps") 22 | suspend fun deleteAll() 23 | 24 | @Delete(entity = AppModel::class) 25 | suspend fun deleteAppList(toDelete: List) 26 | 27 | @Insert(onConflict = OnConflictStrategy.ABORT) 28 | suspend fun insertAppList(app: List) 29 | 30 | @Insert(onConflict = OnConflictStrategy.REPLACE) 31 | suspend fun insertAll(app: List) 32 | 33 | @Insert(onConflict = OnConflictStrategy.REPLACE) 34 | suspend fun insertAppModel(appModel: AppModel) 35 | 36 | 37 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/data/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.data 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import cn.jailedbird.feature.search.data.entity.AppModel 8 | import cn.jailedbird.feature.search.utils.DATABASE_NAME 9 | 10 | /** 11 | * The Room database for this app, 12 | * [document](https://developer.android.com/training/data-storage/room?hl=zh-cn) 13 | */ 14 | @Database(entities = [AppModel::class], version = 1, exportSchema = false) 15 | abstract class AppDatabase : RoomDatabase() { 16 | abstract fun appDao(): AppDao 17 | 18 | companion object { 19 | 20 | // For Singleton instantiation 21 | @Volatile 22 | private var instance: AppDatabase? = null 23 | 24 | fun getInstance(context: Context): AppDatabase { 25 | return instance ?: synchronized(this) { 26 | instance ?: buildDatabase(context).also { instance = it } 27 | } 28 | } 29 | 30 | // Create and pre-populate the database. See this article for more details: 31 | // https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1#4785 32 | private fun buildDatabase(context: Context): AppDatabase { 33 | return Room.databaseBuilder(context, AppDatabase::class.java, DATABASE_NAME) 34 | /*.addCallback( 35 | object : RoomDatabase.Callback() { 36 | override fun onCreate(db: SupportSQLiteDatabase) { 37 | super.onCreate(db) 38 | val request = OneTimeWorkRequestBuilder() 39 | .setInputData(workDataOf(KEY_FILENAME to PLANT_DATA_FILENAME)) 40 | .build() 41 | WorkManager.getInstance(context).enqueue(request) 42 | } 43 | } 44 | )*/ 45 | .build() 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/data/AppRepository.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.data 2 | 3 | import cn.jailedbird.feature.search.data.entity.AppModel 4 | import javax.inject.Inject 5 | import javax.inject.Singleton 6 | 7 | @Singleton 8 | class AppRepository @Inject constructor(private val appDao: AppDao) { 9 | 10 | fun getAppsFlow() = appDao.getAppsFlow() 11 | 12 | suspend fun getApps() = appDao.getApps() 13 | 14 | suspend fun refreshAppModelTable(table: List) { 15 | appDao.deleteAll() 16 | appDao.insertAll(table) 17 | } 18 | 19 | suspend fun refreshAppModel(appModel: AppModel) { 20 | appModel.onItemClick() 21 | appDao.insertAppModel(appModel) 22 | } 23 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.di 2 | 3 | import android.content.Context 4 | import cn.jailedbird.feature.search.data.AppDao 5 | import cn.jailedbird.feature.search.data.AppDatabase 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import dagger.hilt.components.SingletonComponent 11 | import javax.inject.Singleton 12 | 13 | @InstallIn(SingletonComponent::class) 14 | @Module 15 | class DatabaseModule { 16 | 17 | @Singleton 18 | @Provides 19 | fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase { 20 | return AppDatabase.getInstance(context) 21 | } 22 | 23 | @Provides 24 | fun provideAppDao(appDatabase: AppDatabase): AppDao { 25 | return appDatabase.appDao() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/di/SearchModule.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.di 2 | 3 | import cn.jailedbird.feature.search.expose.SearchExpose 4 | import cn.jailedbird.feature.search.exposeimpl.SearchExposeImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @InstallIn(SingletonComponent::class) 12 | @Module 13 | abstract class SearchModule { 14 | @Singleton 15 | @Binds 16 | abstract fun bindSearchExpose(searchExposeImpl: SearchExposeImpl): SearchExpose 17 | 18 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/dialog/AppListPopWindow.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.dialog 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.View 6 | import androidx.core.widget.PopupWindowCompat 7 | import cn.jailedbird.feature.search.R 8 | import cn.jailedbird.feature.search.data.entity.AppModel 9 | import cn.jailedbird.feature.search.databinding.PopUpAppListBinding 10 | import cn.jailedbird.feature.search.utils.gotoApkSettings 11 | import cn.jailedbird.core.common.utils.setDebouncingClick 12 | import cn.jailedbird.core.common.utils.toast 13 | import cn.jailedbird.feature.search.utils.uninstallApk 14 | import java.util.* 15 | 16 | 17 | class AppListPopWindow( 18 | context: Context, 19 | private val appModel: AppModel?, 20 | ) : BaseSimplePopUp(context) { 21 | companion object { 22 | fun open( 23 | context: Context, 24 | anchor: View, 25 | appModel: AppModel?, 26 | ) { 27 | val popWindow = AppListPopWindow(context, appModel) 28 | popWindow.contentView.measure( 29 | makeDropDownMeasureSpec(popWindow.width), 30 | makeDropDownMeasureSpec(popWindow.height) 31 | ) 32 | // Right(center) 33 | val offsetX: Int = -(popWindow.contentView.measuredWidth - anchor.width / 2) 34 | val offsetY = 0 35 | PopupWindowCompat.showAsDropDown(popWindow, anchor, offsetX, offsetY, Gravity.START) 36 | } 37 | } 38 | 39 | private lateinit var binding: PopUpAppListBinding 40 | 41 | private val listener = object : Listener { 42 | override fun showInfo(appModel: AppModel) { 43 | context.gotoApkSettings(appModel.appPackageName) 44 | } 45 | 46 | override fun unInstall(appModel: AppModel) { 47 | context.uninstallApk(appModel.appPackageName) 48 | } 49 | 50 | override fun showDebugInfo(appModel: AppModel) { 51 | "count is ${appModel.count} ; timestamp is ${Date(appModel.timestamp)}".toast() 52 | } 53 | } 54 | 55 | override fun getLayout(): Int { 56 | return R.layout.pop_up_app_list 57 | } 58 | 59 | override fun initView(root: View) { 60 | binding = PopUpAppListBinding.bind(root) 61 | } 62 | 63 | override fun initEvent(root: View) { 64 | binding.tvInfo.setDebouncingClick { 65 | appModel?.let { 66 | listener.showInfo(it) 67 | } 68 | dismiss() 69 | } 70 | 71 | binding.tvUnInstall.setDebouncingClick { 72 | appModel?.let { 73 | listener.unInstall(it) 74 | } 75 | dismiss() 76 | } 77 | 78 | /*if (BuildConfig.DEBUG) { 79 | binding.tvDebug.visibility = View.VISIBLE 80 | binding.tvDebug.setDebouncingClick { 81 | appModel?.let { 82 | listener.showDebugInfo(it) 83 | } 84 | dismiss() 85 | } 86 | }*/ 87 | 88 | } 89 | 90 | interface Listener { 91 | fun showInfo(appModel: AppModel) 92 | fun unInstall(appModel: AppModel) 93 | fun showDebugInfo(appModel: AppModel) 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/dialog/AppSettingsPopWindow.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.dialog 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.view.View 6 | import androidx.core.widget.PopupWindowCompat 7 | import cn.jailedbird.feature.search.R 8 | import cn.jailedbird.feature.search.databinding.PopUpAppSettingBinding 9 | import cn.jailedbird.core.common.utils.setDebouncingClick 10 | 11 | class AppSettingsPopWindow( 12 | context: Context, 13 | private val listener: Listener 14 | ) : BaseSimplePopUp(context) { 15 | companion object { 16 | fun open( 17 | context: Context, 18 | anchor: View, 19 | listener: Listener, 20 | ) { 21 | val popWindow = AppSettingsPopWindow(context, listener) 22 | popWindow.contentView.measure( 23 | makeDropDownMeasureSpec(popWindow.width), 24 | makeDropDownMeasureSpec(popWindow.height) 25 | ) 26 | // Right(center) 27 | val offsetX: Int = -(popWindow.contentView.measuredWidth - anchor.width / 2) 28 | val offsetY = 0 29 | PopupWindowCompat.showAsDropDown(popWindow, anchor, offsetX, offsetY, Gravity.START) 30 | } 31 | } 32 | 33 | 34 | private lateinit var binding: PopUpAppSettingBinding 35 | override fun getLayout(): Int { 36 | return R.layout.pop_up_app_setting 37 | } 38 | 39 | override fun initView(root: View) { 40 | binding = PopUpAppSettingBinding.bind(root) 41 | } 42 | 43 | override fun initEvent(root: View) { 44 | binding.tvRefreshApp.setDebouncingClick { 45 | listener.refreshApp() 46 | dismiss() 47 | } 48 | binding.tvRate.setDebouncingClick { 49 | listener.rate() 50 | dismiss() 51 | } 52 | binding.tvShare.setDebouncingClick { 53 | listener.share() 54 | dismiss() 55 | } 56 | binding.tvClearHistory.setDebouncingClick { 57 | listener.clearHistory() 58 | dismiss() 59 | } 60 | binding.tvSettings.setDebouncingClick { 61 | listener.settings() 62 | dismiss() 63 | } 64 | 65 | binding.tvAbout.setDebouncingClick { 66 | listener.about() 67 | dismiss() 68 | } 69 | } 70 | 71 | interface Listener { 72 | fun refreshApp() 73 | fun rate() 74 | fun share() 75 | fun clearHistory() 76 | fun settings() 77 | fun about() 78 | } 79 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/dialog/BaseSimplePopUp.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.dialog 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.graphics.Color 6 | import android.graphics.drawable.ColorDrawable 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.View.MeasureSpec 10 | import android.view.ViewGroup 11 | import android.widget.PopupWindow 12 | 13 | /** 14 | * Popup document: [Popup document](https://www.jianshu.com/p/6c32889e6377) 15 | * */ 16 | abstract class BaseSimplePopUp(context: Context) : PopupWindow() { 17 | companion object { 18 | fun makeDropDownMeasureSpec(measureSpec: Int): Int { 19 | val mode: Int = if (measureSpec == ViewGroup.LayoutParams.WRAP_CONTENT) { 20 | MeasureSpec.UNSPECIFIED 21 | } else { 22 | MeasureSpec.EXACTLY 23 | } 24 | return MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(measureSpec), mode) 25 | } 26 | } 27 | 28 | init { 29 | innerInit(context) 30 | } 31 | 32 | private fun innerInit(context: Context) { 33 | height = ViewGroup.LayoutParams.WRAP_CONTENT 34 | width = ViewGroup.LayoutParams.WRAP_CONTENT 35 | isOutsideTouchable = true 36 | /** 37 | * Avoid touch-event transparent to bottom view 38 | * [解决PopupWindow点击外部区域消失后事件透传](https://www.jianshu.com/p/6a65107b19a1) 39 | */ 40 | // isFocusable = true // TODO 这种方式会导致输入框失去焦点 键盘闪烁 41 | setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 42 | @SuppressLint("InflateParams") 43 | val contentView: View = LayoutInflater.from(context).inflate( 44 | getLayout(), null, false 45 | ) 46 | setContentView(contentView) 47 | initView(contentView) 48 | initEvent(contentView) 49 | } 50 | 51 | open fun initView(root: View) { 52 | 53 | } 54 | 55 | open fun initEvent(root: View) { 56 | 57 | } 58 | 59 | abstract fun getLayout(): Int 60 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/expose/SearchExpose.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.expose 2 | 3 | import android.content.Context 4 | 5 | interface SearchExpose { 6 | fun openSearchActivity(context: Context) 7 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/exposeimpl/SearchExpose.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.exposeimpl 2 | 3 | import android.content.Context 4 | import cn.jailedbird.feature.search.expose.SearchExpose 5 | import cn.jailedbird.feature.search.main.SearchActivity 6 | import javax.inject.Inject 7 | 8 | class SearchExposeImpl @Inject constructor() : SearchExpose { 9 | override fun openSearchActivity(context: Context) { 10 | SearchActivity.start(context) 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/utils/Constants.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.utils 2 | 3 | const val DATABASE_NAME = "smart-app-search-db" 4 | const val EMPTY = "" 5 | const val LAUNCH_DELAY_TIME = 200L -------------------------------------------------------------------------------- /feature/search/src/main/java/cn/jailedbird/feature/search/utils/Ext.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search.utils 2 | 3 | import com.github.promeg.pinyinhelper.Pinyin 4 | 5 | private fun String?.isChinese(): Boolean { 6 | val s = this 7 | if (s.isNullOrEmpty()) { 8 | return false 9 | } else { 10 | s.forEach { 11 | if (Pinyin.isChinese(it)) { 12 | return true 13 | } 14 | } 15 | } 16 | return false 17 | } 18 | 19 | internal fun String?.toPinyin(): String? { 20 | val s = this 21 | return try { 22 | if (s.isChinese()) { 23 | Pinyin.toPinyin(s, EMPTY) 24 | } else { 25 | null 26 | } 27 | } catch (e: Exception) { 28 | null 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /feature/search/src/main/res/drawable/ic_android.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /feature/search/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /feature/search/src/main/res/drawable/ic_baseline_more_vert_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /feature/search/src/main/res/drawable/ic_baseline_search_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /feature/search/src/main/res/drawable/ic_round_first_item_4dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /feature/search/src/main/res/drawable/ic_round_gray_10dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /feature/search/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 25 | 26 | 27 | 45 | 46 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | -------------------------------------------------------------------------------- /feature/search/src/main/res/layout/item_app_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 30 | 31 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /feature/search/src/main/res/layout/item_app_list_first.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 21 | 22 | 34 | 35 | 42 | 43 | -------------------------------------------------------------------------------- /feature/search/src/main/res/layout/pop_up_app_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 32 | 33 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /feature/search/src/main/res/layout/pop_up_app_setting.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 32 | 33 | 43 | 44 | 54 | 55 | 64 | 65 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /feature/search/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /feature/search/src/test/java/cn/jailedbird/feature/search/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.search 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /feature/settings/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /feature/settings/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | alias(libs.plugins.nowinandroid.android.hilt) 4 | } 5 | 6 | android { 7 | namespace = "cn.jailedbird.feature.settings" 8 | 9 | buildFeatures { 10 | viewBinding = true 11 | } 12 | } 13 | 14 | dependencies { 15 | implementation(libs.androidx.core.ktx) 16 | implementation(libs.androidx.appcompat) 17 | implementation(libs.androidx.navigation.fragment.ktx) 18 | implementation(libs.androidx.navigation.ui.ktx) 19 | implementation(libs.material) 20 | implementation(libs.edgeutils) 21 | 22 | implementation(projects.core.settings) 23 | implementation(projects.core.common) 24 | 25 | compileOnly(projects.feature.searchExpose) 26 | 27 | testImplementation(libs.junit4) 28 | androidTestImplementation(libs.junit) 29 | androidTestImplementation(libs.androidx.test.espresso.core) 30 | } -------------------------------------------------------------------------------- /feature/settings/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/feature/settings/consumer-rules.pro -------------------------------------------------------------------------------- /feature/settings/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. 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 -------------------------------------------------------------------------------- /feature/settings/src/androidTest/java/cn/jailedbird/feature/settings/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.jailedbird.feature.settings.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /feature/settings/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /feature/settings/src/main/java/cn/jailedbird/feature/settings/SettingFragment.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import cn.jailedbird.core.common.base.fragment.BaseVBFragment 6 | import cn.jailedbird.core.common.utils.setDebouncingClick 7 | import cn.jailedbird.core.settings.Settings 8 | import cn.jailedbird.edgeutils.paddingTopSystemWindowInsets 9 | import cn.jailedbird.feature.search.expose.SearchExpose 10 | import cn.jailedbird.feature.settings.databinding.FragmentSettingMainBinding 11 | import com.google.android.material.switchmaterial.SwitchMaterial 12 | import dagger.hilt.android.AndroidEntryPoint 13 | import javax.inject.Inject 14 | 15 | @AndroidEntryPoint 16 | class SettingFragment : BaseVBFragment() { 17 | @Inject 18 | lateinit var searchExpose: SearchExpose 19 | 20 | override val inflate: (LayoutInflater, ViewGroup?, Boolean) -> FragmentSettingMainBinding 21 | get() = FragmentSettingMainBinding::inflate 22 | 23 | override fun initView() { 24 | binding.appbar.paddingTopSystemWindowInsets() 25 | bindPreference(binding.swCenterMatch, Settings.Key.MatchCenter) 26 | bindPreference(binding.swAutoPopIme, Settings.Key.ImeAutoPop) 27 | bindPreference( 28 | binding.swDirectLaunch, 29 | Settings.Key.LaunchDirect 30 | ) 31 | } 32 | 33 | override fun initEvent() { 34 | binding.btReturnSearch.setDebouncingClick { 35 | requireActivity().finish() 36 | searchExpose.openSearchActivity(this@SettingFragment.requireContext()) 37 | } 38 | } 39 | 40 | private fun bindPreference(switch: SwitchMaterial, key: Settings.Key) { 41 | switch.isChecked = Settings[key] 42 | switch.setOnCheckedChangeListener { _, isChecked -> 43 | Settings[key] = isChecked 44 | } 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /feature/settings/src/main/java/cn/jailedbird/feature/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.navigation.NavController 8 | import androidx.navigation.NavGraph 9 | import androidx.navigation.createGraph 10 | import androidx.navigation.fragment.NavHostFragment 11 | import androidx.navigation.fragment.fragment 12 | import cn.jailedbird.edgeutils.EdgeUtils.edgeSetSystemBarLight 13 | import cn.jailedbird.edgeutils.EdgeUtils.edgeToEdge 14 | import cn.jailedbird.feature.search.expose.SearchExpose 15 | import cn.jailedbird.feature.settings.databinding.ActivitySettingsBinding 16 | import dagger.hilt.android.AndroidEntryPoint 17 | import javax.inject.Inject 18 | 19 | @AndroidEntryPoint 20 | class SettingsActivity : AppCompatActivity() { 21 | companion object { 22 | @JvmStatic 23 | internal fun start(context: Context) { 24 | val starter = Intent(context, SettingsActivity::class.java) 25 | context.startActivity(starter) 26 | } 27 | } 28 | 29 | private lateinit var binding: ActivitySettingsBinding 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | edgeToEdge(false) 33 | edgeSetSystemBarLight(true) 34 | super.onCreate(savedInstanceState) 35 | binding = ActivitySettingsBinding.inflate(layoutInflater) 36 | setContentView(binding.root) 37 | initView() 38 | } 39 | 40 | private lateinit var navController: NavController 41 | 42 | private fun initView() { 43 | val navHostFragment = supportFragmentManager 44 | .findFragmentById(R.id.nav_host) as NavHostFragment 45 | navController = navHostFragment.navController 46 | 47 | navHostFragment.navController.apply { 48 | graph = settingGraph() 49 | } 50 | } 51 | 52 | @Suppress("ClassName") 53 | private object nav_routes { 54 | const val setting_main_fragment = "setting_main_fragment" 55 | } 56 | 57 | private fun NavController.settingGraph(): NavGraph = createGraph( 58 | startDestination = nav_routes.setting_main_fragment 59 | ) { 60 | fragment(nav_routes.setting_main_fragment) { 61 | label = "Settings" 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /feature/settings/src/main/java/cn/jailedbird/feature/settings/di/SettingModule.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings.di 2 | 3 | import cn.jailedbird.feature.settings.expose.SettingExpose 4 | import cn.jailedbird.feature.settings.exposeimpl.SettingExposeImpl 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | abstract class SettingModule { 14 | @Binds 15 | @Singleton 16 | abstract fun bindSettingExpose(settingExposeImpl: SettingExposeImpl): SettingExpose 17 | } -------------------------------------------------------------------------------- /feature/settings/src/main/java/cn/jailedbird/feature/settings/expose/SettingExpose.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings.expose 2 | 3 | import android.content.Context 4 | 5 | interface SettingExpose { 6 | fun startSettingActivity(context: Context) 7 | } -------------------------------------------------------------------------------- /feature/settings/src/main/java/cn/jailedbird/feature/settings/exposeimpl/SettingExposeImpl.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings.exposeimpl 2 | 3 | import android.content.Context 4 | import cn.jailedbird.feature.settings.SettingsActivity 5 | import cn.jailedbird.feature.settings.expose.SettingExpose 6 | import javax.inject.Inject 7 | 8 | 9 | class SettingExposeImpl @Inject constructor() : SettingExpose { 10 | override fun startSettingActivity(context: Context) { 11 | SettingsActivity.start(context) 12 | } 13 | } -------------------------------------------------------------------------------- /feature/settings/src/main/res/layout/activity_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /feature/settings/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | SmartAppSearch 4 | 设置 5 | 设置 6 | 启用中缀匹配 7 | 启用in/ing模糊匹配 8 | 启动时自动弹出键盘 9 | 选中app时直接启动 10 | 返回搜索 11 | -------------------------------------------------------------------------------- /feature/settings/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SmartAppSearch 3 | Settings 4 | Choose theme 5 | Infix matching 6 | enable_confuse_ing 7 | enable_auto_pop_ime 8 | enable_auto_open_app 9 | return to search 10 | -------------------------------------------------------------------------------- /feature/settings/src/test/java/cn/jailedbird/feature/settings/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package cn.jailedbird.feature.settings 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /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 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | android.defaults.buildfeatures.buildconfig=true 25 | -------------------------------------------------------------------------------- /gradle/expose/build_gradle_template_android: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | } 4 | 5 | android { 6 | namespace = "%s" 7 | } -------------------------------------------------------------------------------- /gradle/expose/build_gradle_template_expose: -------------------------------------------------------------------------------- 1 | // Customize the build.gradle.kts file for module_expose, named build_gradle_template_expose, and place the build_gradle_template_expose file in the module's root directory. -------------------------------------------------------------------------------- /gradle/expose/build_gradle_template_java: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.jvm.library) 3 | } -------------------------------------------------------------------------------- /gradle/expose/expose.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/gradle/expose/expose.md -------------------------------------------------------------------------------- /gradle/expose_gradle/build_gradle_template_android: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.android.library) 3 | } 4 | 5 | android { 6 | namespace = "%s" 7 | } -------------------------------------------------------------------------------- /gradle/expose_gradle/build_gradle_template_expose: -------------------------------------------------------------------------------- 1 | // Customize the build.gradle.kts file for module_expose, named build_gradle_template_expose, and place the build_gradle_template_expose file in the module's root directory. -------------------------------------------------------------------------------- /gradle/expose_gradle/build_gradle_template_java: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.nowinandroid.jvm.library) 3 | } -------------------------------------------------------------------------------- /gradle/expose_gradle/expose.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/gradle/expose_gradle/expose.md -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Dec 09 11:32:42 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /moduleexpose.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JailedBird/ModuleExpose/9d012e1f643383c7d113c7ccdd6cef462be8b15e/moduleexpose.jks -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | 3 | pluginManagement { 4 | includeBuild("build-logic") 5 | repositories { 6 | // maven { url = uri("https://maven.aliyun.com/repository/public") } 7 | // maven { url = uri("https://maven.aliyun.com/repository/google") } 8 | // maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") } 9 | google() 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | 15 | dependencyResolutionManagement { 16 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 17 | repositories { 18 | // maven { url = uri("https://maven.aliyun.com/repository/public") } 19 | // maven { url = uri("https://maven.aliyun.com/repository/google") } 20 | google() 21 | mavenCentral() 22 | maven { url = uri("https://jitpack.io") } 23 | } 24 | } 25 | // TYPESAFE_PROJECT_ACCESSORS, we can implement project likes: 26 | // implementation(projects.core.resource) 27 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 28 | 29 | apply(from = "$rootDir/gradle/expose/expose.gradle.kts") 30 | val includeWithExpose: (projectPaths: String) -> Unit by extra 31 | val includeWithJavaExpose: (projectPaths: String) -> Unit by extra 32 | 33 | rootProject.name = "ModuleExpose" 34 | include(":app") 35 | include(":core:settings") 36 | include(":core:resource") 37 | include(":core:common") 38 | includeWithExpose(":feature:settings") 39 | includeWithExpose(":feature:search") 40 | includeWithExpose(":feature:about") 41 | includeWithExpose(":feature:benchmark") -------------------------------------------------------------------------------- /subproj/GradleSample/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | *_expose/* 12 | -------------------------------------------------------------------------------- /subproj/GradleSample/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /subproj/GradleSample/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.example.myapplication' 8 | compileSdk 34 9 | 10 | defaultConfig { 11 | applicationId "com.example.myapplication" 12 | minSdk 24 13 | targetSdk 34 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | buildFeatures { 34 | viewBinding true 35 | } 36 | } 37 | 38 | dependencies { 39 | 40 | implementation 'androidx.core:core-ktx:1.9.0' 41 | implementation 'androidx.appcompat:appcompat:1.6.1' 42 | implementation 'com.google.android.material:material:1.8.0' 43 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 44 | implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' 45 | implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' 46 | testImplementation 'junit:junit:4.13.2' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' 49 | } -------------------------------------------------------------------------------- /subproj/GradleSample/app/build_gradle_template_expose: -------------------------------------------------------------------------------- 1 | // Generate by build_gradle_template_expose 2 | plugins { 3 | id 'com.android.library' 4 | id 'org.jetbrains.kotlin.android' 5 | } 6 | 7 | android { 8 | namespace 'cn.jailedbird.module.app_expose' 9 | compileSdk 34 10 | 11 | defaultConfig { 12 | minSdk 24 13 | 14 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles "consumer-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = '1.8' 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /subproj/GradleSample/app/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. 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 -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.example.myapplication", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/java/com/example/myapplication/FirstFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.navigation.fragment.findNavController 9 | import com.example.myapplication.databinding.FragmentFirstBinding 10 | 11 | /** 12 | * A simple [Fragment] subclass as the default destination in the navigation. 13 | */ 14 | class FirstFragment : Fragment() { 15 | 16 | private var _binding: FragmentFirstBinding? = null 17 | 18 | // This property is only valid between onCreateView and 19 | // onDestroyView. 20 | private val binding get() = _binding!! 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View? { 26 | 27 | _binding = FragmentFirstBinding.inflate(inflater, container, false) 28 | return binding.root 29 | 30 | } 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | 35 | binding.buttonFirst.setOnClickListener { 36 | findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment) 37 | } 38 | } 39 | 40 | override fun onDestroyView() { 41 | super.onDestroyView() 42 | _binding = null 43 | } 44 | } -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/java/com/example/myapplication/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import android.os.Bundle 4 | import com.google.android.material.snackbar.Snackbar 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.navigation.findNavController 7 | import androidx.navigation.ui.AppBarConfiguration 8 | import androidx.navigation.ui.navigateUp 9 | import androidx.navigation.ui.setupActionBarWithNavController 10 | import android.view.Menu 11 | import android.view.MenuItem 12 | import com.example.myapplication.databinding.ActivityMainBinding 13 | 14 | class MainActivity : AppCompatActivity() { 15 | 16 | private lateinit var appBarConfiguration: AppBarConfiguration 17 | private lateinit var binding: ActivityMainBinding 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | super.onCreate(savedInstanceState) 21 | 22 | binding = ActivityMainBinding.inflate(layoutInflater) 23 | setContentView(binding.root) 24 | 25 | setSupportActionBar(binding.toolbar) 26 | 27 | val navController = findNavController(R.id.nav_host_fragment_content_main) 28 | appBarConfiguration = AppBarConfiguration(navController.graph) 29 | setupActionBarWithNavController(navController, appBarConfiguration) 30 | 31 | binding.fab.setOnClickListener { view -> 32 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 33 | .setAction("Action", null).show() 34 | } 35 | } 36 | 37 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 38 | // Inflate the menu; this adds items to the action bar if it is present. 39 | menuInflater.inflate(R.menu.menu_main, menu) 40 | return true 41 | } 42 | 43 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 44 | // Handle action bar item clicks here. The action bar will 45 | // automatically handle clicks on the Home/Up button, so long 46 | // as you specify a parent activity in AndroidManifest.xml. 47 | return when (item.itemId) { 48 | R.id.action_settings -> true 49 | else -> super.onOptionsItemSelected(item) 50 | } 51 | } 52 | 53 | override fun onSupportNavigateUp(): Boolean { 54 | val navController = findNavController(R.id.nav_host_fragment_content_main) 55 | return navController.navigateUp(appBarConfiguration) 56 | || super.onSupportNavigateUp() 57 | } 58 | } -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/java/com/example/myapplication/SecondFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.navigation.fragment.findNavController 9 | import com.example.myapplication.databinding.FragmentSecondBinding 10 | 11 | /** 12 | * A simple [Fragment] subclass as the second destination in the navigation. 13 | */ 14 | class SecondFragment : Fragment() { 15 | 16 | private var _binding: FragmentSecondBinding? = null 17 | 18 | // This property is only valid between onCreateView and 19 | // onDestroyView. 20 | private val binding get() = _binding!! 21 | 22 | override fun onCreateView( 23 | inflater: LayoutInflater, container: ViewGroup?, 24 | savedInstanceState: Bundle? 25 | ): View? { 26 | 27 | _binding = FragmentSecondBinding.inflate(inflater, container, false) 28 | return binding.root 29 | 30 | } 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | 35 | binding.buttonSecond.setOnClickListener { 36 | findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment) 37 | } 38 | } 39 | 40 | override fun onDestroyView() { 41 | super.onDestroyView() 42 | _binding = null 43 | } 44 | } -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/java/com/example/myapplication/expose/a.kt: -------------------------------------------------------------------------------- 1 | package com.example.myapplication.expose 2 | 3 | class a { 4 | } -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 32 | 33 | -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /subproj/GradleSample/app/src/main/res/layout/fragment_first.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 |