Skip to content

Instantly share code, notes, and snippets.

@tiagolpadua
Last active February 8, 2026 19:13
Show Gist options
  • Select an option

  • Save tiagolpadua/71ad9e78a54e86de2f964d8fee081ff4 to your computer and use it in GitHub Desktop.

Select an option

Save tiagolpadua/71ad9e78a54e86de2f964d8fee081ff4 to your computer and use it in GitHub Desktop.
Flutter Cheatsheet - Banco Douro App

Flutter Cheatsheet - APIs e Autenticação

Consumo de APIs REST com Autenticação

Objetivo: Referência rápida para acompanhamento da aula e consulta posterior.


Índice

  1. PUT - Atualizar Recursos
  2. DELETE - Remover Recursos
  3. Dialogs de Confirmação
  4. Autenticação com Login e Senha
  5. Token de Autenticação (JWT)
  6. Tratamento de Erros de APIs
  7. Recursos e Documentação

PUT - Atualizar Recursos

Requisição PUT Básica

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<bool> updateAccount(Account account) async {
  final response = await http.put(
    Uri.parse('http://localhost:3000/accounts/${account.id}'),
    headers: {'Content-Type': 'application/json'},
    body: json.encode(account.toMap()),
  );

  return response.statusCode == 200;
}

Documentação: http package

PUT com Autenticação

Future<bool> updateAccount(Account account, String token) async {
  final response = await http.put(
    Uri.parse('$baseUrl/accounts/${account.id}'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer $token',
    },
    body: json.encode(account.toMap()),
  );

  if (response.statusCode != 200) {
    throw Exception('Falha ao atualizar: ${response.body}');
  }

  return true;
}

Diferença PUT vs PATCH

Método Uso Corpo da Requisição
PUT Substituição completa Objeto inteiro
PATCH Atualização parcial Apenas campos alterados
// PUT - envia objeto completo
body: json.encode(account.toMap())

// PATCH - envia apenas o que mudou
body: json.encode({'name': 'Novo Nome'})

DELETE - Remover Recursos

Requisição DELETE Básica

Future<bool> deleteAccount(String id) async {
  final response = await http.delete(
    Uri.parse('http://localhost:3000/accounts/$id'),
    headers: {'Content-Type': 'application/json'},
  );

  return response.statusCode == 200;
}

DELETE com Autenticação

Future<bool> deleteAccount(String id, String token) async {
  final response = await http.delete(
    Uri.parse('$baseUrl/accounts/$id'),
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer $token',
    },
  );

  if (response.statusCode != 200) {
    throw Exception('Falha ao excluir');
  }

  return true;
}

Códigos de Status DELETE

Código Significado
200 Sucesso com corpo na resposta
204 Sucesso sem corpo (No Content)
404 Recurso não encontrado
401 Não autorizado

Dialogs de Confirmação

AlertDialog Básico

Future<bool?> showConfirmationDialog(BuildContext context) {
  return showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('Confirmação'),
      content: Text('Deseja realizar esta ação?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context, false),
          child: Text('Cancelar'),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, true),
          child: Text('Confirmar'),
        ),
      ],
    ),
  );
}

Documentação: AlertDialog

Dialog Reutilizável

Future<bool?> showConfirmationDialog(
  BuildContext context, {
  String title = 'Atenção!',
  String content = 'Deseja realizar esta operação?',
  String confirmText = 'Confirmar',
  String cancelText = 'Cancelar',
}) {
  return showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: Text(title),
      content: Text(content),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context, false),
          child: Text(cancelText),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, true),
          child: Text(
            confirmText,
            style: TextStyle(
              color: Colors.red,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
      ],
    ),
  );
}

Uso do Dialog

void _onDeletePressed() async {
  final confirmed = await showConfirmationDialog(
    context,
    title: 'Excluir Conta',
    content: 'Deseja realmente excluir esta conta?',
    confirmText: 'Excluir',
  );

  if (confirmed == true) {
    await deleteAccount(account.id);
    // Atualizar UI
  }
}

Padrão: Sempre Confirmar Ações Destrutivas

// BOM - Pede confirmação antes de excluir
onPressed: () async {
  final confirmed = await showConfirmationDialog(context);
  if (confirmed == true) {
    await service.delete(id);
  }
}

