Note widget: add dismiss action
This commit is contained in:
parent
9f675da337
commit
5211f5bced
@ -91,8 +91,8 @@ fun WidgetColumn(
|
|||||||
widget = widget,
|
widget = widget,
|
||||||
appWidgetHost = widgetHost,
|
appWidgetHost = widgetHost,
|
||||||
editMode = editMode,
|
editMode = editMode,
|
||||||
onWidgetAdd = {
|
onWidgetAdd = { widget, offset ->
|
||||||
viewModel.addWidget(it, i + 1)
|
viewModel.addWidget(widget, i + offset)
|
||||||
},
|
},
|
||||||
onWidgetRemove = {
|
onWidgetRemove = {
|
||||||
if (widget is AppWidget) {
|
if (widget is AppWidget) {
|
||||||
|
|||||||
@ -62,7 +62,7 @@ fun WidgetItem(
|
|||||||
appWidgetHost: AppWidgetHost,
|
appWidgetHost: AppWidgetHost,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
editMode: Boolean = false,
|
editMode: Boolean = false,
|
||||||
onWidgetAdd: (widget: Widget) -> Unit = {},
|
onWidgetAdd: (widget: Widget, offset: Int) -> Unit = {_, _ ->},
|
||||||
onWidgetUpdate: (widget: Widget) -> Unit = {},
|
onWidgetUpdate: (widget: Widget) -> Unit = {},
|
||||||
onWidgetRemove: () -> Unit = {},
|
onWidgetRemove: () -> Unit = {},
|
||||||
draggableState: DraggableState = rememberDraggableState {},
|
draggableState: DraggableState = rememberDraggableState {},
|
||||||
@ -163,13 +163,7 @@ fun WidgetItem(
|
|||||||
is NotesWidget -> {
|
is NotesWidget -> {
|
||||||
NotesWidget(
|
NotesWidget(
|
||||||
widget,
|
widget,
|
||||||
onNewNote = {
|
onWidgetAdd = onWidgetAdd,
|
||||||
val newWidget = NotesWidget(
|
|
||||||
id = UUID.randomUUID(),
|
|
||||||
widget.config.copy(storedText = "")
|
|
||||||
)
|
|
||||||
onWidgetAdd(newWidget)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,12 @@ package de.mm20.launcher2.ui.launcher.widgets.notes
|
|||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Add
|
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.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.SnackbarDuration
|
||||||
|
import androidx.compose.material3.SnackbarResult
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@ -26,24 +32,35 @@ 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.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import de.mm20.launcher2.ui.R
|
import de.mm20.launcher2.ui.R
|
||||||
import de.mm20.launcher2.ui.component.markdown.MarkdownEditor
|
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.NotesWidget
|
||||||
|
import de.mm20.launcher2.widgets.Widget
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NotesWidget(
|
fun NotesWidget(
|
||||||
widget: NotesWidget,
|
widget: NotesWidget,
|
||||||
onNewNote: () -> Unit,
|
onWidgetAdd: (widget: Widget, offset: Int) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val snackbarHostState = LocalSnackbarHostState.current
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
|
||||||
val viewModel: NotesWidgetVM =
|
val viewModel: NotesWidgetVM =
|
||||||
viewModel(key = "notes-widget-${widget.id}", factory = NotesWidgetVM.Factory)
|
viewModel(key = "notes-widget-${widget.id}", factory = NotesWidgetVM.Factory)
|
||||||
|
|
||||||
|
val isLastWidget by viewModel.isLastNoteWidget.collectAsState(null)
|
||||||
|
|
||||||
LaunchedEffect(widget) {
|
LaunchedEffect(widget) {
|
||||||
viewModel.updateWidget(widget)
|
viewModel.updateWidget(widget)
|
||||||
}
|
}
|
||||||
@ -57,20 +74,45 @@ fun NotesWidget(
|
|||||||
|
|
||||||
val text by viewModel.noteText
|
val text by viewModel.noteText
|
||||||
Column {
|
Column {
|
||||||
MarkdownEditor(
|
Row(
|
||||||
value = text,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
onValueChange = { viewModel.setText(it) },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.heightIn(min = 64.dp)
|
||||||
.padding(16.dp),
|
.animateContentSize(),
|
||||||
placeholder = {
|
) {
|
||||||
Text(stringResource(R.string.notes_widget_placeholder))
|
MarkdownEditor(
|
||||||
|
value = text,
|
||||||
|
onValueChange = { viewModel.setText(it) },
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(16.dp),
|
||||||
|
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()) {
|
AnimatedVisibility(text.isNotBlank()) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
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 {
|
Box {
|
||||||
IconButton(onClick = { showMenu = true }) {
|
IconButton(onClick = { showMenu = true }) {
|
||||||
Icon(Icons.Rounded.MoreVert, null)
|
Icon(Icons.Rounded.MoreVert, null)
|
||||||
@ -82,7 +124,11 @@ fun NotesWidget(
|
|||||||
Icon(Icons.Rounded.Add, null)
|
Icon(Icons.Rounded.Add, null)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
onNewNote()
|
val newWidget = NotesWidget(
|
||||||
|
id = UUID.randomUUID(),
|
||||||
|
widget.config.copy(storedText = "")
|
||||||
|
)
|
||||||
|
onWidgetAdd(newWidget, 1)
|
||||||
showMenu = false
|
showMenu = false
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -103,11 +149,39 @@ fun NotesWidget(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Dismiss") },
|
text = { Text(stringResource(R.string.notes_widget_action_dismiss)) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(Icons.Rounded.Delete, null)
|
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.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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 kotlinx.coroutines.launch
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.get
|
import org.koin.core.component.get
|
||||||
@ -25,6 +28,10 @@ class NotesWidgetVM(
|
|||||||
|
|
||||||
val noteText = mutableStateOf(widget.value?.config?.storedText ?: "")
|
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) {
|
fun updateWidget(widget: NotesWidget) {
|
||||||
val oldId = this.widget.value?.id
|
val oldId = this.widget.value?.id
|
||||||
this.widget.value = widget
|
this.widget.value = widget
|
||||||
@ -53,6 +60,11 @@ class NotesWidgetVM(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dismissNote() {
|
||||||
|
widgetsService.removeWidget(widget.value ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
companion object: KoinComponent {
|
companion object: KoinComponent {
|
||||||
val Factory = viewModelFactory {
|
val Factory = viewModelFactory {
|
||||||
initializer {
|
initializer {
|
||||||
|
|||||||
@ -51,4 +51,7 @@ interface WidgetDao {
|
|||||||
@Query("SELECT EXISTS(SELECT 1 FROM Widget WHERE type = :type)")
|
@Query("SELECT EXISTS(SELECT 1 FROM Widget WHERE type = :type)")
|
||||||
fun exists(type: String): Flow<Boolean>
|
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 set(widgets: List<Widget>, parentId: UUID? = null)
|
||||||
|
|
||||||
fun exists(type: String): Flow<Boolean>
|
fun exists(type: String): Flow<Boolean>
|
||||||
|
fun count(type: String): Flow<Int>
|
||||||
|
|
||||||
suspend fun export(toDir: File)
|
suspend fun export(toDir: File)
|
||||||
suspend fun import(fromDir: File)
|
suspend fun import(fromDir: File)
|
||||||
@ -85,6 +86,12 @@ internal class WidgetRepositoryImpl(
|
|||||||
return dao.exists(type = type)
|
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) {
|
override suspend fun export(toDir: File) = withContext(Dispatchers.IO) {
|
||||||
val dao = database.backupDao()
|
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 {
|
companion object {
|
||||||
const val AppWidgetHostId = 44203
|
const val AppWidgetHostId = 44203
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user