Note widget: add dismiss action
This commit is contained in:
parent
9f675da337
commit
5211f5bced
@ -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) {
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user