diff --git a/Dialog screenshot.png b/Dialog screenshot.png
new file mode 100644
index 0000000..314fe65
Binary files /dev/null and b/Dialog screenshot.png differ
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9252494
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+# Alwan
+Alwan is an Android Jetpack Compose color picker library.
+
+## Preview
+https://user-images.githubusercontent.com/12600936/189493581-ae1d92ea-143f-4b0f-9d62-5b6e097118ba.mp4
+
+![image](https://user-images.githubusercontent.com/12600936/189495507-1f0fe171-7e48-4696-ae49-61e1bab3dc4e.png)
+
+
+## Download
+#### Gradle:
+```gradle
+dependencies {
+ implementation 'com.raedapps:alwan:1.0.0'
+}
+```
+
+## Usage Guide
+You can use `Alwan` composable as following:
+````Kotlin
+Alwan(
+ onColorChanged = { color -> },
+ modifier = Modifier.width(300.dp),
+)
+````
+`onColorChanged` is called whenever the user selects a new color.
+
+### Providing the default color:
+Use AlwanState to control the initially selected color:
+````Kotlin
+Alwan(
+ onColorChanged = { },
+ modifier = Modifier.width(300.dp),
+ state = rememberAlwanState(initialColor = Color.Yellow),
+)
+````
+
+### Showing the alpha slider
+The alpha slider is hidden by default. Use the `showAlphaSlider` parameter to show it:
+```kotlin
+Alwan(
+ onColorChanged = { },
+ showAlphaSlider = true,
+)
+```
+
+### Using AlwanDialog
+You can use the `AlwanDialog` as following:
+````kotlin
+AlwanDialog(
+ onColorChanged = { },
+ onDismissRequest = { },
+)
+````
+
+`AlwanDialog` can be customized with positive & negative buttons:
+````kotlin
+AlwanDialog(
+ onDismissRequest = { },
+ onColorChanged = { },
+ positiveButtonText = "OK",
+ onPositiveButtonClick = { },
+ negativeButtonText = "CANCEL",
+ onNegativeButtonClick = { },
+)
+````
+
+
+## License
+```
+Copyright 2022 Raed Mughaus
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+```
diff --git a/Recording.mp4 b/Recording.mp4
new file mode 100755
index 0000000..34e5645
Binary files /dev/null and b/Recording.mp4 differ
diff --git a/Screenshot recording.png b/Screenshot recording.png
new file mode 100644
index 0000000..04142de
Binary files /dev/null and b/Screenshot recording.png differ
diff --git a/Screenshot.png b/Screenshot.png
new file mode 100644
index 0000000..35705aa
Binary files /dev/null and b/Screenshot.png differ
diff --git a/alwan/.gitignore b/alwan/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/alwan/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/alwan/build.gradle b/alwan/build.gradle
new file mode 100644
index 0000000..320a577
--- /dev/null
+++ b/alwan/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ minSdk 23
+ targetSdk 32
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.3.1'
+ }
+}
+
+dependencies {
+ implementation "androidx.compose.ui:ui:$compose_version"
+ implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
+}
+
+
+ext {
+ PUBLISH_GROUP_ID = 'com.raedapps'
+ PUBLISH_VERSION = '1.0.0'
+ PUBLISH_ARTIFACT_ID = 'alwan'
+ PUBLISH_DESCRIPTION = 'An Android Jetpack Compose color picker'
+ PUBLISH_URL = 'https://github.com/Raed-Mughaus/compose-color-picker'
+ PUBLISH_LICENSE_NAME = 'Apache License'
+ PUBLISH_LICENSE_URL = 'https://github.com/Raed-Mughaus/RasmView/blob/master/LICENSE'
+ PUBLISH_DEVELOPER_ID = 'RaedMughaus'
+ PUBLISH_DEVELOPER_NAME = 'Raed Mughaus'
+ PUBLISH_DEVELOPER_EMAIL = 'raedradi2014@gmail.com'
+ PUBLISH_SCM_CONNECTION = 'scm:git:github.com/Raed-Mughaus/compose-color-picker.git'
+ PUBLISH_SCM_DEVELOPER_CONNECTION = 'scm:git:ssh://github.com/Raed-Mughaus/compose-color-picker.git'
+ PUBLISH_SCM_URL = 'github.com/Raed-Mughaus/compose-color-picker/tree/main'
+}
+
+apply from: "${rootProject.projectDir}/scripts/publish-module.gradle"
\ No newline at end of file
diff --git a/alwan/consumer-rules.pro b/alwan/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/alwan/proguard-rules.pro b/alwan/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/alwan/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/alwan/src/main/AndroidManifest.xml b/alwan/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..9d3144f
--- /dev/null
+++ b/alwan/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/alwan/src/main/java/com/raedapps/alwan/AlwanState.kt b/alwan/src/main/java/com/raedapps/alwan/AlwanState.kt
new file mode 100644
index 0000000..03906b1
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/AlwanState.kt
@@ -0,0 +1,31 @@
+package com.raedapps.alwan
+
+import androidx.compose.runtime.*
+import androidx.compose.ui.graphics.Color
+import com.raedapps.alwan.ktx.toHSV
+
+data class AlwanState(
+ val initialColor: Color,
+) {
+
+ internal var hsvColor by mutableStateOf(initialColor.toHSV())
+
+ internal var alpha by mutableStateOf(initialColor.alpha)
+
+ var color: Color
+ get() = hsvColor.toColor().copy(alpha = alpha)
+ set(value) {
+ hsvColor = value.toHSV()
+ alpha = value.alpha
+ }
+
+}
+
+@Composable
+fun rememberAlwanState(
+ initialColor: Color,
+): AlwanState {
+ return remember {
+ AlwanState(initialColor)
+ }
+}
\ No newline at end of file
diff --git a/alwan/src/main/java/com/raedapps/alwan/Math.kt b/alwan/src/main/java/com/raedapps/alwan/Math.kt
new file mode 100644
index 0000000..58fad74
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/Math.kt
@@ -0,0 +1,30 @@
+package com.raedapps.alwan
+
+import androidx.compose.ui.geometry.Offset
+import kotlin.math.*
+
+internal fun toRadians(degrees: Float) = PI.toFloat() * degrees / 180
+
+internal fun toDegrees(radians: Float) = 180 * radians / PI.toFloat()
+
+internal fun angle(point: Offset): Float? {
+ return if (point.x != 0f) {
+ val m = point.y / point.x
+ val angle = toDegrees(atan(m))
+ when {
+ point.x > 0 && point.y >= 0 -> angle
+ point.x < 0 && point.y >= 0 -> 180 + angle
+ point.x < 0 && point.y < 0 -> 180 + angle
+ point.x > 0 && point.y < 0 -> 360 + angle
+ else -> throw RuntimeException()
+ }
+ } else if (point.y > 0) {
+ 90f
+ } else if (point.y < 0) {
+ 270f
+ } else {
+ null
+ }
+}
+
+internal fun lerp(a: Float, b: Float, t: Float) = a + t * (b - a)
diff --git a/alwan/src/main/java/com/raedapps/alwan/event/handler/AlphaEventHandler.kt b/alwan/src/main/java/com/raedapps/alwan/event/handler/AlphaEventHandler.kt
new file mode 100644
index 0000000..10d5d0e
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/event/handler/AlphaEventHandler.kt
@@ -0,0 +1,35 @@
+package com.raedapps.alwan.event.handler
+
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerInputScope
+import com.raedapps.alwan.ktx.consumeCurrent
+import com.raedapps.alwan.ktx.currentPosition
+import com.raedapps.alwan.ktx.currentPointersCount
+
+internal suspend fun PointerInputScope.detectAlphaChangeEvent(
+ onChangeStart: () -> Unit,
+ onAlphaChanged: (Float) -> Unit,
+ onChangeEnd: () -> Unit,
+) = awaitPointerEventScope {
+ while (true) {
+ awaitFirstDown()
+ if (currentPointersCount != 1) {
+ continue
+ }
+ onChangeStart()
+ while (true) {
+ val alpha = (currentPosition.x / size.width).coerceIn(0f..1f)
+ consumeCurrent()
+ onAlphaChanged(alpha)
+ awaitPointerEvent()
+ if (currentEvent.changes.size != 1) {
+ continue
+ }
+ if (currentEvent.type != PointerEventType.Move) {
+ break
+ }
+ }
+ onChangeEnd()
+ }
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/event/handler/HueEventHandler.kt b/alwan/src/main/java/com/raedapps/alwan/event/handler/HueEventHandler.kt
new file mode 100644
index 0000000..521bffb
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/event/handler/HueEventHandler.kt
@@ -0,0 +1,44 @@
+package com.raedapps.alwan.event.handler
+
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerInputScope
+import com.raedapps.alwan.angle
+import com.raedapps.alwan.ktx.consumeCurrent
+import com.raedapps.alwan.ktx.currentPosition
+import com.raedapps.alwan.ktx.currentPointersCount
+import com.raedapps.alwan.model.Circle
+
+internal suspend fun PointerInputScope.detectHueChangeEvent(
+ outerCircle: Circle,
+ innerCircle: Circle,
+ onChangeStart: () -> Unit,
+ onHueChanged: (Float) -> Unit,
+ onChangeEnd: () -> Unit,
+) = awaitPointerEventScope {
+ while (true) {
+ awaitFirstDown()
+ if (currentPointersCount != 1) {
+ continue
+ }
+ if (!outerCircle.contains(currentPosition) || innerCircle.contains(currentPosition)) {
+ continue
+ }
+ onChangeStart()
+ while (true) {
+ val hue = angle(currentPosition - outerCircle.center)
+ consumeCurrent()
+ if (hue != null) {
+ onHueChanged(hue)
+ }
+ awaitPointerEvent()
+ if (currentEvent.changes.size != 1) {
+ continue
+ }
+ if (currentEvent.type != PointerEventType.Move) {
+ break
+ }
+ }
+ onChangeEnd()
+ }
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/event/handler/SaturationValueEventHandler.kt b/alwan/src/main/java/com/raedapps/alwan/event/handler/SaturationValueEventHandler.kt
new file mode 100644
index 0000000..748b04b
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/event/handler/SaturationValueEventHandler.kt
@@ -0,0 +1,41 @@
+package com.raedapps.alwan.event.handler
+
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.PointerInputScope
+import com.raedapps.alwan.ktx.consumeCurrent
+import com.raedapps.alwan.ktx.currentPointersCount
+import com.raedapps.alwan.ktx.currentPosition
+
+internal suspend fun PointerInputScope.detectSaturationValueChange(
+ rect: Rect,
+ onChangeStart: () -> Unit,
+ onSaturationValueChanged: (s: Float, v: Float) -> Unit,
+ onChangeEnd: () -> Unit,
+) = awaitPointerEventScope {
+ while (true) {
+ awaitFirstDown()
+ if (currentPointersCount != 1) {
+ continue
+ }
+ if (!rect.contains(currentPosition)) {
+ continue
+ }
+ onChangeStart()
+ while (true) {
+ val saturation = ((currentPosition.x - rect.left) / rect.width).coerceIn(0f, 1f)
+ val value = ((rect.bottom - currentPosition.y) / rect.height).coerceIn(0f, 1f)
+ onSaturationValueChanged(saturation, value)
+ consumeCurrent()
+ awaitPointerEvent()
+ if (currentPointersCount != 1) {
+ continue
+ }
+ if (currentEvent.type != PointerEventType.Move) {
+ break
+ }
+ }
+ onChangeEnd()
+ }
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/ktx/AwaitPointerEventScope.kt b/alwan/src/main/java/com/raedapps/alwan/ktx/AwaitPointerEventScope.kt
new file mode 100644
index 0000000..e7dcec1
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ktx/AwaitPointerEventScope.kt
@@ -0,0 +1,14 @@
+package com.raedapps.alwan.ktx
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+
+internal val AwaitPointerEventScope.currentPosition: Offset
+ get() = currentEvent.changes.first().position
+
+internal val AwaitPointerEventScope.currentPointersCount: Int
+ get() = currentEvent.changes.size
+
+internal fun AwaitPointerEventScope.consumeCurrent() {
+ currentEvent.changes.first().consume()
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/ktx/Color.kt b/alwan/src/main/java/com/raedapps/alwan/ktx/Color.kt
new file mode 100644
index 0000000..f0be53b
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ktx/Color.kt
@@ -0,0 +1,16 @@
+package com.raedapps.alwan.ktx
+
+import android.graphics.Color.colorToHSV
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import com.raedapps.alwan.model.HSVColor
+
+internal fun Color.toHSV(): HSVColor {
+ val hsv = FloatArray(3)
+ colorToHSV(toArgb(), hsv)
+ return HSVColor(
+ hsv[0],
+ hsv[1],
+ hsv[2],
+ )
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/ktx/Offset.kt b/alwan/src/main/java/com/raedapps/alwan/ktx/Offset.kt
new file mode 100644
index 0000000..076c910
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ktx/Offset.kt
@@ -0,0 +1,10 @@
+package com.raedapps.alwan.ktx
+
+import androidx.compose.ui.geometry.Offset
+import kotlin.math.sqrt
+
+internal fun Offset.distanceTo(offset: Offset): Float {
+ val dx = offset.x - x
+ val dy = offset.y - y
+ return sqrt(dx * dx + dy * dy)
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/ktx/Path.kt b/alwan/src/main/java/com/raedapps/alwan/ktx/Path.kt
new file mode 100644
index 0000000..6706dff
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ktx/Path.kt
@@ -0,0 +1,10 @@
+package com.raedapps.alwan.ktx
+
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Path
+import com.raedapps.alwan.model.Circle
+
+internal fun Path.addCircle(circle: Circle) {
+ val rect = Rect(circle.center, circle.radius)
+ addOval(rect)
+}
\ No newline at end of file
diff --git a/alwan/src/main/java/com/raedapps/alwan/model/Circle.kt b/alwan/src/main/java/com/raedapps/alwan/model/Circle.kt
new file mode 100644
index 0000000..9fd7c3c
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/model/Circle.kt
@@ -0,0 +1,13 @@
+package com.raedapps.alwan.model
+
+import androidx.compose.ui.geometry.Offset
+import com.raedapps.alwan.ktx.distanceTo
+
+data class Circle(
+ val center: Offset,
+ val radius: Float,
+) {
+
+ fun contains(point: Offset) = point.distanceTo(center) <= radius
+
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/model/HSVColor.kt b/alwan/src/main/java/com/raedapps/alwan/model/HSVColor.kt
new file mode 100644
index 0000000..ef431a4
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/model/HSVColor.kt
@@ -0,0 +1,71 @@
+package com.raedapps.alwan.model
+
+import androidx.compose.ui.graphics.Color
+import com.raedapps.alwan.lerp
+import kotlin.math.pow
+
+private val MAX_PACKED_VALUE = 2f.pow(16).toInt() - 1
+
+@JvmInline
+internal value class HSVColor private constructor(
+ private val value: ULong,
+) {
+
+ constructor(
+ h: Float,
+ s: Float,
+ v: Float,
+ ) : this(pack(h, s, v))
+
+ val h get() = 360 * ((value shr 32) and 0XFFFFu).toFloat() / MAX_PACKED_VALUE
+
+ val s get() = ((value shr 16) and 0XFFFFu).toFloat() / MAX_PACKED_VALUE
+
+ val v get() = (value and 0XFFFFu).toFloat() / MAX_PACKED_VALUE
+
+ fun toColor(): Color {
+ val step = 1 - h / 360
+ val (previousStep, previousHue) = STEP_HUE_LIST
+ .filter { it.first <= step }
+ .maxBy { it.first }
+ val (nextStep, nextHue) = STEP_HUE_LIST
+ .filter { it.first >= step }
+ .minBy { it.first }
+ return Color.hsv(
+ if (previousStep == nextStep) {
+ previousHue
+ } else {
+ lerp(
+ previousHue,
+ nextHue,
+ (step - previousStep) / (nextStep - previousStep),
+ )
+ },
+ s,
+ v,
+ )
+ }
+
+ fun copy(
+ h: Float = this.h,
+ s: Float = this.s,
+ v: Float = this.v,
+ ): HSVColor {
+ return HSVColor(h, s, v)
+ }
+
+ override fun toString() = "HSV(h = $h, s = ${(100_00 * s).toInt() / 100}, v = ${(100_00 * v).toInt() / 100})"
+
+}
+
+private fun pack(
+ h: Float,
+ s: Float,
+ v: Float,
+): ULong {
+ require(h in 0f..360f && s in 0f..1f && v in 0f..1f)
+ val uH = ((h / 360) * MAX_PACKED_VALUE).toULong()
+ val uS = (s * MAX_PACKED_VALUE).toULong()
+ val uB = (v * MAX_PACKED_VALUE).toULong()
+ return (uH shl 32) or (uS shl 16) or uB
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/model/HSVColorPickerUIData.kt b/alwan/src/main/java/com/raedapps/alwan/model/HSVColorPickerUIData.kt
new file mode 100644
index 0000000..b1767e7
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/model/HSVColorPickerUIData.kt
@@ -0,0 +1,64 @@
+package com.raedapps.alwan.model
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.cos
+
+private const val HSV_WEIGH = 0.7f
+
+internal class HSVColorPickerUIData(
+ hsvSize: Int,
+) {
+
+ val hueOuterCircle = run {
+ val radius = hsvSize / 2f
+ Circle(
+ Offset(radius, radius),
+ radius,
+ )
+ }
+
+ val hueInnerCircle = Circle(
+ hueOuterCircle.center,
+ HSV_WEIGH * hueOuterCircle.radius
+ )
+
+ val saturationValueRect = run {
+ rectInside(
+ Circle(
+ hueInnerCircle.center,
+ 0.95f * hueInnerCircle.radius,
+ ),
+ )
+ }
+
+}
+
+private fun rectInside(circle: Circle): Rect {
+ val size = abs(circle.radius * cos(0.25f * PI)).toFloat()
+ val topLeft = circle.center - Offset(size, size)
+ return Rect(topLeft, Size(2 * size, 2 * size))
+}
+
+val STEP_HUE_LIST = listOf(0, 120, 160, 210, 260, 310, 360)
+ .mapIndexed { i, angle ->
+ Pair(
+ 1 - angle / 360f,
+ i * 60f,
+ )
+ }
+
+val STEP_COLOR_LIST = STEP_HUE_LIST
+ .map { (step, hue) ->
+ Pair(
+ step,
+ Color.hsv(hue, 1f, 1f),
+ )
+ }
+ .reversed()
+ .toTypedArray()
diff --git a/alwan/src/main/java/com/raedapps/alwan/renderer/AlphaSliderRenderer.kt b/alwan/src/main/java/com/raedapps/alwan/renderer/AlphaSliderRenderer.kt
new file mode 100644
index 0000000..2abb55c
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/renderer/AlphaSliderRenderer.kt
@@ -0,0 +1,55 @@
+package com.raedapps.alwan.renderer
+
+import androidx.compose.ui.geometry.*
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.unit.dp
+
+internal fun DrawScope.drawAlphaSlider(
+ selectedColor: Color,
+ backgroundColor: Color,
+ squaresColor: Color,
+) {
+ val rect = Rect(Offset.Zero, size)
+ val roundRect = RoundRect(
+ rect,
+ CornerRadius(6.dp.toPx(), 6.dp.toPx())
+ )
+ val path = Path()
+ path.addRoundRect(roundRect)
+ clipPath(path) {
+ drawTransparencyGrid(
+ backgroundColor,
+ squaresColor,
+ )
+ drawRoundRect(
+ Brush.horizontalGradient(
+ listOf(
+ selectedColor.copy(alpha = 0f),
+ selectedColor.copy(alpha = 1f),
+ ),
+ ),
+ rect.topLeft,
+ rect.size,
+ )
+ }
+}
+
+internal fun DrawScope.drawAlphaIndicator(
+ color: Color,
+ active: Boolean,
+) {
+ drawSelectionIndicator(
+ center = Offset(
+ color.alpha * size.width,
+ center.y,
+ ),
+ radius = 12.dp.toPx(),
+ color = color,
+ active,
+ boundary = Rect(Offset.Zero, size)
+ )
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/renderer/CircularSelectionIndicatorRenderer.kt b/alwan/src/main/java/com/raedapps/alwan/renderer/CircularSelectionIndicatorRenderer.kt
new file mode 100644
index 0000000..f02b50c
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/renderer/CircularSelectionIndicatorRenderer.kt
@@ -0,0 +1,36 @@
+package com.raedapps.alwan.renderer
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.unit.dp
+
+internal fun DrawScope.drawSelectionIndicator(
+ center: Offset,
+ radius: Float,
+ color: Color,
+ active: Boolean,
+ boundary: Rect? = null,
+) {
+ val constrainedCenter = if (boundary != null &&
+ boundary.width > 2 * radius &&
+ boundary.height > 2 * radius) {
+ Offset(
+ center.x.coerceIn(boundary.left + radius, boundary.right - radius),
+ center.y.coerceIn(boundary.top + radius, boundary.bottom - radius),
+ )
+ } else {
+ center
+ }
+ val radiusScale = if (active) {
+ 1.4f
+ } else {
+ 1f
+ }
+ val scaledRadius = radiusScale * radius
+ drawCircle(color, scaledRadius, constrainedCenter)
+ drawCircle(Color.Black, scaledRadius, constrainedCenter, style = Stroke(width = 2.dp.toPx()))
+ drawCircle(Color.White, scaledRadius, constrainedCenter, style = Stroke(width = 1.dp.toPx()))
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/renderer/HueRenderer.kt b/alwan/src/main/java/com/raedapps/alwan/renderer/HueRenderer.kt
new file mode 100644
index 0000000..22b014d
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/renderer/HueRenderer.kt
@@ -0,0 +1,52 @@
+package com.raedapps.alwan.renderer
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.ClipOp
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.clipPath
+import androidx.compose.ui.unit.dp
+import com.raedapps.alwan.ktx.addCircle
+import com.raedapps.alwan.model.HSVColorPickerUIData
+import com.raedapps.alwan.model.HSVColor
+import com.raedapps.alwan.model.STEP_COLOR_LIST
+import com.raedapps.alwan.toRadians
+import kotlin.math.cos
+import kotlin.math.sin
+
+internal fun DrawScope.drawHueCircle(
+ uiData: HSVColorPickerUIData,
+) {
+ val path = Path()
+ path.addCircle(uiData.hueInnerCircle)
+ clipPath(path, ClipOp.Difference) {
+ val outerCircle = uiData.hueOuterCircle
+ drawCircle(
+ Brush.sweepGradient(*STEP_COLOR_LIST),
+ outerCircle.radius,
+ outerCircle.center,
+ )
+ }
+}
+
+internal fun DrawScope.drawHueIndicator(
+ uiData: HSVColorPickerUIData,
+ selectedColor: HSVColor,
+ pressed: Boolean,
+) {
+ val outerCircle = uiData.hueOuterCircle
+ val innerCircle = uiData.hueInnerCircle
+ val radius = (outerCircle.radius + innerCircle.radius) / 2f
+ val angleRadians = toRadians(selectedColor.h)
+ drawSelectionIndicator(
+ center = Offset(
+ center.x + radius * cos(angleRadians),
+ center.y - radius * sin(angleRadians),
+ ),
+ radius = (outerCircle.radius - innerCircle.radius - 8.dp.toPx()) / 2f,
+ color = selectedColor.copy(s = 1f, v = 1f).toColor(),
+ active = pressed,
+ )
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/renderer/SaturationValueRenderer.kt b/alwan/src/main/java/com/raedapps/alwan/renderer/SaturationValueRenderer.kt
new file mode 100644
index 0000000..f32af93
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/renderer/SaturationValueRenderer.kt
@@ -0,0 +1,71 @@
+package com.raedapps.alwan.renderer
+
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.unit.dp
+import com.raedapps.alwan.model.HSVColorPickerUIData
+import com.raedapps.alwan.model.HSVColor
+
+internal fun DrawScope.drawSaturationValueRect(
+ uiData: HSVColorPickerUIData,
+ selectedColor: HSVColor,
+) {
+ val rect = uiData.saturationValueRect
+ val cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
+ drawRoundRect(
+ Brush.horizontalGradient(
+ listOf(
+ Color.White,
+ selectedColor
+ .copy(s = 1f, v = 1f)
+ .toColor(),
+ ),
+ startX = rect.left,
+ endX = rect.right,
+ ),
+ rect.topLeft,
+ rect.size,
+ cornerRadius = cornerRadius,
+ )
+ drawRoundRect(
+ Brush.verticalGradient(
+ listOf(Color.Black, Color(0)),
+ startY = rect.bottom,
+ endY = rect.top,
+ ),
+ rect.topLeft,
+ rect.size,
+ cornerRadius = cornerRadius,
+ )
+ drawRoundRect(
+ Color.White,
+ rect.topLeft,
+ rect.size,
+ style = Stroke(
+ width = 1.dp.toPx(),
+ ),
+ cornerRadius = cornerRadius,
+ )
+}
+
+internal fun DrawScope.drawSaturationValueIndicator(
+ uiData: HSVColorPickerUIData,
+ selectedColor: HSVColor,
+ active: Boolean,
+) {
+ val rect = uiData.saturationValueRect
+ drawSelectionIndicator(
+ Offset(
+ rect.left + selectedColor.s * rect.width,
+ rect.bottom - selectedColor.v * rect.height,
+ ),
+ 12.dp.toPx(),
+ selectedColor.toColor(),
+ active,
+ boundary = uiData.saturationValueRect,
+ )
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/renderer/TransparencyGridRenderer.kt b/alwan/src/main/java/com/raedapps/alwan/renderer/TransparencyGridRenderer.kt
new file mode 100644
index 0000000..f79a9ab
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/renderer/TransparencyGridRenderer.kt
@@ -0,0 +1,30 @@
+package com.raedapps.alwan.renderer
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.unit.dp
+
+internal fun DrawScope.drawTransparencyGrid(
+ backgroundColor: Color,
+ squaresColor: Color,
+) {
+ drawRect(backgroundColor)
+ val squareSize = Size(
+ 10.dp.toPx(),
+ 10.dp.toPx(),
+ )
+ var i = 0f
+ while (i < size.width) {
+ var j = 0f
+ while (j < size.height) {
+ drawRect(squaresColor, Offset(i, j), squareSize)
+ val l = i + squareSize.width
+ val t = j + squareSize.height
+ drawRect(squaresColor, Offset(l, t), squareSize)
+ j += 2 * squareSize.height
+ }
+ i += 2 * squareSize.width
+ }
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/ui/AlphaSlider.kt b/alwan/src/main/java/com/raedapps/alwan/ui/AlphaSlider.kt
new file mode 100644
index 0000000..6b269a1
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ui/AlphaSlider.kt
@@ -0,0 +1,55 @@
+package com.raedapps.alwan.ui
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.unit.IntSize
+import com.raedapps.alwan.event.handler.detectAlphaChangeEvent
+import com.raedapps.alwan.model.HSVColorPickerUIData
+import com.raedapps.alwan.renderer.drawAlphaIndicator
+import com.raedapps.alwan.renderer.drawAlphaSlider
+
+@Composable
+internal fun AlphaSlider(
+ modifier: Modifier,
+ selectedColor: Color,
+ onAlphaChanged: (Float) -> Unit,
+) {
+ val backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
+ val squaresColor = MaterialTheme.colors.onSurface.copy(alpha = 1f)
+ var activeSelectionIndicator by remember {
+ mutableStateOf(false)
+ }
+ Canvas(
+ modifier = modifier
+ .pointerInput(Unit) {
+ detectAlphaChangeEvent(
+ onChangeStart = {
+ activeSelectionIndicator = true
+ },
+ onAlphaChanged = onAlphaChanged,
+ onChangeEnd = {
+ activeSelectionIndicator = false
+ },
+ )
+ },
+ onDraw = {
+ clipRect {
+ drawAlphaSlider(
+ selectedColor,
+ backgroundColor = backgroundColor,
+ squaresColor = squaresColor,
+ )
+ drawAlphaIndicator(
+ selectedColor,
+ activeSelectionIndicator,
+ )
+ }
+ },
+ )
+}
\ No newline at end of file
diff --git a/alwan/src/main/java/com/raedapps/alwan/ui/Alwan.kt b/alwan/src/main/java/com/raedapps/alwan/ui/Alwan.kt
new file mode 100644
index 0000000..a5f2ea8
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ui/Alwan.kt
@@ -0,0 +1,73 @@
+package com.raedapps.alwan.ui
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import com.raedapps.alwan.AlwanState
+import com.raedapps.alwan.model.HSVColorPickerUIData
+import com.raedapps.alwan.rememberAlwanState
+import kotlin.math.min
+
+@Composable
+fun Alwan(
+ onColorChanged: (color: Color) -> Unit,
+ modifier: Modifier = Modifier,
+ state: AlwanState = rememberAlwanState(Color.Red),
+ onChangeStart: () -> Unit = { },
+ onChangeEnd: () -> Unit = { },
+ showAlphaSlider: Boolean = false,
+) {
+ val suggestedSize = 300.dp
+ Column(
+ modifier = modifier
+ .width(suggestedSize)
+ .wrapContentHeight(),
+ ) {
+ val currentDensity = LocalDensity.current
+ var sizePx by remember {
+ with(currentDensity) {
+ mutableStateOf(suggestedSize.roundToPx())
+ }
+ }
+ val sizeDp = remember(sizePx) {
+ with(currentDensity) {
+ sizePx.toDp()
+ }
+ }
+ val uiData = remember(sizePx) {
+ HSVColorPickerUIData(sizePx)
+ }
+ HSVColorPicker(
+ modifier = Modifier
+ .aspectRatio(1f)
+ .onSizeChanged {
+ sizePx = min(it.width, it.height)
+ },
+ uiData = uiData,
+ selectedColor = state.hsvColor,
+ onChangeStart = onChangeStart,
+ onColorChanged = {
+ state.hsvColor = it
+ onColorChanged(state.color)
+ },
+ onChangeEnd = onChangeEnd,
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ if (showAlphaSlider) {
+ AlphaSlider(
+ Modifier
+ .width(sizeDp)
+ .height(36.dp),
+ state.color,
+ onAlphaChanged = {
+ state.alpha = it
+ onColorChanged(state.color)
+ }
+ )
+ }
+ }
+}
diff --git a/alwan/src/main/java/com/raedapps/alwan/ui/AlwanDialog.kt b/alwan/src/main/java/com/raedapps/alwan/ui/AlwanDialog.kt
new file mode 100644
index 0000000..0fa7235
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ui/AlwanDialog.kt
@@ -0,0 +1,110 @@
+package com.raedapps.alwan.ui
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
+import androidx.compose.ui.window.Dialog
+import com.raedapps.alwan.AlwanState
+import com.raedapps.alwan.rememberAlwanState
+import com.raedapps.alwan.renderer.drawTransparencyGrid
+
+@Composable
+fun AlwanDialog(
+ onDismissRequest: () -> Unit,
+ onColorChanged: (Color) -> Unit,
+ modifier: Modifier = Modifier,
+ state: AlwanState = rememberAlwanState(Color.Red),
+ onChangeStart: () -> Unit = { },
+ onChangeEnd: () -> Unit = { },
+ positiveButtonText: String? = null,
+ onPositiveButtonClick: () -> Unit = {},
+ negativeButtonText: String? = null,
+ onNegativeButtonClick: () -> Unit = {},
+ showAlphaSlider: Boolean = false,
+) {
+ Dialog(
+ onDismissRequest = onDismissRequest,
+ ) {
+ Surface(
+ modifier = modifier.wrapContentSize(),
+ shape = MaterialTheme.shapes.large,
+ ) {
+ Column(
+ Modifier.padding(24.dp, 24.dp, 24.dp, 12.dp)
+ ) {
+ Box {
+ Alwan(
+ onColorChanged = onColorChanged,
+ modifier = modifier,
+ state = state,
+ onChangeStart = onChangeStart,
+ onChangeEnd = onChangeEnd,
+ showAlphaSlider = showAlphaSlider,
+ )
+ SelectedColor(color = state.color)
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(
+ modifier = Modifier.align(Alignment.End),
+ ) {
+ if (negativeButtonText != null) {
+ TextButton(
+ onClick = onNegativeButtonClick,
+ ) {
+ Text(text = negativeButtonText)
+ }
+ }
+ if (positiveButtonText != null) {
+ TextButton(
+ onClick = onPositiveButtonClick,
+ ) {
+ Text(text = positiveButtonText)
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.SelectedColor(
+ color: Color,
+) {
+ Box(
+ Modifier
+ .size(36.dp)
+ .align(Alignment.TopEnd)
+ .clip(CircleShape),
+ ) {
+ val backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
+ val squaresColor = MaterialTheme.colors.onSurface.copy(alpha = 1f)
+ Canvas(
+ modifier = Modifier.fillMaxSize(),
+ onDraw = {
+ drawTransparencyGrid(
+ backgroundColor,
+ squaresColor,
+ )
+ },
+ )
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = color,
+ border = BorderStroke(1.dp, MaterialTheme.colors.onSurface),
+ elevation = 4.dp,
+ shape = CircleShape,
+ ) { }
+ }
+}
\ No newline at end of file
diff --git a/alwan/src/main/java/com/raedapps/alwan/ui/HSVColorPicker.kt b/alwan/src/main/java/com/raedapps/alwan/ui/HSVColorPicker.kt
new file mode 100644
index 0000000..7a578b7
--- /dev/null
+++ b/alwan/src/main/java/com/raedapps/alwan/ui/HSVColorPicker.kt
@@ -0,0 +1,81 @@
+package com.raedapps.alwan.ui
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.runtime.*
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.input.pointer.pointerInput
+import com.raedapps.alwan.model.HSVColorPickerUIData
+import com.raedapps.alwan.model.HSVColor
+import com.raedapps.alwan.event.handler.detectHueChangeEvent
+import com.raedapps.alwan.event.handler.detectSaturationValueChange
+import com.raedapps.alwan.renderer.drawHueCircle
+import com.raedapps.alwan.renderer.drawHueIndicator
+import com.raedapps.alwan.renderer.drawSaturationValueRect
+import com.raedapps.alwan.renderer.drawSaturationValueIndicator
+
+@Composable
+internal fun HSVColorPicker(
+ modifier: Modifier,
+ uiData: HSVColorPickerUIData,
+ selectedColor: HSVColor,
+ onChangeStart: () -> Unit,
+ onColorChanged: (HSVColor) -> Unit,
+ onChangeEnd: () -> Unit,
+) {
+ var activeHueIndicator by remember { mutableStateOf(false) }
+ var activeSVIndicator by remember { mutableStateOf(false) }
+ var key by remember {
+ mutableStateOf(0)
+ }
+ Canvas(
+ modifier = modifier
+ .pointerInput(uiData, key) {
+ detectHueChangeEvent(
+ uiData.hueOuterCircle,
+ uiData.hueInnerCircle,
+ onChangeStart = {
+ activeHueIndicator = true
+ onChangeStart()
+ },
+ onHueChanged = { h ->
+ onColorChanged(
+ selectedColor.copy(h = 360 - h),
+ )
+ },
+ onChangeEnd = {
+ activeHueIndicator = false
+ onChangeEnd()
+ key++
+ }
+ )
+ }
+ .pointerInput(uiData, key) {
+ detectSaturationValueChange(
+ uiData.saturationValueRect,
+ onChangeStart = {
+ activeSVIndicator = true
+ onChangeStart()
+ },
+ onSaturationValueChanged = { s, v ->
+ onColorChanged(
+ selectedColor.copy(s = s, v = v),
+ )
+ },
+ onChangeEnd = {
+ activeSVIndicator = false
+ onChangeEnd()
+ key++
+ },
+ )
+ },
+ onDraw = {
+ clipRect {
+ drawHueCircle(uiData)
+ drawHueIndicator(uiData, selectedColor, activeHueIndicator)
+ drawSaturationValueRect(uiData, selectedColor)
+ drawSaturationValueIndicator(uiData, selectedColor, activeSVIndicator)
+ }
+ },
+ )
+}
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..62ff03c
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ compileSdk 32
+
+ defaultConfig {
+ applicationId "com.raedapps.alwanapp"
+ minSdk 23
+ targetSdk 32
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary true
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.3.1'
+ }
+ packagingOptions {
+ resources {
+ excludes += '/META-INF/{AL2.0,LGPL2.1}'
+ }
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.7.0'
+ implementation "androidx.compose.ui:ui:$compose_version"
+ implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
+ implementation 'androidx.activity:activity-compose:1.3.1'
+ implementation project(':alwan')
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+ androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
+ debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
+ debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..b17f492
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/raedapps/alwanapp/MainActivity.kt b/app/src/main/java/com/raedapps/alwanapp/MainActivity.kt
new file mode 100644
index 0000000..15cae7d
--- /dev/null
+++ b/app/src/main/java/com/raedapps/alwanapp/MainActivity.kt
@@ -0,0 +1,172 @@
+package com.raedapps.alwanapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.dp
+import com.raedapps.alwan.rememberAlwanState
+import com.raedapps.alwan.ui.Alwan
+import com.raedapps.alwan.ui.AlwanDialog
+import com.raedapps.alwanapp.ui.theme.AlwanTheme
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ AlwanTheme {
+ Scaffold { padding ->
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(padding),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ var color by remember {
+ mutableStateOf(Color.Red)
+ }
+ /*Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp)
+ .padding(24.dp)
+ .clip(
+ RoundedCornerShape(16.dp),
+ )
+ ) {
+ TransparencyGrid(
+ Modifier.fillMaxSize(),
+ )
+ Card(
+ modifier = Modifier.fillMaxSize(),
+ backgroundColor = color,
+ shape = RoundedCornerShape(16.dp)
+ ) {
+ Box(
+ contentAlignment = Alignment.Center,
+ ) {
+ val shape = RoundedCornerShape(16.dp)
+ Text(
+ String.format("#%08X", color.toArgb()),
+ modifier = Modifier
+ .clip(shape)
+ .background(Color.Black, shape)
+ .border(3.dp, Color.White, shape = shape)
+ .padding(6.dp),
+ style = MaterialTheme.typography.h4,
+ fontFamily = FontFamily.Monospace,
+ )
+ }
+ }
+ }
+ Spacer(modifier = Modifier.height(12.dp))
+ Alwan(
+ onColorChanged = {
+ color = it
+ },
+ showAlphaSlider = true,
+ )*/
+ Spacer(modifier = Modifier.height(36.dp))
+ DialogButton(
+ initialColor = color,
+ onColorChanged = {
+ color = it
+ },
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun DialogButton(
+ initialColor: Color,
+ onColorChanged: (Color) -> Unit,
+) {
+ var showDialog by remember {
+ mutableStateOf(false)
+ }
+ Button(
+ onClick = {
+ showDialog = true
+ },
+ ) {
+ Text(text = "Show dialog")
+ }
+ val state = rememberAlwanState(initialColor = initialColor)
+ if (showDialog) {
+ AlwanDialog(
+ onColorChanged = {},
+ state = state,
+ onDismissRequest = {
+ showDialog = false
+ },
+ showAlphaSlider = true,
+ positiveButtonText = "OK",
+ onPositiveButtonClick = {
+ showDialog = false
+ onColorChanged(state.color)
+ },
+ negativeButtonText = "Cancel",
+ onNegativeButtonClick = {
+ showDialog = false
+ },
+ )
+ }
+}
+
+@Composable
+private fun TransparencyGrid(modifier: Modifier) {
+ val backgroundColor = MaterialTheme.colors.onSurface.copy(alpha = 0.5f)
+ val squaresColor = MaterialTheme.colors.onSurface.copy(alpha = 1f)
+ Canvas(
+ modifier = modifier,
+ onDraw = {
+ drawTransparencyGrid(
+ backgroundColor,
+ squaresColor
+ )
+ },
+ )
+}
+
+private fun DrawScope.drawTransparencyGrid(
+ backgroundColor: Color,
+ squaresColor: Color,
+) {
+ drawRect(backgroundColor)
+ val squareSize = Size(
+ 10.dp.toPx(),
+ 10.dp.toPx(),
+ )
+ var i = 0f
+ while (i < size.width) {
+ var j = 0f
+ while (j < size.height) {
+ drawRect(squaresColor, Offset(i, j), squareSize)
+ val l = i + squareSize.width
+ val t = j + squareSize.height
+ drawRect(squaresColor, Offset(l, t), squareSize)
+ j += 2 * squareSize.height
+ }
+ i += 2 * squareSize.width
+ }
+}
diff --git a/app/src/main/java/com/raedapps/alwanapp/ui/theme/Color.kt b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Color.kt
new file mode 100644
index 0000000..9f0a5d2
--- /dev/null
+++ b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Color.kt
@@ -0,0 +1,8 @@
+package com.raedapps.alwanapp.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple200 = Color(0xFFBB86FC)
+val Purple500 = Color(0xFF6200EE)
+val Purple700 = Color(0xFF3700B3)
+val Teal200 = Color(0xFF03DAC5)
\ No newline at end of file
diff --git a/app/src/main/java/com/raedapps/alwanapp/ui/theme/Shape.kt b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Shape.kt
new file mode 100644
index 0000000..acb6b1f
--- /dev/null
+++ b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Shape.kt
@@ -0,0 +1,11 @@
+package com.raedapps.alwanapp.ui.theme
+
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Shapes
+import androidx.compose.ui.unit.dp
+
+val Shapes = Shapes(
+ small = RoundedCornerShape(4.dp),
+ medium = RoundedCornerShape(4.dp),
+ large = RoundedCornerShape(0.dp)
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/raedapps/alwanapp/ui/theme/Theme.kt b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Theme.kt
new file mode 100644
index 0000000..172044b
--- /dev/null
+++ b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Theme.kt
@@ -0,0 +1,44 @@
+package com.raedapps.alwanapp.ui.theme
+
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.darkColors
+import androidx.compose.material.lightColors
+import androidx.compose.runtime.Composable
+
+private val DarkColorPalette = darkColors(
+ primary = Purple200,
+ primaryVariant = Purple700,
+ secondary = Teal200
+)
+
+private val LightColorPalette = lightColors(
+ primary = Purple500,
+ primaryVariant = Purple700,
+ secondary = Teal200
+
+ /* Other default colors to override
+ background = Color.White,
+ surface = Color.White,
+ onPrimary = Color.White,
+ onSecondary = Color.Black,
+ onBackground = Color.Black,
+ onSurface = Color.Black,
+ */
+)
+
+@Composable
+fun AlwanTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
+ val colors = if (darkTheme) {
+ DarkColorPalette
+ } else {
+ LightColorPalette
+ }
+
+ MaterialTheme(
+ colors = colors,
+ typography = Typography,
+ shapes = Shapes,
+ content = content
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/raedapps/alwanapp/ui/theme/Type.kt b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Type.kt
new file mode 100644
index 0000000..5947a31
--- /dev/null
+++ b/app/src/main/java/com/raedapps/alwanapp/ui/theme/Type.kt
@@ -0,0 +1,28 @@
+package com.raedapps.alwanapp.ui.theme
+
+import androidx.compose.material.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
+val Typography = Typography(
+ body1 = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp
+ )
+ /* Other default text styles to override
+ button = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.W500,
+ fontSize = 14.sp
+ ),
+ caption = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp
+ )
+ */
+)
\ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c9d0870
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Allawn
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..4bd2f80
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..658a2d2
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,21 @@
+buildscript {
+ ext {
+ compose_version = '1.2.1'
+ }
+ dependencies {
+ classpath 'io.github.gradle-nexus:publish-plugin:1.1.0'
+ }
+}// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '7.2.0' apply false
+ id 'com.android.library' version '7.2.0' apply false
+ id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
+}
+
+apply plugin: 'io.github.gradle-nexus.publish-plugin'
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
+
+apply from: "${rootProject.projectDir}/scripts/publish-root.gradle"
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..cd0519b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app"s APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..96599cc
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Sep 09 05:09:18 AST 2022
+distributionBase=GRADLE_USER_HOME
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/scripts/publish-module.gradle b/scripts/publish-module.gradle
new file mode 100644
index 0000000..7dc77e1
--- /dev/null
+++ b/scripts/publish-module.gradle
@@ -0,0 +1,77 @@
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+task androidSourcesJar(type: Jar) {
+ archiveClassifier.set('sources')
+ if (project.plugins.findPlugin("com.android.library")) {
+ from android.sourceSets.main.java.srcDirs
+ } else {
+ from sourceSets.main.java.srcDirs
+ }
+}
+
+artifacts {
+ archives androidSourcesJar
+}
+
+group = PUBLISH_GROUP_ID
+version = PUBLISH_VERSION
+
+afterEvaluate {
+ publishing {
+ publications {
+ release(MavenPublication) {
+ // The coordinates of the library, being set from variables that
+ // we'll set up later
+ groupId PUBLISH_GROUP_ID
+ artifactId PUBLISH_ARTIFACT_ID
+ version PUBLISH_VERSION
+
+ // Two artifacts, the `aar` (or `jar`) and the sources
+ if (project.plugins.findPlugin("com.android.library")) {
+ from components.release
+ } else {
+ artifact("$buildDir/libs/${project.getName()}-${version}.jar")
+ }
+
+ artifact androidSourcesJar
+
+ // Mostly self-explanatory metadata
+ pom {
+ name = PUBLISH_ARTIFACT_ID
+ description = PUBLISH_DESCRIPTION
+ url = PUBLISH_URL
+ licenses {
+ license {
+ name = PUBLISH_LICENSE_NAME
+ url = PUBLISH_LICENSE_URL
+ }
+ }
+ developers {
+ developer {
+ id = PUBLISH_DEVELOPER_ID
+ name = PUBLISH_DEVELOPER_NAME
+ email = PUBLISH_DEVELOPER_EMAIL
+ }
+ }
+
+ // Version control info - if you're using GitHub, follow the
+ // format as seen here
+ scm {
+ connection = PUBLISH_SCM_CONNECTION
+ developerConnection = PUBLISH_SCM_DEVELOPER_CONNECTION
+ url = PUBLISH_SCM_URL
+ }
+ }
+ }
+ }
+ }
+}
+
+ext["signing.keyId"] = rootProject.ext["signing.keyId"]
+ext["signing.password"] = rootProject.ext["signing.password"]
+ext["signing.secretKeyRingFile"] = rootProject.ext["signing.secretKeyRingFile"]
+
+signing {
+ sign publishing.publications
+}
diff --git a/scripts/publish-root.gradle b/scripts/publish-root.gradle
new file mode 100644
index 0000000..caec3a3
--- /dev/null
+++ b/scripts/publish-root.gradle
@@ -0,0 +1,36 @@
+// Create variables with empty default values
+ext["signing.keyId"] = ''
+ext["signing.password"] = ''
+ext["signing.secretKeyRingFile"] = ''
+ext["ossrhUsername"] = ''
+ext["ossrhPassword"] = ''
+ext["sonatypeStagingProfileId"] = ''
+
+File secretPropsFile = project.rootProject.file('local.properties')
+if (secretPropsFile.exists()) {
+ // Read local.properties file first if it exists
+ Properties p = new Properties()
+ new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) }
+ p.each { name, value -> ext[name] = value }
+} else {
+ // Use system environment variables
+ ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME')
+ ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD')
+ ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID')
+ ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID')
+ ext["signing.password"] = System.getenv('SIGNING_PASSWORD')
+ ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE')
+}
+
+// Set up Sonatype repository
+nexusPublishing {
+ repositories {
+ sonatype {
+ stagingProfileId = sonatypeStagingProfileId
+ username = ossrhUsername
+ password = ossrhPassword
+ nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/"))
+ snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/"))
+ }
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..a2163ac
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ gradlePluginPortal()
+ google()
+ mavenCentral()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "Alwan"
+include ':app'
+include ':alwan'