Refactor FilterScreen dialog to Compose, animateItemPlacement
This commit is contained in:
1 parent
20b145bee5
commit
ccc6d004de
2 files changed
+118
-99
No files matched your search
@@ -1,8 +1,5 @@
|
|||||||
package com.hippo.ehviewer.ui.settings
|
package com.hippo.ehviewer.ui.settings
|
||||||
|
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.view.View
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -14,30 +11,43 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.Help
|
import androidx.compose.material.icons.automirrored.filled.Help
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.filled.FilterAlt
|
import androidx.compose.material.icons.filled.FilterAlt
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||||
|
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.currentRecomposeScope
|
import androidx.compose.runtime.currentRecomposeScope
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringArrayResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.hippo.ehviewer.R
|
import com.hippo.ehviewer.R
|
||||||
import com.hippo.ehviewer.client.EhFilter
|
import com.hippo.ehviewer.client.EhFilter
|
||||||
@@ -46,14 +56,12 @@ import com.hippo.ehviewer.client.EhFilter.remember
|
|||||||
import com.hippo.ehviewer.client.EhFilter.trigger
|
import com.hippo.ehviewer.client.EhFilter.trigger
|
||||||
import com.hippo.ehviewer.dao.Filter
|
import com.hippo.ehviewer.dao.Filter
|
||||||
import com.hippo.ehviewer.dao.FilterMode
|
import com.hippo.ehviewer.dao.FilterMode
|
||||||
import com.hippo.ehviewer.databinding.DialogAddFilterBinding
|
|
||||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||||
import com.hippo.ehviewer.ui.tools.Deferred
|
import com.hippo.ehviewer.ui.tools.Deferred
|
||||||
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
||||||
import com.ramcosta.composedestinations.annotation.Destination
|
import com.ramcosta.composedestinations.annotation.Destination
|
||||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
import kotlin.coroutines.resume
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import moe.tarsin.coroutines.groupByToObserved
|
import moe.tarsin.coroutines.groupByToObserved
|
||||||
@@ -62,36 +70,110 @@ import moe.tarsin.coroutines.groupByToObserved
|
|||||||
@Composable
|
@Composable
|
||||||
fun FilterScreen(navigator: DestinationsNavigator) {
|
fun FilterScreen(navigator: DestinationsNavigator) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope { Dispatchers.IO }
|
val scope = rememberCoroutineScope()
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
val allFilterMap = remember { scope.async { EhFilter.filters.await().groupByToObserved { it.mode } } }
|
val allFilterMap = remember { scope.async { EhFilter.filters.await().groupByToObserved { it.mode } } }
|
||||||
val dialogState = LocalDialogState.current
|
val dialogState = LocalDialogState.current
|
||||||
class AddFilterDialogHelper(private val dialog: AlertDialog) : View.OnClickListener {
|
val textIsEmpty = stringResource(R.string.text_is_empty)
|
||||||
private val mArray = dialog.context.resources.getStringArray(R.array.filter_entries)
|
val labelExist = stringResource(R.string.label_text_exist)
|
||||||
private val binding = DialogAddFilterBinding.bind(dialog.findViewById(R.id.base)!!)
|
|
||||||
init {
|
fun addFilter() {
|
||||||
dialog.getButton(DialogInterface.BUTTON_POSITIVE)!!.setOnClickListener(this)
|
scope.launch {
|
||||||
}
|
dialogState.dialog { cont ->
|
||||||
override fun onClick(v: View) {
|
val types = stringArrayResource(id = R.array.filter_entries)
|
||||||
binding.textInputLayout.run {
|
var type by remember { mutableStateOf(types[0]) }
|
||||||
val text = editText!!.text.toString().trim()
|
var value by remember { mutableStateOf("") }
|
||||||
if (text.isBlank()) {
|
var error by remember { mutableStateOf<String?>(null) }
|
||||||
error = context.getString(R.string.text_is_empty)
|
fun invalidateAndSave() {
|
||||||
return
|
if (value.isBlank()) {
|
||||||
} else {
|
error = textIsEmpty
|
||||||
|
return
|
||||||
|
}
|
||||||
error = null
|
error = null
|
||||||
}
|
val mode = FilterMode.entries[types.indexOf(type)]
|
||||||
val modeText = binding.spinner.editText!!.text.toString()
|
val filter = Filter(mode, value)
|
||||||
val mode = FilterMode.entries[mArray.indexOf(modeText)]
|
filter.remember {
|
||||||
val filter = Filter(mode, text)
|
if (it) {
|
||||||
filter.remember {
|
cont.resume(Unit)
|
||||||
if (it) {
|
requireNotNull(allFilterMap.getCompleted()[mode]).add(filter)
|
||||||
post { dialog.dismiss() }
|
} else {
|
||||||
requireNotNull(allFilterMap.getCompleted()[mode]).add(filter)
|
error = labelExist
|
||||||
} else {
|
}
|
||||||
post { error = context.getString(R.string.label_text_exist) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { cont.cancel() },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = ::invalidateAndSave) {
|
||||||
|
Text(text = stringResource(id = R.string.add))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = { cont.cancel() }) {
|
||||||
|
Text(text = stringResource(id = android.R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(id = R.string.add_filter))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
Column {
|
||||||
|
ExposedDropdownMenuBox(
|
||||||
|
expanded = expanded,
|
||||||
|
onExpandedChange = { expanded = !expanded },
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = Modifier.menuAnchor(),
|
||||||
|
readOnly = true,
|
||||||
|
value = type,
|
||||||
|
onValueChange = {},
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = R.string.filter_label))
|
||||||
|
},
|
||||||
|
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||||
|
colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(),
|
||||||
|
)
|
||||||
|
ExposedDropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false },
|
||||||
|
) {
|
||||||
|
types.forEach {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(text = it) },
|
||||||
|
onClick = {
|
||||||
|
expanded = false
|
||||||
|
type = it
|
||||||
|
},
|
||||||
|
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.size(16.dp))
|
||||||
|
val isError = error != null
|
||||||
|
OutlinedTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = { value = it },
|
||||||
|
label = { Text(text = stringResource(id = R.string.filter_text)) },
|
||||||
|
supportingText = { error?.let { Text(text = it) } },
|
||||||
|
trailingIcon = {
|
||||||
|
if (isError) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Info,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isError = isError,
|
||||||
|
maxLines = 1,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
imeAction = ImeAction.Done,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +196,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(onClick = { AddFilterDialogHelper(BaseDialogBuilder(context).setTitle(R.string.add_filter).setView(R.layout.dialog_add_filter).setPositiveButton(R.string.add, null).setNegativeButton(android.R.string.cancel, null).show()) }) {
|
FloatingActionButton(onClick = ::addFilter) {
|
||||||
Icon(imageVector = Icons.Default.Add, contentDescription = null)
|
Icon(imageVector = Icons.Default.Add, contentDescription = null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -135,18 +217,18 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
FilterMode.COMMENT -> R.string.filter_comment
|
FilterMode.COMMENT -> R.string.filter_comment
|
||||||
}
|
}
|
||||||
if (filters.isNotEmpty()) {
|
if (filters.isNotEmpty()) {
|
||||||
stickyHeader {
|
stickyHeader(key = filterMode) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(id = title),
|
text = stringResource(id = title),
|
||||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp).animateItemPlacement(),
|
||||||
color = MaterialTheme.colorScheme.tertiary,
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(filters) { filter ->
|
items(filters, key = { requireNotNull(it.id) }) { filter ->
|
||||||
val filterCheckBoxRecomposeScope = currentRecomposeScope
|
val filterCheckBoxRecomposeScope = currentRecomposeScope
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth().clickable { filter.trigger { filterCheckBoxRecomposeScope.invalidate() } },
|
modifier = Modifier.fillMaxWidth().animateItemPlacement().clickable { filter.trigger { filterCheckBoxRecomposeScope.invalidate() } },
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Checkbox(
|
||||||
@@ -161,9 +243,7 @@ fun FilterScreen(navigator: DestinationsNavigator) {
|
|||||||
Text(text = stringResource(id = R.string.delete_filter, filter.text))
|
Text(text = stringResource(id = R.string.delete_filter, filter.text))
|
||||||
}
|
}
|
||||||
filter.forget {
|
filter.forget {
|
||||||
withUIContext {
|
filters.remove(filter)
|
||||||
filters.remove(filter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
|
||||||
~ Copyright 2016 Hippo Seven
|
|
||||||
~
|
|
||||||
~ 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:id="@+id/base"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingHorizontal="?attr/dialogPreferredPadding"
|
|
||||||
android:paddingTop="@dimen/abc_dialog_padding_top_material">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/spinner"
|
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/filter_label">
|
|
||||||
|
|
||||||
<AutoCompleteTextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="none"
|
|
||||||
android:text="@string/filter_title"
|
|
||||||
app:simpleItems="@array/filter_entries" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/text_input_layout"
|
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:hint="@string/filter_text">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/edit_text"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:imeOptions="actionDone"
|
|
||||||
android:inputType="text"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:singleLine="true" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
Reference in new issue
Block a user