// RUIM - Exclui direto sem confirmar
onPressed: () async {
  await service.delete(id);
}

Autenticação com Login e Senha

Estrutura do AuthService

class AuthService {
  static const String _tokenKey = 'accessToken';

  Future<String> login(String email, String password) async {
    final response = await http.post(
      Uri.parse('$baseUrl/login'),
      body: {'email': email, 'password': password},
    );

    if (response.statusCode != 200) {
      throw Exception('Falha no login');
    }

    final data = json.decode(response.body);
    return data['accessToken'];
  }

  Future<String> register(String email, String password) async {
    final response = await http.post(
      Uri.parse('$baseUrl/register'),
      body: {'email': email, 'password': password},
    );

    if (response.statusCode != 201) {
      throw Exception('Falha no registro');
    }

    final data = json.decode(response.body);
    return data['accessToken'];
  }
}

Endpoints de Autenticação (json-server-auth)

Método Endpoint Body Resposta
POST /register {email, password} {accessToken, user}
POST /login {email, password} {accessToken, user}

Tela de Login

class _LoginScreenState extends State<LoginScreen> {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _authService = AuthService();
  bool _isLoading = false;

  Future<void> _onLoginPressed() async {
    setState(() => _isLoading = true);

    try {
      await _authService.login(
        _emailController.text,
        _passwordController.text,
      );
      Navigator.pushReplacementNamed(context, 'home');
    } catch (e) {
      // Mostrar erro
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          TextFormField(controller: _emailController),
          TextFormField(
            controller: _passwordController,
            obscureText: true,
          ),
          ElevatedButton(
            onPressed: _isLoading ? null : _onLoginPressed,
            child: _isLoading
                ? CircularProgressIndicator()
                : Text('Entrar'),
          ),
        ],
      ),
    );
  }
}

Token de Autenticação (JWT)

Instalação do SharedPreferences

# pubspec.yaml
dependencies:
  shared_preferences: ^2.2.2
flutter pub add shared_preferences

Pacote: pub.dev/packages/shared_preferences

Salvar Token

import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveToken(String token) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('accessToken', token);
}

Recuperar Token

Future<String?> getToken() async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getString('accessToken');
}

Verificar se Está Logado

Future<bool> isLoggedIn() async {
  final token = await getToken();
  return token != null && token.isNotEmpty;
}

Logout (Remover Token)

Future<void> logout() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('accessToken');
}

Usar Token nas Requisições

Future<Map<String, String>> _getHeaders() async {
  final token = await getToken();
  return {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer $token',
  };
}

Future<List<Account>> getAccounts() async {
  final headers = await _getHeaders();
  final response = await http.get(
    Uri.parse('$baseUrl/accounts'),
    headers: headers,
  );
  // ...
}

Verificar Token no Startup

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final authService = AuthService();
  final isLoggedIn = await authService.isLoggedIn();

  runApp(MyApp(isLoggedIn: isLoggedIn));
}

class MyApp extends StatelessWidget {
  final bool isLoggedIn;

  const MyApp({required this.isLoggedIn});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: isLoggedIn ? 'home' : 'login',
      routes: {
        'login': (_) => LoginScreen(),
        'home': (_) => HomeScreen(),
      },
    );
  }
}

Tratamento de Erros de APIs

Exceções Customizadas

class UserNotFoundException implements Exception {
  final String message;
  UserNotFoundException([this.message = 'Usuário não encontrado']);
}

class TokenExpiredException implements Exception {
  final String message;
  TokenExpiredException([this.message = 'Token expirado']);
}

class UnauthorizedException implements Exception {
  final String message;
  UnauthorizedException([this.message = 'Não autorizado']);
}

class ServerException implements Exception {
  final String message;
  ServerException([this.message = 'Erro no servidor']);
}

Verificar Erros da API

void _verifyException(String responseBody) {
  final errorMessage = responseBody.toLowerCase();

  if (errorMessage.contains('jwt expired')) {
    throw TokenExpiredException();
  }
  if (errorMessage.contains('cannot find user')) {
    throw UserNotFoundException();
  }
  if (errorMessage.contains('unauthorized')) {
    throw UnauthorizedException();
  }

  throw ServerException(responseBody);
}

Try-Catch com Múltiplas Exceções

