Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
gmerinojimenez committed Dec 17, 2021
2 parents ee566a3 + 0585f74 commit ab23763
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 129 deletions.
54 changes: 30 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
[![Platform](https://img.shields.io/badge/Platform-Android-brightgreen)](https://github.com/gmerinojimenez/tweaks)
[![Version](https://maven-badges.herokuapp.com/maven-central/io.github.gmerinojimenez/tweaks/badge.png)](https://search.maven.org/artifact/io.github.gmerinojimenez/tweaks)
[![Support](https://img.shields.io/badge/Support-%3E%3D%20Android%205.0-brightgreen)](https://github.com/Telefonica/mistica-android)

# Tweaks
<p align="center">
<img src="https://img.shields.io/badge/Platform-Android-brightgreen" />
<img src="https://maven-badges.herokuapp.com/maven-central/io.github.gmerinojimenez/tweaks/badge.png" />
<img src="https://img.shields.io/badge/Support-%3E%3D%20Android%205.0-brightgreen" />
</p>

<p align="center">
<img src="https://user-images.githubusercontent.com/4595241/146578604-32454df7-6c15-456f-9939-7848464600e9.png" width="800" />
</p>

A customizable debug screen to view and edit flags that can be used for development in **Jetpack Compose** applications



<p align="center">
<img src="https://user-images.githubusercontent.com/4595241/138683112-93a58d0f-1365-4392-add1-a547f4308f22.gif" data-canonical-src="https://user-images.githubusercontent.com/4595241/138683112-93a58d0f-1365-4392-add1-a547f4308f22.gif" width="200" />
</p>


To include the library add to your app's `build.gradle`:

Expand All @@ -31,20 +42,15 @@ where `demoTweakGraph` is the structure you want to be rendered:
```kotlin
private fun demoTweakGraph() = tweaksGraph {
cover("Tweaks Demo") {
label("cover-key", "Current user ID:") { flow { emit("1")} }
label("cover-key", "Current user ID:") { flowOf("1") }
}
category("Screen 1") {
group("Group 1") {
label(
key = "timestamp",
name = "Current timestamp",
) {
flow {
while (true) {
emit("${System.currentTimeMillis() / 1000}")
delay(1000)
}
}
timestampState
}
editableString(
key = "value1",
Expand All @@ -53,24 +59,13 @@ private fun demoTweakGraph() = tweaksGraph {
editableBoolean(
key = "value2",
name = "Value 2",
)
editableInt(
key = "value3",
name = "Value 3",
defaultValue = flow {
while (true) {
counter += 1
emit(counter)
delay(1000)
}
}
defaultValue = true,
)
editableLong(
key = "value4",
name = "Value 4",
defaultValue = 0L,
defaultValue = 42L,
)

button(
key = "button1",
name = "Demo button"
Expand Down Expand Up @@ -238,5 +233,16 @@ addTweakGraph(
}
```

## Shake gesture support:
The tweaks can be opened when the user shakes the device, to do this you need to add to your navigation controller:
```kotlin
navController.navigateToTweaksOnShake()
```
And also, optionally
```xml
<uses-permission android:name="android.permission.VIBRATE" />
```
to your `AndroidManifest.xml`

## Special thanks to contributors:
* [Yamal Al Mahamid](https://github.com/yamal-coding)
Original file line number Diff line number Diff line change
@@ -1,72 +1,67 @@
package com.gmerinojimenez.tweaks.demo

import android.app.Application
import android.util.Log
import android.widget.Toast
import com.gmerinojimenez.tweaks.Tweaks
import com.gmerinojimenez.tweaks.domain.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf

class TweakDemoApplication : Application() {
override fun onCreate() {
super.onCreate()
Tweaks.init(this@TweakDemoApplication, demoTweakGraph())
}

var timestampState = MutableStateFlow("0")

init {
CoroutineScope(Dispatchers.Default).launch {
while (true) {
timestampState.value = "${System.currentTimeMillis() / 1000}"
delay(1000)
}
var timestampState = flow {
while (true) {
emit("${System.currentTimeMillis() / 1000}")
delay(1000)
}
}

private fun demoTweakGraph() = tweaksGraph {
cover("Tweaks Demo") {
label("cover-key", "Current user ID:") { MutableStateFlow("1") }
}
category("Screen 1") {
group("Group 1") {
label(
key = "timestamp",
name = "Current timestamp",
) {
timestampState
}
editableString(
key = "value1",
name = "Value 1",
)
editableBoolean(
key = "value2",
name = "Value 2",
defaultValue = true,
)
editableLong(
key = "value4",
name = "Value 4",
defaultValue = 42L,
)
button(
key = "button1",
name = "Demo button"
) {
Toast.makeText(this@TweakDemoApplication, "Demo button", Toast.LENGTH_LONG)
.show()
}

routeButton(
key = "button2",
name = "Custom screen button",
route = "custom-screen"
)
private fun demoTweakGraph() = tweaksGraph {
cover("Tweaks Demo") {
label("cover-key", "Current user ID:") { flowOf("1") }
}
category("Screen 1") {
group("Group 1") {
label(
key = "timestamp",
name = "Current timestamp",
) {
timestampState
}
editableString(
key = "value1",
name = "Value 1",
)
editableBoolean(
key = "value2",
name = "Value 2",
defaultValue = true,
)
editableLong(
key = "value4",
name = "Value 4",
defaultValue = 42L,
)
button(
key = "button1",
name = "Demo button"
) {
Toast.makeText(this@TweakDemoApplication, "Demo button", Toast.LENGTH_LONG)
.show()
}

routeButton(
key = "button2",
name = "Custom screen button",
route = "custom-screen"
)
}
}
}
}
6 changes: 3 additions & 3 deletions library/src/enabled/java/com/gmerinojimenez/tweaks/Tweaks.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import com.gmerinojimenez.tweaks.domain.TweaksGraph
import com.gmerinojimenez.tweaks.ui.TweaksCategoryScreen
import com.gmerinojimenez.tweaks.ui.TweaksScreen
import com.squareup.seismic.ShakeDetector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject


Expand All @@ -39,9 +39,9 @@ open class Tweaks {
@Inject
internal lateinit var tweaksBusinessLogic: TweaksBusinessLogic

open fun <T>getTweakValue(key: String): StateFlow<T?> = tweaksBusinessLogic.getValue(key)
open fun <T>getTweakValue(key: String): Flow<T?> = tweaksBusinessLogic.getValue(key)

open fun <T> getTweakValue(entry: TweakEntry<T>): StateFlow<T?> = tweaksBusinessLogic.getValue(entry)
open fun <T> getTweakValue(entry: TweakEntry<T>): Flow<T?> = tweaksBusinessLogic.getValue(entry)

open suspend fun <T> setTweakValue(key: String, value: T?) {
tweaksBusinessLogic.setValue(key, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package com.gmerinojimenez.tweaks.domain

import androidx.datastore.preferences.core.*
import com.gmerinojimenez.tweaks.data.TweaksDataStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

@Suppress("UNCHECKED_CAST")
@Singleton
class TweaksBusinessLogic @Inject constructor(
private val tweaksDataStore: TweaksDataStore,
Expand Down Expand Up @@ -40,49 +42,43 @@ class TweaksBusinessLogic @Inject constructor(

private fun checkIfRepeatedKey(
alreadyIntroducedKeys: MutableSet<String>,
entry: TweakEntry<*>
entry: TweakEntry<*>,
) {
if (alreadyIntroducedKeys.contains(entry.key)) {
throw IllegalStateException("There is a repeated key in the tweaks, review your graph")
throw IllegalStateException("There is a repeated key in the tweaks: ${entry.key}, review your graph")
}

alreadyIntroducedKeys.add(entry.key)
}

@Suppress("UNCHECKED_CAST")
fun <T> getValue(key: String): StateFlow<T?> {
fun <T> getValue(key: String): Flow<T?> {
val tweakEntry = keyToEntryValueMap[key] as TweakEntry<T>
return getValue(tweakEntry)
}

fun <T> getValue(entry: TweakEntry<T>): StateFlow<T?> = when (entry as Modifiable) {
fun <T> getValue(entry: TweakEntry<T>): Flow<T?> = when (entry as Modifiable) {
is ReadOnly<*> -> (entry as ReadOnly<T>).value
is Editable<*> -> getEditableValue(entry)
}

@FlowPreview
@OptIn(ExperimentalCoroutinesApi::class)
private fun <T> getEditableValue(entry: TweakEntry<T>): StateFlow<T?> {
private fun <T> getEditableValue(entry: TweakEntry<T>): Flow<T?> {
val editableCasted = entry as Editable<T>
val defaultValueFlow: StateFlow<T>? = editableCasted.defaultValue
val initialValue = defaultValueFlow?.value

val mergedFlow: Flow<T?> = if (defaultValueFlow != null) {
merge(
getFromStorage(entry)
.filter { it != null },
defaultValueFlow
)
} else {
getFromStorage(entry)
}
val defaultValue: Flow<T> = editableCasted.defaultValue

return mergedFlow.stateIn(
scope = CoroutineScope(Dispatchers.Default),
started = SharingStarted.Lazily,
initialValue = initialValue
)
return isOverriden(entry)
.flatMapMerge { overriden ->
when (overriden) {
true -> getFromStorage(entry)
else -> defaultValue
}
}
}

private fun isOverriden(entry: TweakEntry<*>): Flow<Boolean> = tweaksDataStore.data
.map { preferences -> preferences[buildIsOverridenKey(entry)] ?: OVERRIDEN_DEFAULT_VALUE }

private fun <T> getFromStorage(entry: TweakEntry<T>) =
tweaksDataStore.data
.map { preferences -> preferences[buildKey(entry)] }
Expand All @@ -91,8 +87,10 @@ class TweaksBusinessLogic @Inject constructor(
tweaksDataStore.edit {
if (value != null) {
it[buildKey(entry)] = value
it[buildIsOverridenKey(entry)] = true
} else {
it.remove(buildKey(entry))
it[buildIsOverridenKey(entry)] = false
}
}
}
Expand All @@ -105,6 +103,7 @@ class TweaksBusinessLogic @Inject constructor(
suspend fun <T> clearValue(entry: TweakEntry<T>) {
tweaksDataStore.edit {
it.remove(buildKey(entry))
it.remove(buildIsOverridenKey(entry))
}
}

Expand All @@ -123,4 +122,11 @@ class TweaksBusinessLogic @Inject constructor(
is ButtonTweakEntry -> throw java.lang.IllegalStateException("Buttons doesn't have keys")
is RouteButtonTweakEntry -> throw java.lang.IllegalStateException("Buttons doesn't have keys")
}

private fun buildIsOverridenKey(entry: TweakEntry<*>): Preferences.Key<Boolean> =
booleanPreferencesKey("${entry.key}.TweakOverriden")

companion object {
private const val OVERRIDEN_DEFAULT_VALUE = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fun ReadOnlyStringTweakEntryBody(
tweakEntry = entry,
onClick = {
Toast
.makeText(context, "Current value is $entry.", Toast.LENGTH_LONG)
.makeText(context, "${entry.key} = $value", Toast.LENGTH_LONG)
.show()
}) {
Text(
Expand Down
Loading

0 comments on commit ab23763

Please sign in to comment.