diff --git a/.github/actions/flutter-prepare/action.yml b/.github/actions/flutter-prepare/action.yml new file mode 100644 index 00000000..f40b9d5a --- /dev/null +++ b/.github/actions/flutter-prepare/action.yml @@ -0,0 +1,27 @@ +name: Flutter Prepare +description: Set up Flutter from .flutter_version and fetch dependencies. + +inputs: + app-dir: + description: Path to the Flutter application directory. + required: true + +runs: + using: composite + steps: + - name: Read Flutter version + id: flutter_version + shell: bash + run: echo "version=$(cat ${{ inputs.app-dir }}/.flutter_version)" >> "$GITHUB_OUTPUT" + + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: ${{ steps.flutter_version.outputs.version }} + cache: true + + - name: Install dependencies + shell: bash + working-directory: ${{ inputs.app-dir }} + run: flutter pub get diff --git a/.github/workflows/pr_build_all_platforms.yml b/.github/workflows/pr_build_all_platforms.yml new file mode 100644 index 00000000..7c868be5 --- /dev/null +++ b/.github/workflows/pr_build_all_platforms.yml @@ -0,0 +1,216 @@ +name: Build PR Artifacts + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + +permissions: + contents: read + pull-requests: write + +concurrency: + group: pr-build-artifacts-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + APP_DIR: open_wearable + +jobs: + build_android: + name: Build Android + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload_android.outputs.artifact-id }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: "17" + cache: gradle + + - name: Set up Flutter project + uses: ./.github/actions/flutter-prepare + with: + app-dir: ${{ env.APP_DIR }} + + - name: Build Android APK + working-directory: ${{ env.APP_DIR }} + run: flutter build apk --release + + - name: Upload Android APK + id: upload_android + uses: actions/upload-artifact@v6 + with: + name: android-apk + path: open_wearable/build/app/outputs/flutter-apk/app-release.apk + if-no-files-found: error + + build_linux: + name: Build Linux + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload_linux.outputs.artifact-id }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Install Linux build dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + clang \ + cmake \ + libgstreamer1.0-dev \ + libgstreamer-plugins-base1.0-dev \ + gstreamer1.0-plugins-base \ + libgtk-3-dev \ + ninja-build \ + pkg-config + + - name: Set up Flutter project + uses: ./.github/actions/flutter-prepare + with: + app-dir: ${{ env.APP_DIR }} + + - name: Build Linux bundle + working-directory: ${{ env.APP_DIR }} + run: flutter build linux --release + + - name: Upload Linux bundle + id: upload_linux + uses: actions/upload-artifact@v6 + with: + name: linux-bundle + path: open_wearable/build/linux/x64/release/bundle + if-no-files-found: error + + build_windows: + name: Build Windows + runs-on: windows-latest + outputs: + artifact_id: ${{ steps.upload_windows.outputs.artifact-id }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Flutter project + uses: ./.github/actions/flutter-prepare + with: + app-dir: ${{ env.APP_DIR }} + + - name: Build Windows runner + working-directory: ${{ env.APP_DIR }} + run: flutter build windows --release + + - name: Upload Windows runner + id: upload_windows + uses: actions/upload-artifact@v6 + with: + name: windows-runner + path: open_wearable/build/windows/x64/runner/Release + if-no-files-found: error + + build_web: + name: Build Web + runs-on: ubuntu-latest + outputs: + artifact_id: ${{ steps.upload_web.outputs.artifact-id }} + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Set up Flutter project + uses: ./.github/actions/flutter-prepare + with: + app-dir: ${{ env.APP_DIR }} + + - name: Build Web bundle + working-directory: ${{ env.APP_DIR }} + run: flutter build web --release + + - name: Upload Web bundle + id: upload_web + uses: actions/upload-artifact@v6 + with: + name: web-bundle + path: open_wearable/build/web + if-no-files-found: error + + comment_artifact_links: + name: Comment Artifact Links + runs-on: ubuntu-latest + needs: + - build_android + - build_linux + - build_windows + - build_web + if: always() + + steps: + - name: Post or update PR comment with artifact links + uses: actions/github-script@v8 + env: + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + ANDROID_ARTIFACT_ID: ${{ needs.build_android.outputs.artifact_id }} + LINUX_ARTIFACT_ID: ${{ needs.build_linux.outputs.artifact_id }} + WINDOWS_ARTIFACT_ID: ${{ needs.build_windows.outputs.artifact_id }} + WEB_ARTIFACT_ID: ${{ needs.build_web.outputs.artifact_id }} + with: + script: | + const marker = ''; + const prNumber = context.payload.pull_request.number; + const runUrl = process.env.RUN_URL; + const repoUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}`; + const linkFor = (id) => id + ? `${repoUrl}/actions/runs/${context.runId}/artifacts/${id}` + : runUrl; + + const body = `${marker} + ### PR Build Artifacts + - Android APK: [Download Android build](${linkFor(process.env.ANDROID_ARTIFACT_ID)}) + - Linux Bundle: [Download Linux build](${linkFor(process.env.LINUX_ARTIFACT_ID)}) + - Windows Runner: [Download Windows build](${linkFor(process.env.WINDOWS_ARTIFACT_ID)}) + - Web Bundle: [Download Web build](${linkFor(process.env.WEB_ARTIFACT_ID)}) + + Full workflow run: ${runUrl}`; + + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const previous = comments.find( + (comment) => + comment.user.type === 'Bot' && + comment.body && + comment.body.includes(marker) + ); + + if (previous) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: previous.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body, + }); + } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f0bb47af --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,187 @@ +# Contributing + +This project expects contributions to be technically clean, easy to review, and safe to maintain. The rules in this guide are the default contribution standard for all changes in this repository. + +## Core Principles + +- Prefer small, focused pull requests over broad mixed changes. +- Preserve a linear Git history. Rebase instead of merging. +- Use conventional commits so history remains searchable and automatable. +- Document code so the next contributor can understand intent, contracts, and tradeoffs. +- Leave the codebase in a better state than you found it. + +## Development Setup + +1. Install Flutter on the stable channel. +2. Clone the repository. +3. Fetch dependencies in the Flutter app module: + +```bash +cd open_wearable +flutter pub get +``` + +4. Run the app locally when needed: + +```bash +flutter run +``` + +## Branching And Git Workflow + +### Branching + +- Create a dedicated branch for every change. +- Branch from the latest `main`. +- Use descriptive branch names, for example: + - `feat/device-reconnect-flow` + - `fix/audio-playback-timeout` + - `docs/state-provider-guide` + +### Rebase Policy + +This repository uses rebases instead of merges to keep history linear and readable. + +- Rebase your branch onto `main` regularly. +- Do not merge `main` into your feature branch. +- Before opening or updating a pull request, rebase onto the current `main`. +- When updating your remote branch after a rebase, use `--force-with-lease`, never plain `--force`. + +Recommended workflow: + +```bash +git checkout main +git pull --rebase origin main +git checkout +git rebase main +``` + +If you need to update the remote branch after rebasing: + +```bash +git push --force-with-lease +``` + +### Commit Hygiene + +- Keep commits focused and logically grouped. +- Do not mix refactors, behavior changes, formatting-only changes, and documentation churn in one commit unless they are inseparable. +- Squash fixup noise before merging unless the intermediate commits are intentionally meaningful. + +## Conventional Commits + +All commits must follow the [Conventional Commits](https://www.conventionalcommits.org/) format: + +```text +(): +``` + +Examples: + +```text +feat(connectors): add websocket reconnect backoff +fix(audio): prevent duplicate playback startup +docs(state): clarify provider ownership rules +refactor(devices): simplify connection status handling +test(sensors): cover merged configuration rendering +chore(ci): run analyze on pull requests +``` + +### Allowed Types + +- `feat`: new user-facing or developer-facing functionality +- `fix`: bug fix +- `refactor`: structural improvement without intended behavior change +- `docs`: documentation-only change +- `test`: test additions or test-only updates +- `chore`: maintenance, tooling, or housekeeping +- `build`: build system or dependency updates +- `ci`: CI workflow changes +- `perf`: measurable performance improvement + +### Commit Rules + +- Write summaries in the imperative mood. +- Keep the subject line concise and specific. +- Use a scope when it adds clarity. +- Mark breaking changes explicitly in the body or footer when applicable. + +## Code Quality Expectations + +### Architecture + +- Prefer clear separation of responsibilities. +- Avoid tightly coupling UI, state management, device communication, and persistence concerns. +- Extend existing patterns before introducing new abstractions. +- If adding a new abstraction, document why it is needed and what problem it solves. +- Remove dead code, stale branches, and unused indirection when touching a relevant area. + +### Documentation + +Code must be documented, especially when it defines reusable behavior or non-obvious decisions. + +- Add documentation comments to public classes, public functions, extensions, and significant state objects. +- Document inputs, outputs, side effects, invariants, and failure behavior when they are not obvious. +- Add short intent comments for complex logic blocks where structure alone is insufficient. +- Keep documentation synchronized with the implementation. +- Update the relevant Markdown docs in `open_wearable/docs/` when a change affects architecture, app flow, or contributor-facing behavior. + +### Style + +- Follow the existing project structure and naming conventions. +- Prefer readability over cleverness. +- Avoid broad drive-by reformatting in unrelated files. +- Keep files cohesive. If a file becomes too large or mixes responsibilities, split it deliberately. + +## Validation Before Opening A Pull Request + +Run validation from `open_wearable/` unless the change clearly does not affect the Flutter module. + +```bash +dart format lib test +flutter analyze +flutter test +``` + +Expectations: + +- New behavior should include tests when feasible. +- Bug fixes should include a regression test when practical. +- If a test is not possible, explain why in the pull request. +- Do not open a pull request with knowingly failing analysis or tests. + +## Pull Request Guidelines + +- Keep pull requests small enough for a focused review. +- Use a clear title consistent with the resulting change. +- Describe the problem, the chosen solution, and any important tradeoffs. +- Call out risky areas, migrations, or follow-up work explicitly. +- Include screenshots or recordings for UI changes when helpful. +- Resolve review comments with follow-up commits or a rebase/squash workflow that preserves clarity. + +Before requesting review, confirm that: + +- your branch is rebased onto the latest `main` +- commits follow conventional commit rules +- code and public APIs are documented +- formatting, analysis, and tests pass +- documentation is updated where needed + +## What To Avoid + +- Merging `main` into feature branches +- Force-pushing with `--force` +- Large unrelated cleanup bundled into feature work +- Undocumented public APIs +- Hidden behavior changes without tests or explanation +- Drive-by dependency upgrades without justification + +## Questions And Ambiguity + +When the correct approach is unclear: + +- prefer the simpler design +- document assumptions in the pull request +- ask for clarification before introducing a large or irreversible change + +Clean history, well-scoped commits, and documented code are part of the feature, not optional polish. diff --git a/README.md b/README.md index 95b6ddf8..99b14a80 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ To get started with the OpenEarable App, you need to have the following: - An OpenEarable device with the newest firmware - A working flutter installation +## Contributing + +Contributor expectations and workflow rules are documented in [CONTRIBUTING.md](./CONTRIBUTING.md). + ### Run the app 1. Clone this repository ``` @@ -38,4 +42,3 @@ To get started with the OpenEarable App, you need to have the following: flutter run --release ``` and select your phone as the target device from the list of connected devices. - diff --git a/open_wearable/README.md b/open_wearable/README.md index fc7cdbe9..653509be 100644 --- a/open_wearable/README.md +++ b/open_wearable/README.md @@ -1,16 +1,28 @@ -# open_wearable +# OpenWearable App Module -A new Flutter project. +Flutter application module for the OpenEarable app. -## Getting Started +## Documentation -This project is a starting point for a Flutter application. +High-level architecture and state-management docs live in [`docs/`](./docs/README.md). -A few resources to get you started if this is your first Flutter project: +- [App Setup and Architecture](./docs/app-setup.md) +- [State and Providers](./docs/state-and-providers.md) -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +## Development Quick Start -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +1. Install Flutter (stable channel). +2. From this folder (`open_wearable/`), fetch dependencies: + ```bash + flutter pub get + ``` +3. Run on a connected device/emulator: + ```bash + flutter run + ``` + +## Notes + +- Core app bootstrap is in `lib/main.dart`. +- Route definitions are in `lib/router.dart`. +- High-level feature state is primarily under `lib/view_models/`. diff --git a/open_wearable/android/app/src/main/AndroidManifest.xml b/open_wearable/android/app/src/main/AndroidManifest.xml index 8cb19723..8cb9f463 100644 --- a/open_wearable/android/app/src/main/AndroidManifest.xml +++ b/open_wearable/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - +