├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── themes.xml │ │ │ │ └── colors.xml │ │ │ ├── drawable │ │ │ │ ├── texture.jpg │ │ │ │ └── ic_launcher_background.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 │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── elixer │ │ │ │ └── surface │ │ │ │ ├── ui │ │ │ │ ├── theme │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Type.kt │ │ │ │ │ └── Theme.kt │ │ │ │ ├── ModernComponent.kt │ │ │ │ ├── CutomComponent.kt │ │ │ │ └── OldMainActivity.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── elixer │ │ │ └── surface │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── elixer │ │ └── surface │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── veneer ├── .gitignore ├── consumer-rules.pro ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── elixer │ │ │ └── veneer │ │ │ ├── Color.kt │ │ │ ├── Utils.kt │ │ │ ├── Veneer.kt │ │ │ └── composables │ │ │ ├── RadialReflectiveButton.kt │ │ │ ├── ReactiveGradientButton.kt │ │ │ ├── GlossyButton.kt │ │ │ └── LinearReflectiveButton.kt │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── elixer │ │ │ └── veneer │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── elixer │ │ └── veneer │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── jitpack.yml ├── assets ├── example.png └── veneer.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── gradle.properties ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /veneer/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /veneer/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 -------------------------------------------------------------------------------- /assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/assets/example.png -------------------------------------------------------------------------------- /assets/veneer.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/assets/veneer.gif -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Surface 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/texture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/drawable/texture.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shivamdhuria/veneer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /veneer/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Nov 20 22:35:23 IST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | maven { url 'https://jitpack.io' } 7 | } 8 | } 9 | rootProject.name = "Surface" 10 | include ':app' 11 | include ':veneer' 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface.ui.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/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/test/java/com/elixer/surface/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface 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 | } -------------------------------------------------------------------------------- /veneer/src/test/java/com/elixer/veneer/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer 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 | } -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/Color.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | 6 | val WHITE200 = Color(0xFFFFFFFF) 7 | val WHITE800 = Color(0xFF5B5B5B) 8 | val WHITE400 = Color(0xFFC5C5C5) 9 | 10 | val BLUE = Color(0xFF1C86EB) 11 | val PINK = Color(0xFFE67FC8) 12 | 13 | val GOLD400 = Color(0xFFDBAE53) 14 | val GOLD200 = Color(0xFFFAF8D6) 15 | val GOLD100 = Color(0xFFECEAC9) 16 | val GOLD300 = Color(0xFFE4DF92) 17 | 18 | val BLUE_DARK = Color(0xFF11677E) 19 | val BLUE_LIGHT = Color(0xFFB6D8DA) 20 | -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer 2 | 3 | class Utils { 4 | 5 | companion object{ 6 | val RADIAL_SILVER = listOf( 7 | WHITE200, 8 | WHITE200, 9 | WHITE400, 10 | WHITE800, 11 | WHITE200, 12 | WHITE400, 13 | WHITE400, 14 | WHITE800, 15 | WHITE200, 16 | ) 17 | 18 | val RADIAL_GOLD = listOf( 19 | GOLD200, 20 | GOLD200, 21 | GOLD300, 22 | GOLD400, 23 | GOLD200, 24 | GOLD300, 25 | GOLD300, 26 | GOLD400, 27 | GOLD200, 28 | ) 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple200 = Color(0xFFBB86FC) 6 | val Purple100 = Color(0xFFE2D1F7) 7 | val Purple500 = Color(0xFF6200EE) 8 | val Purple700 = Color(0xFF3700B3) 9 | val Teal200 = Color(0xFF03DAC5) 10 | 11 | val WHITE200 = Color(0xFFFFFFFF) 12 | val WHITE800 = Color(0xFF5B5B5B) 13 | val WHITE400 = Color(0xFFC5C5C5) 14 | 15 | val GOLD400 = Color(0xFFF7D147) 16 | val GREY800 = Color(0xFF1D1D1C) 17 | val GREY600 = Color(0xFF292929) 18 | 19 | val BLUE = Color(0xFF1C86EB) 20 | val PINK = Color(0xFFE67FC8) 21 | val GOLD800= Color(0xFF5E4F1C) 22 | val BLUE200= Color(0xFFE2F4FC) 23 | val BLUE100= Color(0xFFDBF2FC) -------------------------------------------------------------------------------- /app/src/androidTest/java/com/elixer/surface/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.elixer.surface", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /veneer/src/androidTest/java/com/elixer/veneer/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.elixer.veneer.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /veneer/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface.ui.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/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface.ui.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 SurfaceTheme(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 | } -------------------------------------------------------------------------------- /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=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Enables namespacing of each library's R class so that its R class includes only the 23 | # resources declared in the library itself and none from the library's dependencies, 24 | # thereby reducing the size of the R class for that library 25 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 31 8 | 9 | defaultConfig { 10 | applicationId "com.elixer.surface" 11 | minSdk 26 12 | targetSdk 31 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 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | } 35 | buildFeatures { 36 | compose true 37 | } 38 | composeOptions { 39 | kotlinCompilerExtensionVersion compose_version 40 | kotlinCompilerVersion kotlin_version 41 | } 42 | packagingOptions { 43 | resources { 44 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 45 | } 46 | } 47 | } 48 | 49 | dependencies { 50 | 51 | implementation 'androidx.core:core-ktx:1.7.0' 52 | implementation "androidx.compose.ui:ui:$compose_version" 53 | implementation "androidx.compose.material:material:$compose_version" 54 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 55 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' 56 | implementation 'androidx.activity:activity-compose:1.3.1' 57 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 58 | 59 | implementation project(path: ':veneer') 60 | 61 | testImplementation 'junit:junit:4.13.2' 62 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 63 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 64 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 65 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 66 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # veneer 2 | ### reactive buttons for Jetpack Compose 3 | [![](https://jitpack.io/v/Shivamdhuria/veneer.svg)](https://jitpack.io/#Shivamdhuria/veneer) 4 | 5 | 6 | veneer is a library for reactive buttons. The buttons react depededing upon the roll, pitch and azimuth angle of the phone which is calculated using accelerometer and magnetic field sensor. 7 | 8 | ![Veneer Example](https://github.com/Shivamdhuria/veneer/blob/main/assets/veneer.gif) 9 | 10 | 11 | # Installing 12 | To download it from the jitpack, add these lines in your root build.gradle at the end of repositories: 13 | 14 | ```gradle 15 | allprojects { 16 | repositories { 17 | maven { url 'https://jitpack.io' } 18 | } 19 | } 20 | ``` 21 | 22 | And then add to the module's build.gradle 23 | 24 | ```gradle 25 | implementation 'com.github.Shivamdhuria:veneer:VERSION'" 26 | ``` 27 | 28 | # How to use 29 | 30 | To add a reflective button, you need to first instantiate veneer. Make sure you stop veneer as in OnStop() or onPause() as veneer relies upon data from accelerometer and magnetic field sensor. 31 | 32 | ```Kotlin 33 | override fun onResume() { 34 | super.onResume() 35 | Veneer.init(this) 36 | } 37 | 38 | override fun onPause() { 39 | super.onPause() 40 | Veneer.stop() 41 | } 42 | 43 | ``` 44 | 45 | To use any veneer button just add veneer composable, as you'd add a regular button composable. 46 | 47 | ```Kotlin 48 | override fun onCreate(savedInstanceState: Bundle?) { 49 | super.onCreate(savedInstanceState) 50 | 51 | setContent { 52 | val scrollState = rememberScrollState() 53 | 54 | val azimuthAngle by Veneer.azimuthAngle.collectAsState() 55 | val pitchAngle by Veneer.pitchAngle.collectAsState() 56 | val rollAngle by Veneer.rollAngle.collectAsState() 57 | 58 | SurfaceTheme() { 59 | Surface() { 60 | Box(modifier = Modifier.fillMaxSize()) { 61 | 62 | RadialReflectiveButton(rotationValue = rollAngle, onClick = {}) 63 | { 64 | Icon(Icons.Outlined.Pause, contentDescription = "content description", tint = GREY600) 65 | } 66 | 67 | } 68 | } 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | 75 | -------------------------------------------------------------------------------- /veneer/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'maven-publish' 5 | 6 | } 7 | 8 | android { 9 | compileSdk 31 10 | 11 | defaultConfig { 12 | minSdk 26 13 | targetSdk 31 14 | 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | consumerProguardFiles "consumer-rules.pro" 17 | vectorDrawables { 18 | useSupportLibrary true 19 | } 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | composeOptions { 33 | kotlinCompilerExtensionVersion compose_version 34 | kotlinCompilerVersion kotlin_version 35 | } 36 | buildFeatures { 37 | compose = true 38 | } 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | } 43 | 44 | dependencies { 45 | 46 | implementation 'androidx.core:core-ktx:1.7.0' 47 | implementation "androidx.compose.ui:ui:$compose_version" 48 | implementation "androidx.compose.material:material:$compose_version" 49 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 50 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' 51 | implementation 'androidx.activity:activity-compose:1.3.1' 52 | 53 | testImplementation 'junit:junit:4.13.2' 54 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 55 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 56 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 57 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 58 | } 59 | 60 | afterEvaluate { 61 | publishing { 62 | publications { 63 | // Creates a Maven publication called "release". 64 | release(MavenPublication) { 65 | // Applies the component for the release build variant. 66 | from components.release 67 | 68 | // You can then customize attributes of the publication as shown below. 69 | groupId = 'com.github.Shivamdhuria' 70 | artifactId = 'veneer' 71 | version = '0.3.4' 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/Veneer.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer 2 | 3 | import android.content.Context 4 | import android.hardware.Sensor 5 | import android.hardware.SensorEvent 6 | import android.hardware.SensorEventListener 7 | import android.hardware.SensorManager 8 | import android.hardware.SensorManager.SENSOR_DELAY_UI 9 | import kotlinx.coroutines.flow.MutableStateFlow 10 | 11 | object Veneer : SensorEventListener { 12 | 13 | const val SAMPLING_PERIOD_UI = SENSOR_DELAY_UI 14 | 15 | private val accelerometerReading = FloatArray(3) 16 | private val magnetometerReading = FloatArray(3) 17 | private val rotationMatrix = FloatArray(9) 18 | private val orientationAngles = FloatArray(3) 19 | 20 | private var sensorManager: SensorManager? = null 21 | 22 | val rollAngle = MutableStateFlow(0f) 23 | val pitchAngle = MutableStateFlow(0f) 24 | val azimuthAngle = MutableStateFlow(0f) 25 | 26 | fun init(context: Context) { 27 | sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager 28 | registerListeners() 29 | } 30 | 31 | fun stop() { 32 | unregisterListener() 33 | sensorManager = null 34 | } 35 | 36 | private fun registerListeners() { 37 | sensorManager?.let { sensorManager -> 38 | sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer -> 39 | sensorManager.registerListener( 40 | this, 41 | accelerometer, 42 | 43 | SENSOR_DELAY_UI 44 | ) 45 | } 46 | sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField -> 47 | sensorManager.registerListener( 48 | this, 49 | magneticField, 50 | SAMPLING_PERIOD_UI, 51 | SENSOR_DELAY_UI 52 | ) 53 | } 54 | } 55 | } 56 | 57 | private fun unregisterListener() { 58 | sensorManager?.unregisterListener(this) 59 | } 60 | 61 | override fun onSensorChanged(event: SensorEvent) { 62 | if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) { 63 | System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size) 64 | } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { 65 | System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size) 66 | } 67 | updateOrientationAngles() 68 | } 69 | 70 | override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { 71 | 72 | } 73 | 74 | private fun updateOrientationAngles() { 75 | // Update rotation matrix, which is needed to update orientation angles. 76 | SensorManager.getRotationMatrix( 77 | rotationMatrix, 78 | null, 79 | accelerometerReading, 80 | magnetometerReading 81 | ) 82 | 83 | // "rotationMatrix" now has up-to-date information. 84 | SensorManager.getOrientation(rotationMatrix, orientationAngles) 85 | azimuthAngle.value = (orientationAngles[0] * (180 / Math.PI)).toFloat() 86 | pitchAngle.value = (orientationAngles[1] * (180 / Math.PI)).toFloat() 87 | rollAngle.value = (orientationAngles[2] * (180 / Math.PI)).toFloat() 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/ModernComponent.kt: -------------------------------------------------------------------------------- 1 | //package com.elixer.surface.ui 2 | // 3 | //import androidx.compose.animation.core.LinearEasing 4 | //import androidx.compose.animation.core.animateFloatAsState 5 | //import androidx.compose.animation.core.tween 6 | //import androidx.compose.foundation.layout.Arrangement 7 | //import androidx.compose.foundation.layout.Column 8 | //import androidx.compose.foundation.layout.size 9 | //import androidx.compose.material.Text 10 | //import androidx.compose.runtime.Composable 11 | //import androidx.compose.runtime.getValue 12 | //import androidx.compose.runtime.mutableStateOf 13 | //import androidx.compose.runtime.remember 14 | //import androidx.compose.ui.Alignment 15 | //import androidx.compose.ui.Modifier 16 | //import androidx.compose.ui.draw.drawBehind 17 | //import androidx.compose.ui.geometry.CornerRadius 18 | //import androidx.compose.ui.geometry.Size 19 | //import androidx.compose.ui.graphics.Brush 20 | //import androidx.compose.ui.graphics.Color 21 | //import androidx.compose.ui.graphics.Color.Companion.Blue 22 | //import androidx.compose.ui.graphics.Color.Companion.Red 23 | //import androidx.compose.ui.graphics.Color.Companion.White 24 | //import androidx.compose.ui.graphics.TileMode 25 | //import androidx.compose.ui.graphics.drawscope.DrawScope 26 | //import androidx.compose.ui.semantics.SemanticsProperties.Text 27 | //import androidx.compose.ui.text.font.FontWeight 28 | //import androidx.compose.ui.text.input.KeyboardType.Companion.Text 29 | //import androidx.compose.ui.tooling.preview.Preview 30 | //import androidx.compose.ui.unit.Dp 31 | //import androidx.compose.ui.unit.dp 32 | //import com.elixer.surface.ui.theme.BLUE 33 | //import com.elixer.surface.ui.theme.PINK 34 | //import kotlin.math.absoluteValue 35 | //import kotlin.math.roundToInt 36 | // 37 | //@Composable 38 | //fun modernButton( 39 | // canvasSize: Dp = 300.dp, 40 | // rotationValue: Float, 41 | // colorBegin: Color = PINK, 42 | // colorEnd: Color = BLUE, 43 | //) { 44 | // val lastRotation = remember { mutableStateOf(0f) } // this keeps last rotation 45 | // val difference = rotationValue - lastRotation.value 46 | // val time = 1000 / (difference.absoluteValue) 47 | // lastRotation.value = rotationValue 48 | // 49 | // //converting angle to a float value and reducing sensitivity 50 | // val angle: Float by animateFloatAsState( 51 | // targetValue = rotationValue / 120f, 52 | // animationSpec = tween( 53 | // durationMillis = 200, 54 | // easing = LinearEasing 55 | // ) 56 | // ) 57 | // 58 | // Column( 59 | // verticalArrangement = Arrangement.Center, 60 | // horizontalAlignment = Alignment.CenterHorizontally, 61 | // modifier = Modifier 62 | // .size(height = canvasSize / 3, width = canvasSize) 63 | // .drawBehind { 64 | // val componentSize = size / 1.25f 65 | // drawRoundedRectangle(colorBegin,colorEnd, angle) 66 | // 67 | // }, 68 | // 69 | // ) { 70 | // Text("Modern Button", fontWeight = FontWeight.Bold, color = White) 71 | //// Text(angle.toString()) 72 | // } 73 | //} 74 | // 75 | //fun DrawScope.drawRoundedRectangle( 76 | // colorBegin: Color, 77 | // colorEnd:Color, 78 | // angle: Float 79 | //) { 80 | // drawRoundRect( 81 | //// brush = Brush.horizontalGradient( 82 | //// colors 83 | //// ), 84 | // brush = Brush.horizontalGradient( 85 | //// 0.0f to PINK, 86 | // 0.0f + angle to colorBegin, 87 | // 1.0f + angle to colorEnd, 88 | // 89 | // ), 90 | //// size = Size( 91 | //// width = 300.dp.toPx(), 92 | //// height = 150.dp.toPx() 93 | //// ), 94 | //// topLeft = Offset( 95 | //// x = 60.dp.toPx(), 96 | //// y = 60.dp.toPx() 97 | //// ), 98 | // cornerRadius = CornerRadius( 99 | // x = 10.dp.toPx(), 100 | // y = 10.dp.toPx() 101 | // ) 102 | // ) 103 | //} 104 | // 105 | //@Preview(showBackground = true, showSystemUi = true) 106 | //@Composable 107 | //fun modernButtonPreview() { 108 | // modernButton(canvasSize = 100.dp, rotationValue = 0f) 109 | //} -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/CutomComponent.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface.ui 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.animateFloatAsState 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.foundation.Canvas 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.runtime.mutableStateOf 11 | import androidx.compose.runtime.remember 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.draw.drawBehind 15 | import androidx.compose.ui.draw.rotate 16 | import androidx.compose.ui.geometry.Offset 17 | import androidx.compose.ui.geometry.Rect 18 | import androidx.compose.ui.graphics.* 19 | import androidx.compose.ui.graphics.drawscope.DrawScope 20 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 21 | import androidx.compose.ui.graphics.drawscope.rotate 22 | import androidx.compose.ui.tooling.preview.Preview 23 | import androidx.compose.ui.unit.Dp 24 | import androidx.compose.ui.unit.dp 25 | import com.elixer.surface.ui.theme.GREY800 26 | import com.elixer.surface.ui.theme.WHITE200 27 | import com.elixer.surface.ui.theme.WHITE400 28 | import com.elixer.surface.ui.theme.WHITE800 29 | import kotlin.math.absoluteValue 30 | import kotlin.math.roundToInt 31 | 32 | @Composable 33 | fun metallicComponent( 34 | canvasSize: Dp = 300.dp, 35 | rotationValue: Float, 36 | colors: List = listOf( 37 | WHITE200, 38 | WHITE200, 39 | WHITE400, 40 | WHITE800, 41 | WHITE200, 42 | WHITE400, 43 | WHITE400, 44 | WHITE800, 45 | WHITE200, 46 | ) 47 | ) { 48 | val lastRotation = remember { mutableStateOf(0f) } // this keeps last rotation 49 | val difference = rotationValue - lastRotation.value 50 | val time = 900 / (difference.absoluteValue) 51 | lastRotation.value = rotationValue 52 | 53 | val angle: Float by animateFloatAsState( 54 | targetValue = rotationValue, 55 | animationSpec = tween( 56 | durationMillis = time.roundToInt(), 57 | easing = LinearEasing 58 | ) 59 | ) 60 | 61 | Column( 62 | verticalArrangement = Arrangement.Center, 63 | horizontalAlignment = Alignment.CenterHorizontally, 64 | modifier = Modifier 65 | .size(canvasSize) 66 | .drawBehind { 67 | val componentSize = size / 1.25f 68 | rotate(degrees = angle) { 69 | drawCircle(colors) 70 | } 71 | }, 72 | 73 | ) { 74 | getTriangle() 75 | } 76 | } 77 | 78 | @Composable 79 | private fun getTriangle() { 80 | Canvas( 81 | modifier = Modifier 82 | .fillMaxSize() 83 | .aspectRatio(1f) 84 | .rotate(0f) 85 | ) { 86 | val canvasWidth = size.width / 1.9f 87 | val canvasHeight = size.height / 2f 88 | val rect = Rect(center = Offset(canvasWidth, canvasHeight), canvasWidth / 3f) 89 | val trianglePath = Path().apply { 90 | moveTo(rect.centerRight) 91 | lineTo(rect.bottomLeft) 92 | lineTo(rect.topLeft) 93 | // note that two more point repeats needed to round all corners 94 | lineTo(rect.centerRight) 95 | lineTo(rect.bottomLeft) 96 | } 97 | 98 | drawIntoCanvas { canvas -> 99 | canvas.drawOutline( 100 | outline = Outline.Generic(trianglePath), 101 | paint = Paint().apply { 102 | 103 | color = GREY800 104 | pathEffect = PathEffect.cornerPathEffect(rect.maxDimension / 12) 105 | } 106 | ) 107 | } 108 | } 109 | } 110 | 111 | fun Path.moveTo(offset: Offset) = moveTo(offset.x, offset.y) 112 | fun Path.lineTo(offset: Offset) = lineTo(offset.x, offset.y) 113 | 114 | fun DrawScope.drawCircle( 115 | colors: List 116 | ) { 117 | drawCircle( 118 | brush = Brush.sweepGradient( 119 | colors 120 | ), 121 | ) 122 | } 123 | 124 | @Preview(showBackground = true, showSystemUi = true) 125 | @Composable 126 | fun customComponentPreview() { 127 | metallicComponent(canvasSize = 100.dp, rotationValue = 0f) 128 | } -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/composables/RadialReflectiveButton.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer.composables 2 | 3 | import androidx.compose.animation.core.Easing 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.BorderStroke 8 | import androidx.compose.foundation.interaction.MutableInteractionSource 9 | import androidx.compose.foundation.layout.* 10 | import androidx.compose.material.* 11 | import androidx.compose.material.ripple.rememberRipple 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.drawBehind 16 | import androidx.compose.ui.graphics.Brush 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.graphics.Shape 19 | import androidx.compose.ui.graphics.drawscope.DrawScope 20 | import androidx.compose.ui.graphics.drawscope.rotate 21 | import androidx.compose.ui.semantics.Role 22 | import androidx.compose.ui.tooling.preview.Preview 23 | import androidx.compose.ui.unit.dp 24 | import com.elixer.veneer.Utils.Companion.RADIAL_SILVER 25 | 26 | @OptIn(ExperimentalMaterialApi::class) 27 | @Composable 28 | fun RadialReflectiveButton( 29 | rotationValue: Float, 30 | colorList: List = RADIAL_SILVER, 31 | animationDurationInMillis: Int = 300, 32 | animationEasing :Easing = LinearEasing, 33 | sensitivityInverseConstant:Float = 1f, 34 | colors: ButtonColors = ButtonDefaults.buttonColors(), 35 | onClick: () -> Unit, 36 | modifier: Modifier = Modifier, 37 | enabled: Boolean = true, 38 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 39 | elevation: ButtonElevation? = ButtonDefaults.elevation(), 40 | shape: Shape = MaterialTheme.shapes.small, 41 | border: BorderStroke? = null, 42 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 43 | content: @Composable RowScope.() -> Unit 44 | ) { 45 | // this keeps last rotation, will use it later for something 46 | val lastRotation = remember { mutableStateOf(0f) } 47 | 48 | // lastRotation.value = rotationValue 49 | 50 | val angle: Float by animateFloatAsState( 51 | targetValue = rotationValue/sensitivityInverseConstant, 52 | animationSpec = tween( 53 | durationMillis = animationDurationInMillis, 54 | easing = animationEasing 55 | ) 56 | ) 57 | val contentColor by colors.contentColor(enabled) 58 | Surface( 59 | modifier = modifier, 60 | shape = shape, 61 | color = colors.backgroundColor(enabled).value, 62 | contentColor = contentColor.copy(alpha = 1f), 63 | border = border, 64 | elevation = elevation?.elevation(enabled, interactionSource)?.value ?: 0.dp, 65 | onClick = onClick, 66 | enabled = enabled, 67 | role = Role.Button, 68 | interactionSource = interactionSource, 69 | indication = rememberRipple() 70 | ) { 71 | CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) { 72 | ProvideTextStyle( 73 | value = MaterialTheme.typography.button 74 | ) { 75 | Row( 76 | Modifier 77 | .defaultMinSize( 78 | minWidth = ButtonDefaults.MinWidth, 79 | minHeight = ButtonDefaults.MinHeight 80 | ) 81 | .drawBehind { 82 | rotate(degrees = angle) { 83 | drawCircle(colorList) 84 | } 85 | } 86 | .padding(contentPadding), 87 | horizontalArrangement = Arrangement.Center, 88 | verticalAlignment = Alignment.CenterVertically, 89 | content = content 90 | ) 91 | } 92 | } 93 | } 94 | } 95 | fun DrawScope.drawCircle( 96 | colors: List 97 | ) { 98 | drawCircle( 99 | brush = Brush.sweepGradient( 100 | colors 101 | ), 102 | radius = size.maxDimension 103 | ) 104 | } 105 | 106 | @Preview(showBackground = true, showSystemUi = true) 107 | @Composable 108 | fun customComponentPreview() { 109 | RadialReflectiveButton(rotationValue = 0f, onClick = {}) { 110 | 111 | } 112 | } -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/composables/ReactiveGradientButton.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer.composables 2 | 3 | import androidx.compose.animation.core.Easing 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.BorderStroke 8 | import androidx.compose.foundation.interaction.MutableInteractionSource 9 | import androidx.compose.foundation.layout.* 10 | import androidx.compose.material.* 11 | import androidx.compose.material.ripple.rememberRipple 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.drawBehind 16 | import androidx.compose.ui.graphics.Brush 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.graphics.Shape 19 | import androidx.compose.ui.graphics.drawscope.DrawScope 20 | import androidx.compose.ui.semantics.Role 21 | import androidx.compose.ui.tooling.preview.Preview 22 | import androidx.compose.ui.unit.dp 23 | import com.elixer.veneer.BLUE 24 | import com.elixer.veneer.PINK 25 | import kotlin.math.absoluteValue 26 | 27 | @OptIn(ExperimentalMaterialApi::class) 28 | @Composable 29 | fun ReactiveGradientButton( 30 | rotationValue: Float, 31 | colorBegin: Color = PINK, 32 | colorEnd: Color = BLUE, 33 | animationDurationInMillis: Int = 300, 34 | animationEasing: Easing = LinearEasing, 35 | sensitivityInverseConstant: Float = 90f, 36 | onClick: () -> Unit, 37 | modifier: Modifier = Modifier, 38 | enabled: Boolean = true, 39 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 40 | elevation: ButtonElevation? = ButtonDefaults.elevation(), 41 | shape: Shape = MaterialTheme.shapes.small, 42 | border: BorderStroke? = null, 43 | colors: ButtonColors = ButtonDefaults.buttonColors(), 44 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 45 | content: @Composable RowScope.() -> Unit 46 | 47 | 48 | ) { 49 | // this keeps last rotation, will use it later for something 50 | val lastRotation = remember { mutableStateOf(0f) } 51 | 52 | val angle: Float by animateFloatAsState( 53 | targetValue = rotationValue / sensitivityInverseConstant, 54 | animationSpec = tween( 55 | durationMillis = animationDurationInMillis, 56 | easing = animationEasing 57 | ) 58 | ) 59 | val contentColor by colors.contentColor(enabled) 60 | Surface( 61 | modifier = modifier, 62 | shape = shape, 63 | color = colors.backgroundColor(enabled).value, 64 | contentColor = contentColor.copy(alpha = 1f), 65 | border = border, 66 | elevation = elevation?.elevation(enabled, interactionSource)?.value ?: 0.dp, 67 | onClick = onClick, 68 | enabled = enabled, 69 | role = Role.Button, 70 | interactionSource = interactionSource, 71 | indication = rememberRipple() 72 | ) { 73 | CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) { 74 | ProvideTextStyle( 75 | value = MaterialTheme.typography.button 76 | ) { 77 | Row( 78 | Modifier 79 | .defaultMinSize( 80 | minWidth = ButtonDefaults.MinWidth, 81 | minHeight = ButtonDefaults.MinHeight 82 | ) 83 | .drawBehind { 84 | val componentSize = size / 1.25f 85 | drawRoundedRectangle(colorBegin, colorEnd, angle) 86 | } 87 | .padding(contentPadding), 88 | horizontalArrangement = Arrangement.Center, 89 | verticalAlignment = Alignment.CenterVertically, 90 | content = content 91 | ) 92 | } 93 | } 94 | } 95 | } 96 | 97 | fun DrawScope.drawRoundedRectangle( 98 | colorBegin: Color, 99 | colorEnd: Color, 100 | angle: Float 101 | ) { 102 | drawRoundRect( 103 | brush = Brush.horizontalGradient( 104 | 0.0f + angle to colorBegin, 105 | 1.0f + angle to colorEnd, 106 | 107 | ) 108 | ) 109 | } 110 | 111 | @Preview(showBackground = true, showSystemUi = true) 112 | @Composable 113 | fun modernButtonPreview() { 114 | ReactiveGradientButton(rotationValue = 0f, onClick = {}) { 115 | Text(text = "dsjdjkshd") 116 | 117 | } 118 | } -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/composables/GlossyButton.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer.composables 2 | 3 | import androidx.compose.animation.core.Easing 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.BorderStroke 8 | import androidx.compose.foundation.interaction.MutableInteractionSource 9 | import androidx.compose.foundation.layout.* 10 | import androidx.compose.material.* 11 | import androidx.compose.material.ripple.rememberRipple 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.drawBehind 16 | import androidx.compose.ui.graphics.* 17 | import androidx.compose.ui.graphics.drawscope.DrawScope 18 | import androidx.compose.ui.graphics.drawscope.rotate 19 | import androidx.compose.ui.semantics.Role 20 | import androidx.compose.ui.tooling.preview.Preview 21 | import androidx.compose.ui.unit.dp 22 | import com.elixer.veneer.* 23 | 24 | @OptIn(ExperimentalMaterialApi::class) 25 | @Composable 26 | fun GlossyButton( 27 | rotationValue: Float, 28 | colorBegin: Color = BLUE_LIGHT, 29 | colorMid: Color = WHITE200, 30 | colorEnd: Color = BLUE_DARK, 31 | animationDurationInMillis: Int = 300, 32 | animationEasing: Easing = LinearEasing, 33 | sensitivityInverseConstant: Float = 120f, 34 | onClick: () -> Unit, 35 | modifier: Modifier = Modifier, 36 | enabled: Boolean = true, 37 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 38 | elevation: ButtonElevation? = ButtonDefaults.elevation(), 39 | shape: Shape = MaterialTheme.shapes.small, 40 | border: BorderStroke? = null, 41 | colors: ButtonColors = ButtonDefaults.buttonColors(), 42 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 43 | content: @Composable RowScope.() -> Unit 44 | ) { 45 | // this keeps last rotation, will use it later for something 46 | val lastRotation = remember { mutableStateOf(0f) } 47 | val angle: Float by animateFloatAsState( 48 | targetValue = rotationValue / sensitivityInverseConstant, 49 | animationSpec = tween( 50 | durationMillis = animationDurationInMillis, 51 | easing = animationEasing 52 | ) 53 | ) 54 | 55 | val contentColor by colors.contentColor(enabled) 56 | Surface( 57 | modifier = modifier, 58 | shape = shape, 59 | color = colors.backgroundColor(enabled).value, 60 | contentColor = contentColor.copy(alpha = 1f), 61 | border = border, 62 | elevation = elevation?.elevation(enabled, interactionSource)?.value ?: 0.dp, 63 | onClick = onClick, 64 | enabled = enabled, 65 | role = Role.Button, 66 | interactionSource = interactionSource, 67 | indication = rememberRipple() 68 | ) { 69 | CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) { 70 | ProvideTextStyle( 71 | value = MaterialTheme.typography.button 72 | ) { 73 | Row( 74 | Modifier 75 | .defaultMinSize( 76 | minWidth = ButtonDefaults.MinWidth, 77 | minHeight = ButtonDefaults.MinHeight 78 | ) 79 | .drawBehind { 80 | rotate(45f) { 81 | drawGlossyCircle(colorBegin, colorMid, colorEnd, angle) 82 | } 83 | } 84 | .padding(contentPadding), 85 | horizontalArrangement = Arrangement.Center, 86 | verticalAlignment = Alignment.CenterVertically, 87 | content = content 88 | ) 89 | } 90 | } 91 | } 92 | } 93 | 94 | fun DrawScope.drawGlossyCircle( 95 | colorBegin: Color, 96 | colorMid: Color, 97 | colorEnd: Color, 98 | angle: Float 99 | ) { 100 | drawCircle( 101 | brush = Brush.horizontalGradient( 102 | 0.0f + angle to colorBegin, 103 | 0.5f + angle to colorMid, 104 | 1.0f + angle to colorEnd, 105 | 106 | ), 107 | radius = size.maxDimension 108 | ) 109 | } 110 | 111 | @Preview(showBackground = true, showSystemUi = true) 112 | @Composable 113 | fun glossyButtonPreview() { 114 | GlossyButton(rotationValue = 0f, onClick = {}) { 115 | Text(text = "dsjdjkshd") 116 | 117 | } 118 | } -------------------------------------------------------------------------------- /veneer/src/main/java/com/elixer/veneer/composables/LinearReflectiveButton.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.veneer.composables 2 | 3 | import androidx.compose.animation.core.Easing 4 | import androidx.compose.animation.core.LinearEasing 5 | import androidx.compose.animation.core.animateFloatAsState 6 | import androidx.compose.animation.core.tween 7 | import androidx.compose.foundation.BorderStroke 8 | import androidx.compose.foundation.interaction.MutableInteractionSource 9 | import androidx.compose.foundation.layout.* 10 | import androidx.compose.material.* 11 | import androidx.compose.material.ripple.rememberRipple 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Alignment 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.draw.drawBehind 16 | import androidx.compose.ui.graphics.Brush 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.graphics.Shape 19 | import androidx.compose.ui.graphics.drawscope.DrawScope 20 | import androidx.compose.ui.semantics.Role 21 | import androidx.compose.ui.tooling.preview.Preview 22 | import androidx.compose.ui.unit.dp 23 | import com.elixer.veneer.GOLD200 24 | import com.elixer.veneer.GOLD300 25 | import com.elixer.veneer.GOLD400 26 | 27 | @OptIn(ExperimentalMaterialApi::class) 28 | @Composable 29 | fun LinearReflectiveButton( 30 | rotationValue: Float, 31 | colorBegin: Color = GOLD400, 32 | colorMid: Color = GOLD200, 33 | colorEnd: Color = GOLD400, 34 | animationDurationInMillis: Int = 300, 35 | animationEasing: Easing = LinearEasing, 36 | sensitivityInverseConstant: Float = 150f, 37 | colors: ButtonColors = ButtonDefaults.buttonColors(), 38 | onClick: () -> Unit, 39 | modifier: Modifier = Modifier, 40 | enabled: Boolean = true, 41 | interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, 42 | elevation: ButtonElevation? = ButtonDefaults.elevation(), 43 | shape: Shape = MaterialTheme.shapes.small, 44 | border: BorderStroke? = null, 45 | contentPadding: PaddingValues = ButtonDefaults.ContentPadding, 46 | content: @Composable RowScope.() -> Unit 47 | ) { 48 | 49 | // this keeps last rotation, will use it later for something 50 | val lastRotation = remember { mutableStateOf(0f) } 51 | 52 | val angle: Float by animateFloatAsState( 53 | targetValue = rotationValue / sensitivityInverseConstant, 54 | animationSpec = tween( 55 | durationMillis = animationDurationInMillis, 56 | easing = animationEasing 57 | ) 58 | ) 59 | val contentColor by colors.contentColor(enabled) 60 | Surface( 61 | modifier = modifier, 62 | shape = shape, 63 | color = colors.backgroundColor(enabled).value, 64 | contentColor = contentColor.copy(alpha = 1f), 65 | border = border, 66 | elevation = elevation?.elevation(enabled, interactionSource)?.value ?: 0.dp, 67 | onClick = onClick, 68 | enabled = enabled, 69 | role = Role.Button, 70 | interactionSource = interactionSource, 71 | indication = rememberRipple() 72 | ) { 73 | CompositionLocalProvider(LocalContentAlpha provides contentColor.alpha) { 74 | ProvideTextStyle( 75 | value = MaterialTheme.typography.button 76 | ) { 77 | Row( 78 | Modifier 79 | .defaultMinSize( 80 | minWidth = ButtonDefaults.MinWidth, 81 | minHeight = ButtonDefaults.MinHeight 82 | ) 83 | .drawBehind { 84 | drawLinearReflective(colorBegin, colorMid, colorEnd, angle) 85 | 86 | } 87 | .padding(contentPadding), 88 | horizontalArrangement = Arrangement.Center, 89 | verticalAlignment = Alignment.CenterVertically, 90 | content = content 91 | ) 92 | } 93 | } 94 | } 95 | 96 | } 97 | 98 | fun DrawScope.drawLinearReflective( 99 | colorBegin: Color, 100 | colorMid: Color, 101 | colorEnd: Color, 102 | angle: Float 103 | ) { 104 | drawRoundRect( 105 | brush = Brush.horizontalGradient( 106 | 0.0f + angle to colorBegin, 107 | 0.5f + angle to colorMid, 108 | 1.0f + angle to colorEnd, 109 | ), 110 | ) 111 | } 112 | 113 | @Preview(showBackground = true, showSystemUi = true) 114 | @Composable 115 | fun linearReflectiveButtonPreview() { 116 | LinearReflectiveButton(rotationValue = 0f, onClick = {}) { 117 | 118 | } 119 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/ui/OldMainActivity.kt: -------------------------------------------------------------------------------- 1 | //TODO: Remove this 2 | //package com.elixer.surface.ui 3 | // 4 | //package com.elixer.surface 5 | // 6 | //import android.content.Context 7 | //import android.hardware.Sensor 8 | //import android.hardware.SensorEvent 9 | //import android.hardware.SensorEventListener 10 | //import android.hardware.SensorManager 11 | //import android.os.Bundle 12 | //import android.util.Log 13 | //import androidx.activity.ComponentActivity 14 | //import androidx.activity.compose.setContent 15 | //import androidx.compose.foundation.Image 16 | //import androidx.compose.foundation.layout.* 17 | //import androidx.compose.material.Surface 18 | //import androidx.compose.material.Text 19 | //import androidx.compose.runtime.Composable 20 | //import androidx.compose.runtime.collectAsState 21 | //import androidx.compose.runtime.getValue 22 | //import androidx.compose.ui.Alignment 23 | //import androidx.compose.ui.Modifier 24 | //import androidx.compose.ui.graphics.Color 25 | //import androidx.compose.ui.graphics.Shape 26 | //import androidx.compose.ui.graphics.drawscope.DrawScope 27 | //import androidx.compose.ui.graphics.drawscope.rotate 28 | //import androidx.compose.ui.layout.ContentScale 29 | //import androidx.compose.ui.platform.LocalContext 30 | //import androidx.compose.ui.res.painterResource 31 | //import androidx.compose.ui.tooling.preview.Preview 32 | //import androidx.compose.ui.unit.Dp 33 | //import androidx.compose.ui.unit.dp 34 | //import androidx.compose.ui.unit.sp 35 | //import com.elixer.surface.ui.metallicComponent 36 | //import com.elixer.surface.ui.theme.SurfaceTheme 37 | //import com.elixer.surface.ui.theme.WHITE200 38 | //import com.elixer.surface.ui.theme.WHITE400 39 | //import com.elixer.surface.ui.theme.WHITE800 40 | //import com.elixer.veneer.modernButton 41 | //import kotlinx.coroutines.flow.MutableStateFlow 42 | //import java.util.* 43 | //import kotlin.math.roundToInt 44 | // 45 | // 46 | //class MainActivity : ComponentActivity(), SensorEventListener { 47 | // 48 | // private var mSensorManager: SensorManager? = null 49 | // private lateinit var sensorManager: SensorManager 50 | // 51 | // val azimuthAngle = MutableStateFlow("") 52 | // val azimuthRadian = MutableStateFlow("") 53 | // 54 | // val pitchAngle = MutableStateFlow("") 55 | // val pitchRadian = MutableStateFlow("") 56 | // 57 | // val rollAngle = MutableStateFlow("") 58 | // val rollAngleFloat = MutableStateFlow(600f) 59 | // val rollRadian = MutableStateFlow("") 60 | // 61 | // private val accelerometerReading = FloatArray(3) 62 | // private val magnetometerReading = FloatArray(3) 63 | // 64 | // private val rotationMatrix = FloatArray(9) 65 | // private val orientationAngles = FloatArray(3) 66 | // private val orientationAnglesCompose = MutableStateFlow("unknown") 67 | // 68 | // override fun onCreate(savedInstanceState: Bundle?) { 69 | // super.onCreate(savedInstanceState) 70 | // 71 | // setContent { 72 | // val backgroundImage = painterResource(R.drawable.texture) 73 | // val rollFlo by rollAngleFloat.collectAsState() 74 | // initialize() 75 | // SurfaceTheme() { 76 | // // A surface container using the 'background' color from the theme 77 | // Surface() { 78 | // Box(modifier = Modifier.fillMaxSize()) { 79 | //// Image( 80 | //// painter = backgroundImage, contentDescription = "sdsd", 81 | //// contentScale = ContentScale.FillBounds, 82 | //// ) 83 | // Column( 84 | // horizontalAlignment = Alignment.CenterHorizontally, 85 | // modifier = Modifier 86 | // .fillMaxSize() 87 | // .padding(horizontal = 40.dp) 88 | // ) { 89 | //// Spacer(modifier = Modifier.height(20.dp)) 90 | //// metallicComponent(canvasSize = 200.dp,rotationValue = rollFlo/1.2f) 91 | // Spacer(modifier = Modifier.height(20.dp)) 92 | // Text(text = "veneer",fontSize = 90.sp, color = Color.DarkGray) 93 | // Text(text = "reactive buttons",fontSize = 20.sp, color = Color.DarkGray) 94 | // Spacer(modifier = Modifier.height(200.dp)) 95 | //// modernButton(canvasSize = 200.dp, rotationValue = rollFlo) 96 | // modernButton(canvasSize = 200.dp, rotationValue = rollFlo) 97 | // 98 | // Spacer(modifier = Modifier.height(200.dp)) 99 | // Labels() 100 | // angletext() 101 | // RadianText() 102 | //// Text(text = rollFlo.toString()) 103 | // } 104 | // } 105 | // } 106 | // } 107 | // } 108 | // } 109 | // 110 | // override fun onSensorChanged(event: SensorEvent) { 111 | // if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) { 112 | // System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size) 113 | // } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) { 114 | // System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size) 115 | // } 116 | // updateOrientationAngles() 117 | // } 118 | // 119 | // fun updateOrientationAngles() { 120 | // // Update rotation matrix, which is needed to update orientation angles. 121 | // SensorManager.getRotationMatrix( 122 | // rotationMatrix, 123 | // null, 124 | // accelerometerReading, 125 | // magnetometerReading 126 | // ) 127 | // 128 | // // "rotationMatrix" now has up-to-date information. 129 | // 130 | // SensorManager.getOrientation(rotationMatrix, orientationAngles) 131 | // azimuthAngle.value = orientationAngles[0].toString() 132 | // pitchAngle.value = orientationAngles[1].toString() 133 | // rollAngle.value = orientationAngles[2].toString() 134 | // 135 | // azimuthRadian.value = (orientationAngles[0] * (180 / Math.PI)).roundToInt().toString() 136 | // pitchRadian.value = (orientationAngles[1] * (180 / Math.PI)).roundToInt().toString() 137 | // rollRadian.value = (orientationAngles[2] * (180 / Math.PI)).roundToInt().toString() 138 | // 139 | // 140 | // orientationAnglesCompose.value = " Azimuth ${orientationAngles[0].roundToInt()} pitch ${orientationAngles[1].roundToInt()} roll ${orientationAngles[2].roundToInt()}" 141 | // rollAngleFloat.value = (orientationAngles[2] * (180 / Math.PI)).toFloat() 142 | // // "orientationAngles" now has up-to-date information. 143 | // } 144 | // 145 | // 146 | // override fun onAccuracyChanged(p0: Sensor?, p1: Int) { 147 | // } 148 | // 149 | // @Composable 150 | // fun initialize() { 151 | // val context = LocalContext.current 152 | // mSensorManager = context.getSystemService(SENSOR_SERVICE) as SensorManager?; 153 | //// mAccelerometer = mSensorManager?.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); 154 | // sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager 155 | // 156 | // registerListener() 157 | // Log.e("mAccelerometer", mSensorManager.toString()) 158 | // 159 | // } 160 | // 161 | // override fun onResume() { 162 | // super.onResume() 163 | // sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager 164 | // 165 | // registerListener() 166 | // Log.e("sensorManager", mSensorManager.toString()) 167 | // } 168 | // 169 | // private fun registerListener() { 170 | // sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer -> 171 | // sensorManager.registerListener( 172 | // this, 173 | // accelerometer, 174 | //// SensorManager.SENSOR_DELAY_NORMAL, 175 | // 1000000, 176 | // SensorManager.SENSOR_DELAY_UI 177 | // ) 178 | // } 179 | // sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField -> 180 | // sensorManager.registerListener( 181 | // this, 182 | // magneticField, 183 | //// SensorManager.SENSOR_DELAY_NORMAL, 184 | // 1000000, 185 | // SensorManager.SENSOR_DELAY_UI 186 | // ) 187 | // } 188 | // } 189 | // 190 | // override fun onPause() { 191 | // super.onPause() 192 | // mSensorManager?.unregisterListener(this); 193 | // sensorManager.unregisterListener(this) 194 | // } 195 | // 196 | // @Composable 197 | // fun angletext() { 198 | // val azi by azimuthAngle.collectAsState() 199 | // val pitch by pitchAngle.collectAsState() 200 | // val roll by rollAngle.collectAsState() 201 | // Row( 202 | // modifier = Modifier.fillMaxWidth(), 203 | // horizontalArrangement = Arrangement.SpaceBetween 204 | // ) { 205 | // Text(azi) 206 | // Text(pitch) 207 | // Text(roll) 208 | // } 209 | // } 210 | // 211 | // @Composable 212 | // fun RadianText() { 213 | // val azi by azimuthRadian.collectAsState() 214 | // val pitch by pitchRadian.collectAsState() 215 | // val roll by rollRadian.collectAsState() 216 | // Row( 217 | // modifier = Modifier.fillMaxWidth(), 218 | // horizontalArrangement = Arrangement.SpaceBetween 219 | // ) { 220 | // Text(azi) 221 | // Text(pitch) 222 | // Text(roll) 223 | // } 224 | // } 225 | //} 226 | // 227 | //@Composable 228 | //fun Labels() { 229 | // Row( 230 | // modifier = Modifier.fillMaxWidth(), 231 | // horizontalArrangement = Arrangement.SpaceBetween 232 | // ) { 233 | // Text("Azimuth") 234 | // Text("Pitch") 235 | // Text("Roll") 236 | // } 237 | //} 238 | // 239 | //@Preview(showBackground = true, showSystemUi = true) 240 | //@Composable 241 | //fun DefaultPreview() { 242 | // SurfaceTheme { 243 | // Labels() 244 | // } 245 | //} -------------------------------------------------------------------------------- /app/src/main/java/com/elixer/surface/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.elixer.surface 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | import androidx.compose.foundation.* 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.foundation.shape.CircleShape 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material.* 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.filled.AccountBalance 13 | import androidx.compose.material.icons.filled.PlayArrow 14 | import androidx.compose.material.icons.filled.Stop 15 | import androidx.compose.material.icons.outlined.Pause 16 | import androidx.compose.material.icons.outlined.Person 17 | import androidx.compose.runtime.Composable 18 | import androidx.compose.runtime.collectAsState 19 | import androidx.compose.runtime.getValue 20 | import androidx.compose.ui.Alignment 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.draw.alpha 23 | import androidx.compose.ui.draw.rotate 24 | import androidx.compose.ui.geometry.Offset 25 | import androidx.compose.ui.geometry.Rect 26 | import androidx.compose.ui.graphics.* 27 | import androidx.compose.ui.graphics.drawscope.drawIntoCanvas 28 | import androidx.compose.ui.res.painterResource 29 | import androidx.compose.ui.tooling.preview.Preview 30 | import androidx.compose.ui.unit.dp 31 | import androidx.compose.ui.unit.sp 32 | import com.elixer.surface.ui.lineTo 33 | import com.elixer.surface.ui.moveTo 34 | import com.elixer.surface.ui.theme.* 35 | import com.elixer.surface.ui.theme.GREY800 36 | import com.elixer.veneer.* 37 | import com.elixer.veneer.composables.* 38 | 39 | class MainActivity : ComponentActivity() { 40 | 41 | override fun onCreate(savedInstanceState: Bundle?) { 42 | super.onCreate(savedInstanceState) 43 | 44 | setContent { 45 | val scrollState = rememberScrollState() 46 | 47 | val azimuthAngle by Veneer.azimuthAngle.collectAsState() 48 | val pitchAngle by Veneer.pitchAngle.collectAsState() 49 | val rollAngle by Veneer.rollAngle.collectAsState() 50 | 51 | SurfaceTheme() { 52 | Surface() { 53 | Box(modifier = Modifier.fillMaxSize()) { 54 | Column( 55 | horizontalAlignment = Alignment.CenterHorizontally, 56 | verticalArrangement = Arrangement.spacedBy(15.dp), 57 | modifier = Modifier 58 | .fillMaxSize() 59 | .padding(horizontal = 40.dp) 60 | .verticalScroll(scrollState) 61 | 62 | ) { 63 | Heading() 64 | ModernGradientButtons(rollAngle) 65 | Spacer(modifier = Modifier.height(20.dp)) 66 | ReflectiveButtons(rollAngle) 67 | Spacer(modifier = Modifier.height(20.dp)) 68 | GlossyButtons(rollAngle = rollAngle) 69 | Spacer(modifier = Modifier.height(20.dp)) 70 | LinearReflectiveButton(rollAngle) 71 | angletext(azimuthAngle, pitchAngle, rollAngle) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | @Composable 80 | private fun Heading() { 81 | Text(text = "veneer", fontSize = 80.sp, color = Color.DarkGray) 82 | Text(text = "reactive buttons", fontSize = 15.sp, color = Color.Gray) 83 | } 84 | 85 | override fun onResume() { 86 | super.onResume() 87 | Veneer.init(this) 88 | } 89 | 90 | override fun onPause() { 91 | super.onPause() 92 | Veneer.stop() 93 | } 94 | 95 | @Composable 96 | fun angletext(azimuth: Float, pitchAngle: Float, rollAngle: Float) { 97 | Row( 98 | modifier = Modifier.fillMaxWidth(), 99 | horizontalArrangement = Arrangement.SpaceBetween 100 | ) { 101 | Text("Azimuth") 102 | Text("Pitch") 103 | Text("Roll") 104 | } 105 | 106 | Row( 107 | modifier = Modifier.fillMaxWidth(), 108 | horizontalArrangement = Arrangement.SpaceBetween 109 | ) { 110 | Text(azimuth.toString()) 111 | Text(pitchAngle.toString()) 112 | Text(rollAngle.toString()) 113 | } 114 | } 115 | 116 | 117 | @Composable 118 | fun ReflectiveButtons(rollAngle: Float) { 119 | 120 | Text(text = "radial reflective buttons", fontSize = 15.sp, color = Color.Gray) 121 | Row( 122 | verticalAlignment = Alignment.CenterVertically, 123 | horizontalArrangement = Arrangement.spacedBy(20.dp), 124 | ) { 125 | RadialReflectiveButton( 126 | rotationValue = rollAngle, onClick = { println("pressed") }, 127 | shape = RoundedCornerShape(50) 128 | ) { 129 | Icon( 130 | Icons.Outlined.Pause, contentDescription = "content description", tint = GREY600, 131 | 132 | ) 133 | } 134 | RadialReflectiveButton( 135 | colorList = Utils.RADIAL_GOLD, 136 | rotationValue = rollAngle, onClick = { println("pressed") }, 137 | modifier = Modifier.size(70.dp), //avoid the oval shape 138 | shape = CircleShape, 139 | ) { 140 | Icon( 141 | Icons.Filled.PlayArrow, contentDescription = "content description", tint = GREY600, 142 | modifier = Modifier.size(100.dp) 143 | ) 144 | } 145 | RadialReflectiveButton( 146 | rotationValue = rollAngle, onClick = { println("pressed") }, 147 | shape = RoundedCornerShape(50) 148 | ) { 149 | Icon( 150 | Icons.Filled.Stop, contentDescription = "content description", tint = GREY600, 151 | 152 | ) 153 | } 154 | } 155 | } 156 | 157 | 158 | @Composable 159 | fun GlossyButtons(rollAngle: Float) { 160 | Text(text = "glossy buttons", fontSize = 15.sp, color = Color.Gray) 161 | 162 | Row( 163 | verticalAlignment = Alignment.CenterVertically, 164 | horizontalArrangement = Arrangement.spacedBy(20.dp), 165 | ) { 166 | GlossyButton( 167 | rotationValue = rollAngle, onClick = { println("pressed") }, 168 | shape = RoundedCornerShape(50), 169 | colors = ButtonDefaults.buttonColors(backgroundColor = Color.DarkGray) 170 | ) { 171 | Icon( 172 | Icons.Outlined.Person, contentDescription = "content description", tint = BLUE_DARK, 173 | modifier = Modifier.alpha(0.4f), 174 | ) 175 | } 176 | 177 | GlossyButton( 178 | onClick = { }, rotationValue = rollAngle, shape = RoundedCornerShape(10) 179 | ) { 180 | Text( 181 | text = "Glossy Button", fontSize = 20.sp, 182 | modifier = Modifier 183 | .padding(10.dp) 184 | .alpha(0.4f), 185 | color = BLUE_DARK, 186 | ) 187 | } 188 | } 189 | } 190 | 191 | @Composable 192 | fun ModernGradientButtons(rollAngle: Float) { 193 | 194 | Text(text = "modern gradient buttons", fontSize = 15.sp, color = Color.Gray) 195 | Row( 196 | verticalAlignment = Alignment.CenterVertically, 197 | horizontalArrangement = Arrangement.SpaceBetween 198 | ) { 199 | 200 | ReactiveGradientButton( 201 | onClick = { }, rotationValue = rollAngle, shape = RoundedCornerShape(10) 202 | ) { 203 | Text( 204 | text = "Modern Button", fontSize = 20.sp, 205 | modifier = Modifier.padding(10.dp), 206 | ) 207 | } 208 | Spacer(modifier = Modifier.width(20.dp)) 209 | ReactiveGradientButton( 210 | rotationValue = rollAngle, 211 | onClick = { /*TODO*/ }, 212 | modifier = Modifier.size(50.dp), //avoid the oval shape 213 | shape = CircleShape, 214 | contentPadding = PaddingValues(0.dp), //avoid the little icon 215 | ) { 216 | Icon(Icons.Filled.PlayArrow, contentDescription = "content description", tint = Color.White) 217 | } 218 | } 219 | } 220 | 221 | @Composable 222 | fun LinearReflectiveButton(rollAngle: Float) { 223 | 224 | Text(text = " linear reflective buttons", fontSize = 15.sp, color = Color.Gray) 225 | Row( 226 | verticalAlignment = Alignment.CenterVertically, 227 | horizontalArrangement = Arrangement.spacedBy(20.dp), 228 | ) { 229 | LinearReflectiveButton( 230 | rotationValue = rollAngle, onClick = { println("pressed") }, 231 | shape = RoundedCornerShape(50), 232 | modifier = Modifier.width(300.dp) 233 | ) { 234 | Icon( 235 | Icons.Filled.AccountBalance, contentDescription = "content description", tint = GOLD800, 236 | 237 | ) 238 | } 239 | } 240 | 241 | } 242 | 243 | @Composable 244 | fun Labels() { 245 | Row( 246 | modifier = Modifier.fillMaxWidth(), 247 | horizontalArrangement = Arrangement.SpaceBetween 248 | ) { 249 | Text("Azimuth") 250 | Text("Pitch") 251 | Text("Roll") 252 | } 253 | } 254 | 255 | @Preview(showBackground = true, showSystemUi = true) 256 | @Composable 257 | fun DefaultPreview() { 258 | SurfaceTheme { 259 | Column( 260 | horizontalAlignment = Alignment.CenterHorizontally 261 | ) { 262 | ModernGradientButtons(0f) 263 | Spacer(modifier = Modifier.height(20.dp)) 264 | ReflectiveButtons(0f) 265 | Spacer(modifier = Modifier.height(20.dp)) 266 | LinearReflectiveButton(rollAngle = 0f) 267 | Spacer(modifier = Modifier.height(20.dp)) 268 | } 269 | } 270 | } 271 | } --------------------------------------------------------------------------------