Repository navigation
Releases
v3.4.1
Full Changelog: v3.4.0...v3.4.1
v3.4.0 — Transclusão, .odt e os últimos itens de paridade
Fecha os cinco itens que o plano de paridade com o desktop ainda listava como abertos. O que sobra não é pendência: gravação atômica é teto do SAF, o calendário é fase 3, e grafo, chat e plugins estão fora de escopo no celular de propósito.
Transclusão de nota
![[Nota]] e ![[Nota#Seção]] passam a trazer o conteúdo, não só um link. Era o último item de leitura que faltava — sem ele, um texto usado em dez notas é copiado dez vezes e as cópias vão divergindo.
Aqui o conteúdo vira citação, não uma caixa. No desktop o miolo é envolvido num <div>, porque lá o renderizador reprocessa o que está dentro como Markdown. O Markwon não faz isso: HTML injetado deixaria o Markdown de dentro cru na tela. Citação é Markdown de verdade, dá o mesmo limite visual e continua legível se você abrir a nota em qualquer outro editor.
Duas notas que se embutem uma na outra não travam a renderização, e um alvo que não existe fica visível como ![[Nome]] em vez de sumir — embed que some calado esconde que falta uma nota.
Lê .odt
Documentos do LibreOffice abrem e convertem para Markdown, como já acontecia com .docx. Cobre título, negrito e itálico (lidos dos estilos declarados no próprio documento), link, lista com marcador, lista numerada, lista aninhada e tabela.
O parser XML é escrito à mão, e não com o XmlPullParser do Android, porque o do Android é um stub que lança em teste unitário. Sem isso o conversor iria para a loja sem um único teste — e conversor de formato alheio é justamente onde os casos esquecidos moram.
%%comentário%%
Anotação sua que fica no arquivo e não aparece na leitura. Pode engolir um bloco inteiro — um título de rascunho, uma lista — sem deixar restos.
Exportar HTML
No menu da nota, ao lado de exportar PDF. Usa o mesmo HTML da impressão, então o que você salva é o que você veria impresso.
Abrir nota aleatória
No menu do vault. Cai numa nota que você não estava procurando; exclui a que já está aberta, senão o botão devolveria a mesma tela.
Sem permissões, sem Google Play Services, nada sai do aparelho. 231 testes.
v3.3.0
v3.2.0
v3.1.0
v3.0.0 — O celular deixa de ser a versão reduzida
O celular deixa de ser a versão reduzida.
Desde a 2.0.0 o app ganhou português, wikilinks que funcionam, busca com operadores, diagnóstico do vault, backup, cache do índice e blocos query. Esta versão fecha as três últimas peças — e com elas o Android faz praticamente tudo que o desktop faz, tirando o que só existe num computador.
🃏 Import de baralho do Anki, direto no celular
⋮ da gaveta → Importar do Anki. Sem Anki Desktop, sem passo intermediário.
E sem dependência nenhuma: a coleção dentro de um .apkg é SQLite puro, e o Android já tem SQLite. A versão desktop precisou trazer o SQLite compilado para WebAssembly para fazer o mesmo trabalho.
- Notas cloze viram cartões de lacuna; tags
A::Bviram#A/B. - Cloze é detectado pelo texto, não pela tabela de tipo de nota — cujo formato mudou entre versões do Anki. E como nota cloze não tem campo de resposta, exigir uma jogava todas fora em silêncio. Esse bug já aconteceu no desktop; não valia a pena ter duas vezes.
- Baralho grande vira uma pasta, 100 cartões por nota: uma nota com milhares de cartões leva segundos para abrir; com cem, é instantânea.
- Ele pergunta antes de escrever qualquer coisa no vault.
Uma limitação, dita com todas as letras: o Anki 2.1.50+ comprime a coleção com zstd, que o Android não tem. Em vez de engordar o APK com uma biblioteca nativa só por isso, um pacote nesse formato é recusado com a instrução exata: no Anki, exporte de novo marcando "Suportar versões antigas do Anki". Instrução precisa vale mais que falha misteriosa.
Mídia não entra por aqui. Os arquivos ficam no pacote com nomes numéricos, e uma referência quebrada no meio do cartão é pior que nada. Se o baralho tem imagens, importe no desktop — o vault é o mesmo.
📊 Estatísticas de revisão
⋮ da gaveta → Estatísticas. Retenção, facilidade média, e a previsão dos próximos 14 dias desenhada como barras.
Sem biblioteca de gráficos: são catorze números, e a forma da fila é a mensagem inteira — uma parede no primeiro dia se lê igual em qualquer resolução. Tudo que está atrasado cai nesse primeiro dia, porque é ali que está.
Um vault sem nada revisado mostra retenção 0, que é a resposta honesta — não uma divisão por zero vestida de 100%.
📖 Leitor de EPUB
Abra um .epub do vault e ele lê, com sumário e zoom, um capítulo por vez.
Também sem biblioteca de leitor: um .epub é um ZIP com um manifesto XML, e o WebView já renderiza o resto. Trazer um leitor inteiro adicionaria uma dependência e os bugs dela; o que é genuinamente chato é achar a ordem de leitura, e essa parte está coberta por testes.
Um capítulo por vez é de propósito: um livro-texto tem dezenas de MB de XHTML, e um celular que precisa segurar tudo para mostrar a primeira página não mostra nada. As imagens do capítulo entram embutidas, porque o WebView roda sem acesso a arquivo e sem rede — um livro é conteúdo não confiável como qualquer outro download.
🧪 Por baixo
153 testes, rodando no CI antes de cada APK. O projeto não tinha nenhum há duas semanas.
Um deles pegou um erro que eu não teria visto de outro jeito: em JavaScript, dois cifrões numa substituição viram um cifrão literal; em Kotlin o cifrão começa uma referência de grupo. A mesma linha portada literalmente do desktop produzia outra coisa na matemática dos cartões do Anki.
📥 Instalação
Assinado com a mesma chave desde a 1.x, então instala por cima sem desinstalar nada e sem perder configurações. Android 9 ou mais novo.
v2.4.0 — Índices que se escrevem sozinhos
Índices que se escrevem sozinhos.
🔎 Blocos query
Um bloco assim, dentro de qualquer nota:
```query
tag: sis-cardio
tipo: patologia
sort: modificado desc
limit: 20
```…vira, no preview, a lista das notas que ele encontra — com wikilinks clicáveis. Não é escrito à mão e não desatualiza: ele lê as tags e o frontmatter na hora de abrir a nota.
- Filtra por
tag:,pasta:,has:e por qualquer campo do frontmatter. - Duas linhas
tag:se somam: precisa ter as duas. É o corte que dá trabalho manual. sort:por título, modificação ou caminho, comdesc;limit:corta a lista.- Chaves em português funcionam igual:
pasta:,ordenar:,limite:.
Duas regras que vieram de propósito do desktop:
- Linha que a consulta não entendeu aparece acima da lista, em vez de sumir. Uma consulta que erra em silêncio devolve uma lista que parece certa — e você confia nela.
- Consulta sem nenhum filtro devolve nada, não o vault inteiro. Pelo mesmo motivo.
📊 Estatísticas de revisão
A lógica está pronta e testada — retenção, previsão de 14 dias, notas com mais cartões. A tela ainda não existe; ela entra numa próxima versão. Prefiro dizer isso a listar como entregue.
Duas decisões que valem explicar: um vault sem nada revisado tem retenção 0, que é a resposta honesta, não uma divisão por zero vestida de 100%. E tudo que está atrasado cai no primeiro dia da previsão, porque é ali que está.
📥 Import de .apkg: adiado, e por quê
O vault é compartilhado entre celular e computador. Importe o baralho no desktop — que já faz isso, inclusive com as imagens — e o resultado aparece aqui pela sincronização: as notas e o srs.json são os mesmos arquivos.
Fazer no celular exigiria uma dependência nativa de zstd só para ele, engordando o APK, para resolver um problema que a pasta compartilhada já resolve.
🧪 Por baixo
+23 testes (117 no total), rodando no CI antes de cada APK.
📥 Instalação
Assinado com a mesma chave, instala por cima da 2.3.0. Android 9 ou mais novo.
v2.3.0 — Abrir o app ficou rápido, e o vault tem backup
Abrir o app para de reler o vault inteiro, e o vault ganha um botão de backup.
⚡ O índice não é mais reconstruído do zero
A cada abertura o app lia todas as notas, uma por uma, através do Storage Access Framework — para montar a busca, os backlinks, as tags e os flashcards. Num vault de algumas centenas de notas essa é a coisa mais lenta que ele faz, e fazia por conteúdo que não tinha mudado.
Agora o texto fica guardado entre sessões e é validado por data de modificação: uma nota mexida em outro lugar — no desktop, por exemplo, numa pasta sincronizada — tem mtime diferente e é relida. O resto vem do cache.
Duas decisões que valem explicar:
- O cache não fica dentro do vault. É informação derivada: perder custa uma releitura e nada mais. Um JSON do tamanho do vault não tem o que fazer numa pasta que você sincroniza e faz backup.
- Uma nota que não pôde ser lida fica de fora do cache. Guardá-la como texto vazio com data válida a faria parecer atual para sempre, e ela sumiria da busca e dos backlinks atravessando reinícios. Foi exatamente esse o bug que mordeu a versão desktop.
💾 Backup do vault em .zip
No ⋮ do cabeçalho da gaveta. O destino é escolhido pelo seletor do próprio Android, então o arquivo pode ir para o Drive, para o cartão ou para onde você quiser — e o app não precisa de nenhuma permissão de armazenamento.
Inclui tudo: notas, anexos, PDFs e o agendamento dos flashcards. Backup que descarta as imagens em silêncio é pior que nenhum, porque você descobre quando precisa. Um arquivo que não puder ser lido é contado e pulado, e o total aparece no fim — em vez de abortar a operação inteira.
🧭 Onde ficam as ações do vault
Diagnóstico e backup foram para o ⋮ da gaveta, não para o ⋮ da nota. O da nota só existe quando há uma nota aberta; ações do vault ali ficariam inalcançáveis justamente para quem acabou de abrir o app.
🧪 Por baixo
+13 testes (94 no total), rodando no CI antes de cada APK.
📥 Instalação
Assinado com a mesma chave, instala por cima da 2.2.0. Android 9 ou mais novo.
v2.2.0 — Os links levam a algum lugar
Os links passam a levar a algum lugar, e a busca a devolver o que você procurou.
🔗 Wikilinks completos
Tocar num link era uma busca por nome exato. Isso quer dizer que [[Nota#Seção]], [[Pasta/Nota]] e qualquer alias do frontmatter simplesmente não faziam nada — sem abrir nota, sem mensagem, sem pista de que havia um alvo.
Agora:
[[Nota#Seção]]e[[Nota#^bloco]]abrem a nota.[[Pasta/Nota]]resolve pelo caminho.- Aliases do frontmatter funcionam: com
aliases: [IAM, Infarto], um[[Infarto]]chega na nota. Em medicina quase tudo tem três nomes; sem isso ou o nome do arquivo vira uma lista de sinônimos, ou a nota fica inalcançável pelos outros nomes. - Duas notas com o mesmo nome? Ganha a mais próxima de quem linka — senão o resultado dependeria da ordem em que a pasta foi lida, que ninguém consegue prever.
- Nome de arquivo real sempre ganha de um alias.
- E um link que não leva a lugar nenhum diz isso, em vez de ignorar o toque.
🔍 Busca com operadores
Era uma varredura de substring, que num vault de algumas centenas de notas devolve tudo — e portanto nada.
tag:cardio protocolo -rascunho
path:Clinica "dor torácica"
file:sepse tag:sistema (encontra sistema/cardio também)
Os resultados saem por relevância, não em ordem alfabética. O corpo conta com retorno decrescente: contar cada menção linearmente deixava uma nota longa ganhar de uma nota que é de fato sobre o assunto.
Um campo que não existe (autor:joao) vira texto de busca em vez de ser ignorado — ignorar devolveria o vault inteiro por causa de um erro de digitação.
🩺 Diagnóstico do vault
No menu ⋮. Mostra o que está silenciosamente errado:
- Links mortos, com o nome de quem aponta pra eles. Nomear a origem é a diferença entre uma lista de reclamações e algo em que dá pra agir.
- Notas órfãs — aquelas que nada referencia.
- Nomes duplicados — a razão pela qual um
[[link]]pode ser ambíguo.
Somente leitura, de propósito: ele diz o que achou; consertar é sua decisão.
🧪 Por baixo
+34 testes (81 no total), rodando no CI antes de cada APK. O resolver de links trabalha sobre um tipo próprio em vez do tipo do Android, porque Uri não existe em teste unitário — e justamente a parte mais delicada não podia ser a intestável.
📥 Instalação
Assinado com a mesma chave, instala por cima da 2.1.0. Android 9 ou mais novo.
v2.1.0 — O app fala português
O app fala português.
Até agora todas as strings estavam escritas direto no código, em inglês — inclusive as telas de flashcard e de diagrama que acabaram de nascer na 2.0.0. O desktop já falava três idiomas; o celular, que é onde o app mais é usado, falava um só.
~95 strings extraídas para strings.xml, com português do Brasil, inglês e espanhol. O app segue o idioma do sistema, sem ajuste nenhum a fazer.
O inglês continua sendo a fonte: se uma chave faltar nas outras traduções, ela cai no inglês em vez de quebrar a tela.
Por que agora, e não depois
Os próximos blocos — navegação e busca, robustez, estudo — trazem telas novas. Feito depois, cada uma delas nasceria em inglês e precisaria ser retraduzida. Feito agora, já nascem nos três idiomas.
Instalação
Assinado com a mesma chave das versões anteriores, então instala por cima da 2.0.0 sem desinstalar nada. Android 9 ou mais novo.