Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Push-Up Tracker — Garmin Connect IQ App

A standalone Garmin watch app that lets you log push-up sets, track cumulative progress toward a goal, celebrate milestones, and review daily totals on a bar graph. No phone, no internet, no external services required (Mode A).


Design Document

Chosen App Type

Full App (type app in manifest.xml) with a Glance view.

  • Full App gives maximum screen real estate and full button control, which is necessary for the Quick Add buttons, graph, and settings.
  • The Glance view lets users see total / % without launching the app.
  • Target devices: Fenix 7 Pro, 7X Pro, 7S Pro, Epix 2 Pro, FR965, Venu 3. All share a round screen (240–280 px). All drawing is fully proportional via dc.getWidth() / dc.getHeight() so it adapts to any round screen size.

Data Model

All state lives in AppModel (source/AppModel.mc) and is persisted via Application.Storage:

Key Type Purpose
total Number Cumulative push-ups since startDate
goalCnt Number Target push-up count (default 10 000)
goalType String "count" or "date"
startDate String "YYYY-MM-DD" — when tracking began
targDate String? "YYYY-MM-DD" — optional target date
lastMs Number Last milestone index hit (0–10)
daily Dictionary "YYYY-MM-DD" → Number per-day totals
lastAdd Number Amount of last add (enables single undo)

No networking or external storage is used. All state fits comfortably within Connect IQ's Application.Storage limits.

View Stack & Navigation

Watch face
    └── Glance view (read-only stats)
        └── [Launch app]
            └── MainView  (initial view)
                ├── [SELECT / DOWN] → QuickAddView (pushed)
                │       └── [Custom] → CustomCounterView (pushed)
                │       └── [any add] → MilestoneView (pushed, if milestone hit)
                ├── [UP] → GraphView (pushed)
                └── [MENU] → Settings Menu2 (pushed)

All secondary views are pushed (not swapped) so BACK always returns cleanly.

Milestone Algorithm

function addPushups(count):
    oldTotal = totalPushups
    totalPushups += count
    milestone = findNewMilestone(oldTotal, totalPushups)
    if milestone > 0:
        lastMilestone10 = milestone / 10   # update BEFORE returning
    save()
    return milestone

function findNewMilestone(old, new):
    for pct in [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]:
        idx = pct / 10                           # 1..10
        if idx <= lastMilestone10: skip          # already triggered
        threshold = ceil(goalCount * pct / 100)
        if new >= threshold: hit = pct           # keep scanning for highest
    return hit                                   # highest newly-crossed (or 0)

Properties:

  • lastMilestone10 is monotonically non-decreasing (never rolls back on undo).
  • Each 10% boundary triggers exactly once per goal lifetime.
  • Adding a large amount at once triggers the highest newly-crossed milestone in one shot (not multiple pop-up storms).
  • Changing goalCount resets lastMilestone10 = 0 so milestones restart.

Graph Approach

GraphView.mc draws a bar chart using only the Graphics.Dc API:

  • Bars: dc.fillRectangle() — today's bar is bright green; past = blue.
  • Axes: dc.drawLine().
  • Labels: dc.drawText() with FONT_XTINY.
  • Scaling: each bar height = (dailyVal / maxVal) * chartH.
  • Three selectable ranges: 7 / 14 / 30 days (UP/DOWN to cycle).
  • No external libraries, no bitmaps, fully resolution-independent.

Project Structure

