diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dbf214940..2c326d051 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,6 @@ buildTools = "34.0.0" agp = "8.6.0" androidXCore = "1.6.0" androidXLifecycle = "2.8.7" -compose = "1.7.7" -composeCompiler = "1.5.10" -composeMaterial3 = "1.3.1" detekt = "1.19.0" kotlin = "1.9.22" ktlint-plugin = "11.1.0" @@ -56,14 +53,6 @@ androidx-preference = { module = "androidx.preference:preference-ktx", version = androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.2.1" } androidx-swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" } -compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } -compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "composeMaterial3" } -compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } -compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } -compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } -compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } -constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version = "1.0.1" } - datastore-preferences = { module = "androidx.datastore:datastore-preferences", version = "1.0.0" } google-material = { module = "com.google.android.material:material", version = "1.6.1" } diff --git a/pluto-plugins/plugins/datastore/lib/build.gradle.kts b/pluto-plugins/plugins/datastore/lib/build.gradle.kts index f043022db..88be0581f 100644 --- a/pluto-plugins/plugins/datastore/lib/build.gradle.kts +++ b/pluto-plugins/plugins/datastore/lib/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) alias(libs.plugins.maven.publish) + alias(libs.plugins.ksp) } val version = Versioning.loadVersioningData() @@ -15,17 +16,12 @@ android { namespace = "com.pluto.plugins.datastore.pref" resourcePrefix = "pluto_dts___" - composeOptions { - kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() - } - compileSdk = libs.versions.compileSdk.get().toInt() buildToolsVersion = libs.versions.buildTools.get() buildFeatures { buildConfig = true viewBinding = true - compose = true } defaultConfig { @@ -102,13 +98,8 @@ dependencies { implementation(project(":pluto-plugins:base:lib")) implementation(libs.androidx.core) - implementation(libs.compose.material3) - implementation(libs.compose.foundation) - implementation(libs.compose.runtime) - implementation(libs.compose.ui) - implementation(libs.compose.ui.tooling) - implementation(libs.compose.ui.tooling.preview) - implementation(libs.constraintlayout.compose) - implementation(libs.datastore.preferences) + + implementation(libs.moshi) + ksp(libs.moshi.codegen) } diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/BaseFragment.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/BaseFragment.kt new file mode 100644 index 000000000..1aa50dc07 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/BaseFragment.kt @@ -0,0 +1,5 @@ +package com.pluto.plugins.datastore.pref + +import androidx.fragment.app.Fragment + +internal class BaseFragment : Fragment(R.layout.pluto_dts___fragment_base) diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastorePreferencesPlugin.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastorePreferencesPlugin.kt index a6d426d20..d4ecd5216 100644 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastorePreferencesPlugin.kt +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastorePreferencesPlugin.kt @@ -4,7 +4,6 @@ import androidx.fragment.app.Fragment import com.pluto.plugin.DeveloperDetails import com.pluto.plugin.Plugin import com.pluto.plugin.PluginConfiguration -import com.pluto.plugins.datastore.pref.internal.BaseFragment class PlutoDatastorePreferencesPlugin() : Plugin(ID) { diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastoreWatcher.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastoreWatcher.kt index fe3f7aecb..52eae9caa 100644 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastoreWatcher.kt +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/PlutoDatastoreWatcher.kt @@ -1,7 +1,9 @@ package com.pluto.plugins.datastore.pref +import androidx.annotation.Keep import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences +import com.pluto.utilities.selector.SelectorOption import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update @@ -27,6 +29,13 @@ object PlutoDatastoreWatcher { } } } + + internal fun getSource(name: String): PreferenceHolder { + return sources.value.toList().first { it.name == name } + } } -internal data class PreferenceHolder(val name: String, val preferences: DataStore) +@Keep +internal data class PreferenceHolder(val name: String, val preferences: DataStore) : SelectorOption() { + override fun displayText(): CharSequence = name +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/Session.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/Session.kt new file mode 100644 index 000000000..62c672477 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/Session.kt @@ -0,0 +1,5 @@ +package com.pluto.plugins.datastore.pref + +internal object Session { + var searchText: String? = null +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/BaseFragment.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/BaseFragment.kt deleted file mode 100644 index 30c1d79c7..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/BaseFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.core.graphics.Insets -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels -import com.pluto.plugins.datastore.pref.internal.compose.FilterView -import com.pluto.plugins.datastore.pref.internal.compose.MainComposable -import kotlin.math.max -import kotlinx.coroutines.flow.update - -internal class BaseFragment : Fragment() { - - private val viewModel by viewModels() - private val insets = mutableStateOf(Insets.NONE) - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ) = ComposeView(context = requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow) - setContent { - MaterialTheme { - Box( - Modifier - .fillMaxWidth() - .fillMaxHeight() - ) { - val state = viewModel.output.collectAsState(initial = null) - state.value?.let { - MainComposable( - data = it, - insets = insets, - onExit = { activity?.finish() }, - onFilterClick = { viewModel.showFilterView.update { true } }, - updateValue = viewModel.updateValue - ) - } - FilterView( - showFilterState = viewModel.showFilterView, - filterState = viewModel.filteredPref, - insets = insets, - ) - } - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - ViewCompat.setOnApplyWindowInsetsListener(view) { _, windowInsets -> - val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) - val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime()) - insets.value = Insets.of( - 0, - max(systemBarsInsets.top, imeInsets.top), - 0, - max(systemBarsInsets.bottom, imeInsets.bottom) - ) - windowInsets - } - } -} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/BaseViewModel.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/BaseViewModel.kt deleted file mode 100644 index 8f1a09a4e..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/BaseViewModel.kt +++ /dev/null @@ -1,98 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal - -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.datastore.preferences.core.booleanPreferencesKey -import androidx.datastore.preferences.core.doublePreferencesKey -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.floatPreferencesKey -import androidx.datastore.preferences.core.longPreferencesKey -import androidx.datastore.preferences.core.stringPreferencesKey -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.pluto.plugins.datastore.pref.PlutoDatastoreWatcher -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.flattenMerge -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch - -@OptIn(FlowPreview::class) -internal class BaseViewModel : ViewModel() { - - internal val output = MutableStateFlow>(listOf()) - internal val filteredPref = MutableStateFlow>(mapOf()) - internal val showFilterView: MutableStateFlow = MutableStateFlow(false) - private val expandedMap = mutableMapOf>() - - init { - viewModelScope.launch { - PlutoDatastoreWatcher.sources.map { list -> - filteredPref.value = list.associate { - it.name to (filteredPref.value[it.name] ?: true) - } - list.map { prefHolder -> - prefHolder.preferences.data.map { pref -> - pref to prefHolder.name - } - } - }.map { listFlows -> - combine( - flows = listFlows, - transform = { listPreferences -> - listPreferences.map { namePrefPair -> - PrefUiModel( - name = namePrefPair.second, - data = namePrefPair.first.asMap().map { entry -> - PrefElement( - key = entry.key.toString(), - value = entry.value.toString(), - type = Type.type(entry.value), - prefName = namePrefPair.second - ) - }, - isExpanded = expandedMap.getOrPut(namePrefPair.second) { - mutableStateOf(false) - } - ) - } - } - ) - }.flattenMerge() - .combine(filteredPref) { prefList, filterMap -> - prefList.filter { - filterMap[it.name] ?: true - } - }.collect { list -> - output.value = list - } - } - } - - val updateValue: (PrefElement, String) -> Unit = { preferenceElement, value -> - viewModelScope.launch { - val preferences = PlutoDatastoreWatcher.sources.value.find { - it.name == preferenceElement.prefName - }?.preferences - preferences?.edit { preference -> - when { - preferenceElement.type == Type.TypeBoolean && value.toBooleanStrictOrNull() != null -> - preference[booleanPreferencesKey(preferenceElement.key)] = value.toBoolean() - preferenceElement.type == Type.TypeDouble && value.toDoubleOrNull() != null -> - preference[doublePreferencesKey(preferenceElement.key)] = value.toDouble() - preferenceElement.type == Type.TypeFloat && value.toFloatOrNull() != null -> - preference[floatPreferencesKey(preferenceElement.key)] = value.toFloat() - preferenceElement.type == Type.TypeLong && value.toLongOrNull() != null -> - preference[longPreferencesKey(preferenceElement.key)] = value.toLong() - preferenceElement.type == Type.TypeString -> - preference[stringPreferencesKey(preferenceElement.key)] = value - else -> { - // show some error - // add validation before sending data here - } - } - } - } - } -} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/DataModels.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/DataModels.kt deleted file mode 100644 index d1f744166..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/DataModels.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal - -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf - -data class PrefUiModel( - val name: String, - val data: List, - val isExpanded: MutableState = mutableStateOf(true) -) - -data class PrefElement( - val prefName: String, - val key: String, - val value: String, - val type: Type -) - -sealed class Type(val displayText: String) { - - object TypeString : Type("string") - object TypeBoolean : Type("boolean") - object TypeDouble : Type("double") - object TypeFloat : Type("float") - object TypeLong : Type("long") - object TypeUnknown : Type("unknown") - - companion object { - fun type(obj: K) = when (obj) { - is String -> TypeString - is Boolean -> TypeBoolean - is Double -> TypeDouble - is Long -> TypeLong - is Float -> TypeFloat - else -> TypeUnknown - } - } -} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/DatastorePrefUtils.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/DatastorePrefUtils.kt new file mode 100644 index 000000000..6b41d5500 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/DatastorePrefUtils.kt @@ -0,0 +1,38 @@ +package com.pluto.plugins.datastore.pref.internal + +import android.content.Context +import com.pluto.plugins.datastore.pref.PlutoDatastoreWatcher +import com.pluto.plugins.datastore.pref.PreferenceHolder +import com.pluto.utilities.list.ListItem +import com.pluto.utilities.views.keyvalue.KeyValuePairEditMetaData +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Types + +@SuppressWarnings("UseDataClass") +internal class DatastorePrefUtils(context: Context) { + + private val preferences: Preferences = Preferences(context) + private val moshi: Moshi = Moshi.Builder().build() + private val moshiAdapter: JsonAdapter?> = moshi.adapter(Types.newParameterizedType(List::class.java, String::class.java)) + + internal var selectedPreferenceFiles: List = arrayListOf() + get() { + return preferences.selectedPreferenceFiles?.let { + moshiAdapter.fromJson(it)?.map { label -> PlutoDatastoreWatcher.getSource(label) } + } ?: run { + selectedPreferenceFiles = PlutoDatastoreWatcher.sources.value.toList() + selectedPreferenceFiles + } + } + set(value) { + preferences.selectedPreferenceFiles = moshiAdapter.toJson(value.map { it.name }) + field = value + } +} + +internal data class DatastorePrefKeyValuePair( + val key: String, + val value: Any?, + val prefLabel: String? +) : ListItem(), KeyValuePairEditMetaData diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/EditProcessor.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/EditProcessor.kt new file mode 100644 index 000000000..b85a501e4 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/EditProcessor.kt @@ -0,0 +1,35 @@ +package com.pluto.plugins.datastore.pref.internal + +import com.pluto.utilities.views.keyvalue.KeyValuePairEditInputType +import com.pluto.utilities.views.keyvalue.KeyValuePairEditRequest + +internal fun DatastorePrefKeyValuePair.toEditorData(): KeyValuePairEditRequest { + return KeyValuePairEditRequest( + key = key, + value = value?.toString(), + hint = when (value) { + is Int, is Long -> "12345" + is Boolean -> "true / false" + is Float, is Double -> "1234.89" + else -> "abcde 123" + }, + inputType = when (value) { + is Int, is Long -> KeyValuePairEditInputType.Integer + is Float, is Double -> KeyValuePairEditInputType.Float + is Boolean -> KeyValuePairEditInputType.Boolean + else -> KeyValuePairEditInputType.String + }, + metaData = this + ) +} + +internal fun DatastorePrefKeyValuePair.fromEditorData(text: String): Any { + return when (value) { + is Int -> text.toInt() + is Double -> text.toDouble() + is Long -> text.toLong() + is Float -> text.toFloat() + is Boolean -> text.toBoolean() + else -> text + } +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/Preferences.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/Preferences.kt new file mode 100644 index 000000000..ffb0a8a23 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/Preferences.kt @@ -0,0 +1,18 @@ +package com.pluto.plugins.datastore.pref.internal + +import android.content.Context + +internal class Preferences(context: Context) { + + private val settingsPrefs by lazy { context.preferences("_pluto_datastore_pref_settings") } + + internal var selectedPreferenceFiles: String? + get() = settingsPrefs.getString(SELECTED_PREF_FILE, null) + set(value) = settingsPrefs.edit().putString(SELECTED_PREF_FILE, value).apply() + + companion object { + private const val SELECTED_PREF_FILE = "selected_datastore_pref_file" + } +} + +private fun Context.preferences(name: String, mode: Int = Context.MODE_PRIVATE) = getSharedPreferences(name, mode) diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/DisplayComponents.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/DisplayComponents.kt deleted file mode 100644 index 49bf0a40c..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/DisplayComponents.kt +++ /dev/null @@ -1,294 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal.compose - -import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.OutlinedTextFieldDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.focusTarget -import androidx.compose.ui.focus.onFocusEvent -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.TextRange -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.TextFieldValue -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.pluto.plugins.datastore.pref.R -import com.pluto.plugins.datastore.pref.internal.PrefElement -import com.pluto.plugins.datastore.pref.internal.Type - -@Composable -@SuppressWarnings("LongMethod") -internal fun PrefListItem( - element: PrefElement, - modifier: Modifier = Modifier, - editableItem: MutableState = mutableStateOf(null), - updateValue: (PrefElement, String) -> Unit = { _, _ -> }, - onFocus: () -> Unit = {}, -) { - val isEditing = - editableItem.value?.name == element.prefName && editableItem.value?.key == element.key - val newValue = remember { - mutableStateOf(TextFieldValue(element.value, TextRange(element.value.length))) - } - Column( - modifier = modifier - .animateContentSize() - .clickable(enabled = !isEditing) { - editableItem.value = PreferenceKey(element.prefName, element.key) - newValue.value = TextFieldValue( - element.value, - TextRange(element.value.length) - ) - } - .padding(top = 8.dp) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = element.key, - modifier = Modifier - .padding(horizontal = 16.dp) - .weight(1f), - color = colorResource(id = com.pluto.plugin.R.color.pluto___text_dark_40), - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli)), - fontSize = 12.sp - ) - ) - Text( - text = element.type.displayText, - modifier = Modifier - .padding(horizontal = 16.dp) - .background( - color = colorResource(id = com.pluto.plugin.R.color.pluto___dull_green_08), - shape = RoundedCornerShape(10.dp) - ) - .padding(bottom = 2.dp, start = 8.dp, end = 8.dp), - color = colorResource(id = com.pluto.plugin.R.color.pluto___dull_green), - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli_semibold)), - fontSize = 10.sp - ) - ) - } - Element( - element = element, - updateValue = updateValue, - isEditing = isEditing, - newValue = newValue, - editableItem = editableItem, - onFocus = onFocus - ) - Divider(Modifier.padding(top = 8.dp), color = colorResource(id = com.pluto.plugin.R.color.pluto___dark_05)) - } -} - -@Composable -private fun Element( - element: PrefElement, - updateValue: (PrefElement, String) -> Unit, - isEditing: Boolean = false, - newValue: MutableState = mutableStateOf(TextFieldValue("")), - editableItem: MutableState = mutableStateOf(null), - onFocus: () -> Unit, -) { - val focusRequester = remember { FocusRequester() } - if (isEditing) { - EditableField( - newValue, - focusRequester, - onFocus, - element, - updateValue, - editableItem - ) - } else { - Text( - text = element.value, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 24.dp), - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli)) - ) - ) - } - - LaunchedEffect(isEditing) { - if (isEditing) { - focusRequester.requestFocus() - } - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -private fun EditableField( - newValue: MutableState = mutableStateOf(TextFieldValue("")), - focusRequester: FocusRequester, - onFocus: () -> Unit = {}, - element: PrefElement, - updateValue: (PrefElement, String) -> Unit, - editableItem: MutableState = mutableStateOf(null) -) { - val focusManager = LocalFocusManager.current - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 8.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - OutlinedTextField( - value = newValue.value, - modifier = Modifier - .focusTarget() - .focusRequester(focusRequester) - .onFocusEvent { - if (it.isFocused) { - onFocus() - } - } - .weight(1f), - onValueChange = { input -> - newValue.value = input - }, - colors = OutlinedTextFieldDefaults.colors( - focusedBorderColor = colorResource(id = com.pluto.plugin.R.color.pluto___text_dark_60), - unfocusedBorderColor = colorResource(id = com.pluto.plugin.R.color.pluto___text_dark_20) - ), - keyboardOptions = KeyboardOptions.Default.copy( -// autoCorrect = false, - keyboardType = when (element.type) { - Type.TypeString, Type.TypeBoolean -> KeyboardType.Text - Type.TypeLong, Type.TypeFloat -> KeyboardType.Number - else -> KeyboardType.Text - }, - imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions( - onDone = { - focusManager.clearFocus() - updateValue(element, newValue.value.text) - editableItem.value = null - } - ) - ) - ElementCta( - onSave = { - updateValue(element, newValue.value.text) - editableItem.value = null - }, - onCancel = { - editableItem.value = null - newValue.value = TextFieldValue( - element.value, - TextRange(element.value.length) - ) - } - ) - } -} - -@Composable -private fun ElementCta( - onSave: () -> Unit, - onCancel: () -> Unit, -) { - Column( - verticalArrangement = Arrangement.SpaceBetween, - modifier = Modifier.fillMaxHeight() - ) { - Image( - modifier = Modifier - .clickable(onClick = onCancel) - .size(width = 48.dp, height = 38.dp) - .padding(horizontal = 12.dp) - .padding(top = 10.dp, bottom = 4.dp), - painter = painterResource(id = R.drawable.pluto_dts___ic_clear), - contentDescription = "cancel" - ) - Image( - modifier = Modifier - .clickable(onClick = onSave) - .size(width = 48.dp, height = 38.dp) - .padding(horizontal = 12.dp) - .padding(top = 4.dp, bottom = 10.dp), - painter = painterResource(id = R.drawable.pluto_dts___ic_check), - contentDescription = "save", - colorFilter = ColorFilter.tint(color = colorResource(id = com.pluto.plugin.R.color.pluto___dull_green)) - ) - } -} - -@Composable -@Preview("normal item") -private fun PreviewListItem() { - LazyColumn { - item { - PrefListItem( - element = PrefElement( - "Preferences", - "key param", - "value of the key", - Type.TypeString - ), - modifier = Modifier.background(colorResource(id = com.pluto.plugin.R.color.pluto___white)) - ) - } - } -} - -@Composable -@Preview("very long item") -private fun PreviewLongContentListItem() { - LazyColumn { - item { - PrefListItem( - element = PrefElement( - "Preferences", - "VERY VERY VERY VERY VERY very very very very very very Loooong Key", - "VERY VERY VERY VERY VERY very very very very Loooong value", - Type.TypeBoolean - ), - modifier = Modifier.background(colorResource(id = com.pluto.plugin.R.color.pluto___white)) - ) - } - } -} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/FilterView.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/FilterView.kt deleted file mode 100644 index 8632e1fe7..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/FilterView.kt +++ /dev/null @@ -1,206 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal.compose - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.MutableTransitionState -import androidx.compose.animation.expandIn -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkOut -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentSize -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox -import androidx.compose.material3.CheckboxDefaults -import androidx.compose.material3.Divider -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.core.graphics.Insets -import com.pluto.plugins.datastore.pref.R -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.update - -const val ColumnWidthPercentage = .8f - -@Composable -internal fun FilterView( - showFilterState: MutableStateFlow, - filterState: MutableStateFlow>, - insets: MutableState -) { - val visibleState = remember { MutableTransitionState(false) } - visibleState.targetState = showFilterState.collectAsState().value - Box( - Modifier - .fillMaxWidth() - .fillMaxHeight() - ) { - FilterBackground(visibleState, showFilterState) - FilterItem( - visibleState, filterState, - Modifier - .padding( - top = with(LocalDensity.current) { - insets.value.top.toDp() - } + 16.dp, - end = 12.dp - ) - .align(Alignment.TopEnd) - ) - } -} - -@OptIn(ExperimentalMaterial3Api::class) -@SuppressWarnings("LongMethod") -@Composable -private fun FilterItem( - visibleState: MutableTransitionState, - filterState: MutableStateFlow>, - modifier: Modifier, -) { - AnimatedVisibility( - visibleState = visibleState, - enter = expandIn(expandFrom = Alignment.TopEnd), - exit = shrinkOut(shrinkTowards = Alignment.TopEnd), - modifier = modifier - .wrapContentSize() - ) { - Column( - Modifier - .borderBackground( - bgColor = colorResource(id = com.pluto.plugin.R.color.pluto___white), - borderColor = colorResource(id = com.pluto.plugin.R.color.pluto___white), - shape = RoundedCornerShape(4.dp) - ) - .padding(bottom = 4.dp) - ) { - Column( - Modifier - .fillMaxWidth(ColumnWidthPercentage) - .borderBackground( - bgColor = colorResource(id = com.pluto.plugin.R.color.pluto___section_color), - borderColor = colorResource(id = com.pluto.plugin.R.color.pluto___section_color), - shape = RoundedCornerShape(topStart = 4.dp, topEnd = 4.dp) - ) - .padding(vertical = 12.dp, horizontal = 16.dp) - ) { - Text( - text = "Preferences", - color = colorResource(id = com.pluto.plugin.R.color.pluto___text_dark_80), - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli)), - fontSize = 15.sp - ) - ) - } - filterState.collectAsState().value.entries.forEachIndexed { index, entry -> - Column( - Modifier - .clickable { - filterState.update { srcMap -> - mutableMapOf().also { - it.putAll(srcMap) - it[entry.key] = !(it[entry.key] ?: true) - } - } - } - .fillMaxWidth(ColumnWidthPercentage) - ) { - if (index != 0) { - Divider(color = colorResource(id = com.pluto.plugin.R.color.pluto___dark_05)) - } - Row( - verticalAlignment = Alignment.CenterVertically, -// modifier = Modifier.padding(horizontal = 8.dp) - ) { - Checkbox( - checked = entry.value, - onCheckedChange = { isChecked -> - filterState.update { srcMap -> - mutableMapOf().also { - it.putAll(srcMap) - it[entry.key] = isChecked - } - } - }, - colors = CheckboxDefaults.colors( - checkedColor = colorResource(id = com.pluto.plugin.R.color.pluto___blue), - uncheckedColor = colorResource(id = com.pluto.plugin.R.color.pluto___dark_40) - ) - ) - Text( - text = entry.key, - color = colorResource(id = com.pluto.plugin.R.color.pluto___text_dark_80), - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli_semibold)), - fontSize = 15.sp - ) - ) - } - } - } - } - } -} - -private fun Modifier.borderBackground( - bgColor: Color, - borderColor: Color, - shape: Shape = RectangleShape, -): Modifier { - return background( - color = bgColor, - shape = shape - ).border( - width = 1.dp, - color = borderColor, - shape = shape - ) -} - -@Composable -private fun FilterBackground( - visibleState: MutableTransitionState, - showFilterState: MutableStateFlow -) { - AnimatedVisibility( - visibleState = visibleState, - enter = fadeIn(), - exit = fadeOut(), - ) { - Box( - Modifier - .fillMaxWidth() - .fillMaxHeight() - .background(colorResource(id = com.pluto.plugin.R.color.pluto___dark_80)) - .clickable { - showFilterState.update { - false - } - } - ) - } -} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/MainComposable.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/MainComposable.kt deleted file mode 100644 index b79c3e796..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/MainComposable.kt +++ /dev/null @@ -1,108 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal.compose - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Divider -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp -import androidx.core.graphics.Insets -import com.pluto.plugins.datastore.pref.R -import com.pluto.plugins.datastore.pref.internal.PrefElement -import com.pluto.plugins.datastore.pref.internal.PrefUiModel -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -@Composable -internal fun MainComposable( - data: List, - insets: MutableState = mutableStateOf(Insets.NONE), - onExit: () -> Unit, - onFilterClick: () -> Unit, - updateValue: (PrefElement, String) -> Unit = { _, _ -> }, -) { - val editableItem = remember { - mutableStateOf(null) - } - val scrollState = rememberLazyListState() - val scope = rememberCoroutineScope() - Column( - Modifier - .background(colorResource(id = com.pluto.plugin.R.color.pluto___white)) - .padding( - top = with(LocalDensity.current) { - insets.value.top.toDp() - } - ) - ) { - ToolBar(onExit = onExit, onFilterClick = onFilterClick) - Divider(color = colorResource(id = com.pluto.plugin.R.color.pluto___dark_05)) - val density = LocalDensity.current - LazyColumn( - modifier = Modifier - .fillMaxHeight(), - state = scrollState, - contentPadding = PaddingValues( - bottom = with(LocalDensity.current) { - insets.value.bottom.toDp() - } - ) - ) { - populateList(data, editableItem, updateValue, scope, scrollState, density) - } - } -} - -private fun LazyListScope.populateList( - data: List, - editableItem: MutableState = mutableStateOf(null), - updateValue: (PrefElement, String) -> Unit = { _, _ -> }, - scope: CoroutineScope, - scrollState: LazyListState, - density: Density -) { - data.forEach { uiModel -> - dataStorePrefItems( - uiModel, editableItem, updateValue, - onFocus = { key -> - scope.launch { - delay(FocusDelay) - val itemInfo = - scrollState.layoutInfo.visibleItemsInfo.firstOrNull { itemInfo -> - itemInfo.key == key - } - if (itemInfo != null) { - scrollState.animateScrollToItem( - itemInfo.index, - -with(density) { - 100.dp.roundToPx() - } - ) - } - } - } - ) - } -} - -const val FocusDelay = 100L - -internal data class PreferenceKey( - val name: String, - val key: String, -) diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/PrefListKtx.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/PrefListKtx.kt deleted file mode 100644 index 02981fc3d..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/PrefListKtx.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal.compose - -import androidx.compose.animation.core.Animatable -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListScope -import androidx.compose.material3.Divider -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.pluto.plugins.datastore.pref.R -import com.pluto.plugins.datastore.pref.internal.PrefElement -import com.pluto.plugins.datastore.pref.internal.PrefUiModel -import com.pluto.plugins.datastore.pref.internal.Type - -@OptIn(ExperimentalFoundationApi::class) // for stickyHeader -@SuppressWarnings("LongMethod") -internal fun LazyListScope.dataStorePrefItems( - data: PrefUiModel, - editableItem: MutableState = mutableStateOf(null), - updateValue: (PrefElement, String) -> Unit = { _, _ -> }, - onFocus: (String) -> Unit = {} -) { - stickyHeader(data.name + "title") { - Column( - modifier = Modifier - .clickable { - data.isExpanded.value = !data.isExpanded.value - } - .animateItemPlacement() - .background(colorResource(id = com.pluto.plugin.R.color.pluto___section_color)) - ) { - Row( - horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .padding(top = 4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = data.name, - modifier = Modifier.padding( - vertical = 8.dp - ), - color = colorResource(id = com.pluto.plugin.R.color.pluto___text_dark_80), - letterSpacing = 1.2.sp, - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli_semibold)), - fontSize = 16.sp - ) - ) - - val degrees = remember { Animatable(1f) } - - LaunchedEffect(data.isExpanded.value) { - degrees.animateTo(if (data.isExpanded.value) FlipDegree else 0f) - } - - Image( - painter = painterResource(id = R.drawable.pluto_dts___ic_expand), - contentDescription = "expand", - Modifier - .graphicsLayer { - rotationZ = degrees.value - } - ) - } - Divider(Modifier.padding(top = 4.dp), color = colorResource(id = com.pluto.plugin.R.color.pluto___dark_05)) - } - } - if (data.isExpanded.value) { - data.data.forEach { element -> - item(data.name + element.key) { - PrefListItem( - element = element, - editableItem = editableItem, - updateValue = updateValue, - onFocus = { - onFocus(data.name + element.key) - }, - modifier = Modifier - .animateItemPlacement() - ) - } - } - } -} - -@Preview -@Composable -private fun DataStorePrefItemPreview() { - val prefName = "Preferences" - LazyColumn( - modifier = Modifier - .wrapContentHeight(Alignment.Top) - .background(colorResource(id = com.pluto.plugin.R.color.pluto___white)) - ) { - dataStorePrefItems( - PrefUiModel( - prefName, - listOf( - PrefElement(prefName, "key", "value", Type.TypeString), - PrefElement(prefName, "key1", "value1", Type.TypeString), - PrefElement(prefName, "key2", "value2", Type.TypeString), - PrefElement(prefName, "key3", "value3", Type.TypeString), - PrefElement( - prefName, - "VERY VERY VERY VERY VERY very very very very very very Loooong Key", - "VERY VERY VERY VERY VERY very very very very Loooong value", - Type.TypeString - ), - PrefElement(prefName, "key5", "value5", Type.TypeString), - ) - ), - ) - } -} - -private const val FlipDegree = 180f diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/Toolbar.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/Toolbar.kt deleted file mode 100644 index 16a3c0447..000000000 --- a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/compose/Toolbar.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.pluto.plugins.datastore.pref.internal.compose - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.colorResource -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.Font -import androidx.compose.ui.text.font.FontFamily -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.constraintlayout.compose.ConstraintLayout -import com.pluto.plugins.datastore.pref.R - -@Composable -fun ToolBar( - modifier: Modifier = Modifier, - onExit: () -> Unit, - onFilterClick: () -> Unit, -) { - Row( - modifier = modifier - .fillMaxWidth() - .background(colorResource(id = com.pluto.plugin.R.color.pluto___dark)), - horizontalArrangement = Arrangement.SpaceBetween - ) { - ConstraintLayout( - modifier = modifier.fillMaxWidth() - ) { - val (close, title, filer) = createRefs() - Image( - painter = painterResource(id = R.drawable.pluto_dts___ic_close), - contentDescription = "close", - modifier = Modifier - .constrainAs(close) { - top.linkTo(title.top) - bottom.linkTo(title.bottom) - start.linkTo(parent.start) - } - .clickable { onExit() } - .padding(horizontal = 12.dp) - ) - Text( - stringResource(id = R.string.pluto_dts___plugin_name), - color = colorResource(id = com.pluto.plugin.R.color.pluto___white), - modifier = Modifier - .constrainAs(title) { - top.linkTo(parent.top) - bottom.linkTo(parent.bottom) - start.linkTo(close.end) - } - .padding(vertical = 16.dp), - style = TextStyle( - fontFamily = FontFamily(Font(com.pluto.plugin.R.font.muli_semibold)), - fontSize = 16.sp - ) - ) - Image( - painter = painterResource(id = R.drawable.pluto_dts___ic_filter), - contentDescription = "filter", - modifier = Modifier - .constrainAs(filer) { - top.linkTo(title.top) - bottom.linkTo(title.bottom) - end.linkTo(parent.end) - } - .clickable { onFilterClick() } - .padding(horizontal = 12.dp) - ) - } - } -} - -@Composable -@Preview -private fun PreviewToolbar() { - ToolBar(Modifier.background(colorResource(id = com.pluto.plugin.R.color.pluto___dark)), {}, {}) -} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/DatastorePrefAdapter.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/DatastorePrefAdapter.kt new file mode 100644 index 000000000..7217a39ed --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/DatastorePrefAdapter.kt @@ -0,0 +1,28 @@ +package com.pluto.plugins.datastore.pref.internal.ui + +import android.view.ViewGroup +import com.pluto.plugins.datastore.pref.internal.DatastorePrefKeyValuePair +import com.pluto.utilities.list.BaseAdapter +import com.pluto.utilities.list.DiffAwareHolder +import com.pluto.utilities.list.ListItem + +internal class DatastorePrefAdapter(private val listener: OnActionListener) : BaseAdapter() { + + override fun getItemViewType(item: ListItem): Int? { + return when (item) { + is DatastorePrefKeyValuePair -> ITEM_TYPE_PAIR + else -> null + } + } + + override fun onViewHolderCreated(parent: ViewGroup, viewType: Int): DiffAwareHolder? { + return when (viewType) { + ITEM_TYPE_PAIR -> KeyValueItemHolder(parent, listener) + else -> null + } + } + + companion object { + const val ITEM_TYPE_PAIR = 1001 + } +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/DatastorePrefViewModel.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/DatastorePrefViewModel.kt new file mode 100644 index 000000000..ed77e9e5b --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/DatastorePrefViewModel.kt @@ -0,0 +1,72 @@ +package com.pluto.plugins.datastore.pref.internal.ui + +import android.app.Application +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.doublePreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.floatPreferencesKey +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.pluto.plugins.datastore.pref.PlutoDatastoreWatcher +import com.pluto.plugins.datastore.pref.PreferenceHolder +import com.pluto.plugins.datastore.pref.internal.DatastorePrefKeyValuePair +import com.pluto.plugins.datastore.pref.internal.DatastorePrefUtils +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +internal class DatastorePrefViewModel(application: Application) : AndroidViewModel(application) { + + val preferenceList: LiveData> + get() = _preferences + private val _preferences = MutableLiveData>() + + private val sharePrefUtils = DatastorePrefUtils(application.applicationContext) + + fun getSelectedPrefFiles(): List = sharePrefUtils.selectedPreferenceFiles + + fun setSelectedPrefFiles(files: List) { + sharePrefUtils.selectedPreferenceFiles = files + refresh() + } + + fun setPrefData(pair: DatastorePrefKeyValuePair, value: Any) { + viewModelScope.launch { + val preferences = PlutoDatastoreWatcher.sources.value.find { + it.name == pair.prefLabel + }?.preferences + + preferences?.edit { preference -> + when (pair.value) { + is Boolean -> preference[booleanPreferencesKey(pair.key)] = value as Boolean + is Double -> preference[doublePreferencesKey(pair.key)] = value as Double + is Int -> preference[intPreferencesKey(pair.key)] = value as Int + is Float -> preference[floatPreferencesKey(pair.key)] = value as Float + is Long -> preference[longPreferencesKey(pair.key)] = value as Long + is String -> preference[stringPreferencesKey(pair.key)] = value as String + else -> { + // show some error + // add validation before sending data here + } + } + } + refresh() + } + } + + fun refresh() { + viewModelScope.launch { + val list = arrayListOf() + getSelectedPrefFiles().forEach { + it.preferences.data.first().asMap().map { (key, value) -> + list.add(DatastorePrefKeyValuePair(key.name, value, it.name)) + } + } + _preferences.postValue(list) + } + } +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/KeyValueItemHolder.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/KeyValueItemHolder.kt new file mode 100644 index 000000000..8e30e50e0 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/KeyValueItemHolder.kt @@ -0,0 +1,55 @@ +package com.pluto.plugins.datastore.pref.internal.ui + +import android.view.ViewGroup +import com.pluto.plugins.datastore.pref.R +import com.pluto.plugins.datastore.pref.databinding.PlutoDtsItemSharedPrefKeyValueBinding +import com.pluto.plugins.datastore.pref.internal.DatastorePrefKeyValuePair +import com.pluto.utilities.extensions.color +import com.pluto.utilities.extensions.inflate +import com.pluto.utilities.list.DiffAwareAdapter +import com.pluto.utilities.list.DiffAwareHolder +import com.pluto.utilities.list.ListItem +import com.pluto.utilities.setOnDebounceClickListener +import com.pluto.utilities.spannable.createSpan + +internal class KeyValueItemHolder( + parent: ViewGroup, + actionListener: DiffAwareAdapter.OnActionListener +) : DiffAwareHolder(parent.inflate(R.layout.pluto_dts___item_shared_pref_key_value), actionListener) { + + private val binding = PlutoDtsItemSharedPrefKeyValueBinding.bind(itemView) + private val key = binding.key + private val value = binding.value + private val file = binding.file + + override fun onBind(item: ListItem) { + if (item is DatastorePrefKeyValuePair) { + key.text = item.key + val fileName = item.prefLabel + file.text = if (fileName != null) { + if (fileName.length > MAX_FILENAME_LENGTH) { + "${fileName.substring(0, MAX_FILENAME_LENGTH - 2)}..." + } else { + fileName + } + } else { + itemView.context.createSpan { + append(fontColor(light(italic("null")), context.color(com.pluto.plugin.R.color.pluto___text_dark_40))) + } + } + item.value?.let { value.text = it.toString() } + + itemView.setOnDebounceClickListener { + onAction("click") + } + itemView.setOnLongClickListener { + onAction("long_click") + return@setOnLongClickListener true + } + } + } + + companion object { + const val MAX_FILENAME_LENGTH = 18 + } +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/ListFragment.kt b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/ListFragment.kt new file mode 100644 index 000000000..0f76f541a --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/java/com/pluto/plugins/datastore/pref/internal/ui/ListFragment.kt @@ -0,0 +1,139 @@ +package com.pluto.plugins.datastore.pref.internal.ui + +import android.os.Bundle +import android.view.View +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope +import com.pluto.plugin.share.Shareable +import com.pluto.plugin.share.lazyContentSharer +import com.pluto.plugins.datastore.pref.PlutoDatastoreWatcher +import com.pluto.plugins.datastore.pref.PreferenceHolder +import com.pluto.plugins.datastore.pref.R +import com.pluto.plugins.datastore.pref.Session +import com.pluto.plugins.datastore.pref.databinding.PlutoDtsFragmentListBinding +import com.pluto.plugins.datastore.pref.internal.DatastorePrefKeyValuePair +import com.pluto.plugins.datastore.pref.internal.fromEditorData +import com.pluto.plugins.datastore.pref.internal.toEditorData +import com.pluto.utilities.autoClearInitializer +import com.pluto.utilities.extensions.hideKeyboard +import com.pluto.utilities.extensions.linearLayoutManager +import com.pluto.utilities.list.BaseAdapter +import com.pluto.utilities.list.CustomItemDecorator +import com.pluto.utilities.list.DiffAwareAdapter +import com.pluto.utilities.list.DiffAwareHolder +import com.pluto.utilities.list.ListItem +import com.pluto.utilities.selector.lazyDataSelector +import com.pluto.utilities.setOnDebounceClickListener +import com.pluto.utilities.viewBinding +import com.pluto.utilities.views.keyvalue.KeyValuePairEditResult +import com.pluto.utilities.views.keyvalue.edit.KeyValuePairEditor +import com.pluto.utilities.views.keyvalue.edit.lazyKeyValuePairEditor + +internal class ListFragment : Fragment(R.layout.pluto_dts___fragment_list) { + private val binding by viewBinding(PlutoDtsFragmentListBinding::bind) + private val viewModel: DatastorePrefViewModel by activityViewModels() + private val keyValuePairEditor: KeyValuePairEditor by lazyKeyValuePairEditor() + private val prefAdapter: BaseAdapter by autoClearInitializer { + DatastorePrefAdapter(onActionListener) + } + private val contentSharer by lazyContentSharer() + private val dataSelector by lazyDataSelector() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.refresh() + + binding.list.apply { + adapter = prefAdapter + addItemDecoration(CustomItemDecorator(requireContext())) + } + + binding.search.doOnTextChanged { text, _, _, _ -> + viewLifecycleOwner.lifecycleScope.launchWhenResumed { + text?.toString()?.let { + Session.searchText = it + prefAdapter.list = filteredPrefs(it) + if (it.isEmpty()) { + binding.list.linearLayoutManager()?.scrollToPositionWithOffset(0, 0) + } + } + } + } + binding.filter.setOnDebounceClickListener { openFilterView() } + binding.search.setText(Session.searchText) + viewModel.preferenceList.removeObserver(sharedPrefObserver) + viewModel.preferenceList.observe(viewLifecycleOwner, sharedPrefObserver) + keyValuePairEditor.result.removeObserver(keyValuePairEditObserver) + keyValuePairEditor.result.observe(viewLifecycleOwner, keyValuePairEditObserver) + + binding.close.setOnDebounceClickListener { + activity?.finish() + } + } + + private fun openFilterView() { + dataSelector.selectMultiple( + title = getString(R.string.pluto_dts___datastore_pref_filter), + list = PlutoDatastoreWatcher.sources.value.toList(), + preSelected = viewModel.getSelectedPrefFiles() + ).observe(viewLifecycleOwner) { + val listOfSharePrefFiles = arrayListOf() + it.forEach { option -> + if (option is PreferenceHolder) { + listOfSharePrefFiles.add(option) + } + } + viewModel.setSelectedPrefFiles(listOfSharePrefFiles) + } + } + + private fun filteredPrefs(search: String): List { + var list = emptyList() + viewModel.preferenceList.value?.let { + list = it.filter { pref -> + pref.key.contains(search, true) || + pref.value.toString().contains(search, ignoreCase = true) + } + } + binding.noItemText.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE + return list + } + + private val keyValuePairEditObserver = Observer { + it.value?.let { value -> + if (it.metaData is DatastorePrefKeyValuePair) { + val pref: DatastorePrefKeyValuePair = it.metaData as DatastorePrefKeyValuePair + viewModel.setPrefData(pref, pref.fromEditorData(value)) + } + } + } + + private val sharedPrefObserver = Observer> { + prefAdapter.list = filteredPrefs(binding.search.text.toString()) + } + + private val onActionListener = object : DiffAwareAdapter.OnActionListener { + override fun onAction(action: String, data: ListItem, holder: DiffAwareHolder) { + if (data is DatastorePrefKeyValuePair) { + when (action) { + "click" -> activity?.let { + it.hideKeyboard(viewLifecycleOwner.lifecycleScope) { + keyValuePairEditor.edit(data.toEditorData()) + } + } + + "long_click" -> contentSharer.share( + Shareable( + content = "${data.key} : ${data.value}", + title = "Share Shared Preference", + fileName = "Preference data from Pluto" + ) + ) + } + } + } + } +} diff --git a/pluto-plugins/plugins/datastore/lib/src/main/res/drawable/pluto_dts___bg_shared_pref_file_badge.xml b/pluto-plugins/plugins/datastore/lib/src/main/res/drawable/pluto_dts___bg_shared_pref_file_badge.xml new file mode 100644 index 000000000..2e51c23a1 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/res/drawable/pluto_dts___bg_shared_pref_file_badge.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___fragment_base.xml b/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___fragment_base.xml new file mode 100644 index 000000000..84204aaa1 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___fragment_base.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___fragment_list.xml b/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___fragment_list.xml new file mode 100644 index 000000000..850fe4a2c --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___fragment_list.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___item_shared_pref_key_value.xml b/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___item_shared_pref_key_value.xml new file mode 100644 index 000000000..b475ea891 --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/res/layout/pluto_dts___item_shared_pref_key_value.xml @@ -0,0 +1,71 @@ + + + + + + + + + + \ No newline at end of file diff --git a/pluto-plugins/plugins/datastore/lib/src/main/res/navigation/pluto_dts___navigation.xml b/pluto-plugins/plugins/datastore/lib/src/main/res/navigation/pluto_dts___navigation.xml new file mode 100644 index 000000000..876f4842a --- /dev/null +++ b/pluto-plugins/plugins/datastore/lib/src/main/res/navigation/pluto_dts___navigation.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/pluto-plugins/plugins/datastore/lib/src/main/res/values/strings.xml b/pluto-plugins/plugins/datastore/lib/src/main/res/values/strings.xml index f3cc1c904..fb26cedc3 100644 --- a/pluto-plugins/plugins/datastore/lib/src/main/res/values/strings.xml +++ b/pluto-plugins/plugins/datastore/lib/src/main/res/values/strings.xml @@ -1,3 +1,6 @@ Datastore Preferences + Choose Datastore + No DataStore Preferences present.\nAdd a DataStore Preference or check Filter settings. + Search Preferences \ No newline at end of file diff --git a/sample/src/main/java/com/sampleapp/functions/datastore/DemoDatastorePrefFragment.kt b/sample/src/main/java/com/sampleapp/functions/datastore/DemoDatastorePrefFragment.kt index a473586e1..4e5341778 100644 --- a/sample/src/main/java/com/sampleapp/functions/datastore/DemoDatastorePrefFragment.kt +++ b/sample/src/main/java/com/sampleapp/functions/datastore/DemoDatastorePrefFragment.kt @@ -6,8 +6,10 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.doublePreferencesKey import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.floatPreferencesKey +import androidx.datastore.preferences.core.intPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore @@ -37,7 +39,6 @@ class DemoDatastorePrefFragment : Fragment(R.layout.fragment_demo_datastore_pref override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.button.setOnClickListener { initDataForDataStoreSample(requireContext()) } - initDataForDataStoreSample(requireContext()) } private fun initDataForDataStoreSample(context: Context) { @@ -52,6 +53,8 @@ class DemoDatastorePrefFragment : Fragment(R.layout.fragment_demo_datastore_pref it[stringPreferencesKey("session_uuid")] = "9522b353-e3a9-428c-9af6-338fd5e9f9d6" it[longPreferencesKey("session_duration")] = RANDOM_LONG it[floatPreferencesKey("pi_value")] = PI_VALUE + it[doublePreferencesKey("double_value")] = RANDOM_DOUBLE + it[intPreferencesKey("int_value")] = RANDOM_INT } } } @@ -59,6 +62,8 @@ class DemoDatastorePrefFragment : Fragment(R.layout.fragment_demo_datastore_pref companion object { const val RANDOM_LONG = 13_101_993L const val PI_VALUE = 3.141592653589793238462643383279502884197f + const val RANDOM_INT = 3 + const val RANDOM_DOUBLE = 3.14 const val APP_STATE_PREF_NAME = "app states" const val USER_STATE_PREF_NAME = "user states" }