Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ apps/

# Android local SDK path file
android/local.properties
wearos-watchface/local.properties

# Local tooling downloads/cache
.tools/
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# Binary Bloom Clock

A pastel-themed binary clock project with four runtime options:
A pastel-themed binary clock project with five runtime options:

- Native Windows desktop widget built with PowerShell + WinForms
- Browser-based web app built with HTML, CSS, and JavaScript
- Native Windows desktop app built with WPF + WebView2
- Native Android app built with Kotlin + WebView
- Native Wear OS watch face built with Kotlin + Canvas Watch Face API

Both versions currently use a compact 4-row binary layout with values `8, 4, 2, 1`.
All versions use a compact 4-row binary layout with values `8, 4, 2, 1`.

## Features

- Compact binary clock display for hours, minutes, and seconds
- 12h / 24h toggle
- Elegant pastel UI style
- Resizable widget window (native version)
- Resizable widget window (desktop native versions)
- Drag-to-move header area (native and web widget shell behaviour)
- Tray support for native app (hide/restore)
- Local state persistence (window bounds and preferences)
Expand Down Expand Up @@ -46,6 +47,11 @@ binary-clock/
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── README.md
├── wearos-watchface/ # Native Wear OS watch face project
│ ├── app/
│ ├── build.gradle.kts
│ ├── settings.gradle.kts
│ └── README.md
├── LICENSE
└── README.md
```
Expand All @@ -70,6 +76,7 @@ binary-clock/

- WPF + WebView2 wrapper that runs the same `web/` app in a native window
- Kotlin + WebView Android wrapper that loads local bundled assets
- Kotlin + Canvas Wear OS watch face service

## Requirements

Expand Down Expand Up @@ -141,6 +148,14 @@ The project automatically copies files from `../web` into the output folder.

During build, Gradle copies `../../web` into `app/src/main/assets/web`.

## Run the Wear OS Watch Face

1. Open Android Studio.
2. Open the folder `wearos-watchface`.
3. Let Gradle sync and install required Wear OS SDK components.
4. Run on a Wear OS emulator or watch.
5. Select **Binary Bloom** as the active watch face.

## Native Widget Controls

Top buttons:
Expand Down
10 changes: 10 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This project wraps the existing web clock as a native Android app.

The Android runtime uses a compact app mode optimised for phone screens:

- Dot-based binary lights (instead of rectangular tiles)
- Portrait and landscape layouts tuned to keep the full clock visible
- Automatic full-screen fitting inside the Android WebView

## Prerequisites

- Android Studio (latest)
Expand Down Expand Up @@ -49,3 +55,7 @@ gradle wrapper
## Asset Sync

The Gradle task syncWebAssets copies files from ../../web to app/src/main/assets/web before each build.

## Related Project

If you want a proper Wear OS watch face (for example for Google Play), use the standalone project in `../wearos-watchface`.
40 changes: 35 additions & 5 deletions android/app/src/main/assets/web/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const widgetBar = document.getElementById("widgetBar");
const periodLabel = document.querySelector("[data-period-label]");
const formatButtons = document.querySelectorAll("[data-format-toggle]");
const fitToScreenButton = document.getElementById("fitToScreenButton");
const isEmbeddedAndroidApp = window.location.protocol === "file:";

const SCREEN_MARGIN = 20;
const MIN_WIDGET_WIDTH = 360;
Expand All @@ -23,6 +24,10 @@ const MAX_WIDGET_HEIGHT = 860;

let dragState = null;

if (isEmbeddedAndroidApp) {
document.body.classList.add("android-app");
}

function loadState() {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
Expand Down Expand Up @@ -200,6 +205,14 @@ function clampWidgetPosition(left, top) {
}

function applySavedLayout() {
if (isEmbeddedAndroidApp) {
widgetShell.style.left = "0px";
widgetShell.style.top = "0px";
widgetShell.style.width = "100vw";
widgetShell.style.height = "100dvh";
return;
}

if (window.innerWidth <= 860) {
widgetShell.style.left = "0px";
widgetShell.style.top = "0px";
Expand All @@ -226,6 +239,10 @@ function applySavedLayout() {
}

function fitWidgetToScreen() {
if (isEmbeddedAndroidApp) {
return;
}

if (window.innerWidth <= 860) {
return;
}
Expand All @@ -236,6 +253,12 @@ function fitWidgetToScreen() {
}

function fitContentToShell() {
if (isEmbeddedAndroidApp) {
clockContent.style.transform = "scale(1)";
clockContent.style.width = "100%";
return;
}

if (window.innerWidth <= 860) {
clockContent.style.transform = "scale(1)";
return;
Expand All @@ -258,7 +281,7 @@ function fitContentToShell() {
}

function handlePointerDown(event) {
if (window.innerWidth <= 860 || event.target.closest("button")) {
if (isEmbeddedAndroidApp || window.innerWidth <= 860 || event.target.closest("button")) {
return;
}

Expand Down Expand Up @@ -292,6 +315,10 @@ function handlePointerUp() {
}

function trackResize() {
if (isEmbeddedAndroidApp) {
return;
}

if (window.innerWidth <= 860) {
return;
}
Expand Down Expand Up @@ -326,10 +353,13 @@ function setupEvents() {
});
});

fitToScreenButton.addEventListener("click", fitWidgetToScreen);
widgetBar.addEventListener("pointerdown", handlePointerDown);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
if (!isEmbeddedAndroidApp) {
fitToScreenButton.addEventListener("click", fitWidgetToScreen);
widgetBar.addEventListener("pointerdown", handlePointerDown);
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}

window.addEventListener("resize", () => {
applySavedLayout();
fitContentToShell();
Expand Down
116 changes: 116 additions & 0 deletions android/app/src/main/assets/web/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -594,4 +594,120 @@ body {
font-size: 0.85rem;
}

}

/* Android app mode: compact, dot-based layout that keeps the full clock in view. */
body.android-app {
--dot-size: clamp(12px, 4.2vmin, 24px);
--dot-gap: clamp(6px, 1.6vmin, 12px);
padding: 0;
overflow: hidden;
}

body.android-app .background-glow,
body.android-app .widget-bar,
body.android-app .hero {
display: none;
}

body.android-app .clock-shell {
position: fixed;
inset: 0;
width: 100vw;
height: 100dvh;
min-width: 0;
min-height: 0;
border-radius: 0;
border: 0;
resize: none;
padding: clamp(10px, 2.2vmin, 18px);
}

body.android-app .clock-content,
body.android-app .clock-panel {
height: 100%;
}

body.android-app .time-columns {
height: 100%;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: clamp(8px, 1.8vmin, 14px);
}

body.android-app .time-column {
min-height: 0;
padding: clamp(8px, 1.8vmin, 14px);
gap: clamp(8px, 1.6vmin, 12px);
border-radius: 18px;
}

body.android-app .column-header {
gap: 6px;
}

body.android-app .column-kicker {
font-size: clamp(0.58rem, 1.8vmin, 0.68rem);
letter-spacing: 0.14em;
}

body.android-app .binary-readout {
font-size: clamp(0.55rem, 1.8vmin, 0.72rem);
padding: 5px 8px;
}

body.android-app .digit-label {
font-size: clamp(0.52rem, 1.5vmin, 0.62rem);
}

body.android-app .bit-groups {
gap: clamp(6px, 1.6vmin, 10px);
}

body.android-app .bit-stack {
justify-items: center;
gap: var(--dot-gap);
}

body.android-app .bit-tile {
width: var(--dot-size);
height: var(--dot-size);
min-height: 0;
border-radius: 50%;
box-shadow: inset 0 -2px 6px rgba(255, 255, 255, 0.45);
}

body.android-app .bit-tile::after {
right: auto;
bottom: auto;
inset: 0;
display: grid;
place-items: center;
font-size: clamp(0.52rem, 1.5vmin, 0.62rem);
}

body.android-app .decimal-readout {
font-size: clamp(1.25rem, 4.4vmin, 2.1rem);
line-height: 1;
}

body.android-app .period-badge {
min-width: 54px;
padding: 6px 9px;
font-size: clamp(0.62rem, 1.8vmin, 0.74rem);
}

@media (orientation: landscape) {
body.android-app {
--dot-size: clamp(11px, 3.1vmin, 18px);
--dot-gap: clamp(5px, 1.2vmin, 8px);
}

body.android-app .time-column {
padding: clamp(7px, 1.3vmin, 10px);
gap: clamp(6px, 1.2vmin, 9px);
}

body.android-app .decimal-readout {
font-size: clamp(1.05rem, 3.5vmin, 1.55rem);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
2 changes: 2 additions & 0 deletions wearos-watchface/.gradle/buildOutputCleanup/cache.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#Sat Apr 04 16:03:39 BST 2026
gradle.version=8.10.2
Binary file not shown.
Empty file.
53 changes: 53 additions & 0 deletions wearos-watchface/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Wear OS Watch Face (Binary Bloom)

This folder contains a standalone Wear OS watch face project for Binary Bloom.

It uses a native Canvas watch face service (not WebView), with compact binary dots for:

- Hours
- Minutes
- Seconds

## Prerequisites

- Android Studio (latest stable)
- Wear OS SDK components installed
- JDK 17+
- A Wear OS emulator or physical watch for testing

## Open and Run

1. Open Android Studio.
2. Select Open and choose `wearos-watchface`.
3. Let Gradle sync and install any missing SDK components.
4. Run the `app` configuration on a Wear OS emulator/device.
5. On the watch, choose **Binary Bloom** as the active watch face.

## Build APK From Terminal

From `wearos-watchface`:

```powershell
./gradlew.bat assembleDebug
```

Expected output path:

- `app/build/outputs/apk/debug/app-debug.apk`

## Notes for Play Store Release

This project now includes:

- Watch-face preview assets in app resources and manifest metadata
- Basic branded app/watch-face icon drawable
- A built-in watch-face user setting for 12-hour vs 24-hour time format

For final Google Play publication, you will usually still want to add:

- High-resolution marketing screenshots and listing images in Play Console
- Finalized production branding artwork (PNG/WebP variants)
- Release signing configuration
- Store listing assets and policy declarations

The current implementation is a clean baseline and is ready for further visual polish.
Loading
Loading