diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml index 96637de..0829d0e 100644 --- a/.idea/appInsightsSettings.xml +++ b/.idea/appInsightsSettings.xml @@ -8,7 +8,7 @@ + + + + + + + diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index e55c309..efbce73 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ DataTable( } ``` -Draw a paginated table +Draw a paginated table with fixed size ```kotlin PaginatedDataTable( columns = listOf( @@ -70,7 +70,7 @@ PaginatedDataTable( Text("Column3") }, ), - state = rememberPaginatedDataTableState(5), + state = rememberPaginatedDataTableState(initialSize = PageSize.FixedSize(5)), ) { for (rowIndex in 0 until 100) { row { @@ -88,3 +88,39 @@ PaginatedDataTable( } } ``` + + +Draw paginated table with dynamic size (fill available height) + +```kotlin +PaginatedDataTable( + columns = listOf( + DataColumn { + Text("Column1") + }, + DataColumn { + Text("Column2") + }, + DataColumn { + Text("Column3") + }, + ), + state = rememberPaginatedDataTableState(initialSize = PageSize.FillMaxHeight), +) { + for (rowIndex in 0 until 100) { + row { + onClick = { println("Row clicked: $rowIndex") } + cell { + Text("Row $rowIndex, column 1") + } + cell { + Text("Row $rowIndex, column 2") + } + cell { + Text("Row $rowIndex, column 3") + } + } + } +} +``` + diff --git a/datatable-material3/src/commonMain/kotlin/com/seanproctor/datatable/material3/PaginatedDataTable.kt b/datatable-material3/src/commonMain/kotlin/com/seanproctor/datatable/material3/PaginatedDataTable.kt index d56805f..c7c9bd5 100644 --- a/datatable-material3/src/commonMain/kotlin/com/seanproctor/datatable/material3/PaginatedDataTable.kt +++ b/datatable-material3/src/commonMain/kotlin/com/seanproctor/datatable/material3/PaginatedDataTable.kt @@ -3,6 +3,7 @@ package com.seanproctor.datatable.material3 import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -12,15 +13,20 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.seanproctor.datatable.DataColumn import com.seanproctor.datatable.DataTableScope import com.seanproctor.datatable.paging.BasicPaginatedDataTable import com.seanproctor.datatable.paging.PaginatedDataTableState +import com.seanproctor.datatable.paging.rememberPaginatedDataTableState import com.seanproctor.datatable_material3.generated.resources.Res import com.seanproctor.datatable_material3.generated.resources.chevron_left import com.seanproctor.datatable_material3.generated.resources.chevron_right @@ -55,15 +61,15 @@ fun PaginatedDataTable( headerBackgroundColor = headerBackgroundColor, footerBackgroundColor = footerBackgroundColor, state = state, - footer = { + footer = { pageSize -> Row( modifier = Modifier.height(rowHeight).padding(horizontal = 16.dp).fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp, alignment = Alignment.End), verticalAlignment = Alignment.CenterVertically, ) { - val start = min(state.pageIndex * state.pageSize + 1, state.count) - val end = min(start + state.pageSize - 1, state.count) - val pageCount = (state.count + state.pageSize - 1) / state.pageSize + val start = min(state.pageIndex * pageSize + 1, state.count) + val end = min(start + pageSize - 1, state.count) + val pageCount = (state.count + pageSize - 1) / pageSize Text("$start-$end of ${state.count}") IconButton( onClick = { state.pageIndex = 0 }, diff --git a/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/BasicPaginatedDataTable.kt b/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/BasicPaginatedDataTable.kt index b0946e2..66b6266 100644 --- a/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/BasicPaginatedDataTable.kt +++ b/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/BasicPaginatedDataTable.kt @@ -1,12 +1,25 @@ package com.seanproctor.datatable.paging +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.runtime.* +import androidx.compose.foundation.layout.fillMaxSize +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.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import com.seanproctor.datatable.* +import com.seanproctor.datatable.BasicDataTable +import com.seanproctor.datatable.CellContentProvider +import com.seanproctor.datatable.DataColumn +import com.seanproctor.datatable.DataTableScope +import com.seanproctor.datatable.DataTableState +import com.seanproctor.datatable.DefaultCellContentProvider @Composable fun BasicPaginatedDataTable( @@ -19,34 +32,56 @@ fun BasicPaginatedDataTable( contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp), headerBackgroundColor: Color = Color.Unspecified, footerBackgroundColor: Color = Color.Unspecified, - footer: @Composable () -> Unit = { }, + footer: @Composable (Int) -> Unit = { }, cellContentProvider: CellContentProvider = DefaultCellContentProvider, sortColumnIndex: Int? = null, sortAscending: Boolean = true, logger: ((String) -> Unit)? = null, content: DataTableScope.() -> Unit ) { - BasicDataTable( - columns = columns, - modifier = modifier, - state = remember(state.pageSize, state.pageIndex) { DataTableState() }, - separator = separator, - headerHeight = headerHeight, - rowHeight = rowHeight, - contentPadding = contentPadding, - headerBackgroundColor = headerBackgroundColor, - footerBackgroundColor = footerBackgroundColor, - footer = footer, - cellContentProvider = cellContentProvider, - sortColumnIndex = sortColumnIndex, - sortAscending = sortAscending, - logger = logger, + var pageSize by remember { mutableStateOf(state.pageSize) } + val density = LocalDensity.current + + Box( + Modifier + .fillMaxSize() + .onGloballyPositioned { coords -> + /** + * The table size is calculated to fit the screen height. + * This is if pageSize is equal to 'PAGE_SIZE_FIXED_FLAG (-1)' + * + * Otherwise, the table has a fixed size + */ + if (state.pageSize == PAGE_SIZE_FIXED_FLAG) { + val heightPx = coords.size.height.toFloat() + val rowHeightPx = with(density) { rowHeight.toPx() } + val rows = (heightPx / rowHeightPx).toInt() + pageSize = rows - 3 + } + } ) { - val start = state.pageIndex * state.pageSize - val scope = PaginatedRowScope(start, start + state.pageSize, this) - content(scope) - if (state.count != scope.index) { - state.count = scope.index + BasicDataTable( + columns = columns, + modifier = modifier, + state = remember(pageSize, state.pageIndex) { DataTableState() }, + separator = separator, + headerHeight = headerHeight, + rowHeight = rowHeight, + contentPadding = contentPadding, + headerBackgroundColor = headerBackgroundColor, + footerBackgroundColor = footerBackgroundColor, + footer = { footer(pageSize) }, + cellContentProvider = cellContentProvider, + sortColumnIndex = sortColumnIndex, + sortAscending = sortAscending, + logger = logger, + ) { + val start = state.pageIndex * pageSize + val scope = PaginatedRowScope(start, start + pageSize, this) + content(scope) + if (state.count != scope.index) { + state.count = scope.index + } } } } diff --git a/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/PaginatedDataTableState.kt b/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/PaginatedDataTableState.kt index ad70c54..a037ff8 100644 --- a/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/PaginatedDataTableState.kt +++ b/datatable/src/commonMain/kotlin/com/seanproctor/datatable/paging/PaginatedDataTableState.kt @@ -1,9 +1,20 @@ package com.seanproctor.datatable.paging -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue + + +/** + * PAGE_SIZE_FIXED_FLAG (-1) indicates dynamic page size. + * This is only used internally for automatic calculation. + */ +const val PAGE_SIZE_FIXED_FLAG = -1 interface PaginatedDataTableState { var pageSize: Int @@ -33,11 +44,18 @@ private class PaginatedDataTableStateImpl ( @Composable fun rememberPaginatedDataTableState( - initialPageSize: Int, + initialSize: PageSize, initialPageIndex: Int = 0, initialCount: Int = 0, -): PaginatedDataTableState { + ): PaginatedDataTableState { return rememberSaveable(saver = PaginatedDataTableStateImpl.Saver) { - PaginatedDataTableStateImpl(initialPageSize, initialPageIndex, initialCount) + val pageSizeValue = if (initialSize is PageSize.FixedSize) initialSize.initialPageSize else PAGE_SIZE_FIXED_FLAG + PaginatedDataTableStateImpl(pageSizeValue, initialPageIndex, initialCount) } -} \ No newline at end of file +} + +sealed class PageSize { + data object FillMaxHeight : PageSize() + data class FixedSize(val initialPageSize: Int) : PageSize() +} + diff --git a/demo-common/src/commonMain/kotlin/com/seanproctor/datatable/demo/App.kt b/demo-common/src/commonMain/kotlin/com/seanproctor/datatable/demo/App.kt index 3d54cf7..6b09282 100644 --- a/demo-common/src/commonMain/kotlin/com/seanproctor/datatable/demo/App.kt +++ b/demo-common/src/commonMain/kotlin/com/seanproctor/datatable/demo/App.kt @@ -29,6 +29,7 @@ import com.seanproctor.datatable.TableColumnWidth import com.seanproctor.datatable.material3.DataTable import com.seanproctor.datatable.material3.LazyPaginatedDataTable import com.seanproctor.datatable.material3.PaginatedDataTable +import com.seanproctor.datatable.paging.PageSize import com.seanproctor.datatable.paging.rememberPaginatedDataTableState import io.github.oikvpqya.compose.fastscroller.HorizontalScrollbar import io.github.oikvpqya.compose.fastscroller.VerticalScrollbar @@ -135,7 +136,7 @@ fun App(onRowClick: (Int) -> Unit) { 1 -> { PaginatedDataTable( columns = columns, - state = rememberPaginatedDataTableState(5), + state = rememberPaginatedDataTableState(initialSize = PageSize.FixedSize(2)), sortColumnIndex = sortColumnIndex, sortAscending = sortAscending, modifier = Modifier.fillMaxWidth().padding(16.dp), @@ -152,7 +153,7 @@ fun App(onRowClick: (Int) -> Unit) { else -> { LazyPaginatedDataTable( columns = columns, - state = rememberPaginatedDataTableState(initialPageSize = 5, initialCount = sortedData.size), + state = rememberPaginatedDataTableState(initialSize = PageSize.FillMaxHeight, initialCount = sortedData.size), sortColumnIndex = sortColumnIndex, sortAscending = sortAscending, modifier = Modifier.fillMaxWidth().padding(16.dp),