diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 7cddfd91..2503f6bb 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -131,6 +131,15 @@ kotlin { // needs a coroutine, and a scheduler, to assert it in. implementation(libs.kotlinx.coroutinesTest) } + jvmTest.dependencies { + // A layout modifier cannot be asserted by reading it. `readableContent()` is + // three modifiers whose order decides whether the content is capped, centred, + // both or neither, and every ordering compiles and renders something -- so the + // check has to be a measurement of a real composition. Pinned to the same + // version as the rest of Compose Multiplatform; test-only. + implementation(compose.desktop.uiTestJUnit4) + implementation(libs.kotlin.testJunit) + } jvmMain.dependencies { implementation(compose.desktop.currentOs) implementation(libs.kotlinx.coroutinesSwing) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt index 2babace8..506c772d 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ActiveProfileScreen.kt @@ -69,6 +69,7 @@ import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.ButtonDefaults +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -121,6 +122,7 @@ fun ActiveProfileScreen( ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { LazyColumn( modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt index d4b2b660..f3019db9 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt @@ -85,6 +85,7 @@ import mantra.composeapp.generated.resources.source_dialect import mantra.composeapp.generated.resources.url import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable @@ -229,7 +230,7 @@ fun AddArtifactScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { // TODO: Check that we have direct message relays for this user... Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt index a74454bb..9cede369 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddChapterScreen.kt @@ -66,6 +66,7 @@ import mantra.composeapp.generated.resources.this_artifact_has_no_version_for_a_ import mantra.composeapp.generated.resources.add_chapter_to import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -217,7 +218,7 @@ fun AddChapterScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize().padding(MaterialTheme.spacing.space250), + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize().padding(MaterialTheme.spacing.space250), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { if (!addChapterUIState.canSign) { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt index b1806d33..eabaa2a1 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddDialectScreen.kt @@ -69,6 +69,7 @@ import mantra.composeapp.generated.resources.language import mantra.composeapp.generated.resources.propose_dialect import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -195,7 +196,7 @@ fun AddDialectScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt index 92486b24..42142be6 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddMemberToChatRoomConfirmationScreen.kt @@ -48,6 +48,7 @@ import mantra.composeapp.generated.resources.once_invited_will_be_able_to_receiv import mantra.composeapp.generated.resources.to_join_the_chat_room import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -135,7 +136,7 @@ fun AddMemberToChatRoomConfirmationScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.weight(1f).fillMaxWidth().padding(MaterialTheme.spacing.space250), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt index f4da3638..9b83428a 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddTranslationArtifactVersionScreen.kt @@ -67,6 +67,7 @@ import mantra.composeapp.generated.resources.translate_into_which_dialect import mantra.composeapp.generated.resources.translate import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable @@ -218,7 +219,7 @@ fun AddTranslationArtifactVersionScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize().padding(MaterialTheme.spacing.space250), + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize().padding(MaterialTheme.spacing.space250), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { if (!addTranslationUIState.canSign) { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt index 79598cf3..4a3561d0 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt @@ -66,6 +66,7 @@ import mantra.composeapp.generated.resources.chapter import mantra.composeapp.generated.resources.words_characters import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -120,7 +121,7 @@ fun ArtifactDetailScreen( } ) { innerPadding -> LazyColumn( - modifier = Modifier.padding(innerPadding).fillMaxSize().padding(MaterialTheme.spacing.space250), + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize().padding(MaterialTheme.spacing.space250), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { // Chapters diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt index 7de99e55..1ae3f510 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChapterDetailScreen.kt @@ -50,6 +50,7 @@ import mantra.composeapp.generated.resources.chunks import mantra.composeapp.generated.resources.words_characters import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -103,7 +104,7 @@ fun ChapterDetailScreen( } ) { innerPadding -> LazyColumn( - modifier = Modifier.padding(innerPadding).fillMaxSize().padding(MaterialTheme.spacing.space250), + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize().padding(MaterialTheme.spacing.space250), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { item { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt index 2b97f9d4..8486c033 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomCreationScreen.kt @@ -41,6 +41,7 @@ import mantra.composeapp.generated.resources.what_should_people_know_about_you import mantra.composeapp.generated.resources.what_will_be_discussed_in_this_chat_room import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @Composable fun ChatRoomCreationScreen( @@ -57,7 +58,7 @@ fun ChatRoomCreationScreen( snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }, modifier = Modifier.imePadding()) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxWidth() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxWidth() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt index 63ab31bc..75b6d7b2 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt @@ -94,6 +94,7 @@ import mantra.composeapp.generated.resources.recovered_of_event_s import mantra.composeapp.generated.resources.recovered_of_still_unreadable import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -146,7 +147,7 @@ fun ChatRoomDetailScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { LazyColumn( modifier = Modifier.weight(1f).fillMaxWidth().padding(MaterialTheme.spacing.space250), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt index 45b62ff1..0d84085b 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomMessagingScreen.kt @@ -69,6 +69,7 @@ import mantra.composeapp.generated.resources.private_message_to import mantra.composeapp.generated.resources.private_to import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -150,7 +151,7 @@ fun ChatRoomMessagingScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.weight(1f).fillMaxWidth() diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt index f7055ee7..1db826bb 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/CreateProfileScreen.kt @@ -61,6 +61,7 @@ import mantra.composeapp.generated.resources.you_are_about_to_create_a_nostr_pro import mantra.composeapp.generated.resources.you_will_be_in_full_control_of_this_profile import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -85,6 +86,7 @@ fun CreateProfileScreen( modifier = Modifier.imePadding()) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt index f88e94e8..afa9db37 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgApprovalScaffold.kt @@ -45,6 +45,7 @@ import mantra.composeapp.generated.resources.back import mantra.composeapp.generated.resources.not_now import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent /** * The frame every ChillDKG approval screen sits in: the view model, the states @@ -95,7 +96,7 @@ internal fun DkgApprovalScaffold( ) { padding -> when (val state = dkgRitualViewModel.dkgRitualUIState) { is DkgRitualUIState.Loading -> Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) @@ -103,7 +104,7 @@ internal fun DkgApprovalScaffold( } is DkgRitualUIState.Error -> Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) @@ -118,7 +119,7 @@ internal fun DkgApprovalScaffold( // on the way in, which looks exactly like the screen not working. if (state.session == null) { Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) @@ -138,6 +139,7 @@ internal fun DkgApprovalScaffold( modifier = Modifier .fillMaxWidth() .padding(padding) + .readableContent() .padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space150) @@ -168,6 +170,7 @@ internal fun DkgApprovalScaffold( modifier = Modifier .fillMaxWidth() .padding(padding) + .readableContent() .verticalScroll(rememberScrollState()) .padding(horizontal = MaterialTheme.spacing.space250, vertical = MaterialTheme.spacing.containerPadding), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space200) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt index fa98cf63..c4097836 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/DkgRitualScreen.kt @@ -101,6 +101,7 @@ import mantra.composeapp.generated.resources.of_members_will_be_needed_to_sign_w import mantra.composeapp.generated.resources.shared_key_for import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent /** * The shared-key ceremony: a ChillDKG ritual run across the group's NIP-17 @@ -240,6 +241,7 @@ fun DkgRitualScreen( Column( modifier = Modifier .padding(innerPadding) + .readableContent() .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(MaterialTheme.spacing.space125), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt index b6a6bbbe..14198252 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/FrostSigningScreen.kt @@ -71,6 +71,7 @@ import mantra.composeapp.generated.resources.signed_their_part import mantra.composeapp.generated.resources.events_signed_together import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent /** * One signing session, and the member's decision about it. @@ -136,7 +137,7 @@ fun FrostSigningScreen( is FrostSigningUIState.Loading -> Loading(padding) is FrostSigningUIState.Error -> Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) @@ -163,6 +164,7 @@ fun FrostSigningScreen( Column( modifier = Modifier .padding(padding) + .readableContent() .fillMaxSize() .verticalScroll(scrollState) .padding(MaterialTheme.spacing.space250), @@ -320,7 +322,7 @@ fun FrostSigningScreen( @Composable private fun Loading(padding: androidx.compose.foundation.layout.PaddingValues) { Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt index 2c5360ca..6f2c68e7 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/HomeScreen.kt @@ -65,6 +65,7 @@ import mantra.composeapp.generated.resources.something_went_wrong import press.mantra.compose.ui.composable.widgets.ErrorState import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -96,7 +97,7 @@ fun HomeScreen( HomeScreenUIState.Error -> { Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize(), + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize(), verticalArrangement = Arrangement.Center, ) { ErrorState() @@ -173,7 +174,7 @@ fun HomeScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { PrimaryScrollableTabRow( modifier = Modifier.padding(MaterialTheme.spacing.space125), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt index 3b853dd4..5befbbdb 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ImplementationPendingScreen.kt @@ -19,6 +19,7 @@ import org.jetbrains.compose.resources.stringResource import mantra.composeapp.generated.resources.functionality_coming_soon_2 import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @Composable fun ImplementationPendingScreen( @@ -27,7 +28,7 @@ fun ImplementationPendingScreen( Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.padding(MaterialTheme.spacing.space250).fillMaxSize(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt index 3b2b2f2e..502e150c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/KeyPackageManagementScreen.kt @@ -58,6 +58,7 @@ import androidx.compose.runtime.rememberCoroutineScope import press.mantra.compose.ui.composable.widgets.rememberNotifier import mantra.composeapp.generated.resources.key_package_published import mantra.composeapp.generated.resources.key_package_rotated +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -116,6 +117,7 @@ fun KeyPackageManagementScreen( ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { LazyColumn( modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt index 34dbc60d..f14c3c4c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LandingScreen.kt @@ -31,6 +31,7 @@ import mantra.composeapp.generated.resources.sign_in import mantra.composeapp.generated.resources.sign_in_to_torch_via_nsec_or_remote_signer import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @Composable fun LandingScreen( @@ -41,6 +42,7 @@ fun LandingScreen( Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( modifier = Modifier.fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LoadingScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LoadingScreen.kt index 093059b2..f6e8815f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LoadingScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/LoadingScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @Composable fun LoadingScreen( @@ -18,6 +19,7 @@ fun LoadingScreen( Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { press.mantra.compose.ui.composable.widgets.LoadingDataIndicator( text = text diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt index d3c66669..5855b278 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/NostrEventDetailScreen.kt @@ -41,6 +41,7 @@ import mantra.composeapp.generated.resources.we_couldn_t_find_your_nostr_event_p import press.mantra.compose.ui.composable.widgets.ErrorState import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -107,7 +108,7 @@ fun NostrEventDetailScreen( else -> { Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.fillMaxSize(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt index ca2daee6..221e46e8 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ProposalListScreen.kt @@ -65,6 +65,7 @@ import mantra.composeapp.generated.resources.waiting_for_you import mantra.composeapp.generated.resources.of_them_could_not_be_read import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent /** * Everything the group has asked its shared key to sign. @@ -128,7 +129,7 @@ fun ProposalListScreen( ) { padding -> when (val state = proposalListViewModel.proposalListUIState) { is ProposalListUIState.Loading -> Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) @@ -136,7 +137,7 @@ fun ProposalListScreen( } is ProposalListUIState.Error -> Column( - modifier = Modifier.fillMaxWidth().padding(padding).padding(MaterialTheme.spacing.space250), + modifier = Modifier.fillMaxWidth().padding(padding).readableContent().padding(MaterialTheme.spacing.space250), horizontalAlignment = Alignment.CenterHorizontally ) { Spacer(modifier = Modifier.height(MaterialTheme.spacing.space600)) @@ -155,7 +156,7 @@ fun ProposalListScreen( } LazyColumn( - modifier = Modifier.padding(padding).fillMaxSize().padding(MaterialTheme.spacing.space250), + modifier = Modifier.padding(padding).readableContent().fillMaxSize().padding(MaterialTheme.spacing.space250), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { if (state.proposals.isEmpty()) { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt index 3659bc27..58494ef7 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SearchMemberToAddToChatRoomScreen.kt @@ -48,6 +48,7 @@ import mantra.composeapp.generated.resources.search_member_functionality import mantra.composeapp.generated.resources.search_message_functionality_will_be_here import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -104,7 +105,7 @@ fun SearchMemberToAddToChatRoomScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.weight(1f).fillMaxWidth(), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt index 53f43bb3..34b17012 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomMembersScreen.kt @@ -55,6 +55,7 @@ import mantra.composeapp.generated.resources.next_with import mantra.composeapp.generated.resources.selected import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent /** * Second step of group creation: pick who is in the group. @@ -153,7 +154,7 @@ fun SelectChatRoomMembersScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { if (selectChatRoomMembersUIState.profiles.isEmpty()) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt index fe835df7..ed937b78 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SelectChatRoomTypeScreen.kt @@ -72,6 +72,7 @@ import mantra.composeapp.generated.resources.was_created_but_its_shared_key_cere import mantra.composeapp.generated.resources.you_and_others import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent /** * Last step of group creation: convenient (one admin) or robust (everyone @@ -189,6 +190,7 @@ fun SelectChatRoomTypeScreen( Column( modifier = Modifier .padding(innerPadding) + .readableContent() .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(MaterialTheme.spacing.space125), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt index a6ee9dcf..c2da2b5a 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ShareProfileScreen.kt @@ -54,6 +54,7 @@ import mantra.composeapp.generated.resources.we_couldn_t_find_the_local_profile_ import press.mantra.compose.ui.composable.widgets.ErrorState import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -106,6 +107,7 @@ fun ShareProfileScreen( ) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { LazyColumn( modifier = Modifier.fillMaxWidth().weight(1f), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt index ae7ae4f8..a874a4ad 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SignInScreen.kt @@ -46,6 +46,7 @@ import mantra.composeapp.generated.resources.this_will_give_you_read_only_access import mantra.composeapp.generated.resources.this_will_give_you_write_access_to_the import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -66,6 +67,7 @@ fun SignInToProfileScreen( modifier = Modifier.imePadding()) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt index d18b92d5..98f0e0e5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/SocialPreconditionScreen.kt @@ -33,6 +33,7 @@ import mantra.composeapp.generated.resources.view_invites import mantra.composeapp.generated.resources.who_will_you_be_passing_the_aux_to import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @Composable fun SocialPreconditionScreen( @@ -69,7 +70,7 @@ fun SocialPreconditionScreen( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { Column( modifier = Modifier.fillMaxWidth().padding(MaterialTheme.spacing.space250), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt index 62124519..0aa6b47f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslateChunkScreen.kt @@ -64,6 +64,7 @@ import mantra.composeapp.generated.resources.translated_text import mantra.composeapp.generated.resources.translation import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -197,6 +198,7 @@ fun TranslateChunkScreen( Column( modifier = Modifier .padding(innerPadding) + .readableContent() .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(MaterialTheme.spacing.space250), diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt index f3919405..ad365130 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationArtifactVersionDetailScreen.kt @@ -59,6 +59,7 @@ import mantra.composeapp.generated.resources.chapter import mantra.composeapp.generated.resources.chunks_translated import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -117,7 +118,7 @@ fun TranslationArtifactVersionDetailScreen( } ) { innerPadding -> LazyColumn( - modifier = Modifier.padding(innerPadding).fillMaxSize().padding(MaterialTheme.spacing.space250), + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize().padding(MaterialTheme.spacing.space250), verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.space125) ) { // Chapters the source version has and this translation diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt index 971fab63..3fbf458f 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationChapterScreen.kt @@ -54,6 +54,7 @@ import mantra.composeapp.generated.resources.chapter_translation import mantra.composeapp.generated.resources.this_chapter_has_no_chunks import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -107,7 +108,7 @@ fun TranslationChapterScreen( } ) { innerPadding -> LazyColumn( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { // Header row: dialect names. item { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt index ce8714fd..30222495 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnannouncedProfileScreen.kt @@ -24,6 +24,7 @@ import mantra.composeapp.generated.resources.everything_is_cryptographical_sound import mantra.composeapp.generated.resources.torch_will_be_broadcast_what_you_publish_to import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -31,6 +32,7 @@ fun UnannouncedProfileScreen() { Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt index b48eca3b..aed303c5 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnindexedProfileScreen.kt @@ -24,6 +24,7 @@ import mantra.composeapp.generated.resources.events_are_indexed_so_that_we_can_d import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just_2 import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -31,6 +32,7 @@ fun UnindexedProfileScreen() { Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt index aff957c4..3a48794d 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileScreen.kt @@ -24,6 +24,7 @@ import mantra.composeapp.generated.resources.all_broadcasts_are_queued_so_that_w import mantra.composeapp.generated.resources.everything_is_cryptographical_sound_just_3 import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -31,6 +32,7 @@ fun UnqueuedProfileScreen() { Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt index b2b4e960..ed92eb19 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnqueuedProfileSynchronizationScreen.kt @@ -26,6 +26,7 @@ import mantra.composeapp.generated.resources.we_are_looking_for_your_profile_on_ import mantra.composeapp.generated.resources.we_are_searching_the_internet_to_find_your import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -45,6 +46,7 @@ fun UnqueuedProfileSynchronizationScreen( Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt index 6bf8c904..ff0fdeca 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsignedProfileScreen.kt @@ -24,6 +24,7 @@ import mantra.composeapp.generated.resources.as_long_as_you_control_your_keys_th import mantra.composeapp.generated.resources.your_profile_is_almost_ready_just_getting_it import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -31,6 +32,7 @@ fun UnsignedProfileScreen() { Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt index 6ca4efb3..92e9c883 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/UnsyncedProfileScreen.kt @@ -24,6 +24,7 @@ import mantra.composeapp.generated.resources.we_are_looking_for_your_profile_on_ import mantra.composeapp.generated.resources.we_are_searching_the_internet_to_find_your import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -33,6 +34,7 @@ fun UnsyncedProfileScreen( Scaffold(snackbarHost = { SnackbarHost(LocalSnackbarHostState.current) }) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt index f95a776b..8c581669 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/WriteNewNoteScreen.kt @@ -58,6 +58,7 @@ import mantra.composeapp.generated.resources.what_vibrations_do_you_want_to_send import mantra.composeapp.generated.resources.replying_to import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalFoundationApi::class) @Composable @@ -86,6 +87,7 @@ fun WriteNewNoteScreen( modifier = Modifier.imePadding()) { innerPadding -> Column( modifier = Modifier.padding(innerPadding) + .readableContent() ) { Column( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt index 704f211b..81a82529 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/MetadataEventDetail.kt @@ -69,6 +69,7 @@ import mantra.composeapp.generated.resources.send_message import mantra.composeapp.generated.resources.unfollow import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -146,7 +147,7 @@ fun MetadataEventDetail( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { LazyColumn( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt index 4874b5c6..e3e2ec73 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/detail/TextNoteEventDetail.kt @@ -58,6 +58,7 @@ import mantra.composeapp.generated.resources.reply_to import press.mantra.compose.ui.composable.widgets.ErrorState import androidx.compose.material3.SnackbarHost import press.mantra.compose.ui.composable.widgets.LocalSnackbarHostState +import press.mantra.compose.ui.theme.readableContent @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable @@ -197,7 +198,7 @@ fun TextNoteEventDetail( } ) { innerPadding -> Column( - modifier = Modifier.padding(innerPadding).fillMaxSize() + modifier = Modifier.padding(innerPadding).readableContent().fillMaxSize() ) { LazyColumn( diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/theme/ReadableMeasure.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/theme/ReadableMeasure.kt new file mode 100644 index 00000000..95bae745 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/theme/ReadableMeasure.kt @@ -0,0 +1,90 @@ +package press.mantra.compose.ui.theme + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +/** + * M3's upper bound on line length: *"across all breakpoints, adjust margins and type + * styles to keep text between 40–60 characters per line."* + * + * The lower bound is not enforced here, and does not need to be. A window narrower than + * this holds fewer characters by arithmetic, and no cap can add any -- 40 is a floor on + * the type scale and the margin, which the compact margin of 16dp already respects: a + * 400dp phone at the default text size holds about 46 characters of `bodyLarge`. + */ +const val MaxCharactersPerLine = 60 + +/** + * The width of one character of English prose, as a fraction of the font size. + * + * There is no exact answer -- a proportional face has no character width -- and this is + * the standard estimate for mixed-case Latin text with its spaces: half an em. It is the + * number to change if the app ever takes a font whose average advance is visibly wider or + * narrower than the system stack's, and changing it moves the measure on every screen at + * once, which is the reason it is a named constant rather than a `480.dp` somewhere. + */ +private const val AverageCharacterAdvance = 0.5f + +/** + * The widest a column of [charactersPerLine] characters should be. + * + * Derived from the type scale rather than fixed, and that is the point: `sp` carries the + * reader's font scale, so at 200% text size the column doubles and still holds sixty + * characters. A hardcoded `480.dp` would hold thirty, which is the failure the 40–60 rule + * is about -- and it would fail silently, because the text still fits. + */ +@Composable +fun readableContentWidth(charactersPerLine: Int = MaxCharactersPerLine): Dp { + val bodySize = MaterialTheme.typography.bodyLarge.fontSize + // `bodyLarge` is declared in sp and this is the only role prose is set in, but a type + // scale is a value somebody can edit, and an em-relative or unspecified size here + // would convert to nonsense rather than fail. 16dp is what the baseline scale gives. + val bodySizeDp = if (bodySize.isSp) with(LocalDensity.current) { bodySize.toDp() } else 16.dp + return readableWidthFor(bodySizeDp, charactersPerLine) +} + +/** + * [readableContentWidth] without the composition, so the arithmetic can be asserted. + */ +fun readableWidthFor(fontSize: Dp, charactersPerLine: Int = MaxCharactersPerLine): Dp = + fontSize * AverageCharacterAdvance * charactersPerLine + +/** + * Holds this element to a readable measure and centres it in whatever space it is given. + * + * The one change that decides whether a screen written for a phone survives a 1800dp + * window. Without it a paragraph runs the full width and becomes unreadable -- the eye + * loses the start of the next line -- and a form's fields stretch to 1700dp for a + * six-character input. With it the content keeps the proportions it was designed at and + * the window's extra width becomes margin, which is what M3's single-pane canonical + * layout does. + * + * The centring is of the *column*, not of the text inside it. Those are opposite things: + * a centred column still has a straight leading edge for every row, avatar and icon to + * align to, which is what the grids-and-spacing page asks for. Centred text has none. + * + * Applied at the top of a `Scaffold`'s content, so a screen gets it once: + * + * Scaffold(...) { innerPadding -> + * Column(modifier = Modifier.padding(innerPadding).readableContent()) { … } + * } + * + * A no-op on any window narrower than the measure, which is every phone. + */ +@Composable +fun Modifier.readableContent(charactersPerLine: Int = MaxCharactersPerLine): Modifier = + this + // fillMaxWidth first so that the centring has the whole window to centre within; + // wrapContentWidth then relaxes the minimum so the child may be narrower, and + // widthIn caps it. Reordering these silently loses either the cap or the centring. + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + .widthIn(max = readableContentWidth(charactersPerLine)) diff --git a/composeApp/src/commonTest/kotlin/press/mantra/compose/ui/theme/ReadableMeasureTest.kt b/composeApp/src/commonTest/kotlin/press/mantra/compose/ui/theme/ReadableMeasureTest.kt new file mode 100644 index 00000000..d5c51979 --- /dev/null +++ b/composeApp/src/commonTest/kotlin/press/mantra/compose/ui/theme/ReadableMeasureTest.kt @@ -0,0 +1,70 @@ +package press.mantra.compose.ui.theme + +import androidx.compose.ui.unit.dp +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * The reading measure against M3's 40–60 characters per line. + * + * The whole point of deriving the cap rather than writing `480.dp` is that it follows the + * reader's text size, and that is the property no screenshot catches: a hardcoded column + * still looks correct at 200% text scale, it just holds thirty characters instead of + * sixty. + */ +class ReadableMeasureTest { + + @Test + fun `sixty characters of the default body size is 480dp`() { + // bodyLarge is 16sp in the baseline scale, and 1sp is 1dp at the default text + // size. Half an em per character times sixty. + assertEquals(480.dp, readableWidthFor(16.dp)) + } + + @Test + fun `the measure follows the text size rather than the window`() { + // A reader at 200% text size gets a column twice as wide, and still sixty + // characters. This is the assertion a constant cannot pass, and the reason the + // function takes a font size at all. + assertEquals( + readableWidthFor(16.dp) * 2f, + readableWidthFor(32.dp), + "doubling the text size did not double the measure", + ) + } + + @Test + fun `the cap is the top of M3's range and not the bottom`() { + // 40 is the floor and 60 the ceiling; a max-width enforces the ceiling. Capping + // at 40 would be the same mistake in the other direction -- a column too narrow + // to read comfortably on any window wide enough to matter. + assertEquals(MaxCharactersPerLine, 60) + assertTrue(readableWidthFor(16.dp, 40) < readableWidthFor(16.dp, 60)) + } + + @Test + fun `a phone window holds a line inside the range`() { + // The check that the floor needs no enforcement. A 400dp compact window less its + // two 16dp margins is 368dp, which at 8dp a character is 46 -- inside 40 to 60, + // so the cap is a no-op there and nothing has to widen anything. + val compactContent = 400.dp - spacingFor(Breakpoint.Compact).screenMargin * 2f + val charactersPerLine = compactContent / (readableWidthFor(16.dp) / 60f) + + assertTrue( + charactersPerLine in 40f..60f, + "a 400dp window fits $charactersPerLine characters, outside M3's 40-60", + ) + assertTrue( + compactContent < readableWidthFor(16.dp), + "the measure is narrower than a phone, so it would crop rather than cap", + ) + } + + @Test + fun `a desktop window is held to the measure rather than filled`() { + // The case this exists for. 1800dp of window, 480dp of text, and the remaining + // 1320dp becomes margin instead of a line nobody can track back to its start. + assertTrue(readableWidthFor(16.dp) < 1800.dp) + } +} diff --git a/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/theme/ReadableContentLayoutJvmTest.kt b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/theme/ReadableContentLayoutJvmTest.kt new file mode 100644 index 00000000..647c55af --- /dev/null +++ b/composeApp/src/jvmTest/kotlin/press/mantra/compose/ui/theme/ReadableContentLayoutJvmTest.kt @@ -0,0 +1,122 @@ +package press.mantra.compose.ui.theme + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.material3.MaterialTheme +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo +import androidx.compose.ui.test.assertWidthIsEqualTo +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.runDesktopComposeUiTest +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.test.Test +import kotlin.test.assertEquals + +/** + * What the theme does to a real composition in a window of a given width. + * + * [BreakpointTest] and [ReadableMeasureTest] assert arithmetic. This asserts the two + * things arithmetic cannot reach, in a window that genuinely is 1400 pixels wide: + * + * - that `currentBreakpoint()` reads the window at all. It goes through + * `currentWindowDpSize()`, `LocalWindowInfo` and `LocalDensity`, and a version of it + * that measured the wrong thing -- the parent's constraints, say -- would return + * `Compact` everywhere and pass every unit test in the suite. + * - that `readableContent()` caps *and* centres. It is `fillMaxWidth` then + * `wrapContentWidth` then `widthIn`, and every permutation of those three compiles and + * renders something plausible at phone width. Swap the last two and the content is + * centred but never capped; drop the first and it is capped but hugs the left edge. + * + * The test density is 1, so a pixel is a dp and the window widths below are the ones the + * phase's acceptance criterion names. + */ +@OptIn(ExperimentalTestApi::class) +class ReadableContentLayoutJvmTest { + + /** 60 characters of 16sp `bodyLarge` at the default text scale. */ + private val measure = 480.dp + + @Test + fun `a desktop window caps the content and centres it`() = inWindow(1400) { + // (1400 - 480) / 2 each side. The window's extra width becomes margin, which is + // what M3's single-pane canonical layout does -- not a 1400dp line of text, and + // not a 480dp column pinned against one edge. + onNodeWithTag(Content).assertWidthIsEqualTo(measure) + onNodeWithTag(Content).assertLeftPositionInRootIsEqualTo(460.dp) + } + + @Test + fun `a phone window is left exactly as it was`() = inWindow(400) { + // The claim that let this be applied to all 49 screen roots in one pass: below the + // measure it is not a cap, an inset or a centring -- it is nothing. If this fails, + // every phone layout in the app moved. + onNodeWithTag(Content).assertWidthIsEqualTo(400.dp) + onNodeWithTag(Content).assertLeftPositionInRootIsEqualTo(0.dp) + } + + @Test + fun `the measure itself is the last width left alone`() = inWindow(480) { + onNodeWithTag(Content).assertWidthIsEqualTo(measure) + onNodeWithTag(Content).assertLeftPositionInRootIsEqualTo(0.dp) + } + + @Test + fun `the theme classifies the window it is actually in`() { + listOf( + 400 to Breakpoint.Compact, + 700 to Breakpoint.Medium, + 1000 to Breakpoint.Expanded, + 1400 to Breakpoint.Large, + 1800 to Breakpoint.ExtraLarge, + ).forEach { (windowWidth, expected) -> + var seen: Breakpoint? = null + var margin: Dp? = null + runDesktopComposeUiTest(width = windowWidth, height = 900) { + setContent { + TorchTheme { + seen = MaterialTheme.breakpoint + margin = MaterialTheme.spacing.screenMargin + } + } + } + + assertEquals(expected, seen, "a ${windowWidth}dp window") + assertEquals( + if (expected == Breakpoint.Compact) 16.dp else 24.dp, + margin, + "the screen margin in a ${windowWidth}dp window", + ) + } + } + + /** + * Composes a `readableContent()` column in a window [windowWidth] pixels across. + * + * The tagged node is a child of the modifier rather than the modifier's own node: + * `fillMaxWidth` makes that node the full window, and it is the child inside the cap + * whose width and position are the thing under test. + */ + private fun inWindow(windowWidth: Int, assertions: androidx.compose.ui.test.ComposeUiTest.() -> Unit) = + runDesktopComposeUiTest(width = windowWidth, height = 900) { + setContent { TaggedContent() } + assertions() + } + + @Composable + private fun TaggedContent() { + TorchTheme { + Box(Modifier.readableContent()) { + Box(Modifier.fillMaxWidth().height(8.dp).testTag(Content)) + } + } + } + + private companion object { + const val Content = "content" + } +}