├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── enhancement.md ├── dependabot.yml └── pull_request_template.md ├── .gitignore ├── .idea ├── copyright │ ├── Apache_2_0.xml │ └── profiles_settings.xml ├── kotlinc.xml └── vcs.xml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── mini │ │ └── android │ │ └── sample │ │ ├── MainActivity.kt │ │ ├── SampleActions.kt │ │ ├── SampleScreen.kt │ │ ├── StoreSampleActivity.kt │ │ ├── ViewModelSampleActivity.kt │ │ └── ui │ │ └── theme │ │ ├── Color.kt │ │ ├── Theme.kt │ │ └── Type.kt │ └── res │ ├── drawable │ ├── ic_launcher_background.xml │ ├── ic_launcher_foreground.xml │ └── ic_launcher_monochrome.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle.kts ├── convention-plugins ├── .gitignore ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ └── mini │ └── android │ └── plugins │ ├── AndroidAppConventionPlugin.kt │ ├── AndroidLibConventionPlugin.kt │ ├── JavaLibConventionPlugin.kt │ └── extensions │ ├── MavenPomExtensions.kt │ └── ProjectExtensions.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── jitpack.yml ├── mini-android ├── .gitignore ├── build.gradle.kts ├── mini-android.pro ├── proguard-rules.pro └── src │ └── main │ └── java │ └── mini │ └── android │ ├── FluxActivity.kt │ ├── FluxFragment.kt │ ├── FluxStoreViewModel.kt │ └── FluxViewModel.kt ├── mini-common ├── .gitignore ├── build.gradle.kts └── src │ ├── main │ ├── java │ │ └── mini │ │ │ ├── AndroidInterop.kt │ │ │ ├── Annotations.kt │ │ │ ├── CloseableTracker.kt │ │ │ ├── CompositeCloseable.kt │ │ │ ├── DiffAdapter.kt │ │ │ ├── Dispatcher.kt │ │ │ ├── LoggerMiddleware.kt │ │ │ ├── Middleware.kt │ │ │ ├── Mini.kt │ │ │ ├── Misc.kt │ │ │ ├── NestedStateContainer.kt │ │ │ ├── Resource.kt │ │ │ ├── State.kt │ │ │ ├── StateContainer.kt │ │ │ ├── Store.kt │ │ │ ├── StoreFlow.kt │ │ │ └── Threading.kt │ └── resources │ │ └── META-INF │ │ └── proguard │ │ └── mini-common.pro │ └── test │ └── kotlin │ └── mini │ ├── CompositeCloseableTest.kt │ ├── DispatcherTest.kt │ ├── LoggerMiddlewareTest.kt │ ├── ResourceTest.kt │ ├── SampleStore.kt │ ├── StoreFlowTest.kt │ ├── StoreTest.kt │ ├── TestAction.kt │ └── TestDispatcher.kt ├── mini-kodein-android-compose ├── .gitignore ├── build.gradle.kts ├── mini-kodein-android-compose.pro ├── proguard-rules.pro └── src │ └── main │ └── java │ └── mini │ └── kodein │ └── android │ └── compose │ └── KodeinAndroidComposeUtils.kt ├── mini-kodein-android ├── .gitignore ├── build.gradle.kts ├── mini-kodein-android.pro ├── proguard-rules.pro └── src │ └── main │ ├── java │ └── mini │ │ └── kodein │ │ └── android │ │ ├── FluxTypedViewModel.kt │ │ ├── KodeinAndroidUtils.kt │ │ └── TypedViewModel.kt │ └── res │ └── values │ └── strings.xml ├── mini-kodein ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── java │ └── mini │ │ └── kodein │ │ └── KodeinUtils.kt │ └── resources │ └── META-INF │ └── proguard │ └── mini-kodein.pro ├── mini-processor-test ├── .gitignore ├── build.gradle.kts └── src │ ├── main │ └── java │ │ └── mini │ │ └── test │ │ ├── AnyAction.kt │ │ ├── BasicState.kt │ │ └── ReducersStore.kt │ └── test │ └── java │ └── mini │ └── test │ └── ReducersStoreTest.kt ├── mini-processor ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── java │ └── mini │ │ └── processor │ │ ├── common │ │ ├── ContainerBuilders.kt │ │ ├── ModelGeneratorDelegate.kt │ │ ├── ProcessorException.kt │ │ ├── actions │ │ │ ├── ActionModels.kt │ │ │ ├── ActionTypesGenerator.kt │ │ │ └── ActionTypesGeneratorDelegate.kt │ │ └── reducers │ │ │ ├── ReducersGenerator.kt │ │ │ ├── ReducersGeneratorDelegate.kt │ │ │ └── ReducersModels.kt │ │ ├── kapt │ │ ├── MiniAnnotationProcessor.java │ │ ├── Processor.kt │ │ ├── ProcessorUtils.kt │ │ ├── actions │ │ │ ├── KaptActionModel.kt │ │ │ └── KaptActionTypesGeneratorDelegate.kt │ │ └── reducers │ │ │ ├── KaptReducersGeneratorDelegate.kt │ │ │ └── KaptReducersModels.kt │ │ └── ksp │ │ ├── MiniSymbolProcessor.kt │ │ ├── MiniSymbolProcessorProvider.kt │ │ ├── SymbolProcessorUtils.kt │ │ ├── actions │ │ ├── KspActionModel.kt │ │ └── KspActionTypesGeneratorDelegate.kt │ │ └── reducers │ │ ├── KspReducersGeneratorDelegate.kt │ │ └── KspReducersModels.kt │ └── resources │ └── META-INF │ └── services │ ├── com.google.devtools.ksp.processing.SymbolProcessorProvider │ └── javax.annotation.processing.Processor ├── mini-testing ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── mini │ └── testing │ ├── CleanStateRule.kt │ ├── TestDispatcherMiddleware.kt │ └── TestDispatcherRule.kt └── settings.gradle.kts /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This is a comment. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # Current code owners 5 | # - @nicolasmertanen -> Nicolás Mertanen 6 | # - @finxo -> Alejandro Lopez 7 | # - @FrangSierra -> Francisco García 8 | # - @adriangl -> Adrián García 9 | # - @yamidragut -> Estefanía Sarasola 10 | # - @Babelia13 -> Sara Lucía Pérez 11 | 12 | * @nicolasmertanen @finxo @FrangSierra @adriangl @yamidragut @Babelia13 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: adriangl 7 | 8 | --- 9 | 10 | **Device and SW details (please complete the following information):** 11 | - Device: [e.g. Google Pixel 3] 12 | - OS: [e.g. Android 9] 13 | - Library Version [e.g. 1.0.0] 14 | 15 | **Summary and background of the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **Steps to reproduce** 19 | Steps to reproduce the behavior: 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | Also attach notes or stack traces if applicable. 26 | 27 | **Expected behavior** 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Current behavior** 31 | The summary of what currently happens in your use case. 32 | 33 | **Additional context** 34 | Add any other context about the problem here. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Enhancement 3 | about: Create a report to propose new features and improvements 4 | title: '' 5 | labels: enhancement 6 | assignees: adriangl 7 | 8 | --- 9 | 10 | **Summary and context of the enhancement** 11 | A clear and concise description of what the enhancement is and why it 12 | should be added. 13 | 14 | **Suggested implementation** 15 | Add suggestions about how the enhancement should be implemented. 16 | 17 | **Additional documentation** 18 | Useful links to review the enhancement. -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Updates for Gradle dependencies used in the app 4 | - package-ecosystem: gradle 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Github issue (delete if this does not apply) 2 | https://github.com/hyperdevs-team/mini-kotlin/issues/change_me_issue_number 3 | 4 | ### PR's key points 5 | 6 | ### How to review this PR? 7 | 8 | ### Related Issues (delete if this does not apply) 9 | 10 | ### Definition of Done 11 | - [ ] Tests pass 12 | - [ ] Works with Proguard 13 | - [ ] There is no outcommented or debug code left -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | 11 | 12 | # Created by https://www.gitignore.io/api/java,gradle,intellij,osx,windows,linux 13 | 14 | ### Intellij ### 15 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 16 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 17 | 18 | # User-specific stuff: 19 | .idea/**/workspace.xml 20 | .idea/**/tasks.xml 21 | .idea/**/usage.statistics.xml 22 | .idea/**/dictionaries 23 | .idea/**/shelf 24 | 25 | # Generated files 26 | .idea/**/contentModel.xml 27 | .idea/**/libraries-with-intellij-classes.xml 28 | 29 | # Sensitive or high-churn files: 30 | .idea/**/dataSources/ 31 | .idea/**/dataSources.ids 32 | .idea/**/dataSources.xml 33 | .idea/**/dataSources.local.xml 34 | .idea/**/sqlDataSources.xml 35 | .idea/**/dynamic.xml 36 | .idea/**/uiDesigner.xml 37 | .idea/**/dbnavigator.xml 38 | 39 | # Gradle: 40 | .idea/**/gradle.xml 41 | .idea/**/libraries 42 | 43 | # Mongo Explorer plugin: 44 | .idea/**/mongoSettings.xml 45 | 46 | ## File-based project format: 47 | *.iws 48 | 49 | ## Plugin-specific files: 50 | 51 | # IntelliJ 52 | /out/ 53 | *.iml 54 | .idea/encodings.xml 55 | .idea/vcs.xml 56 | .idea/codeStyles/Project.xml 57 | .idea/workspace.xml 58 | .idea/tasks.xml 59 | .idea/libraries 60 | .idea/dictionaries 61 | .idea/runConfigurations.xml 62 | .idea/terminal.xml 63 | .idea/inspectionProfiles/ 64 | .idea/jarRepositories.xml 65 | .idea/modules 66 | .idea/markdown-exported-files.xml 67 | .idea/markdown-navigator.xml 68 | .idea/markdown-navigator-enh.xml 69 | .idea/markdown-navigator/ 70 | .idea/misc.xml 71 | .idea/modules.xml 72 | .idea/caches 73 | .idea/compiler.xml 74 | .idea/assetWizardSettings.xml 75 | .idea/navEditor.xml 76 | .idea/.name 77 | .idea/deploymentTargetSelector.xml 78 | .idea/migrations.xml 79 | .idea/other.xml 80 | 81 | # mpeltonen/sbt-idea plugin 82 | .idea_modules/ 83 | 84 | # JIRA plugin 85 | atlassian-ide-plugin.xml 86 | 87 | # Cursive Clojure plugin 88 | .idea/replstate.xml 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | 96 | # Editor-based Rest Client 97 | .idea/httpRequests 98 | 99 | # Android studio 3.1+ serialized cache file 100 | .idea/caches/build_file_checksums.ser 101 | 102 | ### Intellij Patch ### 103 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 104 | 105 | # *.iml 106 | # modules.xml 107 | # .idea/misc.xml 108 | # *.ipr 109 | 110 | ### Java ### 111 | # Compiled class file 112 | *.class 113 | 114 | # Log file 115 | *.message 116 | 117 | # BlueJ files 118 | *.ctxt 119 | 120 | # Mobile Tools for Java (J2ME) 121 | .mtj.tmp/ 122 | 123 | # Package Files # 124 | *.jar 125 | *.war 126 | *.ear 127 | *.zip 128 | *.tar.gz 129 | *.rar 130 | 131 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 132 | hs_err_pid* 133 | 134 | ### Linux ### 135 | *~ 136 | 137 | # temporary files which can be created if a process still has a handle open of a deleted file 138 | .fuse_hidden* 139 | 140 | # KDE directory preferences 141 | .directory 142 | 143 | # Linux trash folder which might appear on any partition or disk 144 | .Trash-* 145 | 146 | # .nfs files are created when an open file is removed but is still being accessed 147 | .nfs* 148 | 149 | ### OSX ### 150 | *.DS_Store 151 | .AppleDouble 152 | .LSOverride 153 | 154 | # Icon must end with two \r 155 | Icon 156 | 157 | # Thumbnails 158 | ._* 159 | 160 | # Files that might appear in the root of a volume 161 | .DocumentRevisions-V100 162 | .fseventsd 163 | .Spotlight-V100 164 | .TemporaryItems 165 | .Trashes 166 | .VolumeIcon.icns 167 | .com.apple.timemachine.donotpresent 168 | 169 | # Directories potentially created on remote AFP share 170 | .AppleDB 171 | .AppleDesktop 172 | Network Trash Folder 173 | Temporary Items 174 | .apdisk 175 | 176 | ### Windows ### 177 | # Windows thumbnail cache files 178 | Thumbs.db 179 | ehthumbs.db 180 | ehthumbs_vista.db 181 | 182 | # Folder config file 183 | Desktop.ini 184 | 185 | # Recycle Bin used on file shares 186 | $RECYCLE.BIN/ 187 | 188 | # Windows Installer files 189 | *.cab 190 | *.msi 191 | *.msm 192 | *.msp 193 | 194 | # Windows shortcuts 195 | *.lnk 196 | 197 | ### Gradle ### 198 | .gradle 199 | /build/ 200 | 201 | # Ignore Gradle GUI config 202 | gradle-app.setting 203 | 204 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 205 | !gradle-wrapper.jar 206 | 207 | # Cache of project 208 | .gradletasknamecache 209 | 210 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 211 | # gradle/wrapper/gradle-wrapper.properties 212 | 213 | # End of https://www.gitignore.io/api/java,gradle,intellij,osx,windows,linux 214 | -------------------------------------------------------------------------------- /.idea/copyright/Apache_2_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.android.application) 19 | alias(libs.plugins.kotlin.android) 20 | // alias(libs.plugins.kotlin.kapt) 21 | alias(libs.plugins.kotlin.ksp) 22 | alias(libs.plugins.convention.androidApp) 23 | } 24 | 25 | android { 26 | namespace = "mini.android.sample" 27 | 28 | compileSdk = libs.versions.android.compileSdk.get().toInt() 29 | buildToolsVersion = libs.versions.android.buildTools.get() 30 | 31 | defaultConfig { 32 | applicationId = "mini.android.sample" 33 | minSdk = libs.versions.android.minSdk.get().toInt() 34 | targetSdk = libs.versions.android.targetSdk.get().toInt() 35 | multiDexEnabled = true 36 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 37 | } 38 | 39 | buildFeatures { 40 | compose = true 41 | viewBinding = true 42 | } 43 | 44 | composeOptions { 45 | kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() 46 | } 47 | 48 | buildTypes { 49 | release { 50 | isMinifyEnabled = false 51 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 52 | } 53 | } 54 | 55 | compileOptions { 56 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 57 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 58 | } 59 | 60 | kotlinOptions { 61 | jvmTarget = libs.versions.java.sdk.get() 62 | } 63 | 64 | lint { 65 | abortOnError = false 66 | } 67 | } 68 | 69 | dependencies { 70 | implementation(project(":mini-android")) 71 | implementation(project(":mini-kodein-android")) 72 | 73 | // kapt(project(":mini-processor")) 74 | ksp(project(":mini-processor")) 75 | 76 | // Kotlin 77 | implementation(libs.kotlin.stdlib) 78 | implementation(libs.kotlin.reflect) 79 | 80 | // Coroutines 81 | implementation(libs.kotlinx.coroutines.core) 82 | 83 | // Support 84 | implementation(libs.bundles.androidx) 85 | implementation(libs.bundles.compose) 86 | implementation(libs.androidx.activity) 87 | implementation(libs.bundles.androidx.lifecycle) 88 | 89 | // Test 90 | testImplementation(libs.junit) 91 | androidTestImplementation(libs.androidx.test.runner) 92 | androidTestImplementation(libs.espresso) 93 | androidTestImplementation(libs.androidx.test.junit) 94 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/pablo/development/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle.kts. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample 18 | 19 | import android.content.Intent 20 | import android.net.Uri 21 | import android.os.Bundle 22 | import android.widget.Button 23 | import androidx.activity.compose.setContent 24 | import androidx.activity.enableEdgeToEdge 25 | import androidx.appcompat.app.AppCompatActivity 26 | import androidx.compose.foundation.background 27 | import androidx.compose.foundation.layout.Arrangement 28 | import androidx.compose.foundation.layout.Box 29 | import androidx.compose.foundation.layout.Column 30 | import androidx.compose.foundation.layout.fillMaxSize 31 | import androidx.compose.foundation.layout.padding 32 | import androidx.compose.material3.Button 33 | import androidx.compose.material3.MaterialTheme 34 | import androidx.compose.material3.Scaffold 35 | import androidx.compose.material3.Text 36 | import androidx.compose.runtime.Composable 37 | import androidx.compose.runtime.remember 38 | import androidx.compose.ui.Alignment 39 | import androidx.compose.ui.Modifier 40 | import androidx.compose.ui.graphics.Color 41 | import androidx.compose.ui.platform.LocalContext 42 | import androidx.compose.ui.res.stringResource 43 | import androidx.compose.ui.tooling.preview.Preview 44 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen 45 | import mini.android.sample.ui.theme.AppTheme 46 | 47 | class MainActivity : AppCompatActivity() { 48 | 49 | override fun onCreate(savedInstanceState: Bundle?) { 50 | super.onCreate(savedInstanceState) 51 | 52 | installSplashScreen() 53 | enableEdgeToEdge() 54 | 55 | setContent { 56 | AppTheme { 57 | MainScreen( 58 | onGoToStoreSampleClicked = { 59 | Intent(this, StoreSampleActivity::class.java).apply { 60 | startActivity(this) 61 | } 62 | }, 63 | onGoToViewModelSampleClicked = { 64 | Intent(this, ViewModelSampleActivity::class.java).apply { 65 | startActivity(this) 66 | } 67 | } 68 | ) 69 | } 70 | } 71 | } 72 | } 73 | 74 | @Composable 75 | private fun MainScreen(modifier: Modifier = Modifier, 76 | onGoToStoreSampleClicked: () -> Unit = {}, 77 | onGoToViewModelSampleClicked: () -> Unit = {}) { 78 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 79 | MainContent( 80 | modifier = modifier 81 | .fillMaxSize() 82 | .padding(innerPadding), 83 | onGoToStoreSampleClicked = onGoToStoreSampleClicked, 84 | onGoToViewModelSampleClicked = onGoToViewModelSampleClicked 85 | ) 86 | } 87 | } 88 | 89 | @Composable 90 | private fun MainContent( 91 | modifier: Modifier = Modifier, 92 | onGoToStoreSampleClicked: () -> Unit = {}, 93 | onGoToViewModelSampleClicked: () -> Unit = {} 94 | ) { 95 | Column( 96 | modifier = modifier.fillMaxSize(), 97 | horizontalAlignment = Alignment.CenterHorizontally, 98 | verticalArrangement = Arrangement.Center 99 | ) { 100 | Button(onClick = onGoToStoreSampleClicked) { 101 | Text("Go to Store sample") 102 | } 103 | Button(onClick = onGoToViewModelSampleClicked) { 104 | Text("Go to ViewModel sample") 105 | } 106 | } 107 | } 108 | 109 | @Preview(showBackground = true) 110 | @Composable 111 | fun MainScreenPreview() { 112 | AppTheme { 113 | MainContent() 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/SampleActions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample 18 | 19 | import mini.Action 20 | import mini.Reducer 21 | import mini.SuspendingAction 22 | import java.io.Serializable 23 | 24 | data class MainState( 25 | val text: String = "0", 26 | val loading: Boolean = false 27 | ) : Serializable, mini.State 28 | 29 | @Action 30 | data class SetLoadingAction(val loading: Boolean) 31 | 32 | @Action 33 | data class SetTextAction(val text: String) 34 | 35 | @Action 36 | interface AnalyticsAction 37 | 38 | @Action 39 | class LongUseCaseAction(val userName: String) : AnalyticsAction, SuspendingAction 40 | 41 | /** 42 | * Use any name you like for suspending actions, or use reducer 43 | */ 44 | typealias UseCase = Reducer -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/SampleScreen.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample 18 | 19 | import androidx.compose.foundation.layout.Arrangement 20 | import androidx.compose.foundation.layout.Box 21 | import androidx.compose.foundation.layout.Column 22 | import androidx.compose.foundation.layout.fillMaxSize 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.material3.Button 27 | import androidx.compose.material3.CircularProgressIndicator 28 | import androidx.compose.material3.Text 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.ui.Alignment 31 | import androidx.compose.ui.Modifier 32 | import androidx.compose.ui.text.style.TextAlign 33 | import androidx.compose.ui.unit.dp 34 | 35 | @Composable 36 | fun SampleScreen( 37 | modifier: Modifier = Modifier, 38 | text: String, 39 | isLoading: Boolean, 40 | onStartSampleClicked: () -> Unit 41 | ) { 42 | Column( 43 | modifier = modifier.fillMaxSize().padding(16.dp), 44 | horizontalAlignment = Alignment.CenterHorizontally, 45 | verticalArrangement = Arrangement.Center 46 | ) { 47 | Text( 48 | modifier = Modifier.fillMaxWidth(), 49 | textAlign = TextAlign.Center, 50 | text = text 51 | ) 52 | 53 | Box(modifier = Modifier.fillMaxWidth().height(60.dp), contentAlignment = Alignment.Center) { 54 | if (!isLoading) { 55 | Button(onClick = onStartSampleClicked) { 56 | Text("Start sample") 57 | } 58 | } 59 | 60 | if (isLoading) { 61 | CircularProgressIndicator() 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/StoreSampleActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample 18 | 19 | import android.os.Bundle 20 | import android.view.View 21 | import android.widget.Button 22 | import android.widget.ProgressBar 23 | import android.widget.TextView 24 | import android.widget.Toast 25 | import androidx.activity.compose.setContent 26 | import androidx.compose.foundation.layout.fillMaxSize 27 | import androidx.compose.foundation.layout.padding 28 | import androidx.compose.material3.Scaffold 29 | import androidx.compose.runtime.Composable 30 | import androidx.compose.runtime.LaunchedEffect 31 | import androidx.compose.runtime.collectAsState 32 | import androidx.compose.runtime.mutableStateOf 33 | import androidx.compose.runtime.remember 34 | import androidx.compose.ui.Modifier 35 | import kotlinx.coroutines.delay 36 | import kotlinx.coroutines.flow.map 37 | import kotlinx.coroutines.flow.onEach 38 | import kotlinx.coroutines.launch 39 | import mini.* 40 | import mini.android.FluxActivity 41 | import mini.android.sample.ui.theme.AppTheme 42 | 43 | private val dispatcher = Dispatcher() 44 | 45 | class MainStore : Store() { 46 | 47 | init { 48 | Mini.link(dispatcher, this).track() 49 | } 50 | 51 | @Reducer 52 | fun handleLoading(state: MainState, action: SetLoadingAction): MainState { 53 | return state.copy(loading = action.loading) 54 | } 55 | 56 | @Reducer 57 | fun handleSetTextAction(state: MainState, action: SetTextAction): MainState { 58 | return state.copy(text = action.text) 59 | } 60 | 61 | @Reducer 62 | fun handleAnalyticsAction(action: AnalyticsAction) { 63 | //Log to analytics 64 | } 65 | 66 | @Reducer 67 | fun handleAnyAction(action: Any) { 68 | //Log to analytics 69 | } 70 | 71 | @UseCase 72 | suspend fun useCase(action: LongUseCaseAction) { 73 | if (state.loading) return 74 | dispatcher.dispatch(SetLoadingAction(true)) 75 | dispatcher.dispatch(SetTextAction("Loading from network...")) 76 | delay(5000) 77 | dispatcher.dispatch(SetTextAction("Hello From UseCase")) 78 | dispatcher.dispatch(SetLoadingAction(false)) 79 | } 80 | } 81 | 82 | class StoreSampleActivity : FluxActivity() { 83 | private val mainStore: MainStore by lazy { 84 | MainStore() 85 | } 86 | 87 | override suspend fun whenCreated(savedInstanceState: Bundle?) { 88 | setContent { 89 | AppTheme { 90 | StoreSampleScreen() 91 | } 92 | } 93 | } 94 | 95 | @Composable 96 | private fun StoreSampleScreen() { 97 | val mainState = mainStore.flow().collectAsState(initial = MainState()) 98 | val showToastState = mainStore.flow(hotStart = false).map { !it.loading } 99 | .collectAsState(initial = false) 100 | 101 | LaunchedEffect(showToastState.value) { 102 | if (showToastState.value) { 103 | Toast.makeText( 104 | this@StoreSampleActivity, 105 | "Finished loading", 106 | Toast.LENGTH_LONG 107 | ).show() 108 | } 109 | } 110 | 111 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 112 | SampleScreen( 113 | modifier = Modifier.padding(innerPadding), 114 | text = mainState.value.toString(), 115 | isLoading = mainState.value.loading, 116 | onStartSampleClicked = { 117 | launch { 118 | dispatcher.dispatch(LongUseCaseAction("HyperDevs")) 119 | } 120 | } 121 | ) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/ViewModelSampleActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample 18 | 19 | import android.os.Bundle 20 | import android.view.View 21 | import android.widget.Button 22 | import android.widget.ProgressBar 23 | import android.widget.TextView 24 | import android.widget.Toast 25 | import androidx.activity.compose.setContent 26 | import androidx.activity.viewModels 27 | import androidx.compose.foundation.layout.fillMaxSize 28 | import androidx.compose.foundation.layout.padding 29 | import androidx.compose.material3.Scaffold 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.runtime.LaunchedEffect 32 | import androidx.compose.runtime.collectAsState 33 | import androidx.compose.ui.Modifier 34 | import androidx.lifecycle.SavedStateHandle 35 | import kotlinx.coroutines.* 36 | import kotlinx.coroutines.flow.map 37 | import kotlinx.coroutines.flow.onEach 38 | import mini.* 39 | import mini.android.FluxActivity 40 | import mini.android.FluxStoreViewModel 41 | import mini.android.sample.ui.theme.AppTheme 42 | 43 | private val dispatcher = Dispatcher() 44 | 45 | class MainViewModelReducer : NestedStateContainer() { 46 | 47 | @Reducer 48 | fun handleLoading(state: MainState, action: SetLoadingAction): MainState { 49 | return state.copy(loading = action.loading) 50 | } 51 | 52 | @Reducer 53 | fun handleSetTextAction(state: MainState, action: SetTextAction): MainState { 54 | return state.copy(text = action.text) 55 | } 56 | } 57 | 58 | class MainStoreViewModel(savedStateHandle: SavedStateHandle) : 59 | FluxStoreViewModel(savedStateHandle) { 60 | private val reducerSlice = MainViewModelReducer().apply { parent = this@MainStoreViewModel } 61 | 62 | init { 63 | Mini.link(dispatcher, listOf(this, reducerSlice)).track() 64 | } 65 | 66 | override fun saveState(state: MainState, handle: SavedStateHandle) { 67 | println("State saved") 68 | handle.set("state", state) 69 | } 70 | 71 | override fun restoreState(handle: SavedStateHandle): MainState? { 72 | val restored = handle.get("state") 73 | println("State restored $restored") 74 | return restored 75 | } 76 | 77 | @UseCase 78 | suspend fun useCase(action: LongUseCaseAction) { 79 | if (state.loading) return 80 | dispatcher.dispatch(SetLoadingAction(true)) 81 | delay(2000) 82 | dispatcher.dispatch(SetTextAction("${state.text.toInt() + 1}")) 83 | dispatcher.dispatch(SetLoadingAction(false)) 84 | } 85 | } 86 | 87 | class ViewModelSampleActivity : FluxActivity() { 88 | 89 | private val viewModel: MainStoreViewModel by viewModels() 90 | 91 | override suspend fun whenCreated(savedInstanceState: Bundle?) { 92 | setContent { 93 | AppTheme { 94 | ViewModelSampleScreen() 95 | } 96 | } 97 | } 98 | 99 | @Composable 100 | private fun ViewModelSampleScreen() { 101 | val mainState = viewModel.flow().collectAsState(initial = MainState()) 102 | val showToastState = viewModel.flow(hotStart = false).map { !it.loading } 103 | .collectAsState(initial = false) 104 | 105 | LaunchedEffect(showToastState.value) { 106 | if (showToastState.value) { 107 | Toast.makeText( 108 | this@ViewModelSampleActivity, 109 | "Finished loading", 110 | Toast.LENGTH_LONG 111 | ).show() 112 | } 113 | } 114 | 115 | Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 116 | SampleScreen( 117 | modifier = Modifier.padding(innerPadding), 118 | text = mainState.value.toString(), 119 | isLoading = mainState.value.loading, 120 | onStartSampleClicked = { 121 | launch { 122 | dispatcher.dispatch(LongUseCaseAction("HyperDevs")) 123 | } 124 | } 125 | ) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample.ui.theme 18 | 19 | import androidx.compose.ui.graphics.Color 20 | 21 | val primaryLight = Color(0xFF3C6939) 22 | val onPrimaryLight = Color(0xFFFFFFFF) 23 | val primaryContainerLight = Color(0xFFBDF0B4) 24 | val onPrimaryContainerLight = Color(0xFF393838) 25 | val secondaryLight = Color(0xFF53634F) 26 | val onSecondaryLight = Color(0xFFFFFFFF) 27 | val secondaryContainerLight = Color(0xFFD6E8CE) 28 | val onSecondaryContainerLight = Color(0xFF393838) 29 | val tertiaryLight = Color(0xFF49672E) 30 | val onTertiaryLight = Color(0xFFFFFFFF) 31 | val tertiaryContainerLight = Color(0xFFCAEEA6) 32 | val onTertiaryContainerLight = Color(0xFF393838) 33 | val errorLight = Color(0xFFBA1A1A) 34 | val onErrorLight = Color(0xFFFFFFFF) 35 | val errorContainerLight = Color(0xFFFFDAD6) 36 | val onErrorContainerLight = Color(0xFF410002) 37 | val backgroundLight = Color(0xFFF7FBF1) 38 | val onBackgroundLight = Color(0xFF191D17) 39 | val surfaceLight = Color(0xFFF7FBF1) 40 | val onSurfaceLight = Color(0xFF191D17) 41 | val surfaceVariantLight = Color(0xFFDEE5D8) 42 | val onSurfaceVariantLight = Color(0xFF424940) 43 | val outlineLight = Color(0xFF72796F) 44 | val outlineVariantLight = Color(0xFFC2C8BD) 45 | val scrimLight = Color(0xFF393838) 46 | val inverseSurfaceLight = Color(0xFF2D322B) 47 | val inverseOnSurfaceLight = Color(0xFFEFF2E9) 48 | val inversePrimaryLight = Color(0xFFA1D399) 49 | val surfaceDimLight = Color(0xFFD8DBD2) 50 | val surfaceBrightLight = Color(0xFFF7FBF1) 51 | val surfaceContainerLowestLight = Color(0xFFFFFFFF) 52 | val surfaceContainerLowLight = Color(0xFFF1F5EB) 53 | val surfaceContainerLight = Color(0xFFECEFE6) 54 | val surfaceContainerHighLight = Color(0xFFE6E9E0) 55 | val surfaceContainerHighestLight = Color(0xFFE0E4DB) 56 | 57 | val primaryDark = Color(0xFFA1D399) 58 | val onPrimaryDark = Color(0xFF0B390F) 59 | val primaryContainerDark = Color(0xFF245023) 60 | val onPrimaryContainerDark = Color(0xFFBDF0B4) 61 | val secondaryDark = Color(0xFFBACCB3) 62 | val onSecondaryDark = Color(0xFF253423) 63 | val secondaryContainerDark = Color(0xFF3B4B38) 64 | val onSecondaryContainerDark = Color(0xFFD6E8CE) 65 | val tertiaryDark = Color(0xFFAED18D) 66 | val onTertiaryDark = Color(0xFF1C3703) 67 | val tertiaryContainerDark = Color(0xFF324E18) 68 | val onTertiaryContainerDark = Color(0xFFCAEEA6) 69 | val errorDark = Color(0xFFFFB4AB) 70 | val onErrorDark = Color(0xFF690005) 71 | val errorContainerDark = Color(0xFF93000A) 72 | val onErrorContainerDark = Color(0xFFFFDAD6) 73 | val backgroundDark = Color(0xFF393838) 74 | val onBackgroundDark = Color(0xFFE0E4DB) 75 | val surfaceDark = Color(0xFF393838) 76 | val onSurfaceDark = Color(0xFFE0E4DB) 77 | val surfaceVariantDark = Color(0xFF424940) 78 | val onSurfaceVariantDark = Color(0xFFC2C8BD) 79 | val outlineDark = Color(0xFF8C9388) 80 | val outlineVariantDark = Color(0xFF424940) 81 | val scrimDark = Color(0xFF393838) 82 | val inverseSurfaceDark = Color(0xFFE0E4DB) 83 | val inverseOnSurfaceDark = Color(0xFF2D322B) 84 | val inversePrimaryDark = Color(0xFF3C6939) 85 | val surfaceDimDark = Color(0xFF10140F) 86 | val surfaceBrightDark = Color(0xFF363A34) 87 | val surfaceContainerLowestDark = Color(0xFF0B0F0A) 88 | val surfaceContainerLowDark = Color(0xFF191D17) 89 | val surfaceContainerDark = Color(0xFF1D211B) 90 | val surfaceContainerHighDark = Color(0xFF272B25) 91 | val surfaceContainerHighestDark = Color(0xFF323630) 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/src/main/kotlin/mini/android/sample/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.sample.ui.theme 18 | 19 | import androidx.compose.material3.Typography 20 | 21 | val AppTypography = Typography() 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 25 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | 27 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | #393838 22 | #393838 23 | #8BC34A 24 | @color/white 25 | 26 | #ffffff 27 | #7dffffff 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 12sp 23 | 14sp 24 | 16sp 25 | 20sp 26 | 24sp 27 | 28 | 29 | 4dp 30 | 8dp 31 | 12dp 32 | 16dp 33 | 20dp 34 | 24dp 35 | 36 | 37 | 4dp 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | Mini Sample 21 | Store Sample 22 | View Model Sample 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.android.application) apply false 19 | alias(libs.plugins.android.library) apply false 20 | 21 | alias(libs.plugins.kotlin.jvm) apply false 22 | alias(libs.plugins.kotlin.android) apply false 23 | alias(libs.plugins.kotlin.kapt) apply false 24 | alias(libs.plugins.kotlin.ksp) apply false 25 | 26 | alias(libs.plugins.versions) 27 | } 28 | 29 | allprojects { 30 | group = "com.hyperdevs.mini" 31 | } 32 | 33 | tasks { 34 | val clean by registering(Delete::class) { 35 | delete(rootProject.layout.buildDirectory.asFile.get()) 36 | } 37 | } -------------------------------------------------------------------------------- /convention-plugins/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /convention-plugins/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | `java-gradle-plugin` 19 | `kotlin-dsl` 20 | } 21 | 22 | dependencies { 23 | compileOnly(libs.android.gradle) 24 | implementation(libs.androidgitversion) 25 | } 26 | 27 | java { 28 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 29 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 30 | } 31 | 32 | tasks.withType().configureEach { 33 | kotlinOptions { 34 | jvmTarget = libs.versions.java.sdk.get() 35 | } 36 | } 37 | 38 | gradlePlugin { 39 | plugins { 40 | register("androidApp") { 41 | id = "mini.android.plugins.androidApp" 42 | implementationClass = "mini.android.plugins.AndroidAppConventionPlugin" 43 | } 44 | 45 | register("androidLib") { 46 | id = "mini.android.plugins.androidLib" 47 | implementationClass = "mini.android.plugins.AndroidLibConventionPlugin" 48 | } 49 | 50 | register("javaLib") { 51 | id = "mini.android.plugins.javaLib" 52 | implementationClass = "mini.android.plugins.JavaLibConventionPlugin" 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /convention-plugins/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | @file:Suppress("UnstableApiUsage") 18 | 19 | dependencyResolutionManagement { 20 | repositories { 21 | google() 22 | mavenCentral() 23 | gradlePluginPortal() 24 | } 25 | 26 | versionCatalogs { 27 | create("libs") { 28 | from(files("../gradle/libs.versions.toml")) 29 | } 30 | } 31 | } 32 | 33 | rootProject.name = "convention-plugins" -------------------------------------------------------------------------------- /convention-plugins/src/main/kotlin/mini/android/plugins/AndroidAppConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.plugins 18 | 19 | import mini.android.plugins.extensions.applyAndroidAppVersioning 20 | import mini.android.plugins.extensions.applyAndroidLibPublishing 21 | import mini.android.plugins.extensions.applyVersioning 22 | import org.gradle.api.Plugin 23 | import org.gradle.api.Project 24 | 25 | class AndroidAppConventionPlugin : Plugin { 26 | override fun apply(target: Project) { 27 | with(target) { 28 | val (versionName, versionCode) = applyVersioning() 29 | applyAndroidAppVersioning(versionName, versionCode) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /convention-plugins/src/main/kotlin/mini/android/plugins/AndroidLibConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.plugins 18 | 19 | import mini.android.plugins.extensions.applyAndroidLibPublishing 20 | import mini.android.plugins.extensions.applyVersioning 21 | import org.gradle.api.Plugin 22 | import org.gradle.api.Project 23 | 24 | class AndroidLibConventionPlugin : Plugin { 25 | override fun apply(target: Project) { 26 | with(target) { 27 | val (versionName, _) = applyVersioning() 28 | applyAndroidLibPublishing(versionName) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /convention-plugins/src/main/kotlin/mini/android/plugins/JavaLibConventionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.plugins 18 | 19 | import mini.android.plugins.extensions.applyJavaLibPublishing 20 | import mini.android.plugins.extensions.applyVersioning 21 | import org.gradle.api.Plugin 22 | import org.gradle.api.Project 23 | 24 | class JavaLibConventionPlugin : Plugin { 25 | override fun apply(target: Project) { 26 | with(target) { 27 | val (versionName, _) = applyVersioning() 28 | applyJavaLibPublishing(versionName) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /convention-plugins/src/main/kotlin/mini/android/plugins/extensions/MavenPomExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.plugins.extensions 18 | 19 | import org.gradle.api.publish.maven.internal.publication.DefaultMavenPom 20 | 21 | fun DefaultMavenPom.setMetadata(artifactName: String, artifactVersion: String) { 22 | name.set("Mini ($artifactName)") 23 | description.set("Mini is a minimal Flux architecture written in Kotlin that also adds a mix of useful features to build UIs fast.") 24 | url.set("https://github.com/hyperdevs-team/mini-kotlin/releases/tag/$artifactVersion") 25 | //version.set(miniVersion) 26 | inceptionYear.set("2017") 27 | 28 | licenses { 29 | license { 30 | name.set("The Apache License, Version 2.0") 31 | url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") 32 | } 33 | } 34 | 35 | organization { 36 | name.set("HyperDevs") 37 | url.set("https://github.com/hyperdevs-team") 38 | } 39 | 40 | issueManagement { 41 | system.set("GitHub Issues") 42 | url.set("https://github.com/hyperdevs-team/mini-kotlin/issues") 43 | } 44 | 45 | scm { 46 | connection.set("scm:git:git@github.com:hyperdevs-team/mini-kotlin.git") 47 | url.set("https://github.com/hyperdevs-team/mini-kotlin.git") 48 | } 49 | 50 | developers { 51 | developer { 52 | name.set("Estefanía Sarasola Elvira") 53 | id.set("yamidragut") 54 | url.set("https://github.com/yamidragut") 55 | roles.set(listOf("Maintainer")) 56 | } 57 | 58 | developer { 59 | name.set("Sara Lucía Pérez") 60 | id.set("Babelia13") 61 | url.set("https://github.com/Babelia13") 62 | roles.set(listOf("Maintainer")) 63 | } 64 | 65 | developer { 66 | name.set("Adrián García") 67 | id.set("adriangl") 68 | url.set("https://github.com/adriangl") 69 | roles.set(listOf("Maintainer")) 70 | } 71 | 72 | developer { 73 | name.set("Francisco García Sierra") 74 | id.set("FrangSierra") 75 | url.set("https://github.com/FrangSierra") 76 | roles.set(listOf("Maintainer", "Initial Work")) 77 | } 78 | 79 | developer { 80 | name.set("Pablo Orgaz") 81 | id.set("pabloogc") 82 | url.set("https://github.com/pabloogc") 83 | roles.set(listOf("Initial Work")) 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /convention-plugins/src/main/kotlin/mini/android/plugins/extensions/ProjectExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 mini.android.plugins.extensions 18 | 19 | import com.android.build.api.dsl.ApplicationExtension 20 | import com.android.build.gradle.LibraryExtension 21 | import com.gladed.androidgitversion.AndroidGitVersionExtension 22 | import org.gradle.api.Project 23 | import org.gradle.api.plugins.JavaPluginExtension 24 | import org.gradle.api.publish.PublishingExtension 25 | import org.gradle.api.publish.maven.MavenPublication 26 | import org.gradle.api.publish.maven.internal.publication.DefaultMavenPom 27 | import org.gradle.kotlin.dsl.configure 28 | 29 | data class Version(val name: String, val code: Int) 30 | 31 | internal fun Project.applyVersioning(): Version { 32 | pluginManager.apply("com.gladed.androidgitversion") 33 | project.extensions.configure { 34 | codeFormat = "MNNPP" 35 | baseCode = 1 36 | } 37 | 38 | return Version( 39 | project.extensions.getByType(AndroidGitVersionExtension::class.java).name(), 40 | project.extensions.getByType(AndroidGitVersionExtension::class.java).code() 41 | ) 42 | } 43 | 44 | internal fun Project.applyAndroidAppVersioning(versionName: String, versionCode: Int) { 45 | project.extensions.configure{ 46 | defaultConfig { 47 | this.versionName = versionName 48 | this.versionCode = versionCode 49 | } 50 | } 51 | } 52 | 53 | internal fun Project.applyAndroidLibPublishing(versionName: String) { 54 | val publishingName = "release" 55 | 56 | pluginManager.apply("maven-publish") 57 | 58 | project.extensions.configure { 59 | publishing { 60 | singleVariant(publishingName) { 61 | withSourcesJar() 62 | withJavadocJar() 63 | } 64 | } 65 | } 66 | 67 | project.afterEvaluate { 68 | project.extensions.configure { 69 | publications { 70 | create(publishingName, MavenPublication::class.java) { 71 | from(components.getByName(publishingName)) 72 | 73 | artifactId = project.name 74 | version = versionName 75 | 76 | (pom as DefaultMavenPom).setMetadata( 77 | project.name, 78 | versionName 79 | ) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | internal fun Project.applyJavaLibPublishing(versionName: String) { 87 | val publishingName = "maven" 88 | 89 | pluginManager.apply("java") 90 | pluginManager.apply("maven-publish") 91 | 92 | project.extensions.configure { 93 | withSourcesJar() 94 | withJavadocJar() 95 | } 96 | 97 | project.extensions.configure { 98 | publications { 99 | create(publishingName, MavenPublication::class.java) { 100 | from(components.getByName("java")) 101 | 102 | artifactId = project.name 103 | version = versionName 104 | 105 | (pom as DefaultMavenPom).setMetadata( 106 | project.name, 107 | versionName 108 | ) 109 | } 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 HyperDevs 3 | # 4 | # Copyright 2020 BQ 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | # Project-wide Gradle settings. 20 | # IDE (e.g. Android Studio) users: 21 | # Gradle settings configured through the IDE *will take* 22 | # any settings specified in this file. 23 | # For more details on how to fn your build environment visit 24 | # http://www.gradle.org/docs/current/userguide/build_environment.html 25 | # Specifies the JVM arguments used for the daemon process. 26 | # The setting is particularly useful for tweaking memory settings. 27 | org.gradle.jvmargs=-Xmx1536m 28 | android.useAndroidX=true 29 | android.enableJetifier=false 30 | # When configured, Gradle will run in incubating parallel mode. 31 | # This option should only be used with decoupled projects. More details, visit 32 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 33 | # org.gradle.parallel=true 34 | 35 | # Some performance improvements 36 | org.gradle.parallel=true 37 | org.gradle.configureondemand=true 38 | org.gradle.caching=true 39 | org.gradle.daemon=true 40 | kapt.incremental.apt=true 41 | kapt.use.worker.api=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyperdevs-team/mini-kotlin/ea00956349097f6ffdec7525f1c64dc48eab69b7/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 -------------------------------------------------------------------------------- /mini-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.android.library) 19 | alias(libs.plugins.kotlin.android) 20 | alias(libs.plugins.convention.androidLib) 21 | } 22 | 23 | android { 24 | namespace = "mini.android" 25 | 26 | compileSdk = libs.versions.android.compileSdk.get().toInt() 27 | buildToolsVersion = libs.versions.android.buildTools.get() 28 | 29 | defaultConfig { 30 | minSdk = libs.versions.android.minSdk.get().toInt() 31 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 32 | consumerProguardFiles("mini-android.pro") 33 | } 34 | 35 | buildTypes { 36 | release { 37 | isMinifyEnabled = false 38 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 39 | } 40 | } 41 | 42 | compileOptions { 43 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 44 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 45 | } 46 | } 47 | 48 | tasks.withType().configureEach { 49 | kotlinOptions { 50 | jvmTarget = libs.versions.java.sdk.get() 51 | } 52 | } 53 | 54 | java { 55 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 56 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 57 | } 58 | 59 | kotlin { 60 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 61 | } 62 | 63 | dependencies { 64 | api(project(":mini-common")) 65 | 66 | api(libs.kotlinx.coroutines.android) 67 | api(libs.androidx.appcompat) 68 | api(libs.androidx.activity) 69 | api(libs.androidx.fragment) 70 | 71 | api(libs.bundles.androidx.lifecycle) 72 | 73 | testImplementation(libs.junit) 74 | androidTestImplementation(libs.androidx.test.runner) 75 | androidTestImplementation(libs.espresso) 76 | } 77 | 78 | -------------------------------------------------------------------------------- /mini-android/mini-android.pro: -------------------------------------------------------------------------------- 1 | # ViewModel's empty constructor is considered to be unused by proguard, so keep it 2 | -keepclassmembers,allowobfuscation class * extends androidx.lifecycle.ViewModel { 3 | (); 4 | } 5 | -keepclassmembers,allowobfuscation class * extends androidx.lifecycle.AndroidViewModel { 6 | (android.app.Application); 7 | } -------------------------------------------------------------------------------- /mini-android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /mini-android/src/main/java/mini/android/FluxActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.android 18 | 19 | import android.os.Bundle 20 | import androidx.appcompat.app.AppCompatActivity 21 | import androidx.lifecycle.lifecycleScope 22 | import kotlinx.coroutines.CoroutineScope 23 | import kotlinx.coroutines.flow.Flow 24 | import kotlinx.coroutines.flow.launchIn 25 | import kotlinx.coroutines.launch 26 | import mini.CloseableTracker 27 | import mini.DefaultCloseableTracker 28 | import kotlin.coroutines.CoroutineContext 29 | 30 | abstract class FluxActivity : AppCompatActivity(), 31 | CloseableTracker by DefaultCloseableTracker(), 32 | CoroutineScope { 33 | 34 | override val coroutineContext: CoroutineContext 35 | get() = lifecycleScope.coroutineContext 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | lifecycleScope.launch { whenCreated(savedInstanceState) } 40 | } 41 | 42 | override fun onResume() { 43 | super.onResume() 44 | lifecycleScope.launch { whenResumed() } 45 | } 46 | 47 | override fun onPause() { 48 | super.onPause() 49 | lifecycleScope.launch { whenPaused() } 50 | } 51 | 52 | override fun onStop() { 53 | super.onStop() 54 | lifecycleScope.launch { whenStopped() } 55 | } 56 | 57 | override fun onDestroy() { 58 | lifecycleScope.launch { whenDestroyed() } 59 | close() 60 | super.onDestroy() 61 | } 62 | 63 | fun Flow.launchInLifecycleScope() { 64 | launchIn(lifecycleScope) 65 | } 66 | 67 | protected open suspend fun whenCreated(savedInstanceState: Bundle?) = Unit 68 | protected open suspend fun whenResumed() = Unit 69 | protected open suspend fun whenPaused() = Unit 70 | protected open suspend fun whenStopped() = Unit 71 | protected open suspend fun whenDestroyed() = Unit 72 | 73 | } -------------------------------------------------------------------------------- /mini-android/src/main/java/mini/android/FluxFragment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.android 18 | 19 | import android.os.Bundle 20 | import androidx.fragment.app.Fragment 21 | import androidx.lifecycle.lifecycleScope 22 | import kotlinx.coroutines.CoroutineScope 23 | import kotlinx.coroutines.flow.Flow 24 | import kotlinx.coroutines.flow.launchIn 25 | import kotlinx.coroutines.launch 26 | import mini.CloseableTracker 27 | import mini.DefaultCloseableTracker 28 | import kotlin.coroutines.CoroutineContext 29 | 30 | abstract class FluxFragment : Fragment(), 31 | CloseableTracker by DefaultCloseableTracker(), 32 | CoroutineScope { 33 | 34 | override val coroutineContext: CoroutineContext 35 | get() = lifecycleScope.coroutineContext 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | lifecycleScope.launch { whenCreated(savedInstanceState) } 40 | } 41 | 42 | override fun onResume() { 43 | super.onResume() 44 | lifecycleScope.launch { whenResumed() } 45 | } 46 | 47 | override fun onPause() { 48 | super.onPause() 49 | lifecycleScope.launch { whenPaused() } 50 | } 51 | 52 | override fun onStop() { 53 | super.onStop() 54 | lifecycleScope.launch { whenStopped() } 55 | } 56 | 57 | override fun onDestroy() { 58 | lifecycleScope.launch { whenDestroyed() } 59 | close() 60 | super.onDestroy() 61 | } 62 | 63 | fun Flow.launchOnUi() { 64 | launchIn(lifecycleScope) 65 | } 66 | 67 | protected open suspend fun whenCreated(savedInstanceState: Bundle?) = Unit 68 | protected open suspend fun whenResumed() = Unit 69 | protected open suspend fun whenPaused() = Unit 70 | protected open suspend fun whenStopped() = Unit 71 | protected open suspend fun whenDestroyed() = Unit 72 | } -------------------------------------------------------------------------------- /mini-android/src/main/java/mini/android/FluxStoreViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.android 18 | 19 | import androidx.lifecycle.SavedStateHandle 20 | import androidx.lifecycle.ViewModel 21 | import mini.CloseableTracker 22 | import mini.DefaultCloseableTracker 23 | import mini.State 24 | import mini.StateContainer 25 | import mini.assertOnUiThread 26 | import java.io.Closeable 27 | import java.util.concurrent.CopyOnWriteArrayList 28 | 29 | abstract class FluxStoreViewModel( 30 | val savedStateHandle: SavedStateHandle) : 31 | ViewModel(), 32 | StateContainer, 33 | CloseableTracker by DefaultCloseableTracker() { 34 | 35 | class ViewModelSubscription internal constructor(private val vm: FluxStoreViewModel<*>, 36 | private val fn: Any) : Closeable { 37 | override fun close() { 38 | vm.listeners.remove(fn) 39 | } 40 | } 41 | 42 | private var _state: Any? = StateContainer.Companion.NoState 43 | private val listeners = CopyOnWriteArrayList<(S) -> Unit>() 44 | 45 | override val state: S 46 | get() { 47 | if (_state === StateContainer.Companion.NoState) { 48 | synchronized(this) { 49 | if (_state === StateContainer.Companion.NoState) { 50 | _state = restoreState(savedStateHandle) ?: initialState() 51 | } 52 | } 53 | } 54 | @Suppress("UNCHECKED_CAST") 55 | return _state as S 56 | } 57 | 58 | override fun setState(newState: S) { 59 | assertOnUiThread() 60 | performStateChange(newState) 61 | } 62 | 63 | private fun performStateChange(newState: S) { 64 | if (_state != newState) { 65 | _state = newState 66 | saveState(newState, savedStateHandle) 67 | listeners.forEach { 68 | it(newState) 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Persist the state, no-op by default. 75 | * 76 | * ```handle.set("state", state)``` 77 | */ 78 | open fun saveState(state: S, handle: SavedStateHandle) { 79 | //No-op 80 | } 81 | 82 | /** 83 | * Restore the state from the [SavedStateHandle] or null if nothing was saved. 84 | * 85 | * ```handle.get("state")``` 86 | */ 87 | open fun restoreState(handle: SavedStateHandle): S? { 88 | return null 89 | } 90 | 91 | override fun subscribe(hotStart: Boolean, fn: (S) -> Unit): Closeable { 92 | listeners.add(fn) 93 | if (hotStart) fn(state) 94 | return ViewModelSubscription(this, fn) 95 | } 96 | 97 | override fun onCleared() { 98 | super.onCleared() 99 | close() 100 | } 101 | } -------------------------------------------------------------------------------- /mini-android/src/main/java/mini/android/FluxViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.android 20 | 21 | import androidx.annotation.CallSuper 22 | import androidx.lifecycle.ViewModel 23 | import mini.CloseableTracker 24 | import mini.DefaultCloseableTracker 25 | 26 | abstract class FluxViewModel : ViewModel(), 27 | CloseableTracker by DefaultCloseableTracker() { 28 | 29 | @CallSuper 30 | override fun onCleared() { 31 | super.onCleared() 32 | close() 33 | } 34 | } -------------------------------------------------------------------------------- /mini-common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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("idea") 19 | alias(libs.plugins.kotlin.jvm) 20 | alias(libs.plugins.convention.javaLib) 21 | } 22 | 23 | dependencies { 24 | implementation(libs.kotlin.stdlib) 25 | implementation(libs.kotlin.reflect) 26 | 27 | // Optional Rx and Android bindings, one day these should be modules, 28 | // for now we compile against them but let user package library 29 | compileOnly(libs.android.library) 30 | 31 | compileOnly(libs.kotlinx.coroutines.core) 32 | testImplementation(libs.kotlinx.coroutines.core) 33 | 34 | testImplementation(libs.junit) 35 | testImplementation(libs.kluent) 36 | } 37 | 38 | tasks.withType().configureEach { 39 | kotlinOptions { 40 | jvmTarget = libs.versions.java.sdk.get() 41 | } 42 | } 43 | 44 | java { 45 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 46 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 47 | } 48 | 49 | kotlin { 50 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 51 | } 52 | 53 | idea { 54 | module { 55 | val sourceFoldersToAdd = listOf( 56 | "build/generated/source/kapt/main", 57 | "build/generated/source/kaptKotlin/main", 58 | "build/generated/source/ksp/main" 59 | ).map { File(it) } 60 | 61 | sourceDirs.addAll(sourceFoldersToAdd) 62 | generatedSourceDirs.addAll(sourceFoldersToAdd) 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/AndroidInterop.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | /** 22 | * Check if running on android device / emulator or jvm 23 | */ 24 | internal val isAndroid by lazy { 25 | try { 26 | android.os.Build.VERSION.SDK_INT != 0 27 | } catch (e: Throwable) { 28 | false 29 | } 30 | } 31 | 32 | fun requireAndroid() { 33 | if (!isAndroid) { 34 | throw UnsupportedOperationException("This method can only be called from android environment") 35 | } 36 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/Annotations.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import java.lang.annotation.Inherited 22 | 23 | const val DEFAULT_PRIORITY = 100 24 | 25 | /** 26 | * Mark a type as action for code generation. All actions must include this annotation 27 | * or dispatcher won't work properly. 28 | */ 29 | @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS) 30 | @Retention(AnnotationRetention.RUNTIME) 31 | @Inherited 32 | annotation class Action 33 | 34 | /** 35 | * Mark a function declared in a [StateContainer] as a reducer function. 36 | * 37 | * Reducers function must have two parameters, the state that must have same time 38 | * as the [StateContainer] state, and the action being handled. 39 | * 40 | * If the reducer function is not pure, only the action parameter is allowed 41 | * and function should have no return. 42 | */ 43 | @Target(AnnotationTarget.FUNCTION) 44 | @Retention(AnnotationRetention.RUNTIME) 45 | annotation class Reducer(val priority: Int = DEFAULT_PRIORITY) 46 | 47 | -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/CloseableTracker.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import java.io.Closeable 22 | 23 | interface CloseableTracker : Closeable { 24 | /** 25 | * Start tracking a disposable. 26 | */ 27 | fun T.track(): T 28 | } 29 | 30 | class DefaultCloseableTracker : CloseableTracker { 31 | private val closeables = CompositeCloseable() 32 | override fun close() = closeables.close() 33 | override fun T.track(): T { 34 | closeables.add(this) 35 | return this 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/CompositeCloseable.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import java.io.Closeable 20 | 21 | /** 22 | * A collection of closeables. 23 | */ 24 | class CompositeCloseable : Closeable { 25 | private val items = ArrayList() 26 | 27 | override fun close() { 28 | synchronized(this) { 29 | items.forEach { it.close() } 30 | items.clear() 31 | } 32 | } 33 | 34 | fun add(closeable: T): T { 35 | synchronized(this) { 36 | items.add(closeable) 37 | } 38 | return closeable 39 | } 40 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/DiffAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import kotlin.reflect.KClass 20 | 21 | interface DiffAdapter { 22 | 23 | companion object { 24 | const val GENERATED_CLASS_SUFFIX = "DiffAdapter" 25 | } 26 | 27 | /** 28 | * Produce a json like diff of an object 29 | */ 30 | fun diff(a: T, b: T, indent: String = " "): String? 31 | } 32 | 33 | class ListDiffAdapter> : DiffAdapter { 34 | companion object { 35 | val INSTANCE = ListDiffAdapter>() 36 | } 37 | 38 | override fun diff(a: T, b: T, indent: String): String? { 39 | val sb = StringBuilder() 40 | val aSize = a.size 41 | val bSize = b.size 42 | val range = 0..(kotlin.math.max(aSize, bSize)) 43 | range.forEach { index -> 44 | val ai = a.getOrNull(index) 45 | val bi = b.getOrNull(index) 46 | val diff = diffObjects(ai, bi, indent) 47 | if (diff != null) { 48 | @Suppress("UNCHECKED_CAST") 49 | sb.append("\n$index: $diff") 50 | } 51 | } 52 | return "[${sb.toString().prependIndent(indent)}\n]" 53 | } 54 | } 55 | 56 | @Suppress("UNCHECKED_CAST") 57 | class MapDiffAdapter> : DiffAdapter { 58 | 59 | companion object { 60 | val INSTANCE = MapDiffAdapter>() 61 | } 62 | 63 | override fun diff(a: T, b: T, indent: String): String? { 64 | val sb = StringBuilder() 65 | b.forEach { item -> 66 | val newValue = item.value 67 | val oldValue = a[item.key] 68 | if (oldValue != null) { 69 | val itemDiff = diffObjects(oldValue, newValue, indent) 70 | if (itemDiff != null) { 71 | sb.append("\n~ ${item.key}: ${item.value}") 72 | } 73 | } else { 74 | sb.append("\n+ ${item.key}: ${item.value}") 75 | } 76 | } 77 | //Check for removed keys 78 | a.entries.forEach { entry -> 79 | if (!b.containsKey(entry.key)) { 80 | sb.append("\n- ${entry.key}: ${entry.value}") 81 | } 82 | } 83 | return "{${sb.toString().prependIndent(indent)}\n}" 84 | } 85 | } 86 | 87 | class DirectDiffAdapter : DiffAdapter { 88 | override fun diff(a: T, b: T, indent: String): String? { 89 | return "$a -> $b" 90 | } 91 | } 92 | 93 | @Suppress("UNCHECKED_CAST") 94 | fun diffObjects(a: T?, b: T?, indent: String = " "): String? { 95 | if (a === b) return null //Same instance 96 | if (a == b) return null //Equal 97 | //One is null, other is not 98 | if (a != null && b == null) return "${a.toQuotedString()} -> null" 99 | if (a == null && b != null) return "null -> ${b.toQuotedString()}" 100 | a as Any 101 | b as Any 102 | return when (a) { 103 | is String, is Number -> "${a.toQuotedString()} -> ${b.toQuotedString()}" 104 | is List<*> -> ListDiffAdapter.INSTANCE.diff(a, b as List<*>, indent) 105 | is Map<*, *> -> MapDiffAdapter.INSTANCE.diff(a, b as Map<*, *>, indent) 106 | else -> { 107 | @Suppress("USELESS_CAST") 108 | val adapter: DiffAdapter = findDiffAdapter((a as Any)::class) as DiffAdapter 109 | adapter.diff(a, b as Any, indent) 110 | } 111 | } 112 | } 113 | 114 | private val adapterCache = HashMap>() 115 | private fun findDiffAdapter(kclass: KClass): DiffAdapter { 116 | val adapterClassName = kclass.qualifiedName + DiffAdapter.GENERATED_CLASS_SUFFIX 117 | @Suppress("UNCHECKED_CAST") 118 | return adapterCache.getOrPut(adapterClassName) { 119 | try { 120 | val adapter = Class.forName(adapterClassName) 121 | adapter.getDeclaredConstructor().newInstance() as DiffAdapter<*> 122 | } catch (e: Throwable) { 123 | DirectDiffAdapter() 124 | } 125 | } as DiffAdapter 126 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/LoggerMiddleware.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import android.util.Log 22 | import java.util.concurrent.atomic.AtomicInteger 23 | 24 | /** Actions implementing this interface won't log anything, including nested calls */ 25 | interface SilentAction 26 | 27 | /** 28 | * Actions implementing this interface will log nested actions visually since they will 29 | * most likely dispatch other actions. 30 | */ 31 | interface SuspendingAction 32 | 33 | internal fun extractClassName(clazz: Class<*>): String { 34 | return clazz.name.substringAfterLast(".") 35 | } 36 | 37 | /** 38 | * Action logging for stores. 39 | */ 40 | class LoggerMiddleware(stores: Collection>, 41 | private val tag: String = "MiniLog", 42 | private val diffFunction: ((a: Any?, b: Any?) -> String)? = null, 43 | private val logger: (priority: Int, tag: String, msg: String) -> Unit) : Middleware { 44 | 45 | private var actionCounter = AtomicInteger(0) 46 | 47 | private val stores = stores.toList() 48 | 49 | override suspend fun intercept(action: Any, chain: Chain): Any { 50 | if (action is SilentAction) return chain.proceed(action) //Do nothing 51 | 52 | val isSuspending = action is SuspendingAction 53 | val beforeStates: Array = Array(stores.size) { } 54 | val afterStates: Array = Array(stores.size) { } 55 | val actionName = extractClassName(action.javaClass) 56 | 57 | if (!isSuspending) { 58 | stores.forEachIndexed { idx, store -> beforeStates[idx] = store.state } 59 | } 60 | 61 | val (upCorner, downCorner) = if (isSuspending) { 62 | "╔═════ " to "╚════> " 63 | } else { 64 | "┌── " to "└─> " 65 | } 66 | 67 | val prelude = "[${"${actionCounter.getAndIncrement() % 100}".padStart(2, '0')}] " 68 | 69 | logger(Log.DEBUG, tag, "$prelude$upCorner$actionName") 70 | logger(Log.DEBUG, tag, "$prelude$action") 71 | 72 | //Pass it down 73 | val start = System.nanoTime() 74 | val outAction = chain.proceed(action) 75 | val processTime = (System.nanoTime() - start) / 1000000 76 | 77 | if (!isSuspending) { 78 | stores.forEachIndexed { idx, store -> afterStates[idx] = store.state } 79 | 80 | for (i in beforeStates.indices) { 81 | val oldState = beforeStates[i] 82 | val newState = afterStates[i] 83 | if (oldState !== newState) { 84 | val line = "$prelude│ ${stores[i].javaClass.name}" 85 | logger(Log.VERBOSE, tag, "$line: $newState") 86 | diffFunction?.invoke(oldState, newState)?.let { diff -> 87 | logger(Log.DEBUG, tag, "$line: $diff") 88 | } 89 | } 90 | } 91 | } 92 | 93 | logger(Log.DEBUG, tag, "$prelude$downCorner$actionName ${processTime}ms") 94 | 95 | return outAction 96 | } 97 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/Middleware.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | /** 22 | * Middleware that will be called for every dispatch to modify the 23 | * action or perform side effects like logging. 24 | * 25 | * Call chain.proceed(action) with the new action or dispatcher chain will be broken. 26 | */ 27 | interface Middleware { 28 | suspend fun intercept(action: Any, chain: Chain): Any 29 | } 30 | 31 | /** 32 | * A chain of interceptors. Call [proceed] with 33 | * the intercepted action or directly handle it. 34 | */ 35 | interface Chain { 36 | suspend fun proceed(action: Any): Any 37 | } 38 | -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/Mini.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import java.io.Closeable 22 | import kotlin.reflect.KClass 23 | 24 | const val DISPATCHER_FACTORY_CLASS_NAME = "mini.codegen.Mini_Generated" 25 | 26 | abstract class Mini { 27 | 28 | companion object { 29 | 30 | private val miniInstance: Mini by lazy { 31 | try { 32 | Class.forName(DISPATCHER_FACTORY_CLASS_NAME).getField("INSTANCE").get(null) as Mini 33 | } catch (ex: Throwable) { 34 | throw ClassNotFoundException("Failed to load generated class $DISPATCHER_FACTORY_CLASS_NAME, " + 35 | "most likely the annotation processor did not run, add it as dependency to the project", ex) 36 | } 37 | } 38 | 39 | /** 40 | * Generate all subscriptions from @[Reducer] annotated methods and bundle 41 | * into a single Closeable. 42 | */ 43 | fun link(dispatcher: Dispatcher, container: StateContainer<*>): Closeable { 44 | ensureDispatcherInitialized(dispatcher) 45 | return miniInstance.subscribe(dispatcher, container) 46 | } 47 | 48 | /** 49 | * Generate all subscriptions from @[Reducer] annotated methods and bundle 50 | * into a single Closeable. 51 | */ 52 | fun link(dispatcher: Dispatcher, containers: Iterable>): Closeable { 53 | ensureDispatcherInitialized(dispatcher) 54 | return miniInstance.subscribe(dispatcher, containers) 55 | } 56 | 57 | private fun ensureDispatcherInitialized(dispatcher: Dispatcher) { 58 | if (dispatcher.actionTypeMap.isEmpty()) { 59 | dispatcher.actionTypeMap = miniInstance.actionTypes 60 | } 61 | } 62 | 63 | } 64 | 65 | /** 66 | * All the types an action can be subscribed as. 67 | */ 68 | abstract val actionTypes: Map, List>> 69 | 70 | /** 71 | * Link all [Reducer] functions present in the store to the dispatcher. 72 | */ 73 | protected abstract fun subscribe(dispatcher: Dispatcher, 74 | container: StateContainer): Closeable 75 | 76 | /** 77 | * Link all [Reducer] functions present in the store to the dispatcher. 78 | */ 79 | protected fun subscribe(dispatcher: Dispatcher, containers: Iterable>): Closeable { 80 | val c = CompositeCloseable() 81 | containers.forEach { container -> 82 | c.add(subscribe(dispatcher, container)) 83 | } 84 | return c 85 | } 86 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/Misc.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | fun Any?.toQuotedString(): String = 22 | if (this is String) "\"$this\"" 23 | else this.toString() -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/NestedStateContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import java.io.Closeable 20 | 21 | /** 22 | * Utility class to allow splitting [StateContainer] into chunks so not all reducers live in the same 23 | * file. 24 | * 25 | * From a state container 26 | * 27 | * ``` 28 | * class Reducer : NestedStateContainer() { 29 | * @Reducer 30 | * fun reduceOneAction(...) 31 | * } 32 | * 33 | * class MyStore { 34 | * val reducer = Reducer(this) 35 | * 36 | * init { 37 | * Mini.link(dispatcher, listOf(this, reducer)) 38 | * } 39 | * 40 | * @Reducer 41 | * fun globalReduceFn(...) 42 | * } 43 | * ``` 44 | */ 45 | abstract class NestedStateContainer( 46 | var parent: StateContainer? = null 47 | ) : StateContainer { 48 | override val state: S 49 | get() = parent!!.state 50 | 51 | override fun setState(newState: S) { 52 | parent!!.setState(newState) 53 | } 54 | 55 | override fun subscribe(hotStart: Boolean, fn: (S) -> Unit): Closeable { 56 | return parent!!.subscribe(hotStart, fn) 57 | } 58 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/State.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 HyperDevs 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 mini 18 | 19 | /** 20 | * Class that represents a state in the app. 21 | * 22 | * All state objects need to implement this interface. 23 | */ 24 | interface State { 25 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/StateContainer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import org.jetbrains.annotations.TestOnly 20 | import java.io.Closeable 21 | import java.lang.reflect.ParameterizedType 22 | 23 | /** 24 | * Common interface for state containers. 25 | */ 26 | interface StateContainer { 27 | 28 | companion object { 29 | /** 30 | * Token to mark a state as not initialized. 31 | */ 32 | object NoState : State 33 | } 34 | 35 | val state: S 36 | 37 | fun setState(newState: S) 38 | 39 | /** 40 | * Register a observer to state changes. 41 | * 42 | * @return [Closeable] to cancel the subscription. 43 | */ 44 | fun subscribe(hotStart: Boolean = true, fn: (S) -> Unit): Closeable 45 | 46 | /** 47 | * The initial state of the container. By default it will invoke the primary constructor 48 | * of the State type parameter. If this constructor is not accessible provide your own 49 | * implementation of this method. 50 | */ 51 | @Suppress("UNCHECKED_CAST") 52 | fun initialState(): S { 53 | val type = (javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0] 54 | as Class 55 | try { 56 | val constructor = type.getDeclaredConstructor() 57 | constructor.isAccessible = true 58 | return constructor.newInstance() 59 | } catch (e: Exception) { 60 | throw RuntimeException("Missing default no-args constructor for the state $type", e) 61 | } 62 | } 63 | 64 | /** 65 | * Test only method, don't use in app code. 66 | * Will force state change on UI so it can be called from 67 | * espresso thread. 68 | */ 69 | @TestOnly 70 | fun setTestState(s: S) { 71 | if (isAndroid) { 72 | onUiSync { 73 | setState(s) 74 | } 75 | } else { 76 | setState(s) 77 | } 78 | } 79 | 80 | /** 81 | * Test only method, don't use in app code. 82 | * Will force state change on UI to the initial state. 83 | */ 84 | @TestOnly 85 | fun resetState() { 86 | if (isAndroid) { 87 | onUiSync { 88 | setState(initialState()) 89 | } 90 | } else { 91 | setState(initialState()) 92 | } 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/Store.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import java.io.Closeable 22 | import java.util.concurrent.CopyOnWriteArrayList 23 | 24 | /** 25 | * Basic state holder. 26 | */ 27 | abstract class Store : Closeable, 28 | StateContainer, 29 | CloseableTracker by DefaultCloseableTracker() { 30 | 31 | class StoreSubscription internal constructor( 32 | private val store: Store, 33 | private val fn: Any 34 | ) : Closeable { 35 | override fun close() { 36 | store.listeners.remove(fn) 37 | } 38 | } 39 | 40 | private var _state: Any? = StateContainer.Companion.NoState 41 | private val listeners = CopyOnWriteArrayList<(S) -> Unit>() 42 | 43 | /** 44 | * Initialize the store after dependency injection is complete. 45 | */ 46 | open fun initialize() { 47 | //No-op 48 | } 49 | 50 | /** 51 | * Set new state and notify listeners, only callable from the main thread. 52 | */ 53 | override fun setState(newState: S) { 54 | assertOnUiThread() 55 | performStateChange(newState) 56 | } 57 | 58 | override fun subscribe(hotStart: Boolean, fn: (S) -> Unit): Closeable { 59 | listeners.add(fn) 60 | if (hotStart) fn(state) 61 | return StoreSubscription(this, fn) 62 | } 63 | 64 | override val state: S 65 | get() { 66 | if (_state === StateContainer.Companion.NoState) { 67 | synchronized(this) { 68 | if (_state === StateContainer.Companion.NoState) { 69 | _state = initialState() 70 | } 71 | } 72 | } 73 | @Suppress("UNCHECKED_CAST") 74 | return _state as S 75 | } 76 | 77 | private fun performStateChange(newState: S) { 78 | //State mutation should to happen on UI thread 79 | if (_state != newState) { 80 | _state = newState 81 | listeners.forEach { 82 | it(newState) 83 | } 84 | } 85 | } 86 | 87 | override fun close() { 88 | listeners.clear() //Remove all listeners 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/StoreFlow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import kotlinx.coroutines.channels.Channel 20 | import kotlinx.coroutines.flow.* 21 | 22 | /** 23 | * Map multiple objects into a list to run an effect on any change. 24 | */ 25 | fun Flow.selectMany(vararg mappers: suspend (T) -> R): Flow> { 26 | return this.map { state -> 27 | mappers.map { fn -> fn(state) } 28 | }.distinctUntilChanged() 29 | } 30 | 31 | /** 32 | * Combination of [Flow.map] and [Flow.distinctUntilChanged]. 33 | */ 34 | fun Flow.select(mapper: suspend (T) -> R): Flow { 35 | return this.map { mapper(it) } 36 | .distinctUntilChanged() 37 | } 38 | 39 | /** 40 | * Combination of [Flow.map] and [Flow.distinctUntilChanged] ignoring null values. 41 | */ 42 | fun Flow.selectNotNull(mapper: suspend (T) -> R?): Flow { 43 | return this.map { mapper(it) } 44 | .filterNotNull() 45 | .distinctUntilChanged() 46 | } 47 | 48 | /** 49 | * Emit a value when the filter passes comparing the last emited value and current value. 50 | */ 51 | fun Flow.onEachChange(filter: (prev: T, next: T) -> Boolean, fn: (T) -> Unit): Flow { 52 | return distinctUntilChanged().runningReduce { prev, next -> 53 | if (filter(prev, next)) { 54 | fn(next) 55 | } 56 | next 57 | } 58 | } 59 | 60 | /** 61 | * Emit a value when the value goes from `from` to `to`. 62 | */ 63 | fun Flow.onEachChange(from: T, to: T, fn: (T) -> Unit): Flow { 64 | return onEachChange({ prev, next -> prev == from && next == to }, fn) 65 | } 66 | 67 | /** 68 | * Emit when the value goes from `true` to `false` (it disables). 69 | */ 70 | fun Flow.onEachDisable(fn: (Boolean) -> Unit): Flow { 71 | return onEachChange(from = true, to = false, fn) 72 | } 73 | 74 | /** 75 | * Emit when the value goes from `false` to `true` (it enables). 76 | */ 77 | fun Flow.onEachEnable(fn: (Boolean) -> Unit): Flow { 78 | return onEachChange(from = false, to = true, fn) 79 | } 80 | 81 | /** 82 | * Return the channel that will emit state changes. 83 | * 84 | * @param hotStart emit current state when starting. 85 | */ 86 | fun StateContainer.channel(hotStart: Boolean = true, 87 | capacity: Int = Channel.BUFFERED): Channel { 88 | val channel = Channel(capacity) 89 | val subscription = subscribe(hotStart) { 90 | channel.trySend(it) 91 | } 92 | @Suppress("EXPERIMENTAL_API_USAGE") 93 | channel.invokeOnClose { 94 | subscription.close() 95 | } 96 | return channel 97 | } 98 | 99 | /** 100 | * Return the flow that will emit state changes. 101 | * 102 | * @param hotStart emit current state when starting. 103 | */ 104 | fun StateContainer.flow(hotStart: Boolean = true, 105 | capacity: Int = Channel.BUFFERED): Flow { 106 | return channel(hotStart = hotStart, capacity = capacity).receiveAsFlow() 107 | } 108 | 109 | /** 110 | * Returns a flow that contains the elements of the given flow until the element that matches 111 | * the [predicate]. 112 | * 113 | * It behaves the same way as RxJava's takeUntil. 114 | */ 115 | fun Flow.takeUntil(predicate: suspend (T) -> Boolean): Flow = 116 | transformWhile { emit(it); !predicate(it) } 117 | 118 | class StateMerger { 119 | val containersAndMappers = ArrayList, () -> T>>() 120 | 121 | /** Add a new store + mapper to the flowable. */ 122 | inline fun , U : State> merge(stateContainer: S, 123 | crossinline mapper: (U.() -> T)) { 124 | containersAndMappers.add(stateContainer to { stateContainer.state.mapper() }) 125 | } 126 | } 127 | 128 | inline fun mergeStates(hotStart: Boolean = true, 129 | crossinline builder: StateMerger.() -> Unit): Flow> { 130 | return StateMerger().apply { builder() }.flow(hotStart) 131 | } 132 | 133 | /** Build the StateMerger into the final flowable. */ 134 | @Suppress("UNCHECKED_CAST") 135 | fun StateMerger.flow(hotStart: Boolean = true) : Flow> { 136 | return containersAndMappers 137 | .map { (stateContainer, fn) -> 138 | (stateContainer as StateContainer).flow(hotStart).select { fn() } 139 | } 140 | .reduce { acc, flow -> merge(acc, flow) } 141 | .map { containersAndMappers.map { (_, fn) -> fn() }.toList() } 142 | } -------------------------------------------------------------------------------- /mini-common/src/main/java/mini/Threading.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import android.os.Handler 20 | import android.os.Looper 21 | import java.util.concurrent.Semaphore 22 | 23 | val uiHandler by lazy { 24 | requireAndroid() 25 | Handler(Looper.getMainLooper()) 26 | } 27 | 28 | fun assertOnUiThread() { 29 | if (!isAndroid) return 30 | if (Looper.myLooper() != Looper.getMainLooper()) { 31 | error("This method can only be called from the main application thread") 32 | } 33 | } 34 | 35 | fun assertOnBgThread() { 36 | if (!isAndroid) return 37 | if (Looper.myLooper() == Looper.getMainLooper()) { 38 | error("This method can only be called from non UI threads") 39 | } 40 | } 41 | 42 | @JvmOverloads 43 | inline fun onUi(delayMs: Long = 0, crossinline block: () -> Unit) { 44 | requireAndroid() 45 | if (delayMs > 0) uiHandler.postDelayed({ block() }, delayMs) 46 | else uiHandler.post { block() } 47 | } 48 | 49 | inline fun onUiSync(crossinline block: () -> T) { 50 | uiHandler.postSync(block) 51 | } 52 | 53 | inline fun Handler.postSync(crossinline block: () -> T) { 54 | requireAndroid() 55 | if (Looper.myLooper() == this.looper) { 56 | block() 57 | } else { 58 | val sem = Semaphore(0) 59 | post { 60 | block() 61 | sem.release() 62 | } 63 | sem.acquireUninterruptibly() 64 | } 65 | } -------------------------------------------------------------------------------- /mini-common/src/main/resources/META-INF/proguard/mini-common.pro: -------------------------------------------------------------------------------- 1 | -keep class mini.codegen.** { *; } 2 | 3 | -keepnames class * extends mini.Store { *; } 4 | -keepnames class * extends mini.State { *; } 5 | -keepnames @mini.Action class * { *; } 6 | 7 | -keep class mini.Action 8 | -keep @mini.Action class * { *; } 9 | 10 | -keep class mini.Resource { *; } 11 | 12 | -keep class mini.State { *; } 13 | -keep class * implements mini.State { *; } 14 | 15 | -keep class mini.StateContainer { *; } 16 | -keep class * implements mini.StateContainer { *; } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/CompositeCloseableTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import org.amshove.kluent.`should be equal to` 22 | import org.junit.Test 23 | import java.io.Closeable 24 | 25 | class CompositeCloseableTest { 26 | 27 | @Test 28 | fun itemsAreClosed() { 29 | val c = CompositeCloseable() 30 | val dummyCloseable = DummyCloseable() 31 | c.add(dummyCloseable) 32 | c.close() 33 | c.close() 34 | 35 | dummyCloseable.closed.`should be equal to`(1) 36 | } 37 | 38 | class DummyCloseable : Closeable { 39 | var closed = 0 40 | override fun close() { 41 | closed++ 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/DispatcherTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import org.amshove.kluent.`should be equal to` 22 | import org.junit.Test 23 | 24 | class DispatcherTest { 25 | 26 | @Test 27 | fun `subscriptions are added`() { 28 | val dispatcher = newTestDispatcher() 29 | var called = 0 30 | dispatcher.subscribe { 31 | called++ 32 | } 33 | dispatcher.dispatchBlocking(TestAction()) 34 | called `should be equal to` 1 35 | } 36 | 37 | @Test 38 | fun `order is respected for same priority`() { 39 | val dispatcher = newTestDispatcher() 40 | val calls = ArrayList() 41 | dispatcher.subscribe { 42 | calls.add(0) 43 | } 44 | dispatcher.subscribe { 45 | calls.add(1) 46 | } 47 | dispatcher.dispatchBlocking(TestAction()) 48 | calls[0] `should be equal to` 0 49 | calls[1] `should be equal to` 1 50 | } 51 | 52 | @Test 53 | fun `order is respected for different priority`() { 54 | val dispatcher = newTestDispatcher() 55 | val calls = ArrayList() 56 | dispatcher.subscribe(priority = 10) { 57 | calls.add(0) 58 | } 59 | dispatcher.subscribe(priority = 0) { 60 | calls.add(1) 61 | } 62 | dispatcher.dispatchBlocking(TestAction()) 63 | calls[0] `should be equal to` 1 64 | calls[1] `should be equal to` 0 65 | } 66 | 67 | @Test 68 | fun `disposing registration removes subscription`() { 69 | val dispatcher = newTestDispatcher() 70 | var called = 0 71 | dispatcher.subscribe { 72 | called++ 73 | }.close() 74 | dispatcher.dispatchBlocking(TestAction()) 75 | called `should be equal to` 0 76 | } 77 | 78 | @Test 79 | fun `interceptors are called`() { 80 | val dispatcher = newTestDispatcher() 81 | var called = 0 82 | val interceptor = object : Middleware { 83 | override suspend fun intercept(action: Any, chain: Chain): Any { 84 | called++ 85 | return chain.proceed(action) 86 | } 87 | } 88 | dispatcher.addMiddleware(interceptor) 89 | dispatcher.dispatchBlocking(TestAction()) 90 | called `should be equal to` 1 91 | } 92 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/LoggerMiddlewareTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import org.amshove.kluent.`should not be empty` 20 | import org.junit.Test 21 | 22 | class LoggerMiddlewareTest { 23 | 24 | @Test 25 | fun `logs are printed`() { 26 | val store = SampleStore() 27 | val dispatcher = newTestDispatcher() 28 | dispatcher.subscribe { 29 | store.setState(SampleState("Action sent")) 30 | } 31 | 32 | val out = StringBuilder() 33 | dispatcher.addMiddleware(LoggerMiddleware(listOf(store), 34 | logger = { priority, tag, msg -> 35 | println("[$priority][$tag] $msg") 36 | out.append(priority).append(tag).append(msg) 37 | })) 38 | dispatcher.dispatchBlocking(TestAction()) 39 | out.toString().`should not be empty`() 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/ResourceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import org.amshove.kluent.`should be equal to` 22 | import org.amshove.kluent.`should be null` 23 | import org.amshove.kluent.`should equal` 24 | import org.amshove.kluent.`should not be null` 25 | import org.junit.Before 26 | import org.junit.Test 27 | 28 | class ResourceTest { 29 | 30 | private var successValue: Any? = null 31 | private var errorValue: Any? = null 32 | private var loadingValue: Any? = null 33 | private var emptyValue: Any? = null 34 | 35 | @Before 36 | fun before() { 37 | successValue = null 38 | errorValue = null 39 | loadingValue = null 40 | emptyValue = null 41 | } 42 | 43 | private fun check(resource: Resource) { 44 | var called = 0 45 | resource 46 | .onSuccess { 47 | called++ 48 | successValue = it 49 | }.onFailure { 50 | called++ 51 | errorValue = it 52 | }.onLoading { 53 | called++ 54 | loadingValue = it 55 | }.onEmpty { 56 | called++ 57 | emptyValue = true 58 | } 59 | called `should be equal to` 1 60 | } 61 | 62 | @Test 63 | fun `success calls`() { 64 | check(Resource.success("abc")) 65 | successValue `should equal` "abc" 66 | } 67 | 68 | @Test 69 | fun isEmpty() { 70 | check(Resource.empty()) 71 | emptyValue `should equal` true 72 | } 73 | 74 | @Test 75 | fun isFailure() { 76 | val ex = RuntimeException("ABC") 77 | check(Resource.failure(ex)) 78 | errorValue `should equal` ex 79 | } 80 | 81 | @Test 82 | fun isLoading() { 83 | check(Resource.loading("abc")) 84 | loadingValue `should equal` "abc" 85 | } 86 | 87 | @Test 88 | fun getOrNull() { 89 | Resource.empty().getOrNull().`should be null`() 90 | Resource.success("abc").getOrNull().`should not be null`() 91 | } 92 | 93 | @Test 94 | fun exceptionOrNull() { 95 | Resource.failure(RuntimeException()).exceptionOrNull().`should not be null`() 96 | Resource.success("abc").exceptionOrNull().`should be null`() 97 | } 98 | 99 | @Test 100 | fun map() { 101 | Resource.success("abc") 102 | .map { 0 } 103 | .getOrNull()?.`should be equal to`(0) 104 | 105 | Resource.failure() 106 | .map { 0 } 107 | .getOrNull()?.`should be null`() 108 | } 109 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/SampleStore.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | data class SampleState(val value: String): State 22 | 23 | class SampleStore : Store() { 24 | 25 | companion object { 26 | const val INITIAL_STATE = "initial" 27 | } 28 | 29 | override fun initialState(): SampleState = SampleState(INITIAL_STATE) 30 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/StoreFlowTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini 18 | 19 | import kotlinx.coroutines.* 20 | import kotlinx.coroutines.flow.launchIn 21 | import kotlinx.coroutines.flow.onEach 22 | import kotlinx.coroutines.flow.take 23 | import mini.SampleStore.Companion.INITIAL_STATE 24 | import org.amshove.kluent.`should be equal to` 25 | import org.amshove.kluent.`should equal` 26 | import org.junit.Test 27 | import java.util.concurrent.Executors 28 | 29 | class StoreFlowTest { 30 | 31 | private val testScope = 32 | CoroutineScope(Executors.newScheduledThreadPool(1).asCoroutineDispatcher()) 33 | 34 | @Test(timeout = 1000) 35 | fun `flow sends initial state on collection`(): Unit = runBlocking { 36 | val store = SampleStore() 37 | var observedState = SampleState(INITIAL_STATE) 38 | 39 | val job = store.flow(hotStart = false) 40 | .onEach { observedState = it } 41 | .take(1) 42 | .launchIn(testScope) 43 | 44 | store.setState(SampleState("abc")) //Set before collect 45 | 46 | job.join() 47 | observedState `should be equal to` SampleState("abc") 48 | Unit 49 | } 50 | 51 | @Test(timeout = 1000) 52 | fun `flow sends updates to all`(): Unit = runBlocking { 53 | val store = SampleStore() 54 | val called = intArrayOf(0, 0) 55 | 56 | val job1 = store.flow() 57 | .onEach { called[0]++ } 58 | .take(2) 59 | .launchIn(testScope) 60 | 61 | val job2 = store.flow() 62 | .onEach { called[1]++ } 63 | .take(2) 64 | .launchIn(testScope) 65 | 66 | store.setState(SampleState("abc")) 67 | 68 | job1.join() 69 | job2.join() 70 | 71 | //Called two times, one for initial state, one for updated stated 72 | called.`should be equal to`(intArrayOf(2, 2)) 73 | Unit 74 | } 75 | 76 | @Test(timeout = 1000) 77 | fun `channel sends updates`(): Unit = runBlocking { 78 | val store = SampleStore() 79 | var observedState = SampleState("") 80 | val scope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) 81 | val job = scope.launch { 82 | observedState = store.channel().receive() 83 | } 84 | store.setState(SampleState("abc")) 85 | job.join() 86 | observedState `should be equal to` SampleState("abc") 87 | Unit 88 | } 89 | 90 | @Test(timeout = 1000) 91 | fun `flow closes`(): Unit = runBlocking { 92 | val store = SampleStore() 93 | var observedState = store.state 94 | 95 | val scope = CoroutineScope(Job()) 96 | store.flow() 97 | .onEach { 98 | observedState = it 99 | } 100 | .launchIn(scope) 101 | 102 | scope.cancel() //Cancel the scope 103 | store.setState(SampleState("abc")) 104 | 105 | observedState `should be equal to` SampleState(INITIAL_STATE) 106 | Unit 107 | } 108 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/StoreTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import org.amshove.kluent.`should be equal to` 22 | import org.junit.Test 23 | 24 | class StoreTest { 25 | 26 | @Test 27 | fun `state is updated`() { 28 | val store = SampleStore() 29 | store.setState(SampleState("abc")) 30 | store.state `should be equal to` SampleState("abc") 31 | } 32 | 33 | @Test 34 | fun `observers are called`() { 35 | val store = SampleStore() 36 | var state = SampleState("") 37 | store.subscribe { 38 | state = it 39 | } 40 | store.setState(SampleState("abc")) 41 | state `should be equal to` SampleState("abc") 42 | } 43 | 44 | @Test 45 | fun `initial state is sent on subscribe`() { 46 | val store = SampleStore() 47 | var state = SampleState("initial") 48 | store.subscribe { 49 | state = it 50 | } 51 | state `should be equal to` SampleState("initial") 52 | } 53 | 54 | @Test 55 | fun `observers are removed on close`() { 56 | val store = SampleStore() 57 | var state = SampleState("") 58 | val closeable = store.subscribe(hotStart = false) { 59 | state = it 60 | } 61 | closeable.close() 62 | store.setState(SampleState("abc")) 63 | state `should be equal to` SampleState("") 64 | } 65 | } -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/TestAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | @Action 22 | data class TestAction(val value: String = "dummy") -------------------------------------------------------------------------------- /mini-common/src/test/kotlin/mini/TestDispatcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini 20 | 21 | import kotlin.reflect.KClass 22 | import kotlin.reflect.jvm.jvmErasure 23 | 24 | fun newTestDispatcher(): Dispatcher { 25 | return Dispatcher().apply { 26 | actionTypeMap = newReflectiveMap() 27 | } 28 | } 29 | 30 | private fun reflectActionTypes(type: KClass<*>, depth: Int = 0): List { 31 | return type.supertypes 32 | .asSequence() 33 | .map { (it.jvmErasure.java as Class<*>).kotlin } 34 | .map { reflectActionTypes(it, depth + 1) } 35 | .flatten() 36 | .plus(ReflectedType(type, depth)) 37 | .toList() 38 | } 39 | 40 | private class ReflectedType(val clazz: KClass<*>, val depth: Int) 41 | 42 | private fun newReflectiveMap(): Map, List>> { 43 | return object : Map, List>> { 44 | private val genericTypes = listOf(Object::class) 45 | private val map = HashMap, List>>() 46 | override val entries: Set, List>>> = map.entries 47 | override val keys: Set> = map.keys 48 | override val size: Int = map.size 49 | override val values: Collection>> = map.values 50 | override fun containsKey(key: KClass<*>): Boolean = map.containsKey(key) 51 | override fun containsValue(value: List>): Boolean = map.containsValue(value) 52 | override fun isEmpty(): Boolean = map.isEmpty() 53 | override fun get(key: KClass<*>): List> { 54 | return map.getOrPut(key) { 55 | reflectActionTypes(key) 56 | .asSequence() 57 | .sortedBy { it.depth } 58 | .map { it.clazz } 59 | .filter { it !in genericTypes } 60 | .toList() 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /mini-kodein-android-compose/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /mini-kodein-android-compose/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.android.library) 19 | alias(libs.plugins.kotlin.android) 20 | alias(libs.plugins.convention.androidLib) 21 | } 22 | 23 | android { 24 | namespace = "mini.kodein.android.compose" 25 | 26 | compileSdk = libs.versions.android.compileSdk.get().toInt() 27 | buildToolsVersion = libs.versions.android.buildTools.get() 28 | 29 | defaultConfig { 30 | minSdk = libs.versions.android.minSdk.get().toInt() 31 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 32 | consumerProguardFiles("mini-kodein-android-compose.pro") 33 | } 34 | 35 | buildTypes { 36 | release { 37 | isMinifyEnabled = false 38 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 39 | } 40 | } 41 | 42 | compileOptions { 43 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 44 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 45 | } 46 | } 47 | 48 | tasks.withType().configureEach { 49 | kotlinOptions { 50 | jvmTarget = libs.versions.java.sdk.get() 51 | } 52 | } 53 | 54 | java { 55 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 56 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 57 | } 58 | 59 | kotlin { 60 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 61 | } 62 | 63 | dependencies { 64 | api(project(":mini-kodein-android")) 65 | 66 | api(libs.compose.navigation) 67 | 68 | testImplementation(libs.junit) 69 | androidTestImplementation(libs.androidx.test.runner) 70 | androidTestImplementation(libs.espresso) 71 | } 72 | 73 | -------------------------------------------------------------------------------- /mini-kodein-android-compose/mini-kodein-android-compose.pro: -------------------------------------------------------------------------------- 1 | # Needed for injection 2 | -keep class * extends androidx.lifecycle.ViewModel { *; } -------------------------------------------------------------------------------- /mini-kodein-android-compose/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /mini-kodein-android-compose/src/main/java/mini/kodein/android/compose/KodeinAndroidComposeUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.kodein.android.compose 18 | 19 | import androidx.annotation.MainThread 20 | import androidx.lifecycle.ViewModel 21 | import androidx.lifecycle.ViewModelProvider 22 | import androidx.navigation.NavBackStackEntry 23 | import androidx.navigation.NavController 24 | import mini.kodein.android.TypedViewModel 25 | import org.kodein.di.DI 26 | import org.kodein.di.DIAware 27 | import org.kodein.di.direct 28 | import org.kodein.di.instance 29 | 30 | /** 31 | * Injects a [ViewModel] scoped to the lifecycle of the [NavBackStackEntry]. 32 | * As [NavBackStackEntry] is a final class, can not implement [DIAware] so the [DI] is needed as 33 | * a param. 34 | */ 35 | @MainThread 36 | inline fun NavBackStackEntry.viewModel(di: DI): Lazy { 37 | return lazy { 38 | ViewModelProvider(this, di.direct.instance()).get(VM::class.java) 39 | } 40 | } 41 | 42 | /** 43 | * Injects a [TypedViewModel] scoped to the lifecycle of the [NavBackStackEntry]. 44 | * As [NavBackStackEntry] is a final class, can not implement [DIAware] so the [DI] is needed as 45 | * a param. 46 | * 47 | * Requires previous ViewModelProvider.Factory injection for the ViewModel via bindViewModelFactory 48 | * to work and a TypedViewModel to be used. 49 | */ 50 | @MainThread 51 | inline fun > NavBackStackEntry.viewModel(di: DI, params: T): Lazy { 52 | return lazy { 53 | ViewModelProvider(this, di.direct.instance(VM::class.java, params)).get(VM::class.java) 54 | } 55 | } 56 | 57 | /** 58 | * Injects a [ViewModel] scoped to the lifecycle of the [NavBackStackEntry] of the navigation 59 | * route given in [navigationRoute]. 60 | * This allows to retrieve the same [ViewModel] from a previous route to which you have navigated in 61 | * the navigation graph instead of having a different [ViewModel] instance of the same [ViewModel] 62 | * for each navigation composable. It isn't needed that is from a nested navigation graph, only 63 | * that you have navigated previously to that route so we can find it in the [backStackEntry]. 64 | * As [NavBackStackEntry] is a final class, can not implement [DIAware] so the [DI] is needed as 65 | * a param. 66 | */ 67 | @MainThread 68 | inline fun NavController.sharedViewModelFromRoute(di: DI, navigationRoute: String): Lazy { 69 | return lazy { 70 | val parentBackStackEntry = getBackStackEntry(navigationRoute) 71 | ViewModelProvider(parentBackStackEntry, di.direct.instance()).get(VM::class.java) 72 | } 73 | } 74 | 75 | /** 76 | * Injects a [TypedViewModel] scoped to the lifecycle of the [NavBackStackEntry] of the navigation 77 | * route given in [navigationRoute]. 78 | * This allows to retrieve the same [ViewModel] from a previous route to which you have navigated in 79 | * the navigation graph instead of having a different [ViewModel] instance of the same [ViewModel] 80 | * for each navigation composable. It isn't needed that is from a nested navigation graph, only 81 | * that you have navigated previously to that route so we can find it in the [backStackEntry]. 82 | * As [NavBackStackEntry] is a final class, can not implement [DIAware] so the [DI] is needed as 83 | * a param. 84 | * 85 | * Requires previous ViewModelProvider.Factory injection for the ViewModel via bindViewModelFactory 86 | * to work and a TypedViewModel to be used. 87 | */ 88 | @MainThread 89 | inline fun > NavController.sharedViewModelFromRoute(di: DI, 90 | navigationRoute: String, 91 | params: T): Lazy { 92 | return lazy { 93 | val parentBackStackEntry = getBackStackEntry(navigationRoute) 94 | ViewModelProvider(parentBackStackEntry, di.direct.instance(VM::class.java, params)).get(VM::class.java) 95 | } 96 | } -------------------------------------------------------------------------------- /mini-kodein-android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-kodein-android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.convention.androidLib) 5 | } 6 | 7 | android { 8 | namespace = "mini.kodein.android" 9 | 10 | compileSdk = libs.versions.android.compileSdk.get().toInt() 11 | buildToolsVersion = libs.versions.android.buildTools.get() 12 | 13 | defaultConfig { 14 | minSdk = libs.versions.android.minSdk.get().toInt() 15 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles("mini-kodein-android.pro") 17 | } 18 | 19 | buildTypes { 20 | release { 21 | isMinifyEnabled = false 22 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 23 | } 24 | } 25 | 26 | compileOptions { 27 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 28 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 29 | } 30 | } 31 | 32 | tasks.withType().configureEach { 33 | kotlinOptions { 34 | jvmTarget = libs.versions.java.sdk.get() 35 | } 36 | } 37 | 38 | java { 39 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 40 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 41 | } 42 | 43 | kotlin { 44 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 45 | } 46 | 47 | dependencies { 48 | api(project(":mini-kodein")) 49 | 50 | implementation(libs.kotlin.stdlib) 51 | implementation(libs.kotlin.reflect) 52 | 53 | api(libs.androidx.fragment) 54 | api(libs.androidx.lifecycle.viewmodel) 55 | api(libs.kodein.framework.androidx) 56 | 57 | testImplementation(libs.junit) 58 | androidTestImplementation(libs.androidx.test.runner) 59 | androidTestImplementation(libs.espresso) 60 | } 61 | 62 | -------------------------------------------------------------------------------- /mini-kodein-android/mini-kodein-android.pro: -------------------------------------------------------------------------------- 1 | # Needed for injection 2 | -keep class * extends androidx.lifecycle.ViewModel { *; } -------------------------------------------------------------------------------- /mini-kodein-android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /mini-kodein-android/src/main/java/mini/kodein/android/FluxTypedViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.kodein.android 18 | 19 | import androidx.annotation.CallSuper 20 | import mini.CloseableTracker 21 | import mini.DefaultCloseableTracker 22 | 23 | abstract class FluxTypedViewModel(params: T) : TypedViewModel(params), 24 | CloseableTracker by DefaultCloseableTracker() { 25 | 26 | @CallSuper 27 | override fun onCleared() { 28 | super.onCleared() 29 | close() 30 | } 31 | } -------------------------------------------------------------------------------- /mini-kodein-android/src/main/java/mini/kodein/android/TypedViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.kodein.android 18 | 19 | import androidx.lifecycle.ViewModel 20 | 21 | /** 22 | * Generic [ViewModel] that adds support for adding a single [params] object to ease parameter 23 | * injection. 24 | */ 25 | open class TypedViewModel(private val params: T) : ViewModel() -------------------------------------------------------------------------------- /mini-kodein-android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | mini-kodein-android 21 | 22 | -------------------------------------------------------------------------------- /mini-kodein/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-kodein/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.kotlin.jvm) 19 | alias(libs.plugins.convention.javaLib) 20 | } 21 | 22 | tasks.withType().configureEach { 23 | kotlinOptions { 24 | jvmTarget = libs.versions.java.sdk.get() 25 | } 26 | } 27 | 28 | java { 29 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 30 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 31 | } 32 | 33 | kotlin { 34 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 35 | } 36 | 37 | dependencies { 38 | api(project(":mini-common")) 39 | implementation(libs.kotlin.stdlib) 40 | implementation(libs.kotlin.reflect) 41 | 42 | api(libs.kodein.jvm) 43 | } -------------------------------------------------------------------------------- /mini-kodein/src/main/java/mini/kodein/KodeinUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.kodein 20 | 21 | import mini.Store 22 | import org.kodein.di.* 23 | import org.kodein.di.bindings.NoArgBindingDI 24 | 25 | /** 26 | * Work based on: https://proandroiddev.com/android-viewmodel-dependency-injection-with-kodein-249f80f083c9 27 | */ 28 | 29 | /** 30 | * Binds a store in a Kodein module, assuming that it's a singleton dependency. 31 | */ 32 | inline fun > DI.Builder.bindStore(noinline creator: NoArgBindingDI<*>.() -> T) { 33 | bind() with singleton(creator = creator) 34 | bind>().inSet() with singleton { instance() } 35 | } -------------------------------------------------------------------------------- /mini-kodein/src/main/resources/META-INF/proguard/mini-kodein.pro: -------------------------------------------------------------------------------- 1 | -keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference 2 | -keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest 3 | -keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference 4 | -keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest -------------------------------------------------------------------------------- /mini-processor-test/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-processor-test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.kotlin.jvm) 19 | alias(libs.plugins.kotlin.kapt) 20 | } 21 | 22 | dependencies { 23 | implementation(project(":mini-common")) 24 | kapt(project(":mini-processor")) 25 | 26 | implementation(libs.kotlinx.coroutines.core) 27 | 28 | testImplementation(libs.junit) 29 | testImplementation(libs.kluent) 30 | } 31 | 32 | tasks.withType().configureEach { 33 | kotlinOptions { 34 | jvmTarget = libs.versions.java.sdk.get() 35 | } 36 | } 37 | 38 | java { 39 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 40 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 41 | } 42 | 43 | kotlin { 44 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 45 | } 46 | -------------------------------------------------------------------------------- /mini-processor-test/src/main/java/mini/test/AnyAction.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.test 20 | 21 | import mini.Action 22 | 23 | @Action 24 | data class AnyAction(val value: String) -------------------------------------------------------------------------------- /mini-processor-test/src/main/java/mini/test/BasicState.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.test 18 | 19 | import mini.State 20 | 21 | data class BasicState(val value: String = "initial") : State -------------------------------------------------------------------------------- /mini-processor-test/src/main/java/mini/test/ReducersStore.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.test 18 | 19 | import kotlinx.coroutines.yield 20 | import mini.Reducer 21 | import mini.Store 22 | 23 | class ReducersStore : Store() { 24 | 25 | companion object { 26 | @Reducer 27 | fun staticImpureReducer(action: AnyAction) { 28 | 29 | } 30 | 31 | @Reducer 32 | suspend fun staticSuspendingImpureReducer(action: AnyAction) { 33 | yield() 34 | } 35 | 36 | @Reducer 37 | fun staticPureReducer(state: BasicState, action: AnyAction): BasicState { 38 | return state.copy(value = action.value) 39 | } 40 | 41 | @Reducer 42 | suspend fun staticSuspendingPureReducer(state: BasicState, action: AnyAction): BasicState { 43 | yield() 44 | return state.copy(value = action.value) 45 | } 46 | } 47 | 48 | @Reducer 49 | fun impureReducer(action: AnyAction) { 50 | 51 | } 52 | 53 | @Reducer 54 | suspend fun impureSuspendingReducer(action: AnyAction) { 55 | yield() 56 | } 57 | 58 | @Reducer 59 | fun pureReducer(state: BasicState, action: AnyAction): BasicState { 60 | return state.copy(value = action.value) 61 | } 62 | 63 | @Reducer 64 | suspend fun pureSuspendingReducer(state: BasicState, action: AnyAction): BasicState { 65 | yield() 66 | return state.copy(value = action.value) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mini-processor-test/src/test/java/mini/test/ReducersStoreTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.test 18 | 19 | import kotlinx.coroutines.runBlocking 20 | import mini.Dispatcher 21 | import mini.Mini 22 | import org.amshove.kluent.`should equal` 23 | import org.junit.Test 24 | 25 | internal class ReducersStoreTest { 26 | 27 | private val store = ReducersStore() 28 | private val dispatcher = Dispatcher().apply { 29 | Mini.link(this, listOf(store)) 30 | } 31 | 32 | @Test 33 | fun `pure reducers are called`() { 34 | runBlocking { 35 | dispatcher.dispatch(AnyAction("changed")) 36 | store.state.value.`should equal`("changed") 37 | } 38 | } 39 | 40 | @Test 41 | fun `pure static reducers are called`() { 42 | runBlocking { 43 | dispatcher.dispatch(AnyAction("changed")) 44 | store.state.value.`should equal`("changed") 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /mini-processor/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-processor/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.kotlin.jvm) 19 | alias(libs.plugins.kotlin.kapt) 20 | alias(libs.plugins.convention.javaLib) 21 | } 22 | 23 | dependencies { 24 | api(project(":mini-common")) 25 | implementation(libs.kotlin.stdlib) 26 | implementation(libs.bundles.kotlinpoet) 27 | 28 | // Lib to add incremental annotation processing 29 | compileOnly(libs.incap) 30 | kapt(libs.incap.processor) 31 | 32 | testImplementation(libs.junit) 33 | 34 | implementation(libs.google.ksp.symbolprocessing) 35 | } 36 | 37 | tasks.withType().configureEach { 38 | kotlinOptions { 39 | jvmTarget = libs.versions.java.sdk.get() 40 | } 41 | } 42 | 43 | java { 44 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 45 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 46 | } 47 | 48 | kotlin { 49 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 50 | } 51 | 52 | 53 | -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/ContainerBuilders.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common 18 | 19 | import com.squareup.kotlinpoet.ClassName 20 | import com.squareup.kotlinpoet.FileSpec 21 | import com.squareup.kotlinpoet.TypeSpec 22 | import mini.DISPATCHER_FACTORY_CLASS_NAME 23 | import mini.Mini 24 | 25 | data class ContainerBuilders( 26 | val fileSpecBuilder: FileSpec.Builder, 27 | val typeSpecBuilder: TypeSpec.Builder 28 | ) 29 | 30 | fun getContainerBuilders(): ContainerBuilders { 31 | val containerClassName = ClassName.bestGuess(DISPATCHER_FACTORY_CLASS_NAME) 32 | val containerFile = 33 | FileSpec.builder(containerClassName.packageName, containerClassName.simpleName) 34 | val container = TypeSpec.objectBuilder(containerClassName) 35 | .addKdoc("Automatically generated, do not edit.\n") 36 | .superclass(Mini::class) 37 | return ContainerBuilders(containerFile, container) 38 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/ModelGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common 18 | 19 | interface ModelGeneratorDelegate { 20 | fun provideModels(): List 21 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/ProcessorException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common 18 | 19 | class ProcessorException : IllegalStateException() -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/actions/ActionModels.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common.actions 18 | 19 | import com.squareup.kotlinpoet.CodeBlock 20 | import com.squareup.kotlinpoet.TypeName 21 | 22 | interface ActionModel { 23 | fun listOfSupertypesCodeBlock(): CodeBlock 24 | val typeName: TypeName 25 | } 26 | 27 | class ActionSuperType(val typeName: TypeName, val depth: Int) { 28 | override fun equals(other: Any?): Boolean { 29 | if (this === other) return true 30 | if (javaClass != other?.javaClass) return false 31 | other as ActionSuperType 32 | if (typeName != other.typeName) return false 33 | return true 34 | } 35 | 36 | override fun hashCode(): Int { 37 | return typeName.hashCode() 38 | } 39 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/actions/ActionTypesGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common.actions 18 | 19 | import com.squareup.kotlinpoet.* 20 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 21 | import mini.Mini 22 | import kotlin.reflect.KClass 23 | 24 | class ActionTypesGenerator(private val delegate: ActionTypesGeneratorDelegate) { 25 | fun generate(container: TypeSpec.Builder) { 26 | val actionModels = delegate.provideModels() 27 | container.apply { 28 | val anyClassTypeName = KClass::class.asTypeName().parameterizedBy(STAR) 29 | val listTypeName = List::class.asTypeName().parameterizedBy(anyClassTypeName) 30 | val mapType = Map::class 31 | .asClassName() 32 | .parameterizedBy(anyClassTypeName, listTypeName) 33 | 34 | val prop = PropertySpec.builder(Mini::actionTypes.name, mapType) 35 | .addModifiers(KModifier.OVERRIDE) 36 | .initializer( 37 | CodeBlock.builder() 38 | .add("mapOf(\n⇥") 39 | .apply { 40 | actionModels.forEach { actionModel -> 41 | val comma = if (actionModel != actionModels.last()) "," else "" 42 | add("«") 43 | add("%T::class to ", actionModel.typeName) 44 | add(actionModel.listOfSupertypesCodeBlock()) 45 | add(comma) 46 | add("\n»") 47 | } 48 | } 49 | .add("⇤)") 50 | .build()) 51 | addProperty(prop.build()) 52 | }.build() 53 | } 54 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/actions/ActionTypesGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common.actions 18 | 19 | import mini.processor.common.ModelGeneratorDelegate 20 | 21 | interface ActionTypesGeneratorDelegate : ModelGeneratorDelegate -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/reducers/ReducersGenerator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common.reducers 18 | 19 | import com.squareup.kotlinpoet.* 20 | import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy 21 | import mini.CompositeCloseable 22 | import mini.Dispatcher 23 | import mini.StateContainer 24 | import java.io.Closeable 25 | 26 | class ReducersGenerator(private val delegate: ReducersGeneratorDelegate) { 27 | fun generate(container: TypeSpec.Builder) { 28 | val reducers = delegate.provideModels().groupBy { it.container.typeName } 29 | 30 | val whenBlock = CodeBlock.builder() 31 | .addStatement("val c = %T()", CompositeCloseable::class) 32 | .addStatement("when (container) {").indent() 33 | .apply { 34 | reducers.forEach { (containerName, reducerFunctions) -> 35 | addStatement("is %T -> {", containerName).indent() 36 | reducerFunctions.forEach { function -> 37 | add( 38 | "c.add(dispatcher.subscribe<%T>(priority=%L) { action -> ", 39 | function.actionTypeName, 40 | function.priority 41 | ) 42 | add(function.generateCallBlock("container", "action")) 43 | addStatement("})") 44 | } 45 | unindent().addStatement("}") 46 | } 47 | } 48 | .unindent() 49 | .addStatement("}") //Close when 50 | .addStatement("return c") 51 | .build() 52 | 53 | val typeParam = TypeVariableName("T", ClassName("mini", "State")) 54 | val oneParam = StateContainer::class.asTypeName().parameterizedBy(typeParam) 55 | 56 | val registerOneFn = FunSpec.builder("subscribe") 57 | .addModifiers(KModifier.OVERRIDE) 58 | .addTypeVariable(typeParam) 59 | .addParameter("dispatcher", Dispatcher::class) 60 | .addParameter("container", oneParam) 61 | .returns(Closeable::class) 62 | .addCode(whenBlock) 63 | .build() 64 | 65 | container.addFunction(registerOneFn) 66 | } 67 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/reducers/ReducersGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common.reducers 18 | 19 | import mini.processor.common.ModelGeneratorDelegate 20 | 21 | interface ReducersGeneratorDelegate : ModelGeneratorDelegate 22 | -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/common/reducers/ReducersModels.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.common.reducers 18 | 19 | import com.squareup.kotlinpoet.CodeBlock 20 | import com.squareup.kotlinpoet.TypeName 21 | 22 | interface ReducerModel { 23 | val isPure: Boolean 24 | val isSuspending: Boolean 25 | 26 | val container: ContainerModel 27 | val priority: Int 28 | 29 | val actionTypeName: TypeName 30 | val returnTypeName: TypeName 31 | 32 | fun generateCallBlock(containerParam: String, actionParam: String): CodeBlock 33 | } 34 | 35 | 36 | interface ContainerModel { 37 | val typeName: TypeName 38 | val stateTypeName: TypeName 39 | val isStatic: Boolean 40 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/kapt/MiniAnnotationProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 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 mini.processor.kapt; 18 | 19 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; 20 | import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; 21 | 22 | import java.util.Set; 23 | 24 | import javax.annotation.processing.AbstractProcessor; 25 | import javax.annotation.processing.ProcessingEnvironment; 26 | import javax.annotation.processing.RoundEnvironment; 27 | import javax.annotation.processing.SupportedOptions; 28 | import javax.lang.model.SourceVersion; 29 | import javax.lang.model.element.TypeElement; 30 | 31 | /** 32 | * Dummy Java wrapper that delegates to Kotlin one 33 | */ 34 | @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.AGGREGATING) 35 | @SupportedOptions("kapt.kotlin.generated") 36 | public class MiniAnnotationProcessor extends AbstractProcessor { 37 | 38 | private final Processor processor = new Processor(); 39 | 40 | @Override 41 | public synchronized void init(ProcessingEnvironment processingEnvironment) { 42 | super.init(processingEnvironment); 43 | processor.init(processingEnvironment); 44 | } 45 | 46 | @Override 47 | public Set getSupportedAnnotationTypes() { 48 | return processor.getSupportedAnnotationTypes(); 49 | } 50 | 51 | @Override 52 | public SourceVersion getSupportedSourceVersion() { 53 | return processor.getSupportedSourceVersion(); 54 | } 55 | 56 | @Override 57 | public boolean process(Set set, RoundEnvironment roundEnvironment) { 58 | return processor.process(roundEnvironment); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/kapt/Processor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.processor.kapt 20 | 21 | import mini.Action 22 | import mini.Reducer 23 | import mini.processor.common.ProcessorException 24 | import mini.processor.common.actions.ActionTypesGenerator 25 | import mini.processor.common.getContainerBuilders 26 | import mini.processor.common.reducers.ReducersGenerator 27 | import mini.processor.kapt.actions.KaptActionTypesGeneratorDelegate 28 | import mini.processor.kapt.reducers.KaptReducersGeneratorDelegate 29 | import javax.annotation.processing.ProcessingEnvironment 30 | import javax.annotation.processing.RoundEnvironment 31 | import javax.lang.model.SourceVersion 32 | 33 | class Processor { 34 | 35 | val supportedAnnotationTypes: MutableSet = mutableSetOf( 36 | Reducer::class.java, Action::class.java 37 | ) 38 | .map { it.canonicalName }.toMutableSet() 39 | val supportedSourceVersion: SourceVersion = SourceVersion.RELEASE_8 40 | 41 | fun init(environment: ProcessingEnvironment) { 42 | env = environment 43 | typeUtils = env.typeUtils 44 | elementUtils = env.elementUtils 45 | } 46 | 47 | fun process(roundEnv: RoundEnvironment): Boolean { 48 | 49 | val roundActions = roundEnv.getElementsAnnotatedWith(Action::class.java) 50 | val roundReducers = roundEnv.getElementsAnnotatedWith(Reducer::class.java) 51 | 52 | if (roundActions.isEmpty()) return false 53 | 54 | val (containerFile, container) = getContainerBuilders() 55 | 56 | try { 57 | ActionTypesGenerator(KaptActionTypesGeneratorDelegate(roundActions)).generate(container) 58 | ReducersGenerator(KaptReducersGeneratorDelegate(roundReducers)).generate(container) 59 | } catch (e: Throwable) { 60 | if (e !is ProcessorException) { 61 | kaptLogError( 62 | "Compiler crashed, open an issue please!\n" + 63 | " ${e.stackTraceString()}" 64 | ) 65 | } 66 | } 67 | 68 | containerFile 69 | .addType(container.build()) 70 | .build() 71 | .writeToFile(sourceElements = ((roundActions + roundReducers).toTypedArray())) 72 | 73 | return true 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/kapt/ProcessorUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.processor.kapt 20 | 21 | import com.squareup.kotlinpoet.ClassName 22 | import com.squareup.kotlinpoet.FileSpec 23 | import com.squareup.kotlinpoet.TypeName 24 | import com.squareup.kotlinpoet.asTypeName 25 | import mini.processor.common.ProcessorException 26 | import java.io.ByteArrayOutputStream 27 | import java.io.PrintStream 28 | import javax.annotation.processing.ProcessingEnvironment 29 | import javax.lang.model.element.Element 30 | import javax.lang.model.element.ExecutableElement 31 | import javax.lang.model.type.TypeMirror 32 | import javax.lang.model.util.Elements 33 | import javax.lang.model.util.Types 34 | import javax.tools.Diagnostic 35 | import javax.tools.StandardLocation 36 | 37 | lateinit var env: ProcessingEnvironment 38 | lateinit var elementUtils: Elements 39 | lateinit var typeUtils: Types 40 | 41 | fun Throwable.stackTraceString(): String { 42 | val out = ByteArrayOutputStream() 43 | printStackTrace(PrintStream(out)) 44 | return out.toString() 45 | } 46 | 47 | fun ExecutableElement.isSuspending(): Boolean { 48 | return parameters.last().asType().toString().startsWith("kotlin.coroutines.Continuation") 49 | } 50 | 51 | fun TypeMirror.getAllSuperTypes(depth: Int = 0): Set { 52 | //We want to sort them by depth 53 | val superTypes = typeUtils.directSupertypes(this).toSet() 54 | .map { it.getAllSuperTypes(depth + 1) } 55 | .flatten() 56 | return setOf(this) + superTypes 57 | } 58 | 59 | fun kaptCompilePrecondition( 60 | check: Boolean, 61 | message: String, 62 | element: Element? = null 63 | ) { 64 | if (!check) { 65 | kaptLogError(message, element) 66 | throw ProcessorException() 67 | } 68 | } 69 | 70 | fun kaptLogError(message: String, element: Element? = null) { 71 | kaptLogMessage(Diagnostic.Kind.ERROR, message, element) 72 | } 73 | 74 | fun kaptWarning(message: String, element: Element? = null) { 75 | kaptLogMessage(Diagnostic.Kind.MANDATORY_WARNING, message, element) 76 | } 77 | 78 | fun kaptLogMessage(kind: Diagnostic.Kind, message: String, element: Element? = null) { 79 | env.messager.printMessage(kind, "\n" + message, element) 80 | } 81 | 82 | //KotlinPoet utils 83 | 84 | fun FileSpec.writeToFile(vararg sourceElements: Element) { 85 | val kotlinFileObject = env.filer 86 | .createResource(StandardLocation.SOURCE_OUTPUT, packageName, "$name.kt", *sourceElements) 87 | val openWriter = kotlinFileObject.openWriter() 88 | writeTo(openWriter) 89 | openWriter.close() 90 | } 91 | 92 | /** 93 | * Map [java.lang.Object] to [Any] 94 | */ 95 | fun TypeName.safeAnyTypeName(): TypeName = 96 | if (this is ClassName && this == ClassName("java.lang", "Object")) { 97 | Any::class.asTypeName() 98 | } else { 99 | this 100 | } 101 | -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/kapt/actions/KaptActionModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.kapt.actions 18 | 19 | import com.squareup.kotlinpoet.ANY 20 | import com.squareup.kotlinpoet.ClassName 21 | import com.squareup.kotlinpoet.CodeBlock 22 | import com.squareup.kotlinpoet.asTypeName 23 | import mini.processor.common.actions.ActionModel 24 | import mini.processor.common.actions.ActionSuperType 25 | import mini.processor.kapt.typeUtils 26 | import javax.lang.model.element.Element 27 | import javax.lang.model.type.TypeMirror 28 | 29 | class KaptActionModel(element: Element) : ActionModel { 30 | private val type = element.asType() 31 | private val javaObject = ClassName.bestGuess("java.lang.Object") 32 | 33 | override val typeName = type.asTypeName() 34 | private val superTypes = collectTypes(type) 35 | .sortedBy { it.depth } 36 | .filter { it.typeName != javaObject } 37 | .map { it.typeName } 38 | .plus(ANY) 39 | 40 | override fun listOfSupertypesCodeBlock(): CodeBlock { 41 | val format = superTypes.joinToString(",\n") { "%T::class" } 42 | val args = superTypes.toTypedArray() 43 | return CodeBlock.of("listOf($format)", *args) 44 | } 45 | 46 | private fun collectTypes(mirror: TypeMirror, depth: Int = 0): Set { 47 | //We want to add by depth 48 | val superTypes = typeUtils.directSupertypes(mirror).toSet() 49 | .map { collectTypes(it, depth + 1) } 50 | .flatten() 51 | return setOf(ActionSuperType(mirror.asTypeName(), depth)) + superTypes 52 | } 53 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/kapt/actions/KaptActionTypesGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.kapt.actions 18 | 19 | import mini.processor.common.actions.ActionTypesGeneratorDelegate 20 | import javax.lang.model.element.Element 21 | import javax.lang.model.element.Modifier 22 | 23 | class KaptActionTypesGeneratorDelegate(private val elements: Set) : 24 | ActionTypesGeneratorDelegate { 25 | override fun provideModels() = elements 26 | .filter { Modifier.ABSTRACT !in it.modifiers } 27 | .map { KaptActionModel(it) } 28 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/kapt/reducers/KaptReducersGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.kapt.reducers 18 | 19 | import mini.processor.common.reducers.ReducerModel 20 | import mini.processor.common.reducers.ReducersGeneratorDelegate 21 | import javax.lang.model.element.Element 22 | import javax.lang.model.element.ExecutableElement 23 | 24 | class KaptReducersGeneratorDelegate(private val elements: Set) : 25 | ReducersGeneratorDelegate { 26 | override fun provideModels(): List = elements 27 | .filterIsInstance() 28 | .map { KaptReducerModel(it) } 29 | } 30 | -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/ksp/MiniSymbolProcessor.kt: -------------------------------------------------------------------------------- 1 | package mini.processor.ksp 2 | 3 | import com.google.devtools.ksp.processing.CodeGenerator 4 | import com.google.devtools.ksp.processing.Resolver 5 | import com.google.devtools.ksp.processing.SymbolProcessor 6 | import com.google.devtools.ksp.symbol.KSAnnotated 7 | import com.google.devtools.ksp.symbol.KSClassDeclaration 8 | import com.squareup.kotlinpoet.ksp.writeTo 9 | import mini.Action 10 | import mini.Reducer 11 | import mini.processor.common.ProcessorException 12 | import mini.processor.common.actions.ActionTypesGenerator 13 | import mini.processor.common.getContainerBuilders 14 | import mini.processor.common.reducers.ReducersGenerator 15 | import mini.processor.kapt.stackTraceString 16 | import mini.processor.ksp.actions.KspActionTypesGeneratorDelegate 17 | import mini.processor.ksp.reducers.KspReducersGeneratorDelegate 18 | 19 | class MiniSymbolProcessor( 20 | private val codeGenerator: CodeGenerator 21 | ) : SymbolProcessor { 22 | override fun process(resolver: Resolver): List { 23 | // Get elements with the @Reducer or @Action annotations 24 | val actionSymbols = resolver.getSymbolsWithAnnotation(Action::class.java.canonicalName) 25 | val reducerSymbols = resolver.getSymbolsWithAnnotation(Reducer::class.java.canonicalName) 26 | 27 | // Collect the files that contain the symbols, we will use this to set the originating files 28 | // for the generated code and incremental processing. 29 | val originatingKsFiles = (actionSymbols + reducerSymbols). 30 | filterIsInstance() 31 | .mapNotNull { it.containingFile } 32 | .distinct() 33 | .toList() 34 | 35 | if (!actionSymbols.iterator().hasNext()) return emptyList() 36 | 37 | val (containerFile, container) = getContainerBuilders() 38 | 39 | try { 40 | ActionTypesGenerator(KspActionTypesGeneratorDelegate(actionSymbols)).generate(container) 41 | ReducersGenerator(KspReducersGeneratorDelegate(reducerSymbols)).generate(container) 42 | } catch (e: Throwable) { 43 | if (e !is ProcessorException) { 44 | kspLogError( 45 | "Compiler crashed, open an issue please!\n" + 46 | " ${e.stackTraceString()}" 47 | ) 48 | } 49 | } 50 | 51 | containerFile 52 | .addType(container.build()) 53 | .build() 54 | .writeTo( 55 | codeGenerator = codeGenerator, 56 | aggregating = true, 57 | originatingKSFiles = originatingKsFiles 58 | ) 59 | 60 | return emptyList() 61 | } 62 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/ksp/MiniSymbolProcessorProvider.kt: -------------------------------------------------------------------------------- 1 | package mini.processor.ksp 2 | 3 | import com.google.devtools.ksp.processing.SymbolProcessor 4 | import com.google.devtools.ksp.processing.SymbolProcessorEnvironment 5 | import com.google.devtools.ksp.processing.SymbolProcessorProvider 6 | 7 | class MiniSymbolProcessorProvider : SymbolProcessorProvider { 8 | override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { 9 | logger = environment.logger 10 | 11 | return MiniSymbolProcessor( 12 | codeGenerator = environment.codeGenerator 13 | ) 14 | } 15 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/ksp/SymbolProcessorUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.ksp 18 | 19 | import com.google.devtools.ksp.processing.KSPLogger 20 | import com.google.devtools.ksp.symbol.KSDeclaration 21 | import mini.processor.common.ProcessorException 22 | import javax.tools.Diagnostic 23 | 24 | lateinit var logger: KSPLogger 25 | 26 | fun kspCompilePrecondition( 27 | check: Boolean, 28 | message: String, 29 | declaration: KSDeclaration? = null 30 | ) { 31 | if (!check) { 32 | kspLogError(message, declaration) 33 | throw ProcessorException() 34 | } 35 | } 36 | 37 | fun kspLogError(message: String, declaration: KSDeclaration? = null) { 38 | kspLogMessage(Diagnostic.Kind.ERROR, message, declaration) 39 | } 40 | 41 | fun kspWarning(message: String, declaration: KSDeclaration? = null) { 42 | kspLogMessage(Diagnostic.Kind.MANDATORY_WARNING, message, declaration) 43 | } 44 | 45 | fun kspLogMessage( 46 | kind: Diagnostic.Kind, 47 | message: String, 48 | declaration: KSDeclaration? = null 49 | ) { 50 | when (kind) { 51 | Diagnostic.Kind.ERROR -> logger.error(message, declaration) 52 | Diagnostic.Kind.WARNING, Diagnostic.Kind.MANDATORY_WARNING -> logger.warn( 53 | message, 54 | declaration 55 | ) 56 | else -> logger.logging(message, declaration) 57 | } 58 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/ksp/actions/KspActionModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.ksp.actions 18 | 19 | import com.google.devtools.ksp.symbol.KSClassDeclaration 20 | import com.google.devtools.ksp.symbol.KSType 21 | import com.squareup.kotlinpoet.CodeBlock 22 | import com.squareup.kotlinpoet.ksp.toTypeName 23 | import mini.processor.common.actions.ActionModel 24 | import mini.processor.common.actions.ActionSuperType 25 | 26 | class KspActionModel(declaration: KSClassDeclaration) : ActionModel { 27 | private val type = declaration.asStarProjectedType() 28 | 29 | override val typeName = type.toTypeName() 30 | private val superTypes = collectTypes(type) 31 | .sortedBy { it.depth } 32 | .map { it.typeName } 33 | 34 | override fun listOfSupertypesCodeBlock(): CodeBlock { 35 | val format = superTypes.joinToString(",\n") { "%T::class" } 36 | val args = superTypes.toTypedArray() 37 | return CodeBlock.of("listOf($format)", *args) 38 | } 39 | 40 | private fun collectTypes(type: KSType, depth: Int = 0): Set { 41 | val rootSuperTypes = (type.declaration as? KSClassDeclaration) 42 | ?.superTypes 43 | ?.map { it.resolve() } 44 | ?.toSet() 45 | ?: emptySet() 46 | 47 | val superTypes = rootSuperTypes 48 | .map { 49 | collectTypes(it, depth + 1) 50 | } 51 | .flatten() 52 | 53 | return setOf(ActionSuperType(type.toTypeName(), depth)) + superTypes 54 | } 55 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/ksp/actions/KspActionTypesGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.ksp.actions 18 | 19 | import com.google.devtools.ksp.processing.KSPLogger 20 | import com.google.devtools.ksp.symbol.KSAnnotated 21 | import com.google.devtools.ksp.symbol.KSClassDeclaration 22 | import com.google.devtools.ksp.validate 23 | import mini.processor.common.actions.ActionTypesGeneratorDelegate 24 | 25 | class KspActionTypesGeneratorDelegate( 26 | private val symbols: Sequence 27 | ) : 28 | ActionTypesGeneratorDelegate { 29 | override fun provideModels() = symbols 30 | .filterIsInstance() 31 | .filter { com.google.devtools.ksp.symbol.Modifier.ABSTRACT !in it.modifiers && it.validate() } 32 | .map { KspActionModel(it) } 33 | .toList() 34 | } -------------------------------------------------------------------------------- /mini-processor/src/main/java/mini/processor/ksp/reducers/KspReducersGeneratorDelegate.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 HyperDevs 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 mini.processor.ksp.reducers 18 | 19 | import com.google.devtools.ksp.processing.KSPLogger 20 | import com.google.devtools.ksp.symbol.KSAnnotated 21 | import com.google.devtools.ksp.symbol.KSFunctionDeclaration 22 | import mini.processor.common.reducers.ReducerModel 23 | import mini.processor.common.reducers.ReducersGeneratorDelegate 24 | 25 | class KspReducersGeneratorDelegate( 26 | private val symbols: Sequence 27 | ) : ReducersGeneratorDelegate { 28 | override fun provideModels(): List = symbols 29 | .filterIsInstance() 30 | .map { KspReducerModel(it) } 31 | .toList() 32 | } -------------------------------------------------------------------------------- /mini-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 HyperDevs 3 | # 4 | # Copyright 2020 BQ 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | mini.processor.ksp.MiniSymbolProcessorProvider -------------------------------------------------------------------------------- /mini-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 HyperDevs 3 | # 4 | # Copyright 2020 BQ 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | mini.processor.kapt.MiniAnnotationProcessor -------------------------------------------------------------------------------- /mini-testing/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /mini-testing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | alias(libs.plugins.kotlin.jvm) 19 | alias(libs.plugins.convention.javaLib) 20 | } 21 | 22 | dependencies { 23 | api(project(":mini-common")) 24 | api(libs.kotlin.stdlib) 25 | api(libs.kotlin.reflect) 26 | 27 | api(libs.junit) 28 | } 29 | 30 | tasks.withType().configureEach { 31 | kotlinOptions { 32 | jvmTarget = libs.versions.java.sdk.get() 33 | } 34 | } 35 | 36 | java { 37 | sourceCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 38 | targetCompatibility = JavaVersion.toVersion(libs.versions.java.sdk.get()) 39 | } 40 | 41 | kotlin { 42 | jvmToolchain(libs.versions.java.sdk.get().toInt()) 43 | } -------------------------------------------------------------------------------- /mini-testing/src/main/java/mini/testing/CleanStateRule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.testing 20 | 21 | import mini.Store 22 | import org.junit.rules.TestRule 23 | import org.junit.runner.Description 24 | import org.junit.runners.model.Statement 25 | 26 | /** 27 | * [TestRule] that resets the state of each Store (retrieved via function) after an evaluation. 28 | */ 29 | class CleanStateRule(val storesFn: () -> List>) : TestRule { 30 | override fun apply(base: Statement, description: Description): Statement { 31 | return object : Statement() { 32 | fun reset() { 33 | val stores = storesFn() 34 | stores.forEach { it.resetState() } 35 | } 36 | 37 | override fun evaluate() { 38 | reset() 39 | base.evaluate() //Execute the test 40 | reset() 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /mini-testing/src/main/java/mini/testing/TestDispatcherMiddleware.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.testing 20 | 21 | import mini.Action 22 | import mini.Chain 23 | import mini.Middleware 24 | import java.util.* 25 | 26 | /** 27 | * [Middleware] class for testing purposes which mute all the received actions. 28 | */ 29 | internal class TestDispatcherMiddleware : Middleware { 30 | private val mutedActions = LinkedList() 31 | 32 | val actions: List get() = mutedActions 33 | override suspend fun intercept(action: Any, chain: Chain): Any { 34 | println("Muted: $action") 35 | mutedActions.add(action) 36 | return TestOnlyAction 37 | } 38 | } 39 | 40 | /** 41 | * Action for testing purposes. 42 | */ 43 | @Action 44 | internal object TestOnlyAction -------------------------------------------------------------------------------- /mini-testing/src/main/java/mini/testing/TestDispatcherRule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 HyperDevs 3 | * 4 | * Copyright 2020 BQ 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package mini.testing 20 | 21 | import mini.Dispatcher 22 | import org.junit.rules.TestRule 23 | import org.junit.runner.Description 24 | import org.junit.runners.model.Statement 25 | 26 | /** 27 | * This [TestRule] evaluates every action received with the [TestDispatcherMiddleware] to 28 | * intercept all the actions dispatched during a test and block them, getting them not reaching the store. 29 | */ 30 | class TestDispatcherRule(val dispatcherFn: () -> Dispatcher) : TestRule { 31 | private val testMiddleware = TestDispatcherMiddleware() 32 | val actions: List get() = testMiddleware.actions 33 | 34 | override fun apply(base: Statement, description: Description): Statement { 35 | return object : Statement() { 36 | override fun evaluate() { 37 | val dispatcher = dispatcherFn() 38 | dispatcher.addMiddleware(testMiddleware) 39 | base.evaluate() //Execute the test 40 | dispatcher.removeMiddleware(testMiddleware) 41 | } 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 HyperDevs 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 | @file:Suppress("UnstableApiUsage") 18 | 19 | include(":app") 20 | include(":mini-processor") 21 | include(":mini-common") 22 | include(":mini-android") 23 | include(":mini-processor-test") 24 | include(":mini-kodein") 25 | include(":mini-kodein-android") 26 | include(":mini-kodein-android-compose") 27 | include(":mini-testing") 28 | 29 | // Modules to add as composite builds 30 | includeBuild("convention-plugins") 31 | 32 | pluginManagement { 33 | repositories { 34 | google() 35 | mavenCentral() 36 | gradlePluginPortal() 37 | maven { url = java.net.URI("https://jitpack.io") } 38 | } 39 | } 40 | 41 | dependencyResolutionManagement { 42 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 43 | 44 | repositories { 45 | google() 46 | mavenCentral() 47 | gradlePluginPortal() 48 | maven { url = java.net.URI("https://jitpack.io") } 49 | } 50 | } --------------------------------------------------------------------------------