├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-v26
│ │ │ └── ic_launcher.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── dev
│ │ └── yankew
│ │ └── sample
│ │ └── cleanarch
│ │ ├── MainApplication.kt
│ │ ├── crashlytics
│ │ ├── CrashlyticsHelper.kt
│ │ └── CrashlyticsTree.kt
│ │ ├── AppModule.kt
│ │ └── AppBaseInitializerImpl.kt
├── proguard-rules.pro
├── google-services.json
└── build.gradle.kts
├── domain
├── .gitignore
├── src
│ └── main
│ │ └── java
│ │ └── dev
│ │ └── yankew
│ │ └── sample
│ │ └── domain
│ │ ├── event
│ │ ├── AnalyticsOptionEvent.kt
│ │ ├── CrashlyticsOptionEvent.kt
│ │ ├── EventHandler.kt
│ │ ├── AppLaunchedEvent.kt
│ │ ├── EventDispatcher.kt
│ │ └── EventModule.kt
│ │ ├── util
│ │ ├── Optional.kt
│ │ └── Logger.kt
│ │ ├── model
│ │ ├── AppVariant.kt
│ │ └── DataResult.kt
│ │ ├── config
│ │ ├── NewFeatureParam.kt
│ │ ├── Parameters.kt
│ │ ├── ConfigModule.kt
│ │ ├── ConfigValue.kt
│ │ └── ConfigParam.kt
│ │ ├── repository
│ │ ├── AppRepository.kt
│ │ └── FeatureRepository.kt
│ │ ├── settings
│ │ ├── SettingOption.kt
│ │ └── SettingsQualifiers.kt
│ │ ├── analytics
│ │ ├── AnalyticsTracker.kt
│ │ ├── ClickTrackingEvent.kt
│ │ ├── AnalyticsEventHandlers.kt
│ │ ├── AnalyticsEventDispatcher.kt
│ │ ├── AnalyticsUserProperties.kt
│ │ ├── AnalyticsModule.kt
│ │ └── SettingsChangedTrackingEvent.kt
│ │ ├── coroutine
│ │ ├── CoroutinesQualifiers.kt
│ │ └── CoroutineModule.kt
│ │ └── interactor
│ │ ├── GetAppVariantUseCase.kt
│ │ ├── FlowUseCase.kt
│ │ ├── GetElapsedMinutesUseCase.kt
│ │ └── GetNewFeatureEnabledUseCase.kt
└── build.gradle.kts
├── comp
├── core
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── core
│ │ │ ├── CoreModule.kt
│ │ │ └── LoggerImpl.kt
│ └── build.gradle.kts
├── download
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── feature
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── feature
│ │ │ ├── FeatureRepositoryImpl.kt
│ │ │ ├── FeatureEventHandlers.kt
│ │ │ ├── ElapseDataSource.kt
│ │ │ └── FeatureModule.kt
│ └── build.gradle.kts
├── sample-data
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── data
│ │ │ └── sample
│ │ │ ├── SampleModule.kt
│ │ │ └── SampleFeatureRepository.kt
│ └── build.gradle.kts
├── settings
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── settings
│ │ │ ├── SettingsModule.kt
│ │ │ └── SettingsStore.kt
│ └── build.gradle.kts
├── analytics-debug
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── analytics
│ │ │ └── debug
│ │ │ ├── DebugAnalyticsModule.kt
│ │ │ └── DebugAnalyticsTracker.kt
│ └── build.gradle.kts
├── config-firebase
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── res
│ │ │ └── xml
│ │ │ │ └── remote_config_defaults.xml
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── config
│ │ │ └── firebase
│ │ │ ├── RemoteConfig.kt
│ │ │ ├── RemoteConfigInitializer.kt
│ │ │ ├── RemoteParams.kt
│ │ │ ├── RemoteParam.kt
│ │ │ ├── RemoteConfigModule.kt
│ │ │ ├── RemoteConfigHelper.kt
│ │ │ ├── RemoteConfigImpl.kt
│ │ │ └── RemoteConfigFetchWorker.kt
│ └── build.gradle.kts
├── config-local
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── config
│ │ │ └── local
│ │ │ ├── LocalParams.kt
│ │ │ └── LocalConfigModule.kt
│ └── build.gradle.kts
├── notification
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ └── AndroidManifest.xml
│ └── build.gradle.kts
└── analytics-firebase
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── dev
│ │ └── yankew
│ │ └── sample
│ │ └── analytics
│ │ └── firebase
│ │ ├── AnalyticsOptionHandler.kt
│ │ ├── FirebaseAnalyticsModule.kt
│ │ ├── FirebaseAnalyticsTracker.kt
│ │ └── FirebaseAnalyticsInitializer.kt
│ └── build.gradle.kts
├── lib
├── android
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── android
│ │ │ └── FlowBroadcastReceiver.kt
│ └── build.gradle.kts
├── compose
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── compose
│ │ │ ├── theme
│ │ │ ├── Type.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Color.kt
│ │ │ └── Theme.kt
│ │ │ ├── ActivityViewModel.kt
│ │ │ └── FlowWithLifecycle.kt
│ └── build.gradle.kts
├── init
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ │ └── main
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java
│ │ │ └── dev
│ │ │ └── yankew
│ │ │ └── sample
│ │ │ └── init
│ │ │ ├── AppBaseInitializer.kt
│ │ │ ├── ComponentInitializer.kt
│ │ │ ├── InitModule.kt
│ │ │ └── AppBaseInitializerWrapper.kt
│ └── build.gradle.kts
└── strings
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ └── res
│ │ └── values
│ │ └── strings.xml
│ └── build.gradle.kts
├── apps
└── demo-mobile
│ ├── .gitignore
│ ├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── drawable-v26
│ │ │ └── ic_launcher.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── dev
│ │ └── yankew
│ │ └── sample
│ │ └── demo
│ │ ├── DemoBaseInitializer.kt
│ │ ├── DemoModule.kt
│ │ └── DemoApplication.kt
│ ├── proguard-rules.pro
│ └── build.gradle.kts
├── ui
├── ui-mobile
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── dev
│ │ └── yankew
│ │ └── sample
│ │ └── ui
│ │ ├── navigation
│ │ ├── NavDestinations.kt
│ │ └── MainNavGraph.kt
│ │ ├── MainActivity.kt
│ │ ├── about
│ │ ├── AboutViewModel.kt
│ │ ├── DeveloperViewModel.kt
│ │ ├── DeveloperScreen.kt
│ │ └── AboutScreen.kt
│ │ ├── MainViewModel.kt
│ │ ├── home
│ │ └── HomeViewModel.kt
│ │ ├── new
│ │ └── NewFeatureScreen.kt
│ │ ├── settings
│ │ ├── SettingsViewModel.kt
│ │ └── SettingsSwitch.kt
│ │ ├── MainApp.kt
│ │ └── Scaffolds.kt
└── ui-widget
│ ├── .gitignore
│ ├── consumer-rules.pro
│ ├── src
│ └── main
│ │ └── AndroidManifest.xml
│ └── build.gradle.kts
├── .github
├── debug.keystore
├── ci-gradle.properties
└── workflows
│ └── Check.yaml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .editorconfig
├── .gitignore
├── doc
├── mobile-demo-modules.puml
├── mobile-app-modules.puml
├── all-modules.puml
└── build-scripts.puml
├── .spotless
└── copyright.kt
├── gradle.properties
├── settings.gradle.kts
├── depconstraints
└── build.gradle.kts
├── README.md
└── gradlew.bat
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/core/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib/compose/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib/init/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/lib/init/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/strings/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/apps/demo-mobile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/download/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/download/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/feature/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/feature/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/sample-data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/settings/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/settings/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/android/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/compose/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/strings/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/ui-mobile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/ui-mobile/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/ui-widget/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/ui-widget/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/analytics-debug/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/config-firebase/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/config-local/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/config-local/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/notification/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/notification/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/sample-data/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/analytics-debug/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/comp/analytics-firebase/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/comp/config-firebase/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.github/debug.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yacca/android-clean-arch/HEAD/.github/debug.keystore
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yacca/android-clean-arch/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.github/ci-gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.daemon=false
2 | org.gradle.jvmargs=-Xmx4096m
3 | org.gradle.parallel=true
4 |
5 | kotlin.incremental=false
6 | kotlin.compiler.execution.strategy=in-process
7 |
--------------------------------------------------------------------------------
/comp/core/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/compose/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/lib/strings/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/ui-widget/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/download/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/feature/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/comp/settings/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/config-local/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/notification/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/sample-data/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/analytics-debug/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/res/xml/remote_config_defaults.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | new_feature_enabled
5 | false
6 |
7 |
8 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig: http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | insert_final_newline = true
6 |
7 | [*.{yml, json}]
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [*.{kt, kts, java}]
12 | indent_size = 4
13 | max_line_length = 100
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # OS files
9 | .DS_Store
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild
14 | .cxx
15 |
16 | # Intellij
17 | *.iml
18 | .idea/
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/res/drawable/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/res/drawable-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/doc/mobile-demo-modules.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | folder "apps" {
3 | [demo-mobile]
4 | }
5 |
6 | folder "ui" {
7 | [ui-mobile]
8 | [ui-widget]
9 | }
10 |
11 | folder "comp" {
12 | [analytics-debug]
13 | [config-local]
14 | [core]
15 | [notification]
16 | [sample-data]
17 | [settings]
18 | }
19 |
20 | [demo-mobile] ...> [analytics-debug]
21 | [demo-mobile] ...> [config-local]
22 | [demo-mobile] ...> [core]
23 | [demo-mobile] ...> [notification]
24 | [demo-mobile] ...> [sample-data]
25 | [demo-mobile] ...> [settings]
26 | [demo-mobile] .up.> [ui-mobile]
27 | [demo-mobile] .up.> [ui-widget]
28 |
29 | [demo-mobile] -right--> [domain]
30 | @enduml
31 |
--------------------------------------------------------------------------------
/.spotless/copyright.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright $YEAR WANG Yanke
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 | * http://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 |
--------------------------------------------------------------------------------
/doc/mobile-app-modules.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | folder "ui" {
3 | [ui-mobile]
4 | [ui-widget]
5 | }
6 |
7 | folder "comp" {
8 | [analytics-debug]
9 | [analytics-firebase]
10 | [config-firebase]
11 | [config-local]
12 | [core]
13 | [download]
14 | [feature]
15 | [notification]
16 | [settings]
17 | }
18 |
19 | [app] ...> [analytics-debug]
20 | [app] ...> [analytics-firebase]
21 | [app] ...> [config-firebase]
22 | [app] ...> [config-local]
23 | [app] ...> [core]
24 | [app] ...> [download]
25 | [app] ...> [feature]
26 | [app] ...> [notification]
27 | [app] ...> [settings]
28 | [app] .up.> [ui-mobile]
29 | [app] .up.> [ui-widget]
30 |
31 | [app] -right--> [domain]
32 | @enduml
33 |
--------------------------------------------------------------------------------
/comp/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
--------------------------------------------------------------------------------
/comp/download/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
--------------------------------------------------------------------------------
/comp/sample-data/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
--------------------------------------------------------------------------------
/lib/compose/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.compose-library")
19 | }
20 |
--------------------------------------------------------------------------------
/lib/init/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/comp/analytics-debug/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
--------------------------------------------------------------------------------
/lib/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.base-android-library")
19 | }
20 |
--------------------------------------------------------------------------------
/lib/strings/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.base-android-library")
19 | }
20 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
13 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
12 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/lib/init/src/main/java/dev/yankew/sample/init/AppBaseInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.init
18 |
19 | interface AppBaseInitializer {
20 | fun init()
21 | }
22 |
--------------------------------------------------------------------------------
/ui/ui-widget/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.ui-component")
19 | }
20 |
21 | dependencies {
22 | implementation(project(":lib:compose"))
23 | }
24 |
--------------------------------------------------------------------------------
/comp/notification/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
21 | dependencies {
22 | implementation(project(":lib:strings"))
23 | }
24 |
--------------------------------------------------------------------------------
/lib/init/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.hilt-library")
19 | }
20 |
21 | dependencies {
22 | // App Startup
23 | implementation(Libs.STARTUP)
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/event/AnalyticsOptionEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.event
18 |
19 | data class AnalyticsOptionEvent(val enabled: Boolean)
20 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/event/CrashlyticsOptionEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.event
18 |
19 | data class CrashlyticsOptionEvent(val enabled: Boolean)
20 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/event/EventHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.event
18 |
19 | fun interface EventHandler {
20 | suspend fun handle(event: T)
21 | }
22 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/util/Optional.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.util
18 |
19 | import java.util.Optional
20 |
21 | fun Optional.orNull(): T? = orElse(null)
22 |
--------------------------------------------------------------------------------
/apps/demo-mobile/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
--------------------------------------------------------------------------------
/comp/settings/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
21 | dependencies {
22 | // Datastore Preferences
23 | implementation(Libs.DATA_STORE_PREFERENCES)
24 | }
25 |
--------------------------------------------------------------------------------
/comp/config-local/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
21 | dependencies {
22 | // Datastore Preferences
23 | implementation(Libs.DATA_STORE_PREFERENCES)
24 | }
25 |
--------------------------------------------------------------------------------
/ui/ui-mobile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.ui-component")
19 | }
20 |
21 | dependencies {
22 | implementation(project(":lib:compose"))
23 |
24 | implementation(Libs.ANNOTATION)
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/model/AppVariant.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.model
18 |
19 | enum class AppVariant {
20 | MOBILE,
21 | MOBILE_DEMO,
22 | TV,
23 | TV_DEMO
24 | }
25 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/lib/compose/src/main/java/dev/yankew/sample/compose/theme/Type.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.compose.theme
18 |
19 | import androidx.compose.material.Typography
20 |
21 | val typography = Typography(
22 | // Use default Typography
23 | )
24 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/config/NewFeatureParam.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.config
18 |
19 | import javax.inject.Qualifier
20 |
21 | @Retention(AnnotationRetention.BINARY)
22 | @Qualifier
23 | annotation class NewFeatureParam
24 |
--------------------------------------------------------------------------------
/comp/feature/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
21 | dependencies {
22 | implementation(project(":lib:android"))
23 |
24 | // Datastore Preferences
25 | implementation(Libs.DATA_STORE_PREFERENCES)
26 | }
27 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/repository/AppRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.repository
18 |
19 | import dev.yankew.sample.domain.model.AppVariant
20 |
21 | interface AppRepository {
22 | fun getAppVariant(): AppVariant
23 | }
24 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/repository/FeatureRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.repository
18 |
19 | import kotlinx.coroutines.flow.Flow
20 |
21 | interface FeatureRepository {
22 | fun getElapsedMinutes(): Flow
23 | }
24 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/settings/SettingOption.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.settings
18 |
19 | import kotlinx.coroutines.flow.Flow
20 |
21 | interface SettingOption {
22 | fun getValue(): Flow
23 | suspend fun setValue(value: T)
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/util/Logger.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.util
18 |
19 | interface Logger {
20 | fun e(message: String)
21 | fun w(message: String)
22 | fun i(message: String)
23 | fun d(message: String)
24 | fun v(message: String)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/event/AppLaunchedEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.event
18 |
19 | import dev.yankew.sample.domain.model.AppVariant
20 |
21 | /**
22 | * This event happens in onCreate of Application when all initializations completed
23 | */
24 | data class AppLaunchedEvent(val variant: AppVariant)
25 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteConfig.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import dev.yankew.sample.domain.config.ConfigValue
20 | import kotlinx.coroutines.flow.Flow
21 |
22 | interface RemoteConfig {
23 | operator fun get(param: RemoteParam): Flow>
24 | }
25 |
--------------------------------------------------------------------------------
/lib/init/src/main/java/dev/yankew/sample/init/ComponentInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.init
18 |
19 | import androidx.startup.Initializer
20 |
21 | abstract class ComponentInitializer : Initializer {
22 | override fun dependencies(): List>> =
23 | listOf(AppBaseInitializerWrapper::class.java)
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
21 | dependencies {
22 | implementation(project(":lib:init"))
23 |
24 | // Firebase
25 | implementation(platform(Libs.FIREBASE_BOM))
26 | implementation(Libs.FIREBASE_ANALYTICS)
27 |
28 | // Startup initialization
29 | implementation(Libs.STARTUP)
30 | }
31 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/navigation/NavDestinations.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.navigation
18 |
19 | object NavDestinations {
20 | const val ROUTE_HOME = "home"
21 | const val ROUTE_NEW_FEATURE = "new-feature"
22 | const val ROUTE_SETTINGS = "settings"
23 | const val ROUTE_ABOUT = "about"
24 | const val ROUTE_DEVELOPER = "developer"
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/AnalyticsTracker.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | typealias AnalyticsTrackers = Set<@JvmSuppressWildcards AnalyticsTracker>
20 |
21 | interface AnalyticsTracker {
22 | suspend fun isAnalyticsEnabled(): Boolean
23 | fun logEvent(name: String, params: Map)
24 | fun setUserProperty(key: String, value: String)
25 | }
26 |
--------------------------------------------------------------------------------
/doc/all-modules.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | folder "apps" {
3 | [app-tv]
4 | [demo-mobile]
5 | [demo-tv]
6 | }
7 |
8 | folder "ui" {
9 | [ui-mobile]
10 | [ui-tv]
11 | [ui-widget]
12 | }
13 |
14 | folder "comp" {
15 | [analytics-firebase]
16 | [analytics-debug]
17 | [config-firebase]
18 | [config-local]
19 | [core]
20 | [download]
21 | [feature]
22 | [notification]
23 | [sample-data]
24 | [settings]
25 | }
26 |
27 | folder "lib" {
28 | [android]
29 | [compose]
30 | [strings]
31 | }
32 |
33 | [app] --> [domain]
34 |
35 | [app-tv] --> [domain]
36 | [demo-mobile] --> [domain]
37 | [demo-tv] --> [domain]
38 |
39 | [ui-mobile] --> [domain]
40 | [ui-tv] --> [domain]
41 | [ui-widget] --> [domain]
42 |
43 | [analytics-firebase] -up-> [domain]
44 | [analytics-debug] -up-> [domain]
45 | [config-firebase] -up-> [domain]
46 | [config-local] -up-> [domain]
47 | [core] -up---> [domain]
48 | [download] -up-> [domain]
49 | [feature] -up-> [domain]
50 | [notification] -up-> [domain]
51 | [sample-data] -up-> [domain]
52 | [settings] -up-> [domain]
53 | @enduml
54 |
--------------------------------------------------------------------------------
/lib/init/src/main/java/dev/yankew/sample/init/InitModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.init
18 |
19 | import dagger.BindsOptionalOf
20 | import dagger.Module
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 |
24 | @Module
25 | @InstallIn(SingletonComponent::class)
26 | interface InitModule {
27 | @BindsOptionalOf
28 | fun optionalInitializer(): AppBaseInitializer
29 | }
30 |
--------------------------------------------------------------------------------
/lib/compose/src/main/java/dev/yankew/sample/compose/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.compose.theme
18 |
19 | import androidx.compose.foundation.shape.RoundedCornerShape
20 | import androidx.compose.material.Shapes
21 | import androidx.compose.ui.unit.dp
22 |
23 | val shapes = Shapes(
24 | small = RoundedCornerShape(0.dp),
25 | medium = RoundedCornerShape(0.dp),
26 | large = RoundedCornerShape(0.dp)
27 | )
28 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/config/Parameters.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.config
18 |
19 | /**
20 | * The implementation of the parameter is optional. Always use Optional during injection.
21 | */
22 | typealias NewFeatureLocalParam = LocalConfigParam
23 |
24 | /**
25 | * The implementation of the parameter is optional. Always use Optional during injection.
26 | */
27 | typealias NewFeatureRemoteParam = RemoteConfigParam
28 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/coroutine/CoroutinesQualifiers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.coroutine
18 |
19 | import javax.inject.Qualifier
20 |
21 | @Retention(AnnotationRetention.BINARY)
22 | @Qualifier
23 | annotation class DefaultDispatcher
24 |
25 | @Retention(AnnotationRetention.BINARY)
26 | @Qualifier
27 | annotation class IoDispatcher
28 |
29 | @Retention(AnnotationRetention.BINARY)
30 | @Qualifier
31 | annotation class ApplicationScope
32 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/settings/SettingsQualifiers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.settings
18 |
19 | import javax.inject.Qualifier
20 |
21 | @Retention(AnnotationRetention.BINARY)
22 | @Qualifier
23 | annotation class AnalyticsOption
24 |
25 | @Retention(AnnotationRetention.BINARY)
26 | @Qualifier
27 | annotation class CrashlyticsOption
28 |
29 | @Retention(AnnotationRetention.BINARY)
30 | @Qualifier
31 | annotation class NotificationOption
32 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/interactor/GetAppVariantUseCase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.interactor
18 |
19 | import dev.yankew.sample.domain.model.AppVariant
20 | import dev.yankew.sample.domain.repository.AppRepository
21 | import javax.inject.Inject
22 |
23 | class GetAppVariantUseCase @Inject constructor(
24 | private val appRepository: AppRepository
25 | ) {
26 | operator fun invoke(): AppVariant = appRepository.getAppVariant()
27 | }
28 |
--------------------------------------------------------------------------------
/comp/core/src/main/java/dev/yankew/sample/core/CoreModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.core
18 |
19 | import dagger.Binds
20 | import dagger.Module
21 | import dagger.Reusable
22 | import dagger.hilt.InstallIn
23 | import dagger.hilt.components.SingletonComponent
24 | import dev.yankew.sample.domain.util.Logger
25 |
26 | @Module
27 | @InstallIn(SingletonComponent::class)
28 | interface CoreModule {
29 | @Reusable
30 | @Binds
31 | fun logger(logger: LoggerImpl): Logger
32 | }
33 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteConfigInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import android.content.Context
20 | import dev.yankew.sample.init.ComponentInitializer
21 | import timber.log.Timber
22 |
23 | class RemoteConfigInitializer : ComponentInitializer() {
24 | override fun create(context: Context) {
25 | Timber.i("Initialize Firebase Remote Config")
26 | RemoteConfigFetchWorker.enqueue(context)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/comp/sample-data/src/main/java/dev/yankew/sample/data/sample/SampleModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.data.sample
18 |
19 | import dagger.Module
20 | import dagger.Provides
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 | import dev.yankew.sample.domain.repository.FeatureRepository
24 |
25 | @Module
26 | @InstallIn(SingletonComponent::class)
27 | object SampleModule {
28 | @Provides
29 | fun featureRepository(): FeatureRepository = SampleFeatureRepository
30 | }
31 |
--------------------------------------------------------------------------------
/app/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "310996819373",
4 | "project_id": "clean-arch-sample",
5 | "storage_bucket": "clean-arch-sample.appspot.com"
6 | },
7 | "client": [
8 | {
9 | "client_info": {
10 | "mobilesdk_app_id": "1:310996819373:android:9a199b44b233d06e009524",
11 | "android_client_info": {
12 | "package_name": "dev.yankew.cleanarch"
13 | }
14 | },
15 | "oauth_client": [
16 | {
17 | "client_id": "310996819373-7c14frv82atmppvd3ihjchrdiki04lb8.apps.googleusercontent.com",
18 | "client_type": 3
19 | }
20 | ],
21 | "api_key": [
22 | {
23 | "current_key": "AIzaSyBmR_gDj3Cbw49g6-zduGWk2y8JfXZ_Wxw"
24 | }
25 | ],
26 | "services": {
27 | "appinvite_service": {
28 | "other_platform_oauth_client": [
29 | {
30 | "client_id": "310996819373-7c14frv82atmppvd3ihjchrdiki04lb8.apps.googleusercontent.com",
31 | "client_type": 3
32 | }
33 | ]
34 | }
35 | }
36 | }
37 | ],
38 | "configuration_version": "1"
39 | }
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/event/EventDispatcher.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.event
18 |
19 | import javax.inject.Inject
20 | import javax.inject.Provider
21 |
22 | typealias EventHandlers = Set<@JvmSuppressWildcards EventHandler>
23 |
24 | class EventDispatcher @Inject constructor(
25 | private val handlers: Provider>
26 | ) {
27 | suspend fun dispatch(event: T) {
28 | handlers.get().forEach {
29 | it.handle(event)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/lib/compose/src/main/java/dev/yankew/sample/compose/theme/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.compose.theme
18 |
19 | import androidx.compose.ui.graphics.Color
20 |
21 | val Blue200 = Color(0xff90caf9)
22 | val Blue300 = Color(0xff64b5f6)
23 | val Blue700 = Color(0xff1976d2)
24 | val Blue800 = Color(0xff1565c0)
25 | val Blue900 = Color(0xff0d47a1)
26 |
27 | val Amber200 = Color(0xffffe082)
28 | val Amber300 = Color(0xffffd54f)
29 | val Amber700 = Color(0xffffa000)
30 | val Amber800 = Color(0xffff8f00)
31 | val Amber900 = Color(0xffff6f00)
32 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui
18 |
19 | import android.os.Bundle
20 | import androidx.activity.ComponentActivity
21 | import androidx.activity.compose.setContent
22 | import dagger.hilt.android.AndroidEntryPoint
23 |
24 | @AndroidEntryPoint
25 | class MainActivity : ComponentActivity() {
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | setContent {
29 | MainApp()
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/java/dev/yankew/sample/demo/DemoBaseInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.demo
18 |
19 | import dev.yankew.sample.init.AppBaseInitializer
20 | import timber.log.Timber
21 | import javax.inject.Inject
22 |
23 | class DemoBaseInitializer @Inject constructor() : AppBaseInitializer {
24 | override fun init() {
25 | if (BuildConfig.BUILD_TYPE != "release") {
26 | Timber.plant(Timber.DebugTree())
27 | }
28 | Timber.i("Base initialization of demo version for mobile")
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/comp/feature/src/main/java/dev/yankew/sample/feature/FeatureRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.feature
18 |
19 | import dev.yankew.sample.domain.repository.FeatureRepository
20 | import kotlinx.coroutines.flow.Flow
21 | import kotlinx.coroutines.flow.map
22 | import javax.inject.Inject
23 |
24 | class FeatureRepositoryImpl @Inject constructor(
25 | private val elapseDataSource: ElapseDataSource
26 | ) : FeatureRepository {
27 | override fun getElapsedMinutes(): Flow {
28 | return elapseDataSource.getElapsedSeconds().map { it / 60 }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/Check.yaml:
--------------------------------------------------------------------------------
1 | name: Check
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | timeout-minutes: 30
13 |
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 |
18 | - name: Copy CI gradle.properties
19 | run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties
20 |
21 | - name: Set up JDK 11
22 | uses: actions/setup-java@v2
23 | with:
24 | distribution: 'temurin'
25 | java-version: '11'
26 |
27 | - name: Check and build project
28 | run: ./gradlew spotlessCheck lintDebug assembleDebug
29 |
30 | - name: Upload build outputs
31 | uses: actions/upload-artifact@v2
32 | with:
33 | name: build-outputs
34 | path: |
35 | app/build/outputs
36 | apps/demo-mobile/build/outputs
37 |
38 | - name: Upload build reports
39 | if: always()
40 | uses: actions/upload-artifact@v2
41 | with:
42 | name: build-reports
43 | path: |
44 | app/build/reports
45 | apps/demo-mobile/build/reports
46 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteParams.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import dev.yankew.sample.domain.config.NewFeatureRemoteParam
20 | import javax.inject.Inject
21 |
22 | class RemoteParams @Inject constructor(
23 | private val remoteConfig: RemoteConfig
24 | ) {
25 | val newFeatureParam = NewFeatureRemoteParam {
26 | remoteConfig[BooleanRemoteParam(KEY_NEW_FEATURE_ENABLED)]
27 | }
28 |
29 | companion object {
30 | private const val KEY_NEW_FEATURE_ENABLED = "new_feature_enabled"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/config/ConfigModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.config
18 |
19 | import dagger.BindsOptionalOf
20 | import dagger.Module
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 |
24 | @Module
25 | @InstallIn(SingletonComponent::class)
26 | interface ConfigModule {
27 | @BindsOptionalOf
28 | @NewFeatureParam
29 | fun optionalNewFeatureLocalParam(): NewFeatureLocalParam
30 |
31 | @BindsOptionalOf
32 | @NewFeatureParam
33 | fun optionalNewFeatureRemoteParam(): NewFeatureRemoteParam
34 | }
35 |
--------------------------------------------------------------------------------
/comp/feature/src/main/java/dev/yankew/sample/feature/FeatureEventHandlers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.feature
18 |
19 | import dev.yankew.sample.domain.event.AppLaunchedEvent
20 | import dev.yankew.sample.domain.event.EventHandler
21 | import timber.log.Timber
22 | import javax.inject.Inject
23 |
24 | class FeatureEventHandlers @Inject constructor(
25 | elapseDataSource: ElapseDataSource
26 | ) {
27 | val appLaunchedHandler = EventHandler {
28 | Timber.i("AppLaunchedEvent received by FeatureEventHandlers")
29 | elapseDataSource.saveLaunchTime()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/about/AboutViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.about
18 |
19 | import androidx.lifecycle.ViewModel
20 | import dagger.hilt.android.lifecycle.HiltViewModel
21 | import dev.yankew.sample.domain.config.NewFeatureLocalParam
22 | import dev.yankew.sample.domain.config.NewFeatureParam
23 | import java.util.Optional
24 | import javax.inject.Inject
25 |
26 | @HiltViewModel
27 | class AboutViewModel @Inject constructor(
28 | @NewFeatureParam private val localNewFeatureParam: Optional,
29 | ) : ViewModel() {
30 | val isLocalConfigPresent = localNewFeatureParam.isPresent
31 | }
32 |
--------------------------------------------------------------------------------
/comp/analytics-debug/src/main/java/dev/yankew/sample/analytics/debug/DebugAnalyticsModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.analytics.debug
18 |
19 | import dagger.Binds
20 | import dagger.Module
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 | import dagger.multibindings.IntoSet
24 | import dev.yankew.sample.domain.analytics.AnalyticsTracker
25 | import javax.inject.Singleton
26 |
27 | @Module
28 | @InstallIn(SingletonComponent::class)
29 | interface DebugAnalyticsModule {
30 | @Binds
31 | @IntoSet
32 | @Singleton
33 | fun debugAnalyticsTracker(tracker: DebugAnalyticsTracker): AnalyticsTracker
34 | }
35 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/event/EventModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.event
18 |
19 | import dagger.Module
20 | import dagger.hilt.InstallIn
21 | import dagger.hilt.components.SingletonComponent
22 | import dagger.multibindings.Multibinds
23 |
24 | @Module
25 | @InstallIn(SingletonComponent::class)
26 | interface EventModule {
27 | @Multibinds
28 | fun analyticsOptionHandlers(): EventHandlers
29 |
30 | @Multibinds
31 | fun appLaunchedHandlers(): EventHandlers
32 |
33 | @Multibinds
34 | fun crashlyticsOptionHandlers(): EventHandlers
35 | }
36 |
--------------------------------------------------------------------------------
/comp/sample-data/src/main/java/dev/yankew/sample/data/sample/SampleFeatureRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.data.sample
18 |
19 | import dev.yankew.sample.domain.repository.FeatureRepository
20 | import kotlinx.coroutines.delay
21 | import kotlinx.coroutines.flow.Flow
22 | import kotlinx.coroutines.flow.flow
23 | import timber.log.Timber
24 |
25 | object SampleFeatureRepository : FeatureRepository {
26 | override fun getElapsedMinutes(): Flow {
27 | return flow {
28 | (0L..100L).forEach {
29 | emit(it)
30 | Timber.i("$it emitted")
31 | delay(1000)
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/ClickTrackingEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | import javax.inject.Inject
20 |
21 | class ClickTrackingEvent @Inject constructor(
22 | private val analytics: AnalyticsEventDispatcher,
23 | ) {
24 | fun logHelloClicked() = log(VALUE_BUTTON_HELLO)
25 |
26 | private fun log(button: String) =
27 | analytics.logEvent(NAME, PARAM_BUTTON_NAME to button)
28 |
29 | companion object {
30 | private const val NAME = "button_clicked"
31 | private const val PARAM_BUTTON_NAME = "button_name"
32 | private const val VALUE_BUTTON_HELLO = "hello_clicked"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/AnalyticsEventHandlers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | import dev.yankew.sample.domain.event.AppLaunchedEvent
20 | import dev.yankew.sample.domain.event.EventHandler
21 | import dev.yankew.sample.domain.util.Logger
22 | import javax.inject.Inject
23 |
24 | class AnalyticsEventHandlers @Inject constructor(
25 | analyticsUserProperties: AnalyticsUserProperties,
26 | logger: Logger,
27 | ) {
28 | val appLaunchedHandler = EventHandler {
29 | logger.i("AppLaunchedEvent received by AnalyticsEventHandlers")
30 | analyticsUserProperties.setAppVariant(it.variant)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/comp/core/src/main/java/dev/yankew/sample/core/LoggerImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.core
18 |
19 | import dev.yankew.sample.domain.util.Logger
20 | import timber.log.Timber
21 | import javax.inject.Inject
22 |
23 | class LoggerImpl @Inject constructor() : Logger {
24 | override fun e(message: String) {
25 | Timber.e(message)
26 | }
27 |
28 | override fun w(message: String) {
29 | Timber.w(message)
30 | }
31 |
32 | override fun i(message: String) {
33 | Timber.i(message)
34 | }
35 |
36 | override fun d(message: String) {
37 | Timber.d(message)
38 | }
39 |
40 | override fun v(message: String) {
41 | Timber.v(message)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/interactor/FlowUseCase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.interactor
18 |
19 | import dev.yankew.sample.domain.model.DataResult
20 | import kotlinx.coroutines.CoroutineDispatcher
21 | import kotlinx.coroutines.flow.Flow
22 | import kotlinx.coroutines.flow.catch
23 | import kotlinx.coroutines.flow.flowOn
24 |
25 | abstract class FlowUseCase(private val coroutineDispatcher: CoroutineDispatcher) {
26 | operator fun invoke(parameters: P): Flow> = execute(parameters)
27 | .catch { emit(DataResult.Error(it)) }
28 | .flowOn(coroutineDispatcher)
29 |
30 | protected abstract fun execute(parameters: P): Flow>
31 | }
32 |
--------------------------------------------------------------------------------
/comp/config-firebase/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.main-component")
19 | }
20 |
21 | dependencies {
22 | implementation(project(":lib:init"))
23 |
24 | // Startup initialization
25 | implementation(Libs.STARTUP)
26 |
27 | // WorkManager
28 | implementation(Libs.WORK_RUNTIME_KTX)
29 | implementation(Libs.HILT_WORK)
30 | kapt(Libs.HILT_ANDROIDX_COMPILER)
31 |
32 | // Firebase
33 | implementation(platform(Libs.FIREBASE_BOM))
34 | implementation(Libs.FIREBASE_ANALYTICS)
35 | implementation(Libs.FIREBASE_CONFIG)
36 | implementation(Libs.COROUTINES_PLAY_SERVICE)
37 |
38 | // Serialization for parsing JSON values
39 | implementation(Libs.KOTLINX_SERIALIZATION)
40 | }
41 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteParam.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import kotlinx.serialization.KSerializer
20 |
21 | sealed class RemoteParam(open val key: String)
22 |
23 | data class StringRemoteParam(override val key: String) : RemoteParam(key)
24 |
25 | data class BooleanRemoteParam(override val key: String) : RemoteParam(key)
26 |
27 | data class LongRemoteParam(override val key: String) : RemoteParam(key)
28 |
29 | data class DoubleRemoteParam(override val key: String) : RemoteParam(key)
30 |
31 | data class JsonRemoteParam(
32 | override val key: String,
33 | val serializer: KSerializer
34 | ) : RemoteParam(key)
35 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/config/ConfigValue.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.config
18 |
19 | import kotlinx.coroutines.flow.Flow
20 | import kotlinx.coroutines.flow.catch
21 | import kotlinx.coroutines.flow.flatMapLatest
22 | import kotlinx.coroutines.flow.flowOf
23 |
24 | sealed class ConfigValue {
25 | data class ConfiguredValue(val data: T) : ConfigValue()
26 | object Unset : ConfigValue()
27 | }
28 |
29 | fun Flow>.value(default: () -> Flow): Flow =
30 | catch { default() }.flatMapLatest {
31 | when (it) {
32 | is ConfigValue.ConfiguredValue -> flowOf(it.data)
33 | is ConfigValue.Unset -> default()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/src/main/java/dev/yankew/sample/analytics/firebase/AnalyticsOptionHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.analytics.firebase
18 |
19 | import com.google.firebase.analytics.ktx.analytics
20 | import com.google.firebase.ktx.Firebase
21 | import dev.yankew.sample.domain.event.AnalyticsOptionEvent
22 | import dev.yankew.sample.domain.event.EventHandler
23 | import timber.log.Timber
24 | import javax.inject.Inject
25 |
26 | class AnalyticsOptionHandler @Inject constructor() : EventHandler {
27 | override suspend fun handle(event: AnalyticsOptionEvent) {
28 | Timber.i("AnalyticsOptionEvent received by AnalyticsOptionHandler")
29 | Firebase.analytics.setAnalyticsCollectionEnabled(event.enabled)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/doc/build-scripts.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | object "base-android-app" as baseAppPlugin #skyblue
3 | object "base-android-library" as baseLibPlugin #skyblue
4 | object "compose-library" as composeLibPlugin #skyblue
5 | object "hilt-library" as hiltLibPlugin #skyblue
6 | object "main-component" as mainCompPlugin #skyblue
7 | object "ui-component" as uiCompPlugin #skyblue
8 | object app
9 | object "apps/demo-mobile" as mobileDemoApp
10 | object "lib/android" as androidLib
11 | object "lib/compose" as composeLib
12 | object "lib/init" as initLib
13 | object "lib/strings" as stringsLib
14 | object "ui/ui-mobile" as mobileUi
15 | object "ui/ui-widget" as widget
16 | map Components {
17 | 1 => analytics-debug
18 | 2 => analytics-firebase
19 | 3 => config-local
20 | 4 => config-remote
21 | 5 => core
22 | 6 => download
23 | 7 => feature
24 | 8 => notification
25 | 9 => sample-data
26 | 10 => settings
27 | }
28 |
29 | composeLibPlugin --> baseLibPlugin
30 | hiltLibPlugin --> baseLibPlugin
31 | mainCompPlugin --> hiltLibPlugin
32 | uiCompPlugin --> mainCompPlugin
33 | uiCompPlugin --> composeLibPlugin
34 | Components --> mainCompPlugin
35 |
36 | androidLib --> baseLibPlugin
37 | composeLib --> composeLibPlugin
38 | initLib --> hiltLibPlugin
39 | stringsLib --> baseLibPlugin
40 |
41 | mobileUi --> uiCompPlugin
42 | widget --> uiCompPlugin
43 |
44 | app --> baseAppPlugin
45 | mobileDemoApp --> baseAppPlugin
46 | @enduml
47 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/AnalyticsEventDispatcher.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | import dev.yankew.sample.domain.coroutine.ApplicationScope
20 | import kotlinx.coroutines.CoroutineScope
21 | import kotlinx.coroutines.launch
22 | import javax.inject.Inject
23 |
24 | class AnalyticsEventDispatcher @Inject constructor(
25 | private val trackers: AnalyticsTrackers,
26 | @ApplicationScope private val applicationScope: CoroutineScope,
27 | ) {
28 | fun logEvent(name: String, vararg params: Pair) {
29 | applicationScope.launch {
30 | trackers.filter { it.isAnalyticsEnabled() }.forEach {
31 | it.logEvent(name, mapOf(*params))
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 Versions.javaVersion
18 |
19 | plugins {
20 | `java-library`
21 | kotlin("jvm")
22 | kotlin("kapt")
23 | }
24 |
25 | val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks
26 |
27 | compileKotlin.kotlinOptions {
28 | jvmTarget = javaVersion.toString()
29 | allWarningsAsErrors = true
30 | }
31 |
32 | java {
33 | sourceCompatibility = javaVersion
34 | targetCompatibility = javaVersion
35 | }
36 |
37 | dependencies {
38 | api(platform(project(":depconstraints")))
39 | kapt(platform(project(":depconstraints")))
40 | implementation(Libs.KOTLIN_STDLIB)
41 | implementation(Libs.COROUTINES)
42 |
43 | // Dagger Hilt
44 | implementation(Libs.HILT_CORE)
45 | kapt(Libs.HILT_COMPILER)
46 | }
47 |
--------------------------------------------------------------------------------
/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=-Xmx4096m -Dfile.encoding=UTF-8
10 | org.gradle.caching=true
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | org.gradle.parallel=true
15 | # AndroidX package structure to make it clearer which packages are bundled with the
16 | # Android operating system, and which are packaged with your app"s APK
17 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
18 | android.useAndroidX=true
19 | # Kotlin code style for this project: "official" or "obsolete":
20 | kotlin.code.style=official
21 | # Enables namespacing of each library's R class so that its R class includes only the
22 | # resources declared in the library itself and none from the library's dependencies,
23 | # thereby reducing the size of the R class for that library
24 | android.nonTransitiveRClass=true
25 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui
18 |
19 | import androidx.lifecycle.ViewModel
20 | import dagger.hilt.android.lifecycle.HiltViewModel
21 | import dev.yankew.sample.domain.interactor.GetAppVariantUseCase
22 | import dev.yankew.sample.domain.interactor.GetNewFeatureEnabledUseCase
23 | import dev.yankew.sample.domain.model.successOr
24 | import kotlinx.coroutines.flow.map
25 | import javax.inject.Inject
26 |
27 | @HiltViewModel
28 | class MainViewModel @Inject constructor(
29 | getAppVariantUseCase: GetAppVariantUseCase,
30 | getNewFeatureEnabledUseCase: GetNewFeatureEnabledUseCase,
31 | ) : ViewModel() {
32 | val appVariant = getAppVariantUseCase()
33 |
34 | val newFeatureEnabled = getNewFeatureEnabledUseCase(Unit).map { it.successOr(false) }
35 | }
36 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteConfigModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import dagger.Binds
20 | import dagger.Module
21 | import dagger.Provides
22 | import dagger.Reusable
23 | import dagger.hilt.InstallIn
24 | import dagger.hilt.components.SingletonComponent
25 | import dev.yankew.sample.domain.config.NewFeatureParam
26 |
27 | @Module
28 | @InstallIn(SingletonComponent::class)
29 | object RemoteConfigModule {
30 | @Provides
31 | @NewFeatureParam
32 | fun newFeatureParam(remoteParams: RemoteParams) = remoteParams.newFeatureParam
33 | }
34 |
35 | @Module
36 | @InstallIn(SingletonComponent::class)
37 | interface RemoteConfigInterfaceModule {
38 | @Binds
39 | @Reusable
40 | fun remoteConfig(remoteConfigImpl: RemoteConfigImpl): RemoteConfig
41 | }
42 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.home
18 |
19 | import androidx.lifecycle.ViewModel
20 | import dagger.hilt.android.lifecycle.HiltViewModel
21 | import dev.yankew.sample.domain.analytics.ClickTrackingEvent
22 | import dev.yankew.sample.domain.interactor.GetElapsedMinutesUseCase
23 | import dev.yankew.sample.domain.model.data
24 | import kotlinx.coroutines.flow.mapNotNull
25 | import javax.inject.Inject
26 |
27 | @HiltViewModel
28 | class HomeViewModel @Inject constructor(
29 | getElapsedMinutesUseCase: GetElapsedMinutesUseCase,
30 | private val clickTrackingEvent: ClickTrackingEvent
31 | ) : ViewModel() {
32 | val elapsedMinutes = getElapsedMinutesUseCase(Unit).mapNotNull { it.data }
33 |
34 | fun logHello() {
35 | clickTrackingEvent.logHelloClicked()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/src/main/java/dev/yankew/sample/analytics/firebase/FirebaseAnalyticsModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.analytics.firebase
18 |
19 | import dagger.Binds
20 | import dagger.Module
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 | import dagger.multibindings.IntoSet
24 | import dev.yankew.sample.domain.analytics.AnalyticsTracker
25 | import dev.yankew.sample.domain.event.AnalyticsOptionEvent
26 | import dev.yankew.sample.domain.event.EventHandler
27 |
28 | @Module
29 | @InstallIn(SingletonComponent::class)
30 | interface FirebaseAnalyticsModule {
31 | @Binds
32 | @IntoSet
33 | fun analyticsOptionHandler(handler: AnalyticsOptionHandler): EventHandler
34 |
35 | @Binds
36 | @IntoSet
37 | fun firebaseAnalyticsTracker(tracker: FirebaseAnalyticsTracker): AnalyticsTracker
38 | }
39 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/AnalyticsUserProperties.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | import dev.yankew.sample.domain.model.AppVariant
20 | import javax.inject.Inject
21 |
22 | class AnalyticsUserProperties @Inject constructor(
23 | private val trackers: AnalyticsTrackers,
24 | ) {
25 | suspend fun setAppVariant(variant: AppVariant) {
26 | setProperties(PARAM_APP_VARIANT to variant.toString())
27 | }
28 |
29 | private suspend fun setProperties(vararg properties: Pair) {
30 | trackers.filter { it.isAnalyticsEnabled() }.forEach {
31 | properties.forEach { (key, value) ->
32 | it.setUserProperty(key, value)
33 | }
34 | }
35 | }
36 |
37 | companion object {
38 | private const val PARAM_APP_VARIANT = "app_variant"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/strings/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | CleanArch
4 |
5 |
6 | Open menu
7 | Go back
8 |
9 |
10 | Home
11 | Hello
12 | Opened %1$d minutes ago
13 |
14 |
15 | New feature
16 | This is an experimental new feature.
17 |
18 |
19 | Settings
20 | Allow Clean Arch Sample to notify you updates.
21 | Allow Clean Arch Sample to collect anonymous usage information.
22 | Allow Clean Arch Sample to collect the crash reports to improve the app.
23 |
24 |
25 | About
26 | This is a Clean Architecture sample of Android application.
27 | Developer options
28 |
29 |
30 | Developer options
31 | Enable the new feature.
32 |
33 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/interactor/GetElapsedMinutesUseCase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.interactor
18 |
19 | import dev.yankew.sample.domain.coroutine.IoDispatcher
20 | import dev.yankew.sample.domain.model.DataResult
21 | import dev.yankew.sample.domain.repository.FeatureRepository
22 | import kotlinx.coroutines.CoroutineDispatcher
23 | import kotlinx.coroutines.flow.Flow
24 | import kotlinx.coroutines.flow.map
25 | import javax.inject.Inject
26 |
27 | class GetElapsedMinutesUseCase @Inject constructor(
28 | private val featureRepository: FeatureRepository,
29 | @IoDispatcher ioDispatcher: CoroutineDispatcher
30 | ) : FlowUseCase(ioDispatcher) {
31 | override fun execute(parameters: Unit): Flow> {
32 | return featureRepository.getElapsedMinutes().map {
33 | DataResult.Success(it)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | include(":app")
18 | include(":apps:demo-mobile")
19 | include(":comp:analytics-debug")
20 | include(":comp:analytics-firebase")
21 | include(":comp:config-firebase")
22 | include(":comp:config-local")
23 | include(":comp:core")
24 | include(":comp:download")
25 | include(":comp:feature")
26 | include(":comp:notification")
27 | include(":comp:sample-data")
28 | include(":comp:settings")
29 | include(":depconstraints")
30 | include(":domain")
31 | include(":lib:android")
32 | include(":lib:compose")
33 | include(":lib:init")
34 | include(":lib:strings")
35 | include(":ui:ui-mobile")
36 | include(":ui:ui-widget")
37 |
38 | @Suppress("UnstableApiUsage")
39 | dependencyResolutionManagement {
40 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
41 | repositories {
42 | google()
43 | mavenCentral()
44 | }
45 | }
46 |
47 | rootProject.name = "android-clean-arch"
48 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/AnalyticsModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | import dagger.Module
20 | import dagger.Provides
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 | import dagger.multibindings.IntoSet
24 | import dagger.multibindings.Multibinds
25 | import dev.yankew.sample.domain.event.AppLaunchedEvent
26 | import dev.yankew.sample.domain.event.EventHandler
27 |
28 | @Module
29 | @InstallIn(SingletonComponent::class)
30 | object AnalyticsModule {
31 | @Provides
32 | @IntoSet
33 | fun appLaunchedHandler(
34 | handlers: AnalyticsEventHandlers
35 | ): EventHandler = handlers.appLaunchedHandler
36 | }
37 |
38 | @Module
39 | @InstallIn(SingletonComponent::class)
40 | interface AnalyticsInterfaceModule {
41 | @Multibinds
42 | fun analyticsTrackers(): AnalyticsTrackers
43 | }
44 |
--------------------------------------------------------------------------------
/apps/demo-mobile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.base-android-app")
19 | }
20 |
21 | android {
22 | defaultConfig {
23 | applicationId = "dev.yankew.cleanarch.demo"
24 | versionCode = Versions.versionCodeMobile
25 | versionName = Versions.versionName
26 | }
27 | }
28 |
29 | dependencies {
30 | implementation(project(":domain"))
31 | implementation(project(":lib:init"))
32 | implementation(project(":lib:strings"))
33 |
34 | // Dependencies for injection
35 | implementation(project(":comp:analytics-debug"))
36 | implementation(project(":comp:config-local"))
37 | implementation(project(":comp:core"))
38 | implementation(project(":comp:notification"))
39 | implementation(project(":comp:sample-data"))
40 | implementation(project(":comp:settings"))
41 | implementation(project(":ui:ui-mobile"))
42 | implementation(project(":ui:ui-widget"))
43 | }
44 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/model/DataResult.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.model
18 |
19 | sealed class DataResult {
20 | data class Success(val data: T) : DataResult()
21 | data class Updating(val data: T) : DataResult()
22 | data class Error(val exception: Throwable) : DataResult()
23 | object Loading : DataResult()
24 |
25 | override fun toString(): String {
26 | return when (this) {
27 | is Success -> "Success[data=$data]"
28 | is Updating -> "Updating[data=$data]"
29 | is Error -> "Error[exception=$exception]"
30 | Loading -> "Loading"
31 | }
32 | }
33 | }
34 |
35 | val DataResult.data: T?
36 | get() = (this as? DataResult.Success)?.data ?: (this as? DataResult.Updating)?.data
37 |
38 | fun DataResult.successOr(fallback: T): T {
39 | return data ?: fallback
40 | }
41 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/java/dev/yankew/sample/demo/DemoModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.demo
18 |
19 | import dagger.Binds
20 | import dagger.Module
21 | import dagger.Provides
22 | import dagger.hilt.InstallIn
23 | import dagger.hilt.components.SingletonComponent
24 | import dev.yankew.sample.domain.model.AppVariant
25 | import dev.yankew.sample.domain.repository.AppRepository
26 | import dev.yankew.sample.init.AppBaseInitializer
27 | import javax.inject.Singleton
28 |
29 | @Module
30 | @InstallIn(SingletonComponent::class)
31 | object DemoModule {
32 | @Provides
33 | fun appRepository(): AppRepository = object : AppRepository {
34 | override fun getAppVariant(): AppVariant = AppVariant.MOBILE_DEMO
35 | }
36 | }
37 |
38 | @Module
39 | @InstallIn(SingletonComponent::class)
40 | interface DemoInterfaceModule {
41 | @Singleton
42 | @Binds
43 | fun appBaseInitializer(initializer: DemoBaseInitializer): AppBaseInitializer
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/yankew/sample/cleanarch/MainApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.cleanarch
18 |
19 | import android.app.Application
20 | import dagger.hilt.android.HiltAndroidApp
21 | import dev.yankew.sample.domain.coroutine.ApplicationScope
22 | import dev.yankew.sample.domain.event.AppLaunchedEvent
23 | import dev.yankew.sample.domain.event.EventDispatcher
24 | import dev.yankew.sample.domain.model.AppVariant
25 | import kotlinx.coroutines.CoroutineScope
26 | import kotlinx.coroutines.launch
27 | import javax.inject.Inject
28 |
29 | @HiltAndroidApp
30 | class MainApplication : Application() {
31 | @Inject
32 | @ApplicationScope
33 | lateinit var applicationScope: CoroutineScope
34 |
35 | @Inject
36 | lateinit var eventDispatcher: EventDispatcher
37 |
38 | override fun onCreate() {
39 | super.onCreate()
40 | applicationScope.launch {
41 | eventDispatcher.dispatch(AppLaunchedEvent(AppVariant.MOBILE))
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/java/dev/yankew/sample/demo/DemoApplication.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.demo
18 |
19 | import android.app.Application
20 | import dagger.hilt.android.HiltAndroidApp
21 | import dev.yankew.sample.domain.coroutine.ApplicationScope
22 | import dev.yankew.sample.domain.event.AppLaunchedEvent
23 | import dev.yankew.sample.domain.event.EventDispatcher
24 | import dev.yankew.sample.domain.model.AppVariant
25 | import kotlinx.coroutines.CoroutineScope
26 | import kotlinx.coroutines.launch
27 | import javax.inject.Inject
28 |
29 | @HiltAndroidApp
30 | class DemoApplication : Application() {
31 | @Inject
32 | @ApplicationScope
33 | lateinit var applicationScope: CoroutineScope
34 |
35 | @Inject
36 | lateinit var eventDispatcher: EventDispatcher
37 |
38 | override fun onCreate() {
39 | super.onCreate()
40 | applicationScope.launch {
41 | eventDispatcher.dispatch(AppLaunchedEvent(AppVariant.MOBILE_DEMO))
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/coroutine/CoroutineModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.coroutine
18 |
19 | import dagger.Module
20 | import dagger.Provides
21 | import dagger.hilt.InstallIn
22 | import dagger.hilt.components.SingletonComponent
23 | import kotlinx.coroutines.CoroutineDispatcher
24 | import kotlinx.coroutines.CoroutineScope
25 | import kotlinx.coroutines.Dispatchers
26 | import kotlinx.coroutines.SupervisorJob
27 | import javax.inject.Singleton
28 |
29 | @Module
30 | @InstallIn(SingletonComponent::class)
31 | object CoroutineModule {
32 | @ApplicationScope
33 | @Singleton
34 | @Provides
35 | fun provideApplicationScope(
36 | @DefaultDispatcher defaultDispatcher: CoroutineDispatcher
37 | ): CoroutineScope = CoroutineScope(SupervisorJob() + defaultDispatcher)
38 |
39 | @DefaultDispatcher
40 | @Provides
41 | fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
42 |
43 | @IoDispatcher
44 | @Provides
45 | fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO
46 | }
47 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/about/DeveloperViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.about
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.viewModelScope
21 | import dagger.hilt.android.lifecycle.HiltViewModel
22 | import dev.yankew.sample.domain.config.NewFeatureLocalParam
23 | import dev.yankew.sample.domain.config.NewFeatureParam
24 | import dev.yankew.sample.domain.config.getConfiguredValue
25 | import dev.yankew.sample.domain.util.orNull
26 | import kotlinx.coroutines.flow.flowOf
27 | import kotlinx.coroutines.launch
28 | import java.util.Optional
29 | import javax.inject.Inject
30 |
31 | @HiltViewModel
32 | class DeveloperViewModel @Inject constructor(
33 | @NewFeatureParam private val localNewFeatureParam: Optional,
34 | ) : ViewModel() {
35 | val newFeatureLocalEnabled = localNewFeatureParam.getConfiguredValue { flowOf(false) }
36 |
37 | fun setNewFeatureLocalEnabled(enabled: Boolean) {
38 | viewModelScope.launch {
39 | localNewFeatureParam.orNull()?.setValue(enabled)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/config/ConfigParam.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.config
18 |
19 | import dev.yankew.sample.domain.util.orNull
20 | import kotlinx.coroutines.flow.Flow
21 | import kotlinx.coroutines.flow.flowOf
22 | import java.util.Optional
23 |
24 | fun interface ConfigParam {
25 | fun getValue(): Flow>
26 | }
27 |
28 | interface LocalConfigParam : ConfigParam {
29 | suspend fun setValue(value: T)
30 | }
31 |
32 | fun interface RemoteConfigParam : ConfigParam
33 |
34 | fun Optional>.getConfiguredValue(
35 | default: () -> Flow
36 | ): Flow {
37 | return orNull()?.getValue()?.value { default() } ?: default()
38 | }
39 |
40 | fun Optional>.getConfiguredValue(): Flow> {
41 | return orNull()?.getValue() ?: flowOf(ConfigValue.Unset)
42 | }
43 |
44 | fun Optional>.getConfiguredValue(
45 | remote: Optional>,
46 | default: () -> Flow
47 | ): Flow {
48 | return getConfiguredValue { remote.getConfiguredValue(default) }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/apps/demo-mobile/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/yankew/sample/cleanarch/crashlytics/CrashlyticsHelper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.cleanarch.crashlytics
18 |
19 | import com.google.firebase.crashlytics.FirebaseCrashlytics
20 | import dev.yankew.sample.cleanarch.BuildConfig
21 | import dev.yankew.sample.domain.event.CrashlyticsOptionEvent
22 | import dev.yankew.sample.domain.event.EventHandler
23 | import dev.yankew.sample.domain.settings.CrashlyticsOption
24 | import dev.yankew.sample.domain.settings.SettingOption
25 | import kotlinx.coroutines.flow.first
26 | import timber.log.Timber
27 | import javax.inject.Inject
28 |
29 | class CrashlyticsHelper @Inject constructor(
30 | @CrashlyticsOption private val crashlyticsOption: SettingOption,
31 | ) {
32 | val crashlyticsOptionHandler = EventHandler {
33 | Timber.i("CrashlyticsOptionEvent received by CrashlyticsHelper")
34 | setUpCrashlytics()
35 | }
36 |
37 | suspend fun setUpCrashlytics() {
38 | if (BuildConfig.BUILD_TYPE == "release") {
39 | val enabled = crashlyticsOption.getValue().first()
40 | FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enabled)
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteConfigHelper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import com.google.firebase.ktx.Firebase
20 | import com.google.firebase.remoteconfig.ktx.remoteConfig
21 | import com.google.firebase.remoteconfig.ktx.remoteConfigSettings
22 | import kotlinx.coroutines.channels.BufferOverflow
23 | import kotlinx.coroutines.flow.MutableSharedFlow
24 | import kotlinx.coroutines.flow.asSharedFlow
25 | import javax.inject.Inject
26 | import javax.inject.Singleton
27 |
28 | @Singleton
29 | class RemoteConfigHelper @Inject constructor() {
30 | val remoteConfig = Firebase.remoteConfig.apply {
31 | setConfigSettingsAsync(
32 | remoteConfigSettings {
33 | minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) 60 else 3600
34 | }
35 | )
36 | setDefaultsAsync(R.xml.remote_config_defaults)
37 | }
38 |
39 | private val _updateEvent = MutableSharedFlow(
40 | extraBufferCapacity = 1,
41 | onBufferOverflow = BufferOverflow.DROP_OLDEST
42 | )
43 | val updateEvent = _updateEvent.asSharedFlow()
44 |
45 | suspend fun notifyConfigUpdated() {
46 | _updateEvent.emit(Unit)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/new/NewFeatureScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.new
18 |
19 | import androidx.compose.foundation.layout.Column
20 | import androidx.compose.foundation.layout.fillMaxSize
21 | import androidx.compose.foundation.layout.padding
22 | import androidx.compose.material.Text
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.res.stringResource
27 | import androidx.compose.ui.unit.dp
28 | import dev.yankew.sample.strings.R
29 | import dev.yankew.sample.ui.MainScreenScaffold
30 |
31 | /**
32 | * Stateless composable of New Feature Screen
33 | */
34 | @Composable
35 | fun NewFeatureScreen(
36 | openDrawer: () -> Unit,
37 | ) {
38 | MainScreenScaffold(
39 | titleResId = R.string.new_feature_title,
40 | openDrawer = openDrawer,
41 | ) { innerPaddings ->
42 | Column(
43 | modifier = Modifier
44 | .fillMaxSize()
45 | .padding(innerPaddings)
46 | .padding(16.dp),
47 | horizontalAlignment = Alignment.CenterHorizontally
48 | ) {
49 | Text(stringResource(R.string.new_feature_text))
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/yankew/sample/cleanarch/crashlytics/CrashlyticsTree.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.cleanarch.crashlytics
18 |
19 | import android.util.Log
20 | import com.google.firebase.crashlytics.FirebaseCrashlytics
21 | import com.google.firebase.crashlytics.ktx.setCustomKeys
22 | import timber.log.Timber
23 |
24 | class CrashlyticsTree : Timber.DebugTree() {
25 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
26 | if (priority == Log.VERBOSE || priority == Log.DEBUG || priority == Log.INFO) {
27 | return
28 | }
29 | FirebaseCrashlytics.getInstance().run {
30 | setCustomKeys {
31 | key(KEY_PRIORITY, getPriorityString(priority))
32 | key(KEY_TAG, tag ?: "unknown")
33 | key(KEY_MESSAGE, message)
34 | }
35 | log(t?.message ?: message)
36 | }
37 | }
38 |
39 | private fun getPriorityString(priority: Int) = when (priority) {
40 | Log.WARN -> "Warn($priority)"
41 | else -> "Error($priority)"
42 | }
43 |
44 | companion object {
45 | private const val KEY_PRIORITY = "priority"
46 | private const val KEY_TAG = "tag"
47 | private const val KEY_MESSAGE = "message"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/comp/config-local/src/main/java/dev/yankew/sample/config/local/LocalParams.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.local
18 |
19 | import androidx.datastore.core.DataStore
20 | import androidx.datastore.preferences.core.Preferences
21 | import androidx.datastore.preferences.core.booleanPreferencesKey
22 | import androidx.datastore.preferences.core.edit
23 | import dev.yankew.sample.domain.config.ConfigValue
24 | import dev.yankew.sample.domain.config.NewFeatureLocalParam
25 | import kotlinx.coroutines.flow.Flow
26 | import kotlinx.coroutines.flow.map
27 | import javax.inject.Inject
28 |
29 | class LocalParams @Inject constructor(
30 | @LocalConfigDataStore private val dataStore: DataStore,
31 | ) {
32 | val newFeatureParam = object : NewFeatureLocalParam {
33 | override fun getValue(): Flow> = dataStore.data.map { preferences ->
34 | preferences[KEY_NEW_FEATURE_ENABLED]?.let { enabled ->
35 | ConfigValue.ConfiguredValue(enabled)
36 | } ?: ConfigValue.Unset
37 | }
38 |
39 | override suspend fun setValue(value: Boolean) {
40 | dataStore.edit { it[KEY_NEW_FEATURE_ENABLED] = value }
41 | }
42 | }
43 |
44 | companion object {
45 | private val KEY_NEW_FEATURE_ENABLED = booleanPreferencesKey("new_feature_enabled")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/yankew/sample/cleanarch/AppModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.cleanarch
18 |
19 | import dagger.Binds
20 | import dagger.Module
21 | import dagger.Provides
22 | import dagger.hilt.InstallIn
23 | import dagger.hilt.components.SingletonComponent
24 | import dagger.multibindings.IntoSet
25 | import dev.yankew.sample.cleanarch.crashlytics.CrashlyticsHelper
26 | import dev.yankew.sample.domain.event.CrashlyticsOptionEvent
27 | import dev.yankew.sample.domain.event.EventHandler
28 | import dev.yankew.sample.domain.model.AppVariant
29 | import dev.yankew.sample.domain.repository.AppRepository
30 | import dev.yankew.sample.init.AppBaseInitializer
31 | import javax.inject.Singleton
32 |
33 | @Module
34 | @InstallIn(SingletonComponent::class)
35 | object AppModule {
36 | @Provides
37 | fun appRepository(): AppRepository = object : AppRepository {
38 | override fun getAppVariant(): AppVariant = AppVariant.MOBILE
39 | }
40 |
41 | @Provides
42 | @IntoSet
43 | fun crashlyticsOptionHandler(
44 | helper: CrashlyticsHelper
45 | ): EventHandler = helper.crashlyticsOptionHandler
46 | }
47 |
48 | @Module
49 | @InstallIn(SingletonComponent::class)
50 | interface AppInterfaceModule {
51 | @Singleton
52 | @Binds
53 | fun appBaseInitializer(initializer: AppBaseInitializerImpl): AppBaseInitializer
54 | }
55 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/interactor/GetNewFeatureEnabledUseCase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.interactor
18 |
19 | import dev.yankew.sample.domain.config.NewFeatureLocalParam
20 | import dev.yankew.sample.domain.config.NewFeatureParam
21 | import dev.yankew.sample.domain.config.NewFeatureRemoteParam
22 | import dev.yankew.sample.domain.config.getConfiguredValue
23 | import dev.yankew.sample.domain.coroutine.IoDispatcher
24 | import dev.yankew.sample.domain.model.DataResult
25 | import dev.yankew.sample.domain.util.Logger
26 | import kotlinx.coroutines.CoroutineDispatcher
27 | import kotlinx.coroutines.flow.Flow
28 | import kotlinx.coroutines.flow.flowOf
29 | import kotlinx.coroutines.flow.map
30 | import java.util.Optional
31 | import javax.inject.Inject
32 |
33 | class GetNewFeatureEnabledUseCase @Inject constructor(
34 | @NewFeatureParam private val localParam: Optional,
35 | @NewFeatureParam private val remoteParam: Optional,
36 | private val logger: Logger,
37 | @IoDispatcher ioDispatcher: CoroutineDispatcher
38 | ) : FlowUseCase(ioDispatcher) {
39 | override fun execute(parameters: Unit): Flow> {
40 | return localParam.getConfiguredValue(remoteParam) {
41 | logger.i("Use local default value for new feature enabled")
42 | flowOf(false)
43 | }.map { DataResult.Success(it) }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/domain/src/main/java/dev/yankew/sample/domain/analytics/SettingsChangedTrackingEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.domain.analytics
18 |
19 | import javax.inject.Inject
20 |
21 | class SettingsChangedTrackingEvent @Inject constructor(
22 | private val analytics: AnalyticsEventDispatcher,
23 | ) {
24 | fun logAnalyticsChanged(enabled: Boolean) =
25 | log(if (enabled) VALUE_ANALYTICS_ENABLED else VALUE_ANALYTICS_DISABLED)
26 |
27 | fun logCrashlyticsChanged(enabled: Boolean) =
28 | log(if (enabled) VALUE_CRASHLYTICS_ENABLED else VALUE_CRASHLYTICS_DISABLED)
29 |
30 | fun logNotificationChanged(enabled: Boolean) =
31 | log(if (enabled) VALUE_NOTIFICATION_ENABLED else VALUE_NOTIFICATION_DISABLED)
32 |
33 | private fun log(value: String) = analytics.logEvent(EVENT_NAME, PARAM_SETTING_NAME to value)
34 |
35 | companion object {
36 | private const val EVENT_NAME = "settings_changed"
37 | private const val PARAM_SETTING_NAME = "setting_name"
38 | private const val VALUE_ANALYTICS_ENABLED = "analytics_enabled"
39 | private const val VALUE_ANALYTICS_DISABLED = "analytics_disabled"
40 | private const val VALUE_CRASHLYTICS_ENABLED = "crashlytics_enabled"
41 | private const val VALUE_CRASHLYTICS_DISABLED = "crashlytics_disabled"
42 | private const val VALUE_NOTIFICATION_ENABLED = "notification_enabled"
43 | private const val VALUE_NOTIFICATION_DISABLED = "notification_disabled"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/settings/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.settings
18 |
19 | import androidx.lifecycle.ViewModel
20 | import androidx.lifecycle.viewModelScope
21 | import dagger.hilt.android.lifecycle.HiltViewModel
22 | import dev.yankew.sample.domain.settings.AnalyticsOption
23 | import dev.yankew.sample.domain.settings.CrashlyticsOption
24 | import dev.yankew.sample.domain.settings.NotificationOption
25 | import dev.yankew.sample.domain.settings.SettingOption
26 | import kotlinx.coroutines.launch
27 | import javax.inject.Inject
28 |
29 | @HiltViewModel
30 | class SettingsViewModel @Inject constructor(
31 | @AnalyticsOption private val analyticsOption: SettingOption,
32 | @CrashlyticsOption private val crashlyticsOption: SettingOption,
33 | @NotificationOption private val notificationOption: SettingOption,
34 | ) : ViewModel() {
35 | val analytics = analyticsOption.getValue()
36 | val crashlytics = crashlyticsOption.getValue()
37 | val notification = notificationOption.getValue()
38 |
39 | fun setAnalytics(enabled: Boolean) {
40 | viewModelScope.launch { analyticsOption.setValue(enabled) }
41 | }
42 |
43 | fun setCrashlytics(enabled: Boolean) {
44 | viewModelScope.launch { crashlyticsOption.setValue(enabled) }
45 | }
46 |
47 | fun setNotification(enabled: Boolean) {
48 | viewModelScope.launch { notificationOption.setValue(enabled) }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("common.base-android-app")
19 | id("com.google.gms.google-services")
20 | id("com.google.firebase.crashlytics")
21 | }
22 |
23 | android {
24 | defaultConfig {
25 | applicationId = "dev.yankew.cleanarch"
26 | versionCode = Versions.versionCodeMobile
27 | versionName = Versions.versionName
28 | }
29 | }
30 |
31 | dependencies {
32 | implementation(project(":domain"))
33 | implementation(project(":lib:init"))
34 | implementation(project(":lib:strings"))
35 |
36 | // Dependencies for injection
37 | debugImplementation(project(":comp:analytics-debug"))
38 | implementation(project(":comp:analytics-firebase"))
39 | implementation(project(":comp:config-firebase"))
40 | implementation(project(":comp:config-local"))
41 | implementation(project(":comp:core"))
42 | implementation(project(":comp:download"))
43 | implementation(project(":comp:feature"))
44 | implementation(project(":comp:notification"))
45 | implementation(project(":comp:settings"))
46 | implementation(project(":ui:ui-mobile"))
47 | implementation(project(":ui:ui-widget"))
48 |
49 | // Initialization
50 | // App Startup
51 | implementation(Libs.STARTUP)
52 | // Init WorkManager
53 | implementation(Libs.HILT_WORK)
54 | implementation(Libs.WORK_RUNTIME_KTX)
55 |
56 | // Firebase crashlytics
57 | implementation(platform(Libs.FIREBASE_BOM))
58 | implementation(Libs.FIREBASE_CRASHLYTICS)
59 | }
60 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/src/main/java/dev/yankew/sample/analytics/firebase/FirebaseAnalyticsTracker.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.analytics.firebase
18 |
19 | import com.google.firebase.analytics.ktx.analytics
20 | import com.google.firebase.analytics.ktx.logEvent
21 | import com.google.firebase.ktx.Firebase
22 | import dev.yankew.sample.domain.analytics.AnalyticsTracker
23 | import dev.yankew.sample.domain.settings.AnalyticsOption
24 | import dev.yankew.sample.domain.settings.SettingOption
25 | import kotlinx.coroutines.flow.first
26 | import javax.inject.Inject
27 | import javax.inject.Provider
28 |
29 | class FirebaseAnalyticsTracker @Inject constructor(
30 | @AnalyticsOption private val analyticsOption: Provider>,
31 | ) : AnalyticsTracker {
32 | override suspend fun isAnalyticsEnabled(): Boolean {
33 | return analyticsOption.get().getValue().first()
34 | }
35 |
36 | override fun logEvent(name: String, params: Map) {
37 | Firebase.analytics.logEvent(name) {
38 | params.forEach { (key, value) ->
39 | when (value) {
40 | is String -> param(key, value)
41 | is Int -> param(key, value.toLong())
42 | is Long -> param(key, value)
43 | else -> param(key, value.toString())
44 | }
45 | }
46 | }
47 | }
48 |
49 | override fun setUserProperty(key: String, value: String) {
50 | Firebase.analytics.setUserProperty(key, value)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/comp/config-local/src/main/java/dev/yankew/sample/config/local/LocalConfigModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.local
18 |
19 | import android.content.Context
20 | import androidx.datastore.core.DataStore
21 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
22 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory
23 | import androidx.datastore.preferences.core.Preferences
24 | import androidx.datastore.preferences.core.emptyPreferences
25 | import androidx.datastore.preferences.preferencesDataStoreFile
26 | import dagger.Module
27 | import dagger.Provides
28 | import dagger.hilt.InstallIn
29 | import dagger.hilt.android.qualifiers.ApplicationContext
30 | import dagger.hilt.components.SingletonComponent
31 | import dev.yankew.sample.domain.config.NewFeatureParam
32 | import javax.inject.Qualifier
33 | import javax.inject.Singleton
34 |
35 | @Retention(AnnotationRetention.BINARY)
36 | @Qualifier
37 | annotation class LocalConfigDataStore
38 |
39 | @Module
40 | @InstallIn(SingletonComponent::class)
41 | object LocalConfigModule {
42 | @Provides
43 | @Singleton
44 | @LocalConfigDataStore
45 | fun preferenceDataStore(@ApplicationContext context: Context): DataStore =
46 | PreferenceDataStoreFactory.create(
47 | corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }
48 | ) { context.preferencesDataStoreFile("local-config") }
49 |
50 | @Provides
51 | @NewFeatureParam
52 | fun newFeatureParam(localParams: LocalParams) = localParams.newFeatureParam
53 | }
54 |
--------------------------------------------------------------------------------
/lib/compose/src/main/java/dev/yankew/sample/compose/ActivityViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.compose
18 |
19 | import android.app.Activity
20 | import android.content.ContextWrapper
21 | import androidx.activity.ComponentActivity
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.ui.platform.LocalContext
24 | import androidx.lifecycle.ViewModel
25 | import androidx.lifecycle.ViewModelStoreOwner
26 | import androidx.lifecycle.viewmodel.compose.viewModel
27 |
28 | /**
29 | * Get or create a Activity scope ViewModel instance.
30 | *
31 | * This function is usually used with Hilt because the Activity generated by Hilt implements
32 | * HasDefaultViewModelProviderFactory and the viewModel method can use that factory to create
33 | * the ViewModel.
34 | */
35 | @Composable
36 | inline fun activityViewModel(): VM = viewModel(
37 | getActivityViewModelStoreOwner()
38 | )
39 |
40 | @Composable
41 | fun getActivityViewModelStoreOwner(): ViewModelStoreOwner {
42 | return LocalContext.current.let {
43 | var ctx = it
44 | while (ctx is ContextWrapper) {
45 | if (ctx is Activity) {
46 | if (ctx is ComponentActivity) {
47 | return@let ctx
48 | } else {
49 | break
50 | }
51 | }
52 | ctx = ctx.baseContext
53 | }
54 | throw IllegalStateException(
55 | "Expected a ComponentActivity context as a ViewModelStoreOwner but instead found: $ctx"
56 | )
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/yankew/sample/cleanarch/AppBaseInitializerImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.cleanarch
18 |
19 | import android.content.Context
20 | import androidx.hilt.work.HiltWorkerFactory
21 | import androidx.work.Configuration
22 | import androidx.work.WorkManager
23 | import dagger.hilt.android.qualifiers.ApplicationContext
24 | import dev.yankew.sample.cleanarch.crashlytics.CrashlyticsHelper
25 | import dev.yankew.sample.cleanarch.crashlytics.CrashlyticsTree
26 | import dev.yankew.sample.domain.coroutine.ApplicationScope
27 | import dev.yankew.sample.init.AppBaseInitializer
28 | import kotlinx.coroutines.CoroutineScope
29 | import kotlinx.coroutines.launch
30 | import timber.log.Timber
31 | import javax.inject.Inject
32 |
33 | class AppBaseInitializerImpl @Inject constructor(
34 | @ApplicationContext private val context: Context,
35 | @ApplicationScope private val applicationScope: CoroutineScope,
36 | private val workerFactory: HiltWorkerFactory,
37 | private val crashlyticsHelper: CrashlyticsHelper,
38 | ) : AppBaseInitializer {
39 | override fun init() {
40 | if (BuildConfig.BUILD_TYPE == "release") {
41 | Timber.plant(CrashlyticsTree())
42 | } else {
43 | Timber.plant(CrashlyticsTree(), Timber.DebugTree())
44 | }
45 | Timber.i("App base initialization")
46 |
47 | applicationScope.launch {
48 | crashlyticsHelper.setUpCrashlytics()
49 | }
50 |
51 | WorkManager.initialize(
52 | context,
53 | Configuration.Builder().setWorkerFactory(workerFactory).build()
54 | )
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/compose/src/main/java/dev/yankew/sample/compose/FlowWithLifecycle.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.compose
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.State
22 | import androidx.compose.runtime.mutableStateOf
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.ui.platform.LocalLifecycleOwner
25 | import androidx.lifecycle.Lifecycle
26 | import androidx.lifecycle.repeatOnLifecycle
27 | import kotlinx.coroutines.flow.Flow
28 | import kotlinx.coroutines.flow.StateFlow
29 |
30 | /**
31 | * Collect the flow as Compose State when the lifecycle is at least at STARTED state.
32 | *
33 | * @param initial The initial value of the returned Compose State
34 | */
35 | @Composable
36 | fun Flow.collectAsStateWhenStarted(
37 | initial: R
38 | ): State = collectAsStateWhenStarted { initial }
39 |
40 | /**
41 | * Collect the flow as Compose State when the lifecycle is at least at STARTED state.
42 | *
43 | * No initial parameter is required here because StateFlow has.
44 | */
45 | @Composable
46 | fun StateFlow.collectAsStateWhenStarted(): State = collectAsStateWhenStarted { value }
47 |
48 | @Composable
49 | private fun Flow.collectAsStateWhenStarted(initial: () -> R): State {
50 | val lifecycle: Lifecycle = LocalLifecycleOwner.current.lifecycle
51 | val state = remember(this) { mutableStateOf(initial()) }
52 | LaunchedEffect(this) {
53 | lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
54 | collect { state.value = it }
55 | }
56 | }
57 | return state
58 | }
59 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/navigation/MainNavGraph.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.navigation.NavHostController
21 | import androidx.navigation.compose.NavHost
22 | import androidx.navigation.compose.composable
23 | import androidx.navigation.compose.rememberNavController
24 | import dev.yankew.sample.ui.about.AboutScreen
25 | import dev.yankew.sample.ui.about.DeveloperScreen
26 | import dev.yankew.sample.ui.home.HomeScreen
27 | import dev.yankew.sample.ui.navigation.NavDestinations
28 | import dev.yankew.sample.ui.new.NewFeatureScreen
29 | import dev.yankew.sample.ui.settings.SettingsScreen
30 |
31 | @Composable
32 | fun MainNavGraph(
33 | navController: NavHostController = rememberNavController(),
34 | openDrawer: () -> Unit,
35 | ) {
36 | NavHost(
37 | navController = navController,
38 | startDestination = NavDestinations.ROUTE_HOME,
39 | ) {
40 | composable(route = NavDestinations.ROUTE_HOME) {
41 | HomeScreen(openDrawer)
42 | }
43 | composable(route = NavDestinations.ROUTE_NEW_FEATURE) {
44 | NewFeatureScreen(openDrawer)
45 | }
46 | composable(route = NavDestinations.ROUTE_SETTINGS) {
47 | SettingsScreen(openDrawer)
48 | }
49 | composable(route = NavDestinations.ROUTE_ABOUT) {
50 | AboutScreen(
51 | openDeveloper = { navController.navigate(NavDestinations.ROUTE_DEVELOPER) },
52 | openDrawer = openDrawer
53 | )
54 | }
55 | composable(route = NavDestinations.ROUTE_DEVELOPER) {
56 | DeveloperScreen { navController.navigateUp() }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/MainApp.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui
18 |
19 | import androidx.activity.compose.BackHandler
20 | import androidx.compose.material.DrawerValue
21 | import androidx.compose.material.ModalDrawer
22 | import androidx.compose.material.rememberDrawerState
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import androidx.navigation.compose.rememberNavController
26 | import dev.yankew.sample.compose.activityViewModel
27 | import dev.yankew.sample.compose.theme.MyTheme
28 | import dev.yankew.sample.domain.model.AppVariant
29 | import dev.yankew.sample.ui.navigation.AppDrawer
30 | import kotlinx.coroutines.launch
31 |
32 | @Composable
33 | fun MainApp() {
34 | val isDemo = activityViewModel().appVariant == AppVariant.MOBILE_DEMO
35 | MyTheme(isDemo = isDemo) {
36 | val navController = rememberNavController()
37 | val coroutineScope = rememberCoroutineScope()
38 | val drawerState = rememberDrawerState(DrawerValue.Closed)
39 | val openDrawer: () -> Unit = { coroutineScope.launch { drawerState.open() } }
40 | val closeDrawer: () -> Unit = { coroutineScope.launch { drawerState.close() } }
41 | if (drawerState.isOpen) {
42 | BackHandler { closeDrawer() }
43 | }
44 | ModalDrawer(
45 | drawerContent = {
46 | AppDrawer(
47 | navController = navController,
48 | closeDrawer = closeDrawer
49 | )
50 | },
51 | drawerState = drawerState,
52 | gesturesEnabled = true
53 | ) {
54 | MainNavGraph(navController, openDrawer)
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/lib/android/src/main/java/dev/yankew/sample/android/FlowBroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.android
18 |
19 | import android.content.BroadcastReceiver
20 | import android.content.Context
21 | import android.content.Intent
22 | import android.content.IntentFilter
23 | import kotlinx.coroutines.channels.Channel
24 | import kotlinx.coroutines.channels.consumeEach
25 | import kotlinx.coroutines.flow.Flow
26 | import kotlinx.coroutines.flow.flow
27 |
28 | /**
29 | * Register broadcast receiver to convert data in broadcast to Flow.
30 | * The receiver will be unregistered if the returned Flow is cancelled.
31 | *
32 | * @param filter Selects the Intent broadcasts to be received.
33 | * @param onStartListening A function that will run right after the registration of the receiver.
34 | * It is useful when you do two-way communication via broadcast. The requesting data broadcast
35 | * can be sent in this function.
36 | * @param extractData The function to extract data from the broadcast
37 | */
38 | fun Context.observeBroadcast(
39 | filter: IntentFilter,
40 | onStartListening: () -> Unit = {},
41 | extractData: (Context, Intent) -> T
42 | ): Flow {
43 | return flow {
44 | val channel = Channel(Channel.UNLIMITED)
45 | val receiver = object : BroadcastReceiver() {
46 | override fun onReceive(context: Context, intent: Intent) {
47 | channel.trySend(extractData(context, intent))
48 | }
49 | }
50 | registerReceiver(receiver, filter)
51 | onStartListening()
52 | try {
53 | channel.consumeEach {
54 | emit(it)
55 | }
56 | } finally {
57 | unregisterReceiver(receiver)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/comp/feature/src/main/java/dev/yankew/sample/feature/ElapseDataSource.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.feature
18 |
19 | import android.content.Context
20 | import android.content.Intent
21 | import android.content.IntentFilter
22 | import androidx.datastore.core.DataStore
23 | import androidx.datastore.preferences.core.Preferences
24 | import androidx.datastore.preferences.core.edit
25 | import androidx.datastore.preferences.core.longPreferencesKey
26 | import dagger.hilt.android.qualifiers.ApplicationContext
27 | import dev.yankew.sample.android.observeBroadcast
28 | import kotlinx.coroutines.flow.Flow
29 | import kotlinx.coroutines.flow.combine
30 | import kotlinx.coroutines.flow.mapNotNull
31 | import kotlinx.coroutines.flow.onStart
32 | import java.time.Instant
33 | import javax.inject.Inject
34 |
35 | interface ElapseDataSource {
36 | fun getElapsedSeconds(): Flow
37 | suspend fun saveLaunchTime()
38 | }
39 |
40 | class ElapseDataSourceImpl @Inject constructor(
41 | @ApplicationContext private val context: Context,
42 | @FeatureDataStore private val dataStore: DataStore,
43 | ) : ElapseDataSource {
44 | override fun getElapsedSeconds(): Flow = IntentFilter().apply {
45 | addAction(Intent.ACTION_TIME_TICK)
46 | }.let { filter ->
47 | context.observeBroadcast(filter) { _, _ -> Instant.now().epochSecond }
48 | }.onStart {
49 | emit(Instant.now().epochSecond)
50 | }.combine(
51 | dataStore.data.mapNotNull { it[KEY_LAUNCH_EPOCH] }
52 | ) { now, launch -> now - launch }
53 |
54 | override suspend fun saveLaunchTime() {
55 | dataStore.edit {
56 | it[KEY_LAUNCH_EPOCH] = Instant.now().epochSecond
57 | }
58 | }
59 |
60 | companion object {
61 | private val KEY_LAUNCH_EPOCH = longPreferencesKey("launch_epoch")
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/init/src/main/java/dev/yankew/sample/init/AppBaseInitializerWrapper.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.init
18 |
19 | import android.content.Context
20 | import androidx.startup.Initializer
21 | import dagger.hilt.EntryPoint
22 | import dagger.hilt.InstallIn
23 | import dagger.hilt.android.EntryPointAccessors
24 | import dagger.hilt.components.SingletonComponent
25 | import timber.log.Timber
26 | import java.util.Optional
27 |
28 | /**
29 | * This class is used to overcome the limitation of App Startup: the dependencies should be known
30 | * before create() method and the dependencies can't be provided by injection in create().
31 | * The app module can implement AppBaseInitializer interface to do some basic initialization before
32 | * all others. And feature modules can depend this initializer wrapper to make sure their
33 | * initializations happen after the base initialization.
34 | */
35 | class AppBaseInitializerWrapper : Initializer {
36 | override fun create(context: Context) {
37 | AppBaseInitializerEntryPoint.getBaseInitializer(context).orElse(null)?.init()
38 | Timber.i("Base initialization completed.")
39 | }
40 |
41 | override fun dependencies(): List>> = emptyList()
42 | }
43 |
44 | @EntryPoint
45 | @InstallIn(SingletonComponent::class)
46 | private interface AppBaseInitializerEntryPoint {
47 | fun getBaseInitializer(): Optional
48 |
49 | companion object {
50 | fun getBaseInitializer(context: Context): Optional {
51 | val appContext = context.applicationContext ?: throw IllegalStateException()
52 | return EntryPointAccessors.fromApplication(
53 | appContext,
54 | AppBaseInitializerEntryPoint::class.java
55 | ).getBaseInitializer()
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/comp/settings/src/main/java/dev/yankew/sample/settings/SettingsModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.settings
18 |
19 | import android.content.Context
20 | import androidx.datastore.core.DataStore
21 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
22 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory
23 | import androidx.datastore.preferences.core.Preferences
24 | import androidx.datastore.preferences.core.emptyPreferences
25 | import androidx.datastore.preferences.preferencesDataStoreFile
26 | import dagger.Module
27 | import dagger.Provides
28 | import dagger.hilt.InstallIn
29 | import dagger.hilt.android.qualifiers.ApplicationContext
30 | import dagger.hilt.components.SingletonComponent
31 | import dev.yankew.sample.domain.settings.AnalyticsOption
32 | import dev.yankew.sample.domain.settings.CrashlyticsOption
33 | import dev.yankew.sample.domain.settings.NotificationOption
34 | import javax.inject.Qualifier
35 | import javax.inject.Singleton
36 |
37 | @Retention(AnnotationRetention.BINARY)
38 | @Qualifier
39 | annotation class SettingsDataStore
40 |
41 | @Module
42 | @InstallIn(SingletonComponent::class)
43 | object SettingsModule {
44 | @Provides
45 | @Singleton
46 | @SettingsDataStore
47 | fun preferenceDataStore(@ApplicationContext context: Context): DataStore =
48 | PreferenceDataStoreFactory.create(
49 | corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }
50 | ) { context.preferencesDataStoreFile("settings") }
51 |
52 | @Provides
53 | @AnalyticsOption
54 | fun analyticsOption(settingsStore: SettingsStore) = settingsStore.analyticsOption
55 |
56 | @Provides
57 | @CrashlyticsOption
58 | fun crashlyticsOption(settingsStore: SettingsStore) = settingsStore.crashlyticsOption
59 |
60 | @Provides
61 | @NotificationOption
62 | fun notificationOption(settingsStore: SettingsStore) = settingsStore.notificationOption
63 | }
64 |
--------------------------------------------------------------------------------
/comp/analytics-firebase/src/main/java/dev/yankew/sample/analytics/firebase/FirebaseAnalyticsInitializer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.analytics.firebase
18 |
19 | import android.content.Context
20 | import com.google.firebase.analytics.ktx.analytics
21 | import com.google.firebase.ktx.Firebase
22 | import dagger.hilt.EntryPoint
23 | import dagger.hilt.InstallIn
24 | import dagger.hilt.android.EntryPointAccessors
25 | import dagger.hilt.components.SingletonComponent
26 | import dev.yankew.sample.domain.coroutine.ApplicationScope
27 | import dev.yankew.sample.init.ComponentInitializer
28 | import kotlinx.coroutines.CoroutineScope
29 | import kotlinx.coroutines.launch
30 | import timber.log.Timber
31 | import javax.inject.Inject
32 |
33 | class FirebaseAnalyticsInitializer : ComponentInitializer() {
34 | @Inject
35 | lateinit var firebaseAnalyticsTracker: FirebaseAnalyticsTracker
36 |
37 | @Inject
38 | @ApplicationScope
39 | lateinit var applicationScope: CoroutineScope
40 |
41 | override fun create(context: Context) {
42 | Timber.i("Initialize Firebase Analytics")
43 | FirebaseAnalyticsInitializerEntryPoint.resolve(context).inject(this)
44 | applicationScope.launch {
45 | val enabled = firebaseAnalyticsTracker.isAnalyticsEnabled()
46 | Firebase.analytics.setAnalyticsCollectionEnabled(enabled)
47 | }
48 | }
49 | }
50 |
51 | @EntryPoint
52 | @InstallIn(SingletonComponent::class)
53 | interface FirebaseAnalyticsInitializerEntryPoint {
54 | fun inject(initializer: FirebaseAnalyticsInitializer)
55 |
56 | companion object {
57 | fun resolve(context: Context): FirebaseAnalyticsInitializerEntryPoint {
58 | val appContext = context.applicationContext ?: throw IllegalStateException()
59 | return EntryPointAccessors.fromApplication(
60 | appContext,
61 | FirebaseAnalyticsInitializerEntryPoint::class.java
62 | )
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteConfigImpl.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig
20 | import com.google.firebase.remoteconfig.ktx.get
21 | import dev.yankew.sample.domain.config.ConfigValue
22 | import kotlinx.coroutines.flow.Flow
23 | import kotlinx.coroutines.flow.map
24 | import kotlinx.coroutines.flow.onStart
25 | import kotlinx.serialization.json.Json
26 | import timber.log.Timber
27 | import javax.inject.Inject
28 |
29 | class RemoteConfigImpl @Inject constructor(
30 | private val remoteConfigHelper: RemoteConfigHelper
31 | ) : RemoteConfig {
32 | override operator fun get(param: RemoteParam): Flow> {
33 | return remoteConfigHelper.updateEvent.map { getValue(param) }
34 | .onStart { emit(getValue(param)) }
35 | }
36 |
37 | private fun getValue(param: RemoteParam): ConfigValue {
38 | val value = remoteConfigHelper.remoteConfig[param.key]
39 | when (value.source) {
40 | FirebaseRemoteConfig.VALUE_SOURCE_STATIC -> return ConfigValue.Unset
41 | FirebaseRemoteConfig.VALUE_SOURCE_DEFAULT -> {
42 | Timber.i("The value of key <${param.key}> is retrieved from the defaults")
43 | }
44 | FirebaseRemoteConfig.VALUE_SOURCE_REMOTE -> {
45 | Timber.i("The value of key <${param.key}> is retrieved from Firebase Remote Config")
46 | }
47 | }
48 | @Suppress("UNCHECKED_CAST")
49 | return when (param) {
50 | is StringRemoteParam -> value.asString() as T
51 | is BooleanRemoteParam -> value.asBoolean() as T
52 | is LongRemoteParam -> value.asLong() as T
53 | is DoubleRemoteParam -> value.asDouble() as T
54 | is JsonRemoteParam -> Json.decodeFromString(param.serializer, value.asString())
55 | }.let { ConfigValue.ConfiguredValue(it) }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/comp/config-firebase/src/main/java/dev/yankew/sample/config/firebase/RemoteConfigFetchWorker.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.config.firebase
18 |
19 | import android.content.Context
20 | import androidx.hilt.work.HiltWorker
21 | import androidx.work.Constraints
22 | import androidx.work.CoroutineWorker
23 | import androidx.work.ExistingWorkPolicy
24 | import androidx.work.NetworkType
25 | import androidx.work.OneTimeWorkRequestBuilder
26 | import androidx.work.WorkManager
27 | import androidx.work.WorkerParameters
28 | import dagger.assisted.Assisted
29 | import dagger.assisted.AssistedInject
30 | import kotlinx.coroutines.tasks.await
31 | import timber.log.Timber
32 |
33 | @HiltWorker
34 | class RemoteConfigFetchWorker @AssistedInject constructor(
35 | @Assisted context: Context,
36 | @Assisted params: WorkerParameters,
37 | private val remoteConfigHelper: RemoteConfigHelper,
38 | ) : CoroutineWorker(context, params) {
39 | companion object {
40 | private const val UNIQUE_WORK_NAME = "RemoteConfigFetch"
41 |
42 | fun enqueue(context: Context) {
43 | val constraints = Constraints.Builder()
44 | .setRequiredNetworkType(NetworkType.CONNECTED)
45 | .build()
46 | val requestBuilder = OneTimeWorkRequestBuilder()
47 | .setConstraints(constraints)
48 | WorkManager.getInstance(context).enqueueUniqueWork(
49 | UNIQUE_WORK_NAME,
50 | ExistingWorkPolicy.KEEP,
51 | requestBuilder.build()
52 | )
53 | }
54 | }
55 |
56 | override suspend fun doWork(): Result {
57 | return try {
58 | if (remoteConfigHelper.remoteConfig.fetchAndActivate().await()) {
59 | remoteConfigHelper.notifyConfigUpdated()
60 | }
61 | Result.success()
62 | } catch (e: Exception) {
63 | Timber.w("Fetch remote config failed: ${e.message}")
64 | Result.retry()
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/settings/SettingsSwitch.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.settings
18 |
19 | import androidx.compose.foundation.clickable
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.Row
22 | import androidx.compose.foundation.layout.Spacer
23 | import androidx.compose.foundation.layout.fillMaxWidth
24 | import androidx.compose.foundation.layout.height
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.foundation.layout.width
27 | import androidx.compose.material.MaterialTheme
28 | import androidx.compose.material.Switch
29 | import androidx.compose.material.Text
30 | import androidx.compose.runtime.Composable
31 | import androidx.compose.ui.Alignment
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.unit.dp
34 |
35 | @Composable
36 | fun SettingsSwitch(
37 | title: String,
38 | checked: Boolean,
39 | onCheckedChange: (Boolean) -> Unit,
40 | enabled: Boolean = true
41 | ) {
42 | Row(
43 | modifier = Modifier
44 | .fillMaxWidth()
45 | .clickable(enabled = enabled) { onCheckedChange(!checked) },
46 | verticalAlignment = Alignment.CenterVertically
47 | ) {
48 | Spacer(
49 | modifier = Modifier
50 | .width(72.dp)
51 | .height(56.dp)
52 | )
53 | Text(
54 | text = title,
55 | modifier = Modifier
56 | .weight(1f)
57 | .padding(vertical = 16.dp),
58 | style = MaterialTheme.typography.subtitle2
59 | )
60 | Box(
61 | modifier = Modifier
62 | .width(72.dp)
63 | .height(56.dp),
64 | contentAlignment = Alignment.Center
65 | ) {
66 | Switch(
67 | enabled = enabled,
68 | checked = checked,
69 | onCheckedChange = { onCheckedChange(!checked) }
70 | )
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/comp/feature/src/main/java/dev/yankew/sample/feature/FeatureModule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.feature
18 |
19 | import android.content.Context
20 | import androidx.datastore.core.DataStore
21 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
22 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory
23 | import androidx.datastore.preferences.core.Preferences
24 | import androidx.datastore.preferences.core.emptyPreferences
25 | import androidx.datastore.preferences.preferencesDataStoreFile
26 | import dagger.Binds
27 | import dagger.Module
28 | import dagger.Provides
29 | import dagger.hilt.InstallIn
30 | import dagger.hilt.android.qualifiers.ApplicationContext
31 | import dagger.hilt.components.SingletonComponent
32 | import dagger.multibindings.IntoSet
33 | import dev.yankew.sample.domain.event.AppLaunchedEvent
34 | import dev.yankew.sample.domain.event.EventHandler
35 | import dev.yankew.sample.domain.repository.FeatureRepository
36 | import javax.inject.Qualifier
37 | import javax.inject.Singleton
38 |
39 | @Retention(AnnotationRetention.BINARY)
40 | @Qualifier
41 | annotation class FeatureDataStore
42 |
43 | @Module
44 | @InstallIn(SingletonComponent::class)
45 | object FeatureModule {
46 | @Provides
47 | @IntoSet
48 | fun appLaunchedHandler(
49 | handlers: FeatureEventHandlers
50 | ): EventHandler = handlers.appLaunchedHandler
51 |
52 | @Provides
53 | @Singleton
54 | @FeatureDataStore
55 | fun preferenceDataStore(@ApplicationContext context: Context): DataStore =
56 | PreferenceDataStoreFactory.create(
57 | corruptionHandler = ReplaceFileCorruptionHandler { emptyPreferences() }
58 | ) { context.preferencesDataStoreFile("feature") }
59 | }
60 |
61 | @Module
62 | @InstallIn(SingletonComponent::class)
63 | interface FeatureInterfaceModule {
64 | @Binds
65 | fun elapseDataSource(dataSource: ElapseDataSourceImpl): ElapseDataSource
66 |
67 | @Binds
68 | fun featureRepository(repository: FeatureRepositoryImpl): FeatureRepository
69 | }
70 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/about/DeveloperScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.about
18 |
19 | import androidx.compose.foundation.layout.Column
20 | import androidx.compose.foundation.layout.padding
21 | import androidx.compose.foundation.rememberScrollState
22 | import androidx.compose.foundation.verticalScroll
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.res.stringResource
26 | import androidx.hilt.navigation.compose.hiltViewModel
27 | import dev.yankew.sample.compose.collectAsStateWhenStarted
28 | import dev.yankew.sample.strings.R
29 | import dev.yankew.sample.ui.SubScreenScaffold
30 | import dev.yankew.sample.ui.settings.SettingsSwitch
31 |
32 | /**
33 | * Stateful composable of Developer Screen
34 | */
35 | @Composable
36 | fun DeveloperScreen(
37 | navigateUp: () -> Unit,
38 | ) {
39 | val viewModel = hiltViewModel()
40 | val newFeatureEnabled = viewModel.newFeatureLocalEnabled.collectAsStateWhenStarted(false).value
41 | DeveloperScreen(
42 | navigateUp = navigateUp,
43 | newFeatureEnabled = newFeatureEnabled,
44 | setNewFeatureEnabled = viewModel::setNewFeatureLocalEnabled
45 | )
46 | }
47 |
48 | /**
49 | * Stateless composable of Developer Screen
50 | */
51 | @Composable
52 | fun DeveloperScreen(
53 | navigateUp: () -> Unit,
54 | newFeatureEnabled: Boolean,
55 | setNewFeatureEnabled: (Boolean) -> Unit
56 | ) {
57 | SubScreenScaffold(
58 | titleResId = R.string.developer_title,
59 | navigateUp = navigateUp,
60 | ) { innerPaddings ->
61 | Column(
62 | modifier = Modifier
63 | .padding(innerPaddings)
64 | .verticalScroll(state = rememberScrollState())
65 | ) {
66 | SettingsSwitch(
67 | title = stringResource(R.string.enable_new_feature),
68 | checked = newFeatureEnabled,
69 | onCheckedChange = setNewFeatureEnabled
70 | )
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/depconstraints/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 | plugins {
18 | id("java-platform")
19 | }
20 |
21 | val activity = "1.4.0"
22 | val annotation = "1.3.0"
23 | val compose = Versions.COMPOSE
24 | val coroutines = "1.6.0"
25 | val dataStore = "1.0.0"
26 | val firebaseBom = "29.1.0"
27 | val hilt = Versions.HILT
28 | val hiltJetPack = "1.0.0"
29 | val lifecycle = "2.4.1"
30 | val navigation = "2.4.1"
31 | val serialization = "1.3.2"
32 | val startup = "1.1.1"
33 | val timber = "5.0.1"
34 | val workManager = "2.7.1"
35 |
36 | dependencies {
37 | constraints {
38 | api("${Libs.ACTIVITY_COMPOSE}:$activity")
39 | api("${Libs.ACTIVITY_KTX}:$activity")
40 | api("${Libs.ANNOTATION}:$annotation")
41 | api("${Libs.COMPOSE_ANIMATION}:$compose")
42 | api("${Libs.COMPOSE_MATERIAL}:$compose")
43 | api("${Libs.COMPOSE_RUNTIME}:$compose")
44 | api("${Libs.COMPOSE_THEME_ADAPTER}:$compose")
45 | api("${Libs.COMPOSE_TOOLING}:$compose")
46 | api("${Libs.COMPOSE_UI}:$compose")
47 | api("${Libs.COROUTINES}:$coroutines")
48 | api("${Libs.COROUTINES_PLAY_SERVICE}:$coroutines")
49 | api("${Libs.DATA_STORE_PREFERENCES}:$dataStore")
50 | api("${Libs.FIREBASE_BOM}:$firebaseBom")
51 | api("${Libs.HILT_ANDROID}:$hilt")
52 | api("${Libs.HILT_ANDROID_COMPILER}:$hilt")
53 | api("${Libs.HILT_ANDROIDX_COMPILER}:$hiltJetPack")
54 | api("${Libs.HILT_COMPILER}:$hilt")
55 | api("${Libs.HILT_CORE}:$hilt")
56 | api("${Libs.HILT_NAVIGATION_COMPOSE}:$hiltJetPack")
57 | api("${Libs.HILT_WORK}:$hiltJetPack")
58 | api("${Libs.KOTLIN_STDLIB}:${Versions.KOTLIN}")
59 | api("${Libs.KOTLINX_SERIALIZATION}:$serialization")
60 | api("${Libs.LIFECYCLE_RUNTIME_KTX}:$lifecycle")
61 | api("${Libs.LIFECYCLE_VIEWMODEL_COMPOSE}:$lifecycle")
62 | api("${Libs.LIFECYCLE_VIEWMODEL_KTX}:$lifecycle")
63 | api("${Libs.NAVIGATION_COMPOSE}:$navigation")
64 | api("${Libs.STARTUP}:$startup")
65 | api("${Libs.TIMBER}:$timber")
66 | api("${Libs.WORK_RUNTIME_KTX}:$workManager")
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/about/AboutScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui.about
18 |
19 | import androidx.compose.foundation.layout.Column
20 | import androidx.compose.foundation.layout.Spacer
21 | import androidx.compose.foundation.layout.fillMaxSize
22 | import androidx.compose.foundation.layout.height
23 | import androidx.compose.foundation.layout.padding
24 | import androidx.compose.material.Button
25 | import androidx.compose.material.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.res.stringResource
30 | import androidx.compose.ui.unit.dp
31 | import androidx.hilt.navigation.compose.hiltViewModel
32 | import dev.yankew.sample.strings.R
33 | import dev.yankew.sample.ui.MainScreenScaffold
34 |
35 | /**
36 | * Stateful composable of About Screen
37 | */
38 | @Composable
39 | fun AboutScreen(
40 | openDeveloper: () -> Unit,
41 | openDrawer: () -> Unit,
42 | ) {
43 | AboutScreen(
44 | hasDeveloper = hiltViewModel().isLocalConfigPresent,
45 | openDeveloper = openDeveloper,
46 | openDrawer = openDrawer
47 | )
48 | }
49 |
50 | /**
51 | * Stateless composable of About Screen
52 | */
53 | @Composable
54 | fun AboutScreen(
55 | hasDeveloper: Boolean,
56 | openDeveloper: () -> Unit,
57 | openDrawer: () -> Unit,
58 | ) {
59 | MainScreenScaffold(
60 | titleResId = R.string.about_title,
61 | openDrawer = openDrawer,
62 | ) { innerPaddings ->
63 | Column(
64 | modifier = Modifier
65 | .fillMaxSize()
66 | .padding(innerPaddings)
67 | .padding(16.dp),
68 | horizontalAlignment = Alignment.CenterHorizontally
69 | ) {
70 | Text(stringResource(R.string.about_text))
71 | if (hasDeveloper) {
72 | Spacer(modifier = Modifier.height(16.dp))
73 | Button(onClick = openDeveloper) {
74 | Text(stringResource(R.string.button_developer))
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/ui/ui-mobile/src/main/java/dev/yankew/sample/ui/Scaffolds.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.ui
18 |
19 | import androidx.annotation.StringRes
20 | import androidx.compose.foundation.layout.PaddingValues
21 | import androidx.compose.material.Icon
22 | import androidx.compose.material.IconButton
23 | import androidx.compose.material.Scaffold
24 | import androidx.compose.material.Text
25 | import androidx.compose.material.TopAppBar
26 | import androidx.compose.material.icons.Icons
27 | import androidx.compose.material.icons.filled.ArrowBack
28 | import androidx.compose.material.icons.filled.Menu
29 | import androidx.compose.runtime.Composable
30 | import androidx.compose.ui.res.stringResource
31 | import dev.yankew.sample.strings.R
32 |
33 | @Composable
34 | fun MainScreenScaffold(
35 | @StringRes titleResId: Int,
36 | openDrawer: () -> Unit,
37 | content: @Composable (PaddingValues) -> Unit
38 | ) {
39 | Scaffold(
40 | topBar = {
41 | TopAppBar(
42 | title = { Text(stringResource(titleResId)) },
43 | navigationIcon = {
44 | IconButton(onClick = { openDrawer() }) {
45 | Icon(
46 | Icons.Default.Menu,
47 | contentDescription = stringResource(R.string.open_menu_desc)
48 | )
49 | }
50 | }
51 | )
52 | },
53 | content = content
54 | )
55 | }
56 |
57 | @Composable
58 | fun SubScreenScaffold(
59 | @StringRes titleResId: Int,
60 | navigateUp: () -> Unit,
61 | content: @Composable (PaddingValues) -> Unit
62 | ) {
63 | Scaffold(
64 | topBar = {
65 | TopAppBar(
66 | title = { Text(stringResource(titleResId)) },
67 | navigationIcon = {
68 | IconButton(onClick = navigateUp) {
69 | Icon(
70 | imageVector = Icons.Default.ArrowBack,
71 | contentDescription = stringResource(R.string.go_upward_desc)
72 | )
73 | }
74 | },
75 | )
76 | },
77 | content = content
78 | )
79 | }
80 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Clean Architecture Sample
2 | [](https://github.com/yacca/android-clean-arch/actions/workflows/Check.yaml)
3 |
4 | A sample project demonstrating clean architecture in an Android app
5 |
6 | ## Tech stack
7 | - [Kotlin](https://kotlinlang.org/)
8 | - [Coroutines](https://kotlinlang.org/docs/coroutines-overview.html)
9 | - [Flow](https://kotlinlang.org/docs/flow.html)
10 | - [Jetpack](https://developer.android.com/jetpack)
11 | - [App Startup](https://developer.android.com/topic/libraries/app-startup)
12 | - [Compose](https://developer.android.com/jetpack/compose)
13 | - [DataStore](https://developer.android.com/topic/libraries/architecture/datastore)
14 | - [Hilt](https://dagger.dev/hilt/)
15 | - [Hilt Android](https://developer.android.com/training/dependency-injection/hilt-android)
16 | - [Lifecycle](https://developer.android.com/topic/libraries/architecture/lifecycle)
17 | - [Navigation Compose](https://developer.android.com/jetpack/compose/navigation)
18 | - [WorkManager](https://developer.android.com/topic/libraries/architecture/workmanager)
19 | - [Firebase](https://firebase.google.com/)
20 | - [Analytics](https://firebase.google.com/products/analytics)
21 | - [Crashlytics](https://firebase.google.com/products/crashlytics)
22 | - [Remote Config](https://firebase.google.com/products/remote-config)
23 | - Others
24 | - [Gradle](https://gradle.org/)
25 | - [Gradle Versions Plugin](https://github.com/ben-manes/gradle-versions-plugin)
26 | - [PlantUML](https://plantuml.com/)
27 | - [Spotless](https://github.com/diffplug/spotless)
28 | - [Timber](https://github.com/JakeWharton/timber)
29 |
30 | ## Modules and their dependencies
31 |
32 | All modules
33 | 
34 |
35 | Modules of mobile app
36 | 
37 |
38 | Modules of demo mobile app
39 | 
40 |
41 | ## License
42 | ```
43 | Copyright 2022 WANG Yanke
44 |
45 | Licensed under the Apache License, Version 2.0 (the "License");
46 | you may not use this file except in compliance with the License.
47 | You may obtain a copy of the License at
48 |
49 | https://www.apache.org/licenses/LICENSE-2.0
50 |
51 | Unless required by applicable law or agreed to in writing, software
52 | distributed under the License is distributed on an "AS IS" BASIS,
53 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
54 | See the License for the specific language governing permissions and
55 | limitations under the License.
56 | ```
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/comp/analytics-debug/src/main/java/dev/yankew/sample/analytics/debug/DebugAnalyticsTracker.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.analytics.debug
18 |
19 | import dev.yankew.sample.domain.analytics.AnalyticsTracker
20 | import dev.yankew.sample.domain.settings.AnalyticsOption
21 | import dev.yankew.sample.domain.settings.SettingOption
22 | import kotlinx.coroutines.flow.first
23 | import timber.log.Timber
24 | import javax.inject.Inject
25 | import javax.inject.Provider
26 |
27 | class DebugAnalyticsTracker @Inject constructor(
28 | @AnalyticsOption private val analyticsOption: Provider>,
29 | ) : AnalyticsTracker {
30 | private val userProperties: MutableMap = mutableMapOf()
31 |
32 | override suspend fun isAnalyticsEnabled(): Boolean {
33 | return analyticsOption.get().getValue().first()
34 | }
35 |
36 | override fun logEvent(name: String, params: Map) {
37 | val eventNameString = "Event Name: $name"
38 | val title = " Debug Analytics "
39 | val maxKeyLen = params.keys.maxOfOrNull { it.length } ?: 4
40 | val maxValueLen = params.values.maxOfOrNull { it.toString().length } ?: 4
41 | val dividerLineLen = (maxKeyLen + maxValueLen + 4).coerceAtLeast(eventNameString.length)
42 | val dividerLen = ((dividerLineLen - title.length + 1) / 2).coerceAtLeast(4)
43 | val parameters = params.toString(maxKeyLen)
44 | val maxPropertyKeyLen = userProperties.keys.maxOfOrNull { it.length } ?: 4
45 | val properties = userProperties.toString(maxPropertyKeyLen)
46 | val topDivider = "=".repeat(dividerLen)
47 | val bottomDivider = "-".repeat(dividerLen)
48 | Timber.v(
49 | """
50 | ||
51 | |$topDivider$title$topDivider
52 | |$eventNameString
53 | |Params:
54 | |$parameters
55 | |User Properties:
56 | |$properties
57 | |$bottomDivider$title$bottomDivider
58 | ||
59 | """.trimMargin()
60 | )
61 | }
62 |
63 | override fun setUserProperty(key: String, value: String) {
64 | userProperties[key] = value
65 | }
66 |
67 | private fun Map.toString(maxKeyLen: Int): String {
68 | val keyFormat = "%-${maxKeyLen}s"
69 | return entries.joinToString("\n") {
70 | val keyString = keyFormat.format(it.key)
71 | " $keyString: ${it.value}"
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/comp/settings/src/main/java/dev/yankew/sample/settings/SettingsStore.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.settings
18 |
19 | import androidx.datastore.core.DataStore
20 | import androidx.datastore.preferences.core.Preferences
21 | import androidx.datastore.preferences.core.booleanPreferencesKey
22 | import androidx.datastore.preferences.core.edit
23 | import dev.yankew.sample.domain.analytics.SettingsChangedTrackingEvent
24 | import dev.yankew.sample.domain.event.AnalyticsOptionEvent
25 | import dev.yankew.sample.domain.event.CrashlyticsOptionEvent
26 | import dev.yankew.sample.domain.event.EventDispatcher
27 | import dev.yankew.sample.domain.settings.SettingOption
28 | import kotlinx.coroutines.flow.Flow
29 | import kotlinx.coroutines.flow.map
30 | import javax.inject.Inject
31 |
32 | class SettingsStore @Inject constructor(
33 | @SettingsDataStore private val dataStore: DataStore,
34 | analyticsOptionEventDispatcher: EventDispatcher,
35 | crashlyticsOptionEventDispatcher: EventDispatcher,
36 | settingsChangedTrackingEvent: SettingsChangedTrackingEvent,
37 | ) {
38 | val analyticsOption = getBooleanOption(KEY_ANALYTICS, true) {
39 | // Only 'enabled event' can be logged
40 | settingsChangedTrackingEvent.logAnalyticsChanged(it)
41 | analyticsOptionEventDispatcher.dispatch(AnalyticsOptionEvent(it))
42 | }
43 | val crashlyticsOption = getBooleanOption(KEY_CRASHLYTICS, true) {
44 | settingsChangedTrackingEvent.logCrashlyticsChanged(it)
45 | crashlyticsOptionEventDispatcher.dispatch(CrashlyticsOptionEvent(it))
46 | }
47 | val notificationOption = getBooleanOption(KEY_NOTIFICATION, true) {
48 | settingsChangedTrackingEvent.logNotificationChanged(it)
49 | }
50 |
51 | private fun getBooleanOption(
52 | key: String,
53 | default: Boolean,
54 | dispatch: suspend (Boolean) -> Unit = {}
55 | ) = object : SettingOption {
56 | override fun getValue(): Flow {
57 | return dataStore.data.map { it[booleanPreferencesKey(key)] ?: default }
58 | }
59 |
60 | override suspend fun setValue(value: Boolean) {
61 | dataStore.edit { it[booleanPreferencesKey(key)] = value }
62 | dispatch(value)
63 | }
64 | }
65 |
66 | private companion object {
67 | const val KEY_NOTIFICATION = "notification"
68 | const val KEY_ANALYTICS = "analytics"
69 | const val KEY_CRASHLYTICS = "crashlytics"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/lib/compose/src/main/java/dev/yankew/sample/compose/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 WANG Yanke
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 | * http://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 dev.yankew.sample.compose.theme
18 |
19 | import androidx.compose.foundation.isSystemInDarkTheme
20 | import androidx.compose.material.MaterialTheme
21 | import androidx.compose.material.Surface
22 | import androidx.compose.material.darkColors
23 | import androidx.compose.material.lightColors
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.graphics.Color
26 |
27 | private val LightThemeColors = lightColors(
28 | primary = Blue700,
29 | primaryVariant = Blue900,
30 | onPrimary = Color.White,
31 | secondary = Blue700,
32 | secondaryVariant = Blue900,
33 | onSecondary = Color.White,
34 | error = Blue800,
35 | onBackground = Color.Black,
36 |
37 | )
38 |
39 | private val DarkThemeColors = darkColors(
40 | primary = Blue300,
41 | primaryVariant = Blue700,
42 | onPrimary = Color.Black,
43 | secondary = Blue300,
44 | onSecondary = Color.Black,
45 | error = Blue200,
46 | onBackground = Color.White
47 | )
48 |
49 | private val DemoLightThemeColors = lightColors(
50 | primary = Amber700,
51 | primaryVariant = Amber900,
52 | onPrimary = Color.White,
53 | secondary = Amber700,
54 | secondaryVariant = Amber900,
55 | onSecondary = Color.White,
56 | error = Amber800,
57 | onBackground = Color.Black,
58 |
59 | )
60 |
61 | private val DemoDarkThemeColors = darkColors(
62 | primary = Amber300,
63 | primaryVariant = Amber700,
64 | onPrimary = Color.Black,
65 | secondary = Amber300,
66 | onSecondary = Color.Black,
67 | error = Amber200,
68 | onBackground = Color.White
69 | )
70 |
71 | @Composable
72 | fun MyTheme(
73 | darkTheme: Boolean = isSystemInDarkTheme(),
74 | isDemo: Boolean = false,
75 | content: @Composable () -> Unit
76 | ) {
77 | val colors = if (!isDemo) {
78 | if (darkTheme) DarkThemeColors else LightThemeColors
79 | } else {
80 | if (darkTheme) DemoDarkThemeColors else DemoLightThemeColors
81 | }
82 | MaterialTheme(
83 | colors = colors,
84 | typography = typography,
85 | shapes = shapes,
86 | content = content
87 | )
88 | }
89 |
90 | @Composable
91 | fun PreviewTheme(
92 | darkTheme: Boolean = isSystemInDarkTheme(),
93 | content: @Composable () -> Unit
94 | ) {
95 | MyTheme(darkTheme = darkTheme) {
96 | Surface(color = MaterialTheme.colors.background) {
97 | content()
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------