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

14
.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
# Build output
build/
app/build/
# Gradle cache
.gradle/
# Local SDK/IDE config
local.properties
*.iml
.idea/
# Claude Code session data
.claude/

94
AGENTS.md Normal file
View file

@ -0,0 +1,94 @@
# AGENTS.md — Agent Working Guide for Pacer
## Purpose
This file explains how AI agents should work with the Pacer project. Follow these rules
consistently across sessions, as different agents may work on different themes or user stories.
## Agent Environment
- Runs inside a Docker container with `sudo` rights
- Install any required tools via `sudo pacman -S <package>` or `paru -S <package>` (AUR)
- No need to ask the user to install dependencies — just install them as needed
## Project Structure
```
Pacer/
├── AGENTS.md ← this file
├── BACKLOG.md ← all user stories and acceptance criteria
├── README.md ← build instructions for humans
├── gradlew ← always use this to build, never system gradle
├── gradle/
│ ├── libs.versions.toml ← single source of truth for all dependency versions
│ └── wrapper/
├── settings.gradle.kts
├── build.gradle.kts ← root build file (apply false only)
└── app/
├── build.gradle.kts ← module build file
└── src/
├── main/
│ ├── AndroidManifest.xml
│ ├── kotlin/net/jeena/pacer/ ← all Kotlin source files
│ └── res/
├── test/kotlin/net/jeena/pacer/ ← unit tests
└── androidTest/kotlin/net/jeena/pacer/ ← instrumented tests
```
## Package Name
All Kotlin source files use the package prefix `net.jeena.pacer`.
## Naming Conventions
- Activities: `*Activity.kt` (e.g., `MainActivity.kt`)
- Services: `*Service.kt` (e.g., `PacerService.kt`)
- Composables: `*Screen.kt` for full screens, `*Component.kt` for reusable UI
- ViewModels: `*ViewModel.kt`
## Code Rules (Never Violate)
- Kotlin only — no Java files
- All UI via Jetpack Compose — no XML layouts
- **No audio focus requests anywhere** — app must play alongside AntennaPod without interruption
- Use `AudioTrack` directly for audio — not `MediaPlayer` or `ExoPlayer`
- `minSdk = 24` — use version checks for APIs above this
- Single-activity architecture — do not add new Activities for new screens
- All dependency versions in `gradle/libs.versions.toml` — never hardcode versions
## How to Use the BACKLOG
Stories are identified as `US-XX` in BACKLOG.md, grouped by Theme.
### Workflow Per User Story
1. Read the acceptance criteria for the target story in BACKLOG.md.
2. For TDD stories (US-03, US-04, US-13, US-14): write the failing test first.
3. Implement the minimum code to satisfy acceptance criteria.
4. Verify: `./gradlew lint` passes, `./gradlew test` passes.
### TDD Stories
For US-03, US-04, US-13:
- Commit failing test first: `test(US-XX): add failing test for <feature>`
- Then implement and commit: `feat(US-XX): implement <feature>`
### Commit Message Convention
```
<type>(US-XX): <short description>
Types: feat, fix, test, refactor, docs, chore
Example: feat(US-03): generate 880 Hz sine wave with AudioTrack
```
## Build Commands
```bash
./gradlew assembleDebug # build debug APK
./gradlew test # run unit tests
./gradlew connectedAndroidTest # run instrumented tests (requires device)
./gradlew lint # run lint checks
./gradlew installDebug # install on connected device
./gradlew clean assembleDebug # clean build
```
Output APK: `app/build/outputs/apk/debug/app-debug.apk`
## Key Architecture Notes
- Background audio uses `HandlerThread` for the scheduling loop (battery-efficient)
- `WAKE_LOCK` + foreground service keeps audio alive through Doze mode
- `foregroundServiceType="mediaPlayback"` is required on Android 14+ (already in manifest)
- Do not add new dependencies without a user story requiring them

75
BACKLOG.md Normal file
View file

