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.
Convenção de changelog: entradas
[Unreleased] — <data>são specs de planejamento históricos — registram o que estava previsto antes do lançamento. Não representam versões futuras pendentes. Quando uma versão é lançada, o spec permanece abaixo dela como referência do que foi planejado.
Instale em qualquer Android habilitando “Fontes desconhecidas” nas configurações.
Changelog
v1.2.2 — 01/03/2026
- Melhoria: Orientação da tela gerenciada por contexto —
SetupScreensempre em portrait,GameScreeneGameOverScreensempre em landscape. Controlado no_AppRouterviaSystemChrome.setPreferredOrientationsreativo aogameProvider; valor inicial definido emmain().
v1.2.1 — 01/03/2026
- Bug fix: Busca de comandantes via Scryfall — a API exige os headers
User-AgenteAccept: application/jsonem toda request; sem eles retorna HTTP 400. Builds release também precisavam de<uses-permission android:name="android.permission.INTERNET" />noAndroidManifest.xml. URLs reescritas comUri.encodeQueryComponentde forma absoluta noScryfallService, eliminando ambiguidade de resolução do Dio. - Melhoria: Autocomplete agora filtra por regras Conquest (
t:legendary t:creature or t:planeswalker) via/cards/search— apenas cartas elegíveis aparecem na lista.isValidConquestCommanderatualizado: aceitaLegendary CreatureouPlaneswalkersem restrição de legalidade de formato. - Melhoria: Background da
PlayerAreacom blur reduzido (sigma 18 → 4) e alinhamentotopCenterpara exibir a arte no topo da carta. Com partner, fundo dividido 50/50 entre os dois comandantes.
v1.2.0 — 28/02/2026
- Melhoria: Seleção manual do jogador inicial — durante o pré-início cada
PlayerAreaexibe um ícone de coroa (workspace_premium). Toque na coroa define aquele jogador como inicial; coroa preenchida em champagne indica o selecionado, contorno branco suave indica os demais. Ao iniciar a partida a coroa é substituída peloLandIndicatornormalmente. Novo métodosetStartingPlayer(int playerIndex)noGameNotifier. - Melhoria: Redesign visual — paleta Slate + Ouro. Fundo
#1A1D23, painéis#21252D, ouro dessaturado#C9A84C(champagne), verde musgo#1E3D28, cores de mana/jogador dessaturadas, active glow reduzido (borda 1.5 px, sombra com 40% opacidade). Land indicator ativo#5CB85Cno lugar do neon#00FF88. Aplicado em todos os arquivos do projeto.
v1.1.1 — 28/02/2026
- Bug fix: Veneno — jogador não era eliminado automaticamente ao atingir 10 counters de Poison. O spec de v1.1.0 descrevia a regra mas a implementação não aplicava a consequência. Corrigido em
adjustCounter: ao atingir 10 veneno, chama_eliminateInState+_checkWinCondition, idêntico ao comportamento de vida zerada.
v1.1.0 — 28/02/2026
- Bug fix: Dano de comandante — ao reduzir o contador com
-, a vida do jogador afetado não era restaurada. Correção computarealDelta = newValue - current(após clamp 0–99) e aplica nos dois sentidos viaadjustLifedentro deaddCommanderDamage. - StormCounter na TopBar, ao lado do relógio de partida: botão
⚡N— toque para incrementar a cada magia conjurada, long-press para resetar; reseta automaticamente nonextTurn. - Marcadores de jogador (
PlayerCounters) naPlayerArea: bottom sheet com grid +/- para Poison, Energy, Experience, Rad e Ticket. Botãoexposurena bottom row mostra total acumulado; campocountersadicionado aoPlayerModel.
[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 aleatório ou seleção manual por coroa
- 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 filtrada por regras Conquest (Legendary Creature ou Planeswalker), com preview de arte e identidade de cor
- Background com arte do comandante por jogador — blur leve, ancorado no topo da carta; com partner, fundo dividido 50/50 entre os dois comandantes
- 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
- StormCounter global na TopBar: registra magias conjuradas no turno; reseta automaticamente ao passar o turno (toque = +1, long-press = reset manual)
- Marcadores de jogador por área: Poison, Energy, Experience, Rad e Ticket com botões ±; Poison ≥ 10 elimina o jogador automaticamente
- Orientação adaptativa: portrait no menu de configuração, landscape durante a partida — sem depender de virar o dispositivo
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, stormCount |
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, counters (poison/energy/experience/rad/ticket) |
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 estormCount, avança ao próximo jogador vivo, reinicia o timer de turnoaddCommanderDamage— Atualiza o contador"sourcePlayerId_slot"(clamp 0–99); computarealDelta = newValue - currente aplica viaadjustLifenos dois sentidos (positivo = dano, negativo = restaura vida)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 demaisincrementStorm— IncrementastormCountem 1; restrito à partida iniciada e não encerradaresetStorm— ZerastormCountmanualmenteadjustCounter— Soma delta ao contador do tipo especificado do jogador (clamp 0–999); secounterType == 'poison'e valor resultante ≥ 10, elimina automaticamente sem confirmação (mesmo fluxo deadjustLife)setStartingPlayer— Define manualmente o jogador inicial durante o pré-início; atualizastartingPlayerIndexeactivePlayerIndex
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().
Condições de Eliminação
| Condição | Limite | Confirmação | Método |
|---|---|---|---|
| Vida zerada | life ≤ 0 |
Não — automático | adjustLife |
| Dano de comandante | commanderDamageLimit (configurável) |
Sim — dialog | CommanderDamagePanel._handleAdjust + eliminatePlayer |
| Poison | ≥ 10 (fixo, regra MTG) |
Não — automático | adjustCounter |
Em todos os casos, após marcar o jogador como eliminado, _checkWinCondition verifica se restar apenas 1 jogador ativo para encerrar a partida.
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 + StormCounter (toque=+1, long-press=reset) + botão Passar Turno com tempo do turno |
PlayerArea |
Background com arte do comandante (blur sigma 4, topCenter); com partner divide 50/50. LifeCounter, botões ±1/±5, badge de maior dano, botão PlayerCounters, LandIndicator (in-game) / coroa de seleção (pré-início), 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 |
PlayerCounters |
Bottom sheet com grid de 5 linhas (Poison, Energy, Experience, Rad, Ticket), cada uma com ícone colorido, valor atual e botões ±. Total acumulado visível na PlayerArea |
CommanderSearch |
Busca Scryfall com debounce de 500 ms, até 5 sugestões pré-filtradas por regra Conquest (Legendary Creature ou Planeswalker), preview de arte e identidade de cor. Fallback manual sem API |
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 — Cliente Dio com headers obrigatórios (User-Agent, Accept: application/json) e timeouts de 5 s. URLs absolutas com Uri.encodeQueryComponent. Dois métodos: autocomplete(query) consulta /cards/search filtrando por tipo Conquest; fetchByName(name) consulta /cards/named?fuzzy= e valida via isValidConquestCommander (Legendary Creature ou Planeswalker, sem restrição de legalidade). Provider: scryfallServiceProvider.
Roteamento
_AppRouter — Widget reativo que observa gameProvider e renderiza SetupScreen (state null), GameScreen (state não-null, isGameOver: false) ou GameOverScreen (isGameOver: true). Também gerencia orientação da tela via SystemChrome.setPreferredOrientations: portrait no setup, landscape no jogo. Histórico usa Navigator.push como overlay sobre o fluxo principal.