GarminPushUpApp/
├── manifest.xml                  # App manifest (devices, permissions, UUID)
├── monkey.jungle                 # Build configuration
├── source/
│   ├── PushUpApp.mc              # Application entry point
│   ├── AppModel.mc               # Data model & persistence
│   ├── MainView.mc               # Main stats screen (draw)
│   ├── MainDelegate.mc           # Main screen button handling
│   ├── QuickAddView.mc           # Add push-ups screen
│   ├── QuickAddDelegate.mc       # Quick-add input + milestone trigger
│   ├── CustomCounterView.mc      # Custom-count dial screen
│   ├── CustomCounterDelegate.mc  # Custom-count input
│   ├── GraphView.mc              # Bar chart screen
│   ├── GraphDelegate.mc          # Graph date-range cycling
│   ├── MilestoneView.mc          # Milestone celebration overlay
│   ├── MilestoneDelegate.mc      # Milestone dismiss handler
│   ├── SettingsMenuDelegate.mc   # Settings menu + confirm delegate
│   └── GlanceView.mc             # Compact glance panel view
├── resources/
│   ├── strings/strings.xml       # English strings
│   ├── strings-heb/strings.xml   # Hebrew (he-IL) strings
│   └── drawables/drawables.xml   # Drawables (icon placeholder)
├── README.md
└── CHANGELOG.md

How to Install / Run in Simulator

Prerequisites

  1. Install the Garmin Connect IQ SDK (≥ 4.1.x): https://developer.garmin.com/connect-iq/sdk/
  2. Install Visual Studio Code + the Monkey C extension (optional but recommended), or use the connectiq CLI directly.
  3. Download the Fenix 7 Pro device definition from the SDK Manager.

Build & Simulate

# From the project root:
connectiq build --device fenix7pro --workspace .

# Or via VS Code: open the folder, Cmd/Ctrl+Shift+B → "Build"

Run in the simulator:

# After build, the .prg file appears in bin/
monkeydo bin/PushUpTracker.prg fenix7pro

Or in the SDK simulator UI:

  1. Open the Connect IQ Simulator.
  2. File → Run → Browse to bin/PushUpTracker.prg.
  3. Select Fenix 7 Pro.

How to Sideload to Device

  1. Enable Developer Mode on the watch:
    • Hold MENU → System → Developer Mode → On
  2. Connect the watch via USB.
  3. Copy bin/PushUpTracker.prg to the watch at:
    GARMIN/Apps/PushUpTracker.prg
    
  4. Safely eject and launch from the watch's Activities / Apps list.

How to Use the App

First Launch

  • The app opens to the Main screen showing 0 / 10,000 and 0%.
  • Push-up tracking begins from today (default settings).

Logging Push-Ups (Mode A — Standalone)

  1. From Main, press SELECT or DOWN → Quick Add screen.
  2. Press UP / DOWN to highlight an option: +5, +10, +20, +25, Custom, or Undo.
  3. Press SELECT to apply.
  4. For Custom: dial in a count with UP (+1) / DOWN (-1), then SELECT to confirm.
  5. Undo: removes the most recent add (once only).
  6. Press BACK to return to Main.

Checking Progress

  • Main screen: total, %, remaining, and (in date mode) pace vs. required.
  • Glance: swipe to the widget panel from the watch face for a quick read.
  • Graph (press UP from Main): bar chart of daily totals.
    • Use UP/DOWN to switch between 7 / 14 / 30-day views.
    • Bottom row shows best-day and N-day average.

Milestones

  • When you cross 10%, 20%, … 100% of your goal, the watch vibrates and shows a full-screen celebration: "Take a progress photo!"
  • Each milestone triggers exactly once. Press any button to dismiss.

Settings (MENU from Main)

Setting Behaviour
Goal: Count / Date Toggle goal type
Target: N Cycle through preset goal counts (1k–50k)
End Date Cycle: None → +30d → +60d → +90d → +180d → +1yr
Reset Tracking Clears totals / daily map; keeps goal settings
Reset ALL Full factory reset

Mode B — Companion Phone Component (Optional / Future)

Mode B would automatically pull push-up reps from completed Garmin strength activities via the Garmin Health API (or the Connect IQ Communication module) and sync them to the watch.

Why Mode B Isn't Implemented Yet

The Garmin Health API (activity rep data) is behind a developer approval process at https://developer.garmin.com/health-api/. You need:

  • An approved developer account
  • OAuth2 client credentials (client_id, client_secret)
  • User authorization + token storage on a companion server or phone

