Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
59ec8a1
chore: set up project structure
leanite May 8, 2026
30b0a95
chore(md): add LEARNING.md
leanite May 9, 2026
59c57bc
chore(gradle): add Koin DI dependencies
leanite May 9, 2026
2451930
chore(gradle): add core kotlinx libraries
leanite May 9, 2026
2fc8461
chore(gradle): add Ktor Client with platform engines
leanite May 9, 2026
14a128f
chore(gradle): add SQLDelight with platform drivers
leanite May 9, 2026
c622c45
chore(gradle): declare database creation
leanite May 9, 2026
63370d9
chore(md): update Q&A model in LEARNING.md
leanite May 9, 2026
ec041ff
chore(gradle): add Compose Navigation Multiplatform
leanite May 9, 2026
d518e13
chore(gradle): add Sentry KMP and BuildKonfig for DSN config
leanite May 9, 2026
0779196
chore(gradle): add multiplatform test stack
leanite May 9, 2026
c4b81d8
build(gradle): set JAVA_HOME for Xcode builds
leanite May 9, 2026
606f3d9
chore: remove KMP template demo files
leanite May 9, 2026
d46a1aa
chore: create project package layout
leanite May 10, 2026
18fd2b7
feat(android): add native splash screen with Dynamox brand color
leanite May 10, 2026
ed734f4
feat(ios): add native splash screen with Dynamox brand color
leanite May 10, 2026
7549d00
feat(splash): implement compose splash with Dynaquiz theme
leanite May 10, 2026
9298699
chore(md): update LEARNING.md
leanite May 10, 2026
efb5f4e
feat(core/domain): add main domain models (Player, Question, AppError…
leanite May 10, 2026
2a72bfc
chore(ios): add iPhone Air 17 device support
leanite May 11, 2026
a437037
feat(network): add Ktor HttpClient with platform engines
leanite May 11, 2026
9866af1
feat(core/data): add first end-to-end API call
leanite May 11, 2026
abfa81f
feat(core/di): add Koin
leanite May 11, 2026
ca95cf8
feat(core/data): add SQLDelight schema
leanite May 11, 2026
fbe60ce
feat(core/data/database): wire SQLDelight driver
leanite May 11, 2026
b51bcfb
feat(core): add Player data layer
leanite May 11, 2026
0f31c15
feat(core): add User data layer
leanite May 11, 2026
08e9305
feat(home): add HomeContract and HomeViewModel
leanite May 11, 2026
dc59c9f
feat(home): add initial Home screen and host
leanite May 12, 2026
8c5278a
feat(core): add navigation (splash - home)
leanite May 12, 2026
660c624
feat(splash): add Splash Screen animation
leanite May 12, 2026
94755e0
feat: add animation between Splash and Home
leanite May 12, 2026
e9e60b8
feat(home): add first Home ui version
leanite May 12, 2026
38e88c9
feat(home): add new game style Home UI
leanite May 13, 2026
83414db
feat(core): add simple sprite animation
leanite May 13, 2026
f8421af
feat(difficulty): add difficulty mode use cases
leanite May 14, 2026
0cfc0ec
refactor: unify app top bar as DynaquizTopBar
leanite May 14, 2026
0ece304
feat(difficulty): add first difficulty ui flow
leanite May 14, 2026
12fee55
feat(difficulty): update difficulty ui with animations
leanite May 14, 2026
b1c27cb
feat(home): update home ui animation to match the chosen difficulty
leanite May 14, 2026
85ff1d5
feat(quiz): add data flow foundation
leanite May 14, 2026
de32882
feat(quiz): add ui first version
leanite May 14, 2026
43ea400
fix: remove unnecessary GameBackground recomposition
leanite May 14, 2026
021bb8e
feat(quiz): add mascor timer animation
leanite May 15, 2026
b94b4ab
feat(quiz): update countdown animation
leanite May 15, 2026
26c24d9
feat(quiz): update exit dialog ui and back button/gesture support
leanite May 15, 2026
8771ca7
feat(quiz): add prefetch question strategy
leanite May 15, 2026
07acdd6
feat(ranking): add data flow foundation
leanite May 15, 2026
9b2ada5
feat(ranking): add first ranking ui
leanite May 15, 2026
f8af104
feat(ranking): update ranking ui
leanite May 15, 2026
7606570
feat(ranking): add ranking structure and first ui
leanite May 15, 2026
edaf2a6
fix(home): use trim() to prevent blank spaces on name
leanite May 15, 2026
42675c0
feat: change status bar color to night mode
leanite May 15, 2026
2f40298
fix: screens without GameBackground in layour parent
leanite May 15, 2026
149c64b
feat(splash): add silent warmup request to wake up the server and try…
leanite May 15, 2026
0806be5
chore: add missing Preview to Composable
leanite May 17, 2026
2c36836
test(unit): add missing unit tests
leanite May 17, 2026
b06314d
test(ui): share Compose UI components tests across Android and iOS vi…
leanite May 17, 2026
cfe77af
test(ui): add Compose UI tests for all 5 screens in commonTest
leanite May 17, 2026
7035d46
chore: add ktlint with Compose config to detect inconsistency in the …
leanite May 17, 2026
d848d2e
feat: add lock in portrait orientation mode (android/iOS)
leanite May 17, 2026
0e20d50
perf(compose): declare domain models as stable via compose-stability.…
leanite May 17, 2026
6e1dff2
fix: warm up SQLite driver off the main thread during splash screen
leanite May 17, 2026
ca21f1a
feat(android): add app icon
leanite May 17, 2026
304c424
feat(ios): add app icon
leanite May 17, 2026
1ef800d
refactor: remove unused Player field createdAt and stale TODO comments
leanite May 17, 2026
f950aa7
refactor(di): split CoreModule into core/data/domain modules
leanite May 18, 2026
a5ae178
chore: update README
leanite May 18, 2026
f0cb9b9
refactor(domain): extract QuizSetup and QuizPerformance from session …
leanite May 20, 2026
d8fe2a3
refactor(quiz): extract QuizTimerController from QuizViewModel
leanite May 20, 2026
662ceb2
refactor(quiz): replace imperative prefetch with reactive Flow
leanite May 21, 2026
082d698
refactor(tests): improve dispatcher usage and reduce Compose UI test …
leanite May 21, 2026
ac7a720
refactor(ui): simplify SimpleSpriteAnimator removing unnecessary Cros…
leanite May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions dynaquiz/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
*.iml
.kotlin
.gradle
.editorconfig
**/build/
xcuserdata
!src/**/build/
local.properties
.idea
.DS_Store
captures
.externalNativeBuild
.cxx
*.xcodeproj/*
!*.xcodeproj/project.pbxproj
!*.xcodeproj/xcshareddata/
!*.xcodeproj/project.xcworkspace/
!*.xcworkspace/contents.xcworkspacedata
**/xcshareddata/WorkspaceSettings.xcsettings
node_modules/
ios-build.log
28 changes: 28 additions & 0 deletions dynaquiz/DECISIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Uso de Channel em eventos one-shot

```kotlin
private val _events = Channel<SplashEvent>(Channel.BUFFERED)
val events = _events.receiveAsFlow()
```

Padrão recomendado pela comunidade e pelo próprio Google para garantir que o evento seja consumido exatamente uma vez, mesmo com mudanças de configuração ou quando o app vai para background

## Intent iniciais em Screens

Não costumo deixar intents serem executadas por default em um `ViewModel`. Gosto de, explicitamente, chamar uma intent usando um `onIntent()` inicial para ajudar na visibilidade futura do código, tentando diminuir o número de "mágicas" que ocorrem no código.

## Apenas um PNG com resolução suficiente em `drawable`

Para facilitar a geração de resources, vou usar apenas um *.png com resolução suficiente para funcionar bem na aparesentação/uso da maioria dos dispositivos

## Uso de commonTest
Optei por usar commonTest tendo consciência do trade-off entre ter testes de UI rodando em nível unit e não instrumentado vs ter testes de UI rodando instrumentado apenas no Android. Minha ideia é ter validação de qualidade de forma automática também no iOS e apoiar qualquer verificação instrumentada utilizando testes manuais direto nos aparelhos Google Pixel 7 Pro e iPhone Air 17.

## Detekt fora do projeto
A compatibilidade do detekt com Kotlin 2.x só veio nas versões alpha. As versões estáveis não possuem esse suporte, então opto por deixar de fora essa lib e manter apenas o ktlint.

## Usar portrait no AndroidManifest
Decidi manter a definição de portrait na MainActivity pelo AndroidManifest mesmo com o warning novo do Android Studio/Lint indicando que travar orientação dessa forma deixou de ser a direção recomendada por causa das mudanças do Android 16 em telas grandes.

## Compose Stability
Mantive os modelos de domain livres de qualquer dependência de androidx (UI), removendo @Immutable da implementação. Decidi usar Compose Stability para que o Compose pule recomposição dos @Composables que recebem esses modelos diretamente fora de um UiState (Immutable), como RankingEntryCard, QuestionCard, FinalScorePanel, ChallengeModeOption e PlayingContent.
69 changes: 69 additions & 0 deletions dynaquiz/LEARNING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
## Q&A

Como se gerencia dependências no KMP?
> A forma de gerenciar dependências é a mesma. Cataloga em `gradle/libs.versions.toml` e declara o uso em `composeApp/build.gradle.kts`

Quem é darwin em `iosMain.dependencies { implementation(libs.ktor.client.darwin) }`?
> A lib Darwin usada pelo Ktor no iOS é o mecanismo nativo de cliente HTTP do Kotlin/Native que utiliza a infraestrutura de rede da Apple, especificamente a NSURLSession

Qual a lib que monitora e reporta crashes no KMP?
> Sentry KMP

Onde coloco os resources do projeto Android?
> androidMain/res

## TBD

**Splash nativa no Android:**
- Lib androidx-core-splashscreen
- Criar theme
- Substituir theme na Activity launcher
- `installSplashScreen()` antes de `super.onCreate()` na Activity launcher

**Modificadores `expect` e `actual`:**
Expect: declara assinatura (nome, params, retorno) sem corpo. Cada source set de plataforma é **OBRIGADO** a fornecer um actual correspondente, senão o build falha. Funciona como um "contrato" multiplatforma.

Actual: repete a assinatura do expect (precisa bater exatamente — params, retorno, modificadores) e fornece o corpo. Manter o mesmo nome dos arquivo nos source sets facilita pra IDE encontrar, mas não é obrigatório.

**SQLDelight e Adapters**
O SQLDelight possui uma interface `ColumnAdapter` para adaptar valores de objetos para valores de entidades do BD. Você implementa essa interface (como em `ChallengeModeAdapter`) e ao declarar um campo de uma tabela com AS (como em `QuizSession.sq`), a Entity correspondente será gerada com uma classe interna `Adapter` pronta para receber o código preparado para converter os dois tipos.

**Res e strings**
Para padronizar strings e não usar hardcoded nos `@Composable`, usar `composeResources/values/strings.xml` e importar `Res` para acessar `Res.string.<nome>`.
A quantidade de imports é terrível, então provavelmente eu usarei uma classe wrapper.

No caso da configuração do client http:
> Compose app
```kotlin
internal expect fun httpClientEngine(): HttpClientEngine
```

> Android app
```kotlin
internal actual fun httpClientEngine(): HttpClientEngine = OkHttp.create()
```

> iOS app
```kotlin
internal actual fun httpClientEngine(): HttpClientEngine = Darwin.create()
```

## iOS

O gerenciamento de "resources" (chamados Assets) é feito em `Assets.xcassets`

Info.plist é um arquivo de metadados configurações fundamentais de um aplicativo, funcionando como um dicionário de pares chave-valor

A cor de background da ViewController launcher tem seu próprio asset em`LaunchBackground.colorset/` e seu conteúdo em `Content.json`

Tive que, explicitamente, adicionar a `libsqlite3` pelo XCode em `iosApp > TARGETS > General > Frameworks, Libraries, and Embedded Content` para poder compilar o projeto no iOS.

Para travar a orientação em portrair, ir pelo XCode em target > General > Deployment Info > seção Device Orientation e desmarcar as opções alternativas a portrair no iPhone

Aparentemente, existe um problema com a primeira interação com um TextField no seu primeiro após uma instalação fresh ao app. O iOS bloqueia a main thread por um tempo enquanto inicializa serviços per-app do subsistema de texto. Esses caches são mantidos em diretórios privados do app dentro de ~/Library/Keyboard/ no sandbox, então sobrevivem ao kill do processo (não reproduz após swipe-up no app switcher) mas são apagados em uma reinstalação.

## Aprendizagem futura:

1. Como se comporta um projeto multi módulo no KMP?
2. É possível fazer `build-logic`/convention plugins em arquitetura multi módulo no KMP?
3. O que acontece com o app quando muda de orientação no KMP/iOS?
Loading