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.
63
app/build.gradle.kts
Normal 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
|
|
@ -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 { *; }
|
||||
49
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
39
app/src/main/kotlin/net/jeena/pacer/MainActivity.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
10
app/src/main/kotlin/net/jeena/pacer/PacerService.kt
Normal 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
|
||||
}
|
||||
7
app/src/main/kotlin/net/jeena/pacer/ui/theme/Color.kt
Normal 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)
|
||||
20
app/src/main/kotlin/net/jeena/pacer/ui/theme/Theme.kt
Normal 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
|
||||
)
|
||||
}
|
||||
9
app/src/main/kotlin/net/jeena/pacer/ui/theme/Type.kt
Normal 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)
|
||||
)
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 70 B |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 70 B |
7
app/src/main/res/values/colors.xml
Normal 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>
|
||||
7
app/src/main/res/values/strings.xml
Normal 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>
|
||||
7
app/src/main/res/values/themes.xml
Normal 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>
|
||||