A native macOS menu bar application for the TimeFlip2 time tracking device with seamless Google Calendar and Google Sheets integration.
Disclaimer: despite 25+ years of being a software engineer and architect, and 15+ years of being a macOS user, I never had the pleasure of developing anything for macOS. This project was totally vibecoded -- mostly with OpenAI Codex, with a bit of polishing by Anthropic Claude. Which means: if you are an experienced macOS/Swift developer, you may bleed your eyes out while reading this code, just as I sometimes bleed out mine when reading vibecoded Go or Rust repos. You have been warned.
- Menu Bar Timer: Real-time activity tracking with icon, elapsed time, and pause/play indicators
- BLE Device Integration: Direct connection to TimeFlip2 via Bluetooth Low Energy
- Google Calendar Sync: Automatically creates calendar events for completed time tracking sessions
- Google Sheets Export: Appends activity logs to a designated Google Sheet workbook
- Activity Management: Configure custom activities with icons, colors, and time limits
- Auto-Pause Support: Automatic pause after configurable idle time
- Daily Statistics: Track daily time spent per activity
- Device Control: LED brightness, blink intervals, and double-tap sensitivity configuration
Menu bar item preview:
- Pomodoro timers: totally doable, but I don't use this workflow myself and I am not sure about UX. PRs are welcome
- macOS 14 (Sonoma) or later
- Apple Silicon or Intel Mac with Bluetooth 4.0+
- TimeFlip2 device
- Swift 6.0+ (for building from source)
The project includes configuration for swift-bundler, which creates a proper macOS application bundle.
# Install Mint package manager (if not already installed)
brew install mint
# Install swift-bundler
mint install stackotter/swift-bundler@main
# Clone the repository
git clone https://github.com/growler/TimeFlipApp.git
cd TimeFlipApp
# Build the application bundle
swift bundler bundle --product TimeFlipApp
# The app will be created at .build/bundler/outputs/TimeFlip.app
# Open the app
open .build/bundler/outputs/TimeFlip.app
# or run using bundler
swift bundler runYou can then drag TimeFlip.app to your Applications folder for easy access.
# Clone the repository
git clone https://github.com/growler/TimeFlipApp.git
cd TimeFlipApp
# Build the application
swift build -c release
# Run the application
.build/release/TimeFlipAppThe app will appear in your menu bar with the TimeFlip icon.
To enable Google Calendar and Google Sheets integration, you need to create a Google Cloud project and configure OAuth credentials.
- Go to the Google Cloud Console
- Click on the project dropdown at the top and select "New Project"
- Enter a project name (e.g., "TimeFlip Integration")
- Click "Create"
- In your project, go to "APIs & Services" > "Library"
- Search for and enable the following APIs:
- Google Calendar API
- Google Sheets API
- Go to "APIs & Services" > "OAuth consent screen"
- Select "External" as the user type (unless you have a Google Workspace account)
- Click "Create"
- Fill in the required information:
- App name: TimeFlip macOS
- User support email: Your email address
- Developer contact information: Your email address
- Click "Save and Continue"
- On the "Scopes" page, click "Add or Remove Scopes"
- Add the following scopes:
https://www.googleapis.com/auth/calendar.eventshttps://www.googleapis.com/auth/calendar.readonlyhttps://www.googleapis.com/auth/spreadsheets
- Click "Update" and then "Save and Continue"
- On the "Test users" page, click "Add Users"
- Important: Add your email address as a test user
- Click "Save and Continue"
- Go to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth client ID"
- Select "Desktop app" as the application type
- Enter a name (e.g., "TimeFlip Desktop Client")
- Click "Create"
- You'll see a dialog with your Client ID and Client Secret
- Click "Download JSON" to save the credentials (optional, but recommended as backup)
- Copy both the Client ID and Client Secret - you'll need these for the app
- Launch the TimeFlip app from your menu bar
- Click on the TimeFlip icon and select "Preferences..."
- Go to the "Reports" tab
- Paste your Client ID in the "Client ID" field
- Paste your Client Secret in the "Client Secret" field
- Click "Sign In with Google"
- Your default browser will open with the Google OAuth consent screen
- Sign in with your Google account (the one you added as a test user)
- Review the permissions and click "Continue"
- The browser will show "Authorization complete" and you can close the window
- Return to the TimeFlip app - you should now see "Authenticated"
- In the Reports tab preferences:
- Calendar: Click "Load calendars" to fetch your Google calendars, then select the calendar where events should be created from the dropdown menu. You can use "Refresh calendars" to reload the list if needed.
- Sheet URL:
- Click "Set" to enter a Google Sheets URL (if you have a sheet URL in your clipboard, it will be pre-filled)
- Press Enter to save, or Escape to cancel
- Once set, use "Update" to change the URL or "Open" to view the sheet in your browser
- To remove the URL, click "Update", clear the field, and press Enter
The app will now automatically sync your time tracking data to Google Calendar and Sheets.
- Ensure your TimeFlip2 device is powered on and within Bluetooth range
- Open the TimeFlip app preferences
- Go to the "Device" tab
- Enter your device password (default is
000000) - Click "Pair Device"
- Wait for the connection to establish
- Once connected, the menu bar will show the current activity
- In Preferences > "Facets" tab
- Each TimeFlip facet (1-12) can be assigned:
- Activity Name: Custom label for the activity
- Icon: Native TimeFlip icon (matching the stickers included with your device)
- Color: RGB LED color shown on the device
- Time Limit: Optional daily limit (turns the menu bar item red when exceeded, to make you aware if you've been slacking off enough for today)
Configure your TimeFlip device behavior:
- Auto-Pause: Automatically pause after X minutes of inactivity
- LED Brightness: Adjust LED intensity (1-100%)
- Blink Interval: How often the LED blinks (5-60 seconds)
- Double-Tap Sensitivity: Configure tap detection parameters
- Flip your TimeFlip device to any facet to start tracking that activity
- The menu bar shows the current activity name, icon, and elapsed time
- Flip to another facet to switch activities
- All completed sessions are automatically logged
- Click the menu bar icon and select "Pause" to pause tracking
- Select "Resume" to continue tracking
- Or use the keyboard shortcut:
⌘P
- The app tracks daily totals for each activity
- View current day statistics in the preferences window
- Daily windows reset at midnight
For development and testing without a physical device:
// In ApplicationDelegate.swift
private let enableMockEvents = trueThe app includes a mock device that simulates TimeFlip behavior and accepts commands via HTTP:
# Send a mock facet change event
./scripts/send_mock_event.sh- ApplicationDelegate: App lifecycle and device management
- MenuBarController: Menu bar UI and timer display
- TimeFlipBLEDevice: Bluetooth Low Energy device driver
- HistoryIngestor: Event processing and logbook management
- GoogleIntegrationCoordinator: Syncs data to Google Calendar and Sheets
- AppState: Application state and user preferences
TimeFlip Device (BLE)
↓
Device History Events
↓
Logbook Database (SQLite)
↓
├─> Google Calendar Events
├─> Google Sheets Rows
└─> Menu Bar UI + Daily Stats
- Device sends notifications on facet changes or pause events
- Driver fetches complete history from device
- History ingested into local SQLite logbook (all but last frame)
- Last frame (live interval) drives UI only, never persisted
- Integrations read from logbook using cursor-based sync
- Each integration maintains its own sync cursor
# Build app bundle (recommended for testing full app behavior)
swift bundler bundle --product TimeFlipApp
open .build/bundler/outputs/TimeFlip.app
# Or build in debug mode directly
swift build
# Run tests
swift test
# Run with verbose logging (direct execution)
swift run
# Format code (requires SwiftLint)
swiftlint --fix- Swift-only codebase with 2-space indentation
- Follow SwiftLint rules
- Small, testable functions with dependency injection
- Avoid over-engineering - keep solutions simple and focused
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes using Conventional Commits
feat: add calendar event deduplicationfix: handle device disconnect gracefullydocs: update Google OAuth setup instructions
- Push to your branch
- Open a Pull Request with:
- Purpose and motivation
- Screenshots for UI changes
- Documentation updates
- Never commit Google credentials, API tokens, or device passwords
- Credentials are stored in macOS Keychain
- Ensure Bluetooth is enabled
- Check that the device password is correct (default:
000000) - Try resetting the device by removing and reinserting the battery
- Check Bluetooth permissions in System Preferences > Privacy & Security
- Verify your email is added as a test user in Google Cloud Console
- Check that all required APIs are enabled (Calendar API, Sheets API)
- Ensure the Client ID and Client Secret are correct
- Try signing out and signing in again
- Verify you're authenticated
- Check that Calendar Name and Sheet URL are configured
- Ensure the sheet is accessible to your Google account
- Check Console.app logs for error messages (filter by "timeflip")
- Check that the device is connected (preferences should show "Paired")
- Try manually pausing and resuming
- Restart the application
This project is released into the public domain under The Unlicense.
The TimeFlip icon set included in this project is used with permission from TimeFlip exclusively for this application. If you wish to fork this project, you must obtain your own permission from TimeFlip to use their icon assets, or replace them with your own icons.
- Special thanks to TimeFlip for the hardware device and for graciously permitting the use of their icon set in this application
- AppAuth-iOS for OAuth implementation
- Timeflippers for the Rust TimeFlip client which I've been looking a lot at to get the idea of what the hell is going on in a familiar language
- Built with Swift and macOS native frameworks
For bugs and feature requests, please open an issue.
For device-related questions, visit TimeFlip Support.