@ -0,0 +1,75 @@
# Backlog for Pacer Android App
## Overview
This backlog outlines the development plan for a native Android pacing app, ported from the web version at https://jeena.net/pacer/. The app provides audible beeps at user-set BPM to maintain walking pace, runs offline, and operates in the background during long walks (e.g., 2 hours) without draining battery or interfering with podcasts like AntennaPod. We'll use Test-Driven Development (TDD) where feasible, writing tests before implementation for core logic (e.g., pacing calculations, audio generation).
Key tech: Kotlin, Jetpack Compose, Android SDK (API 24+). Development follows agile user stories with IDs (US-01, etc.), grouped by theme. Each story includes acceptance criteria. Total estimated time: 1-2 weeks.
## Themes and User Stories
### Theme 1: Project Setup
US-00: As a Agent, I want to know how to work with this specific project, so that even if different agents work with it they follow the same rules. It explains how to use the BACKLOG and how the project is structured.
- Acceptance: AGENTS.md is available for the later agents to come.
US-01: As a developer, I want to set up a new Android project with Kotlin and Jetpack Compose so that I can build the UI and logic efficiently.
- Acceptance: Project created in Android Studio, Compose enabled, manifest configured for audio and services.
US-02: As a developer, I want to configure manifest permissions and dependencies so that the app can run audio in background without issues.
- Acceptance: Permissions for foreground service and battery optimization added; AudioTrack dependency ready.
US-17: As a user, I want to have a README which explains to me how I can build the application, including how to install all dependencies necessary on Arch linux.
- Acceptance: A README.md file exists and explains shortly how to set pu the dependencies and build the .apk
### Theme 2: Audio Engine (TDD Priority)
US-03: As a user, I want the app to generate 880 Hz sine wave beeps so that pacing audio matches the web version.
- Acceptance: Unit tests for beep generation; AudioTrack produces correct tone.
US-04: As a user, I want beeps scheduled at BPM intervals with volume control so that pacing is precise and adjustable.
- Acceptance: Tests for interval calculation (e.g., 130 BPM = ~0.46s); volume scales with system volume changes.
US-05: As a user, I want audio to play concurrently with other apps (e.g., AntennaPod) without ducking so that podcasts remain uninterrupted.
- Acceptance: No audio focus requests; beeps play alongside podcast audio without interference.
### Theme 3: UI Development
US-06: As a user, I want a retro-styled UI with BPM display, slider, presets, and volume control so that it matches the web app.
- Acceptance: Compose screens render dark theme, green accents, pulse animations when active.
US-07: As a user, I want start/stop toggle and visual feedback (pulse ring, progress bar) so that pacing state is clear.
- Acceptance: Button toggles state; animations sync with beeps in foreground.
### Theme 4: Background Service
US-08: As a user, I want the app to run pacing in background with a notification so that it persists during long walks.
- Acceptance: Foreground Service starts on toggle; notification shows current BPM.
US-09: As a user, I want a stop button in the notification so that I can halt pacing without reopening the app.
- Acceptance: Notification includes action button to stop service and audio.
US-10: As a user, I want background operation to be battery-efficient for 2+ hour walks so that the phone doesn't drain quickly.
- Acceptance: HandlerThread used for low-power loop; battery tests pass for extended use.
### Theme 5: Persistence and Settings
US-11: As a user, I want BPM and volume settings saved locally so that preferences persist across sessions.
- Acceptance: SharedPreferences stores/reloads values; tests for save/load.
### Theme 6: Integration and Testing
US-12: As a user, I want the app to handle screen lock and long durations without killing audio so that walks aren't interrupted.
- Acceptance: Service survives Doze mode; manual tests for 2-hour playback with phone locked.
US-13: As a developer, I want unit tests for core logic (pacing, audio) so that changes are reliable.
- Acceptance: TDD: Tests written before code; coverage for calculations and scheduling.
US-14: As a developer, I want integration tests for background and UI so that full app works end-to-end.
- Acceptance: Tests for service start/stop, concurrent audio with AntennaPod.
### Theme 7: Polish and Deployment
US-15: As a user, I want an app icon and splash screen so that it feels native.
- Acceptance: Custom icon added; APK builds successfully.
US-16: As a developer, I want a signed APK for installation so that the app can be deployed.
- Acceptance: Release build generated; offline functionality verified.
## Additional Notes
- TDD: Focus on US-03, US-04, US-13 first—write failing tests, then implement.
- Priorities: Complete setup (US-01-02), then audio (US-03-05), UI (US-06-07), service (US-08-10), rest.
- Risks: Battery drain—monitor via profiling; audio latency—test on real devices.
- Definition of Done: All acceptance criteria met, manual testing passed, no lint errors.

