Saltar al contenido
Portada » Códigos

Códigos

Tarjeta de Perfil con Botón de Seguir

import 'package:flutter/material.dart';

void main() {
  runApp(const MiAplicacionWeb());
}

// 1. EL CONTENEDOR PRINCIPAL DE LA APP
// Requerido en DartPad para inicializar el diseño Material Design y el lienzo (Scaffold)
class MiAplicacionWeb extends StatelessWidget {
  const MiAplicacionWeb({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false, // Quita la banda roja de "Debug"
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
      ),
      home: Scaffold(
        backgroundColor: Colors.grey[100], // Fondo gris claro para que resalte la tarjeta
        appBar: AppBar(
          title: const Text('Vista previa en DartPad'),
          backgroundColor: Colors.blue,
          foregroundColor: Colors.white,
        ),
        body: const Center(
          // Centramos la tarjeta en la pantalla web
          child: TarjetaPerfil(),
        ),
      ),
    );
  }
}

// 2. LA ESTRUCTURA DE LA TARJETA (StatelessWidget)
// Distribuye el espacio, los márgenes, bordes redondeados y la sombra
class TarjetaPerfil extends StatelessWidget {
  const TarjetaPerfil({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 350, // Limitamos el ancho para que en pantallas de PC no se estire demasiado
      padding: const EdgeInsets.all(16.0),
      margin: const EdgeInsets.all(20.0),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(15.0),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            spreadRadius: 2,
            blurRadius: 8,
            offset: const Offset(0, 4), 
          ),
        ],
      ),
      child: const Column(
        mainAxisSize: MainAxisSize.min, // Se ajusta estrictamente al alto del contenido
        children: [
          // Fila superior: Avatar + Textos
          Row(
            children: [
              CircleAvatar(
                radius: 30,
                backgroundColor: Colors.blueAccent,
                child: Icon(Icons.person, size: 35, color: Colors.white),
              ),
              SizedBox(width: 15), 
              Column(
                crossAxisAlignment: CrossAxisAlignment.start, 
                children: [
                  Text(
                    'Alejandro Magueyal',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  Text(
                    'Desarrollador Flutter',
                    style: TextStyle(fontSize: 14, color: Colors.grey),
                  ),
                ],
              ),
            ],
          ),
          
          Divider(height: 30, thickness: 1), // Línea separadora
          
          // Componente inferior: Botón Interactivo
          BotonSeguir(), 
        ],
      ),
    );
  }
}

// 3. EL COMPONENTE INTERACTIVO (StatefulWidget)
// Controla el estado del botón (Seguir / Siguiendo) y se redibuja de forma independiente
class BotonSeguir extends StatefulWidget {
  const BotonSeguir({super.key});

  @override
  State<BotonSeguir> createState() => _BotonSeguirState();
}

class _BotonSeguirState extends State<BotonSeguir> {
  bool _siguiendo = false; // Variable de estado

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      style: ElevatedButton.styleFrom(
        backgroundColor: _siguiendo ? Colors.grey[300] : Colors.blue,
        foregroundColor: _siguiendo ? Colors.black87 : Colors.white,
        minimumSize: const Size(double.infinity, 45), // Ancho completo dentro de la tarjeta
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10), // Bordes ligeramente redondeados
        ),
      ),
      onPressed: () {
        setState(() {
          _siguiendo = !_siguiendo; // Cambia el estado y avisa a Flutter que redibuje
        });
      },
      child: Text(
        _siguiendo ? 'Siguiendo' : 'Seguir',
        style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
      ),
    );
  }
}

2do Ejemplo

import 'package:flutter/material.dart';

void main() {
  runApp(const MiSegundoEjercicio());
}

class MiSegundoEjercicio extends StatelessWidget {
  const MiSegundoEjercicio({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      home: Scaffold(
        backgroundColor: Colors.grey[200],
        appBar: AppBar(
          title: const Text('Ejercicio 2: Layouts e Iconos'),
          backgroundColor: Colors.indigo,
          foregroundColor: Colors.white,
        ),
        body: const Center(
          child: TarjetaMetricas(),
        ),
      ),
    );
  }
}

// 1. LA TARJETA PRINCIPAL (Organiza secciones verticalmente)
class TarjetaMetricas extends StatelessWidget {
  const TarjetaMetricas({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 360,
      padding: const EdgeInsets.all(20.0),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(20.0),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.08),
            blurRadius: 10,
            offset: const Offset(0, 5),
          ),
        ],
      ),
      child: const Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            'Estadísticas de Cuenta',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.indigo),
          ),
          SizedBox(height: 20),
          
          // SECCIÓN 1: Fila de Métricas (Uso de Row distribuido)
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Distribuye el espacio equitativamente
            children: [
              ColumnaMetrica(icono: Icons.folder_open, valor: '12', etiqueta: 'Proyectos'),
              ColumnaMetrica(icono: Icons.star_border, valor: '4.9', etiqueta: 'Rating'),
              ColumnaMetrica(icono: Icons.remove_red_eye_outlined, valor: '1.2k', etiqueta: 'Vistas'),
            ],
          ),
          
          Divider(height: 40, thickness: 1),
          
          // SECCIÓN 2: Ajustes interactivos (Uso de StatefulWidgets)
          ElementoAjusteInformativo(),
        ],
      ),
    );
  }
}

// 2. WIDGET REUTILIZABLE: Para no repetir código en la fila de métricas
class ColumnaMetrica extends StatelessWidget {
  final IconData icono;
  final String valor;
  final String etiqueta;

  const ColumnaMetrica({
    super.key,
    required this.icono,
    required this.valor,
    required this.etiqueta,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Icon(icono, color: Colors.indigo, size: 28),
        const SizedBox(height: 6),
        Text(valor, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
        Text(etiqueta, style: const TextStyle(fontSize: 12, color: Colors.grey)),
      ],
    );
  }
}

// 3. SECCIÓN INTERACTIVA: Un interruptor para activar/desactivar notificaciones
class ElementoAjusteInformativo extends StatefulWidget {
  const ElementoAjusteInformativo({super.key});

  @override
  State<ElementoAjusteInformativo> createState() => _ElementoAjusteInformativoState();
}

class _ElementoAjusteInformativoState extends State<ElementoAjusteInformativo> {
  bool _notificacionesActivas = true; // Estado local del interruptor

  @override
  Widget build(BuildContext context) {
    // ListTile es un widget excelente que ya viene con la estructura: Icono izquierdo + Título + Componente derecho
    return ListTile(
      contentPadding: EdgeInsets.zero, // Quita los márgenes internos por defecto
      leading: Icon(
        _notificacionesActivas ? Icons.notifications_active : Icons.notifications_off,
        color: _notificacionesActivas ? Colors.green : Colors.grey,
      ),
      title: const Text('Notificaciones push'),
      subtitle: Text(_notificacionesActivas ? 'Activadas' : 'Silenciadas'),
      trailing: Switch(
        value: _notificacionesActivas,
        onChanged: (nuevoValor) {
          setState(() {
            _notificacionesActivas = nuevoValor; // Actualiza la pantalla al presionar el switch
          });
        },
      ),
    );
  }
}

Formulario

import 'package:flutter/material.dart';

void main() {
  runApp(const MiTercerEjercicio());
}

class MiTercerEjercicio extends StatelessWidget {
  const MiTercerEjercicio({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true, 
        colorSchemeSeed: Colors.teal,
      ),
      home: Scaffold(
        backgroundColor: Colors.teal[50],
        appBar: AppBar(
          title: const Text('Ejercicio 3: Campos de Texto'),
          backgroundColor: Colors.teal,
          foregroundColor: Colors.white,
        ),
        body: const Center(
          child: FormularioLogin(),
        ),
      ),
    );
  }
}

// EL FORMULARIO INTERACTIVO (Debe ser StatefulWidget para capturar el texto y controlar la visibilidad)
class FormularioLogin extends StatefulWidget {
  const FormularioLogin({super.key});

  @override
  State<FormularioLogin> createState() => _FormularioLoginState();
}

class _FormularioLoginState extends State<FormularioLogin> {
  // 1. CONTROLADORES: Son los encargados de "escuchar" y extraer el texto de los inputs
  final TextEditingController _controladorEmail = TextEditingController();
  final TextEditingController _controladorPassword = TextEditingController();

  // Variable de estado para ocultar/mostrar la contraseña (ojo de la contraseña)
  bool _ocultarPassword = true;

  // Liberamos los controladores de la memoria cuando el widget se destruye (Buena práctica)
  @override
  void dispose() {
    _controladorEmail.dispose();
    _controladorPassword.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 340,
      padding: const EdgeInsets.all(24.0),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(20.0),
        boxShadow: [
          BoxShadow(
            color: Colors.teal.withOpacity(0.15),
            blurRadius: 15,
            offset: const Offset(0, 8),
          ),
        ],
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.stretch, // Hace que los botones y campos ocupen todo el ancho
        children: [
          const Text(
            'Crear Cuenta',
            textAlign: TextAlign.center,
            style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold, color: Colors.teal),
          ),
          const SizedBox(height: 25),

          // CAMPO 1: CORREO ELECTRÓNICO
          TextField(
            controller: _controladorEmail,
            keyboardType: TextInputType.emailAddress, // Optimiza el teclado móvil con el símbolo '@'
            decoration: InputDecoration(
              labelText: 'Correo Electrónico',
              hintText: 'ejemplo@correo.com',
              prefixIcon: const Icon(Icons.email_outlined),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(12),
              ),
            ),
          ),
          const SizedBox(height: 20),

          // CAMPO 2: CONTRASEÑA
          TextField(
            controller: _controladorPassword,
            obscureText: _ocultarPassword, // Si es true, transforma el texto en puntitos (••••)
            decoration: InputDecoration(
              labelText: 'Contraseña',
              prefixIcon: const Icon(Icons.lock_outline),
              // Ponemos un botón interactivo dentro del mismo input para mostrar/ocultar texto
              suffixIcon: IconButton(
                icon: Icon(_ocultarPassword ? Icons.visibility_off : Icons.visibility),
                onPressed: () {
                  setState(() {
                    _ocultarPassword = !_ocultarPassword; // Invierte el estado físico del ojo
                  });
                },
              ),
              border: OutlineInputBorder(
                borderRadius: BorderRadius.circular(12),
              ),
            ),
          ),
          const SizedBox(height: 30),

          // BOTÓN DE ACCIÓN
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              backgroundColor: Colors.teal,
              foregroundColor: Colors.white,
              padding: const EdgeInsets.symmetric(vertical: 14),
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
            ),
            onPressed: () {
              // Obtenemos el texto actual directamente desde los controladores
              String emailInput = _controladorEmail.text;
              String passwordInput = _controladorPassword.text;

              // Mostramos un mensaje flotante (SnackBar) con los datos capturados
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Registrando a: $emailInput (Pass: $passwordInput)'),
                  backgroundColor: Colors.teal[700],
                ),
              );
            },
            child: const Text('Registrarse', style: TextStyle(fontSize: 16)),
          ),
        ],
      ),
    );
  }
}