Este post serve como changelog oficial do app. Sempre que uma nova versão for lançada, adicione uma entrada no topo da seção de changelog abaixo.
Instale em qualquer Android habilitando “Fontes desconhecidas” nas configurações.
Changelog
[Unreleased] — 19/02/2026
- Bug fix: Dano de comandante — ao reduzir o contador com
-, a vida do jogador afetado não é restaurada. Correção aplica o delta real (após clamp 0–99) nos dois sentidos dentro do métodoaddCommanderDamage, sem necessidade de método novo. - Nova funcionalidade:
StormCounterna TopBar, ao lado do relógio de partida- Um único botão global — qualquer jogador toca para incrementar o contador a cada magia conjurada na mesa
- Reseta manualmente (long-press) ou automaticamente ao passar o turno do jogador ativo
- Nova funcionalidade: Marcadores de jogador do Magic na PlayerArea:
- Poison (veneno) - jogador perde com 10 counters
- Energy (energia) - recurso persistente entre turnos
- Experience (experiência) - acumula durante a partida
- Rad (radiação do set Fallout) - representa níveis de radiação
- Ticket (do set Unfinity) - usado para mecânicas de sticker
- Implementar
PlayerCounterswidget com grid de botões +/- - Adicionar campo de marcadores no
PlayerModel
v1.0.0 — 18/02/2026
- Lançamento inicial
- Suporte a 1–4 jogadores com layouts dedicados e rotação 180° para modo mesa
- Rastreamento de vida (toque ±1, long-press ±5) e dano de comandante por slot (main/partner)
- Suporte a partners: até 2 comandantes por jogador
- Eliminação automática ao zerar vida (sem confirmação) e por dano de comandante (com confirmação)
- Timers de partida (countdown configurável) e turno (count-up com acúmulo por jogador)
- Busca de comandantes via API Scryfall com validação de legalidade e identidade de cor
- Indicador de land por jogador: ativo apenas para o jogador do turno, reseta automaticamente ao passar o turno
- Fase pré-início com scry, timers pausados e sorteio de jogador inicial
- Histórico de partidas em memória (vencedor, comandante, turno, tempo)
- Regras de Conquest: eliminação por vida zerada ou dano de comandante acumulado ≥ limite
Sobre o App
App Flutter para partidas de Commander no formato Conquest. Offline, sem persistência entre sessões, estado reativo via Riverpod. Todos os dados vivem em memória enquanto o app está aberto.
Funcionalidades
- 1–4 jogadores com layouts dedicados e rotação 180° para modo mesa
- Vida com toque/long-press (±1/±5) por área do jogador
- Dano de comandante rastreado individualmente por slot (main e partner separados)
- Suporte a partners: até 2 comandantes por jogador
- Eliminação por vida zerada: automática e imediata, sem confirmação
- Eliminação por dano de comandante: exibe dialog de confirmação antes de eliminar
- Badge com maior dano recebido e nome do comandante ameaçador
- Indicador de land play por turno (só jogador ativo)
- Regra de scry pela distância circular ao jogador inicial
- Fase pré-início: scry visível, timers pausados, sorteio de início
- Countdown de partida configurável + count-up acumulado por turno/jogador
- Menu in-game (⋮) com reiniciar e voltar ao menu
- Reiniciar mantém nomes e comandantes, zera vida e dano, sorteia novo início
- Busca de comandantes via API Scryfall com identidade de cor e validação de legalidade
- Background desfocado com arte do comandante por jogador
- Configurações editáveis: vida inicial, limite de dano de comandante, deck mínimo, duração
- Histórico em memória: vencedor, comandante, jogadores, turno, tempo
Arquitetura
O app segue arquitetura reativa com Riverpod como gerenciador de estado. A navegação principal é reativa: nenhuma tela faz Navigator.push no fluxo principal — o _AppRouter observa o gameProvider e renderiza a tela certa automaticamente. A HistoryScreen é a única exceção, aberta via Navigator.push como overlay.
Models
| Model | Responsabilidade |
|---|---|
GameState |
Snapshot imutável da partida: jogadores, config, activePlayerIndex, startingPlayerIndex, turnNumber, isStarted, isGameOver, winnerId |
GameConfig |
Configurações: playerCount, vida inicial, limite de dano de comandante, deck mínimo, duração da partida, tableMode, startingPlayerIndex (índice inicial antes de qualquer sorteio) |
PlayerModel |
Dados do jogador: vida, commanders (até 2), dano recebido por chave "sourcePlayerId_slot", isEliminated, landPlayedThisTurn, turnTimeAccumulated |
CommanderModel |
Nome, imageUrl (nullable — null quando adicionado manualmente sem busca Scryfall) e colorIdentity |
MatchRecord |
Registro imutável de partida encerrada: vencedor, comandante, jogadores, turno, tempo decorrido |
Providers
GameNotifier — StateNotifier<GameState?> central. Estado null significa que o app está na tela de setup.
Principais métodos:
startGame— Inicializa jogadores com vida cheia usandoconfig.startingPlayerIndexcomo ponto de partida; entra em fase pré-início com timers pausados (o sorteio do jogador inicial é feito depois, porrandomizeStartingPlayer)startMatch— DefineisStarted: truee inicia os dois timers (partida e turno)restartGame— Reseta vida e dano, preserva nomes e comandantes, sorteia novo início, volta ao pré-inícioresetGame— Para timers e definestate = null, voltando ao SetupScreenadjustLife— Soma delta à vida do jogador; se vida ≤ 0, elimina automaticamente sem confirmaçãoeliminatePlayer— Marca jogador como eliminado e verifica condição de vitórianextTurn— Acumula tempo do turno no jogador ativo, reseta land indicator, avança ao próximo jogador vivo, reinicia o timer de turnoaddCommanderDamage— Atualiza o contador"sourcePlayerId_slot"(clamp 0–99); sedelta > 0, também desconta vida do alvo viaadjustLife; sedelta < 0, apenas corrige o contador (vida não é restaurada — bug conhecido)setCommander— Define ou substitui comandante no slot 0 (main) ou 1 (partner)removeCommander— Remove o comandante de um slot; dano já registrado na chave desse slot permanecerandomizeStartingPlayer— Sorteia novo jogador inicial durante fase pré-início (sempre diferente do atual)getScryValue— Retorna distância circular do jogador aostartingPlayerIndexpara cálculo do scrytoggleLandPlayed— InvertelandPlayedThisTurn; restrito ao jogador ativo, ignorado silenciosamente para os demais
MatchTimerNotifier — Countdown da duração da partida. Congelado no pré-início, parado automaticamente no game over.
TurnTimerNotifier — Count-up do turno atual. Reiniciado do zero a cada nextTurn, parado no game over.
HistoryNotifier — Lista em memória de MatchRecord, mais recente primeiro. Alimentado automaticamente ao fim de cada partida. Suporta clearHistory().
Telas
| Tela | Descrição |
|---|---|
SetupScreen |
Configuração de nomes, comandantes (main + partner por jogador) e GameConfig. Acesso ao histórico via overlay |
GameScreen |
Layout adaptativo 1–4 jogadores com rotação 180° no modo mesa e TopBar fixo |
GameOverScreen |
Exibe vencedor, comandante(s), vida final e tempo acumulado de cada jogador. Botões Reiniciar e Voltar ao Menu |
HistoryScreen |
Lista de partidas registradas com opção de limpar histórico completo |
Widgets Principais
| Widget | Descrição |
|---|---|
TopBar |
Pré-início: ícone de dado (Sortear) + timer estático + botão Iniciar. In-game: menu ⋮ + turno/jogador ativo + countdown com cor dinâmica + botão Passar Turno com tempo do turno |
PlayerArea |
Background desfocado com arte do comandante, LifeCounter, botões ±1/±5, indicadores de comandante, badge de maior dano, LandIndicator, ScryIndicator |
CommanderDamagePanel |
Bottom sheet com dano por fonte (main e partner separados). Botões ±. Ao atingir o limite, exibe dialog de confirmação antes de eliminar. Rodapé exibe “Maior dano único / limite” do jogador alvo |
CommanderSearch |
Busca Scryfall com debounce de 500 ms, até 5 sugestões, preview de arte, identidade de cor e validação de legalidade (Legendary Creature ou Legendary Planeswalker, commander: legal) |
LifeCounter |
Total de vida com estilo adaptado a valores negativos ou de muitos dígitos |
ScryIndicator |
Badge com valor de scry (distância ao jogador inicial). Visível apenas na fase pré-início |
LandIndicator |
Toggle de land play. Clicável apenas pelo jogador ativo; resetado a cada nextTurn |
Serviços
ScryfallService — Dois endpoints: autocomplete(query) para sugestões de nomes e fetchByName(name) para busca completa com validação de tipo e legalidade.
Roteamento
_AppRouter — Widget reativo que observa gameProvider e renderiza SetupScreen (state null), GameScreen (state não-null, isGameOver: false) ou GameOverScreen (isGameOver: true). Histórico usa Navigator.push como overlay sobre o fluxo principal.