From 7af2d86a171183ae75384db83d1d1401018a661a Mon Sep 17 00:00:00 2001
From: Sidiney Rodrigues <4397607+sidineyr@users.noreply.github.com>
Date: Wed, 26 Aug 2026 11:45:31 -0300
Subject: [PATCH 1/3] feat: start SOMENTE Android music learning app
---
.github/workflows/somente-android.yml | 24 ++++
SomenteAndroid/PLAY_STORE.md | 31 +++++
SomenteAndroid/PRIVACY.md | 9 ++
SomenteAndroid/README.md | 37 ++++++
SomenteAndroid/app/build.gradle.kts | 41 ++++++
SomenteAndroid/app/proguard-rules.pro | 1 +
.../app/src/main/AndroidManifest.xml | 24 ++++
.../br/com/sidineyr/somente/AdController.kt | 29 +++++
.../java/br/com/sidineyr/somente/AdPolicy.kt | 23 ++++
.../br/com/sidineyr/somente/Curriculum.kt | 25 ++++
.../br/com/sidineyr/somente/MainActivity.kt | 121 ++++++++++++++++++
.../br/com/sidineyr/somente/TonePlayer.kt | 46 +++++++
.../app/src/main/res/values/styles.xml | 10 ++
.../br/com/sidineyr/somente/AdPolicyTest.kt | 24 ++++
.../br/com/sidineyr/somente/CurriculumTest.kt | 17 +++
SomenteAndroid/build.gradle.kts | 4 +
SomenteAndroid/gradle.properties | 3 +
SomenteAndroid/settings.gradle.kts | 7 +
18 files changed, 476 insertions(+)
create mode 100644 .github/workflows/somente-android.yml
create mode 100644 SomenteAndroid/PLAY_STORE.md
create mode 100644 SomenteAndroid/PRIVACY.md
create mode 100644 SomenteAndroid/README.md
create mode 100644 SomenteAndroid/app/build.gradle.kts
create mode 100644 SomenteAndroid/app/proguard-rules.pro
create mode 100644 SomenteAndroid/app/src/main/AndroidManifest.xml
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdController.kt
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/TonePlayer.kt
create mode 100644 SomenteAndroid/app/src/main/res/values/styles.xml
create mode 100644 SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/AdPolicyTest.kt
create mode 100644 SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/CurriculumTest.kt
create mode 100644 SomenteAndroid/build.gradle.kts
create mode 100644 SomenteAndroid/gradle.properties
create mode 100644 SomenteAndroid/settings.gradle.kts
diff --git a/.github/workflows/somente-android.yml b/.github/workflows/somente-android.yml
new file mode 100644
index 0000000..0801f7f
--- /dev/null
+++ b/.github/workflows/somente-android.yml
@@ -0,0 +1,24 @@
+name: SOMENTE Android
+
+on:
+ push:
+ paths: ["SomenteAndroid/**", ".github/workflows/somente-android.yml"]
+ pull_request:
+ paths: ["SomenteAndroid/**", ".github/workflows/somente-android.yml"]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: SomenteAndroid
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "17"
+ - uses: gradle/actions/setup-gradle@v4
+ with:
+ gradle-version: "8.14.3"
+ - run: gradle testDebugUnitTest lintDebug assembleDebug --stacktrace
diff --git a/SomenteAndroid/PLAY_STORE.md b/SomenteAndroid/PLAY_STORE.md
new file mode 100644
index 0000000..fa02571
--- /dev/null
+++ b/SomenteAndroid/PLAY_STORE.md
@@ -0,0 +1,31 @@
+# Preparação para Google Play
+
+## Nome
+
+SOMENTE — aprenda música ouvindo
+
+## Descrição curta
+
+Treine ouvido, ritmo e notas em um percurso musical humano e gradual.
+
+## Descrição completa
+
+SOMENTE une som e mente para ensinar música do jeito como ela começa: ouvindo. Você aprende a distinguir som e silêncio, comparar sons, reconhecer grave e agudo, perceber intensidade, acompanhar pulsos, imitar ritmos e então conhecer notas e melodias.
+
+Cada atividade é breve, repetível e acolhe o erro como parte da aprendizagem. Não é necessário saber ler partitura nem possuir instrumento. Os sons são criados no próprio aparelho e o progresso fica salvo localmente.
+
+O projeto nasce de uma perspectiva pedagógica: conceito musical vem depois da experiência sonora. A publicidade, quando ativada, aparece somente em transições naturais após aulas concluídas, com limites rígidos para preservar atenção e aprendizagem.
+
+## Checklist antes de enviar
+
+- [ ] confirmar disponibilidade do nome e do pacote;
+- [ ] criar conta/registro no Play Console e verificar identidade;
+- [ ] gerar chave de upload e AAB assinado;
+- [ ] configurar consentimento e IDs AdMob de produção;
+- [ ] declarar corretamente público-alvo, anúncios e segurança de dados;
+- [ ] publicar política de privacidade em URL pública;
+- [ ] criar ícone 512 × 512, feature graphic 1024 × 500 e capturas;
+- [ ] concluir classificação indicativa;
+- [ ] executar testes internos e fechados exigidos para a conta;
+- [ ] corrigir relatório de pré-lançamento;
+- [ ] enviar primeiro para produção gradual.
diff --git a/SomenteAndroid/PRIVACY.md b/SomenteAndroid/PRIVACY.md
new file mode 100644
index 0000000..bcd53e2
--- /dev/null
+++ b/SomenteAndroid/PRIVACY.md
@@ -0,0 +1,9 @@
+# Política de Privacidade — SOMENTE (rascunho)
+
+Última atualização: 26 de agosto de 2026.
+
+O SOMENTE salva no próprio dispositivo apenas o progresso das aulas e os controles de frequência de publicidade. A versão básica não exige conta e não solicita microfone, câmera, contatos, localização ou arquivos pessoais.
+
+Se a publicidade for ativada em uma versão pública, o Google Mobile Ads poderá processar identificadores e dados técnicos conforme o consentimento, a idade declarada e as configurações do aparelho. O aplicativo deverá oferecer anúncios não personalizados quando exigido e respeitar a recusa do usuário. A política definitiva deverá identificar o responsável pelo tratamento, meio de contato, base legal, retenção, direitos do titular e links para os fornecedores utilizados.
+
+Este texto é um rascunho técnico e não deve ser publicado como política final sem preencher os dados do desenvolvedor e conferir as declarações da versão efetivamente distribuída.
diff --git a/SomenteAndroid/README.md b/SomenteAndroid/README.md
new file mode 100644
index 0000000..534020a
--- /dev/null
+++ b/SomenteAndroid/README.md
@@ -0,0 +1,37 @@
+# SOMENTE Android
+
+**SOMENTE** (som + mente) é um aplicativo pedagógico para aprender música pela escuta, pela ação e pela criação. O primeiro percurso evita começar pela memorização abstrata: antes de apresentar nomes de notas, trabalha som, silêncio, comparação, altura, intensidade, pulso e ritmo.
+
+## Percurso v0.1
+
+1. som e silêncio;
+2. sons iguais e diferentes;
+3. grave e agudo;
+4. forte e fraco;
+5. pulso regular;
+6. imitação rítmica;
+7. primeira nota — Dó;
+8. direção melódica.
+
+O aplicativo sintetiza os sons no próprio aparelho e salva o progresso localmente. Não exige conta, microfone, localização ou acesso aos arquivos.
+
+## Publicidade com limite pedagógico
+
+O build público mantém anúncios desativados por padrão. Quando houver IDs de produção e consentimento configurados, a política permite intersticial apenas após uma aula concluída, nunca na primeira sessão, no máximo a cada quatro conclusões, com 12 minutos de intervalo e limite de três por dia. Áudio e exercício em andamento bloqueiam qualquer exibição.
+
+Durante desenvolvimento, use somente os IDs de teste já incluídos. Antes do lançamento, implemente o fluxo de consentimento UMP, defina corretamente a faixa etária e substitua os IDs por configuração privada de release. Para público infantil ou misto, use exclusivamente anúncios não personalizados e SDKs certificados para famílias.
+
+## Executar
+
+Abra `SomenteAndroid` no Android Studio, aguarde a sincronização e execute `app` em Android 8.0 ou superior.
+
+```bash
+./gradlew test
+./gradlew bundleRelease
+```
+
+O pacote planejado é `br.com.sidineyr.somente`, alvo Android 16/API 36 e versão inicial `0.1.0`.
+
+## Limites da v0.1
+
+Esta versão é um alicerce funcional, ainda não um curso completo. Antes da produção serão necessários: revisão por usuários, validação completa com TalkBack, consentimento, assinatura do AAB, ícone, capturas de tela, ficha de segurança de dados e teste fechado exigido pela conta Play Console.
diff --git a/SomenteAndroid/app/build.gradle.kts b/SomenteAndroid/app/build.gradle.kts
new file mode 100644
index 0000000..1df7a9d
--- /dev/null
+++ b/SomenteAndroid/app/build.gradle.kts
@@ -0,0 +1,41 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "br.com.sidineyr.somente"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "br.com.sidineyr.somente"
+ minSdk = 26
+ targetSdk = 36
+ versionCode = 1
+ versionName = "0.1.0"
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ buildConfigField("boolean", "ADS_ENABLED", "false")
+ buildConfigField("String", "ADMOB_INTERSTITIAL_ID", "\"ca-app-pub-3940256099942544/1033173712\"")
+ }
+
+ buildFeatures { buildConfig = true }
+ buildTypes {
+ release {
+ isMinifyEnabled = true
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions { jvmTarget = "17" }
+}
+
+dependencies {
+ implementation("androidx.core:core-ktx:1.16.0")
+ implementation("androidx.appcompat:appcompat:1.7.1")
+ implementation("com.google.android.material:material:1.12.0")
+ implementation("com.google.android.gms:play-services-ads:24.4.0")
+ testImplementation("junit:junit:4.13.2")
+}
diff --git a/SomenteAndroid/app/proguard-rules.pro b/SomenteAndroid/app/proguard-rules.pro
new file mode 100644
index 0000000..547d2eb
--- /dev/null
+++ b/SomenteAndroid/app/proguard-rules.pro
@@ -0,0 +1 @@
+# Rules are intentionally minimal; Google Mobile Ads publishes consumer rules.
diff --git a/SomenteAndroid/app/src/main/AndroidManifest.xml b/SomenteAndroid/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..fd51ed2
--- /dev/null
+++ b/SomenteAndroid/app/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdController.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdController.kt
new file mode 100644
index 0000000..54cb861
--- /dev/null
+++ b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdController.kt
@@ -0,0 +1,29 @@
+package br.com.sidineyr.somente
+
+import android.app.Activity
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.FullScreenContentCallback
+import com.google.android.gms.ads.interstitial.InterstitialAd
+import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
+
+class AdController(private val activity: Activity) {
+ private var loaded: InterstitialAd? = null
+
+ fun preload() {
+ if (!BuildConfig.ADS_ENABLED || loaded != null) return
+ InterstitialAd.load(activity, BuildConfig.ADMOB_INTERSTITIAL_ID, AdRequest.Builder().build(),
+ object : InterstitialAdLoadCallback() {
+ override fun onAdLoaded(ad: InterstitialAd) { loaded = ad }
+ })
+ }
+
+ fun showIfReady(onClosed: () -> Unit): Boolean {
+ val ad = loaded ?: return false
+ loaded = null
+ ad.fullScreenContentCallback = object : FullScreenContentCallback() {
+ override fun onAdDismissedFullScreenContent() { onClosed(); preload() }
+ }
+ ad.show(activity)
+ return true
+ }
+}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt
new file mode 100644
index 0000000..3adac27
--- /dev/null
+++ b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt
@@ -0,0 +1,23 @@
+package br.com.sidineyr.somente
+
+data class AdState(
+ val completedLessons: Int,
+ val adsToday: Int,
+ val lastAdAtMs: Long,
+ val isFirstSession: Boolean,
+ val audioPlaying: Boolean
+)
+
+object AdPolicy {
+ const val LESSON_INTERVAL = 4
+ const val MIN_INTERVAL_MS = 12 * 60 * 1000L
+ const val DAILY_LIMIT = 3
+
+ fun canShowAfterLesson(state: AdState, nowMs: Long): Boolean =
+ !state.isFirstSession &&
+ !state.audioPlaying &&
+ state.completedLessons > 0 &&
+ state.completedLessons % LESSON_INTERVAL == 0 &&
+ state.adsToday < DAILY_LIMIT &&
+ nowMs - state.lastAdAtMs >= MIN_INTERVAL_MS
+}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt
new file mode 100644
index 0000000..8dfc5ae
--- /dev/null
+++ b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt
@@ -0,0 +1,25 @@
+package br.com.sidineyr.somente
+
+enum class ExerciseKind { SOUND_OR_SILENCE, SAME_OR_DIFFERENT, HIGH_OR_LOW, LOUD_OR_SOFT, PULSE, RHYTHM, NOTE, MELODY }
+
+data class Lesson(
+ val id: String,
+ val title: String,
+ val objective: String,
+ val prompt: String,
+ val kind: ExerciseKind,
+ val choices: List
+)
+
+object Curriculum {
+ val lessons = listOf(
+ Lesson("escuta-1", "Som e silêncio", "Perceber quando existe vibração audível.", "Você ouviu um som?", ExerciseKind.SOUND_OR_SILENCE, listOf("Som", "Silêncio")),
+ Lesson("escuta-2", "Igual ou diferente?", "Comparar dois eventos sonoros sem exigir nomes técnicos.", "Os dois sons foram iguais?", ExerciseKind.SAME_OR_DIFFERENT, listOf("Iguais", "Diferentes")),
+ Lesson("altura-1", "Grave e agudo", "Reconhecer altura antes de conhecer nomes de notas.", "O som foi grave ou agudo?", ExerciseKind.HIGH_OR_LOW, listOf("Grave", "Agudo")),
+ Lesson("intensidade-1", "Forte e fraco", "Distinguir intensidade de altura.", "O som foi forte ou fraco?", ExerciseKind.LOUD_OR_SOFT, listOf("Forte", "Fraco")),
+ Lesson("ritmo-1", "Encontre o pulso", "Sentir uma sequência regular de batidas.", "Toque junto com o pulso quatro vezes.", ExerciseKind.PULSE, listOf("Marcar pulso")),
+ Lesson("ritmo-2", "Imite o ritmo", "Perceber que ritmo combina durações e pausas.", "Ouça e repita o desenho rítmico.", ExerciseKind.RHYTHM, listOf("Repetir ritmo")),
+ Lesson("notas-1", "Uma nota tem nome", "Relacionar uma altura já percebida ao nome Dó.", "Esta nota é Dó. Ouça e reconheça.", ExerciseKind.NOTE, listOf("Ouvir Dó", "Reconheci")),
+ Lesson("melodia-1", "Notas em caminho", "Compreender melodia como sequência intencional de alturas e durações.", "A sequência subiu ou desceu?", ExerciseKind.MELODY, listOf("Subiu", "Desceu"))
+ )
+}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt
new file mode 100644
index 0000000..637b31f
--- /dev/null
+++ b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt
@@ -0,0 +1,121 @@
+package br.com.sidineyr.somente
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.ScrollView
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
+import kotlin.random.Random
+
+class MainActivity : AppCompatActivity() {
+ private val player = TonePlayer()
+ private lateinit var ads: AdController
+ private lateinit var root: LinearLayout
+ private val prefs by lazy { getSharedPreferences("progress", MODE_PRIVATE) }
+ private var lessonIndex = 0
+ private var expected = ""
+ private var firstSession = true
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ads = AdController(this)
+ firstSession = !prefs.getBoolean("has_finished_session", false)
+ lessonIndex = prefs.getInt("lesson", 0).coerceIn(0, Curriculum.lessons.lastIndex)
+ root = LinearLayout(this).apply {
+ orientation = LinearLayout.VERTICAL
+ gravity = Gravity.CENTER_HORIZONTAL
+ setPadding(40, 52, 40, 40)
+ setBackgroundColor(Color.rgb(23, 21, 42))
+ }
+ setContentView(ScrollView(this).apply { addView(root) })
+ showLesson()
+ ads.preload()
+ }
+
+ private fun showLesson(message: String? = null) {
+ root.removeAllViews()
+ val lesson = Curriculum.lessons[lessonIndex]
+ root.addView(text("SOMENTE", 30, Color.rgb(247, 211, 91)))
+ root.addView(text("Som + mente: aprender música escutando", 15, Color.LTGRAY))
+ root.addView(ProgressBar(this, null, android.R.attr.progressBarStyleHorizontal).apply {
+ max = Curriculum.lessons.size
+ progress = lessonIndex + 1
+ layoutParams = LinearLayout.LayoutParams(-1, 18).also { it.setMargins(0, 36, 0, 36) }
+ })
+ root.addView(text("${lessonIndex + 1}. ${lesson.title}", 26, Color.WHITE))
+ root.addView(text(lesson.objective, 17, Color.LTGRAY).withMargins(0, 16, 0, 28))
+ root.addView(text(lesson.prompt, 20, Color.WHITE).withMargins(0, 8, 0, 28))
+ button("▶ Ouvir exemplo") { presentExercise(lesson) }
+ lesson.choices.forEach { choice -> button(choice) { answer(lesson, choice) } }
+ message?.let { root.addView(text(it, 17, Color.rgb(247, 211, 91)).withMargins(0, 24, 0, 0)) }
+ root.addView(text("Sem pressa. Você pode ouvir quantas vezes precisar.", 14, Color.GRAY).withMargins(0, 38, 0, 0))
+ }
+
+ private fun presentExercise(lesson: Lesson) {
+ if (player.isPlaying()) return
+ val (notes, answer, volume) = when (lesson.kind) {
+ ExerciseKind.SOUND_OR_SILENCE -> if (Random.nextBoolean()) Triple(listOf(440.0), "Som", .42) else Triple(emptyList(), "Silêncio", .0)
+ ExerciseKind.SAME_OR_DIFFERENT -> if (Random.nextBoolean()) Triple(listOf(330.0, 330.0), "Iguais", .42) else Triple(listOf(330.0, 494.0), "Diferentes", .42)
+ ExerciseKind.HIGH_OR_LOW -> if (Random.nextBoolean()) Triple(listOf(220.0), "Grave", .45) else Triple(listOf(880.0), "Agudo", .40)
+ ExerciseKind.LOUD_OR_SOFT -> if (Random.nextBoolean()) Triple(listOf(440.0), "Forte", .72) else Triple(listOf(440.0), "Fraco", .18)
+ ExerciseKind.PULSE -> Triple(listOf(440.0, 440.0, 440.0, 440.0), "Marcar pulso", .42)
+ ExerciseKind.RHYTHM -> Triple(listOf(392.0, 392.0, 523.25), "Repetir ritmo", .42)
+ ExerciseKind.NOTE -> Triple(listOf(261.63), "Reconheci", .42)
+ ExerciseKind.MELODY -> if (Random.nextBoolean()) Triple(listOf(261.63, 329.63, 392.0), "Subiu", .42) else Triple(listOf(392.0, 329.63, 261.63), "Desceu", .42)
+ }
+ expected = answer
+ if (notes.isEmpty()) window.decorView.postDelayed({ expected = answer }, 650) else player.play(notes, volume)
+ }
+
+ private fun answer(lesson: Lesson, choice: String) {
+ if (expected.isBlank()) { showLesson("Primeiro ouça o exemplo."); return }
+ if (choice != expected && lesson.kind !in setOf(ExerciseKind.PULSE, ExerciseKind.RHYTHM, ExerciseKind.NOTE)) {
+ showLesson("Ainda não. Ouça novamente e compare — errar também treina o ouvido.")
+ return
+ }
+ val completed = prefs.getInt("completed", 0) + 1
+ val next = (lessonIndex + 1).coerceAtMost(Curriculum.lessons.lastIndex)
+ prefs.edit().putInt("completed", completed).putInt("lesson", next).apply()
+ lessonIndex = next
+ expected = ""
+ val now = System.currentTimeMillis()
+ val today = java.time.LocalDate.now().toString()
+ val adsToday = if (prefs.getString("ad_day", "") == today) prefs.getInt("ads_today", 0) else 0
+ if (adsToday == 0) prefs.edit().putString("ad_day", today).putInt("ads_today", 0).apply()
+ val state = AdState(completed, adsToday, prefs.getLong("last_ad", 0), firstSession, player.isPlaying())
+ if (AdPolicy.canShowAfterLesson(state, now) && ads.showIfReady {
+ prefs.edit().putInt("ads_today", state.adsToday + 1).putLong("last_ad", now).apply()
+ showLesson("Muito bem! Vamos ao próximo passo.")
+ }) return
+ showLesson("Muito bem! Vamos ao próximo passo.")
+ }
+
+ override fun onStop() {
+ prefs.edit().putBoolean("has_finished_session", true).apply()
+ super.onStop()
+ }
+
+ private fun button(label: String, action: () -> Unit) {
+ root.addView(Button(this).apply {
+ text = label
+ isAllCaps = false
+ textSize = 17f
+ setOnClickListener { action() }
+ layoutParams = LinearLayout.LayoutParams(-1, -2).also { it.setMargins(0, 8, 0, 8) }
+ })
+ }
+
+ private fun text(value: String, size: Int, color: Int) = TextView(this).apply {
+ text = value; textSize = size.toFloat(); setTextColor(color); gravity = Gravity.CENTER
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
+ }
+
+ private fun View.withMargins(l: Int, t: Int, r: Int, b: Int): View = apply {
+ layoutParams = LinearLayout.LayoutParams(-1, -2).also { it.setMargins(l, t, r, b) }
+ }
+}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/TonePlayer.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/TonePlayer.kt
new file mode 100644
index 0000000..78dcb83
--- /dev/null
+++ b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/TonePlayer.kt
@@ -0,0 +1,46 @@
+package br.com.sidineyr.somente
+
+import android.media.AudioAttributes
+import android.media.AudioFormat
+import android.media.AudioTrack
+import kotlin.concurrent.thread
+import kotlin.math.PI
+import kotlin.math.sin
+
+class TonePlayer {
+ @Volatile private var playing = false
+
+ fun play(frequencies: List, volume: Double = 0.45, noteMs: Int = 420, onDone: () -> Unit = {}) {
+ if (playing) return
+ playing = true
+ thread(name = "somente-tone") {
+ try {
+ frequencies.forEach { playOne(it, volume, noteMs) }
+ } finally {
+ playing = false
+ onDone()
+ }
+ }
+ }
+
+ fun isPlaying() = playing
+
+ private fun playOne(frequency: Double, volume: Double, durationMs: Int) {
+ val sampleRate = 44_100
+ val count = sampleRate * durationMs / 1000
+ val samples = ShortArray(count) { index ->
+ val fade = minOf(1.0, index / 500.0, (count - index) / 500.0)
+ (sin(2.0 * PI * index * frequency / sampleRate) * Short.MAX_VALUE * volume * fade).toInt().toShort()
+ }
+ val track = AudioTrack.Builder()
+ .setAudioAttributes(AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build())
+ .setAudioFormat(AudioFormat.Builder().setSampleRate(sampleRate).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build())
+ .setBufferSizeInBytes(samples.size * 2)
+ .setTransferMode(AudioTrack.MODE_STATIC)
+ .build()
+ track.write(samples, 0, samples.size)
+ track.play()
+ Thread.sleep(durationMs.toLong() + 30)
+ track.release()
+ }
+}
diff --git a/SomenteAndroid/app/src/main/res/values/styles.xml b/SomenteAndroid/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..ad70b14
--- /dev/null
+++ b/SomenteAndroid/app/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/AdPolicyTest.kt b/SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/AdPolicyTest.kt
new file mode 100644
index 0000000..b18c3ba
--- /dev/null
+++ b/SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/AdPolicyTest.kt
@@ -0,0 +1,24 @@
+package br.com.sidineyr.somente
+
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class AdPolicyTest {
+ private val now = 1_000_000L
+
+ @Test fun `never interrupts first session or audio`() {
+ assertFalse(AdPolicy.canShowAfterLesson(AdState(4, 0, 0, true, false), now))
+ assertFalse(AdPolicy.canShowAfterLesson(AdState(4, 0, 0, false, true), now))
+ }
+
+ @Test fun `only shows at fourth completion after cooldown`() {
+ assertFalse(AdPolicy.canShowAfterLesson(AdState(3, 0, 0, false, false), now))
+ assertTrue(AdPolicy.canShowAfterLesson(AdState(4, 0, 0, false, false), now))
+ }
+
+ @Test fun `respects cooldown and daily limit`() {
+ assertFalse(AdPolicy.canShowAfterLesson(AdState(8, 0, now - 1_000, false, false), now))
+ assertFalse(AdPolicy.canShowAfterLesson(AdState(8, 3, 0, false, false), now))
+ }
+}
diff --git a/SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/CurriculumTest.kt b/SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/CurriculumTest.kt
new file mode 100644
index 0000000..bcc9d3d
--- /dev/null
+++ b/SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/CurriculumTest.kt
@@ -0,0 +1,17 @@
+package br.com.sidineyr.somente
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class CurriculumTest {
+ @Test fun `listening precedes notation`() {
+ val kinds = Curriculum.lessons.map { it.kind }
+ assertTrue(kinds.indexOf(ExerciseKind.HIGH_OR_LOW) < kinds.indexOf(ExerciseKind.NOTE))
+ assertTrue(kinds.indexOf(ExerciseKind.PULSE) < kinds.indexOf(ExerciseKind.MELODY))
+ }
+
+ @Test fun `lesson ids are unique`() {
+ assertEquals(Curriculum.lessons.size, Curriculum.lessons.map { it.id }.toSet().size)
+ }
+}
diff --git a/SomenteAndroid/build.gradle.kts b/SomenteAndroid/build.gradle.kts
new file mode 100644
index 0000000..5530762
--- /dev/null
+++ b/SomenteAndroid/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ id("com.android.application") version "8.10.1" apply false
+ id("org.jetbrains.kotlin.android") version "2.1.20" apply false
+}
diff --git a/SomenteAndroid/gradle.properties b/SomenteAndroid/gradle.properties
new file mode 100644
index 0000000..e696167
--- /dev/null
+++ b/SomenteAndroid/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+android.useAndroidX=true
+kotlin.code.style=official
diff --git a/SomenteAndroid/settings.gradle.kts b/SomenteAndroid/settings.gradle.kts
new file mode 100644
index 0000000..5584c39
--- /dev/null
+++ b/SomenteAndroid/settings.gradle.kts
@@ -0,0 +1,7 @@
+pluginManagement { repositories { google(); mavenCentral(); gradlePluginPortal() } }
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories { google(); mavenCentral() }
+}
+rootProject.name = "SOMENTE"
+include(":app")
From be6f50c9b2dac180d4c2b22a5b53705f97611289 Mon Sep 17 00:00:00 2001
From: Sidiney Rodrigues <4397607+sidineyr@users.noreply.github.com>
Date: Wed, 26 Aug 2026 14:29:24 -0300
Subject: [PATCH 2/3] feat: make SOMENTE a usable music-learning MVP
---
.github/workflows/python-app.yml | 37 ++--
.github/workflows/somente-android.yml | 3 +
SomenteAndroid/PLAY_STORE.md | 23 +--
SomenteAndroid/PRIVACY.md | 8 +-
SomenteAndroid/README.md | 39 ++--
SomenteAndroid/app/build.gradle.kts | 4 -
.../app/src/main/AndroidManifest.xml | 6 -
.../br/com/sidineyr/somente/AdController.kt | 29 ---
.../java/br/com/sidineyr/somente/AdPolicy.kt | 23 ---
.../br/com/sidineyr/somente/Curriculum.kt | 25 ---
.../br/com/sidineyr/somente/MainActivity.kt | 190 +++++++++++-------
.../java/br/com/sidineyr/somente/Music.kt | 26 +++
.../java/br/com/sidineyr/somente/StaffView.kt | 44 ++++
.../br/com/sidineyr/somente/TonePlayer.kt | 23 +--
.../br/com/sidineyr/somente/AdPolicyTest.kt | 24 ---
.../br/com/sidineyr/somente/CurriculumTest.kt | 17 --
.../java/br/com/sidineyr/somente/MusicTest.kt | 24 +++
17 files changed, 261 insertions(+), 284 deletions(-)
delete mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdController.kt
delete mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt
delete mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Music.kt
create mode 100644 SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/StaffView.kt
delete mode 100644 SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/AdPolicyTest.kt
delete mode 100644 SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/CurriculumTest.kt
create mode 100644 SomenteAndroid/app/src/test/java/br/com/sidineyr/somente/MusicTest.kt
diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml
index 200872c..75ba08a 100644
--- a/.github/workflows/python-app.yml
+++ b/.github/workflows/python-app.yml
@@ -1,39 +1,28 @@
-# This workflow will install Python dependencies, run tests and lint with a single version of Python
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
-
+# Python checks are intentionally isolated from the native Android project.
name: Python application
on:
push:
branches: [ "master" ]
+ paths-ignore: ["SomenteAndroid/**", ".github/workflows/somente-android.yml"]
pull_request:
branches: [ "master" ]
+ paths-ignore: ["SomenteAndroid/**", ".github/workflows/somente-android.yml"]
permissions:
contents: read
jobs:
build:
-
runs-on: ubuntu-latest
-
steps:
- - uses: actions/checkout@v4
- - name: Set up Python 3.10
- uses: actions/setup-python@v3
- with:
- python-version: "3.10"
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install flake8 pytest
- if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- - name: Lint with flake8
- run: |
- # stop the build if there are Python syntax errors or undefined names
- flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
- # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- - name: Test with pytest
- run: |
- pytest
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+ - run: |
+ python -m pip install --upgrade pip
+ pip install flake8 pytest
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ - run: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+ - run: pytest
diff --git a/.github/workflows/somente-android.yml b/.github/workflows/somente-android.yml
index 0801f7f..a70c65f 100644
--- a/.github/workflows/somente-android.yml
+++ b/.github/workflows/somente-android.yml
@@ -6,6 +6,9 @@ on:
pull_request:
paths: ["SomenteAndroid/**", ".github/workflows/somente-android.yml"]
+permissions:
+ contents: read
+
jobs:
test:
runs-on: ubuntu-latest
diff --git a/SomenteAndroid/PLAY_STORE.md b/SomenteAndroid/PLAY_STORE.md
index fa02571..4317404 100644
--- a/SomenteAndroid/PLAY_STORE.md
+++ b/SomenteAndroid/PLAY_STORE.md
@@ -2,30 +2,25 @@
## Nome
-SOMENTE — aprenda música ouvindo
+SOMENTE — ouvir, ler e criar música
## Descrição curta
-Treine ouvido, ritmo e notas em um percurso musical humano e gradual.
+Treino auditivo, pauta sincronizada e criação de exercícios musicais.
## Descrição completa
-SOMENTE une som e mente para ensinar música do jeito como ela começa: ouvindo. Você aprende a distinguir som e silêncio, comparar sons, reconhecer grave e agudo, perceber intensidade, acompanhar pulsos, imitar ritmos e então conhecer notas e melodias.
+SOMENTE une som e mente em três experiências simples para iniciantes de qualquer idade. Ouça uma nota e tente reconhecê-la; acompanhe pequenas sequências na pauta enquanto elas tocam; ou crie um exercício de até 16 notas para estudar e usar em aula.
-Cada atividade é breve, repetível e acolhe o erro como parte da aprendizagem. Não é necessário saber ler partitura nem possuir instrumento. Os sons são criados no próprio aparelho e o progresso fica salvo localmente.
-
-O projeto nasce de uma perspectiva pedagógica: conceito musical vem depois da experiência sonora. A publicidade, quando ativada, aparece somente em transições naturais após aulas concluídas, com limites rígidos para preservar atenção e aprendizagem.
+Não é necessário saber ler partitura nem possuir instrumento. O aplicativo funciona offline, sem conta e sem anúncios. O progresso e o exercício criado ficam somente no aparelho.
## Checklist antes de enviar
- [ ] confirmar disponibilidade do nome e do pacote;
-- [ ] criar conta/registro no Play Console e verificar identidade;
- [ ] gerar chave de upload e AAB assinado;
-- [ ] configurar consentimento e IDs AdMob de produção;
-- [ ] declarar corretamente público-alvo, anúncios e segurança de dados;
-- [ ] publicar política de privacidade em URL pública;
+- [ ] identificar o responsável e publicar a política de privacidade;
+- [ ] declarar corretamente público-alvo e segurança de dados;
- [ ] criar ícone 512 × 512, feature graphic 1024 × 500 e capturas;
-- [ ] concluir classificação indicativa;
-- [ ] executar testes internos e fechados exigidos para a conta;
-- [ ] corrigir relatório de pré-lançamento;
-- [ ] enviar primeiro para produção gradual.
+- [ ] testar em telefone e tablet, com TalkBack e tamanhos de fonte maiores;
+- [ ] concluir classificação indicativa e teste fechado;
+- [ ] corrigir o relatório de pré-lançamento.
diff --git a/SomenteAndroid/PRIVACY.md b/SomenteAndroid/PRIVACY.md
index bcd53e2..310e67f 100644
--- a/SomenteAndroid/PRIVACY.md
+++ b/SomenteAndroid/PRIVACY.md
@@ -1,9 +1,9 @@
-# Política de Privacidade — SOMENTE (rascunho)
+# Política de Privacidade — SOMENTE v0.1
Última atualização: 26 de agosto de 2026.
-O SOMENTE salva no próprio dispositivo apenas o progresso das aulas e os controles de frequência de publicidade. A versão básica não exige conta e não solicita microfone, câmera, contatos, localização ou arquivos pessoais.
+O SOMENTE v0.1 funciona sem conta e sem conexão com a internet. Não contém publicidade nem ferramentas de análise e não solicita acesso ao microfone, câmera, contatos, localização, identificador de anúncios ou arquivos pessoais.
-Se a publicidade for ativada em uma versão pública, o Google Mobile Ads poderá processar identificadores e dados técnicos conforme o consentimento, a idade declarada e as configurações do aparelho. O aplicativo deverá oferecer anúncios não personalizados quando exigido e respeitar a recusa do usuário. A política definitiva deverá identificar o responsável pelo tratamento, meio de contato, base legal, retenção, direitos do titular e links para os fornecedores utilizados.
+O aplicativo armazena no próprio aparelho apenas a contagem de respostas corretas e uma sequência musical criada pelo usuário. Esses dados não são enviados ao desenvolvedor e podem ser apagados ao limpar os dados do aplicativo ou desinstalá-lo.
-Este texto é um rascunho técnico e não deve ser publicado como política final sem preencher os dados do desenvolvedor e conferir as declarações da versão efetivamente distribuída.
+Antes de publicação pública, esta política deverá incluir a identificação e o contato do responsável pelo aplicativo e corresponder exatamente ao pacote distribuído.
diff --git a/SomenteAndroid/README.md b/SomenteAndroid/README.md
index 534020a..6c833cb 100644
--- a/SomenteAndroid/README.md
+++ b/SomenteAndroid/README.md
@@ -1,37 +1,32 @@
# SOMENTE Android
-**SOMENTE** (som + mente) é um aplicativo pedagógico para aprender música pela escuta, pela ação e pela criação. O primeiro percurso evita começar pela memorização abstrata: antes de apresentar nomes de notas, trabalha som, silêncio, comparação, altura, intensidade, pulso e ritmo.
+**SOMENTE** (som + mente) é um laboratório pedagógico gratuito para iniciantes de qualquer idade. A experiência reúne escuta, leitura e criação sem exigir conta, internet, anúncios, microfone ou conhecimento prévio de partitura.
-## Percurso v0.1
+## Três modos de aula
-1. som e silêncio;
-2. sons iguais e diferentes;
-3. grave e agudo;
-4. forte e fraco;
-5. pulso regular;
-6. imitação rítmica;
-7. primeira nota — Dó;
-8. direção melódica.
+1. **Escutar e reconhecer** — o estudante ouve uma nota, escolhe seu nome e vê a resposta na pauta.
+2. **Ler e ouvir** — uma sequência curta é reproduzida enquanto a nota atual é destacada.
+3. **Criar exercício** — professor ou estudante monta até 16 notas, ouve o resultado e salva a atividade localmente.
-O aplicativo sintetiza os sons no próprio aparelho e salva o progresso localmente. Não exige conta, microfone, localização ou acesso aos arquivos.
-
-## Publicidade com limite pedagógico
-
-O build público mantém anúncios desativados por padrão. Quando houver IDs de produção e consentimento configurados, a política permite intersticial apenas após uma aula concluída, nunca na primeira sessão, no máximo a cada quatro conclusões, com 12 minutos de intervalo e limite de três por dia. Áudio e exercício em andamento bloqueiam qualquer exibição.
-
-Durante desenvolvimento, use somente os IDs de teste já incluídos. Antes do lançamento, implemente o fluxo de consentimento UMP, defina corretamente a faixa etária e substitua os IDs por configuração privada de release. Para público infantil ou misto, use exclusivamente anúncios não personalizados e SDKs certificados para famílias.
+O desenho se inspira na clareza de aplicativos de partitura para Android, mas segue uma progressão própria: ouvir → reconhecer → visualizar → criar. Os exemplos são originais e não incluem repertório protegido.
## Executar
-Abra `SomenteAndroid` no Android Studio, aguarde a sincronização e execute `app` em Android 8.0 ou superior.
+Abra a pasta `SomenteAndroid` no Android Studio, aguarde a sincronização do Gradle e execute o módulo `app` em Android 8.0 ou superior. Na linha de comando, com Gradle 8.14.3 instalado:
```bash
-./gradlew test
-./gradlew bundleRelease
+gradle testDebugUnitTest lintDebug assembleDebug
```
-O pacote planejado é `br.com.sidineyr.somente`, alvo Android 16/API 36 e versão inicial `0.1.0`.
+O pacote é `br.com.sidineyr.somente`, com alvo Android 16/API 36 e versão `0.1.0`.
+
+## Uso responsável
+
+- use volume confortável, especialmente com fones;
+- o nome das notas segue a nomenclatura em português;
+- o exercício salvo permanece apenas neste aparelho;
+- valide contraste, tamanhos de toque e TalkBack em aparelho real antes de publicar.
## Limites da v0.1
-Esta versão é um alicerce funcional, ainda não um curso completo. Antes da produção serão necessários: revisão por usuários, validação completa com TalkBack, consentimento, assinatura do AAB, ícone, capturas de tela, ficha de segurança de dados e teste fechado exigido pela conta Play Console.
+O MVP usa ondas senoidais e valores rítmicos iguais. Compassos, figuras rítmicas, claves adicionais, transposição e exportação ficam para versões posteriores. Antes da Google Play ainda são necessários ícone, capturas, AAB assinado e teste fechado.
diff --git a/SomenteAndroid/app/build.gradle.kts b/SomenteAndroid/app/build.gradle.kts
index 1df7a9d..dfe9917 100644
--- a/SomenteAndroid/app/build.gradle.kts
+++ b/SomenteAndroid/app/build.gradle.kts
@@ -14,11 +14,8 @@ android {
versionCode = 1
versionName = "0.1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- buildConfigField("boolean", "ADS_ENABLED", "false")
- buildConfigField("String", "ADMOB_INTERSTITIAL_ID", "\"ca-app-pub-3940256099942544/1033173712\"")
}
- buildFeatures { buildConfig = true }
buildTypes {
release {
isMinifyEnabled = true
@@ -36,6 +33,5 @@ dependencies {
implementation("androidx.core:core-ktx:1.16.0")
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.12.0")
- implementation("com.google.android.gms:play-services-ads:24.4.0")
testImplementation("junit:junit:4.13.2")
}
diff --git a/SomenteAndroid/app/src/main/AndroidManifest.xml b/SomenteAndroid/app/src/main/AndroidManifest.xml
index fd51ed2..9f5de96 100644
--- a/SomenteAndroid/app/src/main/AndroidManifest.xml
+++ b/SomenteAndroid/app/src/main/AndroidManifest.xml
@@ -1,16 +1,10 @@
-
-
-
- Unit): Boolean {
- val ad = loaded ?: return false
- loaded = null
- ad.fullScreenContentCallback = object : FullScreenContentCallback() {
- override fun onAdDismissedFullScreenContent() { onClosed(); preload() }
- }
- ad.show(activity)
- return true
- }
-}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt
deleted file mode 100644
index 3adac27..0000000
--- a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/AdPolicy.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package br.com.sidineyr.somente
-
-data class AdState(
- val completedLessons: Int,
- val adsToday: Int,
- val lastAdAtMs: Long,
- val isFirstSession: Boolean,
- val audioPlaying: Boolean
-)
-
-object AdPolicy {
- const val LESSON_INTERVAL = 4
- const val MIN_INTERVAL_MS = 12 * 60 * 1000L
- const val DAILY_LIMIT = 3
-
- fun canShowAfterLesson(state: AdState, nowMs: Long): Boolean =
- !state.isFirstSession &&
- !state.audioPlaying &&
- state.completedLessons > 0 &&
- state.completedLessons % LESSON_INTERVAL == 0 &&
- state.adsToday < DAILY_LIMIT &&
- nowMs - state.lastAdAtMs >= MIN_INTERVAL_MS
-}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt
deleted file mode 100644
index 8dfc5ae..0000000
--- a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/Curriculum.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-package br.com.sidineyr.somente
-
-enum class ExerciseKind { SOUND_OR_SILENCE, SAME_OR_DIFFERENT, HIGH_OR_LOW, LOUD_OR_SOFT, PULSE, RHYTHM, NOTE, MELODY }
-
-data class Lesson(
- val id: String,
- val title: String,
- val objective: String,
- val prompt: String,
- val kind: ExerciseKind,
- val choices: List
-)
-
-object Curriculum {
- val lessons = listOf(
- Lesson("escuta-1", "Som e silêncio", "Perceber quando existe vibração audível.", "Você ouviu um som?", ExerciseKind.SOUND_OR_SILENCE, listOf("Som", "Silêncio")),
- Lesson("escuta-2", "Igual ou diferente?", "Comparar dois eventos sonoros sem exigir nomes técnicos.", "Os dois sons foram iguais?", ExerciseKind.SAME_OR_DIFFERENT, listOf("Iguais", "Diferentes")),
- Lesson("altura-1", "Grave e agudo", "Reconhecer altura antes de conhecer nomes de notas.", "O som foi grave ou agudo?", ExerciseKind.HIGH_OR_LOW, listOf("Grave", "Agudo")),
- Lesson("intensidade-1", "Forte e fraco", "Distinguir intensidade de altura.", "O som foi forte ou fraco?", ExerciseKind.LOUD_OR_SOFT, listOf("Forte", "Fraco")),
- Lesson("ritmo-1", "Encontre o pulso", "Sentir uma sequência regular de batidas.", "Toque junto com o pulso quatro vezes.", ExerciseKind.PULSE, listOf("Marcar pulso")),
- Lesson("ritmo-2", "Imite o ritmo", "Perceber que ritmo combina durações e pausas.", "Ouça e repita o desenho rítmico.", ExerciseKind.RHYTHM, listOf("Repetir ritmo")),
- Lesson("notas-1", "Uma nota tem nome", "Relacionar uma altura já percebida ao nome Dó.", "Esta nota é Dó. Ouça e reconheça.", ExerciseKind.NOTE, listOf("Ouvir Dó", "Reconheci")),
- Lesson("melodia-1", "Notas em caminho", "Compreender melodia como sequência intencional de alturas e durações.", "A sequência subiu ou desceu?", ExerciseKind.MELODY, listOf("Subiu", "Desceu"))
- )
-}
diff --git a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt
index 637b31f..a19be76 100644
--- a/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt
+++ b/SomenteAndroid/app/src/main/java/br/com/sidineyr/somente/MainActivity.kt
@@ -6,7 +6,6 @@ import android.view.Gravity
import android.view.View
import android.widget.Button
import android.widget.LinearLayout
-import android.widget.ProgressBar
import android.widget.ScrollView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
@@ -14,108 +13,143 @@ import kotlin.random.Random
class MainActivity : AppCompatActivity() {
private val player = TonePlayer()
- private lateinit var ads: AdController
private lateinit var root: LinearLayout
- private val prefs by lazy { getSharedPreferences("progress", MODE_PRIVATE) }
- private var lessonIndex = 0
- private var expected = ""
- private var firstSession = true
+ private val prefs by lazy { getSharedPreferences("somente", MODE_PRIVATE) }
+ private var answerButtons = mutableListOf