This requires infrastructure (server, HTTPS endpoints, OAuth dance) that isn't feasible to bundle into the watch app itself.

How to Enable Mode B Later

  1. Create a Garmin Health API developer account and get approved.
  2. Build a companion phone app (iOS / Android) or server endpoint that:
    • Authenticates the user via Garmin OAuth.
    • Polls GET /wellness-api/rest/activities for strength workouts since startDate.
    • Finds exercises with activityType = "PUSH_UP" and sums reps.
    • Avoids double-counting by storing processed activityId values.
  3. Send the total to the watch via Connect IQ's Communications.transmit() (add <iq:uses-permission id="Communication"/> to manifest.xml).
  4. In the watch app, handle the incoming message in Application.AppBase.onMessage():
    function onMessage(msg as Application.Message) as Void {
        if (msg has :data && msg.data instanceof Lang.Dictionary) {
            var d = msg.data as Lang.Dictionary;
            var serverTotal = d["pushUps"] as Lang.Number;
            // Merge with local total (use serverTotal if > local, or add delta)
            _model.totalPushups = serverTotal;
            _model.save();
            WatchUi.requestUpdate();
        }
    }
  5. Add Communication permission back in manifest.xml.

Placeholder stub (already in source comments)

PushUpApp.mc contains a commented-out onMessage() stub ready for Mode B.


Acceptance Tests / Manual Test Checklist

Milestone Triggers

  • Add push-ups to cross 10% → milestone screen appears with "10%", vibrates
  • Dismiss milestone → returns to Quick Add
  • Add more → no milestone re-trigger until next boundary
  • Add large batch crossing 50% from 35% → "50%" shown (highest), not 40%+50%
  • Undo an add that crossed 50% → total drops below 50%; adding back to 50% does not re-trigger (lastMilestone10 stays at 5) ✓ by design

Persistence

  • Add 50 push-ups → exit app → relaunch → total still shows 50
  • Change goal count in Settings → exit → relaunch → new goal persists
  • Daily totals persist across app restart

Daily Totals & Midnight Crossing

  • Add 30 push-ups today → graph shows correct bar for today
  • Simulate next day (change watch date) → today's bar starts at 0 again; yesterday's count preserved in the dictionary

Quick Add

  • +5 increments total by exactly 5
  • Custom entry: dial to 37, SELECT → total increases by 37
  • Undo immediately after +20 → total decreases by 20
  • Second undo (no lastAddAmount) → nothing happens, no negative total

Graph

  • 7-day view shows 7 bars; today (rightmost) is green
  • UP from 7-day → 30-day; DOWN from 30-day → 7-day (cycles)
  • Bars scale correctly when best-day value changes
  • Date labels appear under bars without overlapping
  • Empty history → all bars at zero height, stats show 0

Settings

  • Goal type toggle changes label and pace row visibility on Main
  • Goal count cycle reaches 50000 then wraps to 1000
  • End date "+30 days" sets targetDate 30 days from today
  • Reset Tracking → total = 0, daily map cleared, settings unchanged
  • Reset ALL → everything back to defaults

Glance

  • Shows correct total and % without launching the app

Limitations

  1. No sub-second updates — the watch redraws on button events only.
  2. Undo is single-level — only the most recent add can be undone.
  3. Daily map grows unbounded — after years of use the dictionary could theoretically approach storage limits. In practice, 365 × 10 bytes is well within CIQ limits.
  4. No reminder notifications — Garmin CIQ apps cannot push background notifications; reminders are only shown during active use.
  5. No Activity import (Mode A) — push-ups logged in other Garmin apps (running cadence sets, strength activity exercises) are not counted here. Use Mode B when it becomes available.
  6. Hebrew font rendering — CIQ watches support Hebrew strings but RTL layout is not guaranteed on all firmware versions. English fallback is always available.

About

Garmin push up apps

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages