Communitygithub.com

lerchenflo/claudeskills

Use esta skill para migrar seu app Jetpack Compose para suporte adaptativo edge-to-edge e solucionar problemas comuns. Use esta skill para corrigir componentes de UI (como botões ou listas) que ficam ocultos ou sobrepostos pela barra de navegação ou barra de status, corrigir insets de IME e corrigir a legibilidade das barras do sistema.

O que é claudeskills?

Esta skill auxilia desenvolvedores Android a migrar aplicativos Jetpack Compose para o suporte edge-to-edge adaptativo, garantindo que a interface do usuário se estenda corretamente por trás das barras do sistema (status e navegação) sem sobreposições ou cortes. Ela aborda problemas comuns como componentes ocultos por barras do sistema, ajuste de insets do teclado virtual (IME) e legibilidade dos ícones das barras do sistema. A skill oferece um passo a passo detalhado: planejamento (identificação de Activities e componentes sem suporte), adição de `enableEdgeToEdge`, aplicação de insets (usando Scaffold, modificadores de padding, ou `WindowInsetsRulers`), tratamento de IME (com padrões corretos e errados para Scaffolds e sem Scaffolds), contraste das barras de navegação e ícones, listas, diálogos em tela cheia, e uma checklist de verificação. Ela é projetada para uso com assistentes de código como Claude Code, Cursor ou Codex, fornecendo exemplos de código Kotlin e configurações de manifesto Android.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lerchenflo/claudeskills/tree/HEAD/android/edge-to-edge

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Pré-requisitos

  • O projeto DEVE usar Android Jetpack Compose.
  • O projeto DEVE ter como alvo SDK 35 ou superior. Se o SDK for inferior a 35, aumente o SDK para 35.

Passo 1: planejar

  1. Localize e analise todas as classes Activity para detectar quais já possuem suporte edge-to-edge. Para cada Activity sem edge-to-edge, planeje tornar cada Activity edge-to-edge.
  2. Em cada Activity, localize e analise todos os componentes de lista e FAB para detectar quais já possuem suporte edge-to-edge. Para cada componente sem suporte edge-to-edge, planeje tornar cada um desses componentes edge-to-edge.
  3. Em cada Activity, verifique a existência de TextField, OutlinedTextField ou BasicTextField. Se encontrado, você DEVE verificar se o IME não esconde o campo de entrada seguindo a seção IME desta skill.

Passo 2: adicionar suporte edge-to-edge

  1. Adicione enableEdgeToEdge antes de setContent em onCreate em cada Activity que ainda não chame enableEdgeToEdge.
  2. Adicione android:windowSoftInputMode="adjustResize" no AndroidManifest.xml para todas as Activities que usam teclado virtual.

Passo 3: aplicar insets

  • O app DEVE aplicar insets do sistema, ou alinhar o conteúdo às réguas, para que a UI crítica permaneça tocável. Escolha apenas um método para evitar padding duplicado:

    1. PREFERENCIAL: Quando disponível, use Scaffold e passe PaddingValues para o lambda de conteúdo.
    Scaffold { innerPadding ->
        // innerPadding considera as barras do sistema e quaisquer componentes do Scaffold
        LazyColumn(
            modifier = Modifier
                .fillMaxSize()
                .consumeWindowInsets(innerPadding),
            contentPadding = innerPadding
        ) { /* Conteúdo */ }
    }
    
    1. PREFERENCIAL: Quando disponível, use o tratamento automático de insets ou modificadores de padding em componentes material.

      • Componentes Material 3 gerenciam áreas seguras para seus próprios componentes, incluindo:
        • TopAppBar
        • SmallTopAppBar
        • CenterAlignedTopAppBar
        • MediumTopAppBar
        • LargeTopAppBar
        • BottomAppBar
        • ModalDrawerSheet
        • DismissibleDrawerSheet
        • PermanentDrawerSheet
        • ModalBottomSheet
        • NavigationBar
        • NavigationRail
      • Para componentes Material 2, use o parâmetro windowInsets para aplicar insets manualmente para BottomAppBar, TopAppBar e BottomNavigation. NÃO aplique padding ao contêiner pai; em vez disso, passe os insets diretamente para o componente App Bar. Aplicar padding ao contêiner pai impede que o fundo da App Bar se estenda para a área das barras do sistema. Por exemplo, para TopAppBar, escolha apenas uma das seguintes opções:
        1. PREFERENCIAL: TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets)
        2. TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))
        3. TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
    2. Para componentes fora de um Scaffold, use modificadores de padding, como Modifier.safeDrawingPadding() ou Modifier.windowInsetsPadding(WindowInsets.safeDrawing).

      Box(
          modifier = Modifier
              .fillMaxSize()
              .safeDrawingPadding()
      ) {
          Button(
              onClick = {},
              modifier = Modifier.align(Alignment.BottomCenter)
          ) {
              Text("Login")
          }
      }
      
    3. Para componentes profundamente aninhados com padding excessivo, use WindowInsetsRulers (ex.: Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current)). Veja a seção IME para um exemplo de código.

    4. Quando você precisar que um elemento (ex.: um cabeçalho personalizado ou uma proteção decorativa) tenha as mesmas dimensões de uma barra do sistema, use modificadores de tamanho de insets (ex.: Modifier.windowInsetsTopHeight(WindowInsets.systemBars)). Veja a seção Listas para um exemplo de código.