104
README.md Normal file
View file

@ -0,0 +1,104 @@
# Pacer
A native Android pacing/metronome app that plays 880 Hz beeps at a user-set BPM to maintain
walking pace. Runs in the background for 2+ hour walks without draining battery. Plays
alongside other audio apps (podcasts, music) without requesting audio focus.
Ported from the web version at https://jeena.net/pacer/
## Building on Arch Linux
### 1. Install Java 17
```bash
sudo pacman -S jdk17-openjdk
```
Set `JAVA_HOME` (add to `~/.bashrc` or `~/.zshrc`):
```bash
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
export PATH="$JAVA_HOME/bin:$PATH"
```
Verify: `java -version` should show `openjdk version "17.x.x"`.
### 2. Install Android SDK via AUR
```bash
# Remove old package if previously installed (it leaves files behind — clean them up too)
sudo pacman -Rns android-sdk
sudo rm -rf /opt/android-sdk/platform-tools /opt/android-sdk/tools
# Install modern SDK tools
paru -S android-sdk-cmdline-tools-latest android-sdk-platform-tools android-sdk-build-tools android-platform
```
The packages install to `/opt/android-sdk`. Add to `~/.bashrc` or `~/.zshrc`:
```bash
export ANDROID_HOME=/opt/android-sdk
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$PATH"
```
Reload: `source ~/.bashrc`
### 3. Accept SDK Licenses
```bash
sudo /opt/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses
```
### 4. Bootstrap the Gradle Wrapper
The first time only, install system Gradle to generate the wrapper binary:
```bash
sudo pacman -S gradle
cd /path/to/Pacer
gradle wrapper --gradle-version 8.7 --distribution-type bin
sudo pacman -Rs gradle # optional: remove system gradle afterwards
```
### 5. Build
```bash
./gradlew assembleDebug
```
The debug APK will be at: `app/build/outputs/apk/debug/app-debug.apk`
### 6. Install on a Device
Enable USB debugging (Settings > Developer Options > USB Debugging), connect via USB, then:
```bash
./gradlew installDebug
# or
adb install app/build/outputs/apk/debug/app-debug.apk
```
## Troubleshooting
**`ANDROID_HOME not set`** — Reload your shell profile or open a new terminal.
**`License for package ... not accepted`** — Run `sudo /opt/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses`.
**`Unsupported class file major version`** — Wrong Java version. Confirm `java -version` shows
17 and `JAVA_HOME` points to `/usr/lib/jvm/java-17-openjdk`.
**`Permission denied` on `/opt/android-sdk`** — Run sdkmanager with `sudo`.
**`Failed to install build-tools;34.0.0 ... SDK directory is not writable`** — AGP tried to
auto-download build-tools instead of using the installed version. Ensure `buildToolsVersion = "36.1.0"`
is set in `app/build.gradle.kts`.
## Development
See `AGENTS.md` for the developer/agent workflow guide.
See `BACKLOG.md` for all user stories and acceptance criteria.
```bash
./gradlew test # unit tests
./gradlew lint # lint checks
```

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>

5
build.gradle.kts Normal file
View file

@ -0,0 +1,5 @@
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
}

5
gradle.properties Normal file
View file

@ -0,0 +1,5 @@
# Project-wide Gradle settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.suppressUnsupportedCompileSdk=36

31
gradle/libs.versions.toml Normal file
View file

@ -0,0 +1,31 @@
[versions]
agp = "8.5.0"
kotlin = "2.0.0"
coreKtx = "1.13.1"
lifecycleRuntimeKtx = "2.8.3"
activityCompose = "1.9.0"
composeBom = "2024.06.00"
junit = "4.13.2"
androidxJunit = "1.2.1"
espressoCore = "3.6.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

248
gradlew vendored Executable file
View file

@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

93
gradlew.bat vendored Normal file
View file

@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

24
settings.gradle.kts Normal file
View file

@ -0,0 +1,24 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Pacer"
include(":app")