Future<void> _onLoginPressed() async {
  try {
    await authService.login(email, password);
    Navigator.pushReplacementNamed(context, 'home');
  } on UserNotFoundException {
    // Usuário não existe - oferecer registro
    _offerRegistration();
  } on UnauthorizedException {
    // Senha incorreta
    _showError('Senha incorreta');
  } on SocketException {
    // Sem internet
    _showError('Verifique sua conexão');
  } catch (e) {
    // Erro genérico
    _showError('Erro inesperado');
  }
}

Tratamento com catchError (Futures)

authService.login(email, password)
  .then((token) {
    Navigator.pushReplacementNamed(context, 'home');
  })
  .catchError((e) {
    _offerRegistration();
  }, test: (e) => e is UserNotFoundException)
  .catchError((e) {
    _showError(e.message);
  }, test: (e) => e is ServerException);

Dialog de Erro

void showExceptionDialog(
  BuildContext context, {
  required String content,
  String title = 'Ocorreu um problema',
}) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Row(
        children: [
          Icon(Icons.warning, color: Colors.orange),
          SizedBox(width: 8),
          Text(title),
        ],
      ),
      content: Text(content),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: Text('OK'),
        ),
      ],
    ),
  );
}

Códigos HTTP Comuns

Código Significado Ação Sugerida
200 Sucesso Processar resposta
201 Criado Processar resposta
400 Bad Request Validar dados enviados
401 Não Autorizado Redirecionar para login
403 Proibido Mostrar mensagem
404 Não Encontrado Mostrar mensagem
500 Erro Servidor Tentar novamente

Tratamento de Token Expirado

try {
  final accounts = await accountService.getAll();
} on TokenExpiredException {
  await authService.logout();
  Navigator.pushNamedAndRemoveUntil(
    context,
    'login',
    (route) => false,
  );
}

json-server-auth

Instalação

npm install -g json-server-auth

Estrutura do db.json

{
  "users": [],
  "accounts": [],
  "transactions": [],
  "accountTypes": []
}

Arquivo routes.json (Permissões)

{
  "users": 600,
  "accounts": 660,
  "transactions": 660,
  "accountTypes": 444
}

Códigos de Permissão

Código Leitura Escrita Descrição
444 Público - Somente leitura pública
600 Privado Privado Apenas dono acessa
640 Público Privado Lê público, escreve autenticado
660 Autenticado Autenticado Requer token

Executar Servidor

json-server-auth db.json --routes routes.json --port 3000

Recursos e Documentação

Documentação Oficial

Recurso Link
http package pub.dev/packages/http
shared_preferences pub.dev/packages/shared_preferences
json-server-auth npmjs.com/package/json-server-auth

API Reference

Classe Link
AlertDialog api.flutter.dev/flutter/material/AlertDialog
showDialog api.flutter.dev/flutter/material/showDialog
Navigator api.flutter.dev/flutter/widgets/Navigator

HTTP Status Codes

Recurso Link
MDN Web Docs developer.mozilla.org/en-US/docs/Web/HTTP/Status
HTTP Cats http.cat

Quick Reference

Ação Código
Requisição PUT http.put(uri, headers: {...}, body: json)
Requisição DELETE http.delete(uri, headers: {...})
Mostrar dialog showDialog(context: ctx, builder: ...)
Fechar dialog com valor Navigator.pop(context, value)
Salvar token prefs.setString('key', token)
Recuperar token prefs.getString('key')
Remover token prefs.remove('key')
Header de auth 'Authorization': 'Bearer $token'
Lançar exceção throw MinhaException()
Capturar específica on MinhaException catch (e) {...}

Fluxo de Autenticação

App Inicia
    |
    v
Verifica token no SharedPreferences
    |
    +---> Token existe? ---> HomeScreen
    |
    +---> Não existe? ---> LoginScreen
                              |
                              v
                         Usuário digita email/senha
                              |
                              v
                         POST /login
                              |
              +---------------+---------------+
              |               |               |
              v               v               v
           Sucesso       Usuário não      Erro
              |          encontrado         |
              v               |             v
         Salvar token    Oferecer      Mostrar erro
              |          registro
              v               |
         HomeScreen      POST /register
                              |
                              v
                         Salvar token
                              |
                              v
                         HomeScreen

Fluxo de Requisição Autenticada

