diff --git a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTest.kt b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTest.kt index 42d15f1..f3101be 100644 --- a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTest.kt +++ b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTest.kt @@ -30,3 +30,17 @@ fun CylinderSizeDialogCustomScreenshotTest() { CylinderSizeDialogCustomPreview() } +@PreviewTest +@PreviewForScreenshotTestsDialog +@Composable +fun CylinderSizeDialogImperialPresetsScreenshotTest() { + CylinderSizeDialogImperialPresetsPreview() +} + +@PreviewTest +@PreviewForScreenshotTestsDialog +@Composable +fun CylinderSizeDialogImperialCustomScreenshotTest() { + CylinderSizeDialogImperialCustomPreview() +} + diff --git a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTest.kt b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTest.kt index 5289d41..028024d 100644 --- a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTest.kt +++ b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTest.kt @@ -15,24 +15,33 @@ package org.neotech.app.abysner.presentation.screens import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import com.android.tools.screenshot.PreviewTest +import org.neotech.app.abysner.presentation.preview.DEVICE_PHONE_MAX_HEIGHT @PreviewTest -@Preview +@Preview(device = DEVICE_SHARE_IMAGE) @Composable fun ShareImageScreenshotTest() { ShareImagePreview() } @PreviewTest -@Preview +@Preview(device = DEVICE_SHARE_IMAGE) @Composable fun ShareImageExtremeScreenshotTest() { ShareImagePreviewExtreme() } @PreviewTest -@Preview +@Preview(device = DEVICE_SHARE_IMAGE) @Composable fun ShareImageCcrBailoutScreenshotTest() { ShareImagePreviewCcrBailout() } + +@PreviewTest +@Preview(device = DEVICE_SHARE_IMAGE) +@Composable +fun ShareImageImperialScreenshotTest() { + ShareImagePreviewImperial() +} + diff --git a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTest.kt b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTest.kt index fefdbff..f7f6016 100644 --- a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTest.kt +++ b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTest.kt @@ -38,3 +38,10 @@ fun PlannerScreenFoldableScreenshotTest() { PlannerScreenFoldablePreview() } +@PreviewTest +@PreviewForScreenshotTestsMaxHeight +@Composable +fun PlannerScreenImperialScreenshotTest() { + PlannerScreenImperialPreview() +} + diff --git a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTest.kt b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTest.kt index 8664822..8d2d9a6 100644 --- a/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTest.kt +++ b/androidApp/src/screenshotTest/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTest.kt @@ -44,3 +44,11 @@ fun CylinderPickerBottomSheetLockedGasScreenshotTest() { fun StandardGasPickerDialogScreenshotTest() { StandardGasPickerDialogPreview() } + +@PreviewTest +@PreviewForScreenshotTestsDefaultHeight +@Composable +fun CylinderPickerBottomSheetImperialScreenshotTest() { + GasPickerBottomSheetImperialPreview() +} + diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogCustomScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogCustomScreenshotTest_7a9301c3_0.png index dc719aa..604295b 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogCustomScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogCustomScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d531e7d20d2e709ba29fdb73992deab757558d7f72f1d26454aa06968d86fe4 -size 19974 +oid sha256:aaead50d46c97e3a03ee8b7f3b546f7e77ca103b64661ccd39bea6741e3d21aa +size 20805 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogImperialCustomScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogImperialCustomScreenshotTest_7a9301c3_0.png new file mode 100644 index 0000000..fa27eef --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogImperialCustomScreenshotTest_7a9301c3_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:864d18936b7e7e193b8a72a430699c6dfb682ff57d1455e8e264d014565b7ce9 +size 29088 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogImperialPresetsScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogImperialPresetsScreenshotTest_7a9301c3_0.png new file mode 100644 index 0000000..c221936 --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/component/CylinderSizeDialogScreenshotTestKt/CylinderSizeDialogImperialPresetsScreenshotTest_7a9301c3_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ac7a52b2d87d7e9a22882aec55f60a09d8bb2a0993c5084273d6edfa7f9d8c2 +size 36119 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreenScreenshotTestKt/DiveConfigurationScreenScreenshotTest_d12982c9_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreenScreenshotTestKt/DiveConfigurationScreenScreenshotTest_d12982c9_0.png index ba34441..ad90d54 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreenScreenshotTestKt/DiveConfigurationScreenScreenshotTest_d12982c9_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreenScreenshotTestKt/DiveConfigurationScreenScreenshotTest_d12982c9_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10dcc62b67f2bdcde7511fcb7bfdc2943ad56e768de9b800aad6c19ff5797283 -size 249547 +oid sha256:0bd97dad447086a38d980a15c8373f424f5f8b5a36b02ea9feb4a49326b21c8e +size 248522 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageCcrBailoutScreenshotTest_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageCcrBailoutScreenshotTest_0.png deleted file mode 100644 index 22b0617..0000000 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageCcrBailoutScreenshotTest_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:690f97b3419edd8e7a16bf59dba0c916e20250628a2f00549594137309b29e39 -size 168421 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageCcrBailoutScreenshotTest_aac34500_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageCcrBailoutScreenshotTest_aac34500_0.png new file mode 100644 index 0000000..00cf75a --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageCcrBailoutScreenshotTest_aac34500_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90146677122d289387905c560e36f6e90a0e82433c990fe864d9ceef93a7a4a4 +size 172361 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageExtremeScreenshotTest_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageExtremeScreenshotTest_0.png deleted file mode 100644 index 498cb72..0000000 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageExtremeScreenshotTest_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:aeca96503a20d77fc605f3204f283109bdb5a20458bdd1462f64db7863d908e3 -size 168652 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageExtremeScreenshotTest_aac34500_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageExtremeScreenshotTest_aac34500_0.png new file mode 100644 index 0000000..0ae2b04 --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageExtremeScreenshotTest_aac34500_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:635764dd8e4d3046180653f5cee27a9e98c4480e212ba5f382639ffcfd578a22 +size 215858 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageImperialScreenshotTest_aac34500_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageImperialScreenshotTest_aac34500_0.png new file mode 100644 index 0000000..ae3178e --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageImperialScreenshotTest_aac34500_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2021d3e47d7d18f53719ead8e14112ca54983972927541be129274272c06a044 +size 164686 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageScreenshotTest_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageScreenshotTest_0.png deleted file mode 100644 index 7ffaf38..0000000 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageScreenshotTest_0.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3be378abd28c22e3cf5a736190798fea281a9efbefb6c3b4b8ee11d658ac0349 -size 162225 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageScreenshotTest_aac34500_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageScreenshotTest_aac34500_0.png new file mode 100644 index 0000000..7bbb20b --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/ShareImageScreenshotTestKt/ShareImageScreenshotTest_aac34500_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0ef98972386043147caeacf232480640b0b6e89566fb4d15e89ead62a982bdf +size 164226 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenImperialScreenshotTest_d12982c9_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenImperialScreenshotTest_d12982c9_0.png new file mode 100644 index 0000000..7fcb05b --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenImperialScreenshotTest_d12982c9_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:654231a7535cdbb67f2a308031ed828e3aad69e8a09f73a50be3945095ef8cb4 +size 176922 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenScreenshotTest_d12982c9_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenScreenshotTest_d12982c9_0.png index f34dfa7..67310dd 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenScreenshotTest_d12982c9_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenScreenshotTest_d12982c9_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6af9cbf9017d3f0da415dab20ad13ae7909baf73484079403c8f2689f4988d0e -size 179431 +oid sha256:60d40d3432236f14a5e5a8e534e70feaa3bbb653e6460c06b3f7a6f9ef6b7c2f +size 178586 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenWithWarningsScreenshotTest_d12982c9_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenWithWarningsScreenshotTest_d12982c9_0.png index 20159e5..0514d19 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenWithWarningsScreenshotTest_d12982c9_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/PlannerScreenScreenshotTestKt/PlannerScreenWithWarningsScreenshotTest_d12982c9_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c706ce021a9b5c68b842a272522e144f4037128beee03f9aa2611db69befbcd5 -size 207860 +oid sha256:7be5f43137115dfbc255ed813a8893a89c0a4105f76a4e431f9c5733b0993b49 +size 207088 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTestKt/CylinderPickerBottomSheetImperialScreenshotTest_58e6d892_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTestKt/CylinderPickerBottomSheetImperialScreenshotTest_58e6d892_0.png new file mode 100644 index 0000000..7e9da2b --- /dev/null +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheetScreenshotTestKt/CylinderPickerBottomSheetImperialScreenshotTest_58e6d892_0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82ba79dc8745a10bf3c6308fb97e20277c7036553ba70a3a7f7d392e91f80970 +size 47470 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderErrorScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderErrorScreenshotTest_7a9301c3_0.png index 9e3504e..ca738b6 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderErrorScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderErrorScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:273d9a166c8721ed97a398bb22d8fe5bee77c5b47b9e3941e4287f26a38b2fe7 -size 35451 +oid sha256:0b1b6c7f2b70da0ff977ca95cbc0b3842731532fb5151f872fa67bdf370d5494 +size 34459 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderPositiveScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderPositiveScreenshotTest_7a9301c3_0.png index cb8966e..634fe3f 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderPositiveScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderPositiveScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:14d7d6578dffe870c18e6305201cf90492f651c7bf4fbe9b8dc849fac9a0675a -size 37662 +oid sha256:a97c10b3566ab5670115d7c95209a25db0bace604dc700a36ab906a5ce57d5e8 +size 36904 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderWarningScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderWarningScreenshotTest_7a9301c3_0.png index 7783857..d0d93aa 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderWarningScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsOneCylinderWarningScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66d1495c63bc5865b3e045c66a2d36ba7e61f744b847b0c19767e4bca274bc04 -size 36803 +oid sha256:6486eb99fcca85920e5cbc2a75331df2bc0ae52cadacfd0053eac3175a3acb2f +size 35877 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersErrorScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersErrorScreenshotTest_7a9301c3_0.png index 7a7da7c..94266ac 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersErrorScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersErrorScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cc61643df37b9ecd5acb89f7d1af6e9d118cc3bd0f696d10ccd37602fc94101 -size 41253 +oid sha256:3522a7d6ba6aa08ef521c565221ebff74e56156f05fc37db60a009acbfdd99bf +size 40313 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersPositiveScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersPositiveScreenshotTest_7a9301c3_0.png index 4c044db..4a39b5a 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersPositiveScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersPositiveScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:24fcaf4d6567147de42ebcf08f5264c6a7dafa4730fab3f59621365554862082 -size 44503 +oid sha256:e3190300a3f6f42b1c6d703c15c90acf021487af998d2cdc9d9e7d9af58d8c90 +size 43587 diff --git a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersWarningScreenshotTest_7a9301c3_0.png b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersWarningScreenshotTest_7a9301c3_0.png index b073a95..bd7a80c 100644 --- a/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersWarningScreenshotTest_7a9301c3_0.png +++ b/androidApp/src/screenshotTestDebug/reference/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialogScreenshotTestKt/GasUsageDetailsTwoCylindersWarningScreenshotTest_7a9301c3_0.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4eca5e0d90ae41fd363a242b89769dfaadd055db5eba1fd6a84c2fb970ee0914 -size 45558 +oid sha256:3e75bdc6c7552b8e05f7937cd32e703effcb8c1c9f2689b47d8e40779cea6802 +size 44643 diff --git a/composeApp/src/commonMain/kotlin/androidx/compose/material3/AlertDialogCustomContent.kt b/composeApp/src/commonMain/kotlin/androidx/compose/material3/AlertDialogCustomContent.kt index ae79064..303e875 100644 --- a/composeApp/src/commonMain/kotlin/androidx/compose/material3/AlertDialogCustomContent.kt +++ b/composeApp/src/commonMain/kotlin/androidx/compose/material3/AlertDialogCustomContent.kt @@ -94,7 +94,7 @@ fun AlertDialogCustomContent( BasicAlertDialog( onDismissRequest = onDismissRequest, // Limit height to maximum that is equal to the maximum dialog width (internal: AlertDialog.DialogMaxWidth = 560) - modifier = Modifier.heightIn(max = (560 * 1.25).dp), + modifier = modifier.heightIn(max = (560 * 1.25).dp), properties = properties ) { AlertDialogContentCopy( diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeField.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeField.kt index 0fa74ee..7dd888c 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeField.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/CylinderSizeField.kt @@ -21,12 +21,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialogCustomContent import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme @@ -55,27 +55,39 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import org.neotech.app.abysner.domain.core.model.Cylinder +import org.neotech.app.abysner.domain.core.model.Gas +import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.GasEquationOfStateModel +import org.neotech.app.abysner.domain.core.physics.LITERS_PER_CUBIC_FOOT +import org.neotech.app.abysner.domain.core.physics.PSI_PER_BAR import org.neotech.app.abysner.domain.utilities.DecimalFormat import org.neotech.app.abysner.presentation.component.list.LazyColumnWithScrollIndicators import org.neotech.app.abysner.presentation.component.textfield.OutlinedDecimalInputField import org.neotech.app.abysner.presentation.component.textfield.SuffixVisualTransformation import org.neotech.app.abysner.presentation.component.textfield.defaultInputFieldLabel import org.neotech.app.abysner.presentation.theme.AbysnerTheme +import org.neotech.app.abysner.presentation.utilities.pressureUnitLabel +import org.neotech.app.abysner.presentation.utilities.volumeUnitLabel +import kotlin.math.roundToInt @Composable fun CylinderSizeField( modifier: Modifier = Modifier, cylinderSize: Cylinder.Size, + unitSystem: UnitSystem, onCylinderSizeSelected: (Cylinder.Size) -> Unit, ) { var showDialog by remember { mutableStateOf(false) } - val displayText = DecimalFormat.format(1, cylinderSize.waterVolume) + val displayText = when (unitSystem) { + UnitSystem.METRIC -> DecimalFormat.format(1, cylinderSize.waterVolume) + UnitSystem.IMPERIAL -> (cylinderSize.ratedCapacity() / LITERS_PER_CUBIC_FOOT).roundToInt().toString() + } OutlinedTextField( modifier = modifier.clickable { showDialog = true }, value = TextFieldValue(displayText), - visualTransformation = SuffixVisualTransformation(" L"), + visualTransformation = SuffixVisualTransformation(" ${unitSystem.volumeUnitLabel}"), onValueChange = {}, readOnly = true, enabled = false, @@ -98,6 +110,7 @@ fun CylinderSizeField( if (showDialog) { CylinderSizeDialog( currentCylinderSize = cylinderSize, + unitSystem = unitSystem, onDismiss = { showDialog = false }, onCylinderSizeSelected = { selectedSize -> showDialog = false @@ -110,6 +123,7 @@ fun CylinderSizeField( @Composable private fun CylinderSizeDialog( currentCylinderSize: Cylinder.Size, + unitSystem: UnitSystem, onDismiss: () -> Unit, onCylinderSizeSelected: (Cylinder.Size) -> Unit, ) { @@ -125,8 +139,22 @@ private fun CylinderSizeDialog( val pagerState = rememberPagerState(initialPage = initialTab) { 2 } val coroutineScope = rememberCoroutineScope() - var customVolume: Double? by remember { mutableStateOf(currentCylinderSize.waterVolume) } - var customPressure: Double? by remember { mutableStateOf(currentCylinderSize.workingPressure) } + var customVolume: Double? by remember { + mutableStateOf( + when (unitSystem) { + UnitSystem.METRIC -> currentCylinderSize.waterVolume + UnitSystem.IMPERIAL -> currentCylinderSize.ratedCapacity() / LITERS_PER_CUBIC_FOOT + } + ) + } + var customPressure: Double? by remember { + mutableStateOf( + when (unitSystem) { + UnitSystem.METRIC -> currentCylinderSize.workingPressure + UnitSystem.IMPERIAL -> currentCylinderSize.workingPressure * PSI_PER_BAR + } + ) + } val isVolumeValid = remember { mutableStateOf(true) } val isPressureValid = remember { mutableStateOf(true) } @@ -143,12 +171,22 @@ private fun CylinderSizeDialog( TextButton( enabled = isVolumeValid.value && isPressureValid.value, onClick = { - onCylinderSizeSelected( - Cylinder.Size( + val size = when (unitSystem) { + UnitSystem.METRIC -> Cylinder.Size( waterVolume = customVolume!!, workingPressure = customPressure!!, ) - ) + UnitSystem.IMPERIAL -> { + val pressureBar = customPressure!! / PSI_PER_BAR + val targetCapacityLiters = customVolume!! * LITERS_PER_CUBIC_FOOT + val gasVolumePerLiter = GasEquationOfStateModel.Default.getGasVolume(Gas.Air, 1.0, pressureBar) + Cylinder.Size( + waterVolume = targetCapacityLiters / gasVolumePerLiter, + workingPressure = pressureBar, + ) + } + } + onCylinderSizeSelected(size) } ) { Text("OK") @@ -165,9 +203,7 @@ private fun CylinderSizeDialog( selected = pagerState.currentPage == TAB_PRESETS, onClick = { coroutineScope.launch { - pagerState.animateScrollToPage( - TAB_PRESETS - ) + pagerState.animateScrollToPage(TAB_PRESETS) } }, text = { Text("Presets") }, @@ -176,9 +212,7 @@ private fun CylinderSizeDialog( selected = pagerState.currentPage == TAB_CUSTOM, onClick = { coroutineScope.launch { - pagerState.animateScrollToPage( - TAB_CUSTOM - ) + pagerState.animateScrollToPage(TAB_CUSTOM) } }, text = { Text("Custom") }, @@ -193,6 +227,7 @@ private fun CylinderSizeDialog( TAB_PRESETS -> { PresetsTankSizeTab( currentCylinderSize = currentCylinderSize, + unitSystem = unitSystem, onPresetSelected = { preset -> onCylinderSizeSelected(preset) }, @@ -201,6 +236,7 @@ private fun CylinderSizeDialog( TAB_CUSTOM -> { CustomTankSizeTab( + unitSystem = unitSystem, volume = customVolume, pressure = customPressure, isVolumeValid = isVolumeValid, @@ -219,13 +255,18 @@ private fun CylinderSizeDialog( @Composable private fun PresetsTankSizeTab( currentCylinderSize: Cylinder.Size, + unitSystem: UnitSystem, onPresetSelected: (Cylinder.Size) -> Unit, ) { + val presets = when (unitSystem) { + UnitSystem.METRIC -> Cylinder.StandardMetricSizes + Cylinder.StandardImperialSizes + UnitSystem.IMPERIAL -> Cylinder.StandardImperialSizes + Cylinder.StandardMetricSizes + } val selectedPreset = Cylinder.Size.findMatching( currentCylinderSize.waterVolume, currentCylinderSize.workingPressure ) - val selectedIndex = selectedPreset?.let { Cylinder.StandardSizes.indexOf(it) } ?: -1 + val selectedIndex = selectedPreset?.let { presets.indexOf(it) } ?: -1 val listState = rememberLazyListState() LaunchedEffect(selectedIndex) { @@ -235,7 +276,7 @@ private fun PresetsTankSizeTab( } LazyColumnWithScrollIndicators(state = listState) { - items(Cylinder.StandardSizes) { preset -> + items(presets) { preset -> Row( modifier = Modifier .fillMaxWidth() @@ -256,8 +297,20 @@ private fun PresetsTankSizeTab( modifier = Modifier.padding(start = 8.dp).weight(1f), ) InfoPill( - label = null, - value = "${DecimalFormat.format(1, preset.waterVolume)} L", + label = when (unitSystem) { + UnitSystem.METRIC -> null + UnitSystem.IMPERIAL -> { + val pressurePsi = (preset.workingPressure * PSI_PER_BAR).roundToInt() + "$pressurePsi ${unitSystem.pressureUnitLabel}" + } + }, + value = when (unitSystem) { + UnitSystem.METRIC -> "${DecimalFormat.format(1, preset.waterVolume)} ${unitSystem.volumeUnitLabel}" + UnitSystem.IMPERIAL -> { + val capacityCuFt = (preset.ratedCapacity() / LITERS_PER_CUBIC_FOOT).roundToInt() + "$capacityCuFt ${unitSystem.volumeUnitLabel}" + } + }, size = InfoPillSize.SMALL, ) } @@ -267,6 +320,7 @@ private fun PresetsTankSizeTab( @Composable private fun CustomTankSizeTab( + unitSystem: UnitSystem, volume: Double?, pressure: Double?, isVolumeValid: MutableState, @@ -274,6 +328,23 @@ private fun CustomTankSizeTab( onVolumeChanged: (Double?) -> Unit, onPressureChanged: (Double?) -> Unit, ) { + val volumeLabel = when (unitSystem) { + UnitSystem.METRIC -> "Volume" + UnitSystem.IMPERIAL -> "True capacity" + } + val volumeRange = when (unitSystem) { + UnitSystem.METRIC -> 0.1 to 50.0 + UnitSystem.IMPERIAL -> 6.0 to 160.0 + } + val volumeFractionDigits = when (unitSystem) { + UnitSystem.METRIC -> 1 + UnitSystem.IMPERIAL -> 0 + } + val pressureRange = when (unitSystem) { + UnitSystem.METRIC -> 10.0 to 300.0 + UnitSystem.IMPERIAL -> 1800.0 to 4500.0 + } + Column( modifier = Modifier .fillMaxSize() @@ -284,12 +355,12 @@ private fun CustomTankSizeTab( OutlinedDecimalInputField( modifier = Modifier.fillMaxWidth(), initialValue = volume, - label = "Volume", - fractionDigits = 1, - minValue = 0.1, - maxValue = 50.0, + label = volumeLabel, + fractionDigits = volumeFractionDigits, + minValue = volumeRange.first, + maxValue = volumeRange.second, isValid = isVolumeValid, - visualTransformation = SuffixVisualTransformation(" L"), + visualTransformation = SuffixVisualTransformation(" ${unitSystem.volumeUnitLabel}"), onNumberChanged = onVolumeChanged, supportingText = null, ) @@ -298,13 +369,20 @@ private fun CustomTankSizeTab( initialValue = pressure, label = "Working pressure", fractionDigits = 0, - minValue = 10.0, - maxValue = 300.0, + minValue = pressureRange.first, + maxValue = pressureRange.second, isValid = isPressureValid, - visualTransformation = SuffixVisualTransformation(" bar"), + visualTransformation = SuffixVisualTransformation(" ${unitSystem.pressureUnitLabel}"), onNumberChanged = onPressureChanged, supportingText = null, ) + if (unitSystem == UnitSystem.IMPERIAL) { + Text( + text = "Use real-gas capacity from the manufacturer's specifications sheet. This may differ from the marketing name.", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } } } @@ -317,6 +395,7 @@ fun CylinderSizeDialogPresetsPreview() { AbysnerTheme { CylinderSizeDialog( currentCylinderSize = Cylinder.STEEL_12L, + unitSystem = UnitSystem.METRIC, onDismiss = {}, onCylinderSizeSelected = {}, ) @@ -329,9 +408,39 @@ fun CylinderSizeDialogCustomPreview() { AbysnerTheme { CylinderSizeDialog( currentCylinderSize = Cylinder.Size( - waterVolume = 11.1, + waterVolume = 9.0, + workingPressure = 232.0, + ), + unitSystem = UnitSystem.METRIC, + onDismiss = {}, + onCylinderSizeSelected = {}, + ) + } +} + +@Preview +@Composable +fun CylinderSizeDialogImperialPresetsPreview() { + AbysnerTheme { + CylinderSizeDialog( + currentCylinderSize = Cylinder.AL80, + unitSystem = UnitSystem.IMPERIAL, + onDismiss = {}, + onCylinderSizeSelected = {}, + ) + } +} + +@Preview +@Composable +fun CylinderSizeDialogImperialCustomPreview() { + AbysnerTheme { + CylinderSizeDialog( + currentCylinderSize = Cylinder.Size( + waterVolume = 9.0, workingPressure = 232.0, ), + unitSystem = UnitSystem.IMPERIAL, onDismiss = {}, onCylinderSizeSelected = {}, ) diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/GasPropertiesComponent.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/GasPropertiesComponent.kt index 017b8d7..7555ae8 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/GasPropertiesComponent.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/component/GasPropertiesComponent.kt @@ -12,7 +12,6 @@ package org.neotech.app.abysner.presentation.component -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -21,6 +20,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.neotech.app.abysner.domain.core.model.Environment import org.neotech.app.abysner.domain.core.model.Gas @@ -31,7 +31,6 @@ import org.neotech.app.abysner.domain.core.physics.Pressure import org.neotech.app.abysner.domain.utilities.floorTolerant import org.neotech.app.abysner.domain.utilities.format import org.neotech.app.abysner.presentation.theme.AbysnerTheme -import org.neotech.app.abysner.presentation.utilities.depthUnitLabel import org.neotech.app.abysner.presentation.utilities.formatDisplayDepth import kotlin.math.round @@ -135,7 +134,7 @@ fun GasPropertiesComponent( onClick = onClick, size = BigNumberSize.SMALL, value = modSecondary, - label = "O2 MOD (${maxPPO2Secondary!!.format(1)})", + label = "O2 MOD (${maxPPO2Secondary.format(1)})", ) }, back = { modifier, onClick -> diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/preview/PreviewData.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/preview/PreviewData.kt index 1131273..0baa66b 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/preview/PreviewData.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/preview/PreviewData.kt @@ -17,6 +17,8 @@ import org.neotech.app.abysner.domain.core.model.Configuration import org.neotech.app.abysner.domain.core.model.Cylinder import org.neotech.app.abysner.domain.core.model.DiveMode import org.neotech.app.abysner.domain.core.model.Gas +import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.METERS_PER_FOOT import org.neotech.app.abysner.domain.diveplanning.DivePlanner import org.neotech.app.abysner.domain.diveplanning.model.CylinderRole import org.neotech.app.abysner.domain.diveplanning.model.DivePlanSet @@ -28,6 +30,7 @@ import org.neotech.app.abysner.domain.gasplanning.GasPlanner object PreviewData { private val airCylinder = Cylinder.steel12Liter(gas = Gas.Air) + private val airCylinderImperial = Cylinder.AL100.fill(gas = Gas.Air) private val nitrox50Cylinder = Cylinder.aluminium80Cuft(gas = Gas.Nitrox50) private val nitrox80Cylinder = Cylinder.aluminium63Cuft(gas = Gas.Nitrox80) private val oxygenCylinder = Cylinder.steel3LiterOxygen() @@ -145,4 +148,35 @@ object PreviewData { gasPlan = gasPlan ) } + + val divePlan1ImperialProfile by lazy { + persistentListOf(DiveProfileSection(25, 100.0 * METERS_PER_FOOT, airCylinderImperial)) + } + + val divePlan1ImperialCylinders by lazy { + listOf( + PlannedCylinderModel(cylinder = airCylinderImperial, isLocked = true, isChecked = true), + PlannedCylinderModel(cylinder = nitrox50Cylinder, isLocked = false, isChecked = true), + ) + } + + /** + * Similar to [divePlan1] but with depth and cylinders changed to the closest sensible imperial + * numbers. + */ + val divePlan1Imperial: DivePlanSet by lazy { + val divePlan = DivePlanner(unitSystem = UnitSystem.IMPERIAL).addDive( + plan = divePlan1ImperialProfile, + cylinders = divePlan1ImperialCylinders.filter { it.isChecked }.toAssignedCylinders(), + ) + val gasPlan = GasPlanner().calculateGasPlan(divePlan) + DivePlanSet( + base = divePlan, + deeper = null, + longer = null, + bailout = false, + diveMode = DiveMode.OPEN_CIRCUIT, + gasPlan = gasPlan + ) + } } diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreen.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreen.kt index 41afa49..7c10235 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/DiveConfigurationScreen.kt @@ -12,7 +12,6 @@ package org.neotech.app.abysner.presentation.screens -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -31,9 +30,6 @@ import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState 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.platform.LocalInspectionMode import androidx.compose.ui.text.buildAnnotatedString @@ -49,6 +45,7 @@ import androidx.compose.ui.tooling.preview.Preview import org.neotech.app.abysner.domain.core.model.Configuration import org.neotech.app.abysner.domain.core.model.Salinity import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.LITERS_PER_CUBIC_FOOT import org.neotech.app.abysner.domain.core.physics.METERS_PER_FOOT import org.neotech.app.abysner.domain.diveplanning.PlanningRepository import org.neotech.app.abysner.domain.settings.SettingsRepository @@ -65,11 +62,11 @@ import org.neotech.app.abysner.presentation.component.textfield.SuffixVisualTran import org.neotech.app.abysner.presentation.theme.AbysnerTheme import org.neotech.app.abysner.presentation.utilities.depthUnitLabel import org.neotech.app.abysner.presentation.utilities.formatDepth -import org.neotech.app.abysner.presentation.utilities.rateUnitLabel +import org.neotech.app.abysner.presentation.utilities.depthPerMinuteUnitLabel +import org.neotech.app.abysner.presentation.utilities.volumePerMinuteUnitLabel import kotlin.math.abs import kotlin.math.roundToInt - typealias DiveConfigurationScreen = @Composable (navController: NavHostController) -> Unit @OptIn(ExperimentalMaterial3Api::class) @@ -201,8 +198,8 @@ fun DiveConfigurationScreen( initialValue = unitSystem.metersToDisplayDepth(configuration.maxAscentRate).toInt(), minValue = 1, maxValue = if (unitSystem == UnitSystem.IMPERIAL) { 60 } else { 18 }, - valueFormatter = { "$it ${unitSystem.rateUnitLabel}"}, - textFieldVisualTransformation = SuffixVisualTransformation(" ${unitSystem.rateUnitLabel}") + valueFormatter = { "$it ${unitSystem.depthPerMinuteUnitLabel}"}, + textFieldVisualTransformation = SuffixVisualTransformation(" ${unitSystem.depthPerMinuteUnitLabel}") ) { ascentRate -> val meters = unitSystem.displayDepthToMeters(ascentRate.toDouble()) updateConfiguration { it.copy(maxAscentRate = meters) } @@ -214,35 +211,51 @@ fun DiveConfigurationScreen( initialValue = unitSystem.metersToDisplayDepth(configuration.maxDescentRate).toInt(), minValue = 1, maxValue = if (unitSystem == UnitSystem.IMPERIAL) { 130 } else { 40 }, - valueFormatter = { "$it ${unitSystem.rateUnitLabel}"}, - textFieldVisualTransformation = SuffixVisualTransformation(" ${unitSystem.rateUnitLabel}") + valueFormatter = { "$it ${unitSystem.depthPerMinuteUnitLabel}"}, + textFieldVisualTransformation = SuffixVisualTransformation(" ${unitSystem.depthPerMinuteUnitLabel}") ) { descentRate -> val meters = unitSystem.displayDepthToMeters(descentRate.toDouble()) updateConfiguration { it.copy(maxDescentRate = meters) } } - NumberPreference( + DecimalNumberPreference( label = "Gas usage", description = "The average amount of gas the diver is breathing per minute at 1 atmosphere during normal diving conditions. This is also known as SAC or RMV rate.", - initialValue = configuration.sacRate.toInt(), - minValue = 5, - maxValue = 99, - valueFormatter = { "$it l/min"}, - textFieldVisualTransformation = SuffixVisualTransformation(" l/min") + initialValue = when (unitSystem) { + UnitSystem.METRIC -> configuration.sacRate + UnitSystem.IMPERIAL -> configuration.sacRate / LITERS_PER_CUBIC_FOOT + }, + minValue = if (unitSystem == UnitSystem.IMPERIAL) { 0.3 } else { 5.0 }, + maxValue = if (unitSystem == UnitSystem.IMPERIAL) { 3.5 } else { 99.0 }, + fractionDigits = if (unitSystem == UnitSystem.IMPERIAL) { 1 } else { 0 }, + valueFormatter = { "${DecimalFormat.format(if (unitSystem == UnitSystem.IMPERIAL) { 1 } else { 0} , it)} ${unitSystem.volumePerMinuteUnitLabel}"}, + textFieldVisualTransformation = SuffixVisualTransformation(" ${unitSystem.volumePerMinuteUnitLabel}") ) { sacRate -> - updateConfiguration { it.copy(sacRate = sacRate.toDouble()) } + val liters = when (unitSystem) { + UnitSystem.METRIC -> sacRate + UnitSystem.IMPERIAL -> sacRate * LITERS_PER_CUBIC_FOOT + } + updateConfiguration { it.copy(sacRate = liters) } } - NumberPreference( + DecimalNumberPreference( label = "Gas usage emergency", description = "The average amount of gas a diver is breathing per minute at 1 atmosphere during an emergency scenario. This is also known as the panic SAC or RMV rate.", - initialValue = configuration.sacRateOutOfAir.toInt(), - minValue = 5, - maxValue = 99, - valueFormatter = { "$it l/min"}, - textFieldVisualTransformation = SuffixVisualTransformation(" l/min") + initialValue = when (unitSystem) { + UnitSystem.METRIC -> configuration.sacRateOutOfAir + UnitSystem.IMPERIAL -> configuration.sacRateOutOfAir / LITERS_PER_CUBIC_FOOT + }, + minValue = if (unitSystem == UnitSystem.IMPERIAL) { 0.3 } else { 5.0 }, + maxValue = if (unitSystem == UnitSystem.IMPERIAL) { 3.5 } else { 99.0 }, + fractionDigits = if (unitSystem == UnitSystem.IMPERIAL) { 1 } else { 0 }, + valueFormatter = { "${DecimalFormat.format(if (unitSystem == UnitSystem.IMPERIAL) { 1 } else { 0 }, it)} ${unitSystem.volumePerMinuteUnitLabel}"}, + textFieldVisualTransformation = SuffixVisualTransformation(" ${unitSystem.volumePerMinuteUnitLabel}") ) { sacRate -> - updateConfiguration { it.copy(sacRateOutOfAir = sacRate.toDouble()) } + val liters = when (unitSystem) { + UnitSystem.METRIC -> sacRate + UnitSystem.IMPERIAL -> sacRate * LITERS_PER_CUBIC_FOOT + } + updateConfiguration { it.copy(sacRateOutOfAir = liters) } } SettingsSubTitle(subTitle = "Decompression & Planing") @@ -403,12 +416,12 @@ fun DiveConfigurationScreen( DecimalNumberPreference( label = "Loop volume", - description = "Total internal loop volume in liters (counter-lung, scrubber, hoses). Used to calculate diluent usage from loop expansion during descent.", + description = "Total internal loop volume (counter-lung, scrubber, hoses). Used to calculate diluent usage from loop expansion during descent.", initialValue = configuration.ccrLoopVolumeLiters, - minValue = 0.1, + minValue = 1.0, maxValue = 20.0, fractionDigits = 1, - valueFormatter = { "$it L" }, + valueFormatter = { "${DecimalFormat.format(1, it)} L" }, textFieldVisualTransformation = SuffixVisualTransformation(" L"), ) { volume -> updateConfiguration { it.copy(ccrLoopVolumeLiters = volume) } @@ -442,4 +455,3 @@ fun DiveConfigurationScreenPreview() { unitSystem = UnitSystem.METRIC ) } - diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/ShareImage.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/ShareImage.kt index 9fd366f..7968dcd 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/ShareImage.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/ShareImage.kt @@ -47,6 +47,7 @@ import kotlinx.datetime.format import kotlinx.datetime.toLocalDateTime import org.jetbrains.compose.resources.vectorResource import org.neotech.app.abysner.domain.core.model.DiveMode +import org.neotech.app.abysner.domain.core.model.UnitSystem import org.neotech.app.abysner.domain.diveplanning.model.DivePlanSet import org.neotech.app.abysner.domain.settings.model.SettingsModel import org.neotech.app.abysner.domain.utilities.format @@ -165,6 +166,7 @@ fun ShareImage( GasTotalsTable( gasPlan = divePlan.gasPlan, + unitSystem = settingsModel.unitSystem, emergencyLabel = emergencyLabel, usageLabel = usageLabel ) @@ -175,7 +177,7 @@ fun ShareImage( style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold) ) - CylindersTable(divePlanSet = divePlan) + CylindersTable(divePlanSet = divePlan, unitSystem = settingsModel.unitSystem) Text( modifier = Modifier.padding(top = 16.dp, bottom = 4.dp), @@ -192,6 +194,16 @@ fun ShareImage( val configuration = divePlan.configuration + Text( + text = buildAnnotatedString { + appendBold("Units: ") + append(when (settingsModel.unitSystem) { + UnitSystem.METRIC -> "Metric" + UnitSystem.IMPERIAL -> "Imperial" + }) + }, + style = MaterialTheme.typography.bodySmall + ) Text( text = buildAnnotatedString { appendBold("Deco model: ") @@ -301,4 +313,19 @@ fun ShareImagePreviewCcrBailout() { ) } -private const val DEVICE_SHARE_IMAGE = "spec:width=411dp,height=2350dp" +@Preview(device = DEVICE_SHARE_IMAGE) +@Composable +fun ShareImagePreviewImperial() { + ShareImage( + divePlan = PreviewData.divePlan1Imperial, + diveNumber = 1, + surfaceInterval = null, + settingsModel = SettingsModel( + showBasicDecoTable = true, + termsAndConditionsAccepted = true, + unitSystem = UnitSystem.IMPERIAL + ), + ) +} + +const val DEVICE_SHARE_IMAGE = "spec:width=1080px,height=3000px" diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreen.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreen.kt index f02de93..2886d2b 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreen.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreen.kt @@ -55,6 +55,7 @@ import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject import org.neotech.app.abysner.domain.core.model.Cylinder import org.neotech.app.abysner.domain.core.model.DiveMode +import org.neotech.app.abysner.domain.core.model.UnitSystem import org.neotech.app.abysner.domain.diveplanning.model.DiveProfileSection import org.neotech.app.abysner.domain.diveplanning.model.MultiDivePlanSet import org.neotech.app.abysner.domain.settings.model.SettingsModel @@ -365,3 +366,25 @@ fun PlannerScreenFoldablePreview() { ) } } + +@Preview(device = DEVICE_PHONE_MAX_HEIGHT) +@Composable +fun PlannerScreenImperialPreview() { + CompositionLocalProvider( + LocalBitmapRenderController provides remember { BitmapRenderController() }, + ) { + PlannerScreen( + uiState = PlanScreenViewModel.UiState( + isLoading = false, + isCalculatingDivePlan = false, + segments = PreviewData.divePlan1ImperialProfile, + availableGas = PreviewData.divePlan1ImperialCylinders, + configuration = PreviewData.divePlan1Imperial.configuration, + selectedDivePlanSet = Result.success(PreviewData.divePlan1Imperial), + multiDivePlanSet = Result.success(MultiDivePlanSet(divePlanSets = listOf(PreviewData.divePlan1Imperial))), + settingsModel = SettingsModel(unitSystem = UnitSystem.IMPERIAL) + ) + ) + } +} + diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreenContent.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreenContent.kt index 4e5ed25..652a069 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreenContent.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/PlanScreenContent.kt @@ -59,6 +59,7 @@ internal fun PlanScreenContent( CylinderSelectionCardComponent( gases = uiState.availableGas, diveMode = uiState.diveMode, + unitSystem = uiState.settingsModel.unitSystem, onAddCylinder = onAddCylinder, onRemoveCylinder = onRemoveCylinder, onCylinderChecked = onToggleCylinder, diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheet.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheet.kt index 9e3cbfb..29b68fe 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheet.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylinderPickerBottomSheet.kt @@ -50,6 +50,7 @@ import org.neotech.app.abysner.domain.core.model.Cylinder import org.neotech.app.abysner.domain.core.model.Environment import org.neotech.app.abysner.domain.core.model.Gas import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.PSI_PER_BAR import org.neotech.app.abysner.domain.diveplanning.model.DiveProfileSection import org.neotech.app.abysner.domain.diveplanning.model.PlannedCylinderModel import org.neotech.app.abysner.presentation.utilities.ModalTarget @@ -63,6 +64,7 @@ import org.neotech.app.abysner.presentation.component.recordLayoutCoordinates import org.neotech.app.abysner.presentation.component.textfield.OutlinedDecimalInputField import org.neotech.app.abysner.presentation.component.textfield.SuffixVisualTransformation import org.neotech.app.abysner.presentation.component.CylinderSizeField +import org.neotech.app.abysner.presentation.utilities.pressureUnitLabel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -194,7 +196,10 @@ private fun CylinderPickerBottomSheetContent( } var startPressure: Double? by remember(initialValue) { - mutableStateOf(initialValue.pressure) + mutableStateOf(when (unitSystem) { + UnitSystem.METRIC -> initialValue.pressure + UnitSystem.IMPERIAL -> initialValue.pressure * PSI_PER_BAR + }) } val isStartPressureValid = remember { mutableStateOf(true) } @@ -218,6 +223,10 @@ private fun CylinderPickerBottomSheetContent( primaryEnabled = isStartPressureValid.value, onClose = dismiss, onPrimary = { + val pressureBar = when (unitSystem) { + UnitSystem.METRIC -> startPressure!! + UnitSystem.IMPERIAL -> startPressure!! / PSI_PER_BAR + } onAddOrUpdateCylinder( // Copy since we want to maintain the uniqueIdentifier initialValue.copy( @@ -225,7 +234,7 @@ private fun CylinderPickerBottomSheetContent( oxygenFraction = oxygenPercentage / 100.0, heliumFraction = heliumPercentage / 100.0 ), - pressure = startPressure!!, + pressure = pressureBar, size = Cylinder.Size( waterVolume = volume!!, workingPressure = workingPressure, @@ -239,7 +248,8 @@ private fun CylinderPickerBottomSheetContent( ) { Column( modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - ) { val errorMessagePressure = remember { mutableStateOf(null) } + ) { + val errorMessagePressure = remember { mutableStateOf(null) } Row( horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -251,22 +261,27 @@ private fun CylinderPickerBottomSheetContent( waterVolume = volume ?: initialValue.waterVolume, workingPressure = workingPressure, ), + unitSystem = unitSystem, onCylinderSizeSelected = { selectedSize -> volume = selectedSize.waterVolume workingPressure = selectedSize.workingPressure + startPressure = when (unitSystem) { + UnitSystem.METRIC -> selectedSize.workingPressure + UnitSystem.IMPERIAL -> selectedSize.workingPressure * PSI_PER_BAR + } }, ) OutlinedDecimalInputField( modifier = Modifier.weight(1f) .recordLayoutCoordinates("pressure", textFieldPositions), - initialValue = initialValue.pressure, + initialValue = startPressure!!, label = "Start pressure", - minValue = 10.0, - maxValue = 300.0, + minValue = if (unitSystem == UnitSystem.IMPERIAL) {150.0 } else { 10.0 }, + maxValue = if (unitSystem == UnitSystem.IMPERIAL) { 4500.0 } else { 300.0 }, fractionDigits = 0, isValid = isStartPressureValid, - visualTransformation = SuffixVisualTransformation(" bar"), + visualTransformation = SuffixVisualTransformation(" ${unitSystem.pressureUnitLabel}"), errorMessage = errorMessagePressure, onNumberChanged = { startPressure = it @@ -426,3 +441,20 @@ fun GasPickerBottomSheetLockedGasPreview() { } } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@Preview +fun GasPickerBottomSheetImperialPreview() { + AbysnerTheme { + CylinderPickerBottomSheet( + sheetState = rememberExpandedSheetState(), + environment = Environment.Default, + maxPPO2 = 1.4, + maxPPO2Secondary = 1.6, + unitSystem = UnitSystem.IMPERIAL, + isAdd = true, + initialValue = Cylinder.aluminium80Cuft(Gas.Air), + ) + } +} + diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylindersCard.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylindersCard.kt index 9daaeb5..cdb9aad 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylindersCard.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/cylinders/CylindersCard.kt @@ -54,9 +54,10 @@ import org.jetbrains.compose.resources.painterResource import org.neotech.app.abysner.domain.core.model.Cylinder import org.neotech.app.abysner.domain.core.model.DiveMode import org.neotech.app.abysner.domain.core.model.Gas +import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.LITERS_PER_CUBIC_FOOT import org.neotech.app.abysner.domain.diveplanning.model.CylinderRole import org.neotech.app.abysner.domain.diveplanning.model.PlannedCylinderModel -import org.neotech.app.abysner.domain.utilities.DecimalFormat import org.neotech.app.abysner.presentation.component.IconAndTextButton import org.neotech.app.abysner.presentation.component.InfoPill import org.neotech.app.abysner.presentation.component.InfoPillSize @@ -64,12 +65,17 @@ import org.neotech.app.abysner.presentation.component.TextWithStartIcon import org.neotech.app.abysner.presentation.component.core.ifTrue import org.neotech.app.abysner.presentation.component.core.invisible import org.neotech.app.abysner.presentation.theme.AbysnerTheme +import org.neotech.app.abysner.presentation.utilities.formatPressure +import org.neotech.app.abysner.presentation.utilities.formatVolume +import org.neotech.app.abysner.presentation.utilities.volumeUnitLabel +import kotlin.math.roundToInt @Composable fun CylinderSelectionCardComponent( modifier: Modifier = Modifier, gases: List, - diveMode: DiveMode = DiveMode.OPEN_CIRCUIT, + diveMode: DiveMode, + unitSystem: UnitSystem, onAddCylinder: () -> Unit, onRemoveCylinder: (cylinder: Cylinder) -> Unit, onCylinderChecked: (cylinder: Cylinder, checked: Boolean) -> Unit, @@ -120,6 +126,7 @@ fun CylinderSelectionCardComponent( isChecked = availableGas.isChecked, isLocked = availableGas.isLocked, cylinder = availableGas.cylinder, + unitSystem = unitSystem, showBailoutPill = availableGas.isCcrDiluent && availableGas.isAvailableForBailout, onDelete = { onRemoveCylinder(availableGas.cylinder) }, onChecked = { _, checked -> onCylinderChecked(availableGas.cylinder, checked) }, @@ -136,6 +143,7 @@ fun CylinderSelectionCardComponent( isChecked = availableGas.isChecked, isLocked = availableGas.isLocked, cylinder = availableGas.cylinder, + unitSystem = unitSystem, onDelete = { onRemoveCylinder(availableGas.cylinder) }, onChecked = { _, checked -> onCylinderChecked(availableGas.cylinder, checked) }, onLockedClick = { showLockedExplanation = availableGas }, @@ -148,6 +156,7 @@ fun CylinderSelectionCardComponent( isChecked = availableGas.isChecked, isLocked = availableGas.isLocked, cylinder = availableGas.cylinder, + unitSystem = unitSystem, onDelete = { onRemoveCylinder(availableGas.cylinder) }, onChecked = { _, checked -> onCylinderChecked(availableGas.cylinder, checked) }, onLockedClick = { showLockedExplanation = availableGas }, @@ -240,12 +249,20 @@ fun CylinderListItemComponent( cylinder: Cylinder, isChecked: Boolean, isLocked: Boolean, + unitSystem: UnitSystem, showBailoutPill: Boolean = false, onDelete: (cylinder: Cylinder) -> Unit = {}, onChecked: (cylinder: Cylinder, isChecked: Boolean) -> Unit = { _, _ -> }, onLockedClick: () -> Unit = {}, ) { - val cylinderSuffix = " - ${DecimalFormat.format(0, cylinder.pressure)} bar (${DecimalFormat.format(1, cylinder.waterVolume)} L)" + val capacityDisplay = when (unitSystem) { + UnitSystem.METRIC -> cylinder.waterVolume.formatVolume(unitSystem, decimals = 1) + UnitSystem.IMPERIAL -> { + val ratedCapacityCuFt = cylinder.size.ratedCapacity() / LITERS_PER_CUBIC_FOOT + "${ratedCapacityCuFt.roundToInt()} ${unitSystem.volumeUnitLabel}" + } + } + val cylinderSuffix = " - ${cylinder.pressure.formatPressure(unitSystem)} ($capacityDisplay)" Row( modifier = modifier.padding(start = 16.dp), @@ -306,6 +323,7 @@ fun CylinderListItemComponent( fun CylinderSelectionCardComponentPreview() { AbysnerTheme { CylinderSelectionCardComponent( + diveMode = DiveMode.OPEN_CIRCUIT, gases = listOf( PlannedCylinderModel( isChecked = true, @@ -323,6 +341,7 @@ fun CylinderSelectionCardComponentPreview() { cylinder = Cylinder(gas = Gas.Nitrox80, 207.0, 9.0) ) ), + unitSystem = UnitSystem.METRIC, onAddCylinder = {}, onRemoveCylinder = {}, onCylinderChecked = { _, _ -> }, @@ -362,6 +381,7 @@ fun CylinderSelectionCardComponentCcrPreview() { cylinder = Cylinder.aluminium63Cuft(gas = Gas.Nitrox80) ), ), + unitSystem = UnitSystem.METRIC, onAddCylinder = {}, onRemoveCylinder = {}, onCylinderChecked = { _, _ -> }, diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/decoplan/DecoPlanConfigurationSummeryDialog.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/decoplan/DecoPlanConfigurationSummeryDialog.kt index 9010293..7bd83e3 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/decoplan/DecoPlanConfigurationSummeryDialog.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/decoplan/DecoPlanConfigurationSummeryDialog.kt @@ -24,7 +24,7 @@ import org.neotech.app.abysner.domain.utilities.DecimalFormat import androidx.compose.ui.tooling.preview.Preview import org.neotech.app.abysner.presentation.theme.AbysnerTheme import org.neotech.app.abysner.presentation.utilities.formatDepth -import org.neotech.app.abysner.presentation.utilities.rateUnitLabel +import org.neotech.app.abysner.presentation.utilities.depthPerMinuteUnitLabel @Composable fun DecoPlanConfigurationSummeryDialog( @@ -59,11 +59,11 @@ fun DecoPlanConfigurationSummeryDialog( style = MaterialTheme.typography.bodySmall ) Text( - text = "Max ascent speed: ${configuration.maxAscentRate.formatDepth(unitSystem, includeUnit = false)} ${unitSystem.rateUnitLabel}", + text = "Max ascent speed: ${configuration.maxAscentRate.formatDepth(unitSystem, includeUnit = false)} ${unitSystem.depthPerMinuteUnitLabel}", style = MaterialTheme.typography.bodySmall ) Text( - text = "Max descent speed: ${configuration.maxDescentRate.formatDepth(unitSystem, includeUnit = false)} ${unitSystem.rateUnitLabel}", + text = "Max descent speed: ${configuration.maxDescentRate.formatDepth(unitSystem, includeUnit = false)} ${unitSystem.depthPerMinuteUnitLabel}", style = MaterialTheme.typography.bodySmall ) Text( diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasBarChart.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasBarChart.kt index 58ee2ba..24e5254 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasBarChart.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasBarChart.kt @@ -23,13 +23,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.ContentAlpha -import androidx.compose.material.LocalContentAlpha import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawWithContent @@ -46,6 +43,8 @@ import io.github.koalaplot.core.util.ExperimentalKoalaPlotApi import kotlinx.collections.immutable.persistentListOf import org.neotech.app.abysner.domain.core.model.Cylinder import org.neotech.app.abysner.domain.core.model.Gas +import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.PSI_PER_BAR import org.neotech.app.abysner.domain.diveplanning.DivePlanner import org.neotech.app.abysner.domain.diveplanning.model.DiveProfileSection import org.neotech.app.abysner.domain.diveplanning.model.assign @@ -66,6 +65,7 @@ import org.neotech.app.abysner.presentation.component.graphs.StackedHorizontalBa import org.neotech.app.abysner.presentation.theme.AbysnerTheme import org.neotech.app.abysner.presentation.theme.IconFont import org.neotech.app.abysner.presentation.theme.appendIcon +import org.neotech.app.abysner.presentation.utilities.pressureUnitLabel import kotlin.math.max import kotlin.math.roundToInt @@ -88,7 +88,7 @@ fun GasBarChartPreview() = PreviewWrapper { val gasPlan = GasPlanner().calculateGasPlan(divePlan) AbysnerTheme(dynamicColor = false) { - GasPlanBarChart(gasPlan = gasPlan) + GasPlanBarChart(gasPlan = gasPlan, unitSystem = UnitSystem.METRIC) } } @@ -98,6 +98,7 @@ fun GasBarChartPreview() = PreviewWrapper { fun GasPlanBarChart( modifier: Modifier = Modifier, gasPlan: GasPlan, + unitSystem: UnitSystem, emergencyLabel: String = "Reserve", usageLabel: String = "Used", compact: Boolean = false, @@ -158,7 +159,11 @@ fun GasPlanBarChart( } ) - val max = max(gasPlan.maxOf { it.cylinder.pressure }.toFloat(), 200f) + val pressureScale = when (unitSystem) { + UnitSystem.METRIC -> 1.0f + UnitSystem.IMPERIAL -> PSI_PER_BAR.toFloat() + } + val max = max(gasPlan.maxOf { it.cylinder.pressure }.toFloat() * pressureScale, MINIMUM_AXIS_PRESSURE_BAR * pressureScale) val min = 0f val range = max - min @@ -181,14 +186,13 @@ fun GasPlanBarChart( ) } ) - CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) { - Text( - modifier = Modifier.fillMaxWidth(), - style = MaterialTheme.typography.labelSmall, - textAlign = TextAlign.Center, - text = "Pressure in bar" - ) - } + Text( + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.labelSmall, + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.onSurfaceVariant, + text = "Pressure in ${unitSystem.pressureUnitLabel}" + ) } }, verticalAxis = { @@ -212,7 +216,7 @@ fun GasPlanBarChart( drawContent() - val negativeWidth = (size.width) * (50f / range) + val negativeWidth = (size.width) * (RECOMMENDED_END_PRESSURE_BAR * pressureScale / range) drawLine( color = Color.Red.copy(alpha = 0.5f), @@ -229,6 +233,7 @@ fun GasPlanBarChart( onGasBarClicked(index, it) }, cylinderGasRequirements = it, + unitSystem = unitSystem, maxValue = max, minValue = min ) @@ -267,7 +272,7 @@ fun GasPlanBarChart( @Composable fun GasUsageBarPreview() = PreviewWrapper { AbysnerTheme { - GasUsageBar(modifier = Modifier.fillMaxWidth().height(48.dp), maxValue = 230f, minValue = 0f) + GasUsageBar(modifier = Modifier.fillMaxWidth().height(48.dp), unitSystem = UnitSystem.METRIC, maxValue = 230f, minValue = 0f) } } @@ -275,11 +280,16 @@ fun GasUsageBarPreview() = PreviewWrapper { fun GasUsageBar( modifier: Modifier = Modifier, cylinderGasRequirements: CylinderGasRequirements = CylinderGasRequirements(Cylinder.steel12Liter(Gas.Air), 1000.0, 500.0), + unitSystem: UnitSystem, maxValue: Float, minValue: Float, ) { - val pressureLeftWithEmergency = cylinderGasRequirements.pressureLeftWithEmergency?.toFloat() ?: 0f - val pressureLeftWithoutEmergency = cylinderGasRequirements.pressureLeft?.toFloat() ?: 0f + val pressureScale = when (unitSystem) { + UnitSystem.METRIC -> 1.0f + UnitSystem.IMPERIAL -> PSI_PER_BAR.toFloat() + } + val pressureLeftWithEmergency = (cylinderGasRequirements.pressureLeftWithEmergency?.toFloat() ?: 0f) * pressureScale + val pressureLeftWithoutEmergency = (cylinderGasRequirements.pressureLeft?.toFloat() ?: 0f) * pressureScale val redShades = MaterialTheme.colorScheme.error.setSaturation(0.7f).getGradient(lightnessMiddle = 0.65f, difference = 0.2f) @@ -307,7 +317,7 @@ fun GasUsageBar( ), // Usage BarSection( - value = cylinderGasRequirements.cylinder.pressure.toFloat() - pressureLeftWithoutEmergency, + value = cylinderGasRequirements.cylinder.pressure.toFloat() * pressureScale - pressureLeftWithoutEmergency, color = SolidColor(MaterialTheme.colorScheme.outlineVariant), textColor = SolidColor(MaterialTheme.colorScheme.contentColorFor(MaterialTheme.colorScheme.outlineVariant)), textStyle = MaterialTheme.typography.labelSmall.copy(textAlign = TextAlign.Right) @@ -332,3 +342,6 @@ fun GasUsageBar( } ) } + +private const val MINIMUM_AXIS_PRESSURE_BAR = 200f +private const val RECOMMENDED_END_PRESSURE_BAR = 50f diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasPlanCard.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasPlanCard.kt index 149070f..245bc58 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasPlanCard.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasPlanCard.kt @@ -34,10 +34,10 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import org.neotech.app.abysner.domain.core.model.Gas import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.LITERS_PER_CUBIC_FOOT import org.neotech.app.abysner.domain.diveplanning.model.DivePlanSet import org.neotech.app.abysner.domain.gasplanning.model.CylinderGasRequirements import org.neotech.app.abysner.domain.utilities.DecimalFormat -import org.neotech.app.abysner.domain.utilities.format import org.neotech.app.abysner.domain.utilities.greaterThanTolerant import org.neotech.app.abysner.presentation.component.AlertSeverity import org.neotech.app.abysner.presentation.component.Table @@ -54,6 +54,12 @@ import org.neotech.app.abysner.presentation.theme.IconFont import org.neotech.app.abysner.presentation.theme.appendIcon import org.neotech.app.abysner.presentation.utilities.depthUnitLabel import org.neotech.app.abysner.presentation.utilities.formatDisplayDepth +import org.neotech.app.abysner.presentation.utilities.formatPressure +import org.neotech.app.abysner.presentation.utilities.formatVolume +import org.neotech.app.abysner.presentation.utilities.noUnitLabel +import org.neotech.app.abysner.presentation.utilities.pressureUnitLabel +import org.neotech.app.abysner.presentation.utilities.volumeUnitLabel +import kotlin.math.roundToInt @Composable fun GasPlanCardComponent( @@ -123,6 +129,7 @@ fun GasPlanCardComponent( GasUsageDetailsDialog( gasPlan = gasRequirements, index = index, + unitSystem = unitSystem, emergencyLabel = emergencyLabel, usageLabel = usageLabel ) { @@ -133,6 +140,7 @@ fun GasPlanCardComponent( GasPlanBarChart( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp), gasPlan = gasRequirements, + unitSystem = unitSystem, emergencyLabel = emergencyLabel, usageLabel = usageLabel, ) { index, _ -> @@ -149,6 +157,7 @@ fun GasPlanCardComponent( GasTotalsTable( modifier = Modifier.padding(horizontal = 16.dp), gasPlan = gasRequirements, + unitSystem = unitSystem, emergencyLabel = emergencyLabel, usageLabel = usageLabel, ) @@ -163,6 +172,7 @@ fun GasPlanCardComponent( CylindersTable( modifier = Modifier.padding(horizontal = 16.dp), divePlanSet = divePlanSet, + unitSystem = unitSystem, // On CCR: Not enough bailout is a true error not a warning. // On OC: Not enough gas for an out-of-air buddy is warning emergencyIsError = divePlanSet.isCcr, @@ -250,14 +260,15 @@ private fun headerWithUnit(label: String, unit: String) = buildAnnotatedString { fun CylindersTable( modifier: Modifier = Modifier, divePlanSet: DivePlanSet, + unitSystem: UnitSystem, emergencyIsError: Boolean = false, ) { Table( modifier = modifier, header = { Text(modifier = Modifier.weight(0.3f), maxLines = 1, text = "Mix") - Text(modifier = Modifier.weight(0.3f), maxLines = 1, text = headerWithUnit("Size", "ℓ")) - Text(modifier = Modifier.weight(0.4f), maxLines = 1, text = headerWithUnit("Pressure", "bar")) + Text(modifier = Modifier.weight(0.3f), maxLines = 1, text = headerWithUnit("Size", unitSystem.volumeUnitLabel)) + Text(modifier = Modifier.weight(0.4f), maxLines = 1, text = headerWithUnit("Pressure", unitSystem.pressureUnitLabel)) } ) { rows(divePlanSet.gasPlan, key = { it.cylinder.uniqueIdentifier }) { usage -> @@ -267,11 +278,14 @@ fun CylindersTable( ) Text( modifier = Modifier.weight(0.3f), - text = DecimalFormat.format(1, usage.cylinder.waterVolume), + text = when (unitSystem) { + UnitSystem.METRIC -> usage.cylinder.waterVolume.formatVolume(unitSystem, decimals = 1, unit = noUnitLabel) + UnitSystem.IMPERIAL -> (usage.cylinder.size.ratedCapacity() / LITERS_PER_CUBIC_FOOT).roundToInt().toString() + }, ) val endPressure = usage.cylinder.pressureAfter(volumeUsage = usage.totalGasRequirement) - val startPressure = DecimalFormat.format(0, usage.cylinder.pressure) + val startPressureFormatted = usage.cylinder.pressure.formatPressure(unitSystem, includeUnit = false) val alertSeverity = if (endPressure == null) { if (!emergencyIsError && usage.cylinder.pressureAfter(volumeUsage = usage.normalRequirement) != null) { @@ -286,9 +300,9 @@ fun CylindersTable( } val pressureText = if (endPressure == null) { - "$startPressure > empty" + "$startPressureFormatted > empty" } else { - "$startPressure > ${endPressure.format(0)}" + "$startPressureFormatted > ${endPressure.formatPressure(unitSystem, includeUnit = false)}" } TextAlert( @@ -304,6 +318,7 @@ fun CylindersTable( fun GasTotalsTable( modifier: Modifier = Modifier, gasPlan: List, + unitSystem: UnitSystem, emergencyLabel: String = "Reserve", usageLabel: String = "Used", ) { @@ -311,9 +326,9 @@ fun GasTotalsTable( modifier = modifier, header = { Text(modifier = Modifier.weight(0.17f), maxLines = 1, text = "Mix") - Text(modifier = Modifier.weight(0.32f), maxLines = 1, text = headerWithUnit("Capacity", "ℓ")) - Text(modifier = Modifier.weight(0.26f), maxLines = 1, text = headerWithUnit(usageLabel, "ℓ")) - Text(modifier = Modifier.weight(0.25f), maxLines = 1, text = headerWithUnit(emergencyLabel, "ℓ")) + Text(modifier = Modifier.weight(0.32f), maxLines = 1, text = headerWithUnit("Available", unitSystem.volumeUnitLabel)) + Text(modifier = Modifier.weight(0.26f), maxLines = 1, text = headerWithUnit(usageLabel, unitSystem.volumeUnitLabel)) + Text(modifier = Modifier.weight(0.25f), maxLines = 1, text = headerWithUnit(emergencyLabel, unitSystem.volumeUnitLabel)) } ) { rows(gasPlan.groupBy { it.cylinder.gas }.toList(), key = { (gas, _) -> gas }) { (gas, entries) -> @@ -334,16 +349,16 @@ fun GasTotalsTable( } Text(modifier = Modifier.weight(0.17f), text = gas.toString()) - Text(modifier = Modifier.weight(0.32f), text = DecimalFormat.format(0, totalCapacity)) + Text(modifier = Modifier.weight(0.32f), text = totalCapacity.formatVolume(unitSystem, unit = noUnitLabel)) TextAlert( modifier = Modifier.weight(0.26f), alertSeverity = alertSeverityUsage, - text = DecimalFormat.format(0, totalUsage), + text = totalUsage.formatVolume(unitSystem, unit = noUnitLabel), ) TextAlert( modifier = Modifier.weight(0.25f), alertSeverity = alertSeverityReserve, - text = DecimalFormat.format(0, totalReserve), + text = totalReserve.formatVolume(unitSystem, unit = noUnitLabel), ) } } @@ -360,7 +375,7 @@ fun GasLimitsTable( header = { Text(modifier = Modifier.weight(0.2f), maxLines = 1, text = "Mix") Text(modifier = Modifier.weight(0.25f), maxLines = 1, text = "Depth") - Text(modifier = Modifier.weight(0.35f), maxLines = 1, text = headerWithUnit("Density", "g/ℓ")) + Text(modifier = Modifier.weight(0.35f), maxLines = 1, text = headerWithUnit("Density", "g/L")) Text(modifier = Modifier.weight(0.2f), maxLines = 1, text = "PPO2") } ) { diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialog.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialog.kt index 0519d55..b10827c 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialog.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/screens/planner/gasplan/GasUsageDetailsDialog.kt @@ -37,16 +37,17 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import kotlinx.collections.immutable.persistentListOf import org.jetbrains.compose.resources.painterResource -import androidx.compose.ui.tooling.preview.Preview import org.neotech.app.abysner.domain.core.model.Cylinder import org.neotech.app.abysner.domain.core.model.Gas +import org.neotech.app.abysner.domain.core.model.UnitSystem +import org.neotech.app.abysner.domain.core.physics.LITERS_PER_CUBIC_FOOT import org.neotech.app.abysner.domain.gasplanning.model.CylinderGasRequirements import org.neotech.app.abysner.domain.gasplanning.model.GasPlan -import kotlinx.collections.immutable.persistentListOf -import org.neotech.app.abysner.domain.utilities.format -import androidx.compose.ui.text.font.FontWeight import org.neotech.app.abysner.presentation.component.AlertSeverity import org.neotech.app.abysner.presentation.component.Table import org.neotech.app.abysner.presentation.component.appendBold @@ -54,12 +55,16 @@ import org.neotech.app.abysner.presentation.component.appendBoldLine import org.neotech.app.abysner.presentation.theme.AbysnerTheme import org.neotech.app.abysner.presentation.theme.onWarning import org.neotech.app.abysner.presentation.theme.warning +import org.neotech.app.abysner.presentation.utilities.formatPressure +import org.neotech.app.abysner.presentation.utilities.formatVolume +import org.neotech.app.abysner.presentation.utilities.volumeUnitLabel import kotlin.math.roundToInt @Composable fun GasUsageDetailsDialog( gasPlan: GasPlan, index: Int, + unitSystem: UnitSystem, emergencyLabel: String = "Reserve", usageLabel: String = "Used", onDismissRequest: () -> Unit @@ -77,6 +82,9 @@ fun GasUsageDetailsDialog( text = { Column { + val capacity = cylinderGasRequirements.cylinder.capacity() + val labelWidthState = remember { mutableIntStateOf(0) } + Text( modifier = Modifier.padding(bottom = 4.dp), text = "Cylinder details", @@ -84,10 +92,6 @@ fun GasUsageDetailsDialog( color = MaterialTheme.colorScheme.onSurface ) - val capacity = cylinderGasRequirements.cylinder.capacity() - val labelWidthState = remember { mutableIntStateOf(0) } - - // Cylinder-specific info Table(striped = false, defaultRowModifier = Modifier.padding(vertical = 1.dp)) { row { Text( @@ -101,21 +105,37 @@ fun GasUsageDetailsDialog( modifier = Modifier.uniformLabelWidth(labelWidthState).padding(end = 8.dp), text = "Volume", fontWeight = FontWeight.Bold ) - Text(modifier = Modifier.weight(1f), text = "${cylinderGasRequirements.cylinder.waterVolume.format(1)}\u00A0ℓ") + Text( + modifier = Modifier.weight(1f), + text = cylinderGasRequirements.cylinder.waterVolume.formatVolume(UnitSystem.METRIC, decimals = 1) + ) + } + if (unitSystem == UnitSystem.IMPERIAL) { + row { + Text( + modifier = Modifier.uniformLabelWidth(labelWidthState).padding(end = 8.dp), + text = "True capacity", fontWeight = FontWeight.Bold + ) + Text( + modifier = Modifier.weight(1f), + text = "${(cylinderGasRequirements.cylinder.size.ratedCapacity() / LITERS_PER_CUBIC_FOOT).roundToInt()} ${unitSystem.volumeUnitLabel}" + ) + } } row { Text( modifier = Modifier.uniformLabelWidth(labelWidthState).padding(end = 8.dp), text = "Pressure", fontWeight = FontWeight.Bold ) - Text(modifier = Modifier.weight(1f), text = "${cylinderGasRequirements.cylinder.pressure.format(0)} bar") + Text(modifier = Modifier.weight(1f), text = cylinderGasRequirements.cylinder.pressure.formatPressure(unitSystem)) } row { Text( modifier = Modifier.uniformLabelWidth(labelWidthState).padding(end = 8.dp), - text = "Capacity", fontWeight = FontWeight.Bold + text = "Capacity", + fontWeight = FontWeight.Bold ) - Text(modifier = Modifier.weight(1f), text = "${capacity.format(0)}\u00A0ℓ") + Text(modifier = Modifier.weight(1f), text = capacity.formatVolume(unitSystem)) } } @@ -138,14 +158,14 @@ fun GasUsageDetailsDialog( modifier = Modifier.uniformLabelWidth(labelWidthState).padding(end = 8.dp), text = usageLabel, fontWeight = FontWeight.Bold ) - Text(modifier = Modifier.weight(1f), text = "${totalUsage.format(0)}\u00A0ℓ") + Text(modifier = Modifier.weight(1f), text = totalUsage.formatVolume(unitSystem)) } row { Text( modifier = Modifier.uniformLabelWidth(labelWidthState).padding(end = 8.dp), text = emergencyLabel, fontWeight = FontWeight.Bold ) - Text(modifier = Modifier.weight(1f), text = "${totalReserve.format(0)}\u00A0ℓ") + Text(modifier = Modifier.weight(1f), text = totalReserve.formatVolume(unitSystem)) } row { Text( @@ -153,7 +173,7 @@ fun GasUsageDetailsDialog( text = "Unused", fontWeight = FontWeight.Bold ) val totalUnused = totalCapacity - totalRequired - Text(modifier = Modifier.weight(1f), text = "${totalUnused.format(0)}\u00A0ℓ") + Text(modifier = Modifier.weight(1f), text = totalUnused.formatVolume(unitSystem)) } } @@ -169,19 +189,19 @@ fun GasUsageDetailsDialog( ) } - val totalCapacityFormatted = "${totalCapacity.format(0)}\u00A0ℓ" + val totalCapacityFormatted = totalCapacity.formatVolume(unitSystem) // Bar pressure for a single cylinder (what divers read on gauges), liters for // multiple cylinders (no meaningful single pressure to show). val unusedNormalFormatted = if (showTotals) { - "${(totalCapacity - totalUsage).format(0)}\u00A0ℓ" + (totalCapacity - totalUsage).formatVolume(unitSystem) } else { - "${cylinderGasRequirements.pressureLeft?.format(0)} bar" + cylinderGasRequirements.pressureLeft?.formatPressure(unitSystem) ?: "?" } val unusedEmergencyFormatted = if (showTotals) { - "${(totalCapacity - totalRequired).format(0)}\u00A0ℓ" + (totalCapacity - totalRequired).formatVolume(unitSystem) } else { - "${cylinderGasRequirements.pressureLeftWithEmergency?.format(0)} bar" + cylinderGasRequirements.pressureLeftWithEmergency?.formatPressure(unitSystem) ?: "?" } val severity = when { @@ -200,7 +220,7 @@ fun GasUsageDetailsDialog( appendBoldLine("This cylinder has a critical gas shortage!") } append("You need at least ") - appendBold("${totalUsage.format(0)}\u00A0ℓ") + appendBold(totalUsage.formatVolume(unitSystem)) append(if (showTotals) " for the dive, but only have a combined " else " for the dive, but only have ") appendBold(totalCapacityFormatted) append(".") @@ -212,7 +232,7 @@ fun GasUsageDetailsDialog( appendBoldLine("This cylinder has insufficient ${emergencyLabel.lowercase()}!") } append("You need ") - appendBold("${totalRequired.format(0)}\u00A0ℓ") + appendBold(totalRequired.formatVolume(unitSystem)) append(if (showTotals) " including ${emergencyLabel.lowercase()}, but only have a combined " else " including ${emergencyLabel.lowercase()}, but only have ") appendBold(totalCapacityFormatted) append(". Without accounting for ${emergencyLabel.lowercase()}, there is enough gas.") @@ -303,7 +323,7 @@ fun TwoCylindersPositivePreview() { AbysnerTheme { val cylinderA = CylinderGasRequirements(Cylinder.aluminium80Cuft(Gas.Nitrox50, 207.0), 800.0, 300.0) val cylinderB = CylinderGasRequirements(Cylinder.aluminium80Cuft(Gas.Nitrox50, 207.0), 800.0, 300.0) - GasUsageDetailsDialog(gasPlan = persistentListOf(cylinderA, cylinderB), index = 0) {} + GasUsageDetailsDialog(gasPlan = persistentListOf(cylinderA, cylinderB), index = 0, unitSystem = UnitSystem.METRIC) {} } } @@ -313,7 +333,7 @@ fun TwoCylindersWarningPreview() { AbysnerTheme { val cylinderA = CylinderGasRequirements(Cylinder.steel12Liter(Gas.Nitrox50), 2000.0, 900.0) val cylinderB = CylinderGasRequirements(Cylinder.steel12Liter(Gas.Nitrox50), 2000.0, 900.0) - GasUsageDetailsDialog(gasPlan = persistentListOf(cylinderA, cylinderB), index = 0) {} + GasUsageDetailsDialog(gasPlan = persistentListOf(cylinderA, cylinderB), index = 0, unitSystem = UnitSystem.METRIC) {} } } @@ -323,7 +343,7 @@ fun TwoCylindersErrorPreview() { AbysnerTheme { val cylinderA = CylinderGasRequirements(Cylinder.aluminium80Cuft(Gas.Air, 200.0), 2500.0, 400.0) val cylinderB = CylinderGasRequirements(Cylinder.aluminium80Cuft(Gas.Air, 200.0), 2500.0, 400.0) - GasUsageDetailsDialog(gasPlan = persistentListOf(cylinderA, cylinderB), index = 0) {} + GasUsageDetailsDialog(gasPlan = persistentListOf(cylinderA, cylinderB), index = 0, unitSystem = UnitSystem.METRIC) {} } } @@ -334,6 +354,7 @@ fun OneCylinderPositivePreview() { GasUsageDetailsDialog( gasPlan = persistentListOf(CylinderGasRequirements(Cylinder.steel12Liter(Gas.Air), 1200.0, 600.0)), index = 0, + unitSystem = UnitSystem.METRIC, ) {} } } @@ -345,6 +366,7 @@ fun OneCylinderWarningPreview() { GasUsageDetailsDialog( gasPlan = persistentListOf(CylinderGasRequirements(Cylinder.aluminium80Cuft(Gas.Nitrox50, 207.0),1900.0, 600.0)), index = 0, + unitSystem = UnitSystem.METRIC, ) {} } } @@ -356,6 +378,7 @@ fun OneCylinderErrorPreview() { GasUsageDetailsDialog( gasPlan = persistentListOf(CylinderGasRequirements(Cylinder.steel12Liter(Gas.Air), 2800.0, 400.0)), index = 0, + unitSystem = UnitSystem.METRIC, ) {} } } diff --git a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatter.kt b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatter.kt index cdefedf..50ab521 100644 --- a/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatter.kt +++ b/composeApp/src/commonMain/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatter.kt @@ -24,7 +24,7 @@ val UnitSystem.depthUnitLabel: String UnitSystem.IMPERIAL -> "ft" } -val UnitSystem.rateUnitLabel: String +val UnitSystem.depthPerMinuteUnitLabel: String get() = when (this) { UnitSystem.METRIC -> "m/min" UnitSystem.IMPERIAL -> "ft/min" @@ -42,12 +42,14 @@ val UnitSystem.volumeUnitLabel: String UnitSystem.IMPERIAL -> "ft³" } -val UnitSystem.sacRateUnitLabel: String +val UnitSystem.volumePerMinuteUnitLabel: String get() = when (this) { UnitSystem.METRIC -> "L/min" UnitSystem.IMPERIAL -> "ft³/min" } +const val noUnitLabel = "" + /** * Formats a depth value that is already in display-units (meters for metric, feet for imperial). */ diff --git a/composeApp/src/commonTest/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatterTest.kt b/composeApp/src/commonTest/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatterTest.kt index b11add3..06b3c2b 100644 --- a/composeApp/src/commonTest/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatterTest.kt +++ b/composeApp/src/commonTest/kotlin/org/neotech/app/abysner/presentation/utilities/UnitFormatterTest.kt @@ -57,7 +57,7 @@ class UnitFormatterTest { @Test fun formatVolume_supportsUnitOverride() { - assertEquals("20 L/min", 20.0.formatVolume(UnitSystem.METRIC, unit = UnitSystem.METRIC.sacRateUnitLabel)) - assertEquals("1 ft³/min", 28.316846592.formatVolume(UnitSystem.IMPERIAL, unit = UnitSystem.IMPERIAL.sacRateUnitLabel)) + assertEquals("20 L/min", 20.0.formatVolume(UnitSystem.METRIC, unit = UnitSystem.METRIC.volumePerMinuteUnitLabel)) + assertEquals("1 ft³/min", 28.316846592.formatVolume(UnitSystem.IMPERIAL, unit = UnitSystem.IMPERIAL.volumePerMinuteUnitLabel)) } }