Pantrix UI

Uygulamanın kendi modal katmanı

Compose Multiplatform'da bir uygulamanın kendi modal katmanını (bütün pencerenin üstünde bir perde ve onun üstünde uygulamanın çizdiği içerik) kurmasının neden her platformda başka bir iş olduğu, kütüphanenin bunun için ne verdiği ve nasıl sınandığı. Sorun Pantrix'e özgü değildir: bir dialog'un ya da bir sheet'in çizmediği bir katman (kendi süzgeçleri olan bir arama, büyük gösterilen bir resim, telefonu dolduran bir düzenleyici) isteyen her Compose Multiplatform projesinde aynı şekilde görülür.

Compose hakkında burada söylenen her şey Compose Multiplatform 1.12.2'nin kaynaklarında okunmuş ya da bu depoda koşan bir testte görülmüştür; hangisinin nerede görüldüğü 7. ve 8. bölümlerde yazılıdır.

1. Sorun

Belirti

Neden

Dialog'un her platformda kendisi karar verdiği şeyler vardır (Compose Multiplatform 1.12.2, ui modülü, DialogProperties):

PlatformDialog neye kendisi karar verirKapatmak için
AndroidPencerenin karartması (FLAG_DIM_BEHIND), açılış animasyonu, varsayılan genişlik, sistem çubuklarının içinde kalmak, çubukların simgeleriusePlatformDefaultWidth = false, decorFitsSystemWindows = false; pencerede clearFlags(FLAG_DIM_BEHIND), setWindowAnimations(0) ve çubukların görünüşü (WindowCompat.getInsetsController)
iOS, masaüstü, webVarsayılan genişlik, pencerenin payları ve yazılım klavyesinin payı, perdenin rengi, açılış ve kapanış geçişiusePlatformDefaultWidth = false, usePlatformInsets = false, useSoftwareKeyboardInset = false, scrimColor = Color.Transparent, animateTransition = false (deneysel kurucu)

Bunlar kapatılınca katman boş bir yüzeydir: perdeyi, içeriğin yerini, hareketi ve odağı uygulama kendisi kurar, her platformda aynı biçimde.

2. Çözüm

Bileşenlerde (com.pantrixui.components.overlay.modal):

  1. PantrixModal(visible, onDismissRequest, …) { content }: bütün pencerenin üstünde bir perde ve onun üstünde uygulamanın içeriği. PantrixDialog'un, PantrixSheet'in ve PantrixCommandPalette'in üstünde durduğu katmandır.

  2. ModalPosition: içeriğin yeri ve nasıl girip çıktığı. Center pencerenin ortası, Top üstüne yakın: alanı yerinde kalan bir arama.

Modal içeriğe dolgu, şekil ya da genişlik vermez: içerik kendini çizer. Gerisi modalındır:

3. Pantrix ile

3.1. Üstte bir arama

var searching by rememberSaveable { mutableStateOf(false) }
PantrixModal(
    visible = searching,
    onDismissRequest = { searching = false },
    position = ModalPosition.Top,
    paneTitle = stringResource(Res.string.search),
    tagKey = "search"
) {
    PantrixSurface(Modifier.widthIn(max = 640.dp), shape = PantrixTheme.shapes.lg, elevation = 8.dp) {
        SearchPanel(onOpen = { searching = false })
    }
}

3.2. Telefonda bir sayfa

PantrixModal(
    visible = editing,
    onDismissRequest = { editing = false },
    margin = PaddingValues(),
    paneTitle = stringResource(Res.string.edit_note),
    scrimColor = PantrixTheme.colors.background,
    tagKey = "editor"
) {
    NoteEditor(Modifier.fillMaxSize(), onDone = { editing = false })
}

Boşluksuz bir margin, kendini dolduran içerik ve temanın zemini olan bir perde modalı bir sayfa yapar: içerik güvenli alanı doldurur, sistem çubuklarının arkası da zeminin rengindedir.

3.3. Bir karar isteyen

dismissOnScrimClick = false ve dismissOnBack = false modalı yalnızca içindeki bir düğmeyle kapanır yapar; perde o zaman ekran okuyucunun atladığı bir dolgudur.

4. Pantrix olmadan

Aşağıdaki, kütüphanesiz bir projeye kopyalanabilecek en kısa hâldir; perdeyi, Escape'i ve hareketi verir, odak tuzağını ve katlanmayı vermez. Pantrix'in kodu (ui/components/src/*Main/kotlin/com/pantrixui/components/overlay/modal/) aynı şeyi testleri ve kenar durumlarıyla yapar.

@Composable
fun AppModal(visible: Boolean, onDismiss: () -> Unit, content: @Composable () -> Unit) {
    val shown = remember { MutableTransitionState(false) }
    shown.targetState = visible
    if (!visible && shown.isIdle && !shown.currentState) return
    Dialog(
        onDismissRequest = onDismiss,
        // Android'de ayrıca pencerenin karartmasını ve animasyonunu kapatın (1. bölümdeki tablo).
        properties = DialogProperties(usePlatformDefaultWidth = false, dismissOnClickOutside = false)
    ) {
        AnimatedVisibility(shown, enter = fadeIn(), exit = fadeOut()) {
            Box(
                Modifier
                    .fillMaxSize()
                    .background(Color.Black.copy(alpha = 0.4f))
                    .pointerInput(onDismiss) { detectTapGestures { onDismiss() } },
                contentAlignment = Alignment.Center
            ) {
                Box(Modifier.pointerInput(Unit) {}) { content() }
            }
        }
    }
}

5. Yardımcının yapmadıkları

6. Test etmek

7. Tuzaklar

8. Doğrulama

Testler

NeNerede
Kapalıyken hiçbir şey kurulmaması, pencerenin başlığı, ortada durmak, boşluk, perdenin rengi ve ona basmak, ekran okuyucu için "Kapat", Escape, ilk odak, Tab'ın içeride dönmesi, odağın geri dönmesi, altındaki uygulamanın halkası, çıkış hareketi, azaltılmış hareketModalTest (JVM, iOS, JS, Wasm)
Herkese açık modalın etiketi, olayları, üstte ve ortada durmak, varsayılan boşluk ve boşluksuz sayfa, perdenin rengi TOLD_APART'taPantrixModalTest (JVM, iOS, JS, Wasm)
Android'in penceresi: kendi karartmasının ve animasyonunun olmaması, ilk kareyi beklemek, ekran kadar büyüklük, geri, perde, çubukların simgeleriDialogOnAndroidTest (Robolectric; aynı kabuk)
Görünüş: üstte ve ortada içerik, açık ve koyu temaComponentScreenshotTest.modal (JVM, Android, iOS)

Elle bakılanlar

Doğrulanmayanlar

9. Bu depoda nerede

10. Açık kalanlar

Yok.