Skip to content

Repository files navigation

Interval Timer

Native Android interval timer — Laravel 13 · PHP 8.5 · NativePHP Mobile v3.2

Fully offline, JSON storage, up to 10 phases × 50 reps with per-phase pause, cooldown, and configurable beep/TTS countdowns.

FrameworkLaravel 13
PHP8.5 (embedded)
NativePHPMobile v3.2
StorageJSON files (no SQLite)
PlatformAndroid only
Min SDKAndroid 8 (API 26)

About

A demo app showcasing NativePHP Mobile — a native, fully offline interval timer running on real Laravel 13 with PHP 8.5, no web server. Users build multi-phase repeatable timers with per-phase pauses and cooldowns, configurable audio countdowns (beep or Android TTS voice), and a live total duration display.

Core terminology:

  • Program — named collection of up to 10 phases + per-program settings
  • Phase — a named timed block with repetitions, a pause between reps, and a cooldown after the final rep
  • Repetition — one execution of the phase duration (max 50 per phase)
  • Pause — dead-time between repetitions within the same phase
  • Cooldown — dead-time after the last rep of a phase, before the next phase begins. The last phase's cooldown is editable but never executed or counted.
  • Beep Lead-in — per-program (3s or 5s), seeded from the global default on program creation
  • Total Duration(duration × reps) + (pause × (reps−1)) + cooldown for every phase except the last (last phase cooldown excluded)

Phase execution sequence (3 phases, 2 reps each):

Phase 1: [REP 1] → [PAUSE] → [REP 2] → [COOLDOWN]
Phase 2: [REP 1] → [PAUSE] → [REP 2] → [COOLDOWN]
Phase 3: [REP 1] → [PAUSE] → [REP 2] → [COOLDOWN — skipped]
END

A program always ends on the final rep of the final phase.

Tech Stack

LayerTechnologyNotes
RuntimeNativePHP Mobile v3.2PHP 8.5 embedded in Kotlin shell, persistent runtime ~5–30ms/req
FrameworkLaravel 13PHP attributes, typed config, zero breaking changes from L12
LanguagePHP 8.5Pipe operator |>, clone with, readonly class
StorageLaravel Storage (JSON)One JSON file per program + settings.json
UI / WebLivewire v3 + Alpine.jswire:poll.1000ms for timer state, Alpine for 100ms visual tick
UI / NativeEDGE ComponentsNative Top Bar + Bottom Navigation outside the WebView
StylingTailwind CSS v4Dark theme, touch-optimised
AudioWeb Audio API + Android TTSBeep (bundled mp3) or voice (feminine TTS)
BackgroundAndroid Foreground ServiceTimer runs when backgrounded, auto-pause on incoming calls
TestingPest PHPFake clock for TimerRunner, temp storage for JSON I/O

Architecture

TimerRunner state machine:

idle
→ running // executing a repetition
→ paused // user hit pause (or phone call)
→ pause // dead-time between reps
→ cooldown // dead-time after final rep, breathing animation
→ completed // all phases done, end sound fires

Key files:

app/Timer/TimerRunner.php ← singleton, state machine, beep scheduling
app/Timer/TimerCursor.php ← PHP 8.5 readonly class, clone with
app/Timer/TimerProgram.php ← JSON ↔ PHP, pipe operator load/save
app/Livewire/Library.php ← sorted by last_used_at
app/Livewire/ProgramEditor.php ← phase CRUD, 10-phase / 50-rep caps
app/Livewire/ActiveTimer.php ← wire:poll + Alpine tick + beep events
app/Livewire/Settings.php
app/Events/PhaseChanged.php ← #[Broadcast] attribute (L13)
app/Events/ProgramCompleted.php
resources/views/layouts/app.blade.php ← EDGE Bottom Nav + Top Bar
resources/audio/{beep,finish-triple,finish-chime}.mp3
storage/app/programs/*.json
storage/app/settings.json
tests/Feature/{TimerRunnerTest,BeepLogicTest,DurationCalcTest,...}.php

Initial Setup

Requirements: PHP 8.5, Composer, Android SDK (for device/emulator builds)

# 1. Clone and install dependencies
git clone <repo-url> interval-timer
cd interval-timer
composer install
# 2. Install NativePHP Mobile
php artisan native:install
# 3. Copy environment file
cp .env.example .env
php artisan key:generate

Note:composer.json specifies "php": "^8.5" — NativePHP auto-detects and matches the bundled runtime.

No database setup required. This project uses JSON file storage only — there is no SQLite, no migrations.

config/nativephp.php

Ensure the following values are set:

'bundle_id' => 'com.yourname.intervaltimer',
'min_sdk' => 26, // Android 8'compile_sdk' => 35,
'target_sdk' => 35,

Build and Run

Android Emulator

php artisan native:run --os=android

Physical Device

Connect an Android device with USB debugging enabled, then:

php artisan native:run --os=android

NativePHP will detect the connected device automatically.

Development (browser, for UI iteration)

php artisan serve

The spec file interval-timer-spec-v4.html includes a live JS playground that mirrors the timer logic. Open it directly in a browser to validate phase/beep behaviour without a device.


Running the Test Suite

Tests use Pest PHP. Run all tests in parallel:

php artisan test --parallel

Test Suites

SuiteCoveragePriority
TimerRunnerTestState transitions idle→running→pause→cooldown→completed, user pause preserves cursor, total_remaining decrements, 10-phase limitCRITICAL
BeepLogicTestLead-in 3s/5s, short segment fallback (fires from second 1), fires on rep/pause/cooldown end, no double-fireCRITICAL
DurationCalcTestSingle phase, multi-rep with pause, cooldown on last phase excluded, all 10 phases, formattedDuration() mm:ss and h:mm:ssCRITICAL
CursorTestAdvances through reps, skips pause/cooldown when 0, last phase last rep → completed, clone with immutabilityCRITICAL
TimerProgramTestJSON save/load, last_used_at updated on run, totalDuration() formula, 50-rep cap, pipe operator load chainHIGH
SettingsTestDefaults when settings.json missing, new program seeded from global defaults, per-program beep_lead_in override, volume clamped 0–1HIGH
EndSoundTestProgramCompleted fires exactly once, triple vs chime selection, not fired on mid-program phase changeHIGH
LifecycleTestPause state preserved on phone call, resume restores exact cursor position, kill discards state with no history entryHIGH

Conventions

  • Pest PHP throughout — describe() blocks per class
  • TimerRunner uses a fake clock — inject tick count, no real sleep()
  • TimerProgram writes to a temp directory, never real storage
  • Every PR must include tests before merge

License

MIT

About

Interval Timer simple app with NativePHP

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages