📚 Pré-requisitos Teóricos: este projeto aplica conceitos ensinados em Especialização em Android Nativo. Recomendado revisar antes de começar.

🏆 P10: Arena Social: O SuperApp Gamer (Firebase Suite)

Seja muito bem-vindo ao GRANDE FINAL da sua jornada de desenvolvimento móvel nativo com Android e Jetpack Compose! 🏆

Neste projeto de encerramento da Fase 5: Desafio Final do Núcleo Essencial, você integrará todo o conhecimento obtido nas fases anteriores (Interface Declarativa, Gerenciamento de Estados, Listas Recicláveis com LazyColumn, Navegação com Scaffold, Diálogos e Interações) e elevará seu aplicativo ao patamar de produção profissional na nuvem através do ecossistema do Google Firebase.

Desenvolveremos o Arena Social, uma rede social móvel completa com tema cyberpunk neon onde treinadores criam contas, compartilham desafios de batalha e acompanham um mural atualizado instantaneamente via Cloud Firestore (Realtime Database) e Firebase Authentication!

Fluxo Completo de Telas do Arena Social


📱 Galeria de Telas da Aplicação

1. Login & Firebase Auth 2. Mural em Tempo Real 3. Publicar Desafio 4. Perfil & Conquistas
Tela 1 - Login Tela 2 - Mural Tela 3 - Novo Desafio Tela 4 - Perfil
Autenticação segura com e-mail e senha. Feed rolável reativo com curtidas dinâmicas. Diálogo com publicação instantânea no Firestore. Estatísticas de vitórias, insígnias e logout.

✅ Pré-requisitos e Continuidade

Antes de iniciar este laboratório de fechamento, certifique-se de ter compreendido:


🎯 Objetivos de Aprendizagem

Ao concluir este projeto autoguiado, você será capaz de:

  1. Configurar o Google Firebase no Android Studio: Registrar o app no Firebase Console, vincular o arquivo mestre google-services.json e declarar o Firebase BoM (Bill of Materials).
  2. Implementar Controle de Acesso Seguro com Firebase Auth: Criar fluxos de cadastro e login com e-mail e senha (signInWithEmailAndPassword / createUserWithEmailAndPassword).
  3. Construir um Banco de Dados NoSQL em Tempo Real com Cloud Firestore: Conectar escutadores de snapshot (addSnapshotListener) para receber atualizações instantâneas sem necessidade de puxar para atualizar (pull-to-refresh).
  4. Construir Feed Social Reativo com Jetpack Compose: Utilizar LazyColumn com chaves únicas (key = { it.id }), cards estilizados com gradientes e botões de curtida com mutação de estado.
  5. Implementar Diálogos Modais (AlertDialog): Capturar entradas de texto de novos posts e salvar coleções no Firestore.
  6. Desenvolver Modo Dual (Sandbox Local vs Firebase Cloud): Executar o app offline com mockups reativos para testes rápidos em sala e plugar a nuvem sem alterar a arquitetura da UI.

🏗️ Arquitetura e Fluxo de Execução

graph TD
    A[Usuário abre Arena Social] --> B{Sessão ativa ou Login realizado?}
    B -- Não --> C[TelaLogin: Inputs de E-mail e Senha]
    C --> D[Usuário clica em Entrar na Arena / Criar Conta]
    D --> E{Firebase Auth ou Sandbox Local?}
    E -- Firebase Real --> F[FirebaseAuth.signInWithEmailAndPassword]
    E -- Modo Sandbox --> G[Login Imediato com Usuário Local]
    F --> H{Autenticação Válida?}
    H -- Sim --> I[Navega para TelaFeed]
    H -- Não --> J[Exibe Toast com mensagem de erro do Firebase]
    G --> I
    
    I --> K[Scaffold: TopAppBar + Feed LazyColumn + NavigationBar]
    K --> L[Firestore addSnapshotListener: Monitoramento Realtime]
    L --> M[Recebe Documentos da Coleção 'mural']
    M --> N[Renderiza Lista de PostCards com Curtidas]
    
    K --> O{Ação na NavigationBar?}
    O -- Tocar em '+' Desafiar --> P[Abre AlertDialog: Novo Desafio]
    O -- Tocar em Perfil --> Q[Navega para TelaPerfil: Conquistas & Logout]
    
    P --> R[Usuário digita texto e clica em PUBLICAR]
    R --> S[Firestore db.collection('mural').add(...) & Atualiza Feed Local]
    S --> L
    
    Q --> T[Exibe Estatísticas: Vitórias, Insígnias, Desafios]
    Q --> U[Botão Desconectar: Reseta Sessão e Retorna para TelaLogin]

📖 Dicionário Técnico do Projeto

Termo / Componente O que é e para que serve?
Google Firebase Plataforma completa de infraestrutura em nuvem do Google que fornece autenticação, bancos de dados em tempo real, analytics e cloud storage para aplicativos.
Firebase BoM Bill of Materials do Firebase. Gerencia automaticamente as versões compatíveis de todas as bibliotecas do ecossistema Firebase sem conflitos de dependências.
Firebase Authentication Motor de controle de acesso que realiza a validação criptografada de usuários, tokens de sessão e gerenciamento de permissões.
Cloud Firestore Banco de dados NoSQL flexível e escalável baseado em documentos (JSON-like) e coleções, com sincronização em tempo real via websockets.
addSnapshotListener Ouvinte reativo do Firestore que dispara um callback sempre que um documento na nuvem for criado, modificado ou excluído.
google-services.json Arquivo de configuração baixado do Firebase Console que contém o project_id, chaves de API e certificados necessários para a comunicação segura com o servidor.
Scaffold Estrutura de layout base do Material 3 que organiza cabeçalho (topBar), barra de navegação (bottomBar) e conteúdo rolável (content).

🛠️ Passo a Passo de Implementação

🚀 Passo 1: Criando o Novo Projeto no Android Studio

  1. Abra o Android Studio e clique em New Project (ou File > New > New Project).
  2. Selecione o template Empty Activity (com o ícone do Jetpack Compose 🌌).
  3. Configure os dados do aplicativo:
    • Name: Arena Social (ou android_p10_social_firebase)
    • Package name: br.com.curso.arenasocial (Atenção: deve ser exatamente este para coincidir com o Firebase!)
    • Save location: Pasta do seu projeto no repositório
    • Language: Kotlin
    • Minimum SDK: API 24 ("Nougat"; Android 7.0) ou superior
    • Build configuration language: Groovy DSL (build.gradle) ou Kotlin DSL (build.gradle.kts)
  4. Clique em Finish e aguarde o Gradle sincronizar.

☁️ Passo 2: Configurando o Projeto no Firebase Console

  1. Acesse o Firebase Console com sua conta Google.
  2. Clique em Adicionar projeto, digite o nome Arena Social Gamer e confirme.
  3. No painel principal do projeto, clique no ícone do Android para registrar o aplicativo:
    • Nome do pacote Android: br.com.curso.arenasocial
    • Apelido do app: Arena Social
  4. Clique em Registrar app e faça o download do arquivo google-services.json.
  5. Instalação no Android Studio:
    • Mude o modo de visualização do painel esquerdo de Android para Project.
    • Cole o arquivo google-services.json diretamente dentro da pasta app/ (ao lado do build.gradle do módulo).

Tela de Login do Arena Social


🔐 Passo 3: Ativando Serviços no Console do Firebase

  1. Ativar o Firebase Authentication:
    • No menu lateral esquerdo do Firebase Console, acesse Build > Authentication.
    • Clique em Vamos começar e selecione a aba Método de login (Sign-in method).
    • Clique em E-mail/senha, ative a opção Habilitar e salve.
  2. Ativar o Cloud Firestore:
    • No menu lateral, acesse Build > Firestore Database.
    • Clique em Criar banco de dados, escolha a região mais próxima (ex: southamerica-east1 em SP) e selecione Iniciar no modo de teste (para liberar permissões públicas durante os testes em sala de aula).
    • Clique em Ativar.

📦 Passo 4: Configurando Dependências no Gradle

  1. No arquivo raiz build.gradle (Project: ...):
    buildscript {
     dependencies {
         classpath 'com.google.gms:google-services:4.4.0'
     }
    }
    
  2. No arquivo app/build.gradle: ```groovy plugins { id ‘com.android.application’ id ‘org.jetbrains.kotlin.android’ id ‘com.google.gms.google-services’ // Plugin do Google Services }

dependencies { implementation platform(‘androidx.compose:compose-bom:2024.02.00’) implementation ‘androidx.compose.ui:ui’ implementation ‘androidx.compose.ui:ui-graphics’ implementation ‘androidx.compose.ui:ui-tooling-preview’ implementation ‘androidx.compose.material3:material3’ implementation ‘androidx.activity:activity-compose:1.8.2’ implementation ‘androidx.core:core-ktx:1.12.0’ implementation ‘androidx.lifecycle:lifecycle-runtime-ktx:2.7.0’

// Firebase BoM e Bibliotecas Oficiais
implementation platform('com.google.firebase:firebase-bom:32.7.2')
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.google.firebase:firebase-firestore-ktx' } ``` *Clique em **Sync Now**.*

🎴 Passo 5: Modelo de Dados do Post (PostData)

Abra app > src > main > java > br > com > curso > arenasocial > MainActivity.kt:

data class PostData(
    val id: String = "",
    val user: String,
    val time: String,
    val content: String,
    var likes: Int = 0,
    var isLiked: Boolean = false
)

🎨 Passo 6: Tema Cyberpunk Gamer (ArenaSocialTheme)

@Composable
fun ArenaSocialTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = darkColorScheme(
            primary = Color(0xFFE91E63),    // Rosa Neon
            secondary = Color(0xFF9C27B0),  // Roxo Elétrico
            tertiary = Color(0xFF00E676),   // Verde Destaque
            background = Color(0xFF0A0A0A), // Preto Profundo
            surface = Color(0xFF161616),
            surfaceVariant = Color(0xFF222222)
        ),
        content = content
    )
}

📱 Passo 7: Tela de Login e Roteamento de Telas (TelaLogin)

A tela de login possui validação visual, suporte a alternar entre “ENTRAR” e “CRIAR CONTA” e opera de maneira resiliente:

@Composable
fun TelaLogin(aoLogar: (String) -> Unit) {
    val context = LocalContext.current
    var email by remember { mutableStateOf("treinador@curso.com") }
    var senha by remember { mutableStateOf("123456") }
    var isCadastro by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.fillMaxSize().padding(28.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = Modifier
                .size(96.dp)
                .clip(RoundedCornerShape(24.dp))
                .background(Brush.linearGradient(listOf(Color(0xFFE91E63), Color(0xFF9C27B0)))),
            contentAlignment = Alignment.Center
        ) {
            Text("⚔️", fontSize = 48.sp)
        }

        Spacer(modifier = Modifier.height(28.dp))
        Text("ARENA SOCIAL", fontSize = 30.sp, fontWeight = FontWeight.Black, color = Color.White, letterSpacing = 2.sp)
        Text(if (isCadastro) "Crie sua conta de Treinador" else "A rede social definitiva para gamers", fontSize = 13.sp, color = Color.Gray)

        Spacer(modifier = Modifier.height(36.dp))

        OutlinedTextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("E-mail do Treinador") },
            modifier = Modifier.fillMaxWidth(),
            shape = RoundedCornerShape(12.dp)
        )

        Spacer(modifier = Modifier.height(14.dp))

        OutlinedTextField(
            value = senha,
            onValueChange = { senha = it },
            label = { Text("Senha") },
            modifier = Modifier.fillMaxWidth(),
            shape = RoundedCornerShape(12.dp)
        )

        Spacer(modifier = Modifier.height(24.dp))

        Button(
            onClick = {
                val nomeTreinador = email.substringBefore("@").replaceFirstChar { it.uppercase() }
                
                // GABARITO: Descomente para usar Firebase Auth Real!
                /*
                val auth = FirebaseAuth.getInstance()
                if (isCadastro) {
                    auth.createUserWithEmailAndPassword(email, senha).addOnCompleteListener { task ->
                        if (task.isSuccessful) aoLogar(nomeTreinador)
                        else Toast.makeText(context, "Erro: ${task.exception?.message}", Toast.LENGTH_LONG).show() } } else { auth.signInWithEmailAndPassword(email, senha).addOnCompleteListener { task -> if (task.isSuccessful) aoLogar(nomeTreinador) else Toast.makeText(context, "Erro:${task.exception?.message}", Toast.LENGTH_LONG).show()
                    }
                }
                */

                // Modo Sandbox (Entrada Imediata para Teste):
                Toast.makeText(context, "Bem-vindo à Arena, $nomeTreinador!", Toast.LENGTH_SHORT).show()
                aoLogar(nomeTreinador)
            },
            modifier = Modifier.fillMaxWidth().height(52.dp),
            shape = RoundedCornerShape(12.dp),
            colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE91E63))
        ) {
            Text(if (isCadastro) "CRIAR CONTA" else "ENTRAR NA ARENA", fontWeight = FontWeight.Black, color = Color.White)
        }

        Spacer(modifier = Modifier.height(16.dp))
        TextButton(onClick = { isCadastro = !isCadastro }) {
            Text(if (isCadastro) "Já possui conta? Faça Login" else "Não tem conta? Cadastre-se", color = Color(0xFF9C27B0))
        }
    }
}

💬 Passo 8: Mural em Tempo Real com Firestore e Curtidas (TelaFeed)

O feed é renderizado através de uma LazyColumn. Quando um usuário clica no coração de um post, o estado local é atualizado reativamente:

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TelaFeed(usuario: String, aoIrParaPerfil: () -> Unit) {
    val postagens = remember {
        mutableStateListOf(
            PostData("1", "Ash Ketchum", "10m", "Desafio qualquer um para uma batalha no Ginásio Central! ⚡", 150, false),
            PostData("2", "Gary Oak", "2h", "Capturei um Blastoise nível 50 sem esforço na rota 12. 😎", 89, false),
            PostData("3", "Misty", "4h", "Alguém em Cerulean para trocar Pokémons de água hoje?", 45, false)
        )
    }

    var mostrarDialogoPost by remember { mutableStateOf(false) }

    // Listener Realtime com Firestore
    LaunchedEffect(Unit) {
        /*
        val db = FirebaseFirestore.getInstance()
        db.collection("mural").addSnapshotListener { snapshot, e ->
            if (e != null || snapshot == null) return@addSnapshotListener
            postagens.clear()
            for (doc in snapshot.documents) {
                val user = doc.getString("usuario") ?: "Treinador"
                val content = doc.getString("mensagem") ?: ""
                val likes = doc.getLong("likes")?.toInt() ?: 0
                postagens.add(PostData(doc.id, user, "Agora", content, likes, false))
            }
        }
        */
    }

    Scaffold(
        containerColor = Color(0xFF0A0A0A),
        topBar = {
            CenterAlignedTopAppBar(
                title = { Text("MURAL DE BATALHAS", fontWeight = FontWeight.Black, letterSpacing = 2.sp) },
                colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color(0xFF161616), titleContentColor = Color.White)
            )
        },
        bottomBar = {
            NavigationBar(containerColor = Color(0xFF161616)) {
                NavigationBarItem(selected = true, onClick = {}, icon = { Icon(Icons.Default.Home, null) }, label = { Text("Mural") })
                NavigationBarItem(selected = false, onClick = { mostrarDialogoPost = true }, icon = { Icon(Icons.Default.Add, null) }, label = { Text("Desafiar") })
                NavigationBarItem(selected = false, onClick = aoIrParaPerfil, icon = { Icon(Icons.Default.Person, null) }, label = { Text("Perfil") })
            }
        }
    ) { padding ->
        LazyColumn(modifier = Modifier.padding(padding).fillMaxSize()) {
            items(postagens, key = { it.id }) { post ->
                PostCard(
                    post = post,
                    onToggleLike = {
                        val index = postagens.indexOf(post)
                        if (index != -1) {
                            val liked = !post.isLiked
                            val newLikes = if (liked) post.likes + 1 else (post.likes - 1).coerceAtLeast(0)
                            postagens[index] = post.copy(likes = newLikes, isLiked = liked)
                        }
                    }
                )
            }
        }
    }
}

Mural em Tempo Real


📝 Passo 9: Diálogo de Criação de Post (AlertDialog)

Ao tocar no botão de + Desafiar na barra de navegação, o aplicativo exibe uma janela modal para digitar a mensagem e enviá-la para o banco de dados na nuvem:

if (mostrarDialogoPost) {
    AlertDialog(
        onDismissRequest = { mostrarDialogoPost = false },
        containerColor = Color(0xFF1E1E1E),
        title = { Text("⚔️ Novo Desafio no Mural", fontWeight = FontWeight.Bold, color = Color.White) },
        text = {
            Column {
                Text("Publique um desafio ou conquista para toda a comunidade gamer!", fontSize = 13.sp, color = Color.Gray)
                Spacer(modifier = Modifier.height(12.dp))
                OutlinedTextField(
                    value = novoPostTexto,
                    onValueChange = { novoPostTexto = it },
                    placeholder = { Text("Ex: Procuro oponente nível 40!") },
                    modifier = Modifier.fillMaxWidth()
                )
            }
        },
        confirmButton = {
            Button(
                onClick = {
                    if (novoPostTexto.isNotBlank()) {
                        postagens.add(0, PostData(System.currentTimeMillis().toString(), usuario, "Agora", novoPostTexto.trim(), 0, false))
                        
                        // GABARITO: Gravação no Firestore Real
                        /*
                        val db = FirebaseFirestore.getInstance()
                        db.collection("mural").add(mapOf("usuario" to usuario, "mensagem" to novoPostTexto.trim(), "likes" to 0, "criadoEm" to System.currentTimeMillis()))
                        */

                        novoPostTexto = ""
                        mostrarDialogoPost = false
                    }
                },
                colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE91E63))
            ) {
                Text("PUBLICAR", fontWeight = FontWeight.Bold, color = Color.White)
            }
        },
        dismissButton = {
            TextButton(onClick = { mostrarDialogoPost = false }) { Text("Cancelar", color = Color.Gray) }
        }
    )
}

Diálogo de Criação de Post


👤 Passo 10: Tela de Perfil do Treinador e Logout (TelaPerfil)

Exibe as estatísticas do jogador logado, suas conquistas e permite desconectar da sessão:

@Composable
fun TelaPerfil(usuario: String, aoVoltarMural: () -> Unit, aoDesconectar: () -> Unit) {
    Column(
        modifier = Modifier.fillMaxSize().padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Spacer(modifier = Modifier.height(20.dp))
        Box(
            modifier = Modifier.size(100.dp).clip(CircleShape).background(Brush.linearGradient(listOf(Color(0xFFE91E63), Color(0xFF9C27B0)))),
            contentAlignment = Alignment.Center
        ) {
            Text(usuario.firstOrNull()?.uppercase() ?: "T", fontSize = 44.sp, fontWeight = FontWeight.Black, color = Color.White)
        }

        Spacer(modifier = Modifier.height(16.dp))
        Text(usuario, fontSize = 24.sp, fontWeight = FontWeight.Black, color = Color.White)
        Text("Treinador Oficial da Arena", fontSize = 13.sp, color = Color(0xFF00E676))

        Spacer(modifier = Modifier.height(28.dp))

        Card(modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = Color(0xFF161616)), shape = RoundedCornerShape(16.dp)) {
            Row(modifier = Modifier.fillMaxWidth().padding(20.dp), horizontalArrangement = Arrangement.SpaceAround) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("34", fontWeight = FontWeight.Black, fontSize = 20.sp, color = Color(0xFFE91E63))
                    Text("Vitórias", fontSize = 12.sp, color = Color.Gray)
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("8", fontWeight = FontWeight.Black, fontSize = 20.sp, color = Color(0xFF9C27B0))
                    Text("Insígnias", fontSize = 12.sp, color = Color.Gray)
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("142", fontWeight = FontWeight.Black, fontSize = 20.sp, color = Color(0xFF00E676))
                    Text("Desafios", fontSize = 12.sp, color = Color.Gray)
                }
            }
        }

        Spacer(modifier = Modifier.weight(1f))

        Button(onClick = aoVoltarMural, modifier = Modifier.fillMaxWidth().height(50.dp), colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1E1E1E)), shape = RoundedCornerShape(12.dp)) {
            Text("VOLTAR AO MURAL", color = Color.White, fontWeight = FontWeight.Bold)
        }

        Spacer(modifier = Modifier.height(12.dp))

        OutlinedButton(onClick = aoDesconectar, modifier = Modifier.fillMaxWidth().height(50.dp), shape = RoundedCornerShape(12.dp), colors = ButtonDefaults.outlinedButtonColors(contentColor = Color(0xFFFF1744))) {
            Text("DESCONECTAR (LOGOUT)", fontWeight = FontWeight.Bold)
        }
    }
}

Tela de Perfil e Conquistas


🔍 Guia de Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
File google-services.json is missing O arquivo de configuração do Firebase não foi colado na pasta correta. Mude a visualização do Android Studio para Project, abra a pasta do projeto e cole o google-services.json diretamente dentro da subpasta app/.
PERMISSION_DENIED: Missing or insufficient permissions As regras do Cloud Firestore estão bloqueando leituras ou gravações. No Firebase Console, vá em Firestore Database > Regras (Rules) e altere para allow read, write: if true; para testes em laboratório.
The email address is badly formatted O e-mail digitado no campo de login não segue o formato padrão (nome@dominio.com). Certifique-se de preencher um e-mail válido com @ e domínio.
The password must be 6 characters long or more O Firebase Auth exige senhas com no mínimo 6 caracteres. Digite uma senha com pelo menos 6 dígitos.
O aplicativo roda perfeitamente sem o Firebase configurado? O app foi projetado com Modo Sandbox Local com mocks reativos. Sim! Você pode testar todas as transições de tela, likes e publicações de posts no emulador mesmo antes de criar o projeto no Firebase Console.

🏆 Desafios e Upgrades (Mão na Massa!)

  1. 🔥 Persistência de Curtidas no Firestore: Utilize FieldValue.increment(1) na coleção mural do Firestore para sincronizar o contador de curtidas com todos os celulares conectados ao mesmo tempo!
  2. 📸 Upload de Fotos com Firebase Storage: Adicione suporte a anexar uma captura de tela da vitória ou foto do Pokémon no post através do Firebase Cloud Storage.
  3. 🔔 Notificações Push com Firebase Cloud Messaging (FCM): Envie uma notificação para todos os treinadores quando um novo Líder de Ginásio for desafiado.
  4. 🔍 Filtro de Desafios por Cidade: Adicione uma barra de pesquisa para filtrar posts de treinadores da sua região.

📖 Gabarito Oficial de Código (Para Conferência)

Disponível em: app/src/main/java/br/com/curso/arenasocial/MainActivity.kt

package br.com.curso.arenasocial

import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore

data class PostData(
    val id: String = "",
    val user: String,
    val time: String,
    val content: String,
    var likes: Int = 0,
    var isLiked: Boolean = false
)

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ArenaSocialTheme {
                ArenaSocialApp()
            }
        }
    }
}

@Composable
fun ArenaSocialTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = darkColorScheme(
            primary = Color(0xFFE91E63),
            secondary = Color(0xFF9C27B0),
            tertiary = Color(0xFF00E676),
            background = Color(0xFF0A0A0A),
            surface = Color(0xFF161616),
            surfaceVariant = Color(0xFF222222)
        ),
        content = content
    )
}

@Composable
fun ArenaSocialApp() {
    var telaAtual by remember { mutableStateOf("login") }
    var usuarioLogado by remember { mutableStateOf("Treinador(a)") }

    Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
        when (telaAtual) {
            "login" -> TelaLogin(
                aoLogar = { nome ->
                    usuarioLogado = nome
                    telaAtual = "feed"
                }
            )
            "feed" -> TelaFeed(
                usuario = usuarioLogado,
                aoIrParaPerfil = { telaAtual = "perfil" }
            )
            "perfil" -> TelaPerfil(
                usuario = usuarioLogado,
                aoVoltarMural = { telaAtual = "feed" },
                aoDesconectar = {
                    usuarioLogado = "Treinador(a)"
                    telaAtual = "login"
                }
            )
        }
    }
}

@Composable
fun TelaLogin(aoLogar: (String) -> Unit) {
    val context = LocalContext.current
    var email by remember { mutableStateOf("treinador@curso.com") }
    var senha by remember { mutableStateOf("123456") }
    var isCadastro by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(28.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Box(
            modifier = Modifier
                .size(96.dp)
                .clip(RoundedCornerShape(24.dp))
                .background(Brush.linearGradient(listOf(Color(0xFFE91E63), Color(0xFF9C27B0)))),
            contentAlignment = Alignment.Center
        ) {
            Text("⚔️", fontSize = 48.sp)
        }

        Spacer(modifier = Modifier.height(28.dp))

        Text(
            text = "ARENA SOCIAL",
            fontSize = 30.sp,
            fontWeight = FontWeight.Black,
            color = Color.White,
            letterSpacing = 2.sp
        )
        Text(
            text = if (isCadastro) "Crie sua conta de Treinador" else "A rede social definitiva para gamers",
            fontSize = 13.sp,
            color = Color.Gray
        )

        Spacer(modifier = Modifier.height(36.dp))

        OutlinedTextField(
            value = email,
            onValueChange = { email = it },
            label = { Text("E-mail do Treinador") },
            modifier = Modifier.fillMaxWidth(),
            shape = RoundedCornerShape(12.dp),
            colors = OutlinedTextFieldDefaults.colors(
                focusedBorderColor = Color(0xFFE91E63),
                focusedLabelColor = Color(0xFFE91E63),
                unfocusedTextColor = Color.White,
                focusedTextColor = Color.White
            )
        )

        Spacer(modifier = Modifier.height(14.dp))

        OutlinedTextField(
            value = senha,
            onValueChange = { senha = it },
            label = { Text("Senha") },
            modifier = Modifier.fillMaxWidth(),
            shape = RoundedCornerShape(12.dp),
            colors = OutlinedTextFieldDefaults.colors(
                focusedBorderColor = Color(0xFFE91E63),
                focusedLabelColor = Color(0xFFE91E63),
                unfocusedTextColor = Color.White,
                focusedTextColor = Color.White
            )
        )

        Spacer(modifier = Modifier.height(24.dp))

        Button(
            onClick = {
                val nomeTreinador = email.substringBefore("@").replaceFirstChar { it.uppercase() }
                Toast.makeText(context, "Bem-vindo à Arena, $nomeTreinador!", Toast.LENGTH_SHORT).show()
                aoLogar(nomeTreinador)
            },
            modifier = Modifier
                .fillMaxWidth()
                .height(52.dp),
            shape = RoundedCornerShape(12.dp),
            colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE91E63))
        ) {
            Text(
                text = if (isCadastro) "CRIAR CONTA" else "ENTRAR NA ARENA",
                fontWeight = FontWeight.Black,
                fontSize = 15.sp,
                color = Color.White
            )
        }

        Spacer(modifier = Modifier.height(16.dp))

        TextButton(onClick = { isCadastro = !isCadastro }) {
            Text(
                text = if (isCadastro) "Já possui conta? Faça Login" else "Não tem conta? Cadastre-se",
                color = Color(0xFF9C27B0),
                fontWeight = FontWeight.Bold,
                fontSize = 13.sp
            )
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TelaFeed(
    usuario: String,
    aoIrParaPerfil: () -> Unit
) {
    val postagens = remember {
        mutableStateListOf(
            PostData("1", "Ash Ketchum", "10m", "Desafio qualquer um para uma batalha no Ginásio Central! ⚡", 150, false),
            PostData("2", "Gary Oak", "2h", "Capturei um Blastoise nível 50 sem esforço na rota 12. 😎", 89, false),
            PostData("3", "Misty", "4h", "Alguém em Cerulean para trocar Pokémons de água hoje?", 45, false)
        )
    }

    var mostrarDialogoPost by remember { mutableStateOf(false) }
    var novoPostTexto by remember { mutableStateOf("") }

    Scaffold(
        containerColor = Color(0xFF0A0A0A),
        topBar = {
            CenterAlignedTopAppBar(
                title = {
                    Text(
                        "MURAL DE BATALHAS",
                        fontWeight = FontWeight.Black,
                        letterSpacing = 2.sp,
                        fontSize = 18.sp
                    )
                },
                colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                    containerColor = Color(0xFF161616),
                    titleContentColor = Color.White
                )
            )
        },
        bottomBar = {
            NavigationBar(containerColor = Color(0xFF161616)) {
                NavigationBarItem(
                    selected = true,
                    onClick = {},
                    icon = { Icon(Icons.Default.Home, null) },
                    label = { Text("Mural") },
                    colors = NavigationBarItemDefaults.colors(
                        selectedIconColor = Color(0xFFE91E63),
                        selectedTextColor = Color(0xFFE91E63),
                        indicatorColor = Color(0xFFE91E63).copy(alpha = 0.2f)
                    )
                )
                NavigationBarItem(
                    selected = false,
                    onClick = { mostrarDialogoPost = true },
                    icon = { Icon(Icons.Default.Add, null) },
                    label = { Text("Desafiar") }
                )
                NavigationBarItem(
                    selected = false,
                    onClick = aoIrParaPerfil,
                    icon = { Icon(Icons.Default.Person, null) },
                    label = { Text("Perfil") }
                )
            }
        }
    ) { padding ->
        LazyColumn(
            modifier = Modifier
                .padding(padding)
                .fillMaxSize()
        ) {
            items(postagens, key = { it.id }) { post ->
                PostCard(
                    post = post,
                    onToggleLike = {
                        val index = postagens.indexOf(post)
                        if (index != -1) {
                            val liked = !post.isLiked
                            val newLikes = if (liked) post.likes + 1 else (post.likes - 1).coerceAtLeast(0)
                            postagens[index] = post.copy(likes = newLikes, isLiked = liked)
                        }
                    }
                )
            }
        }
    }

    if (mostrarDialogoPost) {
        AlertDialog(
            onDismissRequest = { mostrarDialogoPost = false },
            containerColor = Color(0xFF1E1E1E),
            titleContentColor = Color.White,
            textContentColor = Color.White,
            title = {
                Text(
                    text = "⚔️ Novo Desafio no Mural",
                    fontWeight = FontWeight.Bold,
                    fontSize = 18.sp
                )
            },
            text = {
                Column {
                    Text(
                        text = "Publique um desafio ou conquista para toda a comunidade gamer!",
                        fontSize = 13.sp,
                        color = Color.Gray
                    )
                    Spacer(modifier = Modifier.height(12.dp))
                    OutlinedTextField(
                        value = novoPostTexto,
                        onValueChange = { novoPostTexto = it },
                        placeholder = { Text("Ex: Procuro oponente nível 40!") },
                        modifier = Modifier.fillMaxWidth(),
                        colors = OutlinedTextFieldDefaults.colors(
                            focusedBorderColor = Color(0xFFE91E63),
                            unfocusedTextColor = Color.White,
                            focusedTextColor = Color.White
                        )
                    )
                }
            },
            confirmButton = {
                Button(
                    onClick = {
                        if (novoPostTexto.isNotBlank()) {
                            val newId = System.currentTimeMillis().toString()
                            postagens.add(
                                0,
                                PostData(newId, usuario, "Agora", novoPostTexto.trim(), 0, false)
                            )
                            novoPostTexto = ""
                            mostrarDialogoPost = false
                        }
                    },
                    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFE91E63))
                ) {
                    Text("PUBLICAR", fontWeight = FontWeight.Bold, color = Color.White)
                }
            },
            dismissButton = {
                TextButton(onClick = { mostrarDialogoPost = false }) {
                    Text("Cancelar", color = Color.Gray)
                }
            }
        )
    }
}

@Composable
fun PostCard(
    post: PostData,
    onToggleLike: () -> Unit
) {
    Card(
        modifier = Modifier
            .padding(horizontal = 16.dp, vertical = 8.dp)
            .fillMaxWidth(),
        colors = CardDefaults.cardColors(containerColor = Color(0xFF161616)),
        shape = RoundedCornerShape(18.dp)
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Row(verticalAlignment = Alignment.CenterVertically) {
                Box(
                    modifier = Modifier
                        .size(42.dp)
                        .clip(CircleShape)
                        .background(
                            Brush.linearGradient(listOf(Color(0xFFE91E63), Color(0xFF9C27B0)))
                        ),
                    contentAlignment = Alignment.Center
                ) {
                    Text(
                        text = post.user.firstOrNull()?.uppercase() ?: "T",
                        fontWeight = FontWeight.Black,
                        color = Color.White,
                        fontSize = 16.sp
                    )
                }
                Spacer(modifier = Modifier.width(12.dp))
                Column {
                    Text(post.user, fontWeight = FontWeight.Bold, color = Color.White, fontSize = 15.sp)
                    Text(post.time, fontSize = 11.sp, color = Color.Gray)
                }
            }

            Spacer(modifier = Modifier.height(12.dp))

            Text(
                text = post.content,
                color = Color.White.copy(alpha = 0.95f),
                fontSize = 15.sp,
                lineHeight = 22.sp
            )

            Spacer(modifier = Modifier.height(14.dp))

            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier
                    .clip(RoundedCornerShape(8.dp))
                    .clickable { onToggleLike() }
                    .padding(vertical = 4.dp, horizontal = 6.dp)
            ) {
                Icon(
                    imageVector = if (post.isLiked) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder,
                    contentDescription = "Curtir",
                    tint = if (post.isLiked) Color(0xFFE91E63) else Color.Gray,
                    modifier = Modifier.size(20.dp)
                )
                Spacer(modifier = Modifier.width(6.dp))
                Text(
                    text = "${post.likes} curtidas",
                    color = if (post.isLiked) Color(0xFFE91E63) else Color.Gray,
                    fontSize = 13.sp,
                    fontWeight = if (post.isLiked) FontWeight.Bold else FontWeight.Normal
                )
            }
        }
    }
}

@Composable
fun TelaPerfil(
    usuario: String,
    aoVoltarMural: () -> Unit,
    aoDesconectar: () -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Spacer(modifier = Modifier.height(20.dp))

        Box(
            modifier = Modifier
                .size(100.dp)
                .clip(CircleShape)
                .background(Brush.linearGradient(listOf(Color(0xFFE91E63), Color(0xFF9C27B0)))),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = usuario.firstOrNull()?.uppercase() ?: "T",
                fontSize = 44.sp,
                fontWeight = FontWeight.Black,
                color = Color.White
            )
        }

        Spacer(modifier = Modifier.height(16.dp))

        Text(usuario, fontSize = 24.sp, fontWeight = FontWeight.Black, color = Color.White)
        Text("Treinador Oficial da Arena", fontSize = 13.sp, color = Color(0xFF00E676))

        Spacer(modifier = Modifier.height(28.dp))

        Card(
            modifier = Modifier.fillMaxWidth(),
            colors = CardDefaults.cardColors(containerColor = Color(0xFF161616)),
            shape = RoundedCornerShape(16.dp)
        ) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(20.dp),
                horizontalArrangement = Arrangement.SpaceAround
            ) {
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("34", fontWeight = FontWeight.Black, fontSize = 20.sp, color = Color(0xFFE91E63))
                    Text("Vitórias", fontSize = 12.sp, color = Color.Gray)
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("8", fontWeight = FontWeight.Black, fontSize = 20.sp, color = Color(0xFF9C27B0))
                    Text("Insígnias", fontSize = 12.sp, color = Color.Gray)
                }
                Column(horizontalAlignment = Alignment.CenterHorizontally) {
                    Text("142", fontWeight = FontWeight.Black, fontSize = 20.sp, color = Color(0xFF00E676))
                    Text("Desafios", fontSize = 12.sp, color = Color.Gray)
                }
            }
        }

        Spacer(modifier = Modifier.weight(1f))

        Button(
            onClick = aoVoltarMural,
            modifier = Modifier.fillMaxWidth().height(50.dp),
            colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF1E1E1E)),
            shape = RoundedCornerShape(12.dp)
        ) {
            Text("VOLTAR AO MURAL", color = Color.White, fontWeight = FontWeight.Bold)
        }

        Spacer(modifier = Modifier.height(12.dp))

        OutlinedButton(
            onClick = aoDesconectar,
            modifier = Modifier.fillMaxWidth().height(50.dp),
            shape = RoundedCornerShape(12.dp),
            colors = ButtonDefaults.outlinedButtonColors(contentColor = Color(0xFFFF1744))
        ) {
            Text("DESCONECTAR (LOGOUT)", fontWeight = FontWeight.Bold)
        }

        Spacer(modifier = Modifier.height(16.dp))
    }
}

🚀 Como Executar no Laboratório

1. Abra o terminal na pasta deste projeto

No seu editor/IDE, abra a pasta deste projeto (File > Open Folder) ou navegue via terminal:

cd proj_aplicacoes_full_stack/projetos/android_p10_social_firebase

2. Execute a aplicação e os testes

./gradlew build
./gradlew test
# ou abrir no Android Studio e clicar em Run (Shift+F10)

[!TIP] Dica para execução a partir da raiz do repositório: Se você abriu o repositório completo no VS Code ou Android Studio, abra o projeto diretamente pela pasta proj_aplicacoes_full_stack/projetos/android_p10_social_firebase.