Scaffolds Adaptativos

  • NavigationSuiteScaffold gerencia áreas seguras para seus próprios componentes, como NavigationRail ou NavigationBar. No entanto, os scaffolds adaptativos (ex.: NavigationSuiteScaffold, ListDetailPaneScaffold) não propagam PaddingValues para seus conteúdos internos. Você DEVE aplicar insets a telas ou componentes individuais (ex.: contentPadding da lista ou padding do FAB) conforme descrito no Passo 3. NÃO aplique safeDrawingPadding ou modificadores semelhantes ao pai NavigationSuiteScaffold. Isso corta e impede uma tela edge-to-edge.

IME

  • Para cada Activity com teclado virtual, verifique se android:windowSoftInputMode="adjustResize" está definido no AndroidManifest.xml. NÃO use SOFT_INPUT_ADJUST_RESIZE porque está obsoleto. Em seguida, mantenha o foco no campo de entrada. Escolha uma:
      1. PREFERENCIAL: Adicione Modifier.fitInside(WindowInsetsRulers.Ime.current) ao contêiner de conteúdo. Isso é preferível a imePadding() porque reduz travamentos e padding extra causado por esquecer de consumir insets upstream na hierarquia.
      1. Adicione imePadding ao contêiner de conteúdo. O modificador de padding DEVE ser colocado antes de Modifier.verticalScroll(). NÃO use Modifier.imePadding() se o pai já considera o IME com contentWindowInsets (ex.: contentWindowInsets = WindowInsets.safeDrawing). Isso causará padding duplicado.

Padrões de código para IMEs com Scaffolds

CERTO

CERTO porque contentWindowInsets contém insets do IME, que são passados para o lambda de conteúdo como innerPadding.

// CERTO
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .verticalScroll(rememberScrollState())
    ) { /* Conteúdo */ }
}

CERTO porque fitInside ajusta o conteúdo aos insets do IME independentemente de contentWindowInsets.

// CERTO
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .fitInside(WindowInsetsRulers.Ime.current)
            .verticalScroll(rememberScrollState())
    ) { /* Conteúdo */ }
}

CERTO porque o contentWindowInsets padrão não contém insets do IME, e imePadding() aplica insets do IME:

// CERTO
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .consumeWindowInsets(innerPadding)
            .imePadding()
            .verticalScroll(rememberScrollState())
    ) { /* Conteúdo */ }
}

ERRADO

ERRADO porque haverá padding excessivo quando o IME abrir. Os insets do IME são aplicados duas vezes, uma vez com innerPadding, que contém insets do IME dos valores contentWindowInsets passados, e outra vez com imePadding:

// ERRADO
Scaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .imePadding()
            .verticalScroll(rememberScrollState())
    ) { /* Conteúdo */ }
}

ERRADO porque o IME cobrirá o conteúdo. O contentWindowInsets padrão do Scaffold NÃO contém insets do IME.

// ERRADO
Scaffold() { innerPadding ->
    Column(
        modifier = Modifier
            .padding(innerPadding)
            .verticalScroll(rememberScrollState())
    ) { /* Conteúdo */ }
}

Padrões de código para IMEs sem Scaffolds

CERTO

Os seguintes exemplos de código NÃO causarão padding excessivo.

// CERTO
Box(
    // Insets consumidos
    modifier = Modifier.safeDrawingPadding() // ou imePadding(), safeContentPadding(), safeGesturesPadding()
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* Conteúdo */ }
}

// CERTO
Box(
    // Insets consumidos
    modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // ou WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* Conteúdo */ }
}

// CERTO
Box(
    // Insets não consumidos, mas irrelevantes devido a fitInside
    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // ou WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .fitInside(WindowInsetsRulers.Ime.current)
    ) { /* Conteúdo */ }
}

ERRADO

O seguinte exemplo de código CAUSARÁ padding excessivo porque os insets do IME são aplicados duas vezes:

// ERRADO
Box(
    // Insets não consumidos
    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // ou WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()
) {
    Column(
        modifier = Modifier.imePadding()
    ) { /* Conteúdo */ }
}

