From f44a298d59c146ce1facde80c02bd4a05cf7b64f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Jan 2026 12:08:56 +0000
Subject: [PATCH 01/36] Initial plan
From 0c0268c128340c83cd79e4d87ed00a457a1c7612 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Jan 2026 12:23:26 +0000
Subject: [PATCH 02/36] Major library improvements: update deps, add XML views
support, light source, themes, and animations
Co-authored-by: obieda-hussien <184546202+obieda-hussien@users.noreply.github.com>
---
README.md | 342 +++++++++----
README_AR.md | 159 +++++++
app/build.gradle | 38 +-
app/src/main/AndroidManifest.xml | 3 +-
build.gradle | 7 +-
gradle.properties | 8 +-
gradle/wrapper/gradle-wrapper.properties | 4 +-
library-views/build.gradle | 53 +++
library-views/consumer-rules.pro | 2 +
library-views/proguard-rules.pro | 14 +
library-views/src/main/AndroidManifest.xml | 3 +
.../library/views/NeumorphicButton.kt | 442 +++++++++++++++++
.../library/views/NeumorphicCardView.kt | 448 ++++++++++++++++++
.../library/views/NeumorphicView.kt | 430 +++++++++++++++++
library-views/src/main/res/values/attrs.xml | 95 ++++
library/build.gradle | 36 +-
library/src/main/AndroidManifest.xml | 3 +-
.../nikhilchaudhari/library/NeuExtensions.kt | 125 +++++
.../me/nikhilchaudhari/library/NeuTheme.kt | 117 +++++
.../me/nikhilchaudhari/library/Neumorphic.kt | 79 ++-
.../nikhilchaudhari/library/internal/Blur.kt | 32 +-
.../nikhilchaudhari/library/shapes/Drawing.kt | 129 +++--
.../library/shapes/ShapeConfig.kt | 4 +-
settings.gradle | 9 +
24 files changed, 2386 insertions(+), 196 deletions(-)
create mode 100644 README_AR.md
create mode 100644 library-views/build.gradle
create mode 100644 library-views/consumer-rules.pro
create mode 100644 library-views/proguard-rules.pro
create mode 100644 library-views/src/main/AndroidManifest.xml
create mode 100644 library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt
create mode 100644 library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt
create mode 100644 library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt
create mode 100644 library-views/src/main/res/values/attrs.xml
create mode 100644 library/src/main/java/me/nikhilchaudhari/library/NeuExtensions.kt
create mode 100644 library/src/main/java/me/nikhilchaudhari/library/NeuTheme.kt
diff --git a/README.md b/README.md
index c13e7cff..b0584c58 100644
--- a/README.md
+++ b/README.md
@@ -1,128 +1,286 @@
-# Neumorphism UI with Jetpack Compose
-This library is an experimentation with [Neumorphism UI](https://uxdesign.cc/neumorphism-in-user-interfaces-b47cef3bf3a6) (New + Skeuomorphism) on Android with **Jetpack Compose**.
+# Neumorphism UI Library for Android
-[]()  [](https://github.com/KotlinBy/awesome-kotlin)
+A modern, flexible Neumorphism UI library for Android supporting both **Jetpack Compose** and traditional **XML/Java Android Views**.
+[]()  [](https://github.com/KotlinBy/awesome-kotlin)
-## How to use?
+## Features ✨
-### 1. Add the dependency
+- 🎨 **Jetpack Compose Support** - Use the `neumorphic()` modifier with any composable
+- 📱 **XML/Java Views Support** - Traditional Android Views (NeumorphicView, NeumorphicButton, NeumorphicCardView)
+- 🌓 **Dark Theme Support** - Built-in light and dark theme color schemes
+- 🎭 **Material You Integration** - Dynamic colors on Android 12+
+- 💫 **Animation Support** - Smooth press animations
+- 🔆 **Configurable Light Source** - TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT
+- 🛠 **Modern Implementation** - Migrated from deprecated RenderScript to StackBlur
-And your app level `build.gradle`. (Make sure you have compose dependencies as well)
+## Installation
+
+### Jetpack Compose Library
+
+Add to your app level `build.gradle`:
```kotlin
- implementation("me.nikhilchaudhari:composeNeumorphism:1.0.0-alpha02")
+implementation("me.nikhilchaudhari:composeNeumorphism:2.0.0")
```
-### 2. Use
+### XML/Views Library
-Just add `modifier = Modifier.neumorphic()` to any of your UI element of Jetpack-Compose (just like you do for other modifiers).
+```kotlin
+implementation("me.nikhilchaudhari:neumorphismViews:2.0.0")
+```
-
+## Quick Start
+### Jetpack Compose
-## How do I configure shape, color, etc.?
+```kotlin
+// Basic usage
+Card(
+ modifier = Modifier
+ .padding(16.dp)
+ .size(200.dp)
+ .neumorphic()
+) {
+ // Your content
+}
+
+// With customization
+Button(
+ modifier = Modifier
+ .neumorphic(
+ neuShape = Punched.Rounded(radius = 12.dp),
+ elevation = 8.dp,
+ lightShadowColor = Color.White,
+ darkShadowColor = Color.Gray,
+ lightSource = LightSource.TOP_LEFT
+ )
+) {
+ Text("Click Me")
+}
+```
-### 1. Shapes
-You've three basic **Neu** shapes available -
-1. `Punched`, 2. `Pressed`, 3. `Pot`
+### XML Layout
+
+```xml
+
+
+
+
+
+
+
+```
-These are not standard names for Neumorphism UI, I just named them based on how they look (at least to me).
+### Java Usage
-And you can also configure the corner families of those shapes as **`Oval`** or **`Rounded`**
+```java
+NeumorphicButton button = new NeumorphicButton(context);
+button.setNeuShapeType(NeuShapeType.PUNCHED);
+button.setNeuCornerRadius(dpToPx(12));
+button.setNeuElevation(dpToPx(8));
+button.setNeuLightSource(LightSource.TOP_LEFT);
+```
+
+## Shapes
+
+Three neumorphic shapes are available:
+
+| Shape | Description | Visual |
+|-------|-------------|--------|
+| `Punched` | Raised/bumped effect | `__/‾‾‾‾‾‾‾\__` |
+| `Pressed` | Depressed/sunken effect | `‾‾\________/‾‾` |
+| `Pot` | Combined raised + depressed | `_/\‾‾‾‾‾/\_` |
-
+Each shape supports two corner types:
+- **Rounded** - Configurable corner radius
+- **Oval** - Circular/elliptical shape
```kotlin
-Card(
- backgroundColor = Color(236, 234, 235),
- shape = RoundedCornerShape(12.dp),
- modifier = Modifier
- .padding(16.dp)
- .size(300.dp, 100.dp)
- .neumorphic(
- neuShape =
- // Punched shape
- Punched.Rounded(radius = 8.dp)
- Punched.Oval()
-
- // Presssed Shape
- Pressed.Rounded(radius = 4.dp)
- Presssed.Oval()
-
- // Pot shape
- Pot.Rounded(radius = 8.dp)
- Pot.Oval()
- )
- ){/*card content */}
+// Compose
+Punched.Rounded(radius = 12.dp)
+Punched.Oval()
+Pressed.Rounded(radius = 8.dp)
+Pressed.Oval()
+Pot.Rounded(radius = 16.dp)
+Pot.Oval()
+```
+
+## Configuration Options
+
+| Parameter | Default | Description |
+|-----------|---------|-------------|
+| `neuShape` | `Punched.Rounded(12.dp)` | Shape type and corner configuration |
+| `lightShadowColor` | `Color.White` | Light shadow color |
+| `darkShadowColor` | `Color.LightGray` | Dark shadow color |
+| `elevation` | `6.dp` | Shadow depth/elevation |
+| `strokeWidth` | `6.dp` | Inner shadow stroke width |
+| `neuInsets` | `NeuInsets(6.dp, 6.dp)` | Shadow insets (horizontal, vertical) |
+| `lightSource` | `LightSource.TOP_LEFT` | Direction of light source |
+
+## Light Source
+
+Configure the direction of the light source to change shadow placement:
+
+```kotlin
+// Compose
+Modifier.neumorphic(
+ lightSource = LightSource.TOP_LEFT // Default
+ // or
+ lightSource = LightSource.TOP_RIGHT
+ lightSource = LightSource.BOTTOM_LEFT
+ lightSource = LightSource.BOTTOM_RIGHT
+)
+```
+
+## Theme Integration
+
+### Using Theme Colors
+
+```kotlin
+@Composable
+fun ThemedCard() {
+ val colorScheme = NeuTheme.colorScheme() // Auto light/dark
+
+ Card(
+ backgroundColor = colorScheme.backgroundColor,
+ modifier = Modifier.themedNeumorphic(colorScheme)
+ ) {
+ // Content
+ }
+}
+```
+
+### Material You Dynamic Colors (Android 12+)
+
+```kotlin
+@Composable
+fun DynamicThemedCard() {
+ val colorScheme = NeuTheme.dynamicColorScheme()
+
+ Card(
+ backgroundColor = colorScheme.backgroundColor,
+ modifier = Modifier.themedNeumorphic(colorScheme)
+ ) {
+ // Content
+ }
+}
```
-For `Rounded` corner-family, you can configure the radius of how much rounded corner you want. The default Neu shape is `Punched.Rounded(radius = 12.dp)`.
-The `Punched` shape would work with Cards, Buttons, etc.
-You should use `Modifier.clip()` while using `Pressed` shape, because there's known issue of shadow placeents for Pressed shape. Check the sample app for more code snippets.
-
-P.S: Even I'm yet to experiment it with all UI elements.
-
-### 2. Shadows
-
-The idea of Neumorphism UI is simple, just create two shadows light and dark and draw those around the UI component from top-left and bottom-right corners.
-**Just make sure you are using the same colors for your Surface and for the UI elements.**
-You can configure the shadow `insets`, `color` and `elevation`
-
-Params | Description
-------------------- | -------------
-`lightShadowColor` | Default value - `Color.White` - Set the light shadow color you want
-`darkShadowColor` | Default value - `Color.Gray` - Set the dark shadow color you want.
-`elevation` | Default value - `6.dp` - Set the elevation for the shadow.
-`neuInsets` | Default values - `NeuInsets(6.dp, 6.dp)` - Insets = Horizontal, Vertical - Placements of your shadows i.e How do you want to place your | shadows vertically and horizontally.
-`strokeWidth` | Default value - `6.dp` - Stroke width of the internal shadows. Stroke width is for only `Pressed` and `Punched`.
-
-
- ```kotlin
- Button(
- onClick = { /*TODO*/ },
- modifier = Modifier
- .padding(12.dp)
- .neumorphic(
- // assing neuShape
- //...
- lightShadowColor = Color.White,
- darkShadowColor = Color.Gray,
- elevation = 4.dp,
- strokeWidth: Dp = 6.dp,
- neuInsets = NeuInsets(6.dp, 6.dp)
- )
- )
+### Custom Color Scheme
+
+```kotlin
+val customScheme = NeuTheme.customColorScheme(
+ backgroundColor = Color(0xFFE0E5EC)
+)
```
-The overall configutation
-
+## Animation
+### Animated Press Effect (Compose)
-## ToDos
- - [ ] Add `LightSource` option and place the shadows accordingly (It is, by default, top-left right now)
- - [ ] Migrate from RenderScript
- - [ ] Fix clipping and shadow positioning with insets
- - [ ] Update sample app and code with Jetpack-Compose guidelines
+```kotlin
+Modifier.animatedNeumorphic(
+ neuShape = Punched.Rounded(),
+ elevation = 8.dp,
+ pressed = isPressed, // from interaction state
+ animationDuration = 150
+)
+```
-## FAQs
-### What's the idea? Is there any performance overhead?
-Idea is simple - just draw the two shadows light and dark around the UI element. I'm using [RenderScript](https://developer.android.com/guide/topics/renderscript/compute) to blur two GradientDrawables. I know it's going to be deprecated in the Android 12. I'll surely update the code to migrate from RenderScript. If Renderscript throws any exception / not able to blur the drawable, I'm using [StackBlur](https://github.com/CuriousNikhil/neumorphic-compose/blob/main/library/src/main/java/me/nikhilchaudhari/library/internal/StackBlur.kt) algorithm from Mario Klingemann as a workaround.
+### Clickable with Animation
-### Does this library work with all the Jetpack Compose UI elements?
-I can't surely say. I'm already experimenting drawing shadow on UIs with Jetpack Compose and improving this library. I request you to try and please help to make it work.
+```kotlin
+Modifier.neumorphicClickable(
+ onClick = { /* action */ },
+ elevation = 8.dp,
+ neuShape = Punched.Rounded()
+)
+```
-### When can I use this?
-You can use this right now if you want to play with shiny Neumorphism UI. The library is in alpha and there are some known issues regarding shadow rendering and clipping. Please raise issue if you found any.
+### XML Views Animation
-## Contribution Guide
-Please contribute! I'm just getting my hands dirty with Jetpack-Compose.
-There is heavy chance that the code may/may not be correct/holding best practices. I request you to contribute/ raise issues/ send PRs so I can learn too.
-You can use the Github **Discussion** to discuss and ask questions. Or you can reach out to me on Twitter [@CuriousNikhil](https://twitter.com/curiousnikhil).
+Buttons have built-in press animations. Enable/disable with:
+
+```kotlin
+button.enablePressAnimation = true
+```
+
+## Utility Extensions
+
+```kotlin
+// Soft neumorphic (reduced elevation)
+Modifier.softNeumorphic()
+
+// Deep neumorphic (increased elevation)
+Modifier.deepNeumorphic()
+
+// Generate shadow colors from background
+val (lightShadow, darkShadow) = backgroundColor.toNeuColors()
+
+// Lighten/darken colors
+val lighter = color.lighten(0.2f)
+val darker = color.darken(0.2f)
+```
+
+## Best Practices
+
+1. **Use matching colors**: Background and shadow colors should be similar for best effect
+2. **Avoid pure white/black**: Use off-white and dark gray for realistic shadows
+3. **Consider light source**: Keep light source consistent across your UI
+4. **Use appropriate elevation**: 4-12dp works best for most cases
+5. **Clip for Pressed shape**: Use `Modifier.clip()` when using `Pressed` shape
+
+## Migration from v1.x
+
+```kotlin
+// v1.x
+Modifier.neumorphic(
+ neuShape = Punched.Rounded(),
+ elevation = 6.dp
+)
+
+// v2.0 - same API, new features
+Modifier.neumorphic(
+ neuShape = Punched.Rounded(),
+ elevation = 6.dp,
+ lightSource = LightSource.TOP_LEFT // NEW
+)
+```
+
+## Requirements
+
+- **Minimum SDK**: 21 (Android 5.0)
+- **Target SDK**: 34 (Android 14)
+- **Compose**: 1.5.4+
+- **Kotlin**: 1.9.20+
## License
+
Licensed under Apache License, Version 2.0 [here](https://github.com/CuriousNikhil/neumorphic-compose/blob/main/LICENSE)
+
+## Contributing
+
+Contributions are welcome! Please feel free to submit issues and pull requests.
+
+## Acknowledgments
+
+- Stack Blur Algorithm by Mario Klingemann
+- Original Neumorphism design concept from Alexander Plyuto
diff --git a/README_AR.md b/README_AR.md
new file mode 100644
index 00000000..d6b63643
--- /dev/null
+++ b/README_AR.md
@@ -0,0 +1,159 @@
+
+
+# مكتبة Neumorphism UI لأندرويد
+
+مكتبة حديثة ومرنة لواجهة Neumorphism لأندرويد تدعم كل من **Jetpack Compose** و **Views/XML** التقليدية.
+
+## المميزات ✨
+
+- 🎨 **دعم Jetpack Compose** - استخدم الـ `neumorphic()` modifier مع أي composable
+- 📱 **دعم XML/Java Views** - NeumorphicView, NeumorphicButton, NeumorphicCardView
+- 🌓 **دعم الوضع الداكن** - مخططات ألوان مدمجة للوضع الفاتح والداكن
+- 🎭 **دعم Material You** - ألوان ديناميكية على أندرويد 12+
+- 💫 **دعم الأنيميشن** - تأثيرات ضغط سلسة
+- 🔆 **مصدر الإضاءة قابل للتخصيص** - أعلى اليسار، أعلى اليمين، أسفل اليسار، أسفل اليمين
+- 🛠 **تنفيذ حديث** - انتقال من RenderScript القديم إلى StackBlur
+
+## التثبيت
+
+### مكتبة Jetpack Compose
+
+```kotlin
+implementation("me.nikhilchaudhari:composeNeumorphism:2.0.0")
+```
+
+### مكتبة XML/Views
+
+```kotlin
+implementation("me.nikhilchaudhari:neumorphismViews:2.0.0")
+```
+
+## البداية السريعة
+
+### Jetpack Compose
+
+```kotlin
+// استخدام بسيط
+Card(
+ modifier = Modifier
+ .padding(16.dp)
+ .size(200.dp)
+ .neumorphic()
+) {
+ // المحتوى
+}
+
+// مع التخصيص
+Button(
+ modifier = Modifier
+ .neumorphic(
+ neuShape = Punched.Rounded(radius = 12.dp),
+ elevation = 8.dp,
+ lightShadowColor = Color.White,
+ darkShadowColor = Color.Gray,
+ lightSource = LightSource.TOP_LEFT
+ )
+) {
+ Text("اضغط هنا")
+}
+```
+
+### XML Layout
+
+```xml
+
+```
+
+### استخدام Java
+
+```java
+NeumorphicButton button = new NeumorphicButton(context);
+button.setNeuShapeType(NeuShapeType.PUNCHED);
+button.setNeuCornerRadius(dpToPx(12));
+button.setNeuElevation(dpToPx(8));
+```
+
+## الأشكال
+
+ثلاثة أشكال متاحة:
+
+| الشكل | الوصف |
+|-------|--------|
+| `Punched` | تأثير بارز/مرتفع |
+| `Pressed` | تأثير مضغوط/غائر |
+| `Pot` | مزيج من البارز والغائر |
+
+## خيارات التخصيص
+
+| المعامل | القيمة الافتراضية | الوصف |
+|---------|-------------------|--------|
+| `neuShape` | `Punched.Rounded(12.dp)` | نوع الشكل |
+| `lightShadowColor` | `Color.White` | لون الظل الفاتح |
+| `darkShadowColor` | `Color.LightGray` | لون الظل الداكن |
+| `elevation` | `6.dp` | عمق الظل |
+| `lightSource` | `LightSource.TOP_LEFT` | اتجاه مصدر الإضاءة |
+
+## دعم الثيمات
+
+### استخدام ألوان الثيم
+
+```kotlin
+@Composable
+fun ThemedCard() {
+ val colorScheme = NeuTheme.colorScheme() // فاتح/داكن تلقائياً
+
+ Card(
+ backgroundColor = colorScheme.backgroundColor,
+ modifier = Modifier.themedNeumorphic(colorScheme)
+ ) {
+ // المحتوى
+ }
+}
+```
+
+### ألوان Material You الديناميكية (أندرويد 12+)
+
+```kotlin
+@Composable
+fun DynamicThemedCard() {
+ val colorScheme = NeuTheme.dynamicColorScheme()
+
+ Card(
+ backgroundColor = colorScheme.backgroundColor,
+ modifier = Modifier.themedNeumorphic(colorScheme)
+ ) {
+ // المحتوى
+ }
+}
+```
+
+## أفضل الممارسات
+
+1. **استخدم ألوان متطابقة**: يجب أن تكون ألوان الخلفية والظل متشابهة
+2. **تجنب الأبيض/الأسود النقي**: استخدم ألوان رمادية للحصول على ظلال واقعية
+3. **حافظ على اتساق مصدر الإضاءة**: اجعل مصدر الإضاءة ثابتاً في واجهتك
+4. **استخدم elevation مناسب**: 4-12dp يعمل بشكل أفضل لمعظم الحالات
+
+## المتطلبات
+
+- **الحد الأدنى للـ SDK**: 21 (أندرويد 5.0)
+- **الـ SDK المستهدف**: 34 (أندرويد 14)
+- **Compose**: 1.5.4+
+- **Kotlin**: 1.9.20+
+
+## الرخصة
+
+مرخص تحت Apache License, Version 2.0
+
+## المساهمة
+
+المساهمات مرحب بها! لا تتردد في تقديم issues و pull requests.
+
+
diff --git a/app/build.gradle b/app/build.gradle
index e8415852..b36f33f3 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,15 +4,15 @@ plugins {
}
android {
- compileSdk 30
- buildToolsVersion "30.0.3"
+ namespace 'me.nikhilchaudhari.neumorphiccompose'
+ compileSdk 34
defaultConfig {
applicationId "me.nikhilchaudhari.neumorphiccompose"
minSdk 21
- targetSdk 30
- versionCode 1
- versionName "1.0"
+ targetSdk 34
+ versionCode 2
+ versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -27,36 +27,36 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
- useIR = true
+ jvmTarget = '17'
}
buildFeatures {
compose true
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
- kotlinCompilerVersion kotlin_version
+ kotlinCompilerExtensionVersion compose_compiler_version
}
}
dependencies {
- implementation 'androidx.core:core-ktx:1.6.0'
- implementation 'androidx.appcompat:appcompat:1.3.1'
- implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.material3:material3:1.1.2"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
- implementation 'androidx.activity:activity-compose:1.3.1'
+ implementation "androidx.compose.animation:animation:$compose_version"
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
+ implementation 'androidx.activity:activity-compose:1.8.2'
implementation project(path: ':library')
// implementation "me.nikhilchaudhari:composeNeumorphism:1.0.0-alpha"
- testImplementation 'junit:junit:4.+'
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d8002eaa..030443c4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,5 @@
-
+
;
+}
diff --git a/library-views/src/main/AndroidManifest.xml b/library-views/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..9a40236b
--- /dev/null
+++ b/library-views/src/main/AndroidManifest.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt
new file mode 100644
index 00000000..6501cea8
--- /dev/null
+++ b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt
@@ -0,0 +1,442 @@
+package me.nikhilchaudhari.library.views
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.appcompat.widget.AppCompatButton
+import me.nikhilchaudhari.library.LightSource
+import me.nikhilchaudhari.library.internal.BlurMaker
+import me.nikhilchaudhari.library.internal.stackBlur
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * A Button with neumorphic styling for traditional Android Views/XML layouts
+ *
+ * Usage in XML:
+ * ```xml
+ *
+ * ```
+ *
+ * Usage in Java:
+ * ```java
+ * NeumorphicButton button = new NeumorphicButton(context);
+ * button.setNeuShapeType(NeuShapeType.PUNCHED);
+ * button.setNeuElevation(dpToPx(8));
+ * ```
+ */
+class NeumorphicButton @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = android.R.attr.buttonStyle
+) : AppCompatButton(context, attrs, defStyleAttr) {
+
+ // Neumorphic properties
+ var neuShapeType: NeuShapeType = NeuShapeType.PUNCHED
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuCornerType: NeuCornerType = NeuCornerType.ROUNDED
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuCornerRadius: Float = 12f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuLightShadowColor: Int = Color.WHITE
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuDarkShadowColor: Int = Color.LTGRAY
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuElevation: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuStrokeWidth: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuInsetHorizontal: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuInsetVertical: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuLightSource: LightSource = LightSource.TOP_LEFT
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuBackgroundColor: Int = Color.parseColor("#ECEAEB")
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ // Animation properties
+ var enablePressAnimation: Boolean = true
+ private var isPressed = false
+ private var currentElevation: Float = neuElevation
+
+ // Internal blur maker
+ private var blurMaker: BlurMaker? = null
+
+ // Cached bitmaps
+ private var lightShadowBitmap: Bitmap? = null
+ private var darkShadowBitmap: Bitmap? = null
+ private var foregroundShadowBitmap: Bitmap? = null
+ private var needsRedraw = true
+
+ // Paint objects
+ private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ isFilterBitmap = true
+ }
+
+ init {
+ blurMaker = BlurMaker(context, calculateDefaultBlurRadius())
+
+ // Parse XML attributes
+ attrs?.let {
+ val typedArray = context.obtainStyledAttributes(it, R.styleable.NeumorphicButton)
+
+ neuShapeType = when (typedArray.getInt(R.styleable.NeumorphicButton_neuShape, 0)) {
+ 0 -> NeuShapeType.PUNCHED
+ 1 -> NeuShapeType.PRESSED
+ 2 -> NeuShapeType.POT
+ else -> NeuShapeType.PUNCHED
+ }
+
+ neuCornerType = when (typedArray.getInt(R.styleable.NeumorphicButton_neuCornerType, 0)) {
+ 0 -> NeuCornerType.ROUNDED
+ 1 -> NeuCornerType.OVAL
+ else -> NeuCornerType.ROUNDED
+ }
+
+ neuCornerRadius = typedArray.getDimension(
+ R.styleable.NeumorphicButton_neuCornerRadius, 12f.dpToPx()
+ )
+
+ neuLightShadowColor = typedArray.getColor(
+ R.styleable.NeumorphicButton_neuLightShadowColor, Color.WHITE
+ )
+
+ neuDarkShadowColor = typedArray.getColor(
+ R.styleable.NeumorphicButton_neuDarkShadowColor, Color.LTGRAY
+ )
+
+ neuElevation = typedArray.getDimension(
+ R.styleable.NeumorphicButton_neuElevation, 6f.dpToPx()
+ )
+ currentElevation = neuElevation
+
+ neuStrokeWidth = typedArray.getDimension(
+ R.styleable.NeumorphicButton_neuStrokeWidth, 6f.dpToPx()
+ )
+
+ neuInsetHorizontal = typedArray.getDimension(
+ R.styleable.NeumorphicButton_neuInsetHorizontal, 6f.dpToPx()
+ )
+
+ neuInsetVertical = typedArray.getDimension(
+ R.styleable.NeumorphicButton_neuInsetVertical, 6f.dpToPx()
+ )
+
+ neuLightSource = when (typedArray.getInt(R.styleable.NeumorphicButton_neuLightSource, 0)) {
+ 0 -> LightSource.TOP_LEFT
+ 1 -> LightSource.TOP_RIGHT
+ 2 -> LightSource.BOTTOM_LEFT
+ 3 -> LightSource.BOTTOM_RIGHT
+ else -> LightSource.TOP_LEFT
+ }
+
+ neuBackgroundColor = typedArray.getColor(
+ R.styleable.NeumorphicButton_neuBackgroundColor, Color.parseColor("#ECEAEB")
+ )
+
+ typedArray.recycle()
+ }
+
+ // Remove default button background
+ background = null
+ stateListAnimator = null
+
+ // Set layer type for proper shadow rendering
+ setLayerType(LAYER_TYPE_SOFTWARE, null)
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ needsRedraw = true
+ invalidateShadowBitmaps()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ if (enablePressAnimation && isEnabled) {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ isPressed = true
+ currentElevation = neuElevation * 0.5f
+ needsRedraw = true
+ invalidate()
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ isPressed = false
+ currentElevation = neuElevation
+ needsRedraw = true
+ invalidate()
+ }
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (width <= 0 || height <= 0) return
+
+ if (needsRedraw) {
+ generateShadowBitmaps()
+ needsRedraw = false
+ }
+
+ when (neuShapeType) {
+ NeuShapeType.PUNCHED -> drawPunched(canvas)
+ NeuShapeType.PRESSED -> drawPressed(canvas)
+ NeuShapeType.POT -> drawPot(canvas)
+ }
+
+ super.onDraw(canvas)
+ }
+
+ private fun drawPunched(canvas: Canvas) {
+ drawBackgroundShadows(canvas)
+ drawNeumorphicBackground(canvas)
+ }
+
+ private fun drawPressed(canvas: Canvas) {
+ drawNeumorphicBackground(canvas)
+ drawForegroundShadows(canvas)
+ }
+
+ private fun drawPot(canvas: Canvas) {
+ drawBackgroundShadows(canvas)
+ drawNeumorphicBackground(canvas)
+ drawForegroundShadows(canvas)
+ }
+
+ private fun drawBackgroundShadows(canvas: Canvas) {
+ val (lightOffsetX, lightOffsetY) = getLightOffset()
+ val (darkOffsetX, darkOffsetY) = getDarkOffset()
+
+ lightShadowBitmap?.let {
+ canvas.drawBitmap(
+ it,
+ -neuInsetHorizontal - currentElevation + lightOffsetX,
+ -neuInsetVertical - currentElevation + lightOffsetY,
+ shadowPaint
+ )
+ }
+
+ darkShadowBitmap?.let {
+ canvas.drawBitmap(
+ it,
+ neuInsetHorizontal - currentElevation + darkOffsetX,
+ neuInsetVertical - currentElevation + darkOffsetY,
+ shadowPaint
+ )
+ }
+ }
+
+ private fun drawForegroundShadows(canvas: Canvas) {
+ foregroundShadowBitmap?.let {
+ canvas.drawBitmap(it, 0f, 0f, shadowPaint)
+ }
+ }
+
+ private fun drawNeumorphicBackground(canvas: Canvas) {
+ backgroundPaint.color = neuBackgroundColor
+
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> {
+ canvas.drawOval(
+ 0f, 0f, width.toFloat(), height.toFloat(),
+ backgroundPaint
+ )
+ }
+ NeuCornerType.ROUNDED -> {
+ canvas.drawRoundRect(
+ 0f, 0f, width.toFloat(), height.toFloat(),
+ neuCornerRadius, neuCornerRadius,
+ backgroundPaint
+ )
+ }
+ }
+ }
+
+ private fun generateShadowBitmaps() {
+ invalidateShadowBitmaps()
+
+ if (width <= 0 || height <= 0) return
+
+ lightShadowBitmap = generateShadowBitmap(neuLightShadowColor)
+ darkShadowBitmap = generateShadowBitmap(neuDarkShadowColor)
+
+ if (neuShapeType == NeuShapeType.PRESSED || neuShapeType == NeuShapeType.POT) {
+ foregroundShadowBitmap = generateForegroundShadowBitmap()
+ }
+ }
+
+ private fun generateShadowBitmap(shadowColor: Int): Bitmap? {
+ val bitmapWidth = (width + currentElevation * 2).roundToInt()
+ val bitmapHeight = (height + currentElevation * 2).roundToInt()
+
+ if (bitmapWidth <= 0 || bitmapHeight <= 0) return null
+
+ val drawable = GradientDrawable().apply {
+ setColor(shadowColor)
+ setSize(width, height)
+ setBounds(0, 0, width, height)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ canvas.translate(currentElevation, currentElevation)
+ drawable.draw(canvas)
+
+ return blurMaker?.blur(bitmap) ?: bitmap.stackBlur(calculateDefaultBlurRadius())
+ }
+
+ private fun generateForegroundShadowBitmap(): Bitmap? {
+ if (width <= 0 || height <= 0) return null
+
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+
+ val (lightOffsetX, lightOffsetY) = getLightOffset()
+
+ val lightDrawable = GradientDrawable().apply {
+ setSize(width + currentElevation.toInt(), height + currentElevation.toInt())
+ setStroke(neuStrokeWidth.toInt(), neuLightShadowColor)
+ setBounds(0, 0, width + currentElevation.toInt(), height + currentElevation.toInt())
+ setColor(Color.TRANSPARENT)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ val darkDrawable = GradientDrawable().apply {
+ setSize(width + currentElevation.toInt(), height + currentElevation.toInt())
+ setStroke(neuStrokeWidth.toInt(), neuDarkShadowColor)
+ setBounds(0, 0, width + currentElevation.toInt(), height + currentElevation.toInt())
+ setColor(Color.TRANSPARENT)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ canvas.save()
+ canvas.translate(lightOffsetX * 0.5f, lightOffsetY * 0.5f)
+ lightDrawable.draw(canvas)
+ canvas.restore()
+
+ darkDrawable.draw(canvas)
+
+ return blurMaker?.blur(bitmap) ?: bitmap.stackBlur(calculateDefaultBlurRadius())
+ }
+
+ private fun getLightOffset(): Pair {
+ return when (neuLightSource) {
+ LightSource.TOP_LEFT -> Pair(-currentElevation * 0.5f, -currentElevation * 0.5f)
+ LightSource.TOP_RIGHT -> Pair(currentElevation * 0.5f, -currentElevation * 0.5f)
+ LightSource.BOTTOM_LEFT -> Pair(-currentElevation * 0.5f, currentElevation * 0.5f)
+ LightSource.BOTTOM_RIGHT -> Pair(currentElevation * 0.5f, currentElevation * 0.5f)
+ }
+ }
+
+ private fun getDarkOffset(): Pair {
+ return when (neuLightSource) {
+ LightSource.TOP_LEFT -> Pair(currentElevation * 0.5f, currentElevation * 0.5f)
+ LightSource.TOP_RIGHT -> Pair(-currentElevation * 0.5f, currentElevation * 0.5f)
+ LightSource.BOTTOM_LEFT -> Pair(currentElevation * 0.5f, -currentElevation * 0.5f)
+ LightSource.BOTTOM_RIGHT -> Pair(-currentElevation * 0.5f, -currentElevation * 0.5f)
+ }
+ }
+
+ private fun invalidateShadowBitmaps() {
+ lightShadowBitmap?.recycle()
+ darkShadowBitmap?.recycle()
+ foregroundShadowBitmap?.recycle()
+ lightShadowBitmap = null
+ darkShadowBitmap = null
+ foregroundShadowBitmap = null
+ }
+
+ private fun calculateDefaultBlurRadius(): Int {
+ val density = resources.displayMetrics.density
+ return min(25, (density * 10).roundToInt())
+ }
+
+ private fun Float.dpToPx(): Float {
+ return this * resources.displayMetrics.density
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ invalidateShadowBitmaps()
+ }
+
+ fun refresh() {
+ needsRedraw = true
+ invalidate()
+ }
+}
diff --git a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt
new file mode 100644
index 00000000..d82b4006
--- /dev/null
+++ b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt
@@ -0,0 +1,448 @@
+package me.nikhilchaudhari.library.views
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.GradientDrawable
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import me.nikhilchaudhari.library.LightSource
+import me.nikhilchaudhari.library.internal.BlurMaker
+import me.nikhilchaudhari.library.internal.stackBlur
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * A CardView with neumorphic styling for traditional Android Views/XML layouts
+ *
+ * Usage in XML:
+ * ```xml
+ *
+ *
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * Usage in Java:
+ * ```java
+ * NeumorphicCardView cardView = new NeumorphicCardView(context);
+ * cardView.setNeuShapeType(NeuShapeType.POT);
+ * cardView.setNeuCornerRadius(dpToPx(16));
+ * ```
+ */
+class NeumorphicCardView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : FrameLayout(context, attrs, defStyleAttr) {
+
+ // Neumorphic properties
+ var neuShapeType: NeuShapeType = NeuShapeType.PUNCHED
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuCornerType: NeuCornerType = NeuCornerType.ROUNDED
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuCornerRadius: Float = 16f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuLightShadowColor: Int = Color.WHITE
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuDarkShadowColor: Int = Color.LTGRAY
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuElevation: Float = 8f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuStrokeWidth: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuInsetHorizontal: Float = 8f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuInsetVertical: Float = 8f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuLightSource: LightSource = LightSource.TOP_LEFT
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuBackgroundColor: Int = Color.parseColor("#ECEAEB")
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ // Internal blur maker
+ private var blurMaker: BlurMaker? = null
+
+ // Cached bitmaps
+ private var lightShadowBitmap: Bitmap? = null
+ private var darkShadowBitmap: Bitmap? = null
+ private var foregroundShadowBitmap: Bitmap? = null
+ private var needsRedraw = true
+
+ // Paint objects
+ private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ isFilterBitmap = true
+ }
+
+ init {
+ blurMaker = BlurMaker(context, calculateDefaultBlurRadius())
+
+ // Parse XML attributes
+ attrs?.let {
+ val typedArray = context.obtainStyledAttributes(it, R.styleable.NeumorphicCardView)
+
+ neuShapeType = when (typedArray.getInt(R.styleable.NeumorphicCardView_neuShape, 0)) {
+ 0 -> NeuShapeType.PUNCHED
+ 1 -> NeuShapeType.PRESSED
+ 2 -> NeuShapeType.POT
+ else -> NeuShapeType.PUNCHED
+ }
+
+ neuCornerType = when (typedArray.getInt(R.styleable.NeumorphicCardView_neuCornerType, 0)) {
+ 0 -> NeuCornerType.ROUNDED
+ 1 -> NeuCornerType.OVAL
+ else -> NeuCornerType.ROUNDED
+ }
+
+ neuCornerRadius = typedArray.getDimension(
+ R.styleable.NeumorphicCardView_neuCornerRadius, 16f.dpToPx()
+ )
+
+ neuLightShadowColor = typedArray.getColor(
+ R.styleable.NeumorphicCardView_neuLightShadowColor, Color.WHITE
+ )
+
+ neuDarkShadowColor = typedArray.getColor(
+ R.styleable.NeumorphicCardView_neuDarkShadowColor, Color.LTGRAY
+ )
+
+ neuElevation = typedArray.getDimension(
+ R.styleable.NeumorphicCardView_neuElevation, 8f.dpToPx()
+ )
+
+ neuStrokeWidth = typedArray.getDimension(
+ R.styleable.NeumorphicCardView_neuStrokeWidth, 6f.dpToPx()
+ )
+
+ neuInsetHorizontal = typedArray.getDimension(
+ R.styleable.NeumorphicCardView_neuInsetHorizontal, 8f.dpToPx()
+ )
+
+ neuInsetVertical = typedArray.getDimension(
+ R.styleable.NeumorphicCardView_neuInsetVertical, 8f.dpToPx()
+ )
+
+ neuLightSource = when (typedArray.getInt(R.styleable.NeumorphicCardView_neuLightSource, 0)) {
+ 0 -> LightSource.TOP_LEFT
+ 1 -> LightSource.TOP_RIGHT
+ 2 -> LightSource.BOTTOM_LEFT
+ 3 -> LightSource.BOTTOM_RIGHT
+ else -> LightSource.TOP_LEFT
+ }
+
+ neuBackgroundColor = typedArray.getColor(
+ R.styleable.NeumorphicCardView_neuBackgroundColor, Color.parseColor("#ECEAEB")
+ )
+
+ typedArray.recycle()
+ }
+
+ // Enable drawing
+ setWillNotDraw(false)
+
+ // Set layer type for proper shadow rendering
+ setLayerType(LAYER_TYPE_SOFTWARE, null)
+
+ // Add padding for shadows
+ setPadding(
+ (neuInsetHorizontal + neuElevation).toInt(),
+ (neuInsetVertical + neuElevation).toInt(),
+ (neuInsetHorizontal + neuElevation).toInt(),
+ (neuInsetVertical + neuElevation).toInt()
+ )
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ needsRedraw = true
+ invalidateShadowBitmaps()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (width <= 0 || height <= 0) return
+
+ if (needsRedraw) {
+ generateShadowBitmaps()
+ needsRedraw = false
+ }
+
+ when (neuShapeType) {
+ NeuShapeType.PUNCHED -> drawPunched(canvas)
+ NeuShapeType.PRESSED -> drawPressed(canvas)
+ NeuShapeType.POT -> drawPot(canvas)
+ }
+
+ super.onDraw(canvas)
+ }
+
+ private fun drawPunched(canvas: Canvas) {
+ drawBackgroundShadows(canvas)
+ drawNeumorphicBackground(canvas)
+ }
+
+ private fun drawPressed(canvas: Canvas) {
+ drawNeumorphicBackground(canvas)
+ drawForegroundShadows(canvas)
+ }
+
+ private fun drawPot(canvas: Canvas) {
+ drawBackgroundShadows(canvas)
+ drawNeumorphicBackground(canvas)
+ drawForegroundShadows(canvas)
+ }
+
+ private fun drawBackgroundShadows(canvas: Canvas) {
+ val (lightOffsetX, lightOffsetY) = getLightOffset()
+ val (darkOffsetX, darkOffsetY) = getDarkOffset()
+
+ val contentLeft = neuInsetHorizontal + neuElevation
+ val contentTop = neuInsetVertical + neuElevation
+ val contentWidth = width - 2 * contentLeft
+ val contentHeight = height - 2 * contentTop
+
+ if (contentWidth <= 0 || contentHeight <= 0) return
+
+ lightShadowBitmap?.let {
+ canvas.drawBitmap(
+ it,
+ contentLeft - neuElevation + lightOffsetX,
+ contentTop - neuElevation + lightOffsetY,
+ shadowPaint
+ )
+ }
+
+ darkShadowBitmap?.let {
+ canvas.drawBitmap(
+ it,
+ contentLeft - neuElevation + darkOffsetX + neuInsetHorizontal * 2,
+ contentTop - neuElevation + darkOffsetY + neuInsetVertical * 2,
+ shadowPaint
+ )
+ }
+ }
+
+ private fun drawForegroundShadows(canvas: Canvas) {
+ val contentLeft = neuInsetHorizontal + neuElevation
+ val contentTop = neuInsetVertical + neuElevation
+
+ foregroundShadowBitmap?.let {
+ canvas.drawBitmap(it, contentLeft, contentTop, shadowPaint)
+ }
+ }
+
+ private fun drawNeumorphicBackground(canvas: Canvas) {
+ backgroundPaint.color = neuBackgroundColor
+
+ val contentLeft = neuInsetHorizontal + neuElevation
+ val contentTop = neuInsetVertical + neuElevation
+ val contentWidth = width - 2 * contentLeft
+ val contentHeight = height - 2 * contentTop
+
+ if (contentWidth <= 0 || contentHeight <= 0) return
+
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> {
+ canvas.drawOval(
+ contentLeft, contentTop,
+ contentLeft + contentWidth, contentTop + contentHeight,
+ backgroundPaint
+ )
+ }
+ NeuCornerType.ROUNDED -> {
+ canvas.drawRoundRect(
+ contentLeft, contentTop,
+ contentLeft + contentWidth, contentTop + contentHeight,
+ neuCornerRadius, neuCornerRadius,
+ backgroundPaint
+ )
+ }
+ }
+ }
+
+ private fun generateShadowBitmaps() {
+ invalidateShadowBitmaps()
+
+ val contentLeft = neuInsetHorizontal + neuElevation
+ val contentTop = neuInsetVertical + neuElevation
+ val contentWidth = (width - 2 * contentLeft).toInt()
+ val contentHeight = (height - 2 * contentTop).toInt()
+
+ if (contentWidth <= 0 || contentHeight <= 0) return
+
+ lightShadowBitmap = generateShadowBitmap(neuLightShadowColor, contentWidth, contentHeight)
+ darkShadowBitmap = generateShadowBitmap(neuDarkShadowColor, contentWidth, contentHeight)
+
+ if (neuShapeType == NeuShapeType.PRESSED || neuShapeType == NeuShapeType.POT) {
+ foregroundShadowBitmap = generateForegroundShadowBitmap(contentWidth, contentHeight)
+ }
+ }
+
+ private fun generateShadowBitmap(shadowColor: Int, contentWidth: Int, contentHeight: Int): Bitmap? {
+ val bitmapWidth = (contentWidth + neuElevation * 2).roundToInt()
+ val bitmapHeight = (contentHeight + neuElevation * 2).roundToInt()
+
+ if (bitmapWidth <= 0 || bitmapHeight <= 0) return null
+
+ val drawable = GradientDrawable().apply {
+ setColor(shadowColor)
+ setSize(contentWidth, contentHeight)
+ setBounds(0, 0, contentWidth, contentHeight)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ canvas.translate(neuElevation, neuElevation)
+ drawable.draw(canvas)
+
+ return blurMaker?.blur(bitmap) ?: bitmap.stackBlur(calculateDefaultBlurRadius())
+ }
+
+ private fun generateForegroundShadowBitmap(contentWidth: Int, contentHeight: Int): Bitmap? {
+ if (contentWidth <= 0 || contentHeight <= 0) return null
+
+ val bitmap = Bitmap.createBitmap(contentWidth, contentHeight, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+
+ val (lightOffsetX, lightOffsetY) = getLightOffset()
+
+ val lightDrawable = GradientDrawable().apply {
+ setSize(contentWidth + neuElevation.toInt(), contentHeight + neuElevation.toInt())
+ setStroke(neuStrokeWidth.toInt(), neuLightShadowColor)
+ setBounds(0, 0, contentWidth + neuElevation.toInt(), contentHeight + neuElevation.toInt())
+ setColor(Color.TRANSPARENT)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ val darkDrawable = GradientDrawable().apply {
+ setSize(contentWidth + neuElevation.toInt(), contentHeight + neuElevation.toInt())
+ setStroke(neuStrokeWidth.toInt(), neuDarkShadowColor)
+ setBounds(0, 0, contentWidth + neuElevation.toInt(), contentHeight + neuElevation.toInt())
+ setColor(Color.TRANSPARENT)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ canvas.save()
+ canvas.translate(lightOffsetX * 0.5f, lightOffsetY * 0.5f)
+ lightDrawable.draw(canvas)
+ canvas.restore()
+
+ darkDrawable.draw(canvas)
+
+ return blurMaker?.blur(bitmap) ?: bitmap.stackBlur(calculateDefaultBlurRadius())
+ }
+
+ private fun getLightOffset(): Pair {
+ return when (neuLightSource) {
+ LightSource.TOP_LEFT -> Pair(-neuElevation * 0.5f, -neuElevation * 0.5f)
+ LightSource.TOP_RIGHT -> Pair(neuElevation * 0.5f, -neuElevation * 0.5f)
+ LightSource.BOTTOM_LEFT -> Pair(-neuElevation * 0.5f, neuElevation * 0.5f)
+ LightSource.BOTTOM_RIGHT -> Pair(neuElevation * 0.5f, neuElevation * 0.5f)
+ }
+ }
+
+ private fun getDarkOffset(): Pair {
+ return when (neuLightSource) {
+ LightSource.TOP_LEFT -> Pair(neuElevation * 0.5f, neuElevation * 0.5f)
+ LightSource.TOP_RIGHT -> Pair(-neuElevation * 0.5f, neuElevation * 0.5f)
+ LightSource.BOTTOM_LEFT -> Pair(neuElevation * 0.5f, -neuElevation * 0.5f)
+ LightSource.BOTTOM_RIGHT -> Pair(-neuElevation * 0.5f, -neuElevation * 0.5f)
+ }
+ }
+
+ private fun invalidateShadowBitmaps() {
+ lightShadowBitmap?.recycle()
+ darkShadowBitmap?.recycle()
+ foregroundShadowBitmap?.recycle()
+ lightShadowBitmap = null
+ darkShadowBitmap = null
+ foregroundShadowBitmap = null
+ }
+
+ private fun calculateDefaultBlurRadius(): Int {
+ val density = resources.displayMetrics.density
+ return min(25, (density * 10).roundToInt())
+ }
+
+ private fun Float.dpToPx(): Float {
+ return this * resources.displayMetrics.density
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ invalidateShadowBitmaps()
+ }
+
+ fun refresh() {
+ needsRedraw = true
+ invalidate()
+ }
+}
diff --git a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt
new file mode 100644
index 00000000..701d3c32
--- /dev/null
+++ b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt
@@ -0,0 +1,430 @@
+package me.nikhilchaudhari.library.views
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.GradientDrawable
+import android.os.Build
+import android.util.AttributeSet
+import android.view.View
+import androidx.core.content.ContextCompat
+import me.nikhilchaudhari.library.LightSource
+import me.nikhilchaudhari.library.internal.BlurMaker
+import me.nikhilchaudhari.library.internal.stackBlur
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/**
+ * Shape types for neumorphic views
+ */
+enum class NeuShapeType {
+ PUNCHED,
+ PRESSED,
+ POT
+}
+
+/**
+ * Corner types for neumorphic views
+ */
+enum class NeuCornerType {
+ ROUNDED,
+ OVAL
+}
+
+/**
+ * Base class for neumorphic views that can be used in XML layouts
+ * Supports Java/Kotlin Android development with traditional Views
+ */
+open class NeumorphicView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ // Neumorphic properties
+ var neuShapeType: NeuShapeType = NeuShapeType.PUNCHED
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuCornerType: NeuCornerType = NeuCornerType.ROUNDED
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuCornerRadius: Float = 12f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuLightShadowColor: Int = Color.WHITE
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuDarkShadowColor: Int = Color.LTGRAY
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuElevation: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuStrokeWidth: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuInsetHorizontal: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuInsetVertical: Float = 6f.dpToPx()
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuLightSource: LightSource = LightSource.TOP_LEFT
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ var neuBackgroundColor: Int = Color.parseColor("#ECEAEB")
+ set(value) {
+ field = value
+ invalidate()
+ }
+
+ // Internal blur maker
+ private var blurMaker: BlurMaker? = null
+
+ // Cached bitmaps for performance
+ private var lightShadowBitmap: Bitmap? = null
+ private var darkShadowBitmap: Bitmap? = null
+ private var foregroundShadowBitmap: Bitmap? = null
+ private var needsRedraw = true
+
+ // Paint objects
+ private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+ private val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ isFilterBitmap = true
+ }
+
+ init {
+ // Initialize blur maker
+ blurMaker = BlurMaker(context, calculateDefaultBlurRadius())
+
+ // Parse XML attributes
+ attrs?.let {
+ val typedArray = context.obtainStyledAttributes(it, R.styleable.NeumorphicView)
+
+ neuShapeType = when (typedArray.getInt(R.styleable.NeumorphicView_neuShape, 0)) {
+ 0 -> NeuShapeType.PUNCHED
+ 1 -> NeuShapeType.PRESSED
+ 2 -> NeuShapeType.POT
+ else -> NeuShapeType.PUNCHED
+ }
+
+ neuCornerType = when (typedArray.getInt(R.styleable.NeumorphicView_neuCornerType, 0)) {
+ 0 -> NeuCornerType.ROUNDED
+ 1 -> NeuCornerType.OVAL
+ else -> NeuCornerType.ROUNDED
+ }
+
+ neuCornerRadius = typedArray.getDimension(
+ R.styleable.NeumorphicView_neuCornerRadius, 12f.dpToPx()
+ )
+
+ neuLightShadowColor = typedArray.getColor(
+ R.styleable.NeumorphicView_neuLightShadowColor, Color.WHITE
+ )
+
+ neuDarkShadowColor = typedArray.getColor(
+ R.styleable.NeumorphicView_neuDarkShadowColor, Color.LTGRAY
+ )
+
+ neuElevation = typedArray.getDimension(
+ R.styleable.NeumorphicView_neuElevation, 6f.dpToPx()
+ )
+
+ neuStrokeWidth = typedArray.getDimension(
+ R.styleable.NeumorphicView_neuStrokeWidth, 6f.dpToPx()
+ )
+
+ neuInsetHorizontal = typedArray.getDimension(
+ R.styleable.NeumorphicView_neuInsetHorizontal, 6f.dpToPx()
+ )
+
+ neuInsetVertical = typedArray.getDimension(
+ R.styleable.NeumorphicView_neuInsetVertical, 6f.dpToPx()
+ )
+
+ neuLightSource = when (typedArray.getInt(R.styleable.NeumorphicView_neuLightSource, 0)) {
+ 0 -> LightSource.TOP_LEFT
+ 1 -> LightSource.TOP_RIGHT
+ 2 -> LightSource.BOTTOM_LEFT
+ 3 -> LightSource.BOTTOM_RIGHT
+ else -> LightSource.TOP_LEFT
+ }
+
+ neuBackgroundColor = typedArray.getColor(
+ R.styleable.NeumorphicView_neuBackgroundColor, Color.parseColor("#ECEAEB")
+ )
+
+ typedArray.recycle()
+ }
+
+ // Enable drawing
+ setWillNotDraw(false)
+
+ // Set layer type for proper shadow rendering
+ setLayerType(LAYER_TYPE_SOFTWARE, null)
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ needsRedraw = true
+ invalidateShadowBitmaps()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ if (width <= 0 || height <= 0) return
+
+ if (needsRedraw) {
+ generateShadowBitmaps()
+ needsRedraw = false
+ }
+
+ when (neuShapeType) {
+ NeuShapeType.PUNCHED -> drawPunched(canvas)
+ NeuShapeType.PRESSED -> drawPressed(canvas)
+ NeuShapeType.POT -> drawPot(canvas)
+ }
+
+ super.onDraw(canvas)
+ }
+
+ private fun drawPunched(canvas: Canvas) {
+ // Draw background shadows first
+ drawBackgroundShadows(canvas)
+ // Draw the actual background
+ drawNeumorphicBackground(canvas)
+ }
+
+ private fun drawPressed(canvas: Canvas) {
+ // Draw the actual background first
+ drawNeumorphicBackground(canvas)
+ // Draw foreground shadows
+ drawForegroundShadows(canvas)
+ }
+
+ private fun drawPot(canvas: Canvas) {
+ // Draw background shadows
+ drawBackgroundShadows(canvas)
+ // Draw the actual background
+ drawNeumorphicBackground(canvas)
+ // Draw foreground shadows
+ drawForegroundShadows(canvas)
+ }
+
+ private fun drawBackgroundShadows(canvas: Canvas) {
+ val (lightOffsetX, lightOffsetY) = getLightOffset()
+ val (darkOffsetX, darkOffsetY) = getDarkOffset()
+
+ lightShadowBitmap?.let {
+ canvas.drawBitmap(
+ it,
+ -neuInsetHorizontal - neuElevation + lightOffsetX,
+ -neuInsetVertical - neuElevation + lightOffsetY,
+ shadowPaint
+ )
+ }
+
+ darkShadowBitmap?.let {
+ canvas.drawBitmap(
+ it,
+ neuInsetHorizontal - neuElevation + darkOffsetX,
+ neuInsetVertical - neuElevation + darkOffsetY,
+ shadowPaint
+ )
+ }
+ }
+
+ private fun drawForegroundShadows(canvas: Canvas) {
+ foregroundShadowBitmap?.let {
+ canvas.drawBitmap(it, 0f, 0f, shadowPaint)
+ }
+ }
+
+ private fun drawNeumorphicBackground(canvas: Canvas) {
+ backgroundPaint.color = neuBackgroundColor
+
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> {
+ canvas.drawOval(
+ 0f, 0f, width.toFloat(), height.toFloat(),
+ backgroundPaint
+ )
+ }
+ NeuCornerType.ROUNDED -> {
+ canvas.drawRoundRect(
+ 0f, 0f, width.toFloat(), height.toFloat(),
+ neuCornerRadius, neuCornerRadius,
+ backgroundPaint
+ )
+ }
+ }
+ }
+
+ private fun generateShadowBitmaps() {
+ invalidateShadowBitmaps()
+
+ if (width <= 0 || height <= 0) return
+
+ // Generate background shadows
+ lightShadowBitmap = generateShadowBitmap(neuLightShadowColor)
+ darkShadowBitmap = generateShadowBitmap(neuDarkShadowColor)
+
+ // Generate foreground shadows for pressed and pot shapes
+ if (neuShapeType == NeuShapeType.PRESSED || neuShapeType == NeuShapeType.POT) {
+ foregroundShadowBitmap = generateForegroundShadowBitmap()
+ }
+ }
+
+ private fun generateShadowBitmap(shadowColor: Int): Bitmap? {
+ val bitmapWidth = (width + neuElevation * 2).roundToInt()
+ val bitmapHeight = (height + neuElevation * 2).roundToInt()
+
+ if (bitmapWidth <= 0 || bitmapHeight <= 0) return null
+
+ val drawable = GradientDrawable().apply {
+ setColor(shadowColor)
+ setSize(width, height)
+ setBounds(0, 0, width, height)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+ canvas.translate(neuElevation, neuElevation)
+ drawable.draw(canvas)
+
+ return blurMaker?.blur(bitmap) ?: bitmap.stackBlur(calculateDefaultBlurRadius())
+ }
+
+ private fun generateForegroundShadowBitmap(): Bitmap? {
+ if (width <= 0 || height <= 0) return null
+
+ val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(bitmap)
+
+ val (lightOffsetX, lightOffsetY) = getLightOffset()
+
+ // Light shadow stroke
+ val lightDrawable = GradientDrawable().apply {
+ setSize(width + neuElevation.toInt(), height + neuElevation.toInt())
+ setStroke(neuStrokeWidth.toInt(), neuLightShadowColor)
+ setBounds(0, 0, width + neuElevation.toInt(), height + neuElevation.toInt())
+ setColor(Color.TRANSPARENT)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ // Dark shadow stroke
+ val darkDrawable = GradientDrawable().apply {
+ setSize(width + neuElevation.toInt(), height + neuElevation.toInt())
+ setStroke(neuStrokeWidth.toInt(), neuDarkShadowColor)
+ setBounds(0, 0, width + neuElevation.toInt(), height + neuElevation.toInt())
+ setColor(Color.TRANSPARENT)
+ when (neuCornerType) {
+ NeuCornerType.OVAL -> shape = GradientDrawable.OVAL
+ NeuCornerType.ROUNDED -> {
+ shape = GradientDrawable.RECTANGLE
+ cornerRadius = neuCornerRadius
+ }
+ }
+ }
+
+ canvas.save()
+ canvas.translate(lightOffsetX, lightOffsetY)
+ lightDrawable.draw(canvas)
+ canvas.restore()
+
+ darkDrawable.draw(canvas)
+
+ return blurMaker?.blur(bitmap) ?: bitmap.stackBlur(calculateDefaultBlurRadius())
+ }
+
+ private fun getLightOffset(): Pair {
+ return when (neuLightSource) {
+ LightSource.TOP_LEFT -> Pair(-neuElevation * 0.5f, -neuElevation * 0.5f)
+ LightSource.TOP_RIGHT -> Pair(neuElevation * 0.5f, -neuElevation * 0.5f)
+ LightSource.BOTTOM_LEFT -> Pair(-neuElevation * 0.5f, neuElevation * 0.5f)
+ LightSource.BOTTOM_RIGHT -> Pair(neuElevation * 0.5f, neuElevation * 0.5f)
+ }
+ }
+
+ private fun getDarkOffset(): Pair {
+ return when (neuLightSource) {
+ LightSource.TOP_LEFT -> Pair(neuElevation * 0.5f, neuElevation * 0.5f)
+ LightSource.TOP_RIGHT -> Pair(-neuElevation * 0.5f, neuElevation * 0.5f)
+ LightSource.BOTTOM_LEFT -> Pair(neuElevation * 0.5f, -neuElevation * 0.5f)
+ LightSource.BOTTOM_RIGHT -> Pair(-neuElevation * 0.5f, -neuElevation * 0.5f)
+ }
+ }
+
+ private fun invalidateShadowBitmaps() {
+ lightShadowBitmap?.recycle()
+ darkShadowBitmap?.recycle()
+ foregroundShadowBitmap?.recycle()
+ lightShadowBitmap = null
+ darkShadowBitmap = null
+ foregroundShadowBitmap = null
+ }
+
+ private fun calculateDefaultBlurRadius(): Int {
+ val density = resources.displayMetrics.density
+ return min(25, (density * 10).roundToInt())
+ }
+
+ private fun Float.dpToPx(): Float {
+ return this * resources.displayMetrics.density
+ }
+
+ override fun onDetachedFromWindow() {
+ super.onDetachedFromWindow()
+ invalidateShadowBitmaps()
+ }
+
+ /**
+ * Refresh the neumorphic effect
+ * Call this after changing properties programmatically
+ */
+ fun refresh() {
+ needsRedraw = true
+ invalidate()
+ }
+}
diff --git a/library-views/src/main/res/values/attrs.xml b/library-views/src/main/res/values/attrs.xml
new file mode 100644
index 00000000..7f2fdbde
--- /dev/null
+++ b/library-views/src/main/res/values/attrs.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/build.gradle b/library/build.gradle
index 81b04edb..7e1e1270 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -5,21 +5,21 @@ plugins {
ext {
PUBLISH_GROUP_ID = 'me.nikhilchaudhari'
- PUBLISH_VERSION = '1.0.0-alpha02'
+ PUBLISH_VERSION = '2.0.0'
PUBLISH_ARTIFACT_ID = 'composeNeumorphism'
}
apply from: "${rootProject.projectDir}/scripts/publish-module.gradle"
android {
- compileSdk 30
- buildToolsVersion "30.0.3"
+ namespace 'me.nikhilchaudhari.library'
+ compileSdk 34
defaultConfig {
minSdk 21
- targetSdk 30
- versionCode 1
- versionName "1.0"
+ targetSdk 34
+ versionCode 2
+ versionName "2.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
@@ -33,13 +33,12 @@ android {
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
- useIR = true
+ jvmTarget = '17'
}
buildFeatures {
@@ -47,20 +46,21 @@ android {
}
composeOptions {
- kotlinCompilerExtensionVersion compose_version
- kotlinCompilerVersion kotlin_version
+ kotlinCompilerExtensionVersion compose_compiler_version
}
}
dependencies {
- implementation 'androidx.core:core-ktx:1.6.0'
- implementation 'androidx.appcompat:appcompat:1.3.1'
- implementation 'com.google.android.material:material:1.4.0'
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.material3:material3:1.1.2"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
- testImplementation 'junit:junit:4.+'
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ implementation "androidx.compose.animation:animation:$compose_version"
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}
\ No newline at end of file
diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml
index 4afb35bb..a5918e68 100644
--- a/library/src/main/AndroidManifest.xml
+++ b/library/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-
+
\ No newline at end of file
diff --git a/library/src/main/java/me/nikhilchaudhari/library/NeuExtensions.kt b/library/src/main/java/me/nikhilchaudhari/library/NeuExtensions.kt
new file mode 100644
index 00000000..49516e01
--- /dev/null
+++ b/library/src/main/java/me/nikhilchaudhari/library/NeuExtensions.kt
@@ -0,0 +1,125 @@
+package me.nikhilchaudhari.library
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsPressedAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import me.nikhilchaudhari.library.shapes.NeuShape
+import me.nikhilchaudhari.library.shapes.Punched
+
+/**
+ * Clickable neumorphic modifier with press animation
+ *
+ * @param onClick Action to perform when clicked
+ * @param enabled Whether the click is enabled
+ * @param neuInsets Shadow insets
+ * @param neuShape Shape type
+ * @param lightShadowColor Light shadow color
+ * @param darkShadowColor Dark shadow color
+ * @param strokeWidth Stroke width
+ * @param elevation Shadow elevation
+ * @param lightSource Light source direction
+ */
+fun Modifier.neumorphicClickable(
+ onClick: () -> Unit,
+ enabled: Boolean = true,
+ neuInsets: NeuInsets = NeuInsets(),
+ neuShape: NeuShape = Punched.Rounded(),
+ lightShadowColor: Color = Color.White,
+ darkShadowColor: Color = Color.LightGray,
+ strokeWidth: Dp = 6.dp,
+ elevation: Dp = 6.dp,
+ lightSource: LightSource = LightSource.TOP_LEFT
+) = composed {
+ val interactionSource = remember { MutableInteractionSource() }
+ val isPressed by interactionSource.collectIsPressedAsState()
+
+ this
+ .animatedNeumorphic(
+ neuInsets = neuInsets,
+ neuShape = neuShape,
+ lightShadowColor = lightShadowColor,
+ darkShadowColor = darkShadowColor,
+ strokeWidth = strokeWidth,
+ elevation = elevation,
+ lightSource = lightSource,
+ pressed = isPressed
+ )
+ .clickable(
+ interactionSource = interactionSource,
+ indication = null,
+ enabled = enabled,
+ onClick = onClick
+ )
+}
+
+/**
+ * Apply neumorphic effect using theme colors
+ *
+ * @param colorScheme The color scheme to use
+ * @param neuInsets Shadow insets
+ * @param neuShape Shape type
+ * @param strokeWidth Stroke width
+ * @param elevation Shadow elevation
+ * @param lightSource Light source direction
+ */
+fun Modifier.themedNeumorphic(
+ colorScheme: NeuTheme.NeuColorScheme,
+ neuInsets: NeuInsets = NeuInsets(),
+ neuShape: NeuShape = Punched.Rounded(),
+ strokeWidth: Dp = 6.dp,
+ elevation: Dp = 6.dp,
+ lightSource: LightSource = LightSource.TOP_LEFT
+) = this.neumorphic(
+ neuInsets = neuInsets,
+ neuShape = neuShape,
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ strokeWidth = strokeWidth,
+ elevation = elevation,
+ lightSource = lightSource
+)
+
+/**
+ * Apply soft neumorphic effect with reduced elevation
+ */
+fun Modifier.softNeumorphic(
+ neuInsets: NeuInsets = NeuInsets(4.dp, 4.dp),
+ neuShape: NeuShape = Punched.Rounded(8.dp),
+ lightShadowColor: Color = Color.White.copy(alpha = 0.8f),
+ darkShadowColor: Color = Color.LightGray.copy(alpha = 0.8f),
+ lightSource: LightSource = LightSource.TOP_LEFT
+) = this.neumorphic(
+ neuInsets = neuInsets,
+ neuShape = neuShape,
+ lightShadowColor = lightShadowColor,
+ darkShadowColor = darkShadowColor,
+ strokeWidth = 4.dp,
+ elevation = 4.dp,
+ lightSource = lightSource
+)
+
+/**
+ * Apply deep neumorphic effect with increased elevation
+ */
+fun Modifier.deepNeumorphic(
+ neuInsets: NeuInsets = NeuInsets(10.dp, 10.dp),
+ neuShape: NeuShape = Punched.Rounded(16.dp),
+ lightShadowColor: Color = Color.White,
+ darkShadowColor: Color = Color.Gray,
+ lightSource: LightSource = LightSource.TOP_LEFT
+) = this.neumorphic(
+ neuInsets = neuInsets,
+ neuShape = neuShape,
+ lightShadowColor = lightShadowColor,
+ darkShadowColor = darkShadowColor,
+ strokeWidth = 10.dp,
+ elevation = 10.dp,
+ lightSource = lightSource
+)
diff --git a/library/src/main/java/me/nikhilchaudhari/library/NeuTheme.kt b/library/src/main/java/me/nikhilchaudhari/library/NeuTheme.kt
new file mode 100644
index 00000000..bd3844da
--- /dev/null
+++ b/library/src/main/java/me/nikhilchaudhari/library/NeuTheme.kt
@@ -0,0 +1,117 @@
+package me.nikhilchaudhari.library
+
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Theme configuration for neumorphic components
+ * Supports Material You dynamic colors on Android 12+
+ */
+object NeuTheme {
+
+ /**
+ * Default neumorphic color scheme for light mode
+ */
+ data class NeuColorScheme(
+ val backgroundColor: Color,
+ val lightShadowColor: Color,
+ val darkShadowColor: Color
+ )
+
+ /**
+ * Light theme color scheme
+ */
+ val LightColorScheme = NeuColorScheme(
+ backgroundColor = Color(0xFFECEAEB),
+ lightShadowColor = Color.White,
+ darkShadowColor = Color(0xFFC8C8C8)
+ )
+
+ /**
+ * Dark theme color scheme
+ */
+ val DarkColorScheme = NeuColorScheme(
+ backgroundColor = Color(0xFF2D2D2D),
+ lightShadowColor = Color(0xFF3D3D3D),
+ darkShadowColor = Color(0xFF1D1D1D)
+ )
+
+ /**
+ * Get the appropriate color scheme based on system theme
+ */
+ @Composable
+ @ReadOnlyComposable
+ fun colorScheme(): NeuColorScheme {
+ return if (isSystemInDarkTheme()) DarkColorScheme else LightColorScheme
+ }
+
+ /**
+ * Create a custom color scheme
+ */
+ fun customColorScheme(
+ backgroundColor: Color,
+ lightShadowColor: Color = backgroundColor.lighten(0.15f),
+ darkShadowColor: Color = backgroundColor.darken(0.15f)
+ ) = NeuColorScheme(
+ backgroundColor = backgroundColor,
+ lightShadowColor = lightShadowColor,
+ darkShadowColor = darkShadowColor
+ )
+
+ /**
+ * Get Material You dynamic colors based color scheme (Android 12+)
+ * Falls back to default scheme on older versions
+ */
+ @Composable
+ @ReadOnlyComposable
+ fun dynamicColorScheme(): NeuColorScheme {
+ return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ val surface = MaterialTheme.colorScheme.surface
+ NeuColorScheme(
+ backgroundColor = surface,
+ lightShadowColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f),
+ darkShadowColor = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
+ )
+ } else {
+ colorScheme()
+ }
+ }
+}
+
+/**
+ * Lighten a color by a percentage
+ */
+fun Color.lighten(factor: Float): Color {
+ return Color(
+ red = (red + (1f - red) * factor).coerceIn(0f, 1f),
+ green = (green + (1f - green) * factor).coerceIn(0f, 1f),
+ blue = (blue + (1f - blue) * factor).coerceIn(0f, 1f),
+ alpha = alpha
+ )
+}
+
+/**
+ * Darken a color by a percentage
+ */
+fun Color.darken(factor: Float): Color {
+ return Color(
+ red = (red * (1f - factor)).coerceIn(0f, 1f),
+ green = (green * (1f - factor)).coerceIn(0f, 1f),
+ blue = (blue * (1f - factor)).coerceIn(0f, 1f),
+ alpha = alpha
+ )
+}
+
+/**
+ * Generate neumorphic shadow colors from a background color
+ */
+fun Color.toNeuColors(): Pair {
+ return Pair(
+ this.lighten(0.2f),
+ this.darken(0.15f)
+ )
+}
diff --git a/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt b/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt
index 745ff9fa..d34d3571 100644
--- a/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt
+++ b/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt
@@ -3,6 +3,10 @@ package me.nikhilchaudhari.library
import android.content.Context
import android.os.Build
import android.util.DisplayMetrics
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.DrawModifier
@@ -22,18 +26,43 @@ import me.nikhilchaudhari.library.shapes.ShapeConfig
import kotlin.math.min
import kotlin.math.roundToInt
+/**
+ * Insets configuration for neumorphic shadows
+ */
data class NeuInsets(
val horizontal: Dp = 6.dp,
val vertical: Dp = 6.dp
)
+/**
+ * Light source direction for neumorphic shadows
+ */
+enum class LightSource {
+ TOP_LEFT,
+ TOP_RIGHT,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT
+}
+
+/**
+ * Apply neumorphic effect to a composable
+ *
+ * @param neuInsets Shadow insets (horizontal and vertical)
+ * @param neuShape Shape type (Punched, Pressed, Pot)
+ * @param lightShadowColor Color of the light shadow (typically white or light gray)
+ * @param darkShadowColor Color of the dark shadow (typically dark gray)
+ * @param strokeWidth Stroke width for internal shadows
+ * @param elevation Shadow elevation
+ * @param lightSource Direction of the light source for shadow placement
+ */
fun Modifier.neumorphic(
neuInsets: NeuInsets = NeuInsets(),
neuShape: NeuShape = Punched.Rounded(),
lightShadowColor: Color = Color.White,
darkShadowColor: Color = Color.LightGray,
strokeWidth: Dp = 6.dp,
- elevation: Dp = 6.dp
+ elevation: Dp = 6.dp,
+ lightSource: LightSource = LightSource.TOP_LEFT
) = composed {
val context = LocalContext.current
this.then(
@@ -45,6 +74,7 @@ fun Modifier.neumorphic(
darkShadowColor,
strokeWidth,
elevation,
+ lightSource,
inspectorInfo = debugInspectorInfo {
name = "neumorphic"
properties["context"] = context
@@ -54,11 +84,53 @@ fun Modifier.neumorphic(
properties["strokeWidth"] = strokeWidth
properties["lightShadowColor"] = lightShadowColor
properties["darkShadowColor"] = darkShadowColor
+ properties["lightSource"] = lightSource
}
)
)
}
+/**
+ * Animated neumorphic effect with smooth transitions
+ *
+ * @param neuInsets Shadow insets (horizontal and vertical)
+ * @param neuShape Shape type (Punched, Pressed, Pot)
+ * @param lightShadowColor Color of the light shadow
+ * @param darkShadowColor Color of the dark shadow
+ * @param strokeWidth Stroke width for internal shadows
+ * @param elevation Shadow elevation
+ * @param lightSource Direction of the light source
+ * @param pressed Whether the component is pressed (for animation)
+ * @param animationDuration Duration of the animation in milliseconds
+ */
+fun Modifier.animatedNeumorphic(
+ neuInsets: NeuInsets = NeuInsets(),
+ neuShape: NeuShape = Punched.Rounded(),
+ lightShadowColor: Color = Color.White,
+ darkShadowColor: Color = Color.LightGray,
+ strokeWidth: Dp = 6.dp,
+ elevation: Dp = 6.dp,
+ lightSource: LightSource = LightSource.TOP_LEFT,
+ pressed: Boolean = false,
+ animationDuration: Int = 150
+) = composed {
+ val animatedElevation by animateDpAsState(
+ targetValue = if (pressed) elevation * 0.5f else elevation,
+ animationSpec = tween(durationMillis = animationDuration),
+ label = "elevationAnimation"
+ )
+
+ neumorphic(
+ neuInsets = neuInsets,
+ neuShape = neuShape,
+ lightShadowColor = lightShadowColor,
+ darkShadowColor = darkShadowColor,
+ strokeWidth = strokeWidth,
+ elevation = animatedElevation,
+ lightSource = lightSource
+ )
+}
+
internal class NeumorphicModifier(
context: Context,
private val insets: NeuInsets,
@@ -67,6 +139,7 @@ internal class NeumorphicModifier(
private val darkShadowColor: Color,
private val strokeWidth: Dp,
private val elevation: Dp,
+ private val lightSource: LightSource,
inspectorInfo: InspectorInfo.() -> Unit
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
@@ -79,12 +152,12 @@ internal class NeumorphicModifier(
elevation,
lightShadowColor,
darkShadowColor,
- strokeWidth
+ strokeWidth,
+ lightSource = lightSource
)
neuShape.drawShadows(this, blurMaker, shapeConfig)
}
- // TODO: Try tweaking values
private fun calculateDefaultBlurRadius(displayMetrics: DisplayMetrics): Int {
val densityStable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
DisplayMetrics.DENSITY_DEVICE_STABLE / DisplayMetrics.DENSITY_DEFAULT.toFloat()
diff --git a/library/src/main/java/me/nikhilchaudhari/library/internal/Blur.kt b/library/src/main/java/me/nikhilchaudhari/library/internal/Blur.kt
index b2ec494c..68383222 100644
--- a/library/src/main/java/me/nikhilchaudhari/library/internal/Blur.kt
+++ b/library/src/main/java/me/nikhilchaudhari/library/internal/Blur.kt
@@ -2,12 +2,13 @@ package me.nikhilchaudhari.library.internal
import android.content.Context
import android.graphics.*
+import android.os.Build
import android.renderscript.*
import java.lang.ref.WeakReference
/**
- * Holds required data for of blur operation
+ * Holds required data for blur operation
*/
data class BlurConfig(
val width: Int,
@@ -22,7 +23,10 @@ data class BlurConfig(
}
}
-//TODO: Make sure if this is being invoked properly
+/**
+ * Modern blur implementation with fallback support
+ * Uses RenderScript for API < 31, StackBlur as fallback for all versions
+ */
class BlurMaker(context: Context, private val defaultBlurRadius: Int) {
private val contextRef = WeakReference(context)
@@ -47,7 +51,7 @@ class BlurMaker(context: Context, private val defaultBlurRadius: Int) {
if (width == 0 || height == 0) {
return null
}
- //create a bitmap
+ // Create a bitmap
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
Canvas(bitmap).run {
@@ -59,15 +63,21 @@ class BlurMaker(context: Context, private val defaultBlurRadius: Int) {
drawBitmap(source, 0f, 0f, paint)
}
- //try blur with renderscript
+ // Try blur with appropriate method based on API level
val blurBitmap: Bitmap? = try {
- blurWithRenderScript(bitmap, blurConfig.radius)
- } catch (e: RSRuntimeException) {
- // if renderscript fails, use stack blur
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ // Use StackBlur for Android 12+ as RenderScript is deprecated
+ bitmap.stackBlur(blurConfig.radius)
+ } else {
+ // Use RenderScript for older versions with StackBlur fallback
+ blurWithRenderScript(bitmap, blurConfig.radius)
+ }
+ } catch (e: Exception) {
+ // Fallback to StackBlur if anything fails
bitmap.stackBlur(blurConfig.radius)
}
- // return blurred bitmap
+ // Return blurred bitmap
return blurBitmap?.let {
if (blurConfig.sampling == BlurConfig.DEFAULT_SAMPLING) {
it
@@ -80,8 +90,14 @@ class BlurMaker(context: Context, private val defaultBlurRadius: Int) {
}
}
+ @Suppress("DEPRECATION")
@Throws(RSRuntimeException::class)
private fun blurWithRenderScript(bitmap: Bitmap, radius: Int): Bitmap? {
+ // Only use RenderScript on API < 31
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ return bitmap.stackBlur(radius)
+ }
+
val context = contextRef.get() ?: return null
var blur: ScriptIntrinsicBlur? = null
var input: Allocation? = null
diff --git a/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt b/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt
index a0efeb5d..1242461f 100644
--- a/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt
+++ b/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt
@@ -10,12 +10,34 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.inset
import androidx.compose.ui.graphics.toArgb
import androidx.core.graphics.withTranslation
+import me.nikhilchaudhari.library.LightSource
import me.nikhilchaudhari.library.internal.BlurMaker
import kotlin.math.roundToInt
-// TODO: Try tweaking canvas values
-/*pressed shape - draw after the content */
+/**
+ * Calculate shadow offsets based on light source direction
+ */
+internal fun getLightShadowOffset(lightSource: LightSource, elevation: Float): Pair {
+ return when (lightSource) {
+ LightSource.TOP_LEFT -> Pair(-elevation, -elevation)
+ LightSource.TOP_RIGHT -> Pair(elevation, -elevation)
+ LightSource.BOTTOM_LEFT -> Pair(-elevation, elevation)
+ LightSource.BOTTOM_RIGHT -> Pair(elevation, elevation)
+ }
+}
+
+internal fun getDarkShadowOffset(lightSource: LightSource, elevation: Float): Pair {
+ return when (lightSource) {
+ LightSource.TOP_LEFT -> Pair(elevation, elevation)
+ LightSource.TOP_RIGHT -> Pair(-elevation, elevation)
+ LightSource.BOTTOM_LEFT -> Pair(elevation, -elevation)
+ LightSource.BOTTOM_RIGHT -> Pair(-elevation, -elevation)
+ }
+}
+
+
+/* pressed shape - draw after the content */
internal fun ContentDrawScope.drawOnForeground(
shapeConfig: ShapeConfig,
blurMaker: BlurMaker
@@ -40,7 +62,7 @@ internal fun ContentDrawScope.drawOnForeground(
setStroke(strokeWidth, shapeConfig.lightShadowColor.toArgb())
setBounds(0, 0, width, height)
setColor(Color.Transparent.toArgb())
- setNeuShape(cornerType, ShadowForm.LightShadow, radius)
+ setNeuShape(cornerType, ShadowForm.LightShadow, radius, shapeConfig.lightSource)
}
darkShadowDrawable.apply {
@@ -48,9 +70,11 @@ internal fun ContentDrawScope.drawOnForeground(
setStroke(strokeWidth, shapeConfig.darkShadowColor.toArgb())
setColor(Color.Transparent.toArgb())
setBounds(0, 0, width, height)
- setNeuShape(cornerType, ShadowForm.DarkShadow, radius)
+ setNeuShape(cornerType, ShadowForm.DarkShadow, radius, shapeConfig.lightSource)
}
+ val lightOffset = getLightShadowOffset(shapeConfig.lightSource, elevation)
+ val darkOffset = getDarkShadowOffset(shapeConfig.lightSource, elevation)
generateShadowBitmap(
size.width.toInt(),
@@ -58,7 +82,9 @@ internal fun ContentDrawScope.drawOnForeground(
lightShadowDrawable,
darkShadowDrawable,
elevation,
- blurMaker
+ blurMaker,
+ lightOffset,
+ darkOffset
)?.asImageBitmap()?.let {
drawScope.drawImage(it)
}
@@ -71,17 +97,19 @@ private fun generateShadowBitmap(
lightShadowDrawable: GradientDrawable,
darkShadowDrawable: GradientDrawable,
elevation: Float,
- blurMaker: BlurMaker
+ blurMaker: BlurMaker,
+ lightOffset: Pair,
+ darkOffset: Pair
) = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888).blurred(blurMaker) {
withTranslation(
- x = -elevation,
- y = -elevation
+ x = lightOffset.first,
+ y = lightOffset.second
) {
lightShadowDrawable.draw(this)
}
withTranslation(
-// x = elevation,
-// y = elevation
+ x = darkOffset.first * 0f, // Minimal offset for inner shadow
+ y = darkOffset.second * 0f
) {
darkShadowDrawable.draw(this)
}
@@ -112,14 +140,14 @@ internal fun ContentDrawScope.drawOnBackground(
setColor(shapeConfig.lightShadowColor.toArgb())
setSize(width, height)
setBounds(0, 0, width, height)
- setNeuShape(cornerType, ShadowForm.Default, radius)
+ setNeuShape(cornerType, ShadowForm.Default, radius, shapeConfig.lightSource)
}
darkShadowDrawable.apply {
setColor(shapeConfig.darkShadowColor.toArgb())
setSize(width, height)
setBounds(0, 0, width, height)
- setNeuShape(cornerType, ShadowForm.Default, radius)
+ setNeuShape(cornerType, ShadowForm.Default, radius, shapeConfig.lightSource)
}
val lightShadowBitmap =
@@ -131,20 +159,29 @@ internal fun ContentDrawScope.drawOnBackground(
.toBlurredBitmap(width, height, elevation, blurMaker)
?.asImageBitmap()
+ // Calculate insets based on light source
+ val (lightHInset, lightVInset) = when (shapeConfig.lightSource) {
+ LightSource.TOP_LEFT -> Pair(-(horizontalInset + elevation), -(verticalInset + elevation))
+ LightSource.TOP_RIGHT -> Pair(horizontalInset - elevation, -(verticalInset + elevation))
+ LightSource.BOTTOM_LEFT -> Pair(-(horizontalInset + elevation), verticalInset - elevation)
+ LightSource.BOTTOM_RIGHT -> Pair(horizontalInset - elevation, verticalInset - elevation)
+ }
+
+ val (darkHInset, darkVInset) = when (shapeConfig.lightSource) {
+ LightSource.TOP_LEFT -> Pair(horizontalInset - elevation, verticalInset - elevation)
+ LightSource.TOP_RIGHT -> Pair(-(horizontalInset + elevation), verticalInset - elevation)
+ LightSource.BOTTOM_LEFT -> Pair(horizontalInset - elevation, -(verticalInset + elevation))
+ LightSource.BOTTOM_RIGHT -> Pair(-(horizontalInset + elevation), -(verticalInset + elevation))
+ }
+
lightShadowBitmap?.let { bitmap ->
- drawScope.inset(
- -(horizontalInset + elevation),
- -(verticalInset + elevation)
- ) {
+ drawScope.inset(lightHInset, lightVInset) {
drawImage(bitmap)
}
}
darkShadowBitmap?.let { bitmap ->
- drawScope.inset(
- horizontalInset - elevation,
- verticalInset - elevation
- ) {
+ drawScope.inset(darkHInset, darkVInset) {
drawImage(bitmap)
}
}
@@ -190,7 +227,8 @@ internal sealed class ShadowForm {
private fun GradientDrawable.setNeuShape(
cornerType: CornerType,
shadowForm: ShadowForm,
- radius: Float
+ radius: Float,
+ lightSource: LightSource = LightSource.TOP_LEFT
) {
when (cornerType) {
is CornerType.Oval -> {
@@ -203,33 +241,40 @@ private fun GradientDrawable.setNeuShape(
cornerRadius = radius
}
is ShadowForm.LightShadow -> {
- cornerRadii =
- floatArrayOf(
- 0f,
- 0f,
- radius,
- radius,
- radius,
- radius,
- radius,
- radius
+ // Adjust corner radii based on light source
+ cornerRadii = when (lightSource) {
+ LightSource.TOP_LEFT -> floatArrayOf(
+ 0f, 0f, radius, radius, radius, radius, radius, radius
+ )
+ LightSource.TOP_RIGHT -> floatArrayOf(
+ radius, radius, 0f, 0f, radius, radius, radius, radius
+ )
+ LightSource.BOTTOM_LEFT -> floatArrayOf(
+ radius, radius, radius, radius, 0f, 0f, radius, radius
+ )
+ LightSource.BOTTOM_RIGHT -> floatArrayOf(
+ radius, radius, radius, radius, radius, radius, 0f, 0f
)
+ }
}
is ShadowForm.DarkShadow -> {
- cornerRadii =
- floatArrayOf(
- radius,
- radius,
- radius,
- radius,
- 0f,
- 0f,
- radius,
- radius
+ // Adjust corner radii based on light source (opposite of light)
+ cornerRadii = when (lightSource) {
+ LightSource.TOP_LEFT -> floatArrayOf(
+ radius, radius, radius, radius, 0f, 0f, radius, radius
)
+ LightSource.TOP_RIGHT -> floatArrayOf(
+ radius, radius, radius, radius, radius, radius, 0f, 0f
+ )
+ LightSource.BOTTOM_LEFT -> floatArrayOf(
+ 0f, 0f, radius, radius, radius, radius, radius, radius
+ )
+ LightSource.BOTTOM_RIGHT -> floatArrayOf(
+ radius, radius, 0f, 0f, radius, radius, radius, radius
+ )
+ }
}
}
}
}
}
-
diff --git a/library/src/main/java/me/nikhilchaudhari/library/shapes/ShapeConfig.kt b/library/src/main/java/me/nikhilchaudhari/library/shapes/ShapeConfig.kt
index ca282b41..13f92478 100644
--- a/library/src/main/java/me/nikhilchaudhari/library/shapes/ShapeConfig.kt
+++ b/library/src/main/java/me/nikhilchaudhari/library/shapes/ShapeConfig.kt
@@ -3,6 +3,7 @@ package me.nikhilchaudhari.library.shapes
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import me.nikhilchaudhari.library.LightSource
import me.nikhilchaudhari.library.NeuInsets
@@ -23,5 +24,6 @@ data class ShapeConfig(
val lightShadowColor: Color,
val darkShadowColor: Color,
val strokeWidth: Dp,
- var cornerType: CornerType = CornerType.Rounded()
+ var cornerType: CornerType = CornerType.Rounded(),
+ val lightSource: LightSource = LightSource.TOP_LEFT
)
diff --git a/settings.gradle b/settings.gradle
index f563cff3..9f6f9776 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,11 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
@@ -8,3 +16,4 @@ dependencyResolutionManagement {
rootProject.name = "NeumorphismCompose"
include ':app'
include ':library'
+include ':library-views'
From 80a74e1f4461ae327c860aa4803501029a319bba Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Jan 2026 12:27:28 +0000
Subject: [PATCH 03/36] Address code review feedback: improve constants, layer
type, and remove unused code
Co-authored-by: obieda-hussien <184546202+obieda-hussien@users.noreply.github.com>
---
.../library/views/NeumorphicButton.kt | 11 +++++++++--
.../library/views/NeumorphicCardView.kt | 8 +++++++-
.../library/views/NeumorphicView.kt | 8 +++++++-
.../java/me/nikhilchaudhari/library/Neumorphic.kt | 15 +++++++++++++--
.../me/nikhilchaudhari/library/shapes/Drawing.kt | 10 +++-------
5 files changed, 39 insertions(+), 13 deletions(-)
diff --git a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt
index 6501cea8..250a0399 100644
--- a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt
+++ b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicButton.kt
@@ -7,6 +7,7 @@ import android.util.AttributeSet
import android.view.MotionEvent
import androidx.appcompat.widget.AppCompatButton
import me.nikhilchaudhari.library.LightSource
+import me.nikhilchaudhari.library.NeuConstants
import me.nikhilchaudhari.library.internal.BlurMaker
import me.nikhilchaudhari.library.internal.stackBlur
import kotlin.math.min
@@ -197,7 +198,13 @@ class NeumorphicButton @JvmOverloads constructor(
stateListAnimator = null
// Set layer type for proper shadow rendering
- setLayerType(LAYER_TYPE_SOFTWARE, null)
+ // Use hardware layer on modern devices, fall back to software for complex operations
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ setLayerType(LAYER_TYPE_HARDWARE, null)
+ } else {
+ // Software layer required for blur effects on older devices
+ setLayerType(LAYER_TYPE_SOFTWARE, null)
+ }
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
@@ -211,7 +218,7 @@ class NeumorphicButton @JvmOverloads constructor(
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isPressed = true
- currentElevation = neuElevation * 0.5f
+ currentElevation = neuElevation * NeuConstants.PRESSED_ELEVATION_FACTOR
needsRedraw = true
invalidate()
}
diff --git a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt
index d82b4006..75232c34 100644
--- a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt
+++ b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicCardView.kt
@@ -191,7 +191,13 @@ class NeumorphicCardView @JvmOverloads constructor(
setWillNotDraw(false)
// Set layer type for proper shadow rendering
- setLayerType(LAYER_TYPE_SOFTWARE, null)
+ // Use hardware layer on modern devices, fall back to software for complex operations
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ setLayerType(LAYER_TYPE_HARDWARE, null)
+ } else {
+ // Software layer required for blur effects on older devices
+ setLayerType(LAYER_TYPE_SOFTWARE, null)
+ }
// Add padding for shadows
setPadding(
diff --git a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt
index 701d3c32..dd51c76c 100644
--- a/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt
+++ b/library-views/src/main/java/me/nikhilchaudhari/library/views/NeumorphicView.kt
@@ -190,7 +190,13 @@ open class NeumorphicView @JvmOverloads constructor(
setWillNotDraw(false)
// Set layer type for proper shadow rendering
- setLayerType(LAYER_TYPE_SOFTWARE, null)
+ // Use hardware layer on modern devices, fall back to software for complex operations
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ setLayerType(LAYER_TYPE_HARDWARE, null)
+ } else {
+ // Software layer required for blur effects on older devices
+ setLayerType(LAYER_TYPE_SOFTWARE, null)
+ }
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
diff --git a/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt b/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt
index d34d3571..b30f45ca 100644
--- a/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt
+++ b/library/src/main/java/me/nikhilchaudhari/library/Neumorphic.kt
@@ -44,6 +44,17 @@ enum class LightSource {
BOTTOM_RIGHT
}
+/**
+ * Constants for neumorphic effects
+ */
+object NeuConstants {
+ /** Factor by which elevation is reduced when pressed */
+ const val PRESSED_ELEVATION_FACTOR = 0.5f
+
+ /** Default animation duration in milliseconds */
+ const val DEFAULT_ANIMATION_DURATION_MS = 150
+}
+
/**
* Apply neumorphic effect to a composable
*
@@ -112,10 +123,10 @@ fun Modifier.animatedNeumorphic(
elevation: Dp = 6.dp,
lightSource: LightSource = LightSource.TOP_LEFT,
pressed: Boolean = false,
- animationDuration: Int = 150
+ animationDuration: Int = NeuConstants.DEFAULT_ANIMATION_DURATION_MS
) = composed {
val animatedElevation by animateDpAsState(
- targetValue = if (pressed) elevation * 0.5f else elevation,
+ targetValue = if (pressed) elevation * NeuConstants.PRESSED_ELEVATION_FACTOR else elevation,
animationSpec = tween(durationMillis = animationDuration),
label = "elevationAnimation"
)
diff --git a/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt b/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt
index 1242461f..2160d626 100644
--- a/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt
+++ b/library/src/main/java/me/nikhilchaudhari/library/shapes/Drawing.kt
@@ -99,7 +99,7 @@ private fun generateShadowBitmap(
elevation: Float,
blurMaker: BlurMaker,
lightOffset: Pair,
- darkOffset: Pair
+ @Suppress("UNUSED_PARAMETER") darkOffset: Pair
) = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888).blurred(blurMaker) {
withTranslation(
x = lightOffset.first,
@@ -107,12 +107,8 @@ private fun generateShadowBitmap(
) {
lightShadowDrawable.draw(this)
}
- withTranslation(
- x = darkOffset.first * 0f, // Minimal offset for inner shadow
- y = darkOffset.second * 0f
- ) {
- darkShadowDrawable.draw(this)
- }
+ // Dark shadow is drawn at origin for inner shadow effect
+ darkShadowDrawable.draw(this)
}
From b424fd087e5695be94dbb7dc030da09a88ae7716 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Jan 2026 12:32:40 +0000
Subject: [PATCH 04/36] Add modern CI workflow with APK artifact and update
Material 3 to 1.3.0
Co-authored-by: obieda-hussien <184546202+obieda-hussien@users.noreply.github.com>
---
.github/workflows/ci-build.yml | 110 ++++++++++++++++++++++++++++++---
app/build.gradle | 2 +-
build.gradle | 1 +
library/build.gradle | 2 +-
4 files changed, 104 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 5b908eef..bd51048c 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -1,26 +1,118 @@
-# This workflow will build a Java project with Gradle
+# Modern Android CI/CD workflow with APK artifact
-name: Java/Kotlin CI with Gradle
+name: Android CI Build
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
+ workflow_dispatch: # Allows manual triggering
jobs:
build:
-
+ name: Build & Generate APK
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Set up JDK 11
- uses: actions/setup-java@v2
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
with:
- java-version: '11'
- distribution: 'adopt'
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
- name: Grant execute permission for gradlew
run: chmod +x gradlew
+
+ - name: Validate Gradle wrapper
+ uses: gradle/wrapper-validation-action@v2
+
- name: Build with Gradle
- run: ./gradlew build
+ run: ./gradlew build --no-daemon
+
+ - name: Build Debug APK
+ run: ./gradlew assembleDebug --no-daemon
+
+ - name: Build Release APK
+ run: ./gradlew assembleRelease --no-daemon
+
+ - name: Upload Debug APK
+ uses: actions/upload-artifact@v4
+ with:
+ name: app-debug
+ path: app/build/outputs/apk/debug/app-debug.apk
+ retention-days: 30
+
+ - name: Upload Release APK
+ uses: actions/upload-artifact@v4
+ with:
+ name: app-release-unsigned
+ path: app/build/outputs/apk/release/app-release-unsigned.apk
+ retention-days: 30
+
+ test:
+ name: Run Unit Tests
+ runs-on: ubuntu-latest
+ needs: build
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Run Unit Tests
+ run: ./gradlew test --no-daemon
+
+ - name: Upload Test Results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: test-results
+ path: |
+ app/build/reports/tests/
+ library/build/reports/tests/
+ retention-days: 14
+
+ lint:
+ name: Run Lint
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: gradle
+
+ - name: Grant execute permission for gradlew
+ run: chmod +x gradlew
+
+ - name: Run Lint
+ run: ./gradlew lint --no-daemon
+
+ - name: Upload Lint Results
+ uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: lint-results
+ path: |
+ app/build/reports/lint-results*.html
+ library/build/reports/lint-results*.html
+ retention-days: 14
diff --git a/app/build.gradle b/app/build.gradle
index b36f33f3..a067b274 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -48,7 +48,7 @@ dependencies {
implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
- implementation "androidx.compose.material3:material3:1.1.2"
+ implementation "androidx.compose.material3:material3:$material3_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.animation:animation:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
diff --git a/build.gradle b/build.gradle
index 47c03126..f5d63acf 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,6 +4,7 @@ buildscript {
compose_version = '1.5.4'
kotlin_version = '1.9.20'
compose_compiler_version = '1.5.4'
+ material3_version = '1.3.0'
}
repositories {
google()
diff --git a/library/build.gradle b/library/build.gradle
index 7e1e1270..900f1d51 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -57,7 +57,7 @@ dependencies {
implementation 'com.google.android.material:material:1.11.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
- implementation "androidx.compose.material3:material3:1.1.2"
+ implementation "androidx.compose.material3:material3:$material3_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.animation:animation:$compose_version"
testImplementation 'junit:junit:4.13.2'
From 5a351c8a1e2f3cd6785d534c55607a3360cb284b Mon Sep 17 00:00:00 2001
From: Obieda
Date: Wed, 7 Jan 2026 14:38:11 +0200
Subject: [PATCH 05/36] Modify CI workflow to support all branches
Updated CI workflow to trigger on all branches and pull requests.
---
.github/workflows/ci-build.yml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index bd51048c..8e0b9b77 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -4,10 +4,10 @@ name: Android CI Build
on:
push:
- branches: [ main ]
- pull_request:
- branches: [ main ]
- workflow_dispatch: # Allows manual triggering
+ branches:
+ - '**' # هذا يسمح للـ Workflow بالعمل عند أي Push على أي فرع
+ pull_request: # هذا يسمح للـ Workflow بالعمل مع كل الـ Pull Requests لأي فرع
+ workflow_dispatch: # يسمح بتشغيل الـ Workflow يدوياً
jobs:
build:
From c9acee60e839c96242a16d4456e33d34ea4b8635 Mon Sep 17 00:00:00 2001
From: Obieda
Date: Wed, 7 Jan 2026 14:43:21 +0200
Subject: [PATCH 06/36] Update CI workflow for better branch handling
---
.github/workflows/ci-build.yml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 8e0b9b77..0fcc8dfb 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -5,9 +5,11 @@ name: Android CI Build
on:
push:
branches:
- - '**' # هذا يسمح للـ Workflow بالعمل عند أي Push على أي فرع
- pull_request: # هذا يسمح للـ Workflow بالعمل مع كل الـ Pull Requests لأي فرع
- workflow_dispatch: # يسمح بتشغيل الـ Workflow يدوياً
+ - '**' # يعمل عند أي Push على أي فرع في الـ Fork
+ pull_request:
+ branches:
+ - '**' # يعمل عند فتح PR لأي فرع داخل الـ Fork
+ workflow_dispatch: # يسمح بتشغيل الـ Workflow يدوياً من واجهة GitHub
jobs:
build:
@@ -29,7 +31,7 @@ jobs:
run: chmod +x gradlew
- name: Validate Gradle wrapper
- uses: gradle/wrapper-validation-action@v2
+ uses: gradle/wrapper-validation-action@v3 # تحديث للإصدار الأحدث
- name: Build with Gradle
run: ./gradlew build --no-daemon
@@ -82,8 +84,7 @@ jobs:
with:
name: test-results
path: |
- app/build/reports/tests/
- library/build/reports/tests/
+ **/build/reports/tests/
retention-days: 14
lint:
@@ -113,6 +114,5 @@ jobs:
with:
name: lint-results
path: |
- app/build/reports/lint-results*.html
- library/build/reports/lint-results*.html
+ **/build/reports/lint-results*.html
retention-days: 14
From 3e01403fa5bf207ba102610e14e3fc10becd3450 Mon Sep 17 00:00:00 2001
From: Obieda
Date: Wed, 7 Jan 2026 14:45:51 +0200
Subject: [PATCH 07/36] Update ci-build.yml
---
.github/workflows/ci-build.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml
index 0fcc8dfb..36a5bb5c 100644
--- a/.github/workflows/ci-build.yml
+++ b/.github/workflows/ci-build.yml
@@ -1,4 +1,4 @@
-# Modern Android CI/CD workflow with APK artifact
+# Modern Android CI/CD workflow with APK artifact
name: Android CI Build
From 71cd22a4eaf66d983958584184647a73dc923fa5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Jan 2026 12:56:21 +0000
Subject: [PATCH 08/36] Initial plan
From 6c85e14409c774de94433a0ef6f8add749e22b42 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 7 Jan 2026 13:17:25 +0000
Subject: [PATCH 09/36] Upgrade to Material 3 Expressive with modern neumorphic
components
Co-authored-by: obieda-hussien <184546202+obieda-hussien@users.noreply.github.com>
---
app/build.gradle | 75 +-
.../neumorphiccompose/MainActivity.kt | 876 ++++++++++++++----
.../neumorphiccompose/ui/theme/Color.kt | 28 +
.../neumorphiccompose/ui/theme/Shape.kt | 11 +-
.../neumorphiccompose/ui/theme/Theme.kt | 170 +++-
.../neumorphiccompose/ui/theme/Type.kt | 112 ++-
app/src/main/res/values-night/themes.xml | 7 +-
app/src/main/res/values/themes.xml | 11 +-
build.gradle | 13 +-
gradle/wrapper/gradle-wrapper.properties | 2 +-
.../aapt/AndroidManifest.xml | 7 +
.../aapt/output-metadata.json | 18 +
.../debug/syncDebugLibJars/classes.jar | Bin 0 -> 31239 bytes
.../aar-metadata.properties | 6 +
.../annotationProcessors.json | 1 +
.../extractDebugAnnotations/typedefs.txt | 0
.../debug/generateDebugRFile/R.jar | Bin 0 -> 5214 bytes
.../debug/generateDebugRFile/R.txt | 68 ++
.../debug-mergeJavaRes/merge-state | Bin 0 -> 880 bytes
.../compile-file-map.properties | 1 +
.../merged.dir/values/values.xml | 88 ++
.../debug/packageDebugResources/merger.xml | 164 ++++
.../mergeDebugJniLibFolders/merger.xml | 2 +
.../incremental/mergeDebugShaders/merger.xml | 2 +
.../incremental/packageDebugAssets/merger.xml | 2 +
.../library-views_debug.kotlin_module | Bin 0 -> 24 bytes
.../debug/parseDebugLocalResources/R-def.txt | 26 +
.../manifest-merger-blame-debug-report.txt | 7 +
.../proguard.txt | 2 +
.../feature-library-views.jar | Bin 0 -> 205 bytes
.../processDebugManifest/AndroidManifest.xml | 7 +
.../nestedResourcesValidationReport.txt | 1 +
.../packageDebugResources/values/values.xml | 88 ++
.../generateDebugRFile/package-aware-r.txt | 25 +
.../caches-jvm/inputs/source-to-output.tab | Bin 0 -> 4096 bytes
.../inputs/source-to-output.tab.keystream | Bin 0 -> 4096 bytes
.../inputs/source-to-output.tab.keystream.len | Bin 0 -> 8 bytes
.../inputs/source-to-output.tab.len | Bin 0 -> 8 bytes
.../inputs/source-to-output.tab.values.at | Bin 0 -> 1848 bytes
.../caches-jvm/inputs/source-to-output.tab_i | Bin 0 -> 32768 bytes
.../inputs/source-to-output.tab_i.len | Bin 0 -> 8 bytes
.../jvm/kotlin/class-attributes.tab | Bin 0 -> 4096 bytes
.../jvm/kotlin/class-attributes.tab.keystream | Bin 0 -> 4096 bytes
.../kotlin/class-attributes.tab.keystream.len | Bin 0 -> 8 bytes
.../jvm/kotlin/class-attributes.tab.len | Bin 0 -> 8 bytes
.../jvm/kotlin/class-attributes.tab.values.at | Bin 0 -> 64 bytes
.../jvm/kotlin/class-attributes.tab_i | Bin 0 -> 32768 bytes
.../jvm/kotlin/class-attributes.tab_i.len | Bin 0 -> 8 bytes
.../jvm/kotlin/class-fq-name-to-source.tab | Bin 0 -> 4096 bytes
.../class-fq-name-to-source.tab.keystream | Bin 0 -> 4096 bytes
.../class-fq-name-to-source.tab.keystream.len | Bin 0 -> 8 bytes
.../kotlin/class-fq-name-to-source.tab.len | Bin 0 -> 8 bytes
.../class-fq-name-to-source.tab.values.at | Bin 0 -> 871 bytes
.../jvm/kotlin/class-fq-name-to-source.tab_i | Bin 0 -> 32768 bytes
.../kotlin/class-fq-name-to-source.tab_i.len | Bin 0 -> 8 bytes
.../jvm/kotlin/internal-name-to-source.tab | Bin 0 -> 4096 bytes
.../internal-name-to-source.tab.keystream | Bin 0 -> 4096 bytes
.../internal-name-to-source.tab.keystream.len | Bin 0 -> 8 bytes
.../kotlin/internal-name-to-source.tab.len | Bin 0 -> 8 bytes
.../internal-name-to-source.tab.values.at | Bin 0 -> 1369 bytes
.../jvm/kotlin/internal-name-to-source.tab_i | Bin 0 -> 32768 bytes
.../kotlin/internal-name-to-source.tab_i.len | Bin 0 -> 8 bytes
.../cacheable/caches-jvm/jvm/kotlin/proto.tab | Bin 0 -> 4096 bytes
.../caches-jvm/jvm/kotlin/proto.tab.keystream | Bin 0 -> 4096 bytes
.../jvm/kotlin/proto.tab.keystream.len | Bin 0 -> 8 bytes
.../caches-jvm/jvm/kotlin/proto.tab.len | Bin 0 -> 8 bytes
.../caches-jvm/jvm/kotlin/proto.tab.values.at | Bin 0 -> 9834 bytes
.../caches-jvm/jvm/kotlin/proto.tab_i | Bin 0 -> 32768 bytes
.../caches-jvm/jvm/kotlin/proto.tab_i.len | Bin 0 -> 8 bytes
.../jvm/kotlin/source-to-classes.tab | Bin 0 -> 4096 bytes
.../kotlin/source-to-classes.tab.keystream | Bin 0 -> 4096 bytes
.../source-to-classes.tab.keystream.len | Bin 0 -> 8 bytes
.../jvm/kotlin/source-to-classes.tab.len | Bin 0 -> 8 bytes
.../kotlin/source-to-classes.tab.values.at | Bin 0 -> 1034 bytes
.../jvm/kotlin/source-to-classes.tab_i | Bin 0 -> 32768 bytes
.../jvm/kotlin/source-to-classes.tab_i.len | Bin 0 -> 8 bytes
.../caches-jvm/jvm/kotlin/subtypes.tab | Bin 0 -> 4096 bytes
.../jvm/kotlin/subtypes.tab.keystream | Bin 0 -> 4096 bytes
.../jvm/kotlin/subtypes.tab.keystream.len | Bin 0 -> 8 bytes
.../caches-jvm/jvm/kotlin/subtypes.tab.len | Bin 0 -> 8 bytes
.../jvm/kotlin/subtypes.tab.values.at | Bin 0 -> 300 bytes
.../caches-jvm/jvm/kotlin/subtypes.tab_i | Bin 0 -> 32768 bytes
.../caches-jvm/jvm/kotlin/subtypes.tab_i.len | Bin 0 -> 8 bytes
.../caches-jvm/jvm/kotlin/supertypes.tab | Bin 0 -> 4096 bytes
.../jvm/kotlin/supertypes.tab.keystream | Bin 0 -> 4096 bytes
.../jvm/kotlin/supertypes.tab.keystream.len | Bin 0 -> 8 bytes
.../caches-jvm/jvm/kotlin/supertypes.tab.len | Bin 0 -> 8 bytes
.../jvm/kotlin/supertypes.tab.values.at | Bin 0 -> 170 bytes
.../caches-jvm/jvm/kotlin/supertypes.tab_i | Bin 0 -> 32768 bytes
.../jvm/kotlin/supertypes.tab_i.len | Bin 0 -> 8 bytes
.../cacheable/caches-jvm/lookups/counters.tab | 2 +
.../caches-jvm/lookups/file-to-id.tab | Bin 0 -> 4096 bytes
.../lookups/file-to-id.tab.keystream | Bin 0 -> 4096 bytes
.../lookups/file-to-id.tab.keystream.len | Bin 0 -> 8 bytes
.../caches-jvm/lookups/file-to-id.tab.len | Bin 0 -> 8 bytes
.../lookups/file-to-id.tab.values.at | Bin 0 -> 67 bytes
.../caches-jvm/lookups/file-to-id.tab_i | Bin 0 -> 32768 bytes
.../caches-jvm/lookups/file-to-id.tab_i.len | Bin 0 -> 8 bytes
.../caches-jvm/lookups/id-to-file.tab | Bin 0 -> 4096 bytes
.../lookups/id-to-file.tab.keystream | Bin 0 -> 4096 bytes
.../lookups/id-to-file.tab.keystream.len | Bin 0 -> 8 bytes
.../caches-jvm/lookups/id-to-file.tab.len | Bin 0 -> 8 bytes
.../lookups/id-to-file.tab.values.at | Bin 0 -> 298 bytes
.../caches-jvm/lookups/id-to-file.tab_i | Bin 0 -> 32768 bytes
.../caches-jvm/lookups/id-to-file.tab_i.len | Bin 0 -> 8 bytes
.../cacheable/caches-jvm/lookups/lookups.tab | Bin 0 -> 8192 bytes
.../caches-jvm/lookups/lookups.tab.keystream | Bin 0 -> 36864 bytes
.../lookups/lookups.tab.keystream.len | Bin 0 -> 8 bytes
.../caches-jvm/lookups/lookups.tab.len | Bin 0 -> 8 bytes
.../caches-jvm/lookups/lookups.tab.values.at | Bin 0 -> 5135 bytes
.../caches-jvm/lookups/lookups.tab_i | Bin 0 -> 32768 bytes
.../caches-jvm/lookups/lookups.tab_i.len | Bin 0 -> 8 bytes
.../cacheable/last-build.bin | Bin 0 -> 18 bytes
.../shrunk-classpath-snapshot.bin | Bin 0 -> 31132 bytes
.../local-state/build-history.bin | Bin 0 -> 48 bytes
.../build/outputs/aar/library-views-debug.aar | Bin 0 -> 31688 bytes
.../logs/manifest-merger-debug-report.txt | 16 +
.../library-views_debug.kotlin_module | Bin 0 -> 24 bytes
.../library/views/NeuCornerType.class | Bin 0 -> 1919 bytes
.../library/views/NeuShapeType.class | Bin 0 -> 1972 bytes
.../views/NeumorphicButton$WhenMappings.class | Bin 0 -> 1603 bytes
.../library/views/NeumorphicButton.class | Bin 0 -> 18364 bytes
.../NeumorphicCardView$WhenMappings.class | Bin 0 -> 1609 bytes
.../library/views/NeumorphicCardView.class | Bin 0 -> 18037 bytes
.../views/NeumorphicView$WhenMappings.class | Bin 0 -> 1597 bytes
.../library/views/NeumorphicView.class | Bin 0 -> 17248 bytes
library/build.gradle | 51 +-
.../nikhilchaudhari/library/NeuExtensions.kt | 153 +++
.../me/nikhilchaudhari/library/NeuTheme.kt | 162 +++-
.../me/nikhilchaudhari/library/Neumorphic.kt | 157 ++++
.../library/components/NeuComponents.kt | 667 +++++++++++++
.../library/internal/StackBlur.kt | 3 +-
132 files changed, 2717 insertions(+), 314 deletions(-)
create mode 100644 library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml
create mode 100644 library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json
create mode 100644 library-views/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar
create mode 100644 library-views/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties
create mode 100644 library-views/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json
create mode 100644 library-views/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt
create mode 100644 library-views/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar
create mode 100644 library-views/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt
create mode 100644 library-views/build/intermediates/incremental/debug-mergeJavaRes/merge-state
create mode 100644 library-views/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties
create mode 100644 library-views/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml
create mode 100644 library-views/build/intermediates/incremental/debug/packageDebugResources/merger.xml
create mode 100644 library-views/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml
create mode 100644 library-views/build/intermediates/incremental/mergeDebugShaders/merger.xml
create mode 100644 library-views/build/intermediates/incremental/packageDebugAssets/merger.xml
create mode 100644 library-views/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/library-views_debug.kotlin_module
create mode 100644 library-views/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt
create mode 100644 library-views/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt
create mode 100644 library-views/build/intermediates/merged_consumer_proguard_file/debug/mergeDebugConsumerProguardFiles/proguard.txt
create mode 100644 library-views/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-library-views.jar
create mode 100644 library-views/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml
create mode 100644 library-views/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt
create mode 100644 library-views/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml
create mode 100644 library-views/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i.len
create mode 100644 library-views/build/kotlin/compileDebugKotlin/cacheable/last-build.bin
create mode 100644 library-views/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin
create mode 100644 library-views/build/kotlin/compileDebugKotlin/local-state/build-history.bin
create mode 100644 library-views/build/outputs/aar/library-views-debug.aar
create mode 100644 library-views/build/outputs/logs/manifest-merger-debug-report.txt
create mode 100644 library-views/build/tmp/kotlin-classes/debug/META-INF/library-views_debug.kotlin_module
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeuCornerType.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeuShapeType.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeumorphicButton$WhenMappings.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeumorphicButton.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeumorphicCardView$WhenMappings.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeumorphicCardView.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeumorphicView$WhenMappings.class
create mode 100644 library-views/build/tmp/kotlin-classes/debug/me/nikhilchaudhari/library/views/NeumorphicView.class
create mode 100644 library/src/main/java/me/nikhilchaudhari/library/components/NeuComponents.kt
diff --git a/app/build.gradle b/app/build.gradle
index a067b274..955c2196 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,18 +1,19 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
+ id 'org.jetbrains.kotlin.plugin.compose' version '2.0.21'
}
android {
namespace 'me.nikhilchaudhari.neumorphiccompose'
- compileSdk 34
+ compileSdk 35
defaultConfig {
applicationId "me.nikhilchaudhari.neumorphiccompose"
- minSdk 21
- targetSdk 34
- versionCode 2
- versionName "2.0"
+ minSdk 24
+ targetSdk 35
+ versionCode 3
+ versionName "3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -36,27 +37,57 @@ android {
buildFeatures {
compose true
}
- composeOptions {
- kotlinCompilerExtensionVersion compose_compiler_version
- }
}
dependencies {
-
- implementation 'androidx.core:core-ktx:1.12.0'
- implementation 'androidx.appcompat:appcompat:1.6.1'
- implementation 'com.google.android.material:material:1.11.0'
- implementation "androidx.compose.ui:ui:$compose_version"
- implementation "androidx.compose.material:material:$compose_version"
+ // Compose BOM for version management
+ implementation platform("androidx.compose:compose-bom:$compose_bom_version")
+
+ implementation 'androidx.core:core-ktx:1.15.0'
+ implementation 'androidx.appcompat:appcompat:1.7.0'
+ implementation 'com.google.android.material:material:1.12.0'
+
+ // Compose UI
+ implementation "androidx.compose.ui:ui"
+ implementation "androidx.compose.ui:ui-graphics"
+ implementation "androidx.compose.ui:ui-tooling-preview"
+ implementation "androidx.compose.foundation:foundation"
+
+ // Material Design 3 - Expressive with latest features
implementation "androidx.compose.material3:material3:$material3_version"
- implementation "androidx.compose.ui:ui-tooling:$compose_version"
- implementation "androidx.compose.animation:animation:$compose_version"
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
- implementation 'androidx.activity:activity-compose:1.8.2'
+ implementation "androidx.compose.material3.adaptive:adaptive:$material3_adaptive_version"
+ implementation "androidx.compose.material3.adaptive:adaptive-layout:$material3_adaptive_version"
+ implementation "androidx.compose.material3.adaptive:adaptive-navigation:$material3_adaptive_version"
+
+ // Animation - including spring physics and shared element
+ implementation "androidx.compose.animation:animation"
+ implementation "androidx.compose.animation:animation-core"
+ implementation "androidx.compose.animation:animation-graphics"
+
+ // Material icons extended
+ implementation "androidx.compose.material:material-icons-extended"
+
+ // Lifecycle
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
+ implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.8.7'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7'
+
+ // Activity Compose
+ implementation 'androidx.activity:activity-compose:1.9.3'
+
+ // Navigation
+ implementation 'androidx.navigation:navigation-compose:2.8.5'
+
+ // Library module
implementation project(path: ':library')
-// implementation "me.nikhilchaudhari:composeNeumorphism:1.0.0-alpha"
+
+ // Testing
testImplementation 'junit:junit:4.13.2'
- androidTestImplementation 'androidx.test.ext:junit:1.1.5'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
- androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
+ androidTestImplementation 'androidx.test.ext:junit:1.2.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
+ androidTestImplementation "androidx.compose.ui:ui-test-junit4"
+
+ // Debug tooling
+ debugImplementation "androidx.compose.ui:ui-tooling"
+ debugImplementation "androidx.compose.ui:ui-test-manifest"
}
\ No newline at end of file
diff --git a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/MainActivity.kt b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/MainActivity.kt
index 2c2a21a6..bbb17b55 100644
--- a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/MainActivity.kt
+++ b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/MainActivity.kt
@@ -1,47 +1,96 @@
package me.nikhilchaudhari.neumorphiccompose
-
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.Image
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Notifications
+import androidx.compose.material.icons.filled.Person
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material.icons.outlined.FavoriteBorder
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableFloatStateOf
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.Shape
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import me.nikhilchaudhari.library.NeuInsets
+import me.nikhilchaudhari.library.NeuTheme
+import me.nikhilchaudhari.library.components.NeuButton
+import me.nikhilchaudhari.library.components.NeuCard
+import me.nikhilchaudhari.library.components.NeuChip
+import me.nikhilchaudhari.library.components.NeuIconButton
+import me.nikhilchaudhari.library.components.NeuSwitch
+import me.nikhilchaudhari.library.components.NeuTextField
+import me.nikhilchaudhari.library.expressiveNeumorphic
+import me.nikhilchaudhari.library.expressiveNeumorphicClickable
import me.nikhilchaudhari.library.neumorphic
-import me.nikhilchaudhari.library.shapes.NeuShape
import me.nikhilchaudhari.library.shapes.Pot
import me.nikhilchaudhari.library.shapes.Pressed
import me.nikhilchaudhari.library.shapes.Punched
+import me.nikhilchaudhari.library.softNeumorphic
import me.nikhilchaudhari.neumorphiccompose.ui.theme.NeumorphismComposeTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- //TODO: Fix the colors acc to themes
+ enableEdgeToEdge()
+
setContent {
NeumorphismComposeTheme {
- // A surface container using the 'background' color from the theme
- Surface(color = Color(236, 234, 235)) {
- Greeting("Android")
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.surface
+ ) {
+ MainContent()
}
}
}
@@ -49,247 +98,670 @@ class MainActivity : ComponentActivity() {
}
@Composable
-fun Greeting(name: String) {
- Column(modifier = Modifier.fillMaxSize()) {
- PunchedCards()
- PunchedButtons()
-// PotCard()
- PressedRow()
- SimpleDesignCard()
+fun MainContent() {
+ val neuColorScheme = if (isSystemInDarkTheme()) {
+ NeuTheme.DarkColorScheme
+ } else {
+ NeuTheme.LightColorScheme
+ }
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(neuColorScheme.backgroundColor),
+ contentPadding = PaddingValues(16.dp),
+ verticalArrangement = Arrangement.spacedBy(24.dp)
+ ) {
+ // Header Section
+ item {
+ HeaderSection(colorScheme = neuColorScheme)
+ }
+
+ // Search Bar
+ item {
+ SearchBarSection(colorScheme = neuColorScheme)
+ }
+
+ // Quick Actions
+ item {
+ QuickActionsSection(colorScheme = neuColorScheme)
+ }
+
+ // Main Cards Section
+ item {
+ MainCardsSection(colorScheme = neuColorScheme)
+ }
+
+ // Interactive Controls
+ item {
+ InteractiveControlsSection(colorScheme = neuColorScheme)
+ }
+
+ // Chips Section
+ item {
+ ChipsSection(colorScheme = neuColorScheme)
+ }
+
+ // Bottom Navigation Preview
+ item {
+ BottomNavigationSection(colorScheme = neuColorScheme)
+ }
+
+ // Spacing at the bottom
+ item {
+ Spacer(modifier = Modifier.height(32.dp))
+ }
+ }
+}
+@Composable
+private fun HeaderSection(colorScheme: NeuTheme.NeuColorScheme) {
+ var notificationEnabled by remember { mutableStateOf(false) }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 32.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+ Text(
+ text = "Welcome Back",
+ style = MaterialTheme.typography.titleMedium,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.7f)
+ )
+ Text(
+ text = "Neumorphic UI",
+ style = MaterialTheme.typography.headlineMedium,
+ fontWeight = FontWeight.Bold,
+ color = colorScheme.onBackgroundColor
+ )
+ }
+
+ NeuIconButton(
+ onClick = { notificationEnabled = !notificationEnabled },
+ colorScheme = colorScheme,
+ size = 52.dp
+ ) {
+ Icon(
+ imageVector = if (notificationEnabled) Icons.Filled.Notifications else Icons.Filled.Notifications,
+ contentDescription = "Notifications",
+ tint = if (notificationEnabled) colorScheme.accentColor else colorScheme.onBackgroundColor
+ )
+ }
}
}
+@Composable
+private fun SearchBarSection(colorScheme: NeuTheme.NeuColorScheme) {
+ var searchText by remember { mutableStateOf("") }
+
+ NeuTextField(
+ value = searchText,
+ onValueChange = { searchText = it },
+ placeholder = "Search anything...",
+ colorScheme = colorScheme,
+ modifier = Modifier.fillMaxWidth(),
+ leadingIcon = {
+ Icon(
+ imageVector = Icons.Default.Search,
+ contentDescription = "Search",
+ tint = colorScheme.onBackgroundColor.copy(alpha = 0.6f)
+ )
+ }
+ )
+}
@Composable
-fun SimpleDesignCard() {
- Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
- Card(
- backgroundColor = Color(236, 234, 235),
- elevation = 0.dp,
- shape = RoundedCornerShape(6.dp),
- modifier = Modifier
- .padding(16.dp)
- .size(400.dp, 350.dp)
- .neumorphic(
- neuShape = Pot.Rounded(6.dp),
- strokeWidth = 7.dp,
-// neuInsets = NeuInsets(8.dp, 8.dp)
- )
+private fun QuickActionsSection(colorScheme: NeuTheme.NeuColorScheme) {
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ Text(
+ text = "Quick Actions",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
+ )
+
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly
) {
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Top,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
+ QuickActionItem(
+ icon = Icons.Default.Home,
+ label = "Home",
+ colorScheme = colorScheme
+ )
+ QuickActionItem(
+ icon = Icons.Default.Person,
+ label = "Profile",
+ colorScheme = colorScheme
+ )
+ QuickActionItem(
+ icon = Icons.Default.Settings,
+ label = "Settings",
+ colorScheme = colorScheme
+ )
+ QuickActionItem(
+ icon = Icons.Default.Favorite,
+ label = "Favorites",
+ colorScheme = colorScheme
+ )
+ }
+ }
+}
- SimpleOvalCard(
- resource = R.drawable.ic_baseline_emoji_emotions_24,
- size = 64.dp
+@Composable
+private fun QuickActionItem(
+ icon: ImageVector,
+ label: String,
+ colorScheme: NeuTheme.NeuColorScheme
+) {
+ var isPressed by remember { mutableStateOf(false) }
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(64.dp)
+ .clip(RoundedCornerShape(16.dp))
+ .expressiveNeumorphicClickable(
+ onClick = { isPressed = !isPressed },
+ colorScheme = colorScheme,
+ neuShape = Punched.Rounded(16.dp),
+ elevation = 8.dp
)
+ .background(colorScheme.backgroundColor, RoundedCornerShape(16.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = label,
+ tint = if (isPressed) colorScheme.accentColor else colorScheme.onBackgroundColor,
+ modifier = Modifier.size(28.dp)
+ )
+ }
+
+ Text(
+ text = label,
+ style = MaterialTheme.typography.labelMedium,
+ color = colorScheme.onBackgroundColor
+ )
+ }
+}
+@Composable
+private fun MainCardsSection(colorScheme: NeuTheme.NeuColorScheme) {
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Text(
+ text = "Featured",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
+ )
+
+ // Main Featured Card with Pot shape
+ NeuCard(
+ colorScheme = colorScheme,
+ neuShape = Pot.Rounded(20.dp),
+ modifier = Modifier.fillMaxWidth(),
+ contentPadding = PaddingValues(20.dp)
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Row(
- modifier = Modifier
- .size(300.dp, 150.dp)
- .clip(RoundedCornerShape(12.dp))
- .neumorphic(neuShape = Pressed.Rounded(12.dp))
- .padding(12.dp),
- horizontalArrangement = Arrangement.Center,
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
- Text(
- text = stringResource(id = R.string.lorem),
- textAlign = TextAlign.Center, modifier = Modifier.padding(4.dp)
- )
+ Column {
+ Text(
+ text = "Material 3 Expressive",
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold,
+ color = colorScheme.onBackgroundColor
+ )
+ Text(
+ text = "Spring physics animations",
+ style = MaterialTheme.typography.bodyMedium,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.7f)
+ )
+ }
+
+ Box(
+ modifier = Modifier
+ .size(48.dp)
+ .clip(CircleShape)
+ .neumorphic(
+ neuShape = Punched.Oval(),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ elevation = 6.dp
+ )
+ .background(colorScheme.accentColor.copy(alpha = 0.2f), CircleShape),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Check",
+ tint = colorScheme.accentColor
+ )
+ }
}
-
- Row(
+
+ // Pressed inner section
+ Box(
modifier = Modifier
.fillMaxWidth()
- .padding(8.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
+ .height(100.dp)
+ .clip(RoundedCornerShape(16.dp))
+ .neumorphic(
+ neuShape = Pressed.Rounded(16.dp),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ strokeWidth = 6.dp
+ )
+ .background(colorScheme.backgroundColor, RoundedCornerShape(16.dp))
+ .padding(16.dp),
+ contentAlignment = Alignment.Center
) {
- PunchedButton(
-
- btnShape = CircleShape,
- text = "Always",
- neuInsets = NeuInsets(6.dp),
- neuShape = Punched.Oval()
+ Text(
+ text = "This card showcases the Pot shape with an inner Pressed section, demonstrating the depth variations possible with neumorphic design.",
+ style = MaterialTheme.typography.bodySmall,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.8f),
+ textAlign = TextAlign.Center
)
- PunchedButton(
-
- btnShape = CircleShape,
- text = "Never",
- neuInsets = NeuInsets(6.dp),
- neuShape = Punched.Oval()
+ }
+ }
+ }
+
+ // Two smaller cards in a row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ NeuCard(
+ colorScheme = colorScheme,
+ modifier = Modifier.weight(1f),
+ elevation = 8.dp
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ text = "Punched",
+ style = MaterialTheme.typography.titleSmall,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
+ )
+ Text(
+ text = "Raised effect",
+ style = MaterialTheme.typography.bodySmall,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.6f)
+ )
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .height(80.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .neumorphic(
+ neuShape = Pressed.Rounded(20.dp),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ strokeWidth = 5.dp
+ )
+ .background(colorScheme.backgroundColor, RoundedCornerShape(20.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "Pressed",
+ style = MaterialTheme.typography.titleSmall,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
+ )
+ Text(
+ text = "Sunken effect",
+ style = MaterialTheme.typography.bodySmall,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.6f)
)
}
-
}
}
}
}
@Composable
-fun PressedRow() {
- Row(
- modifier = Modifier
- .padding(18.dp)
- .size(400.dp, 100.dp)
- .background(Color(236, 234, 235))
- .clip(RoundedCornerShape(12.dp))
- .neumorphic(neuShape = Pressed.Rounded(12.dp)),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Image(
- painterResource(id = R.drawable.ic_baseline_shopping_cart_24),
- contentDescription = "image", modifier = Modifier
- .size(80.dp)
- .padding(12.dp)
- )
+private fun InteractiveControlsSection(colorScheme: NeuTheme.NeuColorScheme) {
+ var switchState by remember { mutableStateOf(false) }
+ var buttonCount by remember { mutableIntStateOf(0) }
+ var sliderValue by remember { mutableFloatStateOf(0.5f) }
+
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(
- text = "Simple Row wih Pressed shape.",
- textAlign = TextAlign.Center
+ text = "Interactive Controls",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
)
- }
-}
-
-
-@Composable
-fun PotCard() {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Card(
- backgroundColor = Color(236, 234, 235),
- elevation = 0.dp,
- shape = RoundedCornerShape(12.dp),
- modifier = Modifier
- .padding(16.dp)
- .size(300.dp, 100.dp)
- .neumorphic(
- neuShape = Pot.Rounded(12.dp),
- elevation = 12.dp,
- strokeWidth = 8.dp,
- neuInsets = NeuInsets(10.dp, 10.dp)
- )
+
+ NeuCard(
+ colorScheme = colorScheme,
+ modifier = Modifier.fillMaxWidth()
) {
- Row(
- modifier = Modifier.fillMaxSize(),
- verticalAlignment = Alignment.CenterVertically,
+ Column(
+ verticalArrangement = Arrangement.spacedBy(24.dp),
+ modifier = Modifier.padding(8.dp)
) {
- Image(
- painterResource(id = R.drawable.ic_baseline_shopping_cart_24),
- contentDescription = "image", modifier = Modifier
- .size(80.dp)
- .padding(12.dp)
- )
- Text(
- text = "Sample card wih Pot/Puddle/Basin shape.",
- textAlign = TextAlign.Center
- )
+ // Switch Row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+ Text(
+ text = "Dark Mode",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Medium,
+ color = colorScheme.onBackgroundColor
+ )
+ Text(
+ text = "Spring animated toggle",
+ style = MaterialTheme.typography.bodySmall,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.6f)
+ )
+ }
+
+ NeuSwitch(
+ checked = switchState,
+ onCheckedChange = { switchState = it },
+ colorScheme = colorScheme
+ )
+ }
+
+ // Button Row
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column {
+ Text(
+ text = "Counter: $buttonCount",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Medium,
+ color = colorScheme.onBackgroundColor
+ )
+ Text(
+ text = "Tap to increment",
+ style = MaterialTheme.typography.bodySmall,
+ color = colorScheme.onBackgroundColor.copy(alpha = 0.6f)
+ )
+ }
+
+ NeuButton(
+ onClick = { buttonCount++ },
+ colorScheme = colorScheme,
+ contentPadding = PaddingValues(horizontal = 20.dp, vertical = 10.dp)
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ imageVector = Icons.Default.Add,
+ contentDescription = "Add",
+ modifier = Modifier.size(18.dp)
+ )
+ Text("Add")
+ }
+ }
+ }
+
+ // Slider-like progress
+ Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Text(
+ text = "Progress",
+ style = MaterialTheme.typography.bodyLarge,
+ fontWeight = FontWeight.Medium,
+ color = colorScheme.onBackgroundColor
+ )
+ Text(
+ text = "${(sliderValue * 100).toInt()}%",
+ style = MaterialTheme.typography.bodyMedium,
+ color = colorScheme.accentColor
+ )
+ }
+
+ // Progress track
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(12.dp)
+ .clip(RoundedCornerShape(6.dp))
+ .neumorphic(
+ neuShape = Pressed.Rounded(6.dp),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ strokeWidth = 3.dp
+ )
+ .background(colorScheme.backgroundColor, RoundedCornerShape(6.dp))
+ ) {
+ val animatedProgress by animateFloatAsState(
+ targetValue = sliderValue,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessLow
+ ),
+ label = "progressAnimation"
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth(animatedProgress)
+ .height(12.dp)
+ .clip(RoundedCornerShape(6.dp))
+ .background(colorScheme.accentColor.copy(alpha = 0.7f), RoundedCornerShape(6.dp))
+ )
+ }
+
+ // Increase/Decrease buttons for slider
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceEvenly
+ ) {
+ NeuButton(
+ onClick = { sliderValue = (sliderValue - 0.1f).coerceIn(0f, 1f) },
+ colorScheme = colorScheme,
+ elevation = 6.dp,
+ contentPadding = PaddingValues(horizontal = 24.dp, vertical = 8.dp)
+ ) {
+ Text("-10%")
+ }
+
+ NeuButton(
+ onClick = { sliderValue = (sliderValue + 0.1f).coerceIn(0f, 1f) },
+ colorScheme = colorScheme,
+ elevation = 6.dp,
+ contentPadding = PaddingValues(horizontal = 24.dp, vertical = 8.dp)
+ ) {
+ Text("+10%")
+ }
+ }
+ }
}
}
}
}
@Composable
-fun PunchedButtons() {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(8.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- for (i in 1..2) {
- PunchedButton(
- btnShape = RoundedCornerShape(6.dp),
- text = "Button $i",
- neuShape = Punched.Rounded(6.dp),
- )
+private fun ChipsSection(colorScheme: NeuTheme.NeuColorScheme) {
+ var selectedChip by remember { mutableIntStateOf(0) }
+ val chips = listOf("All", "Design", "Code", "Music", "Art", "Photo")
+
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ Text(
+ text = "Categories",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
+ )
+
+ LazyRow(
+ horizontalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ items(chips.size) { index ->
+ NeuChip(
+ onClick = { selectedChip = index },
+ selected = selectedChip == index,
+ colorScheme = colorScheme,
+ leadingIcon = if (selectedChip == index) {
+ {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = "Selected",
+ modifier = Modifier.size(16.dp)
+ )
+ }
+ } else null
+ ) {
+ Text(chips[index])
+ }
+ }
}
}
}
-
@Composable
-fun PunchedCards() {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(8.dp),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
-
- val resourceArray = arrayOf(
- R.drawable.ic_baseline_android_24,
- R.drawable.ic_baseline_warning_24,
- R.drawable.ic_baseline_construction_24
+private fun BottomNavigationSection(colorScheme: NeuTheme.NeuColorScheme) {
+ var selectedNav by remember { mutableIntStateOf(0) }
+ val navItems = listOf(
+ Icons.Default.Home to "Home",
+ Icons.Default.Search to "Search",
+ Icons.Default.Add to "Add",
+ Icons.Outlined.FavoriteBorder to "Likes",
+ Icons.Default.Person to "Profile"
+ )
+
+ Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
+ Text(
+ text = "Navigation Bar",
+ style = MaterialTheme.typography.titleMedium,
+ fontWeight = FontWeight.SemiBold,
+ color = colorScheme.onBackgroundColor
)
-
- for (i in 0..2) {
- SimpleOvalCard(resource = resourceArray[i])
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(24.dp))
+ .neumorphic(
+ neuShape = Punched.Rounded(24.dp),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ elevation = 10.dp
+ )
+ .background(colorScheme.backgroundColor, RoundedCornerShape(24.dp))
+ .padding(8.dp)
+ ) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceAround,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ navItems.forEachIndexed { index, (icon, label) ->
+ val isSelected = selectedNav == index
+ val scale by animateFloatAsState(
+ targetValue = if (isSelected) 1.1f else 1f,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessMedium
+ ),
+ label = "navScale"
+ )
+
+ Box(
+ modifier = Modifier
+ .size(52.dp)
+ .scale(scale)
+ .clip(RoundedCornerShape(16.dp))
+ .then(
+ if (isSelected) {
+ Modifier.neumorphic(
+ neuShape = Pressed.Rounded(16.dp),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor,
+ strokeWidth = 4.dp
+ )
+ } else {
+ Modifier.softNeumorphic(
+ neuShape = Punched.Rounded(16.dp),
+ lightShadowColor = colorScheme.lightShadowColor,
+ darkShadowColor = colorScheme.darkShadowColor
+ )
+ }
+ )
+ .background(
+ if (isSelected) colorScheme.accentColor.copy(alpha = 0.15f)
+ else colorScheme.backgroundColor,
+ RoundedCornerShape(16.dp)
+ )
+ .expressiveNeumorphicClickable(
+ onClick = { selectedNav = index },
+ colorScheme = colorScheme,
+ elevation = 0.dp
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = label,
+ tint = if (isSelected) colorScheme.accentColor else colorScheme.onBackgroundColor.copy(alpha = 0.6f),
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ }
+ }
}
}
}
+@Preview(showBackground = true, showSystemUi = true)
@Composable
-fun PunchedButton(
- btnShape: Shape, text: String, neuShape: NeuShape,
- neuInsets: NeuInsets = NeuInsets(4.dp, 4.dp)
-) {
- Button(
- onClick = { /*TODO*/ },
- contentPadding = PaddingValues(16.dp),
- colors = ButtonDefaults.buttonColors(backgroundColor = Color(236, 234, 235)),
- elevation = ButtonDefaults.elevation(0.dp),
- shape = btnShape,
- modifier = Modifier
- .padding(12.dp)
- .neumorphic(
- neuShape = neuShape,
- neuInsets = neuInsets
- )
- ) {
- Text(text = text)
- }
-}
-
-
-@Composable
-fun SimpleOvalCard(resource: Int, size: Dp = 32.dp) {
- Card(
- backgroundColor = Color(236, 234, 235),
- elevation = 0.dp,
- border = BorderStroke(2.dp, Color(236, 234, 235)),
- shape = CircleShape, modifier = Modifier
- .size(100.dp)
- .padding(16.dp)
- .neumorphic(neuShape = Punched.Oval())
- ) {
- Row(
+fun MainContentPreview() {
+ NeumorphismComposeTheme {
+ Surface(
modifier = Modifier.fillMaxSize(),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
+ color = MaterialTheme.colorScheme.surface
) {
- Image(
- painterResource(id = resource),
- contentDescription = "image", modifier = Modifier.size(size)
- )
+ MainContent()
}
}
}
-@Preview(showBackground = true)
+@Preview(showBackground = true, showSystemUi = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES)
@Composable
-fun DefaultPreview() {
+fun MainContentDarkPreview() {
NeumorphismComposeTheme {
- Greeting("Android")
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.surface
+ ) {
+ MainContent()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Color.kt b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Color.kt
index fe14dd40..08dedbf5 100644
--- a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Color.kt
+++ b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Color.kt
@@ -2,6 +2,34 @@ package me.nikhilchaudhari.neumorphiccompose.ui.theme
import androidx.compose.ui.graphics.Color
+// Material 3 Expressive - Primary
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6750A4)
+val PurpleGrey40 = Color(0xFF625B71)
+val Pink40 = Color(0xFF7D5260)
+
+// Neumorphic specific colors - Light
+val NeuBackgroundLight = Color(0xFFECEAEB)
+val NeuLightShadowLight = Color(0xFFFFFFFF)
+val NeuDarkShadowLight = Color(0xFFC8C8C8)
+
+// Neumorphic specific colors - Dark
+val NeuBackgroundDark = Color(0xFF2D2D2D)
+val NeuLightShadowDark = Color(0xFF3D3D3D)
+val NeuDarkShadowDark = Color(0xFF1D1D1D)
+
+// Additional expressive colors
+val Lavender = Color(0xFFCBA6F7)
+val Mauve = Color(0xFF8839EF)
+val Peach = Color(0xFFFAB387)
+val Teal = Color(0xFF94E2D5)
+val Sky = Color(0xFF89DCEB)
+val Sapphire = Color(0xFF74C7EC)
+
+// Legacy colors for backward compatibility
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
diff --git a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Shape.kt b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Shape.kt
index 2226c70d..07b3c5be 100644
--- a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Shape.kt
+++ b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Shape.kt
@@ -1,11 +1,14 @@
package me.nikhilchaudhari.neumorphiccompose.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Shapes
+import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
+// Material 3 Expressive Shapes - larger corner radii for more expressive look
val Shapes = Shapes(
- small = RoundedCornerShape(4.dp),
- medium = RoundedCornerShape(4.dp),
- large = RoundedCornerShape(0.dp)
+ extraSmall = RoundedCornerShape(4.dp),
+ small = RoundedCornerShape(8.dp),
+ medium = RoundedCornerShape(16.dp),
+ large = RoundedCornerShape(24.dp),
+ extraLarge = RoundedCornerShape(32.dp)
)
\ No newline at end of file
diff --git a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Theme.kt b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Theme.kt
index d12fc43d..28daf44e 100644
--- a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Theme.kt
+++ b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Theme.kt
@@ -1,45 +1,163 @@
package me.nikhilchaudhari.neumorphiccompose.ui.theme
+import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
-private val DarkColorPalette = darkColors(
- primary = Purple200,
- primaryVariant = Purple700,
- secondary = Teal200
-)
+// Material 3 Expressive Colors - Light
+private val md_theme_light_primary = Color(0xFF6750A4)
+private val md_theme_light_onPrimary = Color(0xFFFFFFFF)
+private val md_theme_light_primaryContainer = Color(0xFFEADDFF)
+private val md_theme_light_onPrimaryContainer = Color(0xFF21005D)
+private val md_theme_light_secondary = Color(0xFF625B71)
+private val md_theme_light_onSecondary = Color(0xFFFFFFFF)
+private val md_theme_light_secondaryContainer = Color(0xFFE8DEF8)
+private val md_theme_light_onSecondaryContainer = Color(0xFF1D192B)
+private val md_theme_light_tertiary = Color(0xFF7D5260)
+private val md_theme_light_onTertiary = Color(0xFFFFFFFF)
+private val md_theme_light_tertiaryContainer = Color(0xFFFFD8E4)
+private val md_theme_light_onTertiaryContainer = Color(0xFF31111D)
+private val md_theme_light_error = Color(0xFFB3261E)
+private val md_theme_light_errorContainer = Color(0xFFF9DEDC)
+private val md_theme_light_onError = Color(0xFFFFFFFF)
+private val md_theme_light_onErrorContainer = Color(0xFF410E0B)
+private val md_theme_light_background = Color(0xFFFFFBFE)
+private val md_theme_light_onBackground = Color(0xFF1C1B1F)
+private val md_theme_light_surface = Color(0xFFECEAEB) // Neumorphic surface
+private val md_theme_light_onSurface = Color(0xFF1C1B1F)
+private val md_theme_light_surfaceVariant = Color(0xFFE7E0EC)
+private val md_theme_light_onSurfaceVariant = Color(0xFF49454F)
+private val md_theme_light_outline = Color(0xFF79747E)
+private val md_theme_light_inverseOnSurface = Color(0xFFF4EFF4)
+private val md_theme_light_inverseSurface = Color(0xFF313033)
+private val md_theme_light_inversePrimary = Color(0xFFD0BCFF)
+private val md_theme_light_surfaceTint = Color(0xFF6750A4)
+private val md_theme_light_outlineVariant = Color(0xFFCAC4D0)
+private val md_theme_light_scrim = Color(0xFF000000)
+
+// Material 3 Expressive Colors - Dark
+private val md_theme_dark_primary = Color(0xFFD0BCFF)
+private val md_theme_dark_onPrimary = Color(0xFF381E72)
+private val md_theme_dark_primaryContainer = Color(0xFF4F378B)
+private val md_theme_dark_onPrimaryContainer = Color(0xFFEADDFF)
+private val md_theme_dark_secondary = Color(0xFFCCC2DC)
+private val md_theme_dark_onSecondary = Color(0xFF332D41)
+private val md_theme_dark_secondaryContainer = Color(0xFF4A4458)
+private val md_theme_dark_onSecondaryContainer = Color(0xFFE8DEF8)
+private val md_theme_dark_tertiary = Color(0xFFEFB8C8)
+private val md_theme_dark_onTertiary = Color(0xFF492532)
+private val md_theme_dark_tertiaryContainer = Color(0xFF633B48)
+private val md_theme_dark_onTertiaryContainer = Color(0xFFFFD8E4)
+private val md_theme_dark_error = Color(0xFFF2B8B5)
+private val md_theme_dark_errorContainer = Color(0xFF8C1D18)
+private val md_theme_dark_onError = Color(0xFF601410)
+private val md_theme_dark_onErrorContainer = Color(0xFFF9DEDC)
+private val md_theme_dark_background = Color(0xFF1C1B1F)
+private val md_theme_dark_onBackground = Color(0xFFE6E1E5)
+private val md_theme_dark_surface = Color(0xFF2D2D2D) // Neumorphic surface
+private val md_theme_dark_onSurface = Color(0xFFE6E1E5)
+private val md_theme_dark_surfaceVariant = Color(0xFF49454F)
+private val md_theme_dark_onSurfaceVariant = Color(0xFFCAC4D0)
+private val md_theme_dark_outline = Color(0xFF938F99)
+private val md_theme_dark_inverseOnSurface = Color(0xFF1C1B1F)
+private val md_theme_dark_inverseSurface = Color(0xFFE6E1E5)
+private val md_theme_dark_inversePrimary = Color(0xFF6750A4)
+private val md_theme_dark_surfaceTint = Color(0xFFD0BCFF)
+private val md_theme_dark_outlineVariant = Color(0xFF49454F)
+private val md_theme_dark_scrim = Color(0xFF000000)
-private val LightColorPalette = lightColors(
- primary = Purple500,
- primaryVariant = Purple700,
- secondary = Teal200
+private val LightColorScheme = lightColorScheme(
+ primary = md_theme_light_primary,
+ onPrimary = md_theme_light_onPrimary,
+ primaryContainer = md_theme_light_primaryContainer,
+ onPrimaryContainer = md_theme_light_onPrimaryContainer,
+ secondary = md_theme_light_secondary,
+ onSecondary = md_theme_light_onSecondary,
+ secondaryContainer = md_theme_light_secondaryContainer,
+ onSecondaryContainer = md_theme_light_onSecondaryContainer,
+ tertiary = md_theme_light_tertiary,
+ onTertiary = md_theme_light_onTertiary,
+ tertiaryContainer = md_theme_light_tertiaryContainer,
+ onTertiaryContainer = md_theme_light_onTertiaryContainer,
+ error = md_theme_light_error,
+ errorContainer = md_theme_light_errorContainer,
+ onError = md_theme_light_onError,
+ onErrorContainer = md_theme_light_onErrorContainer,
+ background = md_theme_light_background,
+ onBackground = md_theme_light_onBackground,
+ surface = md_theme_light_surface,
+ onSurface = md_theme_light_onSurface,
+ surfaceVariant = md_theme_light_surfaceVariant,
+ onSurfaceVariant = md_theme_light_onSurfaceVariant,
+ outline = md_theme_light_outline,
+ inverseOnSurface = md_theme_light_inverseOnSurface,
+ inverseSurface = md_theme_light_inverseSurface,
+ inversePrimary = md_theme_light_inversePrimary,
+ surfaceTint = md_theme_light_surfaceTint,
+ outlineVariant = md_theme_light_outlineVariant,
+ scrim = md_theme_light_scrim
+)
- /* Other default colors to override
- background = Color.White,
- surface = Color.White,
- onPrimary = Color.White,
- onSecondary = Color.Black,
- onBackground = Color.Black,
- onSurface = Color.Black,
- */
+private val DarkColorScheme = darkColorScheme(
+ primary = md_theme_dark_primary,
+ onPrimary = md_theme_dark_onPrimary,
+ primaryContainer = md_theme_dark_primaryContainer,
+ onPrimaryContainer = md_theme_dark_onPrimaryContainer,
+ secondary = md_theme_dark_secondary,
+ onSecondary = md_theme_dark_onSecondary,
+ secondaryContainer = md_theme_dark_secondaryContainer,
+ onSecondaryContainer = md_theme_dark_onSecondaryContainer,
+ tertiary = md_theme_dark_tertiary,
+ onTertiary = md_theme_dark_onTertiary,
+ tertiaryContainer = md_theme_dark_tertiaryContainer,
+ onTertiaryContainer = md_theme_dark_onTertiaryContainer,
+ error = md_theme_dark_error,
+ errorContainer = md_theme_dark_errorContainer,
+ onError = md_theme_dark_onError,
+ onErrorContainer = md_theme_dark_onErrorContainer,
+ background = md_theme_dark_background,
+ onBackground = md_theme_dark_onBackground,
+ surface = md_theme_dark_surface,
+ onSurface = md_theme_dark_onSurface,
+ surfaceVariant = md_theme_dark_surfaceVariant,
+ onSurfaceVariant = md_theme_dark_onSurfaceVariant,
+ outline = md_theme_dark_outline,
+ inverseOnSurface = md_theme_dark_inverseOnSurface,
+ inverseSurface = md_theme_dark_inverseSurface,
+ inversePrimary = md_theme_dark_inversePrimary,
+ surfaceTint = md_theme_dark_surfaceTint,
+ outlineVariant = md_theme_dark_outlineVariant,
+ scrim = md_theme_dark_scrim
)
+/**
+ * Material 3 Expressive Theme for Neumorphism Compose
+ * Supports dynamic colors on Android 12+ and provides expressive animations
+ */
@Composable
fun NeumorphismComposeTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
- content: @Composable() () -> Unit
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
) {
- val colors = if (darkTheme) {
- DarkColorPalette
- } else {
- LightColorPalette
+ val colorScheme = when {
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
}
MaterialTheme(
- colors = colors,
+ colorScheme = colorScheme,
typography = Typography,
shapes = Shapes,
content = content
diff --git a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Type.kt b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Type.kt
index ac8a5544..55530f72 100644
--- a/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Type.kt
+++ b/app/src/main/java/me/nikhilchaudhari/neumorphiccompose/ui/theme/Type.kt
@@ -1,28 +1,116 @@
package me.nikhilchaudhari.neumorphiccompose.ui.theme
-import androidx.compose.material.Typography
+import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
-// Set of Material typography styles to start with
+// Material 3 Expressive Typography
val Typography = Typography(
- body1 = TextStyle(
+ displayLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
- fontSize = 16.sp
- )
- /* Other default text styles to override
- button = TextStyle(
+ fontSize = 57.sp,
+ lineHeight = 64.sp,
+ letterSpacing = (-0.25).sp
+ ),
+ displayMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 45.sp,
+ lineHeight = 52.sp,
+ letterSpacing = 0.sp
+ ),
+ displaySmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 36.sp,
+ lineHeight = 44.sp,
+ letterSpacing = 0.sp
+ ),
+ headlineLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 32.sp,
+ lineHeight = 40.sp,
+ letterSpacing = 0.sp
+ ),
+ headlineMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 28.sp,
+ lineHeight = 36.sp,
+ letterSpacing = 0.sp
+ ),
+ headlineSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 24.sp,
+ lineHeight = 32.sp,
+ letterSpacing = 0.sp
+ ),
+ titleLarge = TextStyle(
fontFamily = FontFamily.Default,
- fontWeight = FontWeight.W500,
- fontSize = 14.sp
+ fontWeight = FontWeight.Normal,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ titleMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.15.sp
+ ),
+ titleSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp
),
- caption = TextStyle(
+ bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
- fontSize = 12.sp
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ ),
+ bodyMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.25.sp
+ ),
+ bodySmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.4.sp
+ ),
+ labelLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 14.sp,
+ lineHeight = 20.sp,
+ letterSpacing = 0.1.sp
+ ),
+ labelMedium = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
+ ),
+ labelSmall = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Medium,
+ fontSize = 11.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.5.sp
)
- */
)
\ No newline at end of file
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 0ef9b46b..e86cfef3 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,6 @@
-
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index e0c95732..ab292583 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
-
-
+
-
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index f5d63acf..0d407e95 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,17 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
- compose_version = '1.5.4'
- kotlin_version = '1.9.20'
- compose_compiler_version = '1.5.4'
- material3_version = '1.3.0'
+ compose_version = '1.7.6'
+ kotlin_version = '2.0.21'
+ compose_compiler_version = '1.5.15'
+ // Material 3 Expressive - latest version with expressive components
+ material3_version = '1.4.0-alpha07'
+ material3_adaptive_version = '1.0.0-rc01'
+ compose_bom_version = '2024.12.01'
}
repositories {
google()
mavenCentral()
}
dependencies {
- classpath "com.android.tools.build:gradle:8.1.4"
+ classpath "com.android.tools.build:gradle:8.7.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f301f877..e247c654 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Tue Jan 07 12:00:00 UTC 2026
distributionBase=GRADLE_USER_HOME
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
diff --git a/library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml b/library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml
new file mode 100644
index 00000000..c0abdf54
--- /dev/null
+++ b/library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json b/library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json
new file mode 100644
index 00000000..4eca20db
--- /dev/null
+++ b/library-views/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json
@@ -0,0 +1,18 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "AAPT_FRIENDLY_MERGED_MANIFESTS",
+ "kind": "Directory"
+ },
+ "applicationId": "me.nikhilchaudhari.library.views",
+ "variantName": "debug",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "outputFile": "AndroidManifest.xml"
+ }
+ ],
+ "elementType": "File"
+}
\ No newline at end of file
diff --git a/library-views/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar b/library-views/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f80511573b9d5911583eee330fd593e6629276ed
GIT binary patch
literal 31239
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
literal 0
HcmV?d00001
diff --git a/library-views/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties b/library-views/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties
new file mode 100644
index 00000000..1211b1ef
--- /dev/null
+++ b/library-views/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties
@@ -0,0 +1,6 @@
+aarFormatVersion=1.0
+aarMetadataVersion=1.0
+minCompileSdk=1
+minCompileSdkExtension=0
+minAndroidGradlePluginVersion=1.0.0
+coreLibraryDesugaringEnabled=false
diff --git a/library-views/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json b/library-views/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json
new file mode 100644
index 00000000..9e26dfee
--- /dev/null
+++ b/library-views/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/library-views/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt b/library-views/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/library-views/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar b/library-views/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b99064a8db3aa9952cc07e357b4d647d55400f88
GIT binary patch
literal 5214
zcmb_gTXPge6h1w>$zHM~n>z^*5(p43vVeF6QCUzTNHD=z=z}%c9kN3ArI_m8Rft
zA-iClz46r_-xx&PUPAA-RdVdDnq9Bd%x1M_dUmO9Z+NEnsI+Ta4;!V`Mbr1avz5Bp
zX#D!$Pd{VKMVeQ1qR){TQi_TX%w4loH=WJW%Ekk$;%6wO=*ZqKGBltl%?uO`n@-hp
z?P`lshO&xsoxTj^6^%Jo^Qu|d+VtF}Q!Tr77vt1&ieF#R2=10$}B!vuZaRiuQGS
z?mpVF!tvKkZ)>e)R^5lK@dX%NuUosOZ@W%tv+Oi1|Ay^cqKbo_O*-o(gs*{$NIuPI^G9WGeQ!}xy;W(EyX2F5wO
zCVuZkg8kpWe}DWBhH^WN0ei>WqFvQOeOA%2zQ(%K2ycPQ*#
z<$!|T?k(7t?bPhb)h6Nz
ziOcwGSKtvP@Ka~7G|S#fNF;MZ{BdJpTQ3
z;@UvfoI_pO#bM>##ax_>iu+uQj0-#gJSp%L@U+1DfoBAs1wJ6~
zLEu9I&jBA6cpezj5b!=C`kD7pftmNBz|8xYz|8x&z|8xEz|4C|VCH>NVCH>FVCH>V
zVCH>BVCD_c0^VmuKl3gL%)HMD%)HMF%)BoM%)DO~n0a3mn0dbjD42q130nG7L
zKDF{a-F(h*A3o=}mFFDyQF~8t7ju%hC;0l)zkaxn4J5ZE817xvYde97K2JJOM#rC%
z^ncWXLbr8T((j%X`n=JcL2fpXo3Y_n2JQxbapzm%9inf)-s4U)B14T
wK3mWo>$}db2fjO{ex&~uNx
z0WZL_@Fu(i<4x4iL(%$T&)75boB#ONA9xf9URB--5*-6u=BEotNhOkq><8n1|ZHf_&3Qi^)?;C@XkJdZhjKM0(iM1)JlGCX6T>%F=
zhny*kA;NK=xI%}>fqR~Gqun>Sfx)un2Hxfd)-}btCU0F$aaZ$$=Bh8INLm^F9qX|t
zyXgUj084_`J+k~S$FP9iIj*>wXT_CTC~1^yk0xhhd9R&WM=a((8trD**U2E$z3DN^{F!>;Wr`>N
Y(qzX$H^|*BVs;*G&F=c?*6w2d0`#XF0RR91
literal 0
HcmV?d00001
diff --git a/library-views/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/library-views/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties
new file mode 100644
index 00000000..045c14a0
--- /dev/null
+++ b/library-views/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties
@@ -0,0 +1 @@
+#Wed Jan 07 13:11:19 UTC 2026
diff --git a/library-views/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml b/library-views/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml
new file mode 100644
index 00000000..86f81825
--- /dev/null
+++ b/library-views/build/intermediates/incremental/debug/packageDebugResources/merged.dir/values/values.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/library-views/build/intermediates/incremental/debug/packageDebugResources/merger.xml
new file mode 100644
index 00000000..31367e53
--- /dev/null
+++ b/library-views/build/intermediates/incremental/debug/packageDebugResources/merger.xml
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/library-views/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml
new file mode 100644
index 00000000..52e5f5ec
--- /dev/null
+++ b/library-views/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/incremental/mergeDebugShaders/merger.xml b/library-views/build/intermediates/incremental/mergeDebugShaders/merger.xml
new file mode 100644
index 00000000..29ac07bf
--- /dev/null
+++ b/library-views/build/intermediates/incremental/mergeDebugShaders/merger.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/incremental/packageDebugAssets/merger.xml b/library-views/build/intermediates/incremental/packageDebugAssets/merger.xml
new file mode 100644
index 00000000..96312bc2
--- /dev/null
+++ b/library-views/build/intermediates/incremental/packageDebugAssets/merger.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/library-views_debug.kotlin_module b/library-views/build/intermediates/java_res/debug/processDebugJavaRes/out/META-INF/library-views_debug.kotlin_module
new file mode 100644
index 0000000000000000000000000000000000000000..c998a4c1db97edbad6d7592a01ece2ba48ee3cc5
GIT binary patch
literal 24
ccmZQzU|?ooU|
+2
+4
+5
+6
+7
diff --git a/library-views/build/intermediates/merged_consumer_proguard_file/debug/mergeDebugConsumerProguardFiles/proguard.txt b/library-views/build/intermediates/merged_consumer_proguard_file/debug/mergeDebugConsumerProguardFiles/proguard.txt
new file mode 100644
index 00000000..ff09c60a
--- /dev/null
+++ b/library-views/build/intermediates/merged_consumer_proguard_file/debug/mergeDebugConsumerProguardFiles/proguard.txt
@@ -0,0 +1,2 @@
+# Consumer ProGuard rules for library-views
+-keep class me.nikhilchaudhari.library.views.** { *; }
diff --git a/library-views/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-library-views.jar b/library-views/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-library-views.jar
new file mode 100644
index 0000000000000000000000000000000000000000..32c200bbd352a1c821e2f8070d69b720aaed1d87
GIT binary patch
literal 205
zcmWIWW@Zs#U|`??Vg`oZuPx`rfwTk=YXPyZYlx$+r=OdCPG(Y3Vo{}TS!QZ^aePW@
zQfazgc791tW?p=5eoARhYH~tCLK+aWB>AzimDVYoU2tT5w2HX|^?LY#=
UTNmKX$_CQN2!s(pIv#8a0D=iIG5`Po
literal 0
HcmV?d00001
diff --git a/library-views/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml b/library-views/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml
new file mode 100644
index 00000000..c0abdf54
--- /dev/null
+++ b/library-views/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt b/library-views/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt
new file mode 100644
index 00000000..08f4ebea
--- /dev/null
+++ b/library-views/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt
@@ -0,0 +1 @@
+0 Warning/Error
\ No newline at end of file
diff --git a/library-views/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml b/library-views/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml
new file mode 100644
index 00000000..86f81825
--- /dev/null
+++ b/library-views/build/intermediates/packaged_res/debug/packageDebugResources/values/values.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/library-views/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt b/library-views/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt
new file mode 100644
index 00000000..6a0bf262
--- /dev/null
+++ b/library-views/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt
@@ -0,0 +1,25 @@
+me.nikhilchaudhari.library.views
+attr neuBackgroundColor
+attr neuCornerRadius
+attr neuCornerType
+attr neuDarkShadowColor
+attr neuElevation
+attr neuInsetHorizontal
+attr neuInsetVertical
+attr neuLightShadowColor
+attr neuLightSource
+attr neuShape
+attr neuStrokeWidth
+id bottomLeft
+id bottomRight
+id oval
+id pot
+id pressed
+id punched
+id rounded
+id topLeft
+id topRight
+styleable NeumorphicButton neuBackgroundColor neuCornerRadius neuCornerType neuDarkShadowColor neuElevation neuInsetHorizontal neuInsetVertical neuLightShadowColor neuLightSource neuShape neuStrokeWidth
+styleable NeumorphicCardView neuBackgroundColor neuCornerRadius neuCornerType neuDarkShadowColor neuElevation neuInsetHorizontal neuInsetVertical neuLightShadowColor neuLightSource neuShape neuStrokeWidth
+styleable NeumorphicImageView neuBackgroundColor neuCornerRadius neuCornerType neuDarkShadowColor neuElevation neuInsetHorizontal neuInsetVertical neuLightShadowColor neuLightSource neuShape neuStrokeWidth
+styleable NeumorphicView neuBackgroundColor neuCornerRadius neuCornerType neuDarkShadowColor neuElevation neuInsetHorizontal neuInsetVertical neuLightShadowColor neuLightSource neuShape neuStrokeWidth
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab
new file mode 100644
index 0000000000000000000000000000000000000000..a15eb5557b4b5f8d6969a4b5446713b5810ae26f
GIT binary patch
literal 4096
zcmbR3vzw0r2$(?x3GFTk7zD
z2#X(V&U$RdJ#FR#6=TP6N?uFIr;~eq2AKbmJ8%ah#qRr93`vV8i}S){f4}ak{BcA&
rH?AkttduSLKsOJi4oAcmxDHeillcQ(G$OiTfBd~ro+pRz!V2UI+}mkp
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..bd56c2e105f7fba02a033c4ca6be70e36997b2f3
GIT binary patch
literal 32768
zcmeI(F%5t)5Qfo9>V^q5SilBj2ZlPp1_=sqf`(4qhJ}*6oBr|3+b>F#a0CbtAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXD71*-tx{rC#JXr(?5FkK+009C72oNAZfB=DL
zY4A+Tj)DLI0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5;&PoU;J`}(`T
Fpchh(2rmEt
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab
new file mode 100644
index 0000000000000000000000000000000000000000..3dca6bd0727f908fca644b98455df3caef85bb5c
GIT binary patch
literal 4096
zcmbR3vzw0r2v|V`3V!Z
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream
new file mode 100644
index 0000000000000000000000000000000000000000..c2d9f94862e32756807c2e26a40886a2bd071678
GIT binary patch
literal 4096
zcmeH^!3x4K5JcY(SrPxh-n}T^wJliGwnVG=_l}F8-s8zhAiLSX?9K}?<3vM2N6Ue|
z&LfrLmXSJQQms(6Wk*syao7E-d&7;h@};s-I=$Y8(5P6ba?$Rk4D%m#DPx|R&1)K%
zWb3pF_k+Bx<@gzq*Icy9pw&q=edtfK>3{#mn~zL*nScqHfC-p@37CKhn1BhGfC>B?
Ffd@V7UVs1q
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..b94f48dd598d0a3960e6c8d2cf1a6ab4e2e7a6a2
GIT binary patch
literal 8
McmZQz00G7i008;`@&Et;
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..ec8f944c8acd49bcace4e4c78d4306ebd9e28078
GIT binary patch
literal 8
LcmZQz0D~0(0I&e5
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..3737e138423429f98a05b0f5daa3414fe62d0dbf
GIT binary patch
literal 64
zcmdOA@JLNeNi9+cN=?o$N>OmjFH#6dEh^3|E=kQR@klJr@J%cTOUx-v4KB$qN=#2>
IWMDu>0JcIB2><{9
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..398bb41aa4ab29225c14f1182f5cc9fb50187917
GIT binary patch
literal 32768
zcmeIup$)=N6a~1PBnwB=Crwep`N-;&zX@
zMZ@$to^~%%K!5-N0t5&UAV7cs0RjXF5FkK+009C72vjF9$KvsPZK@k?FaZJt2oNAZ
ofB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72xJxb1L?jDg#Z8m
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab
new file mode 100644
index 0000000000000000000000000000000000000000..61556c0db3f4046aec5d8d2eda98226d96cc8587
GIT binary patch
literal 4096
zcmbR3vzw0r2v|V`33{#mn~zL*nScqHfC-p@37CKhn1BhGfC>B?
Ffd@V7UVs1q
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..b94f48dd598d0a3960e6c8d2cf1a6ab4e2e7a6a2
GIT binary patch
literal 8
McmZQz00G7i008;`@&Et;
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..ec8f944c8acd49bcace4e4c78d4306ebd9e28078
GIT binary patch
literal 8
LcmZQz0D~0(0I&e5
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..3ed43a268d6cd5068c4e91ac505a992eda07abcd
GIT binary patch
literal 871
zcmeHFOAdlC5Pe7C0?HjUF>E9R6ZRD*f+8i;g7G#kTzE`h*}>L@E^ITOzG+_HOd2B*
zQ;9~olvb-~XEn-1UE{oTJ~6J|o4zpZAO~;Noa70v3@sWQp)j;NRv38}+~5$?6eZ)8
z)dn52GqaHvFrn{=Ur^Jxyjim{pqio*ix=$ij6Eg8Kpx9i`iY~&hKT33WL}fmzk(^b
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..398bb41aa4ab29225c14f1182f5cc9fb50187917
GIT binary patch
literal 32768
zcmeIup$)=N6a~1PBnwB=Crwep`N-;&zX@
zMZ@$to^~%%K!5-N0t5&UAV7cs0RjXF5FkK+009C72vjF9$KvsPZK@k?FaZJt2oNAZ
ofB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72xJxb1L?jDg#Z8m
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab
new file mode 100644
index 0000000000000000000000000000000000000000..af80168ff5c7ab340511ace471952032cb99de6c
GIT binary patch
literal 4096
zcmbR3vzw0r2sl6l3?UOz-S1J
khQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2tYyr0IjtrYXATM
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream
new file mode 100644
index 0000000000000000000000000000000000000000..bc57fd6a318d11f98a45dcb8af7818096826e53d
GIT binary patch
literal 4096
zcmeH_K?{OF5QV=V)3ui98WDM}OH$`jDXCPpEc*AomY}+LQcvT8Gdse2<2M70xsc%~
z$T@M;`%I#^qb3c}tL9YA+2d9BJaxb8p7G$Se5I_GPN{EI&`4OQa?>fL46`0w25jY>
z$#1DHE1g{VK^?-RBmXGyNZEX7z`Q4?roSDw*26k%KqacNrv8pFYE3-~8&gQt|LD8c
n^#3>VJELjG4o$NWKzodU5ikNqzz7%tBVYuKfDteP{S$Zr=Lo1W
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..199a5a0aa0a49eefc9286e60888a7e6405a3d3ed
GIT binary patch
literal 8
McmZQz00HI#004dfbN~PV
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..fa606b628b9821e4e1a5e524f5973b5db3fb44ba
GIT binary patch
literal 8
LcmZQz0D}Vn0LTEr
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..e764301d0e90a1747f8c69aa4cfa9f18f4e7f4de
GIT binary patch
literal 1369
zcmeHGK@Ng25S*v*0p$;x7%mcm3HJ&cL6MSe!T1{w9(<+)H#mAx65H%A7SfS@$aDzigQ{;?S
zW-ByYow@31119th@e4}&nkP$UIuv76V&RNEoUq4a=*VMvOFMGp*bwoYmdtB1`(;%5
r8njGjul=@`TXsX)jf|ZbIy&mPoJt>qrtyzyI??i9;YWXbVH&;xF7^-&
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..3c6f1fc27eeab8d0e288eb3cdfbba3e5947c13ce
GIT binary patch
literal 32768
zcmeIuArb*$7zW^-QDn`GBsqYvqNW(bB_vrD1W^=8C}yJz=#C&dfFkib4h0c@V
zVP3)NDia_;fB=Eg3!Fm3*89FFeNhDo5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U
zAV7cs0RjXF5Fn6|z$WC(j^|58abgG%AV7cs0RjXF6e_R}*Wyn6DD-HGW83hy-+L`n
z{Nm{!3M66kdX%*PaJtC^auZmE;5dQ}PkO;CGMj#)AVE`n@4dozPeI>PKDD_@A8p
z>o+i;tkX8SZ{=+#$76)WWa??8RZBH|=qHoue^2AhXC@DodUtW6H3xNUH0fUX=+Mgq
YOuz(8zywUd1WdpLOuz(8;13Br0UkJHMgRZ+
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..48e9f9a4f1086a6fb0ae4833e23e40b16ad3601c
GIT binary patch
literal 8
McmZQz00AZe000~S6951J
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..9e27f732fe29db9f82162c1114700ef23f6ac174
GIT binary patch
literal 8
LcmZQz0D}zx0Js3L
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..91ac4384f18b29b7b042a190eb3551b5f136154f
GIT binary patch
literal 9834
zcmeHMU02&i8Xk<11pzY{0>u5;G|g_DWLr6~{aUAK!QdK7fW?H+w7czK4Yq=id`1!^
zJ$ut$^oR5+KcM$}xwrdY`UBecoslK^gB+*lbT`>!pCgavnP=Xa_tng39*se&oS9r)
z-Q#svm{%*VxVp=QXL&w%{3`RB_gU+J*#WQlu3!!RpJf8S^u|9YQi~~hk=?IqX1E!1kVMx)4~=r9jrI$AAoh0z6Gpn7qZ@#tm~5X#|vBhlWg&ZY_V`5
z>!xJglC0Yovi>Yt??~3W7qSe=DoWN8u!fMf^(G6UhlQwC
zx810q*WAX<12T4o%PTPk3PG*OT73U$i$hNw9zgM{>#j
zh1h7}W`*REO@;;OLIteMf{iV!(e&Z*nC^MmwOv8xm7N|OY;BNvWviz!UBDre1AA*s
zoFvU%R_1zOWvCm*!Lx4jGv-^a(;hqtK-G$YEbwc-a8LL{%k-OMMzz)fsX9pr`?=u&
zUUh}_Ele|JldzvXu&Uh+)3${JE_|yF^=DPfk{E-D+7bBN=dB{^2BHpA3B5}IZSRuM
zt>=%y1hqtFRBsX(xjk@a!pFPH>L(5129BvlOBl8uST)rR&vAzx*?d=U&s%Y$>H4e_P8n7Ihj6+
zBD%|<7i2M7m!l?RL5W=QZDc7l4-|}*kISuffkdbpT(j;luHqYgMlT5nKz8ln=
z>t_%WGR9?+kIduy%ee32Op}Zf(OhQ@`=r&GbH==W-j-zv^aVS=8#whQG_ErEP$83%
zC9Y2{<4rsQR0uo;!+yRaG9Fn(_ICU-jiQ1x6V_w~FFNVI3Zh5m`oZlOsloYXo6_A}
z_#m<{?Bs0NIX;FlMy9q-T;H}F141lHWD1ILfeK3(EC`BinLRzp!~CxYHsEXgm<6_v
zV<=o`HM6zv?mi)N5L)gt3|B?Crw=U$7F?1M{20R4B(veB;vKoE_}b)0LF3;=c&a2l
zBb2;|1W6=p29-2XO(}pt;IxHJx1gE&?LuZ9woetpSrIa;a^+9!t@(Ej2vI%WZf^1648k9~Ih=vBz3GQHPJ(M+R
z{biOl6l4vKj#5I1+*RWxf!ymsKIuY~1X_veWI^(ziXeC*X|k5gKhut2Rj8Bq2HMYt
z(xl9Uc>v3OiM$>y|DhtO2zJW1*i$zE6){vJ7f3vK^|bZPP>sM_dZW@LdYd
z8+7?moxrKXxSqmug%llq3`Z7ljlrco(=t$1f4=yQzGNm|bMYs{`NGy?cE$`Fl%@fn
zQ7orX!mmA<-3Qy{>iQbo?5)>oa5K8Qv)@h+(1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5*B
zt-vLg7pvl(E5JFzHtV*|wNFr
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab
new file mode 100644
index 0000000000000000000000000000000000000000..0af757d317443f7546373c924209e9bf200bd132
GIT binary patch
literal 4096
zcmbR3vzw0r2$(?x3Jvq
zJ7&J8LZFEbf9I&D`bUbbS8J&;ft-CKzTYHqPx#
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..bd56c2e105f7fba02a033c4ca6be70e36997b2f3
GIT binary patch
literal 32768
zcmeI(F%5t)5Qfo9>V^q5SilBj2ZlPp1_=sqf`(4qhJ}*6oBr|3+b>F#a0CbtAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXD71*-tx{rC#JXr(?5FkK+009C72oNAZfB=DL
zY4A+Tj)DLI0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5;&PoU;J`}(`T
Fpchh(2rmEt
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab
new file mode 100644
index 0000000000000000000000000000000000000000..e9f4207392a74bda2f0779c42bdc0684afb8b673
GIT binary patch
literal 4096
zcmbR3vzw0r2v|S_3-
zX;&bf0;B_hG%M6ze0oRaMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^D~>
GhX4SY^c8pj
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream
new file mode 100644
index 0000000000000000000000000000000000000000..a3c0cc4c6357cbe1285a3e4098e739c8d7e0a79c
GIT binary patch
literal 4096
zcmeIuF%H5o32D&t54ux^Hp9K>z{}fB*y_
S009U<00Izz00ba#tiTu9N+q5E
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..abb4d9ecc3aa42f869484574cf0b7036f587f219
GIT binary patch
literal 8
LcmZQz0E1)z0BZnb
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..93a595bd1bbabaf1bd4ee4909ac49e65e67fa3f4
GIT binary patch
literal 8
LcmZQz0D}bp0H^?=
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..54ddb6b0140d7c1ffc50c5894ca02aeaf87fe3e5
GIT binary patch
literal 300
zcmb7GbD0MaH*zfPZT;DGTP*Y9@whQ2<`Z3dFU-=V@X&Kd>4{QzZmbR8|2YTu-qSV
pO5W|&?hmITG2M6PrdCU=)>6QA3eZdF%8P@{_*#`qgx`@l=mY9KadrRz
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..705a37083f219e940a212904b09d797af9834121
GIT binary patch
literal 32768
zcmeIup$&jA6h+Y{AnOtAfkHA4Ly%yQNP_M&1%c2oF`>B
zT5NFkon9vIC$*B-=t*t7=WUDOcB0X+Pmk}GYe}b5PSWp5s3hG)58_9>DvAGIIF7ks
ZAOHafKmY;|fB*y_009U<00RFe@BxxWUVs1q
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..b2f755748b421f358b6149de73a4dd286dc1948d
GIT binary patch
literal 8
LcmZQz0E5o}0Q&&+
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..ec8f944c8acd49bcace4e4c78d4306ebd9e28078
GIT binary patch
literal 8
LcmZQz0D~0(0I&e5
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..ec61a31adb65032350bfb56b06b2e27fe7eeda12
GIT binary patch
literal 170
zcmY+5F$=;l5Jt1>qN6TZb%?(~#nM4R6uKYHQG-qHxLmCMdqwQv^1*w&Z)0i%k76&_
zl6YH@*fUBFSutr1`E~+spf7k;66kPkR*<|<@J|QKEky=(L+~A{+cNLJVpmitEzRQB
hjzJ|5@dz&}IyI9!X&;h1q!RyWZf4{05pZzLXWnQCJZ}I1
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..398bb41aa4ab29225c14f1182f5cc9fb50187917
GIT binary patch
literal 32768
zcmeIup$)=N6a~1PBnwB=Crwep`N-;&zX@
zMZ@$to^~%%K!5-N0t5&UAV7cs0RjXF5FkK+009C72vjF9$KvsPZK@k?FaZJt2oNAZ
ofB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72xJxb1L?jDg#Z8m
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab
new file mode 100644
index 00000000..c393a517
--- /dev/null
+++ b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab
@@ -0,0 +1,2 @@
+3
+0
\ No newline at end of file
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab
new file mode 100644
index 0000000000000000000000000000000000000000..e3c80060f5179c28e5c7f3a9360ccb9e424dca38
GIT binary patch
literal 4096
zcmbR3vzw0r2$(?x3OmjFH#6dEh^3|E=kQR@klJr@J%cTOUx-v4KB$qN=#2>
MVE_Rz$p|Ky0PMOF6951J
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i
new file mode 100644
index 0000000000000000000000000000000000000000..bd56c2e105f7fba02a033c4ca6be70e36997b2f3
GIT binary patch
literal 32768
zcmeI(F%5t)5Qfo9>V^q5SilBj2ZlPp1_=sqf`(4qhJ}*6oBr|3+b>F#a0CbtAV7cs
z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ
zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXD71*-tx{rC#JXr(?5FkK+009C72oNAZfB=DL
zY4A+Tj)DLI0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5;&PoU;J`}(`T
Fpchh(2rmEt
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab
new file mode 100644
index 0000000000000000000000000000000000000000..704eb7c66a35bfd8003fdd0eb3f317bbc4abefd3
GIT binary patch
literal 4096
zcmbR3vzw0r2$(?x3Yx*d*A>+`v3$0
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len
new file mode 100644
index 0000000000000000000000000000000000000000..29ce11cc9c2acfbfd357e04485500314b13afbb5
GIT binary patch
literal 8
LcmZQz00U+K01E&F
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len
new file mode 100644
index 0000000000000000000000000000000000000000..a9f80ae0249093f1db8b14f71053acce35747e3d
GIT binary patch
literal 8
LcmZQz0D~C-0H6Sw
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at
new file mode 100644
index 0000000000000000000000000000000000000000..1f28bfba1f28cf4a8ea0a58c1e9e7c5280d4cb0c
GIT binary patch
literal 298
zcmb`>O%8%E5J2G`r5Dh-gC>NHh>)-!+ld|N51Fx
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i.len
new file mode 100644
index 0000000000000000000000000000000000000000..131e265740f37d77b7c4a3676d2a7704ca3e4a29
GIT binary patch
literal 8
McmZQz0D%Su009U9fdBvi
literal 0
HcmV?d00001
diff --git a/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab b/library-views/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab
new file mode 100644
index 0000000000000000000000000000000000000000..5ec5426fde67316ece551634e00398d9bf034287
GIT binary patch
literal 8192
zcmeIze|*mc9tZGGlGb9(2{G)JaLWyA$1JhXnzfw#SZIVaT8ojEW0iAGNNz3MLbD`!
zNT{5Lle#6`9t+_lzfMlnXe~Lk{QBP0<8k-L{da%fANxEW+w=R~_s2e;_vih7y%(o{
z5D_?r)VlWj-$yhX1VO*+gJ7D6AqW2JJ=b}D?dRHY?W5N||88xc@%TS~zxLYya_sW`
zExoafUAc)*a5Z0IF+XGhD>$7^{F6ycW)`zKnDf}5YuTIS?92+bV{PXd!&Yp>uB^jB
ztiBowjpu3pfhV}0`?-T<{EnM=i`uN_-7MxX7VuflVKHa0lzIFI$MO<~vXNS)GnU=i
zi-~-MvHT6ASj@)!GwX5>uU-j-PVo%u-4F!F*@63*!R>s8o4J&0xP>J=#zKZTgDvVf
zKXzdbhj2K5%YH0jPkzD#R&mmJFD1{F{Ueq8EndI
zHsn0k=6k$wF%siV*{E90X;v%**zjK+y=QxPD
zoXSyL&Vk&?6jm{j^&^8Io}Jj91KE;!Y`~XUi*NJXPx^{q^GhD!MV7N!L-(7>{D_%c
z$9yj1VlLq(F67@hiNyDpE8!`8O_K>=7k9i;{aZ)4uzg#
zB^U50|HK14$X%>vDO=s5zD(k3KFk$-o+T{dB9?MKzvVpM+SvK?K2GP8%;y5;aWivy
zgxS2r(QMMh^|31la|rwM8K!dqQ}`Dq^BX3yQB(8CyBNpE*^Y&b;m3^PNj7Hqt?nP=
z7|wLoW)83ZDDPO!wLHsRtYkI6=IzbQGyC%ppW!|(;Vy1r8BcKw>o=D_jN=BTa~;QU
z6=!id-(oTMu!ujfkP%V(mv?X`2XY#xathz#B<|xlUglV~YN5Z`i$6*lF^Y{)aL%VurtXLe)2(Y$y*6q?O*T+1r%V+GIiIAhud!4YO~KVRZ*e#~;7
z<5tGElTUn#8@ZnE@jI?&n_rlJj$jEFa}hu1e1^rEe_!1ir{PZeu%MW(*VJ%s-!CV-~Xkw=tXt
zdHI}u&MH>(80&P
z6L=qEIg(MF$_OrCz;!(PPv6TE{5$vaJj)o?$viNgt2vA-_zH_y&N;loe0GYL7krvy
zxq?Ia1=CsYPI%rm|xf^TtQnjW02d8yL+aY{(lD%@aHE>JRoB
z-`}`2mmdEAHjZUCjr3a04fBC0BA053+!fN&20=If)aQ#n(BQpD~R;F_|6jlCR8W
zYc6GD9$*+FyQ$|H{l;^g%PN+!g4f?|zS)OISjhc6%-xLYuHGERtz62Fc$^#Ax`+AZ
zBV5f_xq`b{!g|TBkExu`S)9k6oW;8LxL#&5pUarX3g)tPPxp)CIhva|oMFAxgZHvO
zXEL3CWeOvEs}F}UiSIIjml(&Cd(AgzGln}E#hX&pkA2vH3mMMOS(}aSbHBe=PgZk2
z&vGX#8IfwgGlR#tn1^_j``D(B{m!v0<2r8P1#V`KH2uNfaUHjD6>qxVerGC+Ig3Rs
zXCZG+x8Iq@nS7bkxPwy|{(yes{T#>H9Lwz-#fZMcVcP-biP=2CcX*VS
zcz_QMG+%s;J9wJg*44|5UQj*
zmAR}pS`M%;N3(##SdrS^+0Mj{#Dg2Ddtdk`NcrO$9BICG=?O1<|9N-{EaRD3i
zFdMMdSUJEf*5*oHt<