{"id":18556375,"url":"https://github.com/splitties/composeoclock","last_synced_at":"2025-08-10T23:49:00.761Z","repository":{"id":223454230,"uuid":"753714349","full_name":"Splitties/ComposeOClock","owner":"Splitties","description":"Draw Wear OS Watch Faces with Compose Canvas (\u0026 runtime)","archived":false,"fork":false,"pushed_at":"2024-06-21T00:39:34.000Z","size":1367,"stargazers_count":143,"open_issues_count":8,"forks_count":15,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-11T01:56:23.890Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Splitties.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-06T16:54:21.000Z","updated_at":"2025-04-03T15:03:24.000Z","dependencies_parsed_at":"2024-06-21T17:18:45.921Z","dependency_job_id":"45a7c5a8-86e2-4cd6-b80c-9445c4a8b194","html_url":"https://github.com/Splitties/ComposeOClock","commit_stats":null,"previous_names":["splitties/composeoclock"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Splitties%2FComposeOClock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Splitties%2FComposeOClock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Splitties%2FComposeOClock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Splitties%2FComposeOClock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Splitties","download_url":"https://codeload.github.com/Splitties/ComposeOClock/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248402524,"owners_count":21097330,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T21:30:12.265Z","updated_at":"2025-04-11T12:42:30.549Z","avatar_url":"https://github.com/Splitties.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Compose O'Clock\n\nDraw Wear OS Watch Faces with Compose Canvas (\u0026 runtime)\n\nCompose O'Clock is the fastest way to develop a good looking and efficient Watch Face for Wear OS.\n**You can see the result of your code changes within a second!**\n\nUsing this SDK, knowing how to use Jetpack Compose is about 96% of the knowledge you need to know to\nsuccessfully get a proper watch face that you can also see live in a phone app.\n\nThe 4% remaining to develop a good watch face app is documented here.\n\n## Visual examples\n\nHere are two sample screenshots from this sample repo to give you a quick idea\nof the possibilities offered by Compose O'Clock.\n\nThe screenshots have been taken right from IDE preview.\n\n![Compose O'Clock sample screenshot](.screenshots/kotlin-and-compose-fan-clock.png)\n\n## Developer experience demo (2min 21s, 8.3MB)\n\nWatching **with sound on** is recommended.\n\n[compose-o-clock-demo-small.webm](https://github.com/Splitties/ComposeOClock/assets/6975237/cc3d03c2-0191-4d0b-98c2-def77650a639)\n\n\n## Features  \n\n- ✅ **Live-preview** your Watch Face in Android Studio, a phone, or a watch\n- ✅ **Adaptive refresh-rate** out of the box (up to 60fps on watches, and 120fps on phones)\n- ✅ Compose Canvas and text APIs (**Kotlin-friendly!**)\n- ✅ Shader effects (Wear OS 4+)\n- ✅ Auto recomposition or re-render on state change\n- ✅ Convenience **time APIs** for clocks\n- ✅ Watch Face **specific optimizations**\n- ✅ **Instant** style changes, including while in ambient mode\n- ✅ Render your Watch Faces **in any Composable** in the phone or watch app\n- ✅ Full freedom for **complications** rendering (custom data like weather, step counts, heart rate…)\n- ✅ **Technical support** available for pro licenses\n- ⏳ Layer caching to draw very complex graphics efficiently (under development)\n\n## Getting started\n\nThis repository contains a starter project.\nAfter cloning the project and opening it in Android Studio, you can navigate to the Watch Face\nentry point: [SampleWatchFaceService.kt](app-watch/src/main/kotlin/SampleWatchFaceService.kt).\n\nIt all starts with this function inside `ComposeWatchFaceService`:\n\n```kotlin\n@Composable\noverride fun Content(complicationData: Map\u003cInt, StateFlow\u003cComplicationData\u003e\u003e) {\n    MyWatchFace()\n}\n```\n\nHere's how a simple, typical Watch Face looks like:\n\n```kotlin\n@Composable\nfun MyWatchFace() {\n    FancyBackground()\n    CoolHourPips()\n    SomeExtraStuff()\n    HourHand()\n    MinutesHand()\n    SecondsHand()\n}\n```\n\nInside those other composables, you need to use `OClockCanvas` at some point to draw things.\n\nIt's exactly like `Canvas` from Compose, except that it also takes an optional `onTap` parameter,\nand that it will work in a Watch Face.\n\nHere's how `SecondsHand()` from above could look like:\n\n```kotlin\n@Composable\nprivate fun SecondsHand() {\n    val time = LocalTime.current\n    val isAmbient by LocalIsAmbient.current // We use delegation to get a Boolean here\n    OClockCanvas {\n        if (isAmbient) return@OClockCanvas // Don't read seconds in ambient mode, return early.\n        // Because we read the time just below,\n        // this will auto-re-render when the second changes.\n        val degrees = time.seconds * 6f // 60s × 6 -\u003e 360°\n        val top = size.height / 32f\n        drawLine( // Can use anything from Compose DrawScope\n            someColor,\n            start = center.copy(y = top).rotate(degrees),\n            end = center,\n            strokeWidth = 2.dp.toPx(),\n            blendMode = BlendMode.Lighten, // Because why not! More on that below.\n            cap = StrokeCap.Round\n        )\n    }\n}\n```\n\n## `OClockCanvas`\n\n### One canvas for all by default\n\n`OClockCanvas` works just like `Canvas`:\nAll `OClockCanvas` will draw on the same backing canvas,\nwhich means that if you use a `BlendMode`, it will apply over everything that has been drawn yet.\n\nIt can have multiple use cases, like:\n- Tinting what's been drawn before (without having to touch those components), using `BlendMode.SrcIn`\n- Drawing on the pixels that haven't been drawn yet (e.g. draw a background last), using `BlendMode.DstOver`\n- Avoid overlaps, using `BlendMode.Xor` for example.\n- Make overlaps visible, using `BlendMode.Plus` or `BlendMode.Lighten` for example.\n\n### Taps\n\nWear OS Watch Faces support taps (but not swipes, which the system already uses for tiles, notifications, and quick settings).\n\nWhen displaying an `OClockCanvas` element in a regular compose hierarchy (on the phone, in a watch, or in Android Studio preview),\nthe behavior will be exactly the same as when set as a Watch Face: a swipe will cause the tap to be cancelled. Proper taps will be registered.\n\nMake sure your `onTap` handlers return `true` if the tap occurs in the region where you support actions, and `false` otherwise,\n**regardless of whether it's a tap down or up**.\n\nAlso make sure tap actions are fired only on tap-up, and make sure you're not leaving broken/unwanted state on tap cancel.\n\nIn addition to the `event`, the `onTap` lambda has an `OnTapScope` receiver, which contains the `size`, `center`, and density info, so it can be used along with the `position` `Offset` from the event to determine whether the tap should be handled or not.\n\n## Complications\n\nThe complications APIs from `androidx.wear.watchface` are… well… complicated.\nCompose O'Clock provides several Compose friendly extensions that cover all types of complications\nthat Wear OS supports.\n\n_Complication related code won't crash when running on a phone. Showing placeholder data is supported,\nand it's possible to forward live the actual complication data from the watch face to the phone app._\n\n### For `ComplicationText?` \u0026 `ComplicationText`\n\n```kotlin\n@Composable\nfun ComplicationText?.rememberMeasuredAsState(\n    default: String,\n    callMeasure: TextMeasurer.(string: String) -\u003e TextLayoutResult\n): State\u003cTextLayoutResult\u003e\n\n@Composable\nfun ComplicationText.rememberMeasuredAsState(\n    callMeasure: TextMeasurer.(string: String) -\u003e TextLayoutResult\n): State\u003cTextLayoutResult\u003e\n```\n\nNote that `TextLayoutResult` can directly be used in `drawText` inside `OClockCanvas`.\n\n### For images/icons in `ComplicationData` subclasses\n\n```kotlin\n@Composable\ninline fun NoPermissionComplicationData.rememberDrawableAsState(\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@Composable\ninline fun LongTextComplicationData.rememberDrawableAsState(\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@Composable\ninline fun MonochromaticImageComplicationData.rememberDrawableAsState(\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@Composable\ninline fun PhotoImageComplicationData.rememberDrawableAsState(\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n): State\u003cDrawable?\u003e\n\n@Composable\ninline fun RangedValueComplicationData.rememberDrawableAsState(\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@Composable\ninline fun ShortTextComplicationData.rememberDrawableAsState(\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@Composable\ninline fun SmallImageComplicationData.rememberDrawableAsState(\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@RequiresApi(33)\n@Composable\ninline fun GoalProgressComplicationData.rememberDrawableAsState(\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n\n@RequiresApi(33)\n@Composable\ninline fun WeightedElementsComplicationData.rememberDrawableAsState(\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode\n): State\u003cDrawable?\u003e\n```\n\n### Other complications related convenience extensions for advanced use cases\n\n```kotlin\nfun Drawable.setTintBlendMode(blendMode: BlendMode) // BlendMode from Compose UI graphics\nfun Drawable.setTint(color: Color) // Color from Compose UI graphics\n\n@Composable\nfun SmallImage.rememberDrawableAsState(\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode,\n): State\u003cDrawable?\u003e\n\n@Composable\nfun MonochromaticImage.rememberDrawableAsState(\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode,\n): State\u003cDrawable?\u003e\n\n@Composable\nfun rememberDrawableAsState(\n    monochromaticImage: MonochromaticImage?,\n    smallImage: SmallImage?,\n    preferSmallImage: Boolean = false,\n    tint: Color = Color.Unspecified,\n    tintBlendMode: BlendMode = BlendMode.SrcIn,\n    ambientTint: Color = tint,\n    ambientTintBlendMode: BlendMode = tintBlendMode,\n): State\u003cDrawable?\u003e\n```\n\n## License\n\nCompose O'Clock has a dual license:\n\nIt's free to use in debuggable apps, and you are free to use the dependency in any project that is compatible with the Apache 2.0 license, like this sample repository.\n\nRelease apps using Compose O'Clock require a commercial license.\n\nThe pricing model is based on a share of the Watch Face revenue.\n_For special cases, a custom contract can be negotiated._\n\nTo get in touch and get a license, send an email to [composeoclock@splitties.org](mailto:composeoclock@splitties.org).\n\n## Why Compose O'Clock?\n\nCurrently, there are 3 ways to make a Watch Face for Wear OS:\n\n1. `androidx.wear.watchface`\n2. Samsung Watch Face Studio (WFS)\n3. XML based Watch Face Format (WFF)\n\n`androidx.wear.watchface` gives a lot of flexibility, but the APIs require a lot of boilerplate,\nwhich can steer you away from finishing and refining your watch face idea.\nAlso, you get no IDE preview.\n\nCompose O'Clock uses `androidx.wear.watchface` internally, to ensure the best compatibility.\nHowever, the developer/designer experience is much improved because the API is simplified,\nmany optimizations require much fewer code, or no code, and you get super fast live-preview in\nAndroid Studio, or on a phone app. You get both WYSIWYG (What You See is What You Get),\nand the power of Kotlin code.\n\nSamsung Watch Face Studio (WFS) is a WYSIWYG editor.\nFeatures are limited, making it likely your Watch Face will look like yet another basic one.\nSamsung WFS outputs XML based Watch Face Format (WFF), so all WFF limitations apply to WFS.\n\nXML based Watch Face Format (WFF) has many limitations. Here's a non exhaustive list:\n- Works only on Wear OS 4: many non Samsung and non Google watches are still on Wear OS 2 or 3. 🚫\n- Capped at 15fps: sensor based animations, and other animations never look smooth. 🐌\n- No live-preview: you need to re-build and re-deploy each time you want to see your changes ⏳\n- Settings are very limited (numbers and gradients are not supported, unless you hack deeply) \n- You can't control the order in which settings will appear 🤷🏻🤪\n- No support for in-app purchases(!) 💸\n- No usage statistics possible\n- Near-zero connection with your users\n- No interactivity apart from switching raster images, and launching complications 🔒\n- Doesn't support all types of complications\n- No runtime logic except for basic arithmetic expressions\n- No support for shaders\n- No support for blend modes (except masking)\n- On phone editor is slow and hard to discover 🐌\n- As of Wear OS 4, the WFF renderer is no better than what's possible with `androidx.wear.watchface`\n- Publishing on the Play Store is as long as any other app, if not more, despite reduced risks\n\nIf, for some reason, you still want to try WFF, without the inconvenience of writing XML by hand,\nwe made a Kotlin DSL based on kotlinx.html: https://github.com/Splitties/WffDsl\nWe decided to not publish it because the dev UX and product potential isn't great at the moment.\n\nWe are waiting for the Wear OS team at Google to make an on-device API, so executable watch faces,\nlike ones made with Compose O'Clock, or `androidx.wear.watchface`, can update the WFF that\nwill be used for ambient mode or energy saver mode (which will make sense once the WFF renderer is improved).\n\n## Credits\n\n- Thanks to [Yuri Schimke](https://github.com/yschimke) for encouraging me to try making a Compose API for Watch Faces, and for all the helpful input! And thanks for the PRs on this repo, too🙏❤️\n- Thanks to [Romain Guy](https://github.com/romainguy) for providing helpful clarifications and sharing me his very deep and accurate knowledge on graphics programming.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsplitties%2Fcomposeoclock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsplitties%2Fcomposeoclock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsplitties%2Fcomposeoclock/lists"}