Contraste da Barra de Navegação e Ícones das Barras do Sistema

  • Se a Activity usar enableEdgeToEdge de WindowCompat, você DEVE definir isAppearanceLightNavigationBars e isAppearanceLightStatusBars como o inverso do tema do dispositivo para apps que suportam tema claro e escuro, para que os ícones das barras do sistema sejam legíveis. Recomenda-se fazer isso no arquivo de tema. NÃO faça isso se as Activities usarem enableEdgeToEdge de ComponentActivity, porque isso gerencia as cores dos ícones automaticamente.

    // Use apenas se estiver chamando `enableEdgeToEdge` de `WindowCompat`.
    // Aplique ao seu arquivo de tema.
    @Composable
    fun MyTheme(
        darkTheme: Boolean = isSystemInDarkTheme(),
        content: @Composable () -> Unit
    ) {
        val view = LocalView.current
        if (!view.isInEditMode) {
            SideEffect {
                val window = (view.context as? Activity)?.window ?: return@SideEffect
                val controller = WindowCompat.getInsetsController(window, view)
    
                // Ícones escuros para Modo Claro (!darkTheme), Ícones claros para Modo Escuro
                controller.isAppearanceLightStatusBars = !darkTheme
                controller.isAppearanceLightNavigationBars = !darkTheme
            }
        }
    
        MaterialTheme(content = content)
    }
    
  • Se alguma tela usar um Scaffold ou NavigationSuiteScaffold com uma barra inferior (ex.: BottomAppBar, NavigationBar), defina window.isNavigationBarContrastEnforced = false na Activity correspondente para SDK 29+. Isso impede que o sistema adicione um fundo translúcido à barra de navegação, garantindo que as cores da sua barra inferior se estendam até a parte inferior da tela.

Listas

  • Aplique o padding de insets (como innerPadding do Scaffold) ao parâmetro contentPadding de componentes roláveis (ex.: LazyColumn, LazyRow). NÃO aplique como Modifier.padding() ao contêiner pai da lista, pois isso corta o conteúdo e impede que ele role atrás das barras do sistema.
  • Crie um composable translúcido cobrindo a barra do sistema para que os ícones ainda sejam legíveis.
class SystemBarProtectionSnippets : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // enableEdgeToEdge define window.isNavigationBarContrastEnforced = true
        // que é usado para adicionar uma proteção translúcida para navegação de três botões
        enableEdgeToEdge()

        setContent {
            MyTheme {
                // Conteúdo principal
                MyContent()

                // Após desenhar o conteúdo principal, desenhe a proteção da barra de status
                StatusBarProtection()
            }
        }
    }
}

@Composable
private fun StatusBarProtection(
    color: Color = MaterialTheme.colorScheme.surfaceContainer,
) {
    Spacer(
        modifier = Modifier
            .fillMaxWidth()
            .height(
                with(LocalDensity.current) {
                    (WindowInsets.statusBars.getTop(this) * 1.2f).toDp()
                }
            )
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        color.copy(alpha = 1f),
                        color.copy(alpha = 0.8f),
                        Color.Transparent
                    )
                )
            )
    )
}

Diálogos

Se ambas as condições a seguir forem verdadeiras, o Diálogo é em tela cheia e deve ser tornado edge-to-edge:

  1. O DialogProperties contém usePlatformDefaultWidth = false.
  2. O Diálogo chama Modifier.fillMaxSize().

Para tornar um Diálogo em tela cheia edge-to-edge, defina decorFitsSystemWindows = false nas DialogProperties.

Dialog(
    onDismissRequest = { /* Lidar com dispensa */ },
    properties = DialogProperties(
        // 1. Permite que o diálogo cubra toda a largura da tela
        usePlatformDefaultWidth = false,
        // 2. Permite que o diálogo desenhe atrás das barras de status e navegação
        decorFitsSystemWindows = false
    )
) { /* Conteúdo */ }

Checklist

  • Toda Activity chama enableEdgeToEdge()?
  • adjustResize está definido no AndroidManifest.xml?
  • Todo TextField, OutlinedTextField ou BasicTextField tem um pai com imePadding(), fitInside, Modifier.safeDrawingPadding(), Modifier.safeContentPadding(), Modifier.safeGesturesPadding(), ou contentWindowInsets definido como WindowInsets.safeDrawing ou WindowInsets.ime?
  • O primeiro e o último item da lista são desenhados longe das barras do sistema passando insets para contentPadding?
  • Os FABs são desenhados acima das barras de navegação, seja estando dentro de um Scaffold ou aplicando Modifier.safeDrawingPadding()?
  • O projeto compila? Execute ./gradlew build para ter certeza.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

lerchenflo/claudeskills

Upgrades, or migrates, an Android project to use Android Gradle Plugin (AGP) version 9. Do not use this skill for migrating Kotlin Multiplatform (KMP) projects.

lerchenflo/claudeskills

Provides instructions for installing and using the `android` CLI. The `android` command-line tool is a critical tool for Android development and helps you create new Android projects, run Android apps on devices, manage and interact with Android virtual devices (including screenshots and UI inspection), manage Android SDK components, look up official Android documentation, and discover and install official Android skills.

lerchenflo/claudeskills

Manages Android performance profiling and debugging. Triggers when the user asks to record or analyze Android performance data, such as system traces, heap dumps, method recordings, callstack samples, memory allocations, or investigate bottlenecks, jank, memory leaks, and app startup issues on Android, or when the user asks to write, debug, or execute ad-hoc SQL queries. Applies to both user and system apps or services.

lerchenflo/claudeskills

Analyzes Android apps to identify key user workflows for AppFunctions such as creating a note, playing media, or sending an automated or AI agent triggered message, voice commands, or system shortcuts, without needing to open the app UI. Generates Kotlin code to expose these workflows to the Android system, allowing agents to discover and execute them on-device. Also refines KDoc documentation to ensure AI agents correctly understand and use the provided functionality.

Habilidades Relacionadas