From 78f01e6a1b9ea5b41ec473383d5851f6e8d192cd Mon Sep 17 00:00:00 2001 From: andreia Date: Wed, 9 Sep 2026 15:44:34 +0200 Subject: [PATCH 1/3] add area to LoiJobSheet --- .../HomeScreenMapContainerViewModel.kt | 10 ++ .../jobs/DataCollectionEntryPointData.kt | 1 + .../ui/home/mapcontainer/jobs/LoiJobSheet.kt | 109 ++++++++++++------ .../HomeScreenMapContainerViewModelTest.kt | 30 +++++ .../home/mapcontainer/jobs/LoiJobSheetTest.kt | 18 +++ 5 files changed, 130 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt index ba8d34e36e..1760326acc 100644 --- a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt @@ -63,9 +63,11 @@ import org.groundplatform.domain.repository.SurveyRepositoryInterface import org.groundplatform.domain.repository.UserRepositoryInterface import org.groundplatform.domain.usecases.GetLoiReportUseCase import org.groundplatform.domain.usecases.survey.GetDataSharingTermsUseCase +import org.groundplatform.domain.usecases.user.GetUserSettingsUseCase import org.groundplatform.domain.util.Constants.CLUSTERING_ZOOM_THRESHOLD import org.groundplatform.feature.pdf.LoiReportExporter import org.groundplatform.ui.components.loireport.LoiReportAction +import org.groundplatform.ui.util.getFormattedArea import timber.log.Timber @OptIn(ExperimentalCoroutinesApi::class) @@ -85,6 +87,7 @@ internal constructor( private val userRepository: UserRepositoryInterface, private val locationOfInterestHelper: LocationOfInterestHelper, private val getLoiReportUseCase: GetLoiReportUseCase, + private val getUserSettingsUseCase: GetUserSettingsUseCase, private val loiReportExporter: LoiReportExporter, ) : BaseMapViewModel( @@ -233,6 +236,13 @@ internal constructor( submissionCount = submissionRepository.getTotalSubmissionCount(loi), showDeleteLoiButton = canDelete, loiReport = loiReport, + formattedArea = + loi.geometry + .area() + .takeIf { it > 0.0 } + ?.let { + getFormattedArea(it, getUserSettingsUseCase().measurementUnits) + }, ) } diff --git a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/DataCollectionEntryPointData.kt b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/DataCollectionEntryPointData.kt index 8a9b289a31..1a8d031e91 100644 --- a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/DataCollectionEntryPointData.kt +++ b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/DataCollectionEntryPointData.kt @@ -31,6 +31,7 @@ data class SelectedLoiSheetData( val submissionCount: Int, val showDeleteLoiButton: Boolean, val loiReport: LoiReport?, + val formattedArea: String? = null, ) : DataCollectionEntryPointData data class AdHocDataCollectionButtonData(override val canCollectData: Boolean, val job: Job) : diff --git a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt index 96580404d0..fd5eb26e65 100644 --- a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt +++ b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonObject import org.groundplatform.android.R import org.groundplatform.android.ui.common.ExcludeFromJacocoGeneratedReport import org.groundplatform.android.ui.common.LocationOfInterestHelper @@ -58,6 +59,7 @@ import org.groundplatform.domain.model.job.Job import org.groundplatform.domain.model.job.Style import org.groundplatform.domain.model.locationofinterest.AuditInfo import org.groundplatform.domain.model.locationofinterest.LocationOfInterest +import org.groundplatform.domain.model.locationofinterest.LoiReport import org.groundplatform.domain.model.task.Task import org.groundplatform.ui.components.ShareButton import org.groundplatform.ui.theme.AppTheme @@ -81,11 +83,7 @@ fun LoiJobSheet( dragHandle = { BottomSheetDefaults.DragHandle(width = 32.dp) }, ) { ModalContents( - loi = state.loi, - canUserSubmitData = state.canCollectData, - submissionCount = state.submissionCount, - showDeleteLoiButton = state.showDeleteLoiButton, - showShareButton = state.loiReport != null, + state = state, onDeleteClicked = onDeleteClicked, onCollectClicked = { scope.launch { sheetState.hide() }.invokeOnCompletion { onCollectClicked() } @@ -97,11 +95,7 @@ fun LoiJobSheet( @Composable private fun ModalContents( - loi: LocationOfInterest, - canUserSubmitData: Boolean, - submissionCount: Int, - showDeleteLoiButton: Boolean, - showShareButton: Boolean, + state: SelectedLoiSheetData, onDeleteClicked: (() -> Unit)?, onCollectClicked: () -> Unit, onShareClicked: () -> Unit, @@ -111,19 +105,20 @@ private fun ModalContents( val showDeleteDialog = remember { mutableStateOf(false) } Column(modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 50.dp)) { - JobName(loiHelper = loiHelper, loi = loi) - LoiHeader(loiHelper = loiHelper, loi = loi) + JobName(loiHelper = loiHelper, loi = state.loi) + LoiHeader(loiHelper = loiHelper, loi = state.loi) SubmissionRow( - loi = loi, - submissionCount = submissionCount, - canUserSubmitData = canUserSubmitData, - showShareButton = showShareButton, + loi = state.loi, + formattedArea = state.formattedArea, + submissionCount = state.submissionCount, + canUserSubmitData = state.canCollectData, + showShareButton = state.loiReport != null, onCollectClicked = onCollectClicked, onShareClicked = onShareClicked, ) DeleteSiteSection( - showDeleteLoiButton = showDeleteLoiButton, - isPredefined = loi.isPredefined == true, + showDeleteLoiButton = state.showDeleteLoiButton, + isPredefined = state.loi.isPredefined == true, onClick = { showDeleteDialog.value = true }, ) } @@ -173,6 +168,7 @@ private fun LoiHeader(loiHelper: LocationOfInterestHelper, loi: LocationOfIntere @Composable private fun SubmissionRow( loi: LocationOfInterest, + formattedArea: String?, submissionCount: Int, canUserSubmitData: Boolean, showShareButton: Boolean, @@ -180,6 +176,13 @@ private fun SubmissionRow( onShareClicked: () -> Unit, ) { Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.Top) { + if (formattedArea != null) { + Text( + stringResource(R.string.area_message, formattedArea), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) + } Text( if (submissionCount <= 0) stringResource(R.string.no_submissions) else pluralStringResource(R.plurals.submission_count, submissionCount, submissionCount), @@ -241,6 +244,20 @@ private val user = User(id = "user", email = "user@email.com", displayName = "Us private val auditInfo = AuditInfo(user) private const val SURVEY_ID = "survey" private const val TASK_ID = "task 1" +private val loiReport = + LoiReport( + loiName = "Point A", + geoJson = JsonObject(mapOf()), + submissionDetails = + LoiReport.SubmissionDetails( + surveyName = "Test Survey", + userName = "John Doe", + userEmail = "john.doe@example.com", + submissions = emptyList(), + geometry = Point(Coordinates(0.0, 0.0)), + style = null, + ), + ) @SuppressLint("UnrememberedMutableState") @Composable @@ -258,11 +275,15 @@ private fun PreviewModalContentsWhenJobHasNoTasks() { ) AppTheme { ModalContents( - loi = loi, - canUserSubmitData = true, - submissionCount = 0, - showDeleteLoiButton = false, - showShareButton = true, + state = + SelectedLoiSheetData( + loi = loi, + canCollectData = true, + submissionCount = 0, + showDeleteLoiButton = false, + loiReport = loiReport, + formattedArea = "1.20 ha", + ), onDeleteClicked = null, onShareClicked = {}, onCollectClicked = {}, @@ -303,11 +324,15 @@ private fun PreviewModalContentsWhenUserCannotSubmitData() { ) AppTheme { ModalContents( - loi = loi, - canUserSubmitData = false, - submissionCount = 1, - showDeleteLoiButton = false, - showShareButton = true, + state = + SelectedLoiSheetData( + loi = loi, + canCollectData = false, + submissionCount = 1, + showDeleteLoiButton = false, + loiReport = loiReport, + formattedArea = "1.20 ha", + ), onDeleteClicked = null, onShareClicked = {}, onCollectClicked = {}, @@ -350,11 +375,15 @@ private fun PreviewModalContentsWhenJobHasTasks() { ) AppTheme { ModalContents( - loi = loi, - canUserSubmitData = true, - submissionCount = 20, - showDeleteLoiButton = false, - showShareButton = true, + state = + SelectedLoiSheetData( + loi = loi, + canCollectData = true, + submissionCount = 20, + showDeleteLoiButton = false, + loiReport = loiReport, + formattedArea = "1.20 ha", + ), onDeleteClicked = null, onShareClicked = {}, onCollectClicked = {}, @@ -397,11 +426,15 @@ private fun PreviewModalContentsWhenJobHasTasksAndIsPredefined() { ) AppTheme { ModalContents( - loi = loi, - canUserSubmitData = true, - submissionCount = 20, - showDeleteLoiButton = true, - showShareButton = true, + state = + SelectedLoiSheetData( + loi = loi, + canCollectData = true, + submissionCount = 20, + showDeleteLoiButton = true, + loiReport = loiReport, + formattedArea = "1.20 ha", + ), onDeleteClicked = null, onShareClicked = {}, onCollectClicked = {}, diff --git a/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModelTest.kt b/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModelTest.kt index e7966e1626..6446ad3db0 100644 --- a/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModelTest.kt +++ b/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModelTest.kt @@ -43,6 +43,8 @@ import org.groundplatform.android.ui.home.mapcontainer.jobs.JobMapComponentState import org.groundplatform.android.ui.home.mapcontainer.jobs.SelectedLoiSheetData import org.groundplatform.domain.model.Survey import org.groundplatform.domain.model.geometry.Coordinates +import org.groundplatform.domain.model.geometry.LinearRing +import org.groundplatform.domain.model.geometry.Polygon import org.groundplatform.domain.model.map.Bounds import org.groundplatform.domain.model.map.CameraPosition import org.groundplatform.domain.repository.LocationOfInterestRepositoryInterface @@ -117,6 +119,22 @@ class HomeScreenMapContainerViewModelTest : BaseHiltTest() { ) } + @Test + fun `job card shows the LOI area in the user's measurement units`() = runWithTestDispatcher { + val areaLoi = LOCATION_OF_INTEREST.copy(geometry = ONE_HECTARE_POLYGON) + whenever(loiRepository.getWithinBounds(SURVEY, BOUNDS)).thenReturn(flowOf(listOf(areaLoi))) + viewModel.onMapCameraMoved(CAMERA_POSITION) + advanceUntilIdle() + + viewModel.onFeatureClicked( + features = setOf(LOCATION_OF_INTEREST_FEATURE.copy(geometry = ONE_HECTARE_POLYGON)) + ) + val state = viewModel.processJobMapComponentState().first() + advanceUntilIdle() + + assertThat((state as JobMapComponentState.LoiSelected).loi.formattedArea).isEqualTo("1.00 ha") + } + @Test fun `deleteLoi deletes the loi and deselects it`() = runWithTestDispatcher { viewModel.onFeatureClicked(setOf(LOCATION_OF_INTEREST_FEATURE)) @@ -326,6 +344,18 @@ class HomeScreenMapContainerViewModelTest : BaseHiltTest() { companion object { private val BOUNDS = Bounds(Coordinates(-20.0, -20.0), Coordinates(-10.0, -10.0)) + private val ONE_HECTARE_POLYGON = + Polygon( + LinearRing( + listOf( + Coordinates(0.0, 0.0), + Coordinates(0.0, 0.0009), + Coordinates(0.0009, 0.0009), + Coordinates(0.0009, 0.0), + Coordinates(0.0, 0.0), + ) + ) + ) val CAMERA_POSITION = CameraPosition( coordinates = LOCATION_OF_INTEREST.geometry.center(), diff --git a/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheetTest.kt b/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheetTest.kt index bea437a77a..ef49ca42d0 100644 --- a/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheetTest.kt +++ b/app/src/test/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheetTest.kt @@ -101,10 +101,27 @@ class LoiJobSheetTest { composeTestRule.onNodeWithText(getString(Res.string.share)).assertIsNotDisplayed() } + @Test + fun `area is shown when not null`() { + setContent(FREE_FORM_LOI, formattedArea = "1.20 ha") + + composeTestRule.onNodeWithText(getString(R.string.area_message, "1.20 ha")).assertIsDisplayed() + } + + @Test + fun `area is not shown when null`() { + setContent(FREE_FORM_LOI, formattedArea = null) + + composeTestRule + .onNodeWithText(getString(R.string.area_message, "1.20 ha")) + .assertIsNotDisplayed() + } + private fun setContent( loi: LocationOfInterest, showDeleteLoiButton: Boolean = false, loiReport: LoiReport? = getLoiReport(loi.id), + formattedArea: String? = null, ) { composeTestRule.setContent { LoiJobSheet( @@ -115,6 +132,7 @@ class LoiJobSheetTest { loi = loi, showDeleteLoiButton = showDeleteLoiButton, loiReport = loiReport, + formattedArea = formattedArea, ), onCollectClicked = {}, onDismiss = {}, From 518c480d9eb7c4d5cd3872d24aabd568bfcab080 Mon Sep 17 00:00:00 2001 From: andreia Date: Wed, 9 Sep 2026 16:18:20 +0200 Subject: [PATCH 2/3] add area to pdf --- .../feature/pdf/render/PdfWriterTest.kt | 28 +++++++ .../feature/pdf/render/PdfWriter.kt | 17 ++-- .../feature/pdf/render/layout/TableLayout.kt | 26 ++++-- .../pdf/render/layout/TableLayoutTest.kt | 80 ++++++++++++++++--- 4 files changed, 130 insertions(+), 21 deletions(-) diff --git a/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt b/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt index 8674e25ff0..acc502a691 100644 --- a/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt +++ b/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt @@ -19,6 +19,8 @@ import android.graphics.Bitmap import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +import org.groundplatform.domain.model.geometry.Coordinates +import org.groundplatform.domain.model.geometry.Point import org.groundplatform.feature.pdf.model.SubmissionPdfDocument import org.groundplatform.feature.pdf.render.image.PdfImage import org.groundplatform.feature.pdf.render.image.PdfImageSet @@ -103,6 +105,23 @@ class PdfWriterTest { assertEquals("${TABLE.submissionLabel}: ${TABLE.loiName}", canvas.drawnText[jobLineIndex - 1]) } + @Test + fun `draws the area between the submission title and the job line`() { + val canvas = renderDocument(SINGLE_PAGE_DOCUMENT.copy(mapBlock = MAP_BLOCK)) + + val areaLineIndex = canvas.drawnText.indexOf("${AREA.label}: ${AREA.value}") + assertTrue(areaLineIndex > 0) + assertEquals("${TABLE.submissionLabel}: ${TABLE.loiName}", canvas.drawnText[areaLineIndex - 1]) + assertEquals("${TABLE.jobLabel}: ${TABLE.jobName}", canvas.drawnText[areaLineIndex + 1]) + } + + @Test + fun `draws no area line when the document has no area`() { + val canvas = renderDocument(SINGLE_PAGE_DOCUMENT.copy(mapBlock = MAP_BLOCK.copy(area = null))) + + assertFalse(canvas.drawnText.any { it.startsWith(AREA.label) }) + } + @Test fun `draws the footer text on the page`() { val canvas = renderDocument(SINGLE_PAGE_DOCUMENT) @@ -292,6 +311,15 @@ class PdfWriterTest { val QR_BLOCK = SubmissionPdfDocument.QrBlock(submissionName = "Plot 42", scanCaption = "Scan") + val AREA = SubmissionPdfDocument.Area(label = "Area", value = "1.00 ha") + + val MAP_BLOCK = + SubmissionPdfDocument.MapBlock( + geometry = Point(Coordinates(0.0, 0.0)), + style = null, + area = AREA, + ) + val TABLE = SubmissionPdfDocument.Table( submissionLabel = "Submission", diff --git a/feature/pdf/src/androidMain/kotlin/org/groundplatform/feature/pdf/render/PdfWriter.kt b/feature/pdf/src/androidMain/kotlin/org/groundplatform/feature/pdf/render/PdfWriter.kt index 1b29890f66..2f42c50cd6 100644 --- a/feature/pdf/src/androidMain/kotlin/org/groundplatform/feature/pdf/render/PdfWriter.kt +++ b/feature/pdf/src/androidMain/kotlin/org/groundplatform/feature/pdf/render/PdfWriter.kt @@ -27,6 +27,7 @@ import android.text.TextUtils import android.text.style.StyleSpan import org.groundplatform.feature.pdf.model.SubmissionPdfDocument import org.groundplatform.feature.pdf.model.SubmissionPdfDocument.Answer +import org.groundplatform.feature.pdf.model.SubmissionPdfDocument.Area import org.groundplatform.feature.pdf.model.SubmissionPdfDocument.Footer import org.groundplatform.feature.pdf.model.SubmissionPdfDocument.Header import org.groundplatform.feature.pdf.model.SubmissionPdfDocument.QrBlock @@ -76,7 +77,7 @@ internal class PdfWriter( fun drawDocument(document: SubmissionPdfDocument) { drawQrPage(document.qrBlock) - drawTable(document.table) + drawTable(document.table, document.mapBlock?.area) finalizePage() } @@ -105,21 +106,27 @@ internal class PdfWriter( pdfCanvas.finishPage() } - private fun drawTable(table: SubmissionPdfDocument.Table) { + private fun drawTable(table: SubmissionPdfDocument.Table, area: Area?) { val rows = table.rows.takeIf { it.isNotEmpty() } ?: return pageController.ensurePage() val titleLayout = staticLayout("${table.submissionLabel}: ${table.loiName}", paints.title, USABLE_WIDTH) - val subtitleLayout = + val jobLayout = staticLayout(labeled(table.jobLabel, table.jobName), paints.body, USABLE_WIDTH) + val areaLayout = + area?.value?.let { staticLayout(labeled(area.label, it), paints.body, USABLE_WIDTH) } val titleBlock = TableLayout.getTitleBlock( top = cursor.y, titleHeight = titleLayout.height.toFloat(), - subtitleHeight = subtitleLayout.height.toFloat(), + jobHeight = jobLayout.height.toFloat(), + areaHeight = areaLayout?.height?.toFloat() ?: 0f, ) drawStaticLayoutAt(titleLayout, titleBlock.titleOffset) - drawStaticLayoutAt(subtitleLayout, titleBlock.subtitleOffset) + if (areaLayout != null && titleBlock.areaOffset != null) { + drawStaticLayoutAt(areaLayout, titleBlock.areaOffset) + } + drawStaticLayoutAt(jobLayout, titleBlock.jobOffset) cursor.moveTo(titleBlock.nextCursorY) rows.forEach { row -> when (val answer = row.answer) { diff --git a/feature/pdf/src/commonMain/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayout.kt b/feature/pdf/src/commonMain/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayout.kt index 023187fd52..8ff859692a 100644 --- a/feature/pdf/src/commonMain/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayout.kt +++ b/feature/pdf/src/commonMain/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayout.kt @@ -84,25 +84,37 @@ internal object TableLayout { } /** - * Layout of the two title lines above the table. + * Layout of the title lines above the table. * * @param titleOffset Top-left position of the submission line. - * @param subtitleOffset Top-left position of the job line, directly below the submission line. + * @param areaOffset Top-left position of the area line, directly below the submission line, or + * null when there is no area to show. + * @param jobOffset Top-left position of the job line, below the area line when there is one and + * directly below the submission line otherwise. * @param nextCursorY Cursor Y position after the title block. */ data class TitleBlock( val titleOffset: PdfOffset, - val subtitleOffset: PdfOffset, + val areaOffset: PdfOffset?, + val jobOffset: PdfOffset, val nextCursorY: Float, ) - fun getTitleBlock(top: Float, titleHeight: Float, subtitleHeight: Float): TitleBlock { + fun getTitleBlock( + top: Float, + titleHeight: Float, + jobHeight: Float, + areaHeight: Float = 0f, + ): TitleBlock { + val hasArea = areaHeight > 0f val titleTop = top + LINE_SPACING * 2 - val subtitleTop = titleTop + titleHeight + LINE_SPACING + val areaTop = titleTop + titleHeight + LINE_SPACING + val jobTop = if (hasArea) areaTop + areaHeight + LINE_SPACING else areaTop return TitleBlock( titleOffset = PdfOffset(MARGIN.toFloat(), titleTop), - subtitleOffset = PdfOffset(MARGIN.toFloat(), subtitleTop), - nextCursorY = subtitleTop + subtitleHeight + LINE_SPACING * 2, + areaOffset = if (hasArea) PdfOffset(MARGIN.toFloat(), areaTop) else null, + jobOffset = PdfOffset(MARGIN.toFloat(), jobTop), + nextCursorY = jobTop + jobHeight + LINE_SPACING * 2, ) } diff --git a/feature/pdf/src/commonTest/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayoutTest.kt b/feature/pdf/src/commonTest/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayoutTest.kt index 94360b5f9e..2e8d4ca5f2 100644 --- a/feature/pdf/src/commonTest/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayoutTest.kt +++ b/feature/pdf/src/commonTest/kotlin/org/groundplatform/feature/pdf/render/layout/TableLayoutTest.kt @@ -37,47 +37,109 @@ class TableLayoutTest { @Test fun `title sits below a top gap at the left margin`() { - val layout = TableLayout.getTitleBlock(top = 100f, titleHeight = 14f, subtitleHeight = 12f) + val layout = TableLayout.getTitleBlock(top = 100f, titleHeight = 14f, jobHeight = 12f) assertEquals(PdfOffset(margin, 100f + 2 * lineSpacing), layout.titleOffset) } @Test - fun `subtitle sits below the title at the left margin`() { + fun `job line sits below the title at the left margin`() { + val titleHeight = 14f + + val layout = TableLayout.getTitleBlock(top = 100f, titleHeight = titleHeight, jobHeight = 12f) + + assertEquals( + PdfOffset(margin, 100f + 2 * lineSpacing + titleHeight + lineSpacing), + layout.jobOffset, + ) + } + + @Test + fun `area line sits below the title at the left margin`() { val titleHeight = 14f val layout = - TableLayout.getTitleBlock(top = 100f, titleHeight = titleHeight, subtitleHeight = 12f) + TableLayout.getTitleBlock( + top = 100f, + titleHeight = titleHeight, + jobHeight = 12f, + areaHeight = 12f, + ) assertEquals( PdfOffset(margin, 100f + 2 * lineSpacing + titleHeight + lineSpacing), - layout.subtitleOffset, + layout.areaOffset, ) } + @Test + fun `job line sits below the area line when there is one`() { + val titleHeight = 14f + val areaHeight = 12f + + val layout = + TableLayout.getTitleBlock( + top = 100f, + titleHeight = titleHeight, + jobHeight = 12f, + areaHeight = areaHeight, + ) + + assertEquals( + PdfOffset( + margin, + 100f + 2 * lineSpacing + titleHeight + lineSpacing + areaHeight + lineSpacing, + ), + layout.jobOffset, + ) + } + + @Test + fun `title block has no area line when there is no area`() { + val layout = TableLayout.getTitleBlock(top = 100f, titleHeight = 14f, jobHeight = 12f) + + assertNull(layout.areaOffset) + } + + @Test + fun `the area line pushes the first row further down`() { + val areaHeight = 12f + + val withoutArea = TableLayout.getTitleBlock(top = 0f, titleHeight = 14f, jobHeight = 12f) + val withArea = + TableLayout.getTitleBlock( + top = 0f, + titleHeight = 14f, + jobHeight = 12f, + areaHeight = areaHeight, + ) + + assertEquals(withoutArea.nextCursorY + lineSpacing + areaHeight, withArea.nextCursorY) + } + @Test fun `title block leaves a bottom gap before the first row`() { val top = 100f val titleHeight = 14f - val subtitleHeight = 12f + val jobHeight = 12f val layout = TableLayout.getTitleBlock( top = top, titleHeight = titleHeight, - subtitleHeight = subtitleHeight, + jobHeight = jobHeight, ) assertEquals( - top + 2 * lineSpacing + titleHeight + lineSpacing + subtitleHeight + 2 * lineSpacing, + top + 2 * lineSpacing + titleHeight + lineSpacing + jobHeight + 2 * lineSpacing, layout.nextCursorY, ) } @Test fun `taller title lines push the first row further down`() { - val short = TableLayout.getTitleBlock(top = 0f, titleHeight = 10f, subtitleHeight = 10f) - val tall = TableLayout.getTitleBlock(top = 0f, titleHeight = 30f, subtitleHeight = 20f) + val short = TableLayout.getTitleBlock(top = 0f, titleHeight = 10f, jobHeight = 10f) + val tall = TableLayout.getTitleBlock(top = 0f, titleHeight = 30f, jobHeight = 20f) assertTrue(short.nextCursorY < tall.nextCursorY) assertEquals(30f, tall.nextCursorY - short.nextCursorY) From ebc09d61869aea15bd44d1d76e9fe8f07181e65b Mon Sep 17 00:00:00 2001 From: andreia Date: Wed, 9 Sep 2026 16:36:49 +0200 Subject: [PATCH 3/3] fix code style --- .../HomeScreenMapContainerViewModel.kt | 4 +--- .../ui/home/mapcontainer/jobs/LoiJobSheet.kt | 9 +++++---- .../feature/pdf/render/PdfWriterTest.kt | 16 ++++++++++------ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt index 1760326acc..a0fbd7eb99 100644 --- a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt @@ -240,9 +240,7 @@ internal constructor( loi.geometry .area() .takeIf { it > 0.0 } - ?.let { - getFormattedArea(it, getUserSettingsUseCase().measurementUnits) - }, + ?.let { getFormattedArea(it, getUserSettingsUseCase().measurementUnits) }, ) } diff --git a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt index fd5eb26e65..6c6d2159c5 100644 --- a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt +++ b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/jobs/LoiJobSheet.kt @@ -244,6 +244,7 @@ private val user = User(id = "user", email = "user@email.com", displayName = "Us private val auditInfo = AuditInfo(user) private const val SURVEY_ID = "survey" private const val TASK_ID = "task 1" +private const val AREA = "1.20 ha" private val loiReport = LoiReport( loiName = "Point A", @@ -282,7 +283,7 @@ private fun PreviewModalContentsWhenJobHasNoTasks() { submissionCount = 0, showDeleteLoiButton = false, loiReport = loiReport, - formattedArea = "1.20 ha", + formattedArea = AREA, ), onDeleteClicked = null, onShareClicked = {}, @@ -331,7 +332,7 @@ private fun PreviewModalContentsWhenUserCannotSubmitData() { submissionCount = 1, showDeleteLoiButton = false, loiReport = loiReport, - formattedArea = "1.20 ha", + formattedArea = AREA, ), onDeleteClicked = null, onShareClicked = {}, @@ -382,7 +383,7 @@ private fun PreviewModalContentsWhenJobHasTasks() { submissionCount = 20, showDeleteLoiButton = false, loiReport = loiReport, - formattedArea = "1.20 ha", + formattedArea = AREA, ), onDeleteClicked = null, onShareClicked = {}, @@ -433,7 +434,7 @@ private fun PreviewModalContentsWhenJobHasTasksAndIsPredefined() { submissionCount = 20, showDeleteLoiButton = true, loiReport = loiReport, - formattedArea = "1.20 ha", + formattedArea = AREA, ), onDeleteClicked = null, onShareClicked = {}, diff --git a/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt b/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt index acc502a691..b0bc5f1315 100644 --- a/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt +++ b/feature/pdf/src/androidHostTest/kotlin/org/groundplatform/feature/pdf/render/PdfWriterTest.kt @@ -100,19 +100,19 @@ class PdfWriterTest { fun `draws the submission title above the table with the job below it`() { val canvas = renderDocument(SINGLE_PAGE_DOCUMENT) - val jobLineIndex = canvas.drawnText.indexOf("${TABLE.jobLabel}: ${TABLE.jobName}") + val jobLineIndex = canvas.drawnText.indexOf(JOB_LINE) assertTrue(jobLineIndex > 0) - assertEquals("${TABLE.submissionLabel}: ${TABLE.loiName}", canvas.drawnText[jobLineIndex - 1]) + assertEquals(SUBMISSION_LINE, canvas.drawnText[jobLineIndex - 1]) } @Test fun `draws the area between the submission title and the job line`() { val canvas = renderDocument(SINGLE_PAGE_DOCUMENT.copy(mapBlock = MAP_BLOCK)) - val areaLineIndex = canvas.drawnText.indexOf("${AREA.label}: ${AREA.value}") + val areaLineIndex = canvas.drawnText.indexOf(AREA_LINE) assertTrue(areaLineIndex > 0) - assertEquals("${TABLE.submissionLabel}: ${TABLE.loiName}", canvas.drawnText[areaLineIndex - 1]) - assertEquals("${TABLE.jobLabel}: ${TABLE.jobName}", canvas.drawnText[areaLineIndex + 1]) + assertEquals(SUBMISSION_LINE, canvas.drawnText[areaLineIndex - 1]) + assertEquals(JOB_LINE, canvas.drawnText[areaLineIndex + 1]) } @Test @@ -243,7 +243,7 @@ class PdfWriterTest { val canvas = renderDocument(tableless, pdfImageSet(qr = pdfImage())) assertEquals(listOf(1), canvas.startedPageNumbers) - assertFalse(canvas.drawnText.contains("${TABLE.submissionLabel}: ${TABLE.loiName}")) + assertFalse(canvas.drawnText.contains(SUBMISSION_LINE)) } private fun renderDocument( @@ -329,6 +329,10 @@ class PdfWriterTest { rows = emptyList(), ) + val SUBMISSION_LINE = "${TABLE.submissionLabel}: ${TABLE.loiName}" + val AREA_LINE = "${AREA.label}: ${AREA.value}" + val JOB_LINE = "${TABLE.jobLabel}: ${TABLE.jobName}" + val EMPTY_DOCUMENT = SubmissionPdfDocument( header = HEADER,