Work on new android label selector

This commit is contained in:
Jackson Harper
2023-05-12 12:08:40 +08:00
parent 99f0d945a6
commit 8e3c2f65bb
4 changed files with 227 additions and 63 deletions

View File

@ -136,6 +136,7 @@ dependencies {
implementation 'com.google.android.gms:play-services-auth:20.4.0'
implementation "com.google.accompanist:accompanist-systemuicontroller:0.25.1"
implementation "com.google.accompanist:accompanist-flowlayout:0.25.1"
implementation 'io.coil-kt:coil-compose:2.2.0'
@ -151,6 +152,7 @@ dependencies {
kapt "androidx.room:room-compiler:$room_version"
implementation 'com.github.jeziellago:compose-markdown:0.3.3'
implementation "io.github.dokar3:chiptextfield:0.4.6"
}
apollo {

View File

@ -3,12 +3,14 @@
package app.omnivore.omnivore.ui.components
import LabelChip
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
@ -23,11 +25,22 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.library.LibraryViewModel
import com.dokar.chiptextfield.*
import com.google.accompanist.flowlayout.FlowRow
@ -93,9 +106,109 @@ import com.google.accompanist.flowlayout.FlowRow
// }
//}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CircleIcon(colorHex: String){
val chipColors = LabelChipColors.fromHex(colorHex)
val viewConfiguration = LocalViewConfiguration.current
val viewConfigurationOverride = remember(viewConfiguration) {
ViewConfigurationOverride(
base = viewConfiguration,
minimumTouchTargetSize = DpSize(24.dp, 24.dp)
)
}
CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) {
Row(
modifier = Modifier
.padding(start = 10.dp, end = 2.dp)
.padding(vertical = 10.dp)
) {
Canvas(modifier = Modifier.size(12.dp), onDraw = {
drawCircle(color = chipColors.containerColor)
})
}
}
}
@Composable
fun <T : Chip> CloseButton(
state: ChipTextFieldState<T>,
chip: T,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Transparent,
strokeColor: Color = Color.White,
startPadding: Dp = 0.dp,
endPadding: Dp = 4.dp
) {
Row(
modifier = modifier
.padding(start = startPadding, end = endPadding)
) {
CloseButtonImpl(
onClick = { state.removeChip(chip) },
backgroundColor = backgroundColor,
strokeColor = strokeColor
)
}
}
internal class ViewConfigurationOverride(
base: ViewConfiguration,
override val doubleTapMinTimeMillis: Long = base.doubleTapMinTimeMillis,
override val doubleTapTimeoutMillis: Long = base.doubleTapTimeoutMillis,
override val longPressTimeoutMillis: Long = base.longPressTimeoutMillis,
override val touchSlop: Float = base.touchSlop,
override val minimumTouchTargetSize: DpSize = base.minimumTouchTargetSize
) : ViewConfiguration
@Composable
private fun CloseButtonImpl(
onClick: () -> Unit,
backgroundColor: Color,
strokeColor: Color,
modifier: Modifier = Modifier,
) {
val padding = with(LocalDensity.current) { 6.dp.toPx() }
val strokeWidth = with(LocalDensity.current) { 1.2.dp.toPx() }
val viewConfiguration = LocalViewConfiguration.current
val viewConfigurationOverride = remember(viewConfiguration) {
ViewConfigurationOverride(
base = viewConfiguration,
minimumTouchTargetSize = DpSize(24.dp, 24.dp)
)
}
CompositionLocalProvider(LocalViewConfiguration provides viewConfigurationOverride) {
Canvas(
modifier = modifier
.size(18.dp)
.clip(CircleShape)
.background(backgroundColor)
.clickable(onClick = onClick)
) {
drawLine(
color = strokeColor,
start = Offset(padding, padding),
end = Offset(size.width - padding, size.height - padding),
strokeWidth = strokeWidth
)
drawLine(
color = strokeColor,
start = Offset(padding, size.height - padding),
end = Offset(size.width - padding, padding),
strokeWidth = strokeWidth
)
}
}
}
class LabelChip(label: SavedItemLabel) : Chip(label.name) {
val label = label
}
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun LabelsSelectionSheetContent(
// viewModel: LibraryViewModel,
isLibraryMode: Boolean,
labels: List<SavedItemLabel>,
initialSelectedLabels: List<SavedItemLabel>,
@ -107,13 +220,32 @@ fun LabelsSelectionSheetContent(
val selectedLabels = remember { mutableStateOf(initialSelectedLabels) }
var showCreateLabelDialog by remember { mutableStateOf(false ) }
val focusRequester = remember { FocusRequester() }
val titleText = if (isLibraryMode) "Filter by Label" else "Set Labels"
val findOrCreateLabel: (name: String) -> SavedItemLabel = { name ->
val found = labels.find { it.name == name }
found
?: SavedItemLabel(
savedItemLabelId = "",
name = name,
color = "#FFFFFF",
createdAt = "",
labelDescription = "",
serverSyncStatus = ServerSyncStatus.NEEDS_CREATION.rawValue
)
}
Surface(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.background),
) {
var value by remember { mutableStateOf("Initial text") }
val state = rememberChipTextFieldState<LabelChip>()
if (showCreateLabelDialog) {
LabelCreationDialog(
@ -129,9 +261,9 @@ fun LabelsSelectionSheetContent(
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
// .verticalScroll(rememberScrollState())
// .verticalScroll(rememberScrollState())
.fillMaxSize()
.padding(horizontal = 0.dp)
.padding(horizontal = 5.dp)
) {
Row(
@ -139,6 +271,7 @@ fun LabelsSelectionSheetContent(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 5.dp)
) {
TextButton(onClick = onCancel) {
Text(text = "Cancel")
@ -150,69 +283,97 @@ fun LabelsSelectionSheetContent(
Text(text = if (isLibraryMode) "Search" else "Save")
}
}
LazyColumn(
state = listState,
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
) {
items(labels) { label ->
val isLabelSelected = selectedLabels.value.contains(label)
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable {
if (isLabelSelected) {
selectedLabels.value =
selectedLabels.value.filter { it.savedItemLabelId != label.savedItemLabelId }
} else {
selectedLabels.value = selectedLabels.value + listOf(label)
}
}
.padding(horizontal = 10.dp, vertical = 6.dp)
) {
val chipColors = LabelChipColors.fromHex(label.color)
ChipTextField(
state = state,
onSubmit = { LabelChip(findOrCreateLabel(it)) },
chipLeadingIcon = { chip -> CircleIcon(colorHex = chip.label.color) },
chipTrailingIcon = { chip -> CloseButton(state, chip) },
chipStyle = ChipTextFieldDefaults.chipStyle(
shape = androidx.compose.material.MaterialTheme.shapes.medium,
unfocusedBorderWidth = 0.dp,
focusedTextColor = Color(0xFFAEAEAF),
focusedBorderColor = Color(0xFF2A2A2A),
focusedBackgroundColor = Color(0xFF2A2A2A)
),
colors = androidx.compose.material.TextFieldDefaults.textFieldColors(
textColor = Color(0xFFAEAEAF),
backgroundColor = Color(0xFF3D3D3D)
),
contentPadding = PaddingValues(15.dp),
modifier = Modifier
.defaultMinSize(minHeight = 45.dp)
.fillMaxWidth()
.padding(horizontal = 10.dp)
.focusRequester(focusRequester)
)
LabelChip(
name = label.name,
colors = chipColors
)
if (isLabelSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
}
}
Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp)
}
LazyColumn(
state = listState,
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
items(labels) { label ->
val isLabelSelected = selectedLabels.value.contains(label)
if (!isLibraryMode) {
item {
Row(
horizontalArrangement = Arrangement.Start,
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { showCreateLabelDialog = true }
.padding(horizontal = 6.dp)
.padding(vertical = 12.dp)
)
{
Icon(
imageVector = Icons.Filled.AddCircle,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
.clickable {
if (isLabelSelected) {
selectedLabels.value =
selectedLabels.value.filter { it.savedItemLabelId != label.savedItemLabelId }
} else {
selectedLabels.value = selectedLabels.value + listOf(label)
state.addChip(app.omnivore.omnivore.ui.components.LabelChip(label))
}
}
.padding(horizontal = 10.dp, vertical = 6.dp)
) {
val chipColors = LabelChipColors.fromHex(label.color)
LabelChip(
name = label.name,
colors = chipColors
)
Text(text = "Create a new Label")
if (isLabelSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = null
)
}
}
Divider(color = MaterialTheme.colorScheme.outlineVariant, thickness = 1.dp)
}
if (!isLibraryMode) {
item {
Row(
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable { showCreateLabelDialog = true }
.padding(horizontal = 6.dp)
.padding(vertical = 12.dp)
)
{
Icon(
imageVector = Icons.Filled.AddCircle,
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Text(text = "Create a new Label")
}
}
}
}
}
}
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
}
}

View File

@ -59,7 +59,7 @@ fun LibraryView(
if (showLabelsSelectionSheet) {
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
modalBottomSheetState.show()
}
} else {
coroutineScope.launch {

View File

@ -176,6 +176,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
Color(it.backgroundColor ?: 0xFFFFFFFF)
}
} ?: Color(0xFFFFFFFF)
val themeTintColor = currentTheme?.let {
if (it.themeKey == "System" && isDarkMode) {
Color(0xFFFFFFFF)
@ -189,27 +190,27 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
when (bottomSheetState) {
BottomSheetState.PREFERENCES -> {
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
modalBottomSheetState.show()
}
}
BottomSheetState.NOTEBOOK -> {
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
modalBottomSheetState.show()
}
}
BottomSheetState.HIGHLIGHTNOTE -> {
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
modalBottomSheetState.show()
}
}
BottomSheetState.LABELS -> {
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
modalBottomSheetState.show()
}
}
BottomSheetState.LINK -> {
coroutineScope.launch {
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
modalBottomSheetState.show()
}
}
BottomSheetState.NONE -> {
@ -346,7 +347,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
IconButton(onClick = {
coroutineScope.launch {
webReaderViewModel.setBottomSheet(BottomSheetState.NOTEBOOK)
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
modalBottomSheetState.show()
}
}) {
Icon(
@ -359,7 +360,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
IconButton(onClick = {
coroutineScope.launch {
webReaderViewModel.setBottomSheet(BottomSheetState.PREFERENCES)
modalBottomSheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
modalBottomSheetState.show()
}
}) {
Icon(