Set up Android project with Kotlin and Jetpack Compose

Initial project structure with manifest configured for foreground
audio service, AudioTrack-based playback, and background operation.
Includes Gradle wrapper, dependency catalog, placeholder icons,
and build instructions for Arch Linux.
This commit is contained in:
Jeena 2026-03-09 06:50:17 +00:00
commit a5b3f46eae
33 changed files with 923 additions and 0 deletions

63
app/build.gradle.kts Normal file
View file

@ -0,0 +1,63 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "net.jeena.pacer"
compileSdk = 36
buildToolsVersion = "36.1.0"
defaultConfig {
applicationId = "net.jeena.pacer"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}

5
app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,5 @@
# Add project specific ProGuard rules here.
# Keep AudioTrack-related classes unobfuscated for audio engine work
-keep class android.media.AudioTrack { *; }
-keep class android.media.AudioAttributes { *; }
-keep class android.media.AudioFormat { *; }

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required for foreground service (background audio) -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required on Android 14+ for media playback foreground service type -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<!-- Allows requesting battery optimization exemption for long walks (2+ hours) -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- Keeps CPU awake during background audio playback -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Required on Android 13+ to show the foreground service notification -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Pacer">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation|keyboardHidden|screenSize"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!--
PacerService will be implemented in Theme 4 (US-08).
foregroundServiceType="mediaPlayback" is required on Android 14+.
-->
<service
android:name=".PacerService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
</application>
</manifest>

View file

@ -0,0 +1,39 @@
package net.jeena.pacer
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import net.jeena.pacer.ui.theme.PacerTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
PacerTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Text(
text = "Pacer",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
PacerTheme {
Text("Pacer")
}
}

View file

@ -0,0 +1,10 @@
package net.jeena.pacer
import android.app.Service
import android.content.Intent
import android.os.IBinder
// Stub — full implementation in Theme 4 (US-08)
class PacerService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
}

View file

@ -0,0 +1,7 @@
package net.jeena.pacer.ui.theme
import androidx.compose.ui.graphics.Color
val Green80 = Color(0xFF00FF41) // retro terminal green (matches web version)
val Green40 = Color(0xFF00A826)
val DarkBackground = Color(0xFF0D0D0D)

View file

@ -0,0 +1,20 @@
package net.jeena.pacer.ui.theme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.Composable
private val DarkColorScheme = darkColorScheme(
primary = Green80,
secondary = Green40,
tertiary = DarkBackground
)
@Composable
fun PacerTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = DarkColorScheme,
typography = Typography,
content = content
)
}

View file

@ -0,0 +1,9 @@
package net.jeena.pacer.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.sp
val Typography = Typography(
bodyLarge = TextStyle(fontSize = 16.sp)
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="green_primary">#FF00FF41</color>
<color name="dark_background">#FF0D0D0D</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View file

@ -0,0 +1,7 @@
<resources>
<string name="app_name">Pacer</string>
<string name="start">Start</string>
<string name="stop">Stop</string>
<string name="notification_channel_name">Pacer Audio</string>
<string name="notification_content_text">Pacing at %1$d BPM</string>
</resources>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Pacer" parent="android:Theme.DeviceDefault.NoActionBar">
<item name="android:statusBarColor">@color/dark_background</item>
<item name="android:windowBackground">@color/dark_background</item>
</style>
</resources>