refactor: migrate QuickChat to Compose (#1419)

This commit is contained in:
Andre K 2024-11-19 11:59:28 -03:00 committed by GitHub
parent 4855576248
commit 475e9fc22c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 375 additions and 362 deletions

View file

@ -1,38 +1,35 @@
package com.geeksville.mesh.database
import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.database.dao.QuickChatActionDao
import com.geeksville.mesh.database.entity.QuickChatAction
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import javax.inject.Inject
class QuickChatActionRepository @Inject constructor(private val quickChatDaoLazy: dagger.Lazy<QuickChatActionDao>) {
class QuickChatActionRepository @Inject constructor(
private val quickChatDaoLazy: dagger.Lazy<QuickChatActionDao>,
private val dispatchers: CoroutineDispatchers,
) {
private val quickChatActionDao by lazy {
quickChatDaoLazy.get()
}
suspend fun getAllActions(): Flow<List<QuickChatAction>> = withContext(Dispatchers.IO) {
quickChatActionDao.getAll()
fun getAllActions() = quickChatActionDao.getAll().flowOn(dispatchers.io)
suspend fun upsert(action: QuickChatAction) = withContext(dispatchers.io) {
quickChatActionDao.upsert(action)
}
suspend fun insert(action: QuickChatAction) = withContext(Dispatchers.IO) {
quickChatActionDao.insert(action)
}
suspend fun deleteAll() = withContext(Dispatchers.IO) {
suspend fun deleteAll() = withContext(dispatchers.io) {
quickChatActionDao.deleteAll()
}
suspend fun delete(action: QuickChatAction) = withContext(Dispatchers.IO) {
suspend fun delete(action: QuickChatAction) = withContext(dispatchers.io) {
quickChatActionDao.delete(action)
}
suspend fun update(action: QuickChatAction) = withContext(Dispatchers.IO) {
quickChatActionDao.update(action)
}
suspend fun setItemPosition(uuid: Long, newPos: Int) = withContext(Dispatchers.IO) {
suspend fun setItemPosition(uuid: Long, newPos: Int) = withContext(dispatchers.io) {
quickChatActionDao.updateActionPosition(uuid, newPos)
}
}

View file

@ -1,6 +1,9 @@
package com.geeksville.mesh.database.dao
import androidx.room.*
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
import com.geeksville.mesh.database.entity.QuickChatAction
import kotlinx.coroutines.flow.Flow
@ -10,8 +13,8 @@ interface QuickChatActionDao {
@Query("Select * from quick_chat order by position asc")
fun getAll(): Flow<List<QuickChatAction>>
@Insert
fun insert(action: QuickChatAction)
@Upsert
fun upsert(action: QuickChatAction)
@Query("Delete from quick_chat")
fun deleteAll()
@ -25,13 +28,9 @@ interface QuickChatActionDao {
decrementPositionsAfter(action.position)
}
@Update
fun update(action: QuickChatAction)
@Query("Update quick_chat set position=:position WHERE uuid=:uuid")
fun updateActionPosition(uuid: Long, position: Int)
@Query("Update quick_chat set position=position-1 where position>=:position")
fun decrementPositionsAfter(position: Int)
}
}

View file

@ -6,10 +6,10 @@ import androidx.room.PrimaryKey
@Entity(tableName = "quick_chat")
data class QuickChatAction(
@PrimaryKey(autoGenerate = true) val uuid: Long,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "message") val message: String,
@ColumnInfo(name = "mode") val mode: Mode,
@PrimaryKey(autoGenerate = true) val uuid: Long = 0L,
@ColumnInfo(name = "name") val name: String = "",
@ColumnInfo(name = "message") val message: String = "",
@ColumnInfo(name = "mode") val mode: Mode = Mode.Instant,
@ColumnInfo(name = "position") val position: Int
) {
enum class Mode {

View file

@ -34,7 +34,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted.Companion.Eagerly
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
@ -186,8 +186,8 @@ class UIViewModel @Inject constructor(
private val _channels = MutableStateFlow(channelSet {})
val channels: StateFlow<AppOnlyProtos.ChannelSet> get() = _channels
private val _quickChatActions = MutableStateFlow<List<QuickChatAction>>(emptyList())
val quickChatActions: StateFlow<List<QuickChatAction>> = _quickChatActions
val quickChatActions get() = quickChatActionRepository.getAllActions()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
private val _focusedNode = MutableStateFlow<NodeEntity?>(null)
val focusedNode: StateFlow<NodeEntity?> = _focusedNode
@ -230,7 +230,7 @@ class UIViewModel @Inject constructor(
)
}.stateIn(
scope = viewModelScope,
started = Eagerly,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = NodesUiState.Empty,
)
@ -239,7 +239,7 @@ class UIViewModel @Inject constructor(
nodeDB.getNodes(state.sort, state.filter, state.includeUnknown)
}.stateIn(
scope = viewModelScope,
started = Eagerly,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
@ -270,11 +270,6 @@ class UIViewModel @Inject constructor(
radioConfigRepository.moduleConfigFlow.onEach { config ->
_moduleConfig.value = config
}.launchIn(viewModelScope)
viewModelScope.launch {
quickChatActionRepository.getAllActions().collect { actions ->
_quickChatActions.value = actions
}
}
radioConfigRepository.channelSetFlow.onEach { channelSet ->
_channels.value = channelSet
}.launchIn(viewModelScope)
@ -327,7 +322,7 @@ class UIViewModel @Inject constructor(
}
}.stateIn(
scope = viewModelScope,
started = Eagerly,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList(),
)
@ -699,39 +694,16 @@ class UIViewModel @Inject constructor(
}
}
fun addQuickChatAction(name: String, value: String, mode: QuickChatAction.Mode) {
viewModelScope.launch(Dispatchers.Main) {
val action = QuickChatAction(0, name, value, mode, _quickChatActions.value.size)
quickChatActionRepository.insert(action)
}
fun addQuickChatAction(action: QuickChatAction) = viewModelScope.launch(Dispatchers.IO) {
quickChatActionRepository.upsert(action)
}
fun deleteQuickChatAction(action: QuickChatAction) {
viewModelScope.launch(Dispatchers.Main) {
quickChatActionRepository.delete(action)
}
}
fun updateQuickChatAction(
action: QuickChatAction,
name: String?,
message: String?,
mode: QuickChatAction.Mode?
) {
viewModelScope.launch(Dispatchers.Main) {
val newAction = QuickChatAction(
action.uuid,
name ?: action.name,
message ?: action.message,
mode ?: action.mode,
action.position
)
quickChatActionRepository.update(newAction)
}
fun deleteQuickChatAction(action: QuickChatAction) = viewModelScope.launch(Dispatchers.IO) {
quickChatActionRepository.delete(action)
}
fun updateActionPositions(actions: List<QuickChatAction>) {
viewModelScope.launch(Dispatchers.Main) {
viewModelScope.launch(Dispatchers.IO) {
for (position in actions.indices) {
quickChatActionRepository.setItemPosition(actions[position].uuid, position)
}

View file

@ -1,236 +1,356 @@
package com.geeksville.mesh.ui
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.ImageView
import androidx.annotation.StringRes
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Surface
import androidx.compose.material.ListItem
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Scaffold
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.FastForward
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.activityViewModels
import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.database.entity.QuickChatAction
import com.geeksville.mesh.databinding.QuickChatSettingsFragmentBinding
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.components.dragContainer
import com.geeksville.mesh.ui.components.dragDropItemsIndexed
import com.geeksville.mesh.ui.components.rememberDragDropState
import com.geeksville.mesh.ui.theme.AppTheme
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.switchmaterial.SwitchMaterial
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class QuickChatSettingsFragment : ScreenFragment("Quick Chat Settings"), Logging {
private var _binding: QuickChatSettingsFragmentBinding? = null
private val binding get() = _binding!!
private val model: UIViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = QuickChatSettingsFragmentBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.quickChatSettingsToolbar.setNavigationOnClickListener {
parentFragmentManager.popBackStack()
}
binding.quickChatSettingsCreateButton.setOnClickListener {
val builder = createEditDialog(requireContext(), R.string.quick_chat_new)
builder.builder.setPositiveButton(R.string.add) { _, _ ->
val name = builder.nameInput.text.toString().trim()
val message = builder.messageInput.text.toString()
if (builder.isNotEmpty()) {
model.addQuickChatAction(
name, message,
if (builder.modeSwitch.isChecked) QuickChatAction.Mode.Instant else QuickChatAction.Mode.Append
)
}
}
val dialog = builder.builder.create()
dialog.show()
}
binding.quickChatSettingsView.setContent {
val actions by model.quickChatActions.collectAsStateWithLifecycle()
val listState = rememberLazyListState()
val dragDropState = rememberDragDropState(listState) { fromIndex, toIndex ->
val list = actions.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
model.updateActionPositions(list)
}
AppTheme {
LazyColumn(
modifier = Modifier.dragContainer(
dragDropState = dragDropState,
haptics = LocalHapticFeedback.current,
),
state = listState,
// contentPadding = PaddingValues(16.dp),
) {
dragDropItemsIndexed(
items = actions,
dragDropState = dragDropState,
key = { _, item -> item.uuid },
) { _, action, isDragging ->
val elevation by animateDpAsState(if (isDragging) 8.dp else 4.dp)
QuickChatItem(
elevation = elevation,
action = action,
onEditClick = ::onEditAction,
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setBackgroundColor(ContextCompat.getColor(context, R.color.colorAdvancedBackground))
setContent {
AppTheme {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(id = R.string.quick_chat)) },
navigationIcon = {
IconButton(onClick = { parentFragmentManager.popBackStack() }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
stringResource(id = R.string.navigate_back),
)
}
},
)
},
) { innerPadding ->
QuickChatScreen(
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
data class DialogBuilder(
val builder: MaterialAlertDialogBuilder,
val nameInput: EditText,
val messageInput: EditText,
val modeSwitch: SwitchMaterial,
val instantImage: ImageView
) {
fun isNotEmpty(): Boolean = nameInput.text.isNotEmpty() and messageInput.text.isNotEmpty()
}
private fun getMessageName(message: String): String {
return if (message.length <= 3) {
message.uppercase()
} else {
buildString {
append(message.first().uppercase())
append(message[message.length / 2].uppercase())
append(message.last().uppercase())
}
}
}
private fun createEditDialog(context: Context, @StringRes title: Int): DialogBuilder {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(title)
val layout = LayoutInflater.from(context).inflate(R.layout.dialog_add_quick_chat, null)
val nameInput: EditText = layout.findViewById(R.id.addQuickChatName)
val messageInput: EditText = layout.findViewById(R.id.addQuickChatMessage)
val modeSwitch: SwitchMaterial = layout.findViewById(R.id.addQuickChatMode)
val instantImage: ImageView = layout.findViewById(R.id.addQuickChatInsant)
instantImage.visibility = if (modeSwitch.isChecked) View.VISIBLE else View.INVISIBLE
// don't change action name on edits
var nameHasChanged = title == R.string.quick_chat_edit
modeSwitch.setOnCheckedChangeListener { _, _ ->
if (modeSwitch.isChecked) {
modeSwitch.setText(R.string.quick_chat_instant)
instantImage.visibility = View.VISIBLE
} else {
modeSwitch.setText(R.string.quick_chat_append)
instantImage.visibility = View.INVISIBLE
}
}
messageInput.addTextChangedListener { text ->
if (!nameHasChanged) {
nameInput.setText(getMessageName(text.toString()))
}
}
nameInput.addTextChangedListener {
if (nameInput.isFocused) nameHasChanged = true
}
builder.setView(layout)
return DialogBuilder(builder, nameInput, messageInput, modeSwitch, instantImage)
}
private fun onEditAction(action: QuickChatAction) {
val builder = createEditDialog(requireContext(), R.string.quick_chat_edit)
builder.nameInput.setText(action.name)
builder.messageInput.setText(action.message)
val isInstant = action.mode == QuickChatAction.Mode.Instant
builder.modeSwitch.isChecked = isInstant
builder.instantImage.visibility = if (isInstant) View.VISIBLE else View.INVISIBLE
builder.builder.setNegativeButton(R.string.delete) { _, _ ->
model.deleteQuickChatAction(action)
}
builder.builder.setPositiveButton(R.string.save) { _, _ ->
if (builder.isNotEmpty()) {
model.updateQuickChatAction(
action,
builder.nameInput.text.toString(),
builder.messageInput.text.toString(),
if (builder.modeSwitch.isChecked) {
QuickChatAction.Mode.Instant
} else {
QuickChatAction.Mode.Append
}
)
}
}
val dialog = builder.builder.create()
dialog.show()
}
}
@Composable
internal fun QuickChatItem(
internal fun QuickChatScreen(
viewModel: UIViewModel = hiltViewModel(),
modifier: Modifier = Modifier,
) {
val actions by viewModel.quickChatActions.collectAsStateWithLifecycle()
var showActionDialog by remember { mutableStateOf<QuickChatAction?>(null) }
val listState = rememberLazyListState()
val dragDropState = rememberDragDropState(listState) { fromIndex, toIndex ->
val list = actions.toMutableList().apply { add(toIndex, removeAt(fromIndex)) }
viewModel.updateActionPositions(list)
}
Box(modifier = modifier.fillMaxSize()) {
if (showActionDialog != null) {
val action = showActionDialog ?: return
EditQuickChatDialog(
action = action,
onSave = viewModel::addQuickChatAction,
onDelete = viewModel::deleteQuickChatAction,
) { showActionDialog = null }
}
FloatingActionButton(
onClick = {
showActionDialog = QuickChatAction(position = actions.size)
},
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(16.dp)
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(id = R.string.add),
)
}
LazyColumn(
modifier = Modifier.dragContainer(
dragDropState = dragDropState,
haptics = LocalHapticFeedback.current,
),
state = listState,
contentPadding = PaddingValues(16.dp),
) {
dragDropItemsIndexed(
items = actions,
dragDropState = dragDropState,
key = { _, item -> item.uuid },
) { _, action, isDragging ->
val elevation by animateDpAsState(
targetValue = if (isDragging) 8.dp else 4.dp,
label = "DragAndDropElevationAnimation",
)
QuickChatItem(
elevation = elevation,
action = action,
onEdit = { showActionDialog = it },
)
}
}
}
}
@Suppress("MagicNumber")
private fun getMessageName(message: String): String = if (message.length <= 3) {
message.uppercase()
} else {
buildString {
append(message.first().uppercase())
append(message[message.length / 2].uppercase())
append(message.last().uppercase())
}
}
@OptIn(ExperimentalLayoutApi::class)
@Suppress("LongMethod")
@Composable
private fun EditQuickChatDialog(
action: QuickChatAction,
onSave: (QuickChatAction) -> Unit,
onDelete: (QuickChatAction) -> Unit,
onDismiss: () -> Unit,
) {
var actionInput by remember { mutableStateOf(action) }
val newQuickChat = action.uuid == 0L
val isInstant = actionInput.mode == QuickChatAction.Mode.Instant
val title = if (newQuickChat) R.string.quick_chat_new else R.string.quick_chat_edit
AlertDialog(
onDismissRequest = onDismiss,
shape = RoundedCornerShape(16.dp),
backgroundColor = MaterialTheme.colors.background,
text = {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = stringResource(id = title),
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.h6.copy(
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
),
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextFieldWithCounter(
label = stringResource(R.string.name),
value = actionInput.name,
maxSize = 5,
singleLine = true,
modifier = Modifier.fillMaxWidth(),
) { actionInput = actionInput.copy(name = it.uppercase()) }
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextFieldWithCounter(
label = stringResource(id = R.string.message),
value = actionInput.message,
maxSize = 235,
getSize = { it.toByteArray().size + 1 },
modifier = Modifier.fillMaxWidth()
) {
actionInput = actionInput.copy(message = it)
if (newQuickChat) {
actionInput = actionInput.copy(name = getMessageName(it))
}
}
Spacer(modifier = Modifier.height(8.dp))
val (text, icon) = if (isInstant) {
R.string.quick_chat_instant to Icons.Default.FastForward
} else {
R.string.quick_chat_append to Icons.Default.Add
}
Row(
verticalAlignment = Alignment.CenterVertically,
) {
if (isInstant) {
Icon(
imageVector = icon,
contentDescription = stringResource(id = text),
)
Spacer(Modifier.width(12.dp))
}
Text(
text = stringResource(text),
modifier = Modifier.weight(1f),
)
Switch(
checked = isInstant,
onCheckedChange = { checked ->
actionInput = actionInput.copy(
mode = when (checked) {
true -> QuickChatAction.Mode.Instant
false -> QuickChatAction.Mode.Append
}
)
},
)
}
}
},
buttons = {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TextButton(
modifier = Modifier.weight(1f),
onClick = onDismiss,
) { Text(stringResource(R.string.cancel)) }
if (!newQuickChat) {
Button(
modifier = Modifier.weight(1f),
onClick = {
onDelete(actionInput)
onDismiss()
},
) { Text(text = stringResource(R.string.delete)) }
}
Button(
modifier = Modifier.weight(1f),
onClick = {
onSave(actionInput)
onDismiss()
},
enabled = actionInput.name.isNotEmpty() && actionInput.message.isNotEmpty(),
) { Text(text = stringResource(R.string.save)) }
}
},
)
}
@Composable
private fun OutlinedTextFieldWithCounter(
label: String,
value: String,
modifier: Modifier = Modifier,
singleLine: Boolean = false,
maxSize: Int,
getSize: (String) -> Int = { it.length },
onValueChange: (String) -> Unit = {},
) = Column(modifier) {
var isFocused by remember { mutableStateOf(false) }
OutlinedTextField(
value = value,
onValueChange = {
if (getSize(it) <= maxSize) {
onValueChange(it)
}
},
modifier = Modifier.onFocusEvent { isFocused = it.isFocused },
label = { Text(text = label) },
singleLine = singleLine,
)
if (isFocused) {
Text(
text = "${getSize(value)}/$maxSize",
style = MaterialTheme.typography.caption,
modifier = Modifier
.align(Alignment.End)
.padding(top = 4.dp, end = 16.dp)
)
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun QuickChatItem(
action: QuickChatAction,
modifier: Modifier = Modifier,
onEditClick: (QuickChatAction) -> Unit = {},
onEdit: (QuickChatAction) -> Unit = {},
elevation: Dp = 4.dp,
) {
Card(
@ -240,56 +360,37 @@ internal fun QuickChatItem(
elevation = elevation,
shape = RoundedCornerShape(12.dp),
) {
Surface {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val showInstantIcon = action.mode == QuickChatAction.Mode.Instant
Icon(
painter = painterResource(id = R.drawable.ic_baseline_fast_forward_24),
contentDescription = null,
modifier = Modifier.padding(start = 8.dp),
tint = if (showInstantIcon) LocalContentColor.current else Color.Transparent,
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = 8.dp)
) {
Text(
text = action.name,
fontSize = 20.sp,
modifier = Modifier.padding(top = 8.dp)
)
Text(
text = action.message,
modifier = Modifier.padding(vertical = 8.dp)
)
}
IconButton(
onClick = { onEditClick(action) },
modifier = Modifier.size(48.dp)
) {
ListItem(
icon = {
if (action.mode == QuickChatAction.Mode.Instant) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_edit_24),
contentDescription = null
imageVector = Icons.Default.FastForward,
contentDescription = stringResource(id = R.string.quick_chat_instant),
)
}
},
text = { Text(text = action.name) },
secondaryText = { Text(text = action.message) },
trailing = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
IconButton(
onClick = { onEdit(action) },
modifier = Modifier.size(48.dp)
) {
Icon(
painter = painterResource(id = R.drawable.ic_baseline_edit_24),
contentDescription = stringResource(id = R.string.quick_chat_edit),
)
}
Icon(
painter = painterResource(id = R.drawable.ic_baseline_drag_handle_24),
contentDescription = stringResource(id = R.string.quick_chat),
)
}
Icon(
painter = painterResource(id = R.drawable.ic_baseline_drag_handle_24),
contentDescription = null,
modifier = Modifier.padding(end = 8.dp),
)
}
}
)
}
}
@ -299,12 +400,27 @@ private fun QuickChatItemPreview() {
AppTheme {
QuickChatItem(
action = QuickChatAction(
uuid = 0L,
name = "TST",
message = "Test",
mode = QuickChatAction.Mode.Instant,
position = 0,
),
)
}
}
@PreviewLightDark
@Composable
private fun EditQuickChatDialogPreview() {
AppTheme {
EditQuickChatDialog(
action = QuickChatAction(
name = "TST",
message = "Test",
position = 0,
),
onSave = {},
onDelete = {},
onDismiss = {}
)
}
}