{"id":16464974,"url":"https://github.com/sellmair/pacemaker","last_synced_at":"2025-04-06T05:16:14.658Z","repository":{"id":135054649,"uuid":"604060995","full_name":"sellmair/pacemaker","owner":"sellmair","description":"App for monitoring a whole groups heart rate, notifying the whole group if one exceeds his personal limit","archived":false,"fork":false,"pushed_at":"2025-01-07T06:23:37.000Z","size":3580,"stargazers_count":121,"open_issues_count":4,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-30T03:11:04.283Z","etag":null,"topics":["app","bluetooth","bluetooth-low-energy","heart-rate","kmp","kotlin","running"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sellmair.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"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":"2023-02-20T08:53:20.000Z","updated_at":"2025-01-16T12:39:37.000Z","dependencies_parsed_at":"2024-10-11T11:31:10.401Z","dependency_job_id":"7f2a9210-3273-427c-9275-3a7ae023db45","html_url":"https://github.com/sellmair/pacemaker","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sellmair%2Fpacemaker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sellmair%2Fpacemaker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sellmair%2Fpacemaker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sellmair%2Fpacemaker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sellmair","download_url":"https://codeload.github.com/sellmair/pacemaker/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247436286,"owners_count":20938533,"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":["app","bluetooth","bluetooth-low-energy","heart-rate","kmp","kotlin","running"],"created_at":"2024-10-11T11:31:04.670Z","updated_at":"2025-04-06T05:16:14.640Z","avatar_url":"https://github.com/sellmair.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pacemaker (iOS \u0026 Android) [KMP]\n[![Build](https://github.com/sellmair/pacemaker/actions/workflows/build.yaml/badge.svg)](https://github.com/sellmair/pacemaker/actions/workflows/build.yaml)\n\nRun together! A running companion monitoring the heart rate of a group of people doing sports together.\n\n## Supports\n\n- External Bluetooth (LE) heart rate monitors (tested with Polar H10)\n- iPhone \u003c-\u003e iPhone connections (No internet necessary, BLE)\n- Android \u003c-\u003e Android connections (No internet necessary, BLE)\n- iPhone \u003c-\u003e Android connections (No internet necessary, BLE)\n\n## Planned\n\nWatchOS support (via UWB chip and Internet)\n\n## Screenshots\n\n\u003cp float=\"left\"\u003e\n    \u003cimg src=\"/.img/screenshot-ios-1.png\" alt=\"Screenshot iOS\" width=\"250\"\u003e\n    \u003cimg src=\"/.img/screenshot-ios-2.png\" alt=\"Screenshot iOS\" width=\"250\"\u003e\n    \u003cimg src=\"/.img/screenshot-android-1.png\" alt=\"Screenshot iOS\" width=\"250\"\u003e\n\u003c/p\u003e\n\n## Install\n\n- Google Play: https://play.google.com/store/apps/details?id=io.sellmair.pacemaker\n- Apple App Store: https://apps.apple.com/us/app/pacemaker-heart-rate-monitor/id6446760560\n\n# Technical Details\n\n## Kotlin Multiplatform\n\nThis application is built as a Test/Dogfooding project for Kotlin/Multiplatform, Compose and JetBrains Fleet.\n\n## Architecture\n\n### No! ViewModels!: This project uses 'State Actors' instead.\n\nThe State Actor pattern used in 'Pacemaker' can be defined by two high level concepts:\n\n#### Events\n\nEvery component in the application can emit any kind of event, including intents.\nExample: Some UI button that emits an event to the application\n\n```kotlin\n@Composable\nfun MyButton() {\n    Button(\n        onClick = Launching { LoginIntent.emit() }\n    ) {\n        // ...\n    }\n}\n```\n\n#### State Producers\n\nStates can be produced and observed. Lets look at the producing site first:\u003cbr\u003e\nLets take the classic login example:\n\n```kotlin\ndata class LoginState(val email: String, val password: String, val isLoggedIn: Boolean) : State {\n    companion object Key : State.Key\u003cLoginState\u003e {\n        val default get() = LoginState(email = \"\", password = \"\", isLoggedIn = false)\n    }\n}\n\nfun CoroutineScope.launchLoginStateActor() = launchState(LoginState) {\n    var state = LoginState.default\n\n    collectEventsAsync\u003cEmailChangedEvent\u003e {\n        state = state.copy(email = it.email)\n        state.emit()\n    }\n\n    collectEventsAsync\u003cPasswordChangedEvent\u003e {\n        state = state.copy(password = it.password)\n        state.emit()\n    }\n\n    collectEventsAsync\u003cLoginIntent\u003e {\n        val isLoggedIn = attemptLogin(state.email, state.password)\n        state = state.copy(isLoggedIn = isLoggedIn)\n        state.emit()\n    }\n}\n```\n\nSuch states can then be used in the Application UI/Frontend easily\n\n```kotlin\n@Composable\nfun MyLoginScreen() {\n    val loginState by LoginState.collectAsState()\n    MyLoginScreen(\n        email = loginState.email,\n        password = loginState.password\n    )\n}\n\n@Composable\nfun MyLoginScreen(\n    email: String,\n    password: String\n) {\n    Text(email)\n    Text(password)\n    Button(\n        onClick = Launching { LoginIntent.emit() }\n    ) {\n        Text(\"Login\")\n    }\n}\n```\n\n### Libraries used\n\n- kotlinx.coroutines\n- kotlinx.datetime\n- SQLDelight\n- Multiplatform Settings\n- Okio\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsellmair%2Fpacemaker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsellmair%2Fpacemaker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsellmair%2Fpacemaker/lists"}