diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 19dcc50..7339168 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,6 +13,7 @@ androidXTestJunit = "1.1.5"
androidXTestEspresso = "3.5.1"
skydovesBalloon = "1.6.4"
flexbox = "3.0.0"
+counterSlider = "0.1.4"
composeActivity = "1.9.0"
composeBom = "2024.06.00"
@@ -29,6 +30,7 @@ fastadapter-extensions-binding = { group = "com.mikepenz", name = "fastadapter-e
fastadapter-extensions-diff = { group = "com.mikepenz", name = "fastadapter-extensions-diff", version.ref = "fastadapter" }
arklib = { group = "dev.arkbuilders", name = "arklib", version.ref = "arkLib" }
orbit-mvi-viewmodel = { group = "org.orbit-mvi", name = "orbit-viewmodel", version.ref = "orbitMvi" }
+orbit-mvi-compose = { group = "org.orbit-mvi", name = "orbit-compose", version.ref = "orbitMvi" }
viewbinding-property-delegate = { group = "com.github.kirich1409", name = "viewbindingpropertydelegate-noreflection", version.ref = "viewbindingPropertyDelegate" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidXCore" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidXAppcompat" }
@@ -38,6 +40,7 @@ androidx-test-junit = { group = "androidx.test.ext", name = "junit", version.ref
androidx-test-espresso = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "androidXTestEspresso" }
skydoves-balloon = { group = "com.github.skydoves", name = "balloon", version.ref = "skydovesBalloon" }
flexbox = { group = "com.google.android.flexbox", name = "flexbox", version.ref = "flexbox" }
+counterslider = { module = "com.github.mdrlzy:ComposeCounterSlider", version.ref = "counterSlider" }
#Compose
androidx-compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "composeActivity" }
@@ -47,3 +50,4 @@ androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graph
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-compose-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts
index 5897047..deb3915 100644
--- a/sample/build.gradle.kts
+++ b/sample/build.gradle.kts
@@ -46,6 +46,10 @@ android {
buildFeatures {
buildConfig = true
viewBinding = true
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
}
splits {
@@ -72,11 +76,16 @@ android {
dependencies {
implementation(project(":filepicker"))
implementation(project(":about"))
+ implementation(project(":scorewidget"))
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.ui)
+ implementation(libs.androidx.compose.material3)
implementation(libraries.arklib)
implementation("androidx.core:core-ktx:1.12.0")
implementation(libraries.androidx.appcompat)
implementation(libraries.android.material)
+ implementation(libraries.orbit.mvi.viewmodel)
testImplementation(libraries.junit)
androidTestImplementation(libraries.androidx.test.junit)
androidTestImplementation(libraries.androidx.test.espresso)
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 4680920..34778de 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -34,6 +34,7 @@
+
diff --git a/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt b/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt
index db201bd..7922f87 100644
--- a/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt
+++ b/sample/src/main/java/dev/arkbuilders/sample/MainActivity.kt
@@ -59,6 +59,12 @@ class MainActivity : AppCompatActivity() {
val intent = Intent(this, AboutActivity::class.java)
startActivity(intent)
}
+
+ findViewById(R.id.btn_score).setOnClickListener {
+ resolvePermissions()
+ val intent = Intent(this, ScoreActivity::class.java)
+ startActivity(intent)
+ }
}
private fun getFilePickerConfig(mode: ArkFilePickerMode? = null) = ArkFilePickerConfig(
diff --git a/sample/src/main/java/dev/arkbuilders/sample/ScoreActivity.kt b/sample/src/main/java/dev/arkbuilders/sample/ScoreActivity.kt
new file mode 100644
index 0000000..96c088b
--- /dev/null
+++ b/sample/src/main/java/dev/arkbuilders/sample/ScoreActivity.kt
@@ -0,0 +1,138 @@
+package dev.arkbuilders.sample
+
+import android.os.Bundle
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.foundation.layout.padding
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.button.MaterialButton
+import dev.arkbuilders.arklib.data.folders.FoldersRepo
+import dev.arkbuilders.arklib.data.index.ResourceIndex
+import dev.arkbuilders.arklib.data.index.ResourceIndexRepo
+import dev.arkbuilders.arklib.user.score.ScoreStorage
+import dev.arkbuilders.arklib.user.score.ScoreStorageRepo
+import dev.arkbuilders.components.filepicker.ArkFilePickerConfig
+import dev.arkbuilders.components.filepicker.ArkFilePickerFragment
+import dev.arkbuilders.components.filepicker.ArkFilePickerMode
+import dev.arkbuilders.components.filepicker.onArkPathPicked
+import dev.arkbuilders.components.scorewidget.HorizontalScoreWidgetComposable
+import dev.arkbuilders.components.scorewidget.ScoreWidgetController
+import dev.arkbuilders.components.scorewidget.VerticalScoreWidgetComposable
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.nio.file.Path
+import kotlin.io.path.name
+
+class ScoreActivity : AppCompatActivity() {
+ private var rootFolder: Path? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_score)
+
+ val btnPickRoot = findViewById(R.id.btn_pick_root)
+ val btnPickResource = findViewById(R.id.btn_pick_resource)
+
+ supportFragmentManager.onArkPathPicked(
+ this,
+ customRequestKey = PICK_ROOT_KEY
+ ) { root ->
+ rootFolder = root
+ btnPickRoot.text = root.name
+ }
+
+ supportFragmentManager.onArkPathPicked(
+ this,
+ customRequestKey = PICK_RESOURCE_KEY
+ ) { resourcePath ->
+ rootFolder?.let {
+ onResourcePicked(root = it, resourcePath)
+ }
+ }
+
+ btnPickRoot.setOnClickListener {
+ ArkFilePickerFragment
+ .newInstance(ArkFilePickerConfig(pathPickedRequestKey = PICK_ROOT_KEY))
+ .show(supportFragmentManager, null)
+ }
+
+ btnPickResource.setOnClickListener {
+ ArkFilePickerFragment
+ .newInstance(
+ ArkFilePickerConfig(
+ pathPickedRequestKey = PICK_RESOURCE_KEY,
+ mode = ArkFilePickerMode.FILE
+ )
+ ).show(supportFragmentManager, null)
+ }
+ }
+
+ private fun onResourcePicked(
+ root: Path,
+ resourcePath: Path
+ ) = lifecycleScope.launch {
+ val (index, scoreStorage) = setupIndexAndScoreStorage(root)
+ val id = index.allPaths().toList()
+ .find { it.second == resourcePath }?.first
+
+ id ?: let {
+ Toast.makeText(
+ this@ScoreActivity,
+ "File does not belong to root",
+ Toast.LENGTH_SHORT
+ ).show()
+ return@launch
+ }
+
+ findViewById(R.id.btn_pick_resource).text = resourcePath.name
+
+ val scoreWidgetController = ScoreWidgetController(
+ lifecycleScope,
+ getCurrentId = { id },
+ onScoreChanged = {}
+ )
+ scoreWidgetController.init(scoreStorage)
+
+ val horizontal = findViewById(R.id.score_widget_horizontal)
+ val vertical = findViewById(R.id.score_widget_vertical)
+
+ horizontal.disposeComposition()
+ horizontal.setContent {
+ HorizontalScoreWidgetComposable(
+ size = DpSize(200.dp, 80.dp),
+ controller = scoreWidgetController
+ )
+ }
+
+ vertical.disposeComposition()
+ vertical.setContent {
+ VerticalScoreWidgetComposable(
+ modifier = Modifier.padding(40.dp),
+ size = DpSize(50.dp, 120.dp),
+ controller = scoreWidgetController
+ )
+ }
+
+ scoreWidgetController.setVisible(true)
+ scoreWidgetController.displayScore()
+ }
+
+ private suspend fun setupIndexAndScoreStorage(
+ root: Path
+ ): Pair = withContext(Dispatchers.IO) {
+ val foldersRepo = FoldersRepo(applicationContext)
+ val index = ResourceIndexRepo(foldersRepo).provide(root)
+ val scoreStorage = ScoreStorageRepo(lifecycleScope).provide(index)
+ return@withContext index to scoreStorage
+ }
+
+ companion object {
+ private val PICK_ROOT_KEY = "pickRootKey"
+ private val PICK_RESOURCE_KEY = "pickResourceKey"
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
index 09d74b6..f404d8e 100644
--- a/sample/src/main/res/layout/activity_main.xml
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -1,8 +1,10 @@
-
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="vertical">
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_score.xml b/sample/src/main/res/layout/activity_score.xml
new file mode 100644
index 0000000..c6cd6e0
--- /dev/null
+++ b/sample/src/main/res/layout/activity_score.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scorewidget/build.gradle.kts b/scorewidget/build.gradle.kts
index b05f489..06081fb 100644
--- a/scorewidget/build.gradle.kts
+++ b/scorewidget/build.gradle.kts
@@ -34,19 +34,30 @@ android {
kotlinOptions {
jvmTarget = "17"
}
-
+ buildFeatures {
+ compose = true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
+ }
buildFeatures {
viewBinding = true
}
}
dependencies {
-
- implementation(libraries.androidx.core.ktx)
- implementation(libraries.androidx.appcompat)
- implementation(libraries.android.material)
+ implementation(libraries.androidx.compose.activity)
+ implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.androidx.compose.ui)
+ implementation(libs.androidx.compose.ui.graphics)
+ implementation(libs.androidx.compose.ui.tooling)
+ implementation(libs.androidx.compose.ui.tooling.preview)
+ implementation(libs.androidx.compose.material3)
+ implementation(libs.androidx.compose.icons.extended)
implementation(libraries.arklib)
implementation(libraries.orbit.mvi.viewmodel)
+ implementation(libs.orbit.mvi.compose)
+ implementation(libs.counterslider)
testImplementation(libraries.junit)
androidTestImplementation(libraries.androidx.test.junit)
androidTestImplementation(libraries.androidx.test.espresso)
diff --git a/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidget.kt b/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidget.kt
deleted file mode 100644
index 9f03495..0000000
--- a/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidget.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package dev.arkbuilders.components.scorewidget
-
-import androidx.core.view.isVisible
-import androidx.lifecycle.LifecycleOwner
-import dev.arkbuilders.components.databinding.ScoreWidgetBinding
-import org.orbitmvi.orbit.viewmodel.observe
-
-class ScoreWidget(
- val controller: ScoreWidgetController,
- val lifecycleOwner: LifecycleOwner,
-) {
- var binding: ScoreWidgetBinding? = null
-
- fun init(binding: ScoreWidgetBinding) {
- this.binding = binding
- binding.increaseScore.setOnClickListener {
- controller.onIncrease()
- }
- binding.decreaseScore.setOnClickListener {
- controller.onDecrease()
- }
- controller.observe(lifecycleOwner, state = ::render)
- }
-
- fun onDestroyView() {
- binding = null
- }
-
- private fun render(state: ScoreWidgetState) {
- val score = state.score
- binding!!.root.isVisible = state.visible
- binding!!.scoreValue.text = if (score == 0) null else score.toString()
- }
-}
\ No newline at end of file
diff --git a/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetComposable.kt b/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetComposable.kt
new file mode 100644
index 0000000..0de9964
--- /dev/null
+++ b/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetComposable.kt
@@ -0,0 +1,57 @@
+package dev.arkbuilders.components.scorewidget
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.DpSize
+import androidx.compose.ui.unit.dp
+import com.mdrlzy.counterslider.HorizontalCounterSlider
+import com.mdrlzy.counterslider.VerticalCounterSlider
+import org.orbitmvi.orbit.compose.collectAsState
+
+
+@Composable
+fun HorizontalScoreWidgetComposable(
+ modifier: Modifier = Modifier,
+ size: DpSize = DpSize(200.dp, 80.dp),
+ allowTopToReset: Boolean = true,
+ allowBottomToReset: Boolean = true,
+ controller: ScoreWidgetController,
+) {
+ val state by controller.collectAsState()
+ if (state.visible.not())
+ return
+ HorizontalCounterSlider(
+ modifier = modifier,
+ size = size,
+ value = state.score.toString(),
+ allowTopToReset = allowTopToReset,
+ allowBottomToReset = allowBottomToReset,
+ onValueIncreaseClick = { controller.onIncrease() },
+ onValueDecreaseClick = { controller.onDecrease() },
+ onValueClearClick = { controller.onReset() }
+ )
+}
+
+@Composable
+fun VerticalScoreWidgetComposable(
+ modifier: Modifier = Modifier,
+ size: DpSize = DpSize(80.dp, 200.dp),
+ allowLeftToReset: Boolean = true,
+ allowRightToReset: Boolean = true,
+ controller: ScoreWidgetController,
+) {
+ val state by controller.collectAsState()
+ if (state.visible.not())
+ return
+ VerticalCounterSlider(
+ modifier = modifier,
+ size = size,
+ value = state.score.toString(),
+ allowLeftToReset = allowLeftToReset,
+ allowRightToReset = allowRightToReset,
+ onValueIncreaseClick = { controller.onIncrease() },
+ onValueDecreaseClick = { controller.onDecrease() },
+ onValueClearClick = { controller.onReset() }
+ )
+}
diff --git a/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetController.kt b/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetController.kt
index 63f905f..df18ddf 100644
--- a/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetController.kt
+++ b/scorewidget/src/main/java/dev/arkbuilders/components/scorewidget/ScoreWidgetController.kt
@@ -22,7 +22,7 @@ class ScoreWidgetController(
val scope: CoroutineScope,
val getCurrentId: () -> ResourceId,
val onScoreChanged: (ResourceId) -> Unit
-): ContainerHost {
+) : ContainerHost {
private lateinit var scoreStorage: ScoreStorage
@@ -33,25 +33,31 @@ class ScoreWidgetController(
this.scoreStorage = scoreStorage
}
- fun setVisible(visible: Boolean) = intent {
- reduce {
- state.copy(visible = visible)
+ fun setVisible(visible: Boolean) {
+ intent {
+ reduce {
+ state.copy(visible = visible)
+ }
}
}
- fun displayScore() = intent {
- reduce {
- state.copy(score = scoreStorage.getScore(getCurrentId()))
+
+ fun displayScore() {
+ intent {
+ reduce {
+ state.copy(score = scoreStorage.getScore(getCurrentId()))
+ }
}
}
- fun onIncrease() = changeScore(1)
+ fun onIncrease() = changeScore(scoreStorage.getScore(getCurrentId()) + 1)
+
+ fun onDecrease() = changeScore(scoreStorage.getScore(getCurrentId()) - 1)
- fun onDecrease() = changeScore(-1)
+ fun onReset() = changeScore(0)
- private fun changeScore(inc: Score) = scope.launch {
+ private fun changeScore(score: Score) = scope.launch {
val id = getCurrentId()
- val score = scoreStorage.getScore(id) + inc
scoreStorage.setScore(id, score)
withContext(Dispatchers.IO) {
diff --git a/scorewidget/src/main/res/layout/item_tag.xml b/scorewidget/src/main/res/layout/item_tag.xml
deleted file mode 100644
index f4d1745..0000000
--- a/scorewidget/src/main/res/layout/item_tag.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
diff --git a/scorewidget/src/main/res/layout/score_widget.xml b/scorewidget/src/main/res/layout/score_widget.xml
deleted file mode 100644
index 7e582fc..0000000
--- a/scorewidget/src/main/res/layout/score_widget.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file