├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── demo │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── me │ │ │ └── pavi2410 │ │ │ └── useCompose │ │ │ └── demo │ │ │ ├── MainActivity.kt │ │ │ ├── components │ │ │ └── ExampleScaffold.kt │ │ │ ├── screens │ │ │ ├── ContextExample.kt │ │ │ ├── CounterExample.kt │ │ │ ├── ExampleScreen.kt │ │ │ ├── MainScreen.kt │ │ │ ├── MutationExample.kt │ │ │ ├── NetworkExample.kt │ │ │ ├── QueryExample.kt │ │ │ ├── ReducerExample.kt │ │ │ └── ToggleExample.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── me │ └── pavi2410 │ └── useCompose │ └── demo │ └── ExampleUnitTest.kt ├── build.gradle.kts ├── colors ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── me │ └── pavi2410 │ └── useCompose │ └── colors │ └── tailwind.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hooks ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── hooks │ │ └── HooksTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── hooks │ │ └── hooks.kt │ └── test │ └── java │ └── me │ └── pavi2410 │ └── useCompose │ └── hooks │ └── ExampleUnitTest.kt ├── jitpack.yml ├── network ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── network │ │ └── NetworkTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── network │ │ └── hooks.kt │ └── test │ └── java │ └── me │ └── pavi2410 │ └── useCompose │ └── network │ └── ExampleUnitTest.kt ├── query ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── query │ │ └── ReactQueryTest.kt │ └── main │ ├── AndroidManifest.xml │ └── java │ └── me │ └── pavi2410 │ └── useCompose │ └── query │ └── hooks.kt ├── react ├── .gitignore ├── build.gradle.kts ├── consumer-rules.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── react │ │ └── HooksTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── me │ │ └── pavi2410 │ │ └── useCompose │ │ └── react │ │ └── hooks.kt │ └── test │ └── java │ └── me │ └── pavi2410 │ └── useCompose │ └── react │ └── ExampleUnitTest.kt └── settings.gradle.kts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: set up JDK 11 16 | uses: actions/setup-java@v2 17 | with: 18 | java-version: '11' 19 | distribution: 'adopt' 20 | cache: gradle 21 | - name: Grant execute permission for gradlew 22 | run: chmod +x gradlew 23 | - name: Build with Gradle 24 | run: ./gradlew --console=plain --stacktrace build 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,kotlin 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,androidstudio,gradle,kotlin 4 | 5 | ### Android ### 6 | # Gradle files 7 | .gradle/ 8 | build/ 9 | 10 | # Local configuration file (sdk path, etc) 11 | local.properties 12 | 13 | # Log/OS Files 14 | *.log 15 | 16 | # Android Studio generated files and folders 17 | captures/ 18 | .externalNativeBuild/ 19 | .cxx/ 20 | *.apk 21 | output.json 22 | 23 | # IntelliJ 24 | *.iml 25 | .idea/ 26 | 27 | # Keystore files 28 | *.jks 29 | *.keystore 30 | 31 | # Google Services (e.g. APIs or Firebase) 32 | google-services.json 33 | 34 | # Android Profiling 35 | *.hprof 36 | 37 | ### Android Patch ### 38 | gen-external-apklibs 39 | 40 | # Replacement of .externalNativeBuild directories introduced 41 | # with Android Studio 3.5. 42 | 43 | ### Kotlin ### 44 | # Compiled class file 45 | *.class 46 | 47 | # Log file 48 | 49 | # BlueJ files 50 | *.ctxt 51 | 52 | # Mobile Tools for Java (J2ME) 53 | .mtj.tmp/ 54 | 55 | # Package Files # 56 | *.jar 57 | *.war 58 | *.nar 59 | *.ear 60 | *.zip 61 | *.tar.gz 62 | *.rar 63 | 64 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 65 | hs_err_pid* 66 | replay_pid* 67 | 68 | ### Gradle ### 69 | .gradle 70 | 71 | # Ignore Gradle GUI config 72 | gradle-app.setting 73 | 74 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 75 | !gradle-wrapper.jar 76 | 77 | # Cache of project 78 | .gradletasknamecache 79 | 80 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 81 | # gradle/wrapper/gradle-wrapper.properties 82 | 83 | ### Gradle Patch ### 84 | **/build/ 85 | 86 | ### AndroidStudio ### 87 | # Covers files to be ignored for android development using Android Studio. 88 | 89 | # Built application files 90 | *.ap_ 91 | *.aab 92 | 93 | # Files for the ART/Dalvik VM 94 | *.dex 95 | 96 | # Java class files 97 | 98 | # Generated files 99 | bin/ 100 | gen/ 101 | out/ 102 | 103 | # Gradle files 104 | 105 | # Signing files 106 | .signing/ 107 | 108 | # Local configuration file (sdk path, etc) 109 | 110 | # Proguard folder generated by Eclipse 111 | proguard/ 112 | 113 | # Log Files 114 | 115 | # Android Studio 116 | /*/build/ 117 | /*/local.properties 118 | /*/out 119 | /*/*/build 120 | /*/*/production 121 | .navigation/ 122 | *.ipr 123 | *~ 124 | *.swp 125 | 126 | # Keystore files 127 | 128 | # Google Services (e.g. APIs or Firebase) 129 | # google-services.json 130 | 131 | # Android Patch 132 | 133 | # External native build folder generated in Android Studio 2.2 and later 134 | .externalNativeBuild 135 | 136 | # NDK 137 | obj/ 138 | 139 | # IntelliJ IDEA 140 | *.iws 141 | /out/ 142 | 143 | # User-specific configurations 144 | .idea/caches/ 145 | .idea/libraries/ 146 | .idea/shelf/ 147 | .idea/workspace.xml 148 | .idea/tasks.xml 149 | .idea/.name 150 | .idea/compiler.xml 151 | .idea/copyright/profiles_settings.xml 152 | .idea/encodings.xml 153 | .idea/misc.xml 154 | .idea/modules.xml 155 | .idea/scopes/scope_settings.xml 156 | .idea/dictionaries 157 | .idea/vcs.xml 158 | .idea/jsLibraryMappings.xml 159 | .idea/datasources.xml 160 | .idea/dataSources.ids 161 | .idea/sqlDataSources.xml 162 | .idea/dynamic.xml 163 | .idea/uiDesigner.xml 164 | .idea/assetWizardSettings.xml 165 | .idea/gradle.xml 166 | .idea/jarRepositories.xml 167 | .idea/navEditor.xml 168 | 169 | # OS-specific files 170 | .DS_Store 171 | .DS_Store? 172 | ._* 173 | .Spotlight-V100 174 | .Trashes 175 | ehthumbs.db 176 | Thumbs.db 177 | 178 | # Legacy Eclipse project files 179 | .classpath 180 | .project 181 | .cproject 182 | .settings/ 183 | 184 | # Mobile Tools for Java (J2ME) 185 | 186 | # Package Files # 187 | 188 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 189 | 190 | ## Plugin-specific files: 191 | 192 | # mpeltonen/sbt-idea plugin 193 | .idea_modules/ 194 | 195 | # JIRA plugin 196 | atlassian-ide-plugin.xml 197 | 198 | # Mongo Explorer plugin 199 | .idea/mongoSettings.xml 200 | 201 | # Crashlytics plugin (for Android Studio and IntelliJ) 202 | com_crashlytics_export_strings.xml 203 | crashlytics.properties 204 | crashlytics-build.properties 205 | fabric.properties 206 | 207 | ### AndroidStudio Patch ### 208 | 209 | !/gradle/wrapper/gradle-wrapper.jar 210 | 211 | # End of https://www.toptal.com/developers/gitignore/api/android,androidstudio,gradle,kotlin 212 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 119 | 120 | 122 | 123 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Pavitra Golchha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # useCompose 2 | 3 | Headless @Composable hooks that drive UI logic. Inspired by React. 4 | 5 | [![](https://jitpack.io/v/pavi2410/useCompose.svg)](https://jitpack.io/#pavi2410/useCompose) [![CI](https://github.com/pavi2410/useCompose/actions/workflows/ci.yml/badge.svg)](https://github.com/pavi2410/useCompose/actions/workflows/ci.yml) 6 | 7 | | ⚛ `react` | ❓ `query` | 8 | | --- | --- | 9 | | ![react example](https://github.com/pavi2410/useCompose/assets/28837746/0a271bda-fc47-47b3-8cb6-daf6748a2633) | ![query example](https://github.com/pavi2410/useCompose/assets/28837746/2317d447-8f8b-4626-b92e-2e024e242714) | 10 | 11 | 12 | ## Modules 13 | 14 | ### ⚛ react 15 | - useState 16 | - useEffect 17 | - useContext 18 | - useReducer 19 | 20 | ### 🪝 hooks 21 | - useToggle 22 | 23 | ### 🕸 network 24 | - useConnnectionStatus 25 | 26 | ### ❓ query 27 | - useQuery 28 | 29 | ## Installation 30 | 31 | Step 1. Add the JitPack repository to your build file 32 | 33 | Add it in your root build.gradle at the end of repositories: 34 | ```gradle 35 | repositories { 36 | ... 37 | maven { 38 | url = uri("https://jitpack.io") 39 | } 40 | } 41 | ``` 42 | 43 | Step 2. Add the dependency 44 | ```gradle 45 | dependencies { 46 | implementation("com.github.pavi2410.useCompose::") 47 | } 48 | ``` 49 | 50 | ## Help Wanted 51 | 52 | I want your help in making this library extensive such that this cover many of the commonly used hooks. Also, I want your help in building a KMP friendly library. 53 | 54 | ## License 55 | 56 | [MIT](https://choosealicense.com/licenses/mit/) 57 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | compileSdk = 34 8 | namespace = "me.pavi2410.useCompose.demo" 9 | 10 | defaultConfig { 11 | minSdk = 21 12 | targetSdk = 34 13 | versionCode = 1 14 | versionName = "1.0" 15 | 16 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 17 | vectorDrawables { 18 | useSupportLibrary = true 19 | } 20 | } 21 | 22 | buildTypes { 23 | getByName("release") { 24 | isMinifyEnabled = false 25 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 26 | } 27 | } 28 | buildFeatures { 29 | compose = true 30 | } 31 | composeOptions { 32 | kotlinCompilerExtensionVersion = libs.versions.compose.get() 33 | } 34 | kotlinOptions { 35 | jvmTarget = "17" 36 | } 37 | compileOptions { 38 | sourceCompatibility = JavaVersion.VERSION_17 39 | targetCompatibility = JavaVersion.VERSION_17 40 | } 41 | packaging { 42 | resources { 43 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 44 | } 45 | } 46 | } 47 | 48 | dependencies { 49 | implementation(projects.react) 50 | implementation(projects.hooks) 51 | implementation(projects.network) 52 | implementation(projects.query) 53 | 54 | val composeBom = platform(libs.compose.bom) 55 | implementation(composeBom) 56 | androidTestImplementation(composeBom) 57 | implementation(libs.androidx.core.ktx) 58 | implementation(libs.compose.ui) 59 | implementation(libs.compose.material) 60 | implementation(libs.compose.tooling.preview) 61 | implementation(libs.androidx.lifecycle.runtime.ktx) 62 | implementation(libs.androidx.activity.compose) 63 | implementation(libs.androidx.navigation.compose) 64 | 65 | testImplementation(libs.junit) 66 | androidTestImplementation(libs.androidx.junit) 67 | androidTestImplementation(libs.androidx.espresso.core) 68 | // androidTestImplementation(libs.compose.test.junit4) 69 | debugImplementation(libs.compose.test.manifest) 70 | debugImplementation(libs.compose.tooling.debug) 71 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/me/pavi2410/useCompose/demo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("me.pavi2410.usecompose", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.Surface 8 | import androidx.navigation.compose.NavHost 9 | import androidx.navigation.compose.composable 10 | import androidx.navigation.compose.rememberNavController 11 | import me.pavi2410.useCompose.demo.components.ExampleScreenScaffold 12 | import me.pavi2410.useCompose.demo.screens.MainScreen 13 | import me.pavi2410.useCompose.demo.screens.exampleScreens 14 | import me.pavi2410.useCompose.demo.theme.UseComposeTheme 15 | 16 | class MainActivity : ComponentActivity() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContent { 20 | UseComposeTheme { 21 | // A surface container using the 'background' color from the theme 22 | Surface(color = MaterialTheme.colors.background) { 23 | val navController = rememberNavController() 24 | 25 | NavHost(navController = navController, startDestination = "main") { 26 | composable("main") { MainScreen(navController) } 27 | exampleScreens.forEach { screen -> 28 | composable(screen.route) { 29 | ExampleScreenScaffold(navController, title = screen.title) { 30 | screen.content() 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/components/ExampleScaffold.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.components 2 | 3 | import androidx.compose.material.* 4 | import androidx.compose.material.icons.Icons 5 | import androidx.compose.material.icons.filled.ArrowBack 6 | import androidx.compose.runtime.Composable 7 | import androidx.navigation.NavController 8 | 9 | @Composable 10 | fun ExampleScreenScaffold(navController: NavController, title: String, content: @Composable () -> Unit) { 11 | Scaffold(topBar = { 12 | TopAppBar( 13 | title = { 14 | Text("Example / $title") 15 | }, 16 | navigationIcon = { 17 | IconButton(onClick = { 18 | navController.popBackStack() 19 | }) { 20 | Icon(Icons.Default.ArrowBack, null) 21 | } 22 | } 23 | ) 24 | }) { 25 | content() 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/ContextExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.unit.dp 12 | import me.pavi2410.useCompose.react.createContext 13 | import me.pavi2410.useCompose.react.useContext 14 | 15 | @Suppress("LocalVariableName") 16 | @Composable 17 | fun ContextExample() { 18 | Column { 19 | val ColorContext = createContext(Color.Red) 20 | 21 | val outerColor = useContext(ColorContext) 22 | Box( 23 | modifier = Modifier 24 | .size(128.dp) 25 | .background(outerColor), 26 | contentAlignment = Alignment.Center 27 | ) { 28 | ColorContext.Provider(value = Color.Blue) { 29 | val innerColor = useContext(ColorContext) 30 | Box( 31 | modifier = Modifier 32 | .size(64.dp) 33 | .background(innerColor) 34 | ) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/CounterExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import me.pavi2410.useCompose.react.useEffect 8 | import me.pavi2410.useCompose.react.useState 9 | 10 | @Composable 11 | fun CounterExample() { 12 | Column { 13 | val (count, setCount) = useState(0) 14 | 15 | useEffect { 16 | println("started") 17 | } 18 | 19 | useEffect(count) { 20 | println("Count is $count") 21 | } 22 | 23 | Text("You clicked $count times") 24 | 25 | Button(onClick = { setCount(count + 1) }) { 26 | Text("Click me") 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/ExampleScreen.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | 5 | sealed class ExampleScreen( 6 | val route: String, 7 | val title: String, 8 | val content: @Composable () -> Unit 9 | ) { 10 | object Counter : ExampleScreen("counter", "Counter", { CounterExample() }) 11 | object Context : ExampleScreen("context", "Context", { ContextExample() }) 12 | object Reducer : ExampleScreen("reducer", "Reducer", { ReducerExample() }) 13 | object Toggle : ExampleScreen("toggle", "Toggle", { ToggleExample() }) 14 | object Network : ExampleScreen("network", "Network", { NetworkExample() }) 15 | object Query : ExampleScreen("query", "Query", { QueryExample() }) 16 | object Mutation : ExampleScreen("mutation", "Mutation", { MutationExample() }) 17 | } 18 | 19 | val exampleScreens 20 | get() = listOf( 21 | ExampleScreen.Counter, 22 | ExampleScreen.Context, 23 | ExampleScreen.Reducer, 24 | ExampleScreen.Toggle, 25 | ExampleScreen.Network, 26 | ExampleScreen.Query, 27 | ExampleScreen.Mutation, 28 | ) 29 | -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/MainScreen.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.ExperimentalFoundationApi 4 | import androidx.compose.foundation.border 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.fillMaxWidth 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.foundation.lazy.LazyColumn 10 | import androidx.compose.foundation.lazy.items 11 | import androidx.compose.material.* 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.unit.Dp 16 | import androidx.compose.ui.unit.dp 17 | import androidx.navigation.NavController 18 | 19 | @OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) 20 | @Composable 21 | fun MainScreen(navController: NavController) { 22 | Scaffold( 23 | topBar = { 24 | TopAppBar( 25 | title = { 26 | Text("useCompose") 27 | } 28 | ) 29 | } 30 | ) { 31 | LazyColumn { 32 | items(exampleScreens) { screen -> 33 | Card(modifier = Modifier 34 | .fillMaxWidth() 35 | .padding(16.dp) 36 | .border(Dp.Hairline, Color.Green), 37 | onClick = { 38 | navController.navigate(screen.route) 39 | }) { 40 | Column(Modifier.padding(16.dp)) { 41 | Text(screen.title, Modifier.fillMaxSize()) 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/MutationExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.platform.testTag 9 | import kotlinx.coroutines.delay 10 | import me.pavi2410.useCompose.query.useMutation 11 | 12 | @Composable 13 | fun MutationExample() { 14 | Column { 15 | var token by remember { mutableStateOf("") } 16 | 17 | val loginMutation = useMutation { (username, password) -> 18 | delay(500) 19 | "secret_token:$username/$password" 20 | } 21 | 22 | Button( 23 | modifier = Modifier.testTag("login_button"), 24 | onClick = { 25 | // todo: is this blocking the main thread? 26 | // todo: this makes me think I need a mutateAsync too... 27 | loginMutation.mutate("pavi2410", "secretpw123") { 28 | token = it 29 | } 30 | } 31 | ) { 32 | Text("Login") 33 | } 34 | 35 | Text( 36 | if (token.isEmpty()) "Please login" else "Welcome! token = $token", 37 | modifier = Modifier.testTag("status") 38 | ) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/NetworkExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.size 5 | import androidx.compose.material.Icon 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.Check 8 | import androidx.compose.material.icons.filled.Clear 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.unit.dp 13 | import me.pavi2410.useCompose.network.useConnectionStatus 14 | 15 | @Composable 16 | fun NetworkExample() { 17 | Column { 18 | val isConnected = useConnectionStatus() 19 | 20 | if (isConnected) { 21 | Icon(Icons.Default.Check, null, tint = Color.Green, modifier = Modifier.size(64.dp)) 22 | } else { 23 | Icon(Icons.Default.Clear, null, tint = Color.Red, modifier = Modifier.size(64.dp)) 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/QueryExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Text 5 | import androidx.compose.runtime.Composable 6 | import kotlinx.coroutines.delay 7 | import me.pavi2410.useCompose.query.useQuery 8 | 9 | @Composable 10 | fun QueryExample() { 11 | Column { 12 | val data = useQuery { 13 | delay(500) 14 | "secret_token" 15 | } 16 | 17 | Text(data.toString()) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/ReducerExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material.Button 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.tooling.preview.Preview 10 | import androidx.compose.ui.unit.dp 11 | import me.pavi2410.useCompose.react.useReducer 12 | 13 | data class MyState(val count: Int) 14 | sealed interface MyAction { 15 | object Increment : MyAction 16 | object Decrement : MyAction 17 | } 18 | 19 | val initialState = MyState(0) 20 | 21 | @Preview(showBackground = true) 22 | @Composable 23 | fun ReducerExample() { 24 | Column(Modifier.padding(16.dp)) { 25 | val (state, dispatch) = useReducer(initialState) { state, action -> 26 | when (action) { 27 | MyAction.Increment -> state.copy(count = state.count + 1) 28 | MyAction.Decrement -> state.copy(count = state.count - 1) 29 | } 30 | } 31 | 32 | Text("Count: ${state.count}") 33 | Button(onClick = { 34 | dispatch(MyAction.Increment) 35 | }) { 36 | Text("+") 37 | } 38 | Button(onClick = { 39 | dispatch(MyAction.Decrement) 40 | }) { 41 | Text("-") 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/screens/ToggleExample.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.screens 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.Text 6 | import androidx.compose.runtime.Composable 7 | import me.pavi2410.useCompose.hooks.useToggle 8 | 9 | @Composable 10 | fun ToggleExample() { 11 | Column { 12 | val (state, toggle) = useToggle() 13 | 14 | Text("Switch = ${if (state) "ON" else "OFF"}") 15 | 16 | Button(onClick = { toggle() }) { 17 | Text("Toggle") 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple200 = Color(0xFFBB86FC) 6 | val Purple500 = Color(0xFF6200EE) 7 | val Purple700 = Color(0xFF3700B3) 8 | val Teal200 = Color(0xFF03DAC5) -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = Purple200, 11 | primaryVariant = Purple700, 12 | secondary = Teal200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = Purple500, 17 | primaryVariant = Purple700, 18 | secondary = Teal200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun UseComposeTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { 32 | val colors = if (darkTheme) { 33 | DarkColorPalette 34 | } else { 35 | LightColorPalette 36 | } 37 | 38 | MaterialTheme( 39 | colors = colors, 40 | typography = Typography, 41 | shapes = Shapes, 42 | content = content 43 | ) 44 | } -------------------------------------------------------------------------------- /app/src/main/java/me/pavi2410/useCompose/demo/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | useCompose 3 | -------------------------------------------------------------------------------- /app/src/test/java/me/pavi2410/useCompose/demo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.demo 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) apply false 3 | alias(libs.plugins.android.library) apply false 4 | alias(libs.plugins.kotlin.android) apply false 5 | } 6 | 7 | tasks.register("clean", Delete::class) { 8 | delete(rootProject.layout.buildDirectory) 9 | } -------------------------------------------------------------------------------- /colors/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /colors/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | namespace = "me.pavi2410.useCompose.colors" 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | getByName("release") { 20 | isMinifyEnabled = false 21 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 22 | } 23 | } 24 | buildFeatures { 25 | compose = true 26 | } 27 | composeOptions { 28 | kotlinCompilerExtensionVersion = libs.versions.compose.get() 29 | } 30 | kotlinOptions { 31 | jvmTarget = "17" 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | packaging { 38 | resources { 39 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | val composeBom = platform(libs.compose.bom) 46 | api(composeBom) 47 | androidTestApi(composeBom) 48 | api(libs.compose.ui) 49 | api(libs.compose.material) 50 | api(libs.compose.tooling.preview) 51 | 52 | testImplementation(libs.junit) 53 | androidTestImplementation(libs.androidx.junit) 54 | androidTestImplementation(libs.androidx.espresso.core) 55 | androidTestImplementation(libs.androidx.uiautomator) 56 | // Test rules and transitive dependencies: 57 | androidTestImplementation(libs.compose.test.junit4) 58 | // Needed for createComposeRule, but not createAndroidComposeRule: 59 | debugImplementation(libs.compose.test.manifest) 60 | } 61 | 62 | publishing { 63 | publications { 64 | register("release") { 65 | groupId = "me.pavi2410.useCompose" 66 | artifactId = "colors" 67 | version = "1.0.0" 68 | 69 | afterEvaluate { 70 | from(components["release"]) 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /colors/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/colors/consumer-rules.pro -------------------------------------------------------------------------------- /colors/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 -------------------------------------------------------------------------------- /colors/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /colors/src/main/java/me/pavi2410/useCompose/colors/tailwind.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("unused") 2 | 3 | package me.pavi2410.useCompose.colors 4 | 5 | import androidx.compose.ui.graphics.Color 6 | 7 | 8 | /** 9 | * Colors taken from tailwindcss. This set of colors look much vibrant and beautiful compared to Material Design one. 10 | * 11 | * Check out the [tailwindcss color palette](https://tailwindcss.com/docs/customizing-colors#default-color-palette) 12 | */ 13 | object TwColors { 14 | val slate50 = Color(0xfff8fafc) 15 | val slate100 = Color(0xfff1f5f9) 16 | val slate200 = Color(0xffe2e8f0) 17 | val slate300 = Color(0xffcbd5e1) 18 | val slate400 = Color(0xff94a3b8) 19 | val slate500 = Color(0xff64748b) 20 | val slate600 = Color(0xff475569) 21 | val slate700 = Color(0xff334155) 22 | val slate800 = Color(0xff1e293b) 23 | val slate900 = Color(0xff0f172a) 24 | 25 | val gray50 = Color(0xfff9fafb) 26 | val gray100 = Color(0xfff3f4f6) 27 | val gray200 = Color(0xffe5e7eb) 28 | val gray300 = Color(0xffd1d5db) 29 | val gray400 = Color(0xff9ca3af) 30 | val gray500 = Color(0xff6b7280) 31 | val gray600 = Color(0xff4b5563) 32 | val gray700 = Color(0xff374151) 33 | val gray800 = Color(0xff1f2937) 34 | val gray900 = Color(0xff111827) 35 | 36 | val zinc50 = Color(0xfffafafa) 37 | val zinc100 = Color(0xfff4f4f5) 38 | val zinc200 = Color(0xffe4e4e7) 39 | val zinc300 = Color(0xffd4d4d8) 40 | val zinc400 = Color(0xffa1a1aa) 41 | val zinc500 = Color(0xff71717a) 42 | val zinc600 = Color(0xff52525b) 43 | val zinc700 = Color(0xff3f3f46) 44 | val zinc800 = Color(0xff27272a) 45 | val zinc900 = Color(0xff18181b) 46 | 47 | val neutral50 = Color(0xfffafafa) 48 | val neutral100 = Color(0xfff5f5f5) 49 | val neutral200 = Color(0xffe5e5e5) 50 | val neutral300 = Color(0xffd4d4d4) 51 | val neutral400 = Color(0xffa3a3a3) 52 | val neutral500 = Color(0xff737373) 53 | val neutral600 = Color(0xff525252) 54 | val neutral700 = Color(0xff404040) 55 | val neutral800 = Color(0xff262626) 56 | val neutral900 = Color(0xff171717) 57 | 58 | val stone50 = Color(0xfffafaf9) 59 | val stone100 = Color(0xfff5f5f4) 60 | val stone200 = Color(0xffe7e5e4) 61 | val stone300 = Color(0xffd6d3d1) 62 | val stone400 = Color(0xffa8a29e) 63 | val stone500 = Color(0xff78716c) 64 | val stone600 = Color(0xff57534e) 65 | val stone700 = Color(0xff44403c) 66 | val stone800 = Color(0xff292524) 67 | val stone900 = Color(0xff1c1917) 68 | 69 | val red50 = Color(0xfffef2f2) 70 | val red100 = Color(0xfffee2e2) 71 | val red200 = Color(0xfffecaca) 72 | val red300 = Color(0xfffca5a5) 73 | val red400 = Color(0xfff87171) 74 | val red500 = Color(0xffef4444) 75 | val red600 = Color(0xffdc2626) 76 | val red700 = Color(0xffb91c1c) 77 | val red800 = Color(0xff991b1b) 78 | val red900 = Color(0xff7f1d1d) 79 | 80 | val orange50 = Color(0xfffff7ed) 81 | val orange100 = Color(0xffffedd5) 82 | val orange200 = Color(0xfffed7aa) 83 | val orange300 = Color(0xfffdba74) 84 | val orange400 = Color(0xfffb923c) 85 | val orange500 = Color(0xfff97316) 86 | val orange600 = Color(0xffea580c) 87 | val orange700 = Color(0xffc2410c) 88 | val orange800 = Color(0xff9a3412) 89 | val orange900 = Color(0xff7c2d12) 90 | 91 | val amber50 = Color(0xfffffbeb) 92 | val amber100 = Color(0xfffef3c7) 93 | val amber200 = Color(0xfffde68a) 94 | val amber300 = Color(0xfffcd34d) 95 | val amber400 = Color(0xfffbbf24) 96 | val amber500 = Color(0xfff59e0b) 97 | val amber600 = Color(0xffd97706) 98 | val amber700 = Color(0xffb45309) 99 | val amber800 = Color(0xff92400e) 100 | val amber900 = Color(0xff78350f) 101 | 102 | val yellow50 = Color(0xfffefce8) 103 | val yellow100 = Color(0xfffef9c3) 104 | val yellow200 = Color(0xfffef08a) 105 | val yellow300 = Color(0xfffde047) 106 | val yellow400 = Color(0xfffacc15) 107 | val yellow500 = Color(0xffeab308) 108 | val yellow600 = Color(0xffca8a04) 109 | val yellow700 = Color(0xffa16207) 110 | val yellow800 = Color(0xff854d0e) 111 | val yellow900 = Color(0xff713f12) 112 | 113 | val lime50 = Color(0xfff7fee7) 114 | val lime100 = Color(0xffecfccb) 115 | val lime200 = Color(0xffd9f99d) 116 | val lime300 = Color(0xffbef264) 117 | val lime400 = Color(0xffa3e635) 118 | val lime500 = Color(0xff84cc16) 119 | val lime600 = Color(0xff65a30d) 120 | val lime700 = Color(0xff4d7c0f) 121 | val lime800 = Color(0xff3f6212) 122 | val lime900 = Color(0xff365314) 123 | 124 | val green50 = Color(0xfff0fdf4) 125 | val green100 = Color(0xffdcfce7) 126 | val green200 = Color(0xffbbf7d0) 127 | val green300 = Color(0xff86efac) 128 | val green400 = Color(0xff4ade80) 129 | val green500 = Color(0xff22c55e) 130 | val green600 = Color(0xff16a34a) 131 | val green700 = Color(0xff15803d) 132 | val green800 = Color(0xff166534) 133 | val green900 = Color(0xff14532d) 134 | 135 | val emerald50 = Color(0xffecfdf5) 136 | val emerald100 = Color(0xffd1fae5) 137 | val emerald200 = Color(0xffa7f3d0) 138 | val emerald300 = Color(0xff6ee7b7) 139 | val emerald400 = Color(0xff34d399) 140 | val emerald500 = Color(0xff10b981) 141 | val emerald600 = Color(0xff059669) 142 | val emerald700 = Color(0xff047857) 143 | val emerald800 = Color(0xff065f46) 144 | val emerald900 = Color(0xff064e3b) 145 | 146 | val teal50 = Color(0xfff0fdfa) 147 | val teal100 = Color(0xffccfbf1) 148 | val teal200 = Color(0xff99f6e4) 149 | val teal300 = Color(0xff5eead4) 150 | val teal400 = Color(0xff2dd4bf) 151 | val teal500 = Color(0xff14b8a6) 152 | val teal600 = Color(0xff0d9488) 153 | val teal700 = Color(0xff0f766e) 154 | val teal800 = Color(0xff115e59) 155 | val teal900 = Color(0xff134e4a) 156 | 157 | val cyan50 = Color(0xffecfeff) 158 | val cyan100 = Color(0xffcffafe) 159 | val cyan200 = Color(0xffa5f3fc) 160 | val cyan300 = Color(0xff67e8f9) 161 | val cyan400 = Color(0xff22d3ee) 162 | val cyan500 = Color(0xff06b6d4) 163 | val cyan600 = Color(0xff0891b2) 164 | val cyan700 = Color(0xff0e7490) 165 | val cyan800 = Color(0xff155e75) 166 | val cyan900 = Color(0xff164e63) 167 | 168 | val sky50 = Color(0xfff0f9ff) 169 | val sky100 = Color(0xffe0f2fe) 170 | val sky200 = Color(0xffbae6fd) 171 | val sky300 = Color(0xff7dd3fc) 172 | val sky400 = Color(0xff38bdf8) 173 | val sky500 = Color(0xff0ea5e9) 174 | val sky600 = Color(0xff0284c7) 175 | val sky700 = Color(0xff0369a1) 176 | val sky800 = Color(0xff075985) 177 | val sky900 = Color(0xff0c4a6e) 178 | 179 | val blue50 = Color(0xffeff6ff) 180 | val blue100 = Color(0xffdbeafe) 181 | val blue200 = Color(0xffbfdbfe) 182 | val blue300 = Color(0xff93c5fd) 183 | val blue400 = Color(0xff60a5fa) 184 | val blue500 = Color(0xff3b82f6) 185 | val blue600 = Color(0xff2563eb) 186 | val blue700 = Color(0xff1d4ed8) 187 | val blue800 = Color(0xff1e40af) 188 | val blue900 = Color(0xff1e3a8a) 189 | 190 | val indigo50 = Color(0xffeef2ff) 191 | val indigo100 = Color(0xffe0e7ff) 192 | val indigo200 = Color(0xffc7d2fe) 193 | val indigo300 = Color(0xffa5b4fc) 194 | val indigo400 = Color(0xff818cf8) 195 | val indigo500 = Color(0xff6366f1) 196 | val indigo600 = Color(0xff4f46e5) 197 | val indigo700 = Color(0xff4338ca) 198 | val indigo800 = Color(0xff3730a3) 199 | val indigo900 = Color(0xff312e81) 200 | 201 | val violet50 = Color(0xfff5f3ff) 202 | val violet100 = Color(0xffede9fe) 203 | val violet200 = Color(0xffddd6fe) 204 | val violet300 = Color(0xffc4b5fd) 205 | val violet400 = Color(0xffa78bfa) 206 | val violet500 = Color(0xff8b5cf6) 207 | val violet600 = Color(0xff7c3aed) 208 | val violet700 = Color(0xff6d28d9) 209 | val violet800 = Color(0xff5b21b6) 210 | val violet900 = Color(0xff4c1d95) 211 | 212 | val purple50 = Color(0xfffaf5ff) 213 | val purple100 = Color(0xfff3e8ff) 214 | val purple200 = Color(0xffe9d5ff) 215 | val purple300 = Color(0xffd8b4fe) 216 | val purple400 = Color(0xffc084fc) 217 | val purple500 = Color(0xffa855f7) 218 | val purple600 = Color(0xff9333ea) 219 | val purple700 = Color(0xff7e22ce) 220 | val purple800 = Color(0xff6b21a8) 221 | val purple900 = Color(0xff581c87) 222 | 223 | val fuchsia50 = Color(0xfffdf4ff) 224 | val fuchsia100 = Color(0xfffae8ff) 225 | val fuchsia200 = Color(0xfff5d0fe) 226 | val fuchsia300 = Color(0xfff0abfc) 227 | val fuchsia400 = Color(0xffe879f9) 228 | val fuchsia500 = Color(0xffd946ef) 229 | val fuchsia600 = Color(0xffc026d3) 230 | val fuchsia700 = Color(0xffa21caf) 231 | val fuchsia800 = Color(0xff86198f) 232 | val fuchsia900 = Color(0xff701a75) 233 | 234 | val pink50 = Color(0xfffdf2f8) 235 | val pink100 = Color(0xfffce7f3) 236 | val pink200 = Color(0xfffbcfe8) 237 | val pink300 = Color(0xfff9a8d4) 238 | val pink400 = Color(0xfff472b6) 239 | val pink500 = Color(0xffec4899) 240 | val pink600 = Color(0xffdb2777) 241 | val pink700 = Color(0xffbe185d) 242 | val pink800 = Color(0xff9d174d) 243 | val pink900 = Color(0xff831843) 244 | 245 | val rose50 = Color(0xfffff1f2) 246 | val rose100 = Color(0xffffe4e6) 247 | val rose200 = Color(0xfffecdd3) 248 | val rose300 = Color(0xfffda4af) 249 | val rose400 = Color(0xfffb7185) 250 | val rose500 = Color(0xfff43f5e) 251 | val rose600 = Color(0xffe11d48) 252 | val rose700 = Color(0xffbe123c) 253 | val rose800 = Color(0xff9f1239) 254 | val rose900 = Color(0xff881337) 255 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=false 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | android.defaults.buildfeatures.buildconfig=true 23 | android.nonTransitiveRClass=false 24 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | androidGradlePlugin = "8.1.1" 3 | kotlinAndroid = "1.9.10" 4 | compose = "1.5.3" 5 | espressoCore = "3.5.1" 6 | junit = "1.1.5" 7 | junitVersion = "4.13.2" 8 | uiautomator = "2.2.0" 9 | 10 | [libraries] 11 | compose-bom = "androidx.compose:compose-bom:2023.09.00" 12 | compose-ui = { module = "androidx.compose.ui:ui" } 13 | compose-material = { module = "androidx.compose.material:material" } 14 | compose-tooling-debug = { module = "androidx.compose.ui:ui-tooling" } 15 | compose-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } 16 | compose-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } 17 | compose-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } 18 | 19 | androidx-core-ktx = "androidx.core:core-ktx:1.12.0" 20 | androidx-lifecycle-runtime-ktx = "androidx.lifecycle:lifecycle-runtime-ktx:2.6.2" 21 | androidx-activity-compose = "androidx.activity:activity-compose:1.7.2" 22 | androidx-navigation-compose = "androidx.navigation:navigation-compose:2.7.2" 23 | 24 | androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } 25 | androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" } 26 | androidx-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "uiautomator" } 27 | junit = { module = "junit:junit", version.ref = "junitVersion" } 28 | 29 | [plugins] 30 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } 31 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } 32 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinAndroid" } 33 | 34 | [bundles] 35 | 36 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 14 17:11:03 IST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 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 | # https://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 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /hooks/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /hooks/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | namespace = "me.pavi2410.useCompose.hooks" 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | getByName("release") { 20 | isMinifyEnabled = false 21 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 22 | } 23 | } 24 | buildFeatures { 25 | compose = true 26 | } 27 | composeOptions { 28 | kotlinCompilerExtensionVersion = libs.versions.compose.get() 29 | } 30 | kotlinOptions { 31 | jvmTarget = "17" 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | packaging { 38 | resources { 39 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | val composeBom = platform(libs.compose.bom) 46 | api(composeBom) 47 | androidTestApi(composeBom) 48 | api(libs.compose.ui) 49 | api(libs.compose.material) 50 | api(libs.compose.tooling.preview) 51 | 52 | testImplementation(libs.junit) 53 | androidTestImplementation(libs.androidx.junit) 54 | androidTestImplementation(libs.androidx.espresso.core) 55 | androidTestImplementation(libs.androidx.uiautomator) 56 | // Test rules and transitive dependencies: 57 | androidTestImplementation(libs.compose.test.junit4) 58 | // Needed for createComposeRule, but not createAndroidComposeRule: 59 | debugImplementation(libs.compose.test.manifest) 60 | } 61 | 62 | publishing { 63 | publications { 64 | register("release") { 65 | groupId = "me.pavi2410.useCompose" 66 | artifactId = "hooks" 67 | version = "1.0.0" 68 | 69 | afterEvaluate { 70 | from(components["release"]) 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /hooks/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/hooks/consumer-rules.pro -------------------------------------------------------------------------------- /hooks/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 -------------------------------------------------------------------------------- /hooks/src/androidTest/java/me/pavi2410/useCompose/hooks/HooksTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.hooks 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Surface 7 | import androidx.compose.material.Text 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.platform.testTag 10 | import androidx.compose.ui.test.* 11 | import androidx.compose.ui.test.junit4.createComposeRule 12 | import me.pavi2410.useCompose.hooks.useToggle 13 | import org.junit.Rule 14 | import org.junit.Test 15 | 16 | class HooksTest { 17 | 18 | @get:Rule 19 | val composeTestRule = createComposeRule() 20 | // use createAndroidComposeRule() if you need access to 21 | // an activity 22 | 23 | @Test 24 | fun useToggleTest() { 25 | // Start the app 26 | composeTestRule.setContent { 27 | Surface(color = MaterialTheme.colors.background) { 28 | Column { 29 | val (state, toggle) = useToggle() 30 | 31 | Text("Switch = ${if (state) "ON" else "OFF"}", Modifier.testTag("status")) 32 | 33 | Button(onClick = { toggle() }) { 34 | Text("Toggle") 35 | } 36 | } 37 | } 38 | } 39 | 40 | // initial state 41 | composeTestRule.onNodeWithTag("status").assertTextEquals("Switch = OFF") 42 | 43 | composeTestRule.onNodeWithText("Toggle").performClick() 44 | composeTestRule.onNodeWithTag("status").assertTextEquals("Switch = ON") 45 | 46 | composeTestRule.onNodeWithText("Toggle").performClick() 47 | composeTestRule.onNodeWithTag("status").assertTextEquals("Switch = OFF") 48 | } 49 | } -------------------------------------------------------------------------------- /hooks/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /hooks/src/main/java/me/pavi2410/useCompose/hooks/hooks.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.hooks 2 | 3 | import androidx.compose.runtime.* 4 | 5 | /** 6 | * See [https://usehooks.com/useToggle/] 7 | */ 8 | @Composable 9 | fun useToggle(initialState: Boolean = false): Pair Unit> { 10 | var state by remember { mutableStateOf(initialState) } 11 | 12 | fun toggle() { 13 | state = !state 14 | } 15 | 16 | return Pair(state, ::toggle) 17 | } 18 | -------------------------------------------------------------------------------- /hooks/src/test/java/me/pavi2410/useCompose/hooks/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.hooks 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk17 3 | before_install: 4 | - sdk install java 17.0.1-open 5 | - sdk use java 17.0.1-open -------------------------------------------------------------------------------- /network/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /network/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | namespace = "me.pavi2410.useCompose.network" 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | getByName("release") { 20 | isMinifyEnabled = false 21 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 22 | } 23 | } 24 | buildFeatures { 25 | compose = true 26 | } 27 | composeOptions { 28 | kotlinCompilerExtensionVersion = libs.versions.compose.get() 29 | } 30 | kotlinOptions { 31 | jvmTarget = "17" 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | packaging { 38 | resources { 39 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | val composeBom = platform(libs.compose.bom) 46 | api(composeBom) 47 | androidTestApi(composeBom) 48 | api(libs.compose.ui) 49 | api(libs.compose.material) 50 | api(libs.compose.tooling.preview) 51 | api(libs.androidx.core.ktx) 52 | 53 | testImplementation(libs.junit) 54 | androidTestImplementation(libs.androidx.junit) 55 | androidTestImplementation(libs.androidx.espresso.core) 56 | androidTestImplementation(libs.androidx.uiautomator) 57 | // Test rules and transitive dependencies: 58 | androidTestImplementation(libs.compose.test.junit4) 59 | // Needed for createComposeRule, but not createAndroidComposeRule: 60 | debugImplementation(libs.compose.test.manifest) 61 | } 62 | 63 | publishing { 64 | publications { 65 | register("release") { 66 | groupId = "me.pavi2410.useCompose" 67 | artifactId = "network" 68 | version = "1.0.0" 69 | 70 | afterEvaluate { 71 | from(components["release"]) 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /network/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/network/consumer-rules.pro -------------------------------------------------------------------------------- /network/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 -------------------------------------------------------------------------------- /network/src/androidTest/java/me/pavi2410/useCompose/network/NetworkTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.network 2 | 3 | import android.content.Intent 4 | import android.provider.Settings 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.Surface 8 | import androidx.compose.material.Text 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.platform.testTag 11 | import androidx.compose.ui.test.assertIsDisplayed 12 | import androidx.compose.ui.test.assertTextEquals 13 | import androidx.compose.ui.test.junit4.createComposeRule 14 | import androidx.compose.ui.test.onNodeWithTag 15 | import androidx.compose.ui.test.onNodeWithText 16 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 17 | import androidx.test.uiautomator.By 18 | import androidx.test.uiautomator.UiDevice 19 | import androidx.test.uiautomator.Until 20 | import org.junit.Rule 21 | import org.junit.Test 22 | 23 | 24 | class NetworkTest { 25 | 26 | @get:Rule 27 | val composeTestRule = createComposeRule() 28 | // use createAndroidComposeRule() if you need access to 29 | // an activity 30 | 31 | @Test 32 | fun useConnectionStatusTest() { 33 | // Start the app 34 | composeTestRule.setContent { 35 | Surface(color = MaterialTheme.colors.background) { 36 | Column { 37 | val isConnected = useConnectionStatus() 38 | 39 | Text( 40 | if (isConnected) "Connected" else "Disconnected", 41 | modifier = Modifier.testTag("status") 42 | ) 43 | } 44 | } 45 | } 46 | 47 | composeTestRule.onNodeWithTag("status").assertIsDisplayed() 48 | 49 | // turn on airplane mode 50 | setAirplaneMode(true) 51 | Thread.sleep(1000) 52 | 53 | composeTestRule.onNodeWithTag("status").assertTextEquals("Disconnected") 54 | 55 | // turn off airplane mode 56 | setAirplaneMode(false) 57 | Thread.sleep(1000) 58 | 59 | composeTestRule.onNodeWithTag("status").assertTextEquals("Connected") 60 | } 61 | } 62 | 63 | private fun setAirplaneMode(enable: Boolean) { 64 | if ((if (enable) 1 else 0) == Settings.System.getInt( 65 | getInstrumentation().context.contentResolver, 66 | Settings.Global.AIRPLANE_MODE_ON, 0 67 | ) 68 | ) { 69 | return 70 | } 71 | val device = UiDevice.getInstance(getInstrumentation()) 72 | device.openQuickSettings() 73 | // Find the text of your language 74 | val description = By.desc("Airplane mode") 75 | // Need to wait for the button, as the opening of quick settings is animated. 76 | device.wait(Until.hasObject(description), 500) 77 | device.findObject(description).click() 78 | getInstrumentation().context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) 79 | } -------------------------------------------------------------------------------- /network/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /network/src/main/java/me/pavi2410/useCompose/network/hooks.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.network 2 | 3 | import android.Manifest.permission.ACCESS_NETWORK_STATE 4 | import android.annotation.SuppressLint 5 | import android.net.ConnectivityManager 6 | import android.net.ConnectivityManager.NetworkCallback 7 | import android.net.Network 8 | import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET 9 | import android.net.NetworkRequest 10 | import androidx.annotation.RequiresPermission 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.platform.LocalContext 13 | import androidx.core.content.getSystemService 14 | 15 | /** 16 | * A hook to monitor active network connection. 17 | */ 18 | @RequiresPermission(ACCESS_NETWORK_STATE) 19 | @SuppressLint("ComposableNaming") 20 | @Composable 21 | fun useConnectionStatus(): Boolean { 22 | val context = LocalContext.current 23 | 24 | var isConnected by remember { mutableStateOf(false) } 25 | 26 | DisposableEffect(true) { 27 | val connectivityManager = context.getSystemService() 28 | 29 | val callback = object : NetworkCallback() { 30 | override fun onAvailable(network: Network) { 31 | isConnected = connectivityManager?.let { isNetworkConnected(it) } ?: true 32 | } 33 | 34 | override fun onLost(network: Network) { 35 | isConnected = connectivityManager?.let { isNetworkConnected(it) } ?: false 36 | } 37 | } 38 | 39 | val networkRequest = NetworkRequest.Builder() 40 | .addCapability(NET_CAPABILITY_INTERNET) 41 | .build() 42 | 43 | connectivityManager?.registerNetworkCallback(networkRequest, callback) 44 | 45 | if (connectivityManager != null) { 46 | isConnected = isNetworkConnected(connectivityManager) 47 | } 48 | 49 | onDispose { 50 | connectivityManager?.unregisterNetworkCallback(callback) 51 | } 52 | } 53 | 54 | return isConnected 55 | } 56 | 57 | private fun isNetworkConnected(connectivityManager: ConnectivityManager): Boolean { 58 | val activeNetwork = connectivityManager.activeNetworkInfo 59 | return activeNetwork?.isConnectedOrConnecting == true 60 | } -------------------------------------------------------------------------------- /network/src/test/java/me/pavi2410/useCompose/network/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.network 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /query/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /query/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | namespace = "me.pavi2410.useCompose.query" 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | getByName("release") { 20 | isMinifyEnabled = false 21 | proguardFiles( 22 | getDefaultProguardFile("proguard-android-optimize.txt"), 23 | "proguard-rules.pro" 24 | ) 25 | } 26 | } 27 | buildFeatures { 28 | compose = true 29 | } 30 | composeOptions { 31 | kotlinCompilerExtensionVersion = libs.versions.compose.get() 32 | } 33 | kotlinOptions { 34 | jvmTarget = "17" 35 | } 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_17 38 | targetCompatibility = JavaVersion.VERSION_17 39 | } 40 | packaging { 41 | resources { 42 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 43 | } 44 | } 45 | } 46 | 47 | dependencies { 48 | val composeBom = platform(libs.compose.bom) 49 | api(composeBom) 50 | androidTestApi(composeBom) 51 | api(libs.compose.ui) 52 | api(libs.compose.material) 53 | api(libs.compose.tooling.preview) 54 | 55 | testImplementation(libs.junit) 56 | androidTestImplementation(libs.androidx.junit) 57 | androidTestImplementation(libs.androidx.espresso.core) 58 | androidTestImplementation(libs.androidx.uiautomator) 59 | // Test rules and transitive dependencies: 60 | androidTestImplementation(libs.compose.test.junit4) 61 | // Needed for createComposeRule, but not createAndroidComposeRule: 62 | debugImplementation(libs.compose.test.manifest) 63 | } 64 | 65 | publishing { 66 | publications { 67 | register("release") { 68 | groupId = "me.pavi2410.useCompose" 69 | artifactId = "query" 70 | version = "1.0.0" 71 | 72 | afterEvaluate { 73 | from(components["release"]) 74 | } 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /query/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/query/consumer-rules.pro -------------------------------------------------------------------------------- /query/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 -------------------------------------------------------------------------------- /query/src/androidTest/java/me/pavi2410/useCompose/query/ReactQueryTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.query 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Surface 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.getValue 9 | import androidx.compose.runtime.mutableStateOf 10 | import androidx.compose.runtime.remember 11 | import androidx.compose.runtime.setValue 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.platform.testTag 14 | import androidx.compose.ui.test.assertIsDisplayed 15 | import androidx.compose.ui.test.assertTextEquals 16 | import androidx.compose.ui.test.junit4.createComposeRule 17 | import androidx.compose.ui.test.onNodeWithTag 18 | import androidx.compose.ui.test.performClick 19 | import kotlinx.coroutines.delay 20 | import org.junit.Rule 21 | import org.junit.Test 22 | 23 | class ReactQueryTest { 24 | 25 | @get:Rule 26 | val composeTestRule = createComposeRule() 27 | // use createAndroidComposeRule() if you need access to 28 | // an activity 29 | 30 | @Suppress("BlockingMethodInNonBlockingContext") 31 | @Test 32 | fun useQueryTest() { 33 | val someData = """{data: "some data"}""" 34 | 35 | // Start the app 36 | composeTestRule.setContent { 37 | Surface(color = MaterialTheme.colors.background) { 38 | Column { 39 | val queryResult by useQuery { 40 | fetchSomeData(someData) 41 | } 42 | 43 | when (queryResult) { 44 | is QueryState.Loading -> { 45 | Text("Fetching data...", modifier = Modifier.testTag("loading")) 46 | } 47 | is QueryState.Error -> { 48 | Text("something went wrong...", modifier = Modifier.testTag("error")) 49 | } 50 | is QueryState.Content -> { 51 | val data = (queryResult as QueryState.Content).data 52 | Text("got data -> $data", modifier = Modifier.testTag("data")) 53 | } 54 | } 55 | } 56 | } 57 | } 58 | 59 | composeTestRule.onNodeWithTag("loading").assertIsDisplayed() 60 | composeTestRule.onNodeWithTag("error").assertDoesNotExist() 61 | composeTestRule.onNodeWithTag("data").assertDoesNotExist() 62 | 63 | Thread.sleep(1000) // wait for data to fetch 64 | 65 | composeTestRule.onNodeWithTag("loading").assertDoesNotExist() 66 | composeTestRule.onNodeWithTag("data").assertIsDisplayed() 67 | composeTestRule.onNodeWithTag("data").assertTextEquals("got data -> $someData") 68 | } 69 | 70 | @Test 71 | fun useMutationTest() { 72 | // Start the app 73 | composeTestRule.setContent { 74 | Surface(color = MaterialTheme.colors.background) { 75 | Column { 76 | var token by remember { mutableStateOf("") } 77 | 78 | val loginMutation = useMutation { (username, password) -> 79 | doLogin(username, password) 80 | } 81 | 82 | Button( 83 | modifier = Modifier.testTag("login_button"), 84 | onClick = { 85 | // todo: is this blocking the main thread? 86 | // todo: this makes me think I need a mutateAsync too... 87 | loginMutation.mutate("pavi2410", "secretpw123") { 88 | token = it 89 | } 90 | } 91 | ) { 92 | Text("Login") 93 | } 94 | 95 | Text( 96 | if (token.isEmpty()) "Please login" else "Welcome! token = $token", 97 | modifier = Modifier.testTag("status") 98 | ) 99 | } 100 | } 101 | } 102 | 103 | composeTestRule.onNodeWithTag("login_button").performClick() 104 | composeTestRule.onNodeWithTag("status").assertTextEquals("Please login") 105 | 106 | Thread.sleep(1000) // wait for data to fetch 107 | 108 | // verify that mutation is called 109 | composeTestRule.onNodeWithTag("status").assertTextEquals("Welcome! token = secret_token") 110 | } 111 | 112 | private suspend fun fetchSomeData(someData: String): String { 113 | delay(500) 114 | return someData 115 | } 116 | 117 | private suspend fun doLogin(username: String, password: String): String { 118 | delay(5) 119 | return "secret_token" 120 | } 121 | } -------------------------------------------------------------------------------- /query/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /query/src/main/java/me/pavi2410/useCompose/query/hooks.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.query 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.runtime.* 5 | import kotlinx.coroutines.* 6 | 7 | sealed interface QueryState { 8 | object Loading : QueryState 9 | data class Error(val message: Throwable) : QueryState 10 | data class Content(val data: T) : QueryState 11 | } 12 | 13 | sealed interface MutationState { 14 | object Idle : MutationState 15 | object Loading : MutationState 16 | data class Error(val message: Throwable) : MutationState 17 | data class Content(val data: T) : MutationState 18 | } 19 | 20 | interface Mutation { 21 | fun mutate(vararg args: String, callback: (T) -> Unit) 22 | fun cancel() 23 | // val state: MutationState 24 | } 25 | 26 | @Composable 27 | fun useQuery(query: suspend CoroutineScope.() -> T): State> { 28 | return produceState>(initialValue = QueryState.Loading) { 29 | value = withContext(Dispatchers.IO) { 30 | val res = query() 31 | QueryState.Content(res) 32 | } 33 | } 34 | } 35 | 36 | @SuppressLint("ComposableNaming") 37 | @Composable 38 | fun useMutation(query: suspend CoroutineScope.(args: Array) -> T): Mutation { 39 | // var mutationState by remember { mutableStateOf(MutationState.Idle) } 40 | val coroutineScope = rememberCoroutineScope() 41 | return object: Mutation { 42 | override fun mutate(vararg args: String, callback: (T) -> Unit) { 43 | coroutineScope.launch { 44 | // mutationState = MutationState.Loading 45 | callback(query(args)) 46 | // mutationState = MutationState.Success 47 | } 48 | } 49 | override fun cancel() { 50 | coroutineScope.cancel() 51 | } 52 | 53 | // override val state: MutationState 54 | // get() = mutationState 55 | } 56 | } -------------------------------------------------------------------------------- /react/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /react/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | id("maven-publish") 5 | } 6 | 7 | android { 8 | compileSdk = 34 9 | namespace = "me.pavi2410.useCompose.react" 10 | 11 | defaultConfig { 12 | minSdk = 21 13 | 14 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 15 | consumerProguardFiles("consumer-rules.pro") 16 | } 17 | 18 | buildTypes { 19 | getByName("release") { 20 | isMinifyEnabled = false 21 | proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") 22 | } 23 | } 24 | buildFeatures { 25 | compose = true 26 | } 27 | composeOptions { 28 | kotlinCompilerExtensionVersion = libs.versions.compose.get() 29 | } 30 | kotlinOptions { 31 | jvmTarget = "17" 32 | } 33 | compileOptions { 34 | sourceCompatibility = JavaVersion.VERSION_17 35 | targetCompatibility = JavaVersion.VERSION_17 36 | } 37 | packaging { 38 | resources { 39 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 40 | } 41 | } 42 | } 43 | 44 | dependencies { 45 | val composeBom = platform(libs.compose.bom) 46 | api(composeBom) 47 | androidTestApi(composeBom) 48 | api(libs.compose.ui) 49 | api(libs.compose.material) 50 | api(libs.compose.tooling.preview) 51 | 52 | testImplementation(libs.junit) 53 | androidTestImplementation(libs.androidx.junit) 54 | androidTestImplementation(libs.androidx.espresso.core) 55 | androidTestImplementation(libs.androidx.uiautomator) 56 | // Test rules and transitive dependencies: 57 | androidTestImplementation(libs.compose.test.junit4) 58 | // Needed for createComposeRule, but not createAndroidComposeRule: 59 | debugImplementation(libs.compose.test.manifest) 60 | } 61 | 62 | afterEvaluate { 63 | publishing { 64 | publications { 65 | // Creates a Maven publication called "release". 66 | create("maven") { 67 | from(components["release"]) 68 | 69 | groupId = "me.pavi2410.useCompose" 70 | artifactId = "react" 71 | version = "1.0.0" 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /react/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pavi2410/useCompose/0dd9b6b5821a025c43a66b6fe84769232d8393b5/react/consumer-rules.pro -------------------------------------------------------------------------------- /react/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 -------------------------------------------------------------------------------- /react/src/androidTest/java/me/pavi2410/useCompose/react/HooksTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.react 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.material.Button 5 | import androidx.compose.material.MaterialTheme 6 | import androidx.compose.material.Surface 7 | import androidx.compose.material.Text 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.platform.testTag 10 | import androidx.compose.ui.test.* 11 | import androidx.compose.ui.test.junit4.createComposeRule 12 | import org.junit.Rule 13 | import org.junit.Test 14 | 15 | class HooksTest { 16 | 17 | @get:Rule 18 | val composeTestRule = createComposeRule() 19 | // use createAndroidComposeRule() if you need access to 20 | // an activity 21 | 22 | @Test 23 | fun useStateTest() { 24 | // Start the app 25 | composeTestRule.setContent { 26 | Surface(color = MaterialTheme.colors.background) { 27 | Column { 28 | val (count, setCount) = useState(0) 29 | 30 | Text("You clicked $count times") 31 | 32 | Button(onClick = { setCount(count + 1) }) { 33 | Text("Click me") 34 | } 35 | } 36 | } 37 | } 38 | 39 | repeat(5) { 40 | composeTestRule.onNodeWithText("Click me").performClick() 41 | } 42 | 43 | composeTestRule.onNodeWithText("You clicked 5 times").assertIsDisplayed() 44 | } 45 | 46 | @Test 47 | fun useEffectTest() { 48 | var onEnterInvoked = 0 49 | var countUpdateInvoked = 0 50 | 51 | // Start the app 52 | composeTestRule.setContent { 53 | Surface(color = MaterialTheme.colors.background) { 54 | Column { 55 | val (count, setCount) = useState(0) 56 | 57 | useEffect { 58 | onEnterInvoked++ 59 | } 60 | 61 | useEffect(count) { 62 | countUpdateInvoked++ 63 | } 64 | 65 | Text("You clicked $count times") 66 | 67 | Button(onClick = { setCount(count + 1) }) { 68 | Text("Click me") 69 | } 70 | } 71 | } 72 | } 73 | 74 | assert(onEnterInvoked == 1) 75 | 76 | repeat(5) { 77 | composeTestRule.onNodeWithText("Click me").performClick() 78 | } 79 | 80 | assert(onEnterInvoked == 1) 81 | assert(countUpdateInvoked == 5) 82 | } 83 | 84 | @Suppress("LocalVariableName") 85 | @Test 86 | fun useContextTest() { 87 | composeTestRule.setContent { 88 | val MyContext = createContext("") 89 | 90 | Column { 91 | MyContext.Provider(value = "outer") { 92 | val outerContextValue = useContext(MyContext) 93 | Text("context value is $outerContextValue", modifier = Modifier.testTag("outer")) 94 | 95 | MyContext.Provider(value = "inner") { 96 | val innerContextValue = useContext(MyContext) 97 | Text("context value is $innerContextValue", modifier = Modifier.testTag("inner")) 98 | } 99 | } 100 | } 101 | } 102 | 103 | composeTestRule.onNodeWithTag("outer").assertTextEquals("context value is outer") 104 | composeTestRule.onNodeWithTag("inner").assertTextEquals("context value is inner") 105 | } 106 | 107 | @Test 108 | fun useReducerTest() { 109 | val initialState = MyState(0) 110 | 111 | // Start the app 112 | composeTestRule.setContent { 113 | Surface(color = MaterialTheme.colors.background) { 114 | Column { 115 | val (state, dispatch) = useReducer(initialState) { state, action -> 116 | when (action.type) { 117 | "increment" -> state.copy(count = state.count + 1) 118 | "decrement" -> state.copy(count = state.count - 1) 119 | else -> throw Error() 120 | } 121 | } 122 | 123 | Text("Count: ${state.count}") 124 | Button(onClick = { 125 | dispatch(MyAction("increment")) 126 | }) { 127 | Text("+") 128 | } 129 | Button(onClick = { 130 | dispatch(MyAction("decrement")) 131 | }) { 132 | Text("-") 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | data class MyState(val count: Int) 141 | data class MyAction(val type: String) -------------------------------------------------------------------------------- /react/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react/src/main/java/me/pavi2410/useCompose/react/hooks.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.react 2 | 3 | import android.annotation.SuppressLint 4 | import androidx.compose.runtime.* 5 | import kotlinx.coroutines.CoroutineScope 6 | 7 | /** 8 | * A React-ish hook that saves state across recompositions. 9 | * 10 | * @see [useState](https://reactjs.org/docs/hooks-state.html) 11 | */ 12 | @SuppressLint("ComposableNaming") 13 | @Composable 14 | fun useState(defaultValue: T): Pair Unit> { 15 | val (state, setState) = remember { mutableStateOf(defaultValue) } 16 | 17 | return Pair(state, setState) 18 | } 19 | 20 | /** 21 | * A React-ish hook that triggers on recompositions or state changes. 22 | * 23 | * @see [useEffect](https://reactjs.org/docs/hooks-effect.html) 24 | */ 25 | @SuppressLint("ComposableNaming") 26 | @Composable 27 | fun useEffect(vararg deps: Any, block: suspend CoroutineScope.() -> Unit) { 28 | LaunchedEffect(keys = deps, block = block) 29 | } 30 | 31 | @Suppress("PropertyName") 32 | interface ReactContext { 33 | val LocalCtx: ProvidableCompositionLocal 34 | @Composable 35 | fun Provider(value: T, content: @Composable () -> Unit) 36 | } 37 | 38 | /** 39 | * Composable function to create an context object. 40 | */ 41 | @Composable 42 | fun createContext(initialValue: T): ReactContext { 43 | return object: ReactContext { 44 | override val LocalCtx = compositionLocalOf { initialValue } 45 | @Composable 46 | override fun Provider(value: T, content: @Composable () -> Unit) { 47 | CompositionLocalProvider( 48 | LocalCtx provides value, 49 | content = content 50 | ) 51 | } 52 | } 53 | } 54 | 55 | /** 56 | * A React-ish hook that returns the current value for that context. 57 | * 58 | * @see [useContext](https://reactjs.org/docs/hooks-reference.html#usecontext) 59 | */ 60 | @SuppressLint("ComposableNaming") 61 | @Composable 62 | fun useContext(context: ReactContext): T { 63 | return context.LocalCtx.current 64 | } 65 | 66 | /** 67 | * A hook from React that allows you to create a state machine using a [reducer] function. 68 | * 69 | * @see [useReducer](https://reactjs.org/docs/hooks-reference.html#usereducer) 70 | */ 71 | @Composable 72 | fun useReducer(initialState: S, reducer: (S, A) -> S): Pair Unit> { 73 | var thisState by remember { mutableStateOf(initialState) } 74 | 75 | fun dispatch(action: A) { 76 | thisState = reducer(thisState, action) 77 | } 78 | 79 | return Pair(thisState, ::dispatch) 80 | } -------------------------------------------------------------------------------- /react/src/test/java/me/pavi2410/useCompose/react/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package me.pavi2410.useCompose.react 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 18 | 19 | rootProject.name = "useCompose" 20 | 21 | include(":app") 22 | include(":react") 23 | include(":hooks") 24 | include(":network") 25 | include(":colors") 26 | include(":query") 27 | --------------------------------------------------------------------------------