refactor: add dynamic font scaling support and integrate ProvideDynamicTypeFontScale across modules
This commit is contained in:
1 parent
df38936893
commit
abeb484258
12 files changed
+143
-1
No files matched your search
@@ -1,9 +1,18 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("gwenedeg.kmp.library")
|
id("gwenedeg.kmp.library")
|
||||||
|
id("gwenedeg.compose")
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
androidLibrary {
|
androidLibrary {
|
||||||
namespace = "bzh.ajaury.chombev.core.accessibility"
|
namespace = "bzh.ajaury.chombev.core.accessibility"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain.dependencies {
|
||||||
|
// UI - Compose
|
||||||
|
implementation(libs.compose.runtime)
|
||||||
|
implementation(libs.compose.ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package bzh.ajaury.chombev.core.accessibility.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
/** No-op: the font scale already follows the system setting. */
|
||||||
|
@Composable
|
||||||
|
actual fun ProvideDynamicTypeFontScale(content: @Composable () -> Unit) = content()
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package bzh.ajaury.chombev.core.accessibility.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the system text size to [content] as soon as the user changes it.
|
||||||
|
*
|
||||||
|
* Only needed on iOS, see its implementation. Every Compose scene must be wrapped: the root one
|
||||||
|
* and each dialog or bottom sheet, which iOS renders in a scene of its own that does not inherit
|
||||||
|
* the font scale provided by the root one.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
expect fun ProvideDynamicTypeFontScale(content: @Composable () -> Unit)
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package bzh.ajaury.chombev.core.accessibility.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import platform.Foundation.NSNotificationCenter
|
||||||
|
import platform.Foundation.NSOperationQueue
|
||||||
|
import platform.UIKit.UIApplication
|
||||||
|
import platform.UIKit.UIContentSizeCategoryAccessibilityExtraExtraExtraLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryAccessibilityExtraExtraLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryAccessibilityExtraLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryAccessibilityLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryAccessibilityMedium
|
||||||
|
import platform.UIKit.UIContentSizeCategoryDidChangeNotification
|
||||||
|
import platform.UIKit.UIContentSizeCategoryExtraExtraExtraLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryExtraExtraLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryExtraLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryExtraSmall
|
||||||
|
import platform.UIKit.UIContentSizeCategoryLarge
|
||||||
|
import platform.UIKit.UIContentSizeCategoryMedium
|
||||||
|
import platform.UIKit.UIContentSizeCategorySmall
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the system text size (Dynamic Type) to [content] as soon as the user changes it.
|
||||||
|
*
|
||||||
|
* Compose Multiplatform reads the font scale once, when the Compose scene is created, so a change
|
||||||
|
* made in the iOS settings only shows once the scene is created again: after the app restarts for
|
||||||
|
* the root scene, or when it is opened again for a dialog or bottom sheet. Workaround for CMP-10365
|
||||||
|
* (https://youtrack.jetbrains.com/issue/CMP-10365), fixed in Compose Multiplatform 1.13.0: remove
|
||||||
|
* it once the project is upgraded.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
actual fun ProvideDynamicTypeFontScale(content: @Composable () -> Unit) {
|
||||||
|
var contentSizeCategory by remember {
|
||||||
|
mutableStateOf(UIApplication.sharedApplication.preferredContentSizeCategory)
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val notificationCenter = NSNotificationCenter.defaultCenter
|
||||||
|
val observer = notificationCenter.addObserverForName(
|
||||||
|
name = UIContentSizeCategoryDidChangeNotification,
|
||||||
|
`object` = null,
|
||||||
|
queue = NSOperationQueue.mainQueue,
|
||||||
|
) { _ ->
|
||||||
|
contentSizeCategory = UIApplication.sharedApplication.preferredContentSizeCategory
|
||||||
|
}
|
||||||
|
onDispose { notificationCenter.removeObserver(observer) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val density = LocalDensity.current
|
||||||
|
// Only the font scale is overridden: the pixel density stays the one Compose computed.
|
||||||
|
val fontScale = contentSizeCategoryToFontScale[contentSizeCategory] ?: density.fontScale
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalDensity provides Density(density = density.density, fontScale = fontScale),
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same mapping as Compose Multiplatform (`uiContentSizeCategoryToFontScaleMap`, which is private),
|
||||||
|
// so that the font scale matches the one Compose computes when the scene is created.
|
||||||
|
private val contentSizeCategoryToFontScale = mapOf(
|
||||||
|
UIContentSizeCategoryExtraSmall to 0.8f,
|
||||||
|
UIContentSizeCategorySmall to 0.85f,
|
||||||
|
UIContentSizeCategoryMedium to 0.9f,
|
||||||
|
UIContentSizeCategoryLarge to 1f,
|
||||||
|
UIContentSizeCategoryExtraLarge to 1.1f,
|
||||||
|
UIContentSizeCategoryExtraExtraLarge to 1.2f,
|
||||||
|
UIContentSizeCategoryExtraExtraExtraLarge to 1.3f,
|
||||||
|
UIContentSizeCategoryAccessibilityMedium to 1.4f,
|
||||||
|
UIContentSizeCategoryAccessibilityLarge to 1.5f,
|
||||||
|
UIContentSizeCategoryAccessibilityExtraLarge to 1.6f,
|
||||||
|
UIContentSizeCategoryAccessibilityExtraExtraLarge to 1.7f,
|
||||||
|
UIContentSizeCategoryAccessibilityExtraExtraExtraLarge to 1.8f,
|
||||||
|
)
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package bzh.ajaury.chombev.core.accessibility.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
/** No-op: the font scale already follows the system setting. */
|
||||||
|
@Composable
|
||||||
|
actual fun ProvideDynamicTypeFontScale(content: @Composable () -> Unit) = content()
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package bzh.ajaury.chombev.core.accessibility.ui
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
/** No-op: the font scale already follows the system setting. */
|
||||||
|
@Composable
|
||||||
|
actual fun ProvideDynamicTypeFontScale(content: @Composable () -> Unit) = content()
|
||||||
@@ -13,6 +13,9 @@ kotlin {
|
|||||||
implementation(libs.compose.uiToolingPreview)
|
implementation(libs.compose.uiToolingPreview)
|
||||||
}
|
}
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
|
// Core modules
|
||||||
|
implementation(projects.core.accessibility)
|
||||||
|
|
||||||
// Data modules
|
// Data modules
|
||||||
implementation(projects.data.preferences)
|
implementation(projects.data.preferences)
|
||||||
implementation(projects.data.resources)
|
implementation(projects.data.resources)
|
||||||
|
|||||||
+3
@@ -33,6 +33,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import bzh.ajaury.chombev.core.accessibility.ui.ProvideDynamicTypeFontScale
|
||||||
import bzh.ajaury.chombev.onboarding.model.OnboardingLevel
|
import bzh.ajaury.chombev.onboarding.model.OnboardingLevel
|
||||||
import bzh.ajaury.chombev.onboarding.ui.components.OnboardingInfoPage
|
import bzh.ajaury.chombev.onboarding.ui.components.OnboardingInfoPage
|
||||||
import bzh.ajaury.chombev.onboarding.ui.components.OnboardingLevelPage
|
import bzh.ajaury.chombev.onboarding.ui.components.OnboardingLevelPage
|
||||||
@@ -90,6 +91,7 @@ fun OnboardingScreen(
|
|||||||
// The content sizes itself against the window rather than the platform default width.
|
// The content sizes itself against the window rather than the platform default width.
|
||||||
properties = DialogProperties(usePlatformDefaultWidth = false),
|
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||||
) {
|
) {
|
||||||
|
ProvideDynamicTypeFontScale {
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.sizeIn(maxWidth = DIALOG_MAX_WIDTH, maxHeight = DIALOG_MAX_HEIGHT)
|
.sizeIn(maxWidth = DIALOG_MAX_WIDTH, maxHeight = DIALOG_MAX_HEIGHT)
|
||||||
@@ -105,6 +107,7 @@ fun OnboardingScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Scaffold { innerPadding ->
|
Scaffold { innerPadding ->
|
||||||
OnboardingContent(
|
OnboardingContent(
|
||||||
|
|||||||
+3
@@ -14,6 +14,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import bzh.ajaury.chombev.core.accessibility.ui.ProvideDynamicTypeFontScale
|
||||||
import bzh.ajaury.chombev.player.ui.components.LabeledSlider
|
import bzh.ajaury.chombev.player.ui.components.LabeledSlider
|
||||||
import bzh.ajaury.chombev.preferences.model.PlaybackPreferences
|
import bzh.ajaury.chombev.preferences.model.PlaybackPreferences
|
||||||
import bzh.ajaury.chombev.preferences.ui.PreferenceToggle
|
import bzh.ajaury.chombev.preferences.ui.PreferenceToggle
|
||||||
@@ -50,6 +51,7 @@ fun PlaybackSpeedBottomSheet(
|
|||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
) {
|
) {
|
||||||
|
ProvideDynamicTypeFontScale {
|
||||||
PlaybackSpeedContent(
|
PlaybackSpeedContent(
|
||||||
speed = speed,
|
speed = speed,
|
||||||
breakSeconds = breakSeconds,
|
breakSeconds = breakSeconds,
|
||||||
@@ -60,6 +62,7 @@ fun PlaybackSpeedBottomSheet(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlaybackSpeedContent(
|
private fun PlaybackSpeedContent(
|
||||||
|
|||||||
+3
@@ -32,6 +32,7 @@ import androidx.compose.ui.semantics.setProgress
|
|||||||
import androidx.compose.ui.semantics.stateDescription
|
import androidx.compose.ui.semantics.stateDescription
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import bzh.ajaury.chombev.core.accessibility.ui.ProvideDynamicTypeFontScale
|
||||||
import bzh.ajaury.chombev.preferences.model.SubtitlePreferences
|
import bzh.ajaury.chombev.preferences.model.SubtitlePreferences
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.Res
|
import bzh.ajaury.chombev.resources.generated.resources.Res
|
||||||
import bzh.ajaury.chombev.resources.generated.resources.subtitle_marked_letters_description
|
import bzh.ajaury.chombev.resources.generated.resources.subtitle_marked_letters_description
|
||||||
@@ -68,6 +69,7 @@ fun SubtitlePreferencesBottomSheet(
|
|||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
) {
|
) {
|
||||||
|
ProvideDynamicTypeFontScale {
|
||||||
SubtitlePreferencesContent(
|
SubtitlePreferencesContent(
|
||||||
subtitlePreferences = subtitlePreferences,
|
subtitlePreferences = subtitlePreferences,
|
||||||
onShowTranscriptionChange = onShowTranscriptionChange,
|
onShowTranscriptionChange = onShowTranscriptionChange,
|
||||||
@@ -78,6 +80,7 @@ fun SubtitlePreferencesBottomSheet(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SubtitlePreferencesContent(
|
private fun SubtitlePreferencesContent(
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ kotlin {
|
|||||||
}
|
}
|
||||||
commonMain.dependencies {
|
commonMain.dependencies {
|
||||||
// Core modules
|
// Core modules
|
||||||
|
implementation(projects.core.accessibility)
|
||||||
implementation(projects.core.coroutines)
|
implementation(projects.core.coroutines)
|
||||||
implementation(projects.core.logging)
|
implementation(projects.core.logging)
|
||||||
implementation(projects.core.model)
|
implementation(projects.core.model)
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package bzh.ajaury.chombev
|
package bzh.ajaury.chombev
|
||||||
|
|
||||||
import androidx.compose.ui.window.ComposeUIViewController
|
import androidx.compose.ui.window.ComposeUIViewController
|
||||||
|
import bzh.ajaury.chombev.core.accessibility.ui.ProvideDynamicTypeFontScale
|
||||||
|
|
||||||
@Suppress("ktlint:standard:function-naming")
|
@Suppress("ktlint:standard:function-naming")
|
||||||
fun MainViewController() = ComposeUIViewController { App() }
|
fun MainViewController() =
|
||||||
|
ComposeUIViewController {
|
||||||
|
ProvideDynamicTypeFontScale {
|
||||||
|
App()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user