Ação do Usuário (ex: editar conta)
         |
         v
Recuperar token do SharedPreferences
         |
         v
Montar headers com Authorization
         |
         v
Fazer requisição (PUT/DELETE)
         |
         +---> 200: Sucesso --> Atualizar UI
         |
         +---> 401: Não autorizado --> Verificar token
         |                                   |
         |                           Token expirado?
         |                                   |
         |                           Logout e ir para Login
         |
         +---> 4xx/5xx: Erro --> Mostrar mensagem

Checklist da Aula

  • Fazer requisição PUT para atualizar recurso
  • Fazer requisição DELETE para remover recurso
  • Criar dialog de confirmação reutilizável
  • Usar dialog antes de ações destrutivas
  • Implementar login com email e senha
  • Implementar registro de novo usuário
  • Salvar token com SharedPreferences
  • Recuperar e verificar token no startup
  • Enviar token no header Authorization
  • Implementar logout (remover token)
  • Criar exceções customizadas
  • Tratar erros específicos da API
  • Mostrar mensagens de erro amigáveis
  • Redirecionar para login quando token expira
  • Configurar json-server-auth

Flutter Cheatsheet - Estilizacao e Layouts

Estilizando e Reproduzindo Layouts do Figma

Objetivo: Referencia rapida para acompanhamento da aula e consulta posterior.


Indice

  1. BoxDecoration e Gradientes
  2. TextStyle e Text.Rich
  3. InkWell e GestureDetector
  4. Widgets Nativos vs Material Design
  5. ThemeData e Temas
  6. Fontes Personalizadas
  7. Organizacao de Codigo
  8. Recursos e Documentacao

BoxDecoration e Gradientes

BoxDecoration Basico

Container(
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(16),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 10,
        offset: Offset(0, 4),
      ),
    ],
  ),
  child: Text('Conteudo'),
)

Documentacao: BoxDecoration

Propriedades do BoxDecoration

Propriedade Tipo Descricao
color Color Cor de fundo solida
gradient Gradient Gradiente (nao usar com color)
borderRadius BorderRadius Cantos arredondados
boxShadow List Sombra(s)
border Border Borda
image DecorationImage Imagem de fundo
shape BoxShape Formato (rectangle, circle)

LinearGradient

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [
        Color(0xFF0A1F3D),
        Color(0xFF1B4F72),
      ],
    ),
  ),
)

Direcoes do Gradiente

Direcao begin end
Cima para baixo topCenter bottomCenter
Esquerda para direita centerLeft centerRight
Diagonal topLeft bottomRight
Diagonal inversa topRight bottomLeft

Gradiente com Stops

LinearGradient(
  colors: [cor1, cor2, cor3],
  stops: [0.0, 0.5, 1.0],
)

BoxShadow

BoxShadow(
  color: Colors.black.withOpacity(0.1),
  blurRadius: 10,
  spreadRadius: 2,
  offset: Offset(0, 4),
)
Parametro Descricao
color Cor com opacidade
blurRadius Quanto desfoca
spreadRadius Quanto se expande
offset Deslocamento (x, y)

BorderRadius

// Todos iguais
BorderRadius.circular(16)

// Individuais
BorderRadius.only(
  topLeft: Radius.circular(16),
  topRight: Radius.circular(16),
)

// Vertical
BorderRadius.vertical(
  top: Radius.circular(16),
)

Border

// Todas as bordas
Border.all(color: Colors.grey, width: 1)

// Bordas especificas
Border(
  bottom: BorderSide(color: Colors.grey, width: 1),
)

TextStyle e Text.Rich

TextStyle

Text(
  'Texto estilizado',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Color(0xFF2C3E50),
    letterSpacing: 0.5,
    height: 1.5,
    fontFamily: 'Montserrat',
  ),
)

Documentacao: TextStyle

FontWeight

Constante Valor Nome
FontWeight.w300 300 Light
FontWeight.w400 400 Regular
FontWeight.w500 500 Medium
FontWeight.w600 600 SemiBold
FontWeight.w700 700 Bold

Text.Rich com TextSpan

Text.rich(
  TextSpan(
    text: 'Saldo: ',
    style: TextStyle(color: Colors.grey),
    children: [
      TextSpan(
        text: 'R\$ 5.230,45',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Colors.black,
        ),
      ),
    ],
  ),
)

