Skip to content

Repository files navigation

Bonsai — cronômetro

App de cronômetros, focado em minimalismo e design inspirado pelo meu pai :)

Feito com Kotlin + Jetpack Compose, Material 3, nenhuma dependência além do AndroidX.

Baixar

O APK está em Releases. Baixe no próprio celular e toque no arquivo para instalar — o Android vai pedir para autorizar instalação de fonte desconhecida, o que é esperado para um app fora da Play Store.

Precisa de Android 8.0 (API 26) ou superior.

O que ele faz

  • Vários cronômetros ao mesmo tempo, cada um com seu título (toque no título para renomear).
  • Contagem crescente com décimos de segundo até 1 hora; depois disso, h:mm:ss, com os dias num prefixo miúdo (2d + 5:07:31) para não estourar a largura da coluna.
  • Retorno tátil no play/pause, no zerar e ao começar a rega.
  • Notificação opcional por cronômetro (o ícone de folha no card liga e desliga), com botões de Pausar/Retomar e Zerar direto na barra de status.
  • Um bonsai que cresce junto com o cronômetro, de semente a ancião ao longo de anos, com uma espécie própria (e uma cor própria) que se revela depois de semanas.
  • Segure o dedo no bonsai para regar — gotas caindo e respingo na terra. É puro afago: não altera estado nenhum.
  • Tema claro, escuro ou seguir o sistema, no botão do canto superior direito. A escolha fica em SharedPreferences.

O bonsai que cresce

Cada cronômetro tem sua própria árvore, desenhada em Canvas a partir de um fator de crescimento 0..1 derivado do tempo decorrido. A escala é logarítmica: em escala linear um cronômetro de três meses ficaria idêntico a um de três dias durante quase toda a vida dele. Com log, cada ordem de grandeza avança um tanto parecido no desenho.

decorrido fator estágio
1 min 0.01 semente
1 hora 0.16 broto
1 dia 0.42 muda
2 semanas 0.62 formação
1 mês 0.70 adulto
3 meses 0.79 robusto
6 meses 0.85 maduro
1 ano 0.91 ancião
3 anos 1.00 ancião

O vaso e a linha da terra não mudam de tamanho — são a régua contra a qual se percebe que a árvore cresceu. O que responde ao fator, em ordem de entrada:

elemento entra em ≈ tempo
cotilédones (o V de duas folhinhas) 0.004 – 0.03 primeiros minutos
copa principal 0.04 – 0.22 primeira hora
galho direito + copa 0.30 – 0.52 ~1 dia
galho esquerdo + copa 0.50 – 0.72 ~2 semanas
copa alta (4º pad) 0.70 – 0.84 1 a 3 meses
musgo na base 0.62 – 0.90 semanas a 6 meses
nebari (raízes aflorando) 0.76 – 0.94 3 meses a 1 ano

Os três últimos são o que impede a faixa dos meses de ser um planalto: sem eles, 1 mês e 1 ano desenhavam praticamente a mesma árvore. Altura, espessura e curvatura do tronco crescem continuamente ao longo de tudo.

Espécies

Cada cronômetro tem uma espécie própria, derivada do hash do id — estável para sempre, sem precisar guardar nada. A cor dela não vem com a muda: aparece entre ~2 semanas e ~8 meses (expressionOf), então é recompensa de bonsai tardio. O nome da espécie só se anuncia no card quando a cor já está visível na copa.

espécie como se manifesta
ácer folhagem vira carmim
ginkgo folhagem vira dourado
pinheiro folhagem vira verde-azulado escuro
cerejeira flores rosa pontilhadas nas copas
azaleia flores magenta pontilhadas nas copas
romãzeira poucos frutos alaranjados na parte de baixo das copas

Cada espécie tem duas versões da cor. Não é capricho: no tema claro as copas são verde-escuras, então flores precisam ser claras para aparecer; no escuro as copas são verde-sálvia claras e as flores precisam ser saturadas. As espécies TINT fazem o caminho oposto — a cor tinge a copa inteira, então precisa contrastar com o fundo da tela, não com o verde. A escuridão é lida de MaterialTheme.colorScheme.background.luminance(), e não do tema do sistema, para acertar mesmo quando o usuário força um tema contra ele.

O musgo da base fica sempre verde em qualquer espécie — musgo não é a árvore.

As transições usam smoothstep entre limiares para que galho e copa surjam crescendo em vez de pipocar na tela. O fator é quantizado em 300 passos, senão a árvore se redesenharia 15 vezes por segundo sem nenhuma mudança visível.

Como o tempo é contado

Não existe loop contando segundos, nem serviço em primeiro plano. Cada cronômetro guarda apenas dois números: quanto tempo já acumulou e o instante (epoch) em que a sessão atual começou. O tempo decorrido é sempre derivado do relógio do sistema, então nada se perde se o Android matar o processo.

Na barra de status, quem desenha o tempo subindo é o próprio Android — a notificação usa setUsesChronometer com o when no passado. O app só reposta a notificação quando o estado muda (play, pause, zerar, renomear, boot).

O estado vive num JSON em filesDir/stopwatches.json, reescrito de forma atômica a cada mudança.

Compilando

A máquina já está preparada:

ferramenta versão onde
Microsoft OpenJDK 17.0.20 C:\Program Files\Microsoft\jdk-17.0.20.8-hotspot
Android SDK (platform 35, build-tools 35.0.0, platform-tools) %LOCALAPPDATA%\Android\Sdk
Gradle 8.10.2 wrapper no projeto + cópia em %LOCALAPPDATA%\Gradle

Para recompilar:

/Cronometro && ./gradlew assembleDebug

O APK sai em app/build/outputs/apk/debug/app-debug.apk.

Instalando no celular

Ative Opções do desenvolvedor (Ajustes → Sobre o telefone → toque 7× em "Número da versão") e ligue a Depuração USB. Com o cabo conectado:

"$LOCALAPPDATA/Android/Sdk/platform-tools/adb.exe" install -r app/build/outputs/apk/debug/app-debug.apk

Publicando uma versão

O build de release é assinado com um keystore que não vive no repositório. O Gradle lê o caminho e as senhas de ~/.gradle/gradle.properties; sem essas chaves o assembleRelease ainda roda, só sai sem assinatura.

Uma vez na vida, gere o keystore (escolha uma senha e guarde-a junto do arquivo — perder qualquer um dos dois significa nunca mais conseguir atualizar o app por cima):

keytool -genkeypair -v -keystore ~/keys/bonsai.jks -alias bonsai -keyalg RSA -keysize 4096 -validity 10950

Depois acrescente ao ~/.gradle/gradle.properties (nunca ao do projeto):

BONSAI_KEYSTORE=C:/Users/<voce>/keys/bonsai.jks
BONSAI_KEYSTORE_PASSWORD=<sua senha>
BONSAI_KEY_ALIAS=bonsai
BONSAI_KEY_PASSWORD=<sua senha>

A partir daí, publicar uma versão é:

./gradlew assembleRelease && gh release create v1.0 app/build/outputs/apk/release/app-release.apk --title "Bonsai 1.0" --notes "Primeira versão."

Guarde o bonsai.jks como você guarda senhas. O Android identifica um app pela assinatura: se um dia você compilar com outro keystore, o celular vai recusar a atualização e a única saída será desinstalar — o que apaga todos os cronômetros acumulados.

Android Studio (opcional)

O projeto abre normalmente no Android Studio via Open nesta pasta — útil para o preview do Compose e o inspetor de layout. Não é necessário para compilar.

local.properties aponta o sdk.dir desta máquina e está no .gitignore, como deve ser.

Estrutura

app/src/main/java/com/bonsai/cronometro/
  BonsaiApp.kt              observa o repositório e mantém a barra de status em dia
  MainActivity.kt
  data/
    Stopwatch.kt            modelo: acumulado + instante de início
    StopwatchRepository.kt  singleton de processo, persistência em JSON
    ThemePreference.kt      claro / escuro / seguir o sistema
  notify/
    Notifications.kt        montagem e sincronização das notificações
    StopwatchActionReceiver.kt  botões Pausar/Zerar da notificação
    BootReceiver.kt         reposta as notificações depois de reiniciar
  ui/
    HomeScreen.kt           lista, diálogos, permissão de notificação, tick da tela
    StopwatchCard.kt        o card de cada cronômetro
    BonsaiArt.kt            o bonsai desenhado em Canvas, em função do crescimento
    Growth.kt               a curva logarítmica e os nove estágios
    Species.kt              as seis espécies e como a cor delas emerge
    Watering.kt             a rega ao segurar o dedo
    Glyphs.kt               play, pause, folha, sol, lua, + e × desenhados à mão
    Theme.kt                paleta e tipografia
  util/TimeFormat.kt

Licença

Apache 2.0. Use, modifique e redistribua à vontade, inclusive comercialmente, mantendo o aviso de copyright e declarando o que mudou.

Requisitos

  • Android 8.0 (API 26) ou superior.
  • No Android 13+ o app pede permissão de notificação na primeira vez que você liga a folha. Negar não quebra nada — o cronômetro continua correndo, só não aparece na barra.

About

Cronômetro para Android, focado em minimalismo e design inspirado pelo meu pai :)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages