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.
14
.gitignore
vendored
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||||
5
build.gradle.kts
Normal 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
|
|
@ -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
|
|
@ -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
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
||||||