Note widget: add dismiss action

This commit is contained in:
MM20 2023-04-27 18:45:52 +02:00
parent 9f675da337
commit 5211f5bced
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 119 additions and 23 deletions

View File

@ -91,8 +91,8 @@ fun WidgetColumn(
widget = widget,
appWidgetHost = widgetHost,
editMode = editMode,
onWidgetAdd = {
viewModel.addWidget(it, i + 1)
onWidgetAdd = { widget, offset ->
viewModel.addWidget(widget, i + offset)
},
onWidgetRemove = {
if (widget is AppWidget) {

View File

@ -62,7 +62,7 @@ fun WidgetItem(
appWidgetHost: AppWidgetHost,
modifier: Modifier = Modifier,
editMode: Boolean = false,
onWidgetAdd: (widget: Widget) -> Unit = {},
onWidgetAdd: (widget: Widget, offset: Int) -> Unit = {_, _ ->},
onWidgetUpdate: (widget: Widget) -> Unit = {},
onWidgetRemove: () -> Unit = {},
draggableState: DraggableState = rememberDraggableState {},
@ -163,13 +163,7 @@ fun WidgetItem(
is NotesWidget -> {
NotesWidget(
widget,
onNewNote = {
val newWidget = NotesWidget(
id = UUID.randomUUID(),
widget.config.copy(storedText = "")
)
onWidgetAdd(newWidget)
}
onWidgetAdd = onWidgetAdd,
)
}

View File

@ -3,9 +3,12 @@ package de.mm20.launcher2.ui.launcher.widgets.notes
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Add
@ -16,9 +19,12 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@ -26,24 +32,35 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.markdown.MarkdownEditor
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
import de.mm20.launcher2.widgets.NotesWidget
import de.mm20.launcher2.widgets.Widget
import kotlinx.coroutines.launch
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.UUID
@Composable
fun NotesWidget(
widget: NotesWidget,
onNewNote: () -> Unit,
onWidgetAdd: (widget: Widget, offset: Int) -> Unit,
) {
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current
val lifecycleOwner = LocalLifecycleOwner.current
val viewModel: NotesWidgetVM =
viewModel(key = "notes-widget-${widget.id}", factory = NotesWidgetVM.Factory)
val isLastWidget by viewModel.isLastNoteWidget.collectAsState(null)
LaunchedEffect(widget) {
viewModel.updateWidget(widget)
}
@ -57,20 +74,45 @@ fun NotesWidget(
val text by viewModel.noteText
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.heightIn(min = 64.dp)
.animateContentSize(),
) {
MarkdownEditor(
value = text,
onValueChange = { viewModel.setText(it) },
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(16.dp),
placeholder = {
Text(stringResource(R.string.notes_widget_placeholder))
Text(
stringResource(R.string.notes_widget_placeholder),
)
}
)
AnimatedVisibility(isLastWidget == false && text.isBlank()) {
IconButton(
onClick = {
viewModel.dismissNote()
},
modifier = Modifier.padding(8.dp)
) {
Icon(Icons.Rounded.Delete, null)
}
}
}
AnimatedVisibility(text.isNotBlank()) {
var showMenu by remember { mutableStateOf(false) }
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp),
contentAlignment = Alignment.CenterEnd
) {
Box {
IconButton(onClick = { showMenu = true }) {
Icon(Icons.Rounded.MoreVert, null)
@ -82,7 +124,11 @@ fun NotesWidget(
Icon(Icons.Rounded.Add, null)
},
onClick = {
onNewNote()
val newWidget = NotesWidget(
id = UUID.randomUUID(),
widget.config.copy(storedText = "")
)
onWidgetAdd(newWidget, 1)
showMenu = false
},
)
@ -103,11 +149,39 @@ fun NotesWidget(
},
)
DropdownMenuItem(
text = { Text("Dismiss") },
text = { Text(stringResource(R.string.notes_widget_action_dismiss)) },
leadingIcon = {
Icon(Icons.Rounded.Delete, null)
},
onClick = { /*TODO*/ },
onClick = {
if (isLastWidget == false) {
viewModel.dismissNote()
lifecycleOwner.lifecycleScope.launch {
val result = snackbarHostState.showSnackbar(
message = context.getString(R.string.notes_widget_dismissed),
actionLabel = context.getString(R.string.action_undo),
duration = SnackbarDuration.Short,
)
if (result == SnackbarResult.ActionPerformed) {
onWidgetAdd(widget, 0)
}
}
} else {
val content = text
viewModel.setText("")
lifecycleOwner.lifecycleScope.launch {
val result = snackbarHostState.showSnackbar(
message = context.getString(R.string.notes_widget_dismissed),
actionLabel = context.getString(R.string.action_undo),
duration = SnackbarDuration.Short,
)
if (result == SnackbarResult.ActionPerformed) {
viewModel.setText(content)
}
}
}
showMenu = false
},
)
}
}

View File

@ -14,6 +14,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
@ -25,6 +28,10 @@ class NotesWidgetVM(
val noteText = mutableStateOf(widget.value?.config?.storedText ?: "")
val isLastNoteWidget = widgetsService.countWidgets(NotesWidget.Type).map {
it == 1
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
fun updateWidget(widget: NotesWidget) {
val oldId = this.widget.value?.id
this.widget.value = widget
@ -53,6 +60,11 @@ class NotesWidgetVM(
}
}
fun dismissNote() {
widgetsService.removeWidget(widget.value ?: return)
}
companion object: KoinComponent {
val Factory = viewModelFactory {
initializer {

View File

@ -51,4 +51,7 @@ interface WidgetDao {
@Query("SELECT EXISTS(SELECT 1 FROM Widget WHERE type = :type)")
fun exists(type: String): Flow<Boolean>
@Query("SELECT COUNT(*) FROM Widget WHERE type = :type")
fun count(type: String): Flow<Int>
}

View File

@ -21,6 +21,7 @@ interface WidgetRepository {
fun set(widgets: List<Widget>, parentId: UUID? = null)
fun exists(type: String): Flow<Boolean>
fun count(type: String): Flow<Int>
suspend fun export(toDir: File)
suspend fun import(fromDir: File)
@ -85,6 +86,12 @@ internal class WidgetRepositoryImpl(
return dao.exists(type = type)
}
override fun count(type: String): Flow<Int> {
val dao = database.widgetDao()
return dao.count(type = type)
}
override suspend fun export(toDir: File) = withContext(Dispatchers.IO) {
val dao = database.backupDao()

View File

@ -80,6 +80,12 @@ class WidgetsService(
}
}
fun countWidgets(type: String) = widgetRepository.count(type)
fun removeWidget(widget: Widget) {
widgetRepository.delete(widget)
}
companion object {
const val AppWidgetHostId = 44203
}