Documentacao: Text.rich

Text.Rich com Multiplos Trechos

Text.rich(
  TextSpan(
    children: [
      TextSpan(text: 'Texto ', style: TextStyle(color: Colors.grey)),
      TextSpan(text: 'destaque', style: TextStyle(fontWeight: FontWeight.bold)),
      TextSpan(text: ' normal.', style: TextStyle(color: Colors.grey)),
    ],
  ),
)

TextOverflow

Text(
  'Texto longo...',
  overflow: TextOverflow.ellipsis,
  maxLines: 1,
)
Valor Efeito
clip Corta
ellipsis "..."
fade Desfoca
visible Extravasa

InkWell e GestureDetector

InkWell (com efeito ripple)

InkWell(
  onTap: () => print('tap'),
  onLongPress: () => print('segurar'),
  borderRadius: BorderRadius.circular(12),
  splashColor: Colors.blue.withOpacity(0.2),
  child: Container(
    padding: EdgeInsets.all(16),
    child: Text('Clique'),
  ),
)

Documentacao: InkWell

GestureDetector (sem efeito visual)

GestureDetector(
  onTap: () => print('tap'),
  onDoubleTap: () => print('duplo'),
  onLongPress: () => print('segurar'),
  child: Container(
    child: Text('Toque'),
  ),
)

Comparacao

InkWell GestureDetector
Efeito Ripple Nenhum
Material Necessario Nao necessario
Gestos Basicos Basicos + complexos
Uso Botoes Gestos complexos

Widgets Nativos vs Material Design

Widgets Nativos

Container()    // Caixa com decoracao
Column()       // Vertical
Row()          // Horizontal
Stack()        // Sobreposicao
Padding()      // Espacamento
SizedBox()     // Espaco fixo
Expanded()     // Preenche espaco
Text()         // Texto
Image()        // Imagem
Icon()         // Icone

Widgets Material Design

Scaffold()              // Estrutura da tela
AppBar()                // Barra superior
Card()                  // Cartao com elevacao
ElevatedButton()        // Botao elevado
TextButton()            // Botao de texto
IconButton()            // Botao de icone
ListTile()              // Item de lista
Drawer()                // Menu lateral
BottomNavigationBar()   // Barra inferior
FloatingActionButton()  // Botao flutuante

Quando Usar

Necessidade Escolha
Layout customizado Nativos (Container, Row, Column)
Botao padrao Material (ElevatedButton)
Botao customizado InkWell + Container
Cartao customizado Container + BoxDecoration
Cartao simples Card

ThemeData e Temas

ThemeData Basico

MaterialApp(
  theme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Color(0xFF0A1F3D),
    ),
    fontFamily: 'Montserrat',
    scaffoldBackgroundColor: Color(0xFFF5F5F5),
  ),
)

Documentacao: ThemeData

Acessando o Tema

final theme = Theme.of(context);
final colors = theme.colorScheme;
final textTheme = theme.textTheme;

Text('Titulo', style: theme.textTheme.headlineLarge);

AppBarTheme

appBarTheme: AppBarTheme(
  backgroundColor: Color(0xFF0A1F3D),
  foregroundColor: Colors.white,
  elevation: 0,
  centerTitle: false,
)

CardTheme

cardTheme: CardTheme(
  elevation: 2,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(16),
  ),
)

ElevatedButtonTheme

