diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt index 51374d4..5747647 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt @@ -9,10 +9,10 @@ import io.github.wiiznokes.gitnote.ui.model.Cred import io.github.wiiznokes.gitnote.ui.model.CredType import io.github.wiiznokes.gitnote.ui.model.GitAuthor import io.github.wiiznokes.gitnote.ui.model.NoteMinWidth +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.model.SortOrder import io.github.wiiznokes.gitnote.ui.model.StorageConfiguration import io.github.wiiznokes.gitnote.ui.theme.Theme -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.runBlocking import kotlin.io.path.pathString @@ -140,6 +140,7 @@ class AppPreferences( val noteMinWidth = enumPreference("noteMinWidth", NoteMinWidth.Default) val showFullNoteHeight = booleanPreference("showFullNoteHeight", false) + val noteViewType = enumPreference("noteViewType", NoteViewType.Grid) val rememberLastOpenedFolder = booleanPreference("rememberLastOpenedFolder", false) val lastOpenedFolder = stringPreference("lastOpenedFolder", "") diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt index ad56a32..74fd2c6 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/Dao.kt @@ -1,7 +1,6 @@ package io.github.wiiznokes.gitnote.data.room import android.util.Log -import android.webkit.MimeTypeMap import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt index 280397e..aabe62c 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/room/RepoDatabase.kt @@ -27,7 +27,7 @@ abstract class RepoDatabase : RoomDatabase() { abstract val repoDatabaseDao: RepoDatabaseDao companion object { - fun generateUid() = Random.Default.nextInt() + fun generateUid() = Random.nextInt() fun buildDatabase(context: Context): RepoDatabase { val onMigration = object : Callback() { diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt index 7ac898c..12ae7db 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/component/CustomDropDown.kt @@ -16,10 +16,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalResources +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import androidx.core.util.TypedValueCompat.pxToDp private val TAG = "CustomDropDown" @@ -41,10 +40,10 @@ fun CustomDropDown( mutableStateOf(Offset.Zero) } ) { - val m = LocalResources.current.displayMetrics - val x = pxToDp(clickPosition.value.x, m).dp - val y = pxToDp(clickPosition.value.y, m).dp - val offset = DpOffset(x, y) + val density = LocalDensity.current + val offset = with(density) { + DpOffset(clickPosition.value.x.toDp(), clickPosition.value.y.toDp()) + } MaterialTheme( shapes = MaterialTheme.shapes.copy(extraSmall = shape) @@ -75,4 +74,4 @@ fun CustomDropDown( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt index 328bbc8..8f35ef3 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Grid.kt @@ -34,9 +34,14 @@ enum class NoteMinWidth(val size: Int) { override fun toString(): String = this.size.toString() } +enum class NoteViewType { + Grid, + List, +} + data class GridNote( @Embedded val note: Note, val isUnique: Boolean, val selected: Boolean = false, -) \ No newline at end of file +) diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt index 14dd422..140a056 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt @@ -39,7 +39,8 @@ enum class CredType { @Parcelize sealed class StorageConfiguration : Parcelable { data object App : StorageConfiguration() - class Device(var path: String, val useUrlForRootFolder: Boolean = false) : StorageConfiguration() + class Device(var path: String, val useUrlForRootFolder: Boolean = false) : + StorageConfiguration() fun repoPath(): String { return when (this) { diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt index 3c65095..8e3f557 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/edit/EditScreen.kt @@ -1,6 +1,5 @@ package io.github.wiiznokes.gitnote.ui.screen.app.edit -import android.webkit.MimeTypeMap import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -49,7 +48,6 @@ import io.github.wiiznokes.gitnote.manager.extensionType import io.github.wiiznokes.gitnote.ui.component.SimpleIcon import io.github.wiiznokes.gitnote.ui.destination.EditParams import io.github.wiiznokes.gitnote.ui.model.EditType -import io.github.wiiznokes.gitnote.ui.model.FileExtension import io.github.wiiznokes.gitnote.ui.viewmodel.edit.MarkDownVM import io.github.wiiznokes.gitnote.ui.viewmodel.edit.TextVM import io.github.wiiznokes.gitnote.ui.viewmodel.edit.newEditViewModel diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt index 55d85f0..407ffb4 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/GridScreen.kt @@ -17,6 +17,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeContent import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan @@ -44,7 +46,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -63,14 +64,16 @@ import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems -import dev.jeziellago.compose.markdowntext.MarkdownText import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.data.room.Note import io.github.wiiznokes.gitnote.ui.component.CustomDropDown import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.model.EditType import io.github.wiiznokes.gitnote.ui.model.FileExtension +import io.github.wiiznokes.gitnote.ui.model.GridNote +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.screen.app.DrawerScreen import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel @@ -80,6 +83,8 @@ private const val TAG = "GridScreen" private const val maxOffset = -500f internal val topBarHeight = 80.dp +internal val topSpacerHeight = topBarHeight + 40.dp + 15.dp + @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun GridScreen( @@ -112,6 +117,8 @@ fun GridScreen( } } + val noteViewType by vm.prefs.noteViewType.getAsState() + val searchFocusRequester = remember { FocusRequester() } val fabExpanded = remember { @@ -148,7 +155,8 @@ fun GridScreen( onEditClick = onEditClick, selectedNotes = selectedNotes, nestedScrollConnection = nestedScrollConnection, - padding = padding + padding = padding, + noteViewType = noteViewType, ) TopBar( @@ -181,24 +189,11 @@ private fun GridView( onEditClick: (Note, EditType) -> Unit, selectedNotes: List, padding: PaddingValues, + noteViewType: NoteViewType, ) { val gridNotes = vm.gridNotes.collectAsLazyPagingItems() - - val gridState = rememberLazyStaggeredGridState() - - val query = vm.query.collectAsState() - var lastQuery: String = rememberSaveable { query.value } - - if (lastQuery != query.value) { - @Suppress("UNUSED_VALUE") - lastQuery = query.value - LaunchedEffect(null) { - gridState.animateScrollToItem(index = 0) - } - } - val isRefreshing by vm.isRefreshing.collectAsStateWithLifecycle() val pullRefreshState = rememberPullRefreshState(isRefreshing, { @@ -206,160 +201,53 @@ private fun GridView( vm.refresh() }) + val showFullPathOfNotes = vm.prefs.showFullPathOfNotes.getAsState() + Box { // todo: scroll even when there is nothing to scroll // todo: add scroll bar - val noteMinWidth = vm.prefs.noteMinWidth.getAsState() - val showFullPathOfNotes = vm.prefs.showFullPathOfNotes.getAsState() - val showFullNoteHeight = vm.prefs.showFullNoteHeight.getAsState() + val commonModifier = Modifier + .fillMaxSize() + .pullRefresh(pullRefreshState) + .nestedScroll(nestedScrollConnection) - LazyVerticalStaggeredGrid( - modifier = Modifier - .fillMaxSize() - .pullRefresh(pullRefreshState) - .nestedScroll(nestedScrollConnection), contentPadding = PaddingValues( - horizontal = 3.dp - ), columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), state = gridState + when (noteViewType) { + NoteViewType.Grid -> { + val gridState = rememberLazyStaggeredGridState() - ) { + LaunchedEffect(query.value) { + gridState.animateScrollToItem(index = 0) + } - item( - span = StaggeredGridItemSpan.FullLine - ) { - Spacer(modifier = Modifier.height(topBarHeight + 40.dp + 15.dp)) + GridNotesView( + gridNotes = gridNotes, + gridState = gridState, + modifier = commonModifier, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes.value, + onEditClick = onEditClick, + vm = vm, + ) } + NoteViewType.List -> { + val listState = rememberLazyListState() - items( - count = gridNotes.itemCount, - key = { index -> - val note = gridNotes[index]!! - note.note.id - } - ) { index -> - val gridNote = gridNotes[index]!! - - val dropDownExpanded = remember { - mutableStateOf(false) + LaunchedEffect(query.value) { + listState.animateScrollToItem(index = 0) } - val clickPosition = remember { - mutableStateOf(Offset.Zero) - } - - Card( - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surface - ), - border = if (dropDownExpanded.value) { - BorderStroke( - width = 2.dp, color = MaterialTheme.colorScheme.primary - ) - } else if (gridNote.selected) { - BorderStroke( - width = 2.dp, color = MaterialTheme.colorScheme.onSurface - ) - } else { - BorderStroke( - width = 1.dp, - color = MaterialTheme.colorScheme.surfaceColorAtElevation(1000.dp) - ) - }, - modifier = Modifier - .sizeIn( - maxHeight = if (showFullNoteHeight.value) Dp.Unspecified else 500.dp - ) - .padding(3.dp) - .combinedClickable(onLongClick = { - dropDownExpanded.value = true - }, onClick = { - if (selectedNotes.isEmpty()) { - onEditClick( - gridNote.note, EditType.Update - ) - } else { - vm.selectNote( - gridNote.note, add = !gridNote.selected - ) - } - }) - .pointerInteropFilter { - clickPosition.value = Offset(it.x, it.y) - false - }, - ) { - Box { - - // need this box for clickPosition - Box { - CustomDropDown( - expanded = dropDownExpanded, - shape = MaterialTheme.shapes.medium, - options = listOf( - CustomDropDownModel( - text = stringResource(R.string.delete_this_note), - onClick = { - vm.deleteNote(gridNote.note) - }), - if (selectedNotes.isEmpty()) CustomDropDownModel( - text = stringResource( - R.string.select_multiple_notes - ), onClick = { - vm.selectNote(gridNote.note, true) - }) else null, - ), - clickPosition = clickPosition - ) - } - Column( - modifier = Modifier.padding(10.dp), - verticalArrangement = Arrangement.Top, - horizontalAlignment = Alignment.Start, - ) { - Text( - text = if (showFullPathOfNotes.value || !gridNote.isUnique) gridNote.note.relativePath else - gridNote.note.nameWithoutExtension(), - modifier = Modifier.padding(bottom = 6.dp), - overflow = TextOverflow.Ellipsis, - style = MaterialTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Bold, - ), - color = MaterialTheme.colorScheme.tertiary - ) - - if (gridNote.note.fileExtension() is FileExtension.Md) { -// MarkdownText( -// markdown = gridNote.note.content, -// disableLinkMovementMethod = true, -// isTextSelectable = false, -// onLinkClicked = { } -// ) - Text( - text = gridNote.note.content, - modifier = Modifier, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) - } else { - Text( - text = gridNote.note.content, - modifier = Modifier, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) - } - } - } - } - } - - - item( - span = StaggeredGridItemSpan.FullLine - ) { - Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) + NoteListView( + gridNotes = gridNotes, + listState = listState, + modifier = commonModifier, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes.value, + onEditClick = onEditClick, + vm = vm, + ) } } @@ -379,6 +267,195 @@ private fun GridView( } +@Composable +private fun GridNotesView( + gridNotes: LazyPagingItems, + gridState: LazyStaggeredGridState, + modifier: Modifier = Modifier, + selectedNotes: List, + showFullPathOfNotes: Boolean, + onEditClick: (Note, EditType) -> Unit, + vm: GridViewModel, +) { + + + val noteMinWidth = vm.prefs.noteMinWidth.getAsState() + val showFullNoteHeight = vm.prefs.showFullNoteHeight.getAsState() + + LazyVerticalStaggeredGrid( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 3.dp), + columns = StaggeredGridCells.Adaptive(noteMinWidth.value.size.dp), + state = gridState + ) { + item(span = StaggeredGridItemSpan.FullLine) { + Spacer(modifier = Modifier.height(topSpacerHeight)) + } + + items( + count = gridNotes.itemCount, + key = { index -> gridNotes[index]!!.note.id } + ) { index -> + val gridNote = gridNotes[index]!! + + NoteCard( + gridNote = gridNote, + vm = vm, + onEditClick = onEditClick, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes, + showFullNoteHeight = showFullNoteHeight.value, + modifier = Modifier.padding(3.dp) + ) + } + + item(span = StaggeredGridItemSpan.FullLine) { + Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) + } + } +} + +@Composable +private fun NoteCard( + gridNote: GridNote, + vm: GridViewModel, + onEditClick: (Note, EditType) -> Unit, + selectedNotes: List, + showFullPathOfNotes: Boolean, + showFullNoteHeight: Boolean, + modifier: Modifier = Modifier, +) { + val dropDownExpanded = remember { + mutableStateOf(false) + } + + val clickPosition = remember { + mutableStateOf(Offset.Zero) + } + + Card( + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surface + ), + border = if (dropDownExpanded.value) { + BorderStroke( + width = 2.dp, color = MaterialTheme.colorScheme.primary + ) + } else if (gridNote.selected) { + BorderStroke( + width = 2.dp, color = MaterialTheme.colorScheme.onSurface + ) + } else { + BorderStroke( + width = 1.dp, + color = MaterialTheme.colorScheme.surfaceColorAtElevation(1000.dp) + ) + }, + modifier = modifier + .sizeIn( + maxHeight = if (showFullNoteHeight) Dp.Unspecified else 500.dp + ) + .combinedClickable(onLongClick = { + dropDownExpanded.value = true + }, onClick = { + if (selectedNotes.isEmpty()) { + onEditClick( + gridNote.note, EditType.Update + ) + } else { + vm.selectNote( + gridNote.note, add = !gridNote.selected + ) + } + }) + .pointerInteropFilter { + clickPosition.value = Offset(it.x, it.y) + false + }, + ) { + Box { + + NoteActionsDropdown( + vm = vm, + gridNote = gridNote, + selectedNotes = selectedNotes, + dropDownExpanded = dropDownExpanded, + clickPosition = clickPosition + ) + + Column( + modifier = Modifier.padding(10.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start, + ) { + val title = if (showFullPathOfNotes || !gridNote.isUnique) { + gridNote.note.relativePath + } else { + gridNote.note.nameWithoutExtension() + } + Text( + text = title, + modifier = Modifier.padding(bottom = 6.dp), + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold, + ), + color = MaterialTheme.colorScheme.tertiary + ) + + if (gridNote.note.fileExtension() is FileExtension.Md) { +// MarkdownText( +// markdown = gridNote.note.content, +// disableLinkMovementMethod = true, +// isTextSelectable = false, +// onLinkClicked = { } +// ) + Text( + text = gridNote.note.content, + modifier = Modifier, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + } else { + Text( + text = gridNote.note.content, + modifier = Modifier, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) + } + } + } + } +} + +@Composable +internal fun NoteActionsDropdown( + vm: GridViewModel, + gridNote: GridNote, + selectedNotes: List, + dropDownExpanded: MutableState, + clickPosition: MutableState, +) { + + // need this box for clickPosition + Box { + CustomDropDown( + expanded = dropDownExpanded, + shape = MaterialTheme.shapes.medium, + options = listOf( + CustomDropDownModel( + text = stringResource(R.string.delete_this_note), + onClick = { vm.deleteNote(gridNote.note) }), + if (selectedNotes.isEmpty()) CustomDropDownModel( + text = stringResource(R.string.select_multiple_notes), + onClick = { vm.selectNote(gridNote.note, true) }) else null, + ), + clickPosition = clickPosition + ) + } +} + // https://stackoverflow.com/questions/73079388/android-jetpack-compose-keyboard-not-close // https://medium.com/@debdut.saha.1/top-app-bar-animation-using-nestedscrollconnection-like-facebook-jetpack-compose-b446c109ee52 // todo: fix scroll is blocked when the full size of the grid is the screen, @@ -430,4 +507,4 @@ private fun rememberNestedScrollConnection( } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt new file mode 100644 index 0000000..442c33e --- /dev/null +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/ListView.kt @@ -0,0 +1,173 @@ +package io.github.wiiznokes.gitnote.ui.screen.app.grid + +import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Description +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.surfaceColorAtElevation +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.pointerInteropFilter +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.paging.compose.LazyPagingItems +import io.github.wiiznokes.gitnote.data.room.Note +import io.github.wiiznokes.gitnote.ui.model.EditType +import io.github.wiiznokes.gitnote.ui.model.GridNote +import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel +import java.text.DateFormat +import java.util.Date + +@Composable +internal fun NoteListView( + gridNotes: LazyPagingItems, + listState: LazyListState, + modifier: Modifier = Modifier, + selectedNotes: List, + showFullPathOfNotes: Boolean, + onEditClick: (Note, EditType) -> Unit, + vm: GridViewModel, +) { + + LazyColumn( + modifier = modifier, + state = listState + ) { + item { + Spacer(modifier = Modifier.height(topSpacerHeight)) + } + + items( + count = gridNotes.itemCount, + key = { index -> + val note = gridNotes[index]!! + note.note.id + } + ) { index -> + val gridNote = gridNotes[index]!! + NoteListRow( + gridNote = gridNote, + vm = vm, + onEditClick = onEditClick, + selectedNotes = selectedNotes, + showFullPathOfNotes = showFullPathOfNotes, + ) + } + + item { + Spacer(modifier = Modifier.height(topBarHeight + 10.dp)) + } + } +} + +@Composable +private fun NoteListRow( + gridNote: GridNote, + vm: GridViewModel, + onEditClick: (Note, EditType) -> Unit, + selectedNotes: List, + showFullPathOfNotes: Boolean, +) { + val dropDownExpanded = remember { mutableStateOf(false) } + val clickPosition = remember { mutableStateOf(Offset.Zero) } + + val formattedDate = remember(gridNote.note.lastModifiedTimeMillis) { + DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT) + .format(Date(gridNote.note.lastModifiedTimeMillis)) + } + + val title = gridNote.note.relativePath + + val rowBackground = + if (gridNote.selected) MaterialTheme.colorScheme.surfaceColorAtElevation(6.dp) + else MaterialTheme.colorScheme.surface + + Column( + modifier = Modifier + .fillMaxWidth() + .background(rowBackground) + .combinedClickable( + onLongClick = { dropDownExpanded.value = true }, + onClick = { + if (selectedNotes.isEmpty()) { + onEditClick(gridNote.note, EditType.Update) + } else { + vm.selectNote(gridNote.note, add = !gridNote.selected) + } + } + ) + .pointerInteropFilter { + clickPosition.value = Offset(it.x, it.y) + false + } + ) { + Box { + NoteActionsDropdown( + vm = vm, + gridNote = gridNote, + selectedNotes = selectedNotes, + dropDownExpanded = dropDownExpanded, + clickPosition = clickPosition + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Icon( + imageVector = Icons.Rounded.Description, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurface + ) + + Column( + verticalArrangement = Arrangement.Center + ) { + Text( + text = title, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Bold, + ), + color = MaterialTheme.colorScheme.onSurface + ) + Text( + text = formattedDate, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } + + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.surfaceColorAtElevation(80.dp) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt index fe95cf1..0699567 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/app/grid/TopGrid.kt @@ -1,6 +1,5 @@ package io.github.wiiznokes.gitnote.ui.screen.app.grid -import android.util.Log import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility @@ -23,12 +22,14 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.ViewList import androidx.compose.material.icons.filled.CloudDone import androidx.compose.material.icons.filled.CloudDownload import androidx.compose.material.icons.filled.CloudUpload import androidx.compose.material.icons.rounded.Close import androidx.compose.material.icons.rounded.Menu import androidx.compose.material.icons.rounded.MoreVert +import androidx.compose.material.icons.rounded.ViewModule import androidx.compose.material3.DrawerState import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -66,6 +67,7 @@ import io.github.wiiznokes.gitnote.manager.SyncState import io.github.wiiznokes.gitnote.ui.component.CustomDropDown import io.github.wiiznokes.gitnote.ui.component.CustomDropDownModel import io.github.wiiznokes.gitnote.ui.component.SimpleIcon +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import io.github.wiiznokes.gitnote.ui.viewmodel.GridViewModel import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -123,8 +125,7 @@ private fun SearchBar( ) { - var queryTextField by remember { - Log.d(TAG, "") + val queryTextField = remember { mutableStateOf( TextFieldValue( text = vm.query.value, @@ -135,12 +136,13 @@ private fun SearchBar( val focusManager = LocalFocusManager.current fun clearQuery() { - queryTextField = TextFieldValue("") + queryTextField.value = TextFieldValue("") vm.clearQuery() focusManager.clearFocus() } val query = vm.query.collectAsState() + val noteViewType = vm.prefs.noteViewType.getAsState() if (query.value.isNotEmpty()) { BackHandler { clearQuery() @@ -156,9 +158,9 @@ private fun SearchBar( .padding(top = 15.dp) .offset { IntOffset(x = 0, y = offset.roundToInt()) } .focusRequester(searchFocusRequester), - value = queryTextField, + value = queryTextField.value, onValueChange = { - queryTextField = it + queryTextField.value = it vm.search(it.text) }, colors = TextFieldDefaults.colors( @@ -190,24 +192,45 @@ private fun SearchBar( ) } }, - trailingIcon = if (queryTextField.text.isEmpty()) { - { - Row( - verticalAlignment = Alignment.CenterVertically - ) { + trailingIcon = { + Row( + verticalAlignment = Alignment.CenterVertically + ) { - val syncState = vm.syncState.collectAsState() + val isEmpty = queryTextField.value.text.isEmpty() - SyncStateIcon(syncState.value, { + if (isEmpty) { + val syncState = vm.syncState.collectAsState() + SyncStateIcon(syncState.value) { vm.consumeOkSyncState() - }) + } + } + + IconButton( + onClick = { vm.toggleViewType() } + ) { + SimpleIcon( + imageVector = if (noteViewType.value == NoteViewType.Grid) { + Icons.AutoMirrored.Rounded.ViewList + } else { + Icons.Rounded.ViewModule + }, + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + if (noteViewType.value == NoteViewType.Grid) { + R.string.switch_to_list_view + } else { + R.string.switch_to_grid_view + } + ) + ) + } + if (isEmpty) { Box { val expanded = remember { mutableStateOf(false) } IconButton( - onClick = { - expanded.value = true - } + onClick = { expanded.value = true } ) { SimpleIcon( imageVector = Icons.Rounded.MoreVert, @@ -217,7 +240,7 @@ private fun SearchBar( val readOnlyMode = vm.prefs.isReadOnlyModeActive.getAsState().value - @Suppress("KotlinConstantConditions") + @Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants") CustomDropDown( expanded = expanded, options = listOf( @@ -245,18 +268,16 @@ private fun SearchBar( ) } } - } - } else { - { - IconButton( - onClick = { - clearQuery() + + if (!isEmpty) { + IconButton( + onClick = { clearQuery() } + ) { + SimpleIcon( + imageVector = Icons.Rounded.Close, + tint = MaterialTheme.colorScheme.onSurface + ) } - ) { - SimpleIcon( - imageVector = Icons.Rounded.Close, - tint = MaterialTheme.colorScheme.onSurface - ) } } } @@ -410,4 +431,4 @@ private fun SyncStateIcon( modifier = modifier, ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt index 0bd5e95..96c6b21 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/SetupNav.kt @@ -96,7 +96,10 @@ fun SetupNav( ) }, onFinish = { path, useUrlForRootFolder -> - val storageConfig = StorageConfiguration.Device(path, useUrlForRootFolder = useUrlForRootFolder) + val storageConfig = StorageConfiguration.Device( + path, + useUrlForRootFolder = useUrlForRootFolder + ) when (setupDestination.newRepoMethod) { NewRepoMethod.Create -> vm.createLocalRepo( diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt index 6066d60..4a9ee09 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/AuthorizeGitNoteScreen.kt @@ -3,8 +3,6 @@ package io.github.wiiznokes.gitnote.ui.screen.setup.remote import android.content.Intent import android.util.Log import androidx.compose.foundation.layout.Arrangement -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt index 440473f..5c75105 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/setup/remote/SelectGenerateNewSshKeysScreen.kt @@ -1,8 +1,6 @@ package io.github.wiiznokes.gitnote.ui.screen.setup.remote import androidx.compose.foundation.layout.Arrangement -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.res.stringResource @@ -12,7 +10,6 @@ import io.github.wiiznokes.gitnote.ui.component.AppPage import io.github.wiiznokes.gitnote.ui.component.SetupButton import io.github.wiiznokes.gitnote.ui.component.SetupLine import io.github.wiiznokes.gitnote.ui.component.SetupPage -import io.github.wiiznokes.gitnote.ui.component.SimpleButton @Composable diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt index a2205fc..71a4754 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt @@ -17,6 +17,7 @@ import io.github.wiiznokes.gitnote.data.room.RepoDatabase import io.github.wiiznokes.gitnote.helper.NameValidation import io.github.wiiznokes.gitnote.manager.StorageManager import io.github.wiiznokes.gitnote.ui.model.FileExtension +import io.github.wiiznokes.gitnote.ui.model.NoteViewType import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -157,6 +158,17 @@ class GridViewModel : ViewModel() { _selectedNotes.emit(emptyList()) } + fun toggleViewType() { + viewModelScope.launch { + val next = if (prefs.noteViewType.get() == NoteViewType.Grid) { + NoteViewType.List + } else { + NoteViewType.Grid + } + prefs.noteViewType.update(next) + } + } + fun deleteSelectedNotes() { CoroutineScope(Dispatchers.IO).launch { val currentSelectedNotes = selectedNotes.value @@ -254,4 +266,3 @@ class GridViewModel : ViewModel() { } } } - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57ab7ab..88a605f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,6 +69,9 @@ Folder already exist No modification Grid + List + Switch to list view + Switch to grid view About Choose method Create local repository