elevatedButtonTheme: ElevatedButtonThemeData(
  style: ElevatedButton.styleFrom(
    backgroundColor: Color(0xFF0A1F3D),
    foregroundColor: Colors.white,
    padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
)

TextTheme

textTheme: TextTheme(
  headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
  titleMedium: TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
  bodyMedium: TextStyle(fontSize: 14, color: Colors.grey),
)

Dark Theme

MaterialApp(
  theme: AppTheme.lightTheme,
  darkTheme: AppTheme.darkTheme,
  themeMode: ThemeMode.system,
)

Fontes Personalizadas

Passo 1: Estrutura de Arquivos

assets/
  fonts/
    Montserrat-Regular.ttf
    Montserrat-Medium.ttf
    Montserrat-SemiBold.ttf
    Montserrat-Bold.ttf

Passo 2: pubspec.yaml

flutter:
  fonts:
    - family: Montserrat
      fonts:
        - asset: assets/fonts/Montserrat-Regular.ttf
          weight: 400
        - asset: assets/fonts/Montserrat-Medium.ttf
          weight: 500
        - asset: assets/fonts/Montserrat-SemiBold.ttf
          weight: 600
        - asset: assets/fonts/Montserrat-Bold.ttf
          weight: 700

Passo 3: Usar no Codigo

// Em um widget
Text('Texto', style: TextStyle(fontFamily: 'Montserrat'))

// No tema global
ThemeData(fontFamily: 'Montserrat')

Fontes Populares para Apps

Fonte Estilo Uso
Montserrat Geometrica Titulos e UI
Inter Neutra Corpo de texto
Poppins Geometrica UI moderna
Roboto Padrao Android Consistencia
SF Pro Padrao iOS Consistencia

Organizacao de Codigo

Estrutura de Pastas

lib/
  main.dart
  theme/
    app_colors.dart
    app_theme.dart
    app_text_styles.dart
  screens/
    home_screen.dart
  widgets/
    header_widget.dart
    balance_card.dart
    action_button.dart
    transaction_tile.dart

Constantes de Cores

class AppColors {
  static const Color primary = Color(0xFF0A1F3D);
  static const Color primaryLight = Color(0xFF1B4F72);
  static const Color background = Color(0xFFF5F5F5);
  static const Color textPrimary = Color(0xFF2C3E50);
  static const Color textSecondary = Color(0xFF7F8C8D);
  static const Color positive = Color(0xFF27AE60);
  static const Color negative = Color(0xFFE74C3C);
}

Boas Praticas

Pratica Exemplo
Centralizar cores AppColors.primary
Centralizar estilos AppTheme.lightTheme
Componentes reutilizaveis ActionButton(icon, label, onTap)
Nomes descritivos HeaderWidget, BalanceCard
Separar por responsabilidade theme/, screens/, widgets/

Quick Reference

Acao Codigo
Cor hexadecimal Color(0xFF0A1F3D)
Gradiente linear LinearGradient(colors: [c1, c2])
Bordas arredondadas BorderRadius.circular(16)
Sombra BoxShadow(blurRadius: 10, offset: Offset(0, 4))
Texto em negrito FontWeight.bold
Texto com estilos mistos Text.rich(TextSpan(children: [...]))
Area clicavel com ripple InkWell(onTap: ..., child: ...)
Acessar tema Theme.of(context)
Estilo de texto do tema theme.textTheme.headlineLarge
Fonte no tema ThemeData(fontFamily: 'Montserrat')
Cor com opacidade color.withOpacity(0.1)
Avatar circular CircleAvatar(radius: 24, child: Icon(...))

Mapeamento Figma para Flutter

Elemento do Figma Widget Flutter
Retangulo com cor Container(color: ...)
Retangulo com gradiente Container(decoration: BoxDecoration(gradient: ...))
Retangulo arredondado Container(decoration: BoxDecoration(borderRadius: ...))
Sombra BoxDecoration(boxShadow: [...])
Texto Text(style: TextStyle(...))
Texto misto Text.rich(TextSpan(children: [...]))
Imagem circular CircleAvatar ou ClipRRect
Grupo horizontal Row(children: [...])
Grupo vertical Column(children: [...])
Sobreposicao Stack(children: [...])
Espaco entre SizedBox(height/width: ...)
Botao customizado InkWell + Container
Barra de navegacao BottomNavigationBar

Checklist da Aula

  • Analisar layout do Figma e identificar componentes
  • Mapear componentes para Widgets Flutter
  • Extrair cores, fontes e espacamentos
  • Criar constantes de cores centralizadas
  • Usar BoxDecoration para estilizar containers
  • Criar gradientes com LinearGradient
  • Adicionar sombras com BoxShadow
  • Usar BorderRadius para cantos arredondados
  • Estilizar textos com TextStyle
  • Misturar estilos com Text.Rich e TextSpan
  • Criar areas clicaveis com InkWell
  • Diferenciar InkWell de GestureDetector
  • Configurar ThemeData global
  • Adicionar fontes personalizadas no pubspec.yaml
  • Organizar codigo em pastas (theme, screens, widgets)

Proxima Aula: Navegacao e Animacoes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment