From a615bb03003d248c1178b38f4fcbe899b04f45a3 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:40:15 -0700 Subject: [PATCH 01/87] Initial Commit of CI Pipeline Original development was done in a fork, and certain processes changed many additional files. A fresh commit (manually copied new files) ensures changes can be better tracked. This commit introduces all workflows, NPM dependencies, and the build-reports directory (for a web view of CI outputs). The last feature will not work until a GB admin enables Pages on the repo. --- .github/README.md | 43 ++ .github/workflows/build_artifacts.yml | 112 ++++ .github/workflows/format_and_lint.yml | 102 ++++ .github/workflows/reporting_orchestrator.yml | 136 +++++ .../workflows/test_and_report_coverage.yml | 143 +++++ .prettierignore | 3 + README.md | 18 +- build_reports/.gitignore | 8 + build_reports/README.md | 5 + build_reports/index.html | 81 +++ build_reports/styles.css | 97 ++++ package-lock.json | 537 ++++++++++++++++++ package.json | 15 + 13 files changed, 1296 insertions(+), 4 deletions(-) create mode 100644 .github/README.md create mode 100644 .github/workflows/build_artifacts.yml create mode 100644 .github/workflows/format_and_lint.yml create mode 100644 .github/workflows/reporting_orchestrator.yml create mode 100644 .github/workflows/test_and_report_coverage.yml create mode 100644 .prettierignore create mode 100644 build_reports/.gitignore create mode 100644 build_reports/README.md create mode 100644 build_reports/index.html create mode 100644 build_reports/styles.css create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 000000000..6b47a7354 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,43 @@ +# Mesh CI + +--- + +## Pipeline Flow + +The CI pipeline behaves as follows: + +1. run Prettier on web-related filetypes, as well as XML + a. Do not use prettier-plugin-kotlin, it is not maintained and errors regularly +2. On push, run super-linter. For all possible cases, run autofix (this covers Kotlin) + +- Needs updating + +--- + +## General TODO + +### Ongoing + +- For all workflows, add current build summary as commit/pr comments +- Alter system to use GitHub Pages instead of github HTML preview. This will fix CSS on previews + - Add script to change iFrame title and onscreen title + pass/fail indicator +- Add orchestrator workflow for all reporting tasks + - determine how to allow multiple reusable workflows to share build cache + - add link to super-linter actions output + - onsider whether to delete reports files after use + - add setting on manual run whether to commit anything/deploy to pages +- replace build-artifacts setup gradle with coverage setup gradle + +### Backlog + +- Integrate with Dokka +- Test auto UML diagramming +- Break workflows out into multiple intelligently-grouped jobs for improved execution visibility + +--- + +## Integration TODO + +- Update Super-Linter reusable workflow to point locally instead of to Thalia Wood's (polygeist111) copy +- Update any GitHub pages links in workflow doc to point to correct GB Page +- Petition Chris to configure GitHub pages on the repo \ No newline at end of file diff --git a/.github/workflows/build_artifacts.yml b/.github/workflows/build_artifacts.yml new file mode 100644 index 000000000..55f31cca7 --- /dev/null +++ b/.github/workflows/build_artifacts.yml @@ -0,0 +1,112 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle + +name: Build APKs +run-name: ${{ github.event.inputs.custom_run_name || github.workflow }} + +on: + # run when called by another workflow + workflow_call: + inputs: + skip_commit: + description: "Skip committing reports" + required: false + type: boolean + default: false + + # run on demand + workflow_dispatch: + inputs: + custom-run-name: + description: "Custom name for this Actions run" + required: false + type: string + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-Build APKs + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Notify User to Scroll + run: | + echo "## Scroll to the end of the page for artifacts :arrow_down:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. + # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md + - name: Setup Gradle + uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + + #- name: Change wrapper permissions + # run: chmod +x ./gradlew + + - name: Build with Gradle Wrapper + run: ./gradlew build + continue-on-error: true # allows APK build even if tests fail + + - name: Upload App Debug APK + uses: actions/upload-artifact@v4 + with: + name: app-debug-apk # Optional: Name your artifact + path: app/build/outputs/apk/debug/app-debug.apk # Path to your build output (e.g., JARs, WARs) + retention-days: 1 # Optional: Set a custom retention period in days + + - name: Upload App Release APK + uses: actions/upload-artifact@v5 + with: + name: app-release-apk # Optional: Name your artifact + path: app/build/outputs/apk/release/app-release-unsigned.apk # Path to your build output (e.g., JARs, WARs) + retention-days: 1 # Optional: Set a custom retention period in days + # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). + # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. + # + # - name: Setup Gradle + # uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 + # with: + # gradle-version: '8.9' + # + # - name: Build with Gradle 8.9 + # run: gradle build + + dependency-submission: + name: Submit Dependencies + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: "17" + distribution: "temurin" + + # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. + # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md + - name: Generate and submit dependency graph + uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml new file mode 100644 index 000000000..b087b8c26 --- /dev/null +++ b/.github/workflows/format_and_lint.yml @@ -0,0 +1,102 @@ +name: Lint Code Base +run-name: ${{ github.event.inputs.custom_run_name || github.workflow }} + +on: + # run when called by another workflow + workflow_call: + inputs: + skip_commit: + description: "Skip committing reports" + required: false + type: boolean + default: false + + # run on demand + workflow_dispatch: + inputs: + custom-run-name: + description: "Custom name for this Actions run" + required: false + type: string + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-Lint Code Base + cancel-in-progress: true + +permissions: read-all + +# adapted from https://mskelton.medium.com/auto-formatting-code-using-prettier-and-github-actions-ed458f58b7df +jobs: + call-gradle-linter: + name: Call Gradle Linter + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Set Up Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Call Debug and Release Linters + # ostensibly, ./gradlew lint should run both, but in practice on my (Thalia Wood's), it only runs the Debug linter + run: | + ./gradlew lintDebug + ./gradlew lintRelease + + - name: Configure Git Identity (if called individually) + if: ${{ inputs.skip_commit != true }} + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + + - name: Copy Linting Reports to Viewing Directory (if called individually) + if: ${{ inputs.skip_commit != true }} + run: | + mkdir ./build_reports/lint_results_debug + cp ./app/build/reports/lint-results-debug.html ./build_reports/lint_report_debug/lint_report_debug.html + mkdir ./build_reports/lint_results_release + cp ./app/build/reports/lint-results-release.html ./build_reports/lint_report_release/lint_report_release.html + git add --force ./build_reports/ + git commit -m "Relocate coverage report for viewing" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Lint Debug Artifact (if called by orchestrator) + if: ${{ inputs.skip_commit == true }} + uses: actions/upload-artifact@v5 + with: + name: lint_report_debug_artifact + path: ./app/build/reports/lint-results-debug.html + retention-days: 1 + + - name: Upload Lint Release Artifact (if called by orchestrator) + if: ${{ inputs.skip_commit == true }} + uses: actions/upload-artifact@v5 + with: + name: lint_report_release_artifact + path: ./app/build/reports/lint-results-release.html + retention-days: 1 + + call-super-linter: + name: Call Super-Linter + permissions: + contents: read # clone the repo to lint + statuses: write # read/write to repo custom statuses + + uses: polygeist111/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main + # TODO: update url to point to gb reusable linter, not thalia's + + # with: + ### A regex to exclude files from linting + ### defaults to empty + # filter-regex-exclude: diff --git a/.github/workflows/reporting_orchestrator.yml b/.github/workflows/reporting_orchestrator.yml new file mode 100644 index 000000000..4232fee55 --- /dev/null +++ b/.github/workflows/reporting_orchestrator.yml @@ -0,0 +1,136 @@ +name: Compile Build Reports +run-name: ${{ github.event.inputs.custom_run_name || github.workflow }} + +on: + # run anytime a PR is merged to main or a direct push to main + push: + branches: [main] + + # run on any push to a PR branch + pull_request: + + # run on demand + workflow_dispatch: + inputs: + custom-run-name: + description: "Custom name for this Actions run" + required: false + type: string + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + format-and-lint: + name: Format and Lint + uses: ./.github/workflows/format_and_lint.yml + with: + skip_commit: true + + test-and-report-coverage: + name: Test and Report Coverage + uses: ./.github/workflows/test_and_report_coverage.yml + with: + skip_commit: true + + upload-apks: + name: Upload APKs + # needs: build-and-test # uses cached build from prior job + uses: ./.github/workflows/build_artifacts.yml + + finalize-report-viewer: + name: Finalize Report Viewer + needs: [format-and-lint, test-and-report-coverage] + if: always() + runs-on: ubuntu-latest + permissions: + contents: write + actions: write + id-token: write + pages: write + environment: + name: github-pages + url: ${{steps.deployment.outputs.page_url}} + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Ensure Report Dir is empty + run: | + export GLOBIGNORE="./build_reports/index.html:./build_reports/styles.css:./build_reports/README.md:./build_reports/.gitignore" + rm -rf ./build_reports/* + unset GLOBIGNORE + + - name: Download All Artifacts + uses: actions/download-artifact@v5 + with: + path: ./build_reports/ + # TODO: edit to never pull APKs + + - name: Relocate Linting Reports to Dirs + run: | + rm -rf ./build_reports/app-debug-apk 2> /dev/null + rm -rf ./build_reports/app-release-apk 2> /dev/null + + mkdir ./build_reports/coverage_report + cp -a ./build_reports/coverage_report_artifact/. ./build_reports/coverage_report/ + rm -r ./build_reports/coverage_report_artifact + + mkdir ./build_reports/lint_report_debug + cp ./build_reports/lint_report_debug_artifact/lint-results-debug.html ./build_reports/lint_report_debug/lint_report_debug.html + rm -r ./build_reports/lint_report_debug_artifact + + ls ./build_reports + echo "----" + ls ./build_reports/lint_report_debug + + sed -i 's/1px; 1px;/1px 1px;/g' ./build_reports/lint_report_debug/lint_report_debug.html + + mkdir ./build_reports/lint_report_release + cp ./build_reports/lint_report_release_artifact/lint-results-release.html ./build_reports/lint_report_release/lint_report_release.html + rm -r ./build_reports/lint_report_release_artifact + + ls ./build_reports + echo "----" + ls ./build_reports/lint_report_release + + sed -i 's/1px; 1px;/1px 1px;/g' ./build_reports/lint_report_release/lint_report_release.html + + mkdir ./build_reports/test_report_debug + cp -a ./build_reports/test_report_debug_artifact/. ./build_reports/test_report_debug/ + rm -r ./build_reports/test_report_debug_artifact + + mkdir ./build_reports/test_report_release + cp -a ./build_reports/test_report_release_artifact/. ./build_reports/test_report_release/ + rm -r ./build_reports/test_report_release_artifact + + - name: Configure Git Identity + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + + - name: Commit Reports to Viewing Directory + run: | + git add --force ./build_reports/ + git commit -m "Relocate reports for viewing" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Reports Viewer Directory to Pages + uses: actions/upload-pages-artifact@v3 + with: + path: ./build_reports + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + + - name: Link Pages in Build Summary + run: | + echo "All reports can be viewed at ${{ steps.deployment.outputs.page_url }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/test_and_report_coverage.yml b/.github/workflows/test_and_report_coverage.yml new file mode 100644 index 000000000..901f2b7c8 --- /dev/null +++ b/.github/workflows/test_and_report_coverage.yml @@ -0,0 +1,143 @@ +name: Measure Test Coverage +run-name: ${{ github.event.inputs.custom_run_name || github.workflow }} + +on: + # run when called by another workflow + workflow_call: + inputs: + skip_commit: + description: "Skip committing reports" + required: false + type: boolean + default: false + + # run on demand + workflow_dispatch: + inputs: + custom-run-name: + description: "Custom name for this Actions run" + required: false + type: string + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-Measure Test Coverage + cancel-in-progress: true + +jobs: + test_and_report_coverage: + name: Test and Report Coverage + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Define Coverage Minimums + run: | + echo "min-coverage-overall=80" >> $GITHUB_ENV + echo "min-coverage-changed-files=80" >> $GITHUB_ENV + - uses: actions/checkout@v4 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Set Up Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Generate Kover Coverage Report + id: koverXmlReport + run: ./gradlew koverXmlReport --continue + continue-on-error: true + + - name: Download Kover CLI (only on test failure) + if: steps.koverXmlReport.outcome == 'failure' + run: wget -O kover-cli.jar https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kover-cli/0.9.3/kover-cli-0.9.3.jar + # ensure the versions in the link match the kover version in the top-level build.gradle.kts file + + - name: Format Kover XML Coverage Report Via CLI (only on test failure) + if: steps.koverXmlReport.outcome == 'failure' + run: java -jar kover-cli.jar report ./app/build/kover/bin-reports/testDebugUnitTest.ic --classfiles ./app/build/tmp/kotlin-classes --src ./app/src/main/java --xml ./app/build/reports/kover/report.xml + + - name: Format Kover HTML Coverage Report Via CLI (only on test failure) + if: steps.koverXmlReport.outcome == 'failure' + run: java -jar kover-cli.jar report ./app/build/kover/bin-reports/testDebugUnitTest.ic --classfiles ./app/build/tmp/kotlin-classes --src ./app/src/main/java --html ./app/build/reports/kover/html/ + + - name: Add Coverage Report to PR + id: kover + uses: mi-kas/kover-report@v1 + with: + path: ${{ github.workspace }}/app/build/reports/kover/report.xml + title: Code Coverage + update-comment: true + min-coverage-overall: ${{ env.min-coverage-overall }} + min-coverage-changed-files: ${{ env.min-coverage-changed-files }} + coverage-counter-type: LINE + + - name: Configure Git Identity (if called individually) + if: ${{ inputs.skip_commit != true }} + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + + - name: Copy Coverage Report & Test Outputs to Viewing Directory (if called individually) + if: ${{ inputs.skip_commit != true }} + run: | + cp -r ./app/build/reports/kover/html/ ./build_reports/coverage_report + cp -r ./app/build/reports/tests/testDebugUnitTest ./build_reports/test_report_debug + cp -r ./app/build/reports/tests/testReleaseUnitTest ./build_reports/test_report_release + git add --force ./build_reports/ + git commit -m "Relocate coverage report for viewing" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Coverage Report Artifact (if called by orchestrator) + if: ${{ inputs.skip_commit == true }} + uses: actions/upload-artifact@v5 + with: + name: coverage_report_artifact + path: ./app/build/reports/kover/html + retention-days: 1 + + - name: Upload Test Debug Artifact (if called by orchestrator) + if: ${{ inputs.skip_commit == true }} + uses: actions/upload-artifact@v5 + with: + name: test_report_debug_artifact + path: ./app/build/reports/tests/testDebugUnitTest + retention-days: 1 + + - name: Upload Test Release Artifact (if called by orchestrator) + if: ${{ inputs.skip_commit == true }} + uses: actions/upload-artifact@v5 + with: + name: test_report_release_artifact + path: ./app/build/reports/tests/testReleaseUnitTest + retention-days: 1 + + - name: Add Summary to Workflow Run + run: | + echo "| Type | Coverage | Passing |" >> $GITHUB_STEP_SUMMARY + echo "|---|---|---|" >> $GITHUB_STEP_SUMMARY + echo "| Overall Coverage | ${{ steps.kover.outputs.coverage-overall }}% | ${{ steps.kover.outputs.coverage-overall >= env.min-coverage-overall && ':white_check_mark:' || ':x:' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Changed File Coverage | ${{ steps.kover.outputs.coverage-changed-files }}% | ${{ steps.kover.outputs.coverage-changed-files >= env.min-coverage-changed-files && ':white_check_mark:' || ':x:' }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "(Changed File Coverage is currently bugged, disregard)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Full coverage report can be viewed at https://html-preview.github.io/?url=https://github.com/polygeist111/Project-Mesh/blob/main/build_reports/coverage_report/index.html" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "---" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + # TODO: update coverage report link to point to GB + # configure Mesh for GitHub Pages (need to ask a higher-up) + + - name: Verify Test/Build Outcome + if: steps.koverXmlReport.outcome == 'failure' + run: | + echo "Tests (or build) failed, marking workflow as failed" + exit 1 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..00543e568 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +build +coverage +node_modules \ No newline at end of file diff --git a/README.md b/README.md index ddb1d424c..9fc333137 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,29 @@ # Project Mesh -------------------- + +--- + Grey-box.ca New version by wil-mesh-rmit -Project Mesh runs locally on an android device. Build the project then either export the APK and send to the device, or use ADB to install. The app only works on physical devices, and not the android simulator. +Project Mesh runs locally on an android device. Build the project then either export the APK and send to the device, or use ADB to install. The app only works on physical devices, and not the android simulator. + +
+ +![Super-Linter](https://github.com/polygeist111/project-mesh/actions/workflows/format_and_lint.yml/badge.svg) +![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square) + +
# Deployment Instructions -The Android app runs independently of an internet connection. The APK needs to be installed in each phone, then the phones are able to connect to each other from within the app. + +The Android app runs independently of an internet connection. The APK needs to be installed in each phone, then the phones are able to connect to each other from within the app. + 1. Open the app 2. When prompted, allow location and nearby devices permissions 3. Wait for a QR code to appear on screen 4. On each phone, use the ‘Scan QR code’ button to scan the codes of adjacent devices 5. Messages are able to be sent to connected devices with the ‘Send’ button and ‘Message’ text box - Credential info: N/A GitHub URL: https://github.com/grey-box/Project-Mesh diff --git a/build_reports/.gitignore b/build_reports/.gitignore new file mode 100644 index 000000000..4703dcaf1 --- /dev/null +++ b/build_reports/.gitignore @@ -0,0 +1,8 @@ +# ignore all +* + +# except +!.gitignore +!README.md +!index.html +!styles.css \ No newline at end of file diff --git a/build_reports/README.md b/build_reports/README.md new file mode 100644 index 000000000..0369efca6 --- /dev/null +++ b/build_reports/README.md @@ -0,0 +1,5 @@ +# Reports Output + +The purpose of this folder is to store linkable copies of reports generated by CI actions for viewing on the web. + +**No files should ever be manually put here.** diff --git a/build_reports/index.html b/build_reports/index.html new file mode 100644 index 000000000..33c9616a1 --- /dev/null +++ b/build_reports/index.html @@ -0,0 +1,81 @@ + + + + Grey-Box Project Mesh: Build Diagnostics + + + + + + +
+
+
+ Mesh GitHub ↗ + +
+ Grey-Box logo +
+
+ +
+
+
+ +
+
+
+ + + diff --git a/build_reports/styles.css b/build_reports/styles.css new file mode 100644 index 000000000..92e618aa3 --- /dev/null +++ b/build_reports/styles.css @@ -0,0 +1,97 @@ +:root { + --gb-bg-green: #3d8570; + --gb-link-blue: #51a3ec; +} + +html, +body { + margin: 0; + padding: 0; + width: 100%; +} + +body { + font-family: + Open Sans, + Arial, + sans-serif; + font-size: 14px; + color: #666666; + background-color: #ffffff; + padding: 0; +} + +body a { + color: var(--gb-link-blue); + text-shadow: 2px 2px 7px black; + text-decoration: none; +} + +body a:hover { + text-decoration: underline; +} + +header { + background-color: var(--gb-bg-green); + position: sticky; + padding: 2vh; +} + +.grid-img { + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; +} + +header #home_link { + font-size: 2rem; + padding-left: 1vw; +} + +nav { + padding-top: 1vh; + display: flex; + flex-direction: column; + padding-left: 5vw; +} +nav a { + font-size: 1.1rem; + padding-right: 5vw; + white-space: nowrap; +} + +main { + margin-top: 2vh; + margin-bottom: 1vh; +} + +iframe { + width: 90vw; + height: 75vh; +} + +footer p { + margin: 0; + text-align: center; +} + +.header-grid { + display: grid; + grid-template-columns: 1fr 96px; +} + +.centered-content { + display: flex; + justify-content: center; + align-items: center; +} + +@media screen and (min-width: 492px) { + /* Styles applied when the screen width is 768px or less */ + nav { + flex-direction: row; + flex-wrap: wrap; + padding-left: 1vw; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..035bbd2bf --- /dev/null +++ b/package-lock.json @@ -0,0 +1,537 @@ +{ + "name": "Project-Mesh", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "prettier": "^3.6.2" + } + }, + "node_modules/ansi-escapes": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz", + "integrity": "sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", + "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lint-staged": { + "version": "16.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.7.tgz", + "integrity": "sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.2", + "listr2": "^9.0.5", + "micromatch": "^4.0.8", + "nano-spawn": "^2.0.0", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.8.1" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nano-spawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/nano-spawn?sponsor=1" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..4b08fa81a --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "scripts": { + "format": "prettier --write . --ignore-unknown", + "prepare": "husky install", + "lint-staged": "lint-staged" + }, + "lint-staged": { + "**/*": "prettier --write --ignore-unknown" + }, + "devDependencies": { + "husky": "^9.1.7", + "lint-staged": "^16.2.6", + "prettier": "^3.6.2" + } +} From 0900426a71068a724602f3577057a9769b0f68a5 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:29:46 -0700 Subject: [PATCH 02/87] Add missing layout constraints Constraints may not be accurate as I am not an android UI person by any means, but their absence blocks most workflows --- .github/README.md | 1 + .idea/misc.xml | 2 ++ app/src/main/res/layout/activity_crash_screen.xml | 14 ++++++++++---- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/README.md b/.github/README.md index 6b47a7354..f726b708e 100644 --- a/.github/README.md +++ b/.github/README.md @@ -27,6 +27,7 @@ The CI pipeline behaves as follows: - onsider whether to delete reports files after use - add setting on manual run whether to commit anything/deploy to pages - replace build-artifacts setup gradle with coverage setup gradle +- Prevent any non-main commits from showing in the Pages preview, but build in a system to view the webpage from any commit iteration and branch. ### Backlog diff --git a/.idea/misc.xml b/.idea/misc.xml index 0bd3ec25a..74dd639e4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,6 @@ + + diff --git a/app/src/main/res/layout/activity_crash_screen.xml b/app/src/main/res/layout/activity_crash_screen.xml index 51904a590..59400e6f9 100644 --- a/app/src/main/res/layout/activity_crash_screen.xml +++ b/app/src/main/res/layout/activity_crash_screen.xml @@ -9,9 +9,15 @@ + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> \ No newline at end of file From eb104e621343781050864ff4d597e1d6f4ea9c43 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:03:34 -0700 Subject: [PATCH 03/87] Add French Localization of "Log" Missing localization string fails build --- .github/README.md | 6 +++++- app/src/main/res/values-fr-rCA/strings.xml | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index f726b708e..a535218ac 100644 --- a/.github/README.md +++ b/.github/README.md @@ -34,6 +34,9 @@ The CI pipeline behaves as follows: - Integrate with Dokka - Test auto UML diagramming - Break workflows out into multiple intelligently-grouped jobs for improved execution visibility +- Add graceful error handling if specific preview reports cannot be generated +- Add graceful error handling to note that coverage reporting can only be generated on PRs and pushes, not workflow dispatches +- Research if it's possible to ask to run workflows on any push --- @@ -41,4 +44,5 @@ The CI pipeline behaves as follows: - Update Super-Linter reusable workflow to point locally instead of to Thalia Wood's (polygeist111) copy - Update any GitHub pages links in workflow doc to point to correct GB Page -- Petition Chris to configure GitHub pages on the repo \ No newline at end of file +- Petition Chris to configure GitHub pages on the repo +- Petition Chris to configure GitHub Dependency Graph on the repo \ No newline at end of file diff --git a/app/src/main/res/values-fr-rCA/strings.xml b/app/src/main/res/values-fr-rCA/strings.xml index 1270e1527..b3db8cfec 100644 --- a/app/src/main/res/values-fr-rCA/strings.xml +++ b/app/src/main/res/values-fr-rCA/strings.xml @@ -60,4 +60,5 @@ Concurrence STA/AP Test manuel Réinitialiser + Registre \ No newline at end of file From 4890ec741fbc511222996672a9a84cf3c8308a67 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:24:58 -0700 Subject: [PATCH 04/87] Localize Super Linter Workflow Brings the super-linter reusable workflow into the project, instead of referencing my (Thalia Wood, polygeist111) personal reusable workflow repo --- .github/linters/.markdown-lint.yml | 7 + .github/linters/.yaml-lint.yml | 53 +++++ .github/workflows/format_and_lint.yml | 3 +- .github/workflows/super_lint.yml | 214 ++++++++++++++++++ .../workflows/test_and_report_coverage.yml | 2 +- 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 .github/linters/.markdown-lint.yml create mode 100644 .github/linters/.yaml-lint.yml create mode 100644 .github/workflows/super_lint.yml diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml new file mode 100644 index 000000000..cb41d4883 --- /dev/null +++ b/.github/linters/.markdown-lint.yml @@ -0,0 +1,7 @@ +--- +# MD013/line-length - Line length +MD013: + # Number of characters, default is 80 + line_length: 9999 + # check code blocks? + code_blocks: false \ No newline at end of file diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml new file mode 100644 index 000000000..030c37f04 --- /dev/null +++ b/.github/linters/.yaml-lint.yml @@ -0,0 +1,53 @@ +--- +########################################### +# These are the rules used for # +# linting all the yaml files in the stack # +# NOTE: # +# You can disable line with: # +# # yamllint disable-line # +########################################### +rules: + braces: + level: warning + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: 1 + max-spaces-inside-empty: 5 + brackets: + level: warning + min-spaces-inside: 0 + max-spaces-inside: 0 + min-spaces-inside-empty: 1 + max-spaces-inside-empty: 5 + colons: + level: warning + max-spaces-before: 0 + max-spaces-after: 1 + commas: + level: warning + max-spaces-before: 0 + min-spaces-after: 1 + max-spaces-after: 1 + comments: disable + comments-indentation: disable + document-end: disable + document-start: disable + empty-lines: + level: warning + max: 2 + max-start: 0 + max-end: 0 + hyphens: + level: warning + max-spaces-after: 1 + indentation: + level: warning + spaces: consistent + indent-sequences: true + check-multi-line-strings: false + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable \ No newline at end of file diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index b087b8c26..0beebb147 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -93,7 +93,8 @@ jobs: contents: read # clone the repo to lint statuses: write # read/write to repo custom statuses - uses: polygeist111/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main + #uses: polygeist111/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main + uses: ./.github/workflows/format_and_lint.yml # TODO: update url to point to gb reusable linter, not thalia's # with: diff --git a/.github/workflows/super_lint.yml b/.github/workflows/super_lint.yml new file mode 100644 index 000000000..34997d03a --- /dev/null +++ b/.github/workflows/super_lint.yml @@ -0,0 +1,214 @@ +--- +# original template from: https://github.com/bretfisher/super-linter-workflow/blob/main/.github/workflows/reusable-super-linter.yaml + +########################### +########################### +## Linter GitHub Actions ## +########################### +########################### +name: Super Lint +run-name: ${{ github.event.inputs.custom_run_name || github.workflow }} + +# +# Documentation: +# https://help.github.com/en/articles/workflow-syntax-for-github-actions +# + +on: + # run when called by another workflow + workflow_call: + inputs: + devops-only: + description: For a DevOps-focused repository. Prevents some code-language linters from running + required: false + type: boolean + default: false + filter-regex-exclude: + description: A regex to exclude files from linting + required: false + type: string + + # run on demand + workflow_dispatch: + inputs: + custom-run-name: + description: "Custom name for this Actions run" + required: false + type: string + devops-only: + description: For a DevOps-focused repository. Prevents some code-language linters from running + required: false + type: boolean + default: false + filter-regex-exclude: + description: A regex to exclude files from linting + required: false + type: string + +permissions: + contents: read # git permissions to repo pull/push + statuses: write # read/write to repo custom statuses and checks + +jobs: + super-lint: + + name: Super-Linter + + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v5 + with: + # Full git history is needed to get a proper list of changed files within super-linter + fetch-depth: 0 + + ############################# + # custom DEFAULT_BRANCH for repos where PR target isn't always main/master + ############################# + - name: Set DEFAULT_BRANCH to PR target + # if base_ref has a value, this is a PR + # we save the PR target branch name to a variable for use in linter config + # we pass string between job steps by echoing to $GITHUB_ENV, making it available in $env later + if: ${{ github.base_ref != '' }} + run: | + # shellcheck disable=2086 + echo "DEFAULT_BRANCH=${{ github.base_ref }}" >> $GITHUB_ENV + echo "this is a PR branch. Let's only lint the files that are changed against the target branch '${{ github.base_ref }}'" + + - name: Set DEFAULT_BRANCH to current branch + # if base_ref has no value, this is just a commit on a branch + # we need to strip refs/heads from github.ref to find the current branch name + # then save the current branch name to a variable for use in linter config later + # we pass strings between job steps by echoing to $GITHUB_ENV, making it available in $env later + if: ${{ github.base_ref == '' }} + run: | + # shellcheck disable=2086 + echo "DEFAULT_BRANCH=$(echo '${{ github.ref }}' | sed 's/refs\/heads\///')" >> $GITHUB_ENV + echo "this is just a branch push, not a PR." + + # used as a debug step to ensure we're only linting all files on release branches + - name: Are we linting all files? + run: | + echo VALIDATE_ALL_CODEBASE=${{ !contains(github.event_name, 'pull_request') }} + + # customize excluded paths and files with regex + - name: FILTER_REGEX_EXCLUDE + if: ${{ inputs.filter-regex-exclude }} + run: | + # shellcheck disable=2086 + { + echo "FILTER_REGEX_EXCLUDE=${{ inputs.filter-regex-exclude }}" >> $GITHUB_ENV + } + + # disable non-DevOps focused linters that might run on sample code or 3rd party code + # these env's will get pass to the next step + - name: Disable non-DevOps linters + if: ${{ inputs.devops-only == true }} + run: | + # shellcheck disable=2086 + { + echo "VALIDATE_CSS=false"; + echo "VALIDATE_HTML=false"; + echo "VALIDATE_JAVASCRIPT_ES=false"; + echo "VALIDATE_TYPESCRIPT_ES=false"; + echo "VALIDATE_TYPESCRIPT_STANDARD=false"; + echo "VALIDATE_JAVASCRIPT_STANDARD=false"; + echo "VALIDATE_PYTHON_MYPY=false"; + echo "VALIDATE_PYTHON_BLACK=false"; + echo "VALIDATE_PYTHON_FLAKE8=false"; + echo "VALIDATE_PYTHON_ISORT=false"; + echo "VALIDATE_RUBY=false"; + echo "VALIDATE_PHP=false"; + echo "VALIDATE_CSHARP=false"; + + } >> $GITHUB_ENV + + ############################# + # Run many Linters against changed files on PRs, and ALL files on commit to release branch + ############################# + # https://github.com/marketplace/actions/super-linter + - name: Lint Code Base + uses: super-linter/super-linter@v8.2.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # by default super-linter assumes our repo default branch doesn't change + # and it also assumes our PRs are always against that default branch + # for multi-trunk (releases) repos, this get the base branch from the previous steps + # see issue https://github.com/github/super-linter/issues/1123 + DEFAULT_BRANCH: ${{ env.DEFAULT_BRANCH }} + # setting this to false means that only changed files will be scanned in each commit + VALIDATE_ALL_CODEBASE: ${{ !contains(github.event_name, 'pull_request') }} + # turn off dockerfilelint, as its a dead project + # https://github.com/replicatedhq/dockerfilelint/issues/169 + # hadolint will still run and is sufficient (no need for two linters) + VALIDATE_DOCKERFILE: false + # turn off JSCPD copy/paste detection, which results in lots of results for examples and devops repos + VALIDATE_JSCPD: false + # turn off shfmt shell formatter as we already have shellcheck + VALIDATE_SHELL_SHFMT: false + # editorconfig is great, but... + # editorconfig-linter is rather generic and file-specific linters are better + # turn off editorconfig-checker, which flags too many false positives + VALIDATE_EDITORCONFIG: false + # prevent Kubernetes CRD API's from causing kubeval to fail + # also change schema location to an up-to-date list + # https://github.com/yannh/kubernetes-json-schema/#kubeval + KUBERNETES_KUBEVAL_OPTIONS: --ignore-missing-schemas --schema-location https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/ + + # default all linters to fix mode + FIX_ANSIBLE: true + FIX_BIOME_FORMAT: true + FIX_BIOME_LINT: true + FIX_CLANG_FORMAT: true + FIX_CSHARP: true + FIX_CSS_PRETTIER: true + FIX_CSS: true + FIX_DOTNET_SLN_FORMAT_ANALYZERS: true + FIX_DOTNET_SLN_FORMAT_STYLE: true + FIX_DOTNET_SLN_FORMAT_WHITESPACE: true + FIX_ENV: true + FIX_GITHUB_ACTIONS_ZIZMOR: true + FIX_GO_MODULES: true + FIX_GO: true + FIX_GOOGLE_JAVA_FORMAT: true + FIX_GRAPHQL_PRETTIER: true + FIX_GROOVY: true + FIX_HTML_PRETTIER: true + FIX_JAVASCRIPT_ES: true + FIX_JAVASCRIPT_PRETTIER: true + FIX_JSON_PRETTIER: true + FIX_JSON: true + FIX_JSONC: true + FIX_JSONC_PRETTIER: true + FIX_JSX_PRETTIER: true + FIX_JSX: true + FIX_JUPYTER_NBQA_BLACK: true + FIX_JUPYTER_NBQA_ISORT: true + FIX_JUPYTER_NBQA_RUFF: true + FIX_KOTLIN: true + FIX_MARKDOWN_PRETTIER: true + FIX_MARKDOWN: true + FIX_NATURAL_LANGUAGE: true + FIX_POWERSHELL: true + FIX_PROTOBUF: true + FIX_PYTHON_BLACK: true + FIX_PYTHON_ISORT: true + FIX_PYTHON_RUFF: true + FIX_PYTHON_RUFF_FORMAT: true + FIX_RUBY: true + FIX_RUST_2015: true + FIX_RUST_2018: true + FIX_RUST_2021: true + FIX_RUST_CLIPPY: true + FIX_SCALAFMT: true + FIX_SHELL_SHFMT: false # see note above about removing validation + FIX_SNAKEMAKE_SNAKEFMT: true + FIX_SQLFLUFF: true + FIX_TERRAFORM_FMT: true + FIX_TSX: true + FIX_TYPESCRIPT_ES: true + FIX_TYPESCRIPT_PRETTIER: true + FIX_VUE: true + FIX_VUE_PRETTIER: true + FIX_YAML_PRETTIER: true \ No newline at end of file diff --git a/.github/workflows/test_and_report_coverage.yml b/.github/workflows/test_and_report_coverage.yml index 901f2b7c8..c9ac3f145 100644 --- a/.github/workflows/test_and_report_coverage.yml +++ b/.github/workflows/test_and_report_coverage.yml @@ -129,7 +129,7 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "(Changed File Coverage is currently bugged, disregard)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Full coverage report can be viewed at https://html-preview.github.io/?url=https://github.com/polygeist111/Project-Mesh/blob/main/build_reports/coverage_report/index.html" >> $GITHUB_STEP_SUMMARY + echo "Full coverage report can be viewed at " >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY From 593520691cd2ad7b8ee343164b7c4fa46aada45c Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:40:54 -0700 Subject: [PATCH 05/87] quick fix to super-linter local workflow --- .github/workflows/format_and_lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index 0beebb147..cfa1bdb24 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -94,7 +94,7 @@ jobs: statuses: write # read/write to repo custom statuses #uses: polygeist111/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main - uses: ./.github/workflows/format_and_lint.yml + uses: ./.github/workflows/super_lint.yml # TODO: update url to point to gb reusable linter, not thalia's # with: From eb90bcfa9e7600c08b8db09321de68c692f77488 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:19:46 -0700 Subject: [PATCH 06/87] Continued compatibility updates --- .github/README.md | 11 +---------- .github/workflows/format_and_lint.yml | 2 -- .github/workflows/test_and_report_coverage.yml | 6 +++--- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/.github/README.md b/.github/README.md index a535218ac..7b873d30e 100644 --- a/.github/README.md +++ b/.github/README.md @@ -36,13 +36,4 @@ The CI pipeline behaves as follows: - Break workflows out into multiple intelligently-grouped jobs for improved execution visibility - Add graceful error handling if specific preview reports cannot be generated - Add graceful error handling to note that coverage reporting can only be generated on PRs and pushes, not workflow dispatches -- Research if it's possible to ask to run workflows on any push - ---- - -## Integration TODO - -- Update Super-Linter reusable workflow to point locally instead of to Thalia Wood's (polygeist111) copy -- Update any GitHub pages links in workflow doc to point to correct GB Page -- Petition Chris to configure GitHub pages on the repo -- Petition Chris to configure GitHub Dependency Graph on the repo \ No newline at end of file +- Research if it's possible to ask to run workflows on any push \ No newline at end of file diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index cfa1bdb24..cf5bf1d18 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -93,9 +93,7 @@ jobs: contents: read # clone the repo to lint statuses: write # read/write to repo custom statuses - #uses: polygeist111/super-linter-workflow/.github/workflows/reusable-super-linter.yaml@main uses: ./.github/workflows/super_lint.yml - # TODO: update url to point to gb reusable linter, not thalia's # with: ### A regex to exclude files from linting diff --git a/.github/workflows/test_and_report_coverage.yml b/.github/workflows/test_and_report_coverage.yml index c9ac3f145..0c1e56c26 100644 --- a/.github/workflows/test_and_report_coverage.yml +++ b/.github/workflows/test_and_report_coverage.yml @@ -68,6 +68,8 @@ jobs: run: java -jar kover-cli.jar report ./app/build/kover/bin-reports/testDebugUnitTest.ic --classfiles ./app/build/tmp/kotlin-classes --src ./app/src/main/java --html ./app/build/reports/kover/html/ - name: Add Coverage Report to PR + # conditional for graceful handling of Kover limitations + if: ${{ github.event_name == "pull_request" || github.event_name == "push" }} id: kover uses: mi-kas/kover-report@v1 with: @@ -129,12 +131,10 @@ jobs: echo "" >> $GITHUB_STEP_SUMMARY echo "(Changed File Coverage is currently bugged, disregard)" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - echo "Full coverage report can be viewed at " >> $GITHUB_STEP_SUMMARY + echo "Full coverage report can be viewed at https://grey-box.github.io/Project-Mesh/" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY echo "---" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY - # TODO: update coverage report link to point to GB - # configure Mesh for GitHub Pages (need to ask a higher-up) - name: Verify Test/Build Outcome if: steps.koverXmlReport.outcome == 'failure' From dc835011af63377ef7a9b4d40b3e4ce87f90498e Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:27:47 -0700 Subject: [PATCH 07/87] Compatibility Updates Attempt 2 --- .github/workflows/test_and_report_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_report_coverage.yml b/.github/workflows/test_and_report_coverage.yml index 0c1e56c26..0b111e51c 100644 --- a/.github/workflows/test_and_report_coverage.yml +++ b/.github/workflows/test_and_report_coverage.yml @@ -69,8 +69,8 @@ jobs: - name: Add Coverage Report to PR # conditional for graceful handling of Kover limitations - if: ${{ github.event_name == "pull_request" || github.event_name == "push" }} id: kover + if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} uses: mi-kas/kover-report@v1 with: path: ${{ github.workspace }}/app/build/reports/kover/report.xml From 1b648357efd260af72f9eebd8855956af88f8f71 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 14:09:01 -0700 Subject: [PATCH 08/87] Necessary configuration changes Top level build.gradle.kts now targets jave 17 --- .github/README.md | 5 +++++ .gitignore | 1 + .idea/compiler.xml | 2 +- .idea/misc.xml | 2 +- build.gradle.kts | 7 +++++++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/README.md b/.github/README.md index 7b873d30e..f8593bec3 100644 --- a/.github/README.md +++ b/.github/README.md @@ -2,6 +2,11 @@ --- +## NOTE + +To run the orchestrator to test on branches outside of main, run ```gh workflow run reporting_orchestrator.yml --ref feature/ras-cicd```, replacing "feature/ras-cicd" whichever branch you wish to run the workflow against. +--- + ## Pipeline Flow The CI pipeline behaves as follows: diff --git a/.gitignore b/.gitignore index 6e1af9e57..15394319f 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,4 @@ kotlin-ide/ .idea/ .aider* .env +.vscode diff --git a/.idea/compiler.xml b/.idea/compiler.xml index b86273d94..b589d56e9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 74dd639e4..3b0be2284 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - + diff --git a/build.gradle.kts b/build.gradle.kts index 2f6b827a8..e7a9ef118 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,4 +5,11 @@ plugins { kotlin("plugin.serialization") version "1.9.0" kotlin("jvm") version "1.9.0" id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false +} + +java { + toolchain { + // version must be at least 11, + languageVersion = JavaLanguageVersion.of(17) + } } \ No newline at end of file From 831c13106e3acfe0c84bf9cb449e0da3b011496b Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 15:02:29 -0700 Subject: [PATCH 09/87] Add Kover Dependency --- app/build.gradle.kts | 1 + build.gradle.kts | 1 + 2 files changed, 2 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 967417e76..d68a8183c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("kotlin-kapt") id("com.google.devtools.ksp") version "1.9.0-1.0.13" kotlin("plugin.serialization") version "1.9.0" + id("org.jetbrains.kotlinx.kover") version "0.9.3" } android { diff --git a/build.gradle.kts b/build.gradle.kts index e7a9ef118..cde24fd78 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,7 @@ plugins { kotlin("plugin.serialization") version "1.9.0" kotlin("jvm") version "1.9.0" id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false + id("org.jetbrains.kotlinx.kover") version "0.9.3" apply false } java { From 3adf2a8f803b2c5262b4e4836fdaa53a833c0b7c Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:03:46 -0700 Subject: [PATCH 10/87] Update test_and_report_coverage.yml Add placeholder passing content for orchestrator stability --- .github/workflows/test_and_report_coverage.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_and_report_coverage.yml b/.github/workflows/test_and_report_coverage.yml index 0b111e51c..ec1920840 100644 --- a/.github/workflows/test_and_report_coverage.yml +++ b/.github/workflows/test_and_report_coverage.yml @@ -54,6 +54,11 @@ jobs: run: ./gradlew koverXmlReport --continue continue-on-error: true + # if all tests pass, no report is generated, which errors the orchestrator. Generate a simple passing notification in the same location. + - name: Insert Passing Report (only on test pass) + if: steps.koverXmlReport.outcome == 'success' + run: echo "

All tests pass! (or tests not found :/)

" > ./app/build/reports/kover/html/index.html + - name: Download Kover CLI (only on test failure) if: steps.koverXmlReport.outcome == 'failure' run: wget -O kover-cli.jar https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kover-cli/0.9.3/kover-cli-0.9.3.jar @@ -67,7 +72,7 @@ jobs: if: steps.koverXmlReport.outcome == 'failure' run: java -jar kover-cli.jar report ./app/build/kover/bin-reports/testDebugUnitTest.ic --classfiles ./app/build/tmp/kotlin-classes --src ./app/src/main/java --html ./app/build/reports/kover/html/ - - name: Add Coverage Report to PR + - name: Add Coverage Report to PR (if triggered via valid event) # conditional for graceful handling of Kover limitations id: kover if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }} From 4d734f143a4db24fae82a5d11165eb7d06f59f31 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:12:20 -0700 Subject: [PATCH 11/87] Update test_and_report_coverage.yml --- .github/workflows/test_and_report_coverage.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_and_report_coverage.yml b/.github/workflows/test_and_report_coverage.yml index ec1920840..afdd2dde0 100644 --- a/.github/workflows/test_and_report_coverage.yml +++ b/.github/workflows/test_and_report_coverage.yml @@ -57,7 +57,9 @@ jobs: # if all tests pass, no report is generated, which errors the orchestrator. Generate a simple passing notification in the same location. - name: Insert Passing Report (only on test pass) if: steps.koverXmlReport.outcome == 'success' - run: echo "

All tests pass! (or tests not found :/)

" > ./app/build/reports/kover/html/index.html + run: | + mkdir ./app/build/reports/kover/html + echo "

All tests pass! (or tests not found :/)

" > ./app/build/reports/kover/html/index.html - name: Download Kover CLI (only on test failure) if: steps.koverXmlReport.outcome == 'failure' From 3127b21330538c5d446b956cdf67f758af7a59dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Feb 2026 23:23:28 +0000 Subject: [PATCH 12/87] Relocate reports for viewing --- build_reports/coverage_report/index.html | 1 + .../lint_report_debug/lint_report_debug.html | 3807 +++++++++++++++++ .../lint_report_release.html | 3738 ++++++++++++++++ ...m.greybox.projectmesh.ExampleUnitTest.html | 96 + .../test_report_debug/css/base-style.css | 179 + build_reports/test_report_debug/css/style.css | 84 + build_reports/test_report_debug/index.html | 133 + build_reports/test_report_debug/js/report.js | 194 + .../packages/com.greybox.projectmesh.html | 103 + ...m.greybox.projectmesh.ExampleUnitTest.html | 96 + .../test_report_release/css/base-style.css | 179 + .../test_report_release/css/style.css | 84 + build_reports/test_report_release/index.html | 133 + .../test_report_release/js/report.js | 194 + .../packages/com.greybox.projectmesh.html | 103 + 15 files changed, 9124 insertions(+) create mode 100644 build_reports/coverage_report/index.html create mode 100644 build_reports/lint_report_debug/lint_report_debug.html create mode 100644 build_reports/lint_report_release/lint_report_release.html create mode 100644 build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html create mode 100644 build_reports/test_report_debug/css/base-style.css create mode 100644 build_reports/test_report_debug/css/style.css create mode 100644 build_reports/test_report_debug/index.html create mode 100644 build_reports/test_report_debug/js/report.js create mode 100644 build_reports/test_report_debug/packages/com.greybox.projectmesh.html create mode 100644 build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html create mode 100644 build_reports/test_report_release/css/base-style.css create mode 100644 build_reports/test_report_release/css/style.css create mode 100644 build_reports/test_report_release/index.html create mode 100644 build_reports/test_report_release/js/report.js create mode 100644 build_reports/test_report_release/packages/com.greybox.projectmesh.html diff --git a/build_reports/coverage_report/index.html b/build_reports/coverage_report/index.html new file mode 100644 index 000000000..0ff89d225 --- /dev/null +++ b/build_reports/coverage_report/index.html @@ -0,0 +1 @@ +

All tests pass! (or tests not found :/)

diff --git a/build_reports/lint_report_debug/lint_report_debug.html b/build_reports/lint_report_debug/lint_report_debug.html new file mode 100644 index 000000000..fd821dab2 --- /dev/null +++ b/build_reports/lint_report_debug/lint_report_debug.html @@ -0,0 +1,3807 @@ + + + + + +Lint Report + + + + + + + + +
+
+
+ Lint Report: 143 warnings +
+ +
+
+ +
+
+ +
+
+
+

Overview

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Correctness +
2warning +ScopedStorage: Affected by scoped storage
1warning +InlinedApi: Using inlined constants on older versions
1warning +OldTargetApi: Target SDK attribute is not targeting latest version
2warning +SimpleDateFormat: Implied locale in date format
1warning +UnusedAttribute: Attribute unused on older versions
1warning +AppBundleLocaleChanges: App Bundle handling of runtime locale changes
1warning +RedundantLabel: Redundant label on activity
3warning +AndroidGradlePluginVersion: Obsolete Android Gradle Plugin Version
70warning +GradleDependency: Obsolete Gradle Dependency
1warning +LockedOrientationActivity: Incompatible screenOrientation value
6warning +SimilarGradleDependency: Multiple Versions Gradle Dependency
1warning +DiscouragedApi: Using discouraged APIs
Security +
3warning +TrustAllX509TrustManager: Insecure TLS/SSL trust manager
Performance +
5warning +ObsoleteSdkInt: Obsolete SDK_INT Version Check
1warning +AutoboxingStateCreation: State<T> will autobox values assigned to this state. Use a specialized state type instead.
19warning +UnusedResources: Unused resources
Productivity +
30warning +UseTomlInstead: Use TOML Version Catalog Instead
Internationalization +
2warning +HardcodedText: Hardcoded text
Included Additional Checks (70) +
Disabled Checks (41) +
+
+
+
+
+
+ + +
+
+
+

Affected by scoped storage

+
+
+
+
+../../src/main/AndroidManifest.xml:40: READ_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to query or interact with MediaStore or media files on the shared storage, you should instead use one or more new storage permissions: READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READ_MEDIA_AUDIO.
+  37     <uses-permission
+  38         android:name="android.permission.ACCESS_COARSE_LOCATION" />
+  39 
+  40     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />                     
+  41     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+  42     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+
+ +../../src/main/AndroidManifest.xml:41: WRITE_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to write to shared storage, use the MediaStore.createWriteRequest intent.
+  38         android:name="android.permission.ACCESS_COARSE_LOCATION" />
+  39 
+  40     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+  41     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />                    
+  42     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+  43 
+  44     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+
+ +
+ +
+
+ + ScopedStorage + + + Correctness + + + Warning + + + Priority 8/10 + +
+
+
+
+
+
+
+
+
+

Using inlined constants on older versions

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:69: Field requires API level 33 (current min is 26): android.Manifest.permission#NEARBY_WIFI_DEVICES
+  66   0 -> { // Request Nearby Wi-Fi Permission
+  67       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+  68           !hasPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)) {
+  69           nearbyWifiPermissionLauncher.launch(Manifest.permission.NEARBY_WIFI_DEVICES)    
+  70       } else {
+  71           currentStep = 1
+  72       }
+
+ +
+ +
+
+ + InlinedApi + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

Target SDK attribute is not targeting latest version

+
+
+
+
+../../build.gradle.kts:17: Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details.
+  14     defaultConfig {
+  15         applicationId = "com.greybox.projectmesh"
+  16         minSdk = 26
+  17         targetSdk = 34                                                                              
+  18         versionCode = 1
+  19         versionName = "1.0"
+
+ +
+ +
+
+ + OldTargetApi + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

Implied locale in date format

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt:506: To get local formatting use getDateInstance(), getDateTimeInstance(), or getTimeInstance(), or use new SimpleDateFormat(String template, Locale locale) with for example Locale.US for ASCII dates.
+ 503           horizontalArrangement = Arrangement.End
+ 504       ) {
+ 505           Text(
+ 506               text = SimpleDateFormat("HH:mm").format(Date(chatMessage.dateReceived)),        
+ 507               style = MaterialTheme.typography.labelSmall
+ 508           )
+ 509       }
+
+ +../../src/main/java/com/greybox/projectmesh/messaging/utils/MessageUtils.kt:6: To get local formatting use getDateInstance(), getDateTimeInstance(), or getTimeInstance(), or use new SimpleDateFormat(String template, Locale locale) with for example Locale.US for ASCII dates.
+  3 object MessageUtils {
+  4     fun formatTimestamp(timestamp: Long): String {
+  5         //Adding timestamp formatting logic
+  6         return java.text.SimpleDateFormat("HH:mm").format(timestamp)                                
+  7     }
+  8 
+  9     fun generateChatId(sender: String, receiver: String): String {
+
+ +
+ +
+
+ + SimpleDateFormat + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

Attribute unused on older versions

+
+
+
+
+../../src/main/AndroidManifest.xml:34: Attribute usesPermissionFlags is only used in API level 31 and higher (current min is 26)
+  31           or higher, you must declare the NEARBY_WIFI_DEVICES permission.
+  32     -->
+  33     <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+  34         android:usesPermissionFlags="neverForLocation" />                                           
+  35     <uses-permission
+  36         android:name="android.permission.ACCESS_FINE_LOCATION" />
+  37     <uses-permission
+ +
+ +
+
+ + UnusedAttribute + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

App Bundle handling of runtime locale changes

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/MainActivity.kt:245: Found dynamic locale changes, but did not find corresponding Play Core library calls for downloading languages and splitting by language is not disabled in the bundle configuration
+ 242     private fun updateLocale(languageCode: String): Locale {
+ 243         val locale = Locale(languageCode)
+ 244         val config = resources.configuration
+ 245         config.setLocale(locale)                                                                    
+ 246         @Suppress("DEPRECATION")
+ 247         resources.updateConfiguration(config, resources.displayMetrics)
+ 248         return locale
+
+ +
+ +
+
+ + AppBundleLocaleChanges + + + Correctness + + + Warning + + + Priority 5/10 + +
+
+
+
+
+
+
+
+
+

Redundant label on activity

+
+
+
+
+../../src/main/AndroidManifest.xml:76: Redundant label can be removed
+  73         <activity
+  74             android:name=".MainActivity"
+  75             android:exported="true"
+  76             android:label="@string/app_name"                                                        
+  77             android:theme="@style/Theme.ProjectMesh.Launcher">
+  78             <intent-filter>
+  79                 <action android:name="android.intent.action.MAIN" />
+
+ +
+ +
+
+ + RedundantLabel + + + Correctness + + + Warning + + + Priority 5/10 + +
+
+
+
+
+
+
+
+
+

Obsolete Android Gradle Plugin Version

+
+
+
+
+../../../gradle/libs.versions.toml:2: A newer version of com.android.application than 8.5.1 is available: 9.0.1. (There is also a newer version of 8.5.�� available, if upgrading to 9.0.1 is difficult: 8.5.2)
+  1 [versions]
+  2 agp = "8.5.1"                                                                                       
+  3 datastoreCoreVersion = "1.1.1"
+  4 datastorePreferences = "1.1.1"
+  5 kotlin = "1.9.0"
+
+ +../../../gradle/libs.versions.toml:2: A newer version of com.android.application than 8.5.1 is available: 9.0.1. (There is also a newer version of 8.5.�� available, if upgrading to 9.0.1 is difficult: 8.5.2)
+  1 [versions]
+  2 agp = "8.5.1"                                                                                       
+  3 datastoreCoreVersion = "1.1.1"
+  4 datastorePreferences = "1.1.1"
+  5 kotlin = "1.9.0"
+
+ +../../../gradle/libs.versions.toml:2: A newer version of com.android.application than 8.5.1 is available: 9.0.1. (There is also a newer version of 8.5.�� available, if upgrading to 9.0.1 is difficult: 8.5.2)
+  1 [versions]
+  2 agp = "8.5.1"                                                                                       
+  3 datastoreCoreVersion = "1.1.1"
+  4 datastorePreferences = "1.1.1"
+  5 kotlin = "1.9.0"
+
+ +
+ +
+
+ + AndroidGradlePluginVersion + + + Correctness + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Obsolete Gradle Dependency

+
+
+
+
+../../build.gradle.kts:64: A newer version of androidx.lifecycle:lifecycle-viewmodel-compose than 2.8.6 is available: 2.10.0
+  61 }
+  62 
+  63 dependencies {
+  64     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")                          
+  65     implementation(libs.androidx.core.ktx)
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+
+ +../../build.gradle.kts:71: A newer version of androidx.compose.material3:material3 than 1.2.1 is available: 1.4.0
+  68     implementation("ch.acra:acra-http:5.11.0")
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")                                    
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+
+ +../../build.gradle.kts:72: A newer version of androidx.compose.material:material-icons-core than 1.6.8 is available: 1.7.8
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")                           
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+  75     implementation(libs.androidx.ui)
+
+ +../../build.gradle.kts:73: A newer version of androidx.compose.material:material-icons-extended-android than 1.6.8 is available: 1.7.8
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")               
+  74     implementation(libs.androidx.foundation)
+  75     implementation(libs.androidx.ui)
+  76     implementation(libs.androidx.ui.graphics)
+
+ +../../build.gradle.kts:96: A newer version of com.github.seancfoley:ipaddress than 5.3.3 is available: 5.4.0
+  93     debugImplementation(libs.androidx.ui.tooling)
+  94     debugImplementation(libs.androidx.ui.test.manifest)
+  95     implementation("com.github.UstadMobile.Meshrabiya:lib-meshrabiya:0.1d10-snapshot")
+  96     implementation("com.github.seancfoley:ipaddress:5.3.3")                                         
+  97     implementation("com.squareup.okhttp3:okhttp:4.10.0")
+  98     implementation("org.nanohttpd:nanohttpd:2.3.1")
+  99     implementation (libs.material)
+
+ + + +
+ +
+
+ + GradleDependency + + + Correctness + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Incompatible screenOrientation value

+
+
+
+
+../../src/main/AndroidManifest.xml:96: Expecting android:screenOrientation="unspecified" or "fullSensor" for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices
+  93   </provider>
+  94   <activity
+  95       android:name="com.journeyapps.barcodescanner.CaptureActivity"
+  96       android:screenOrientation="portrait"                                                    
+  97       android:stateNotNeeded="true"
+  98       tools:replace="android:screenOrientation" />
+  99   <activity android:name=".debug.CrashScreenActivity" android:process=":error_handler" android:exported="false"></activity>
+ +
+ +
+
+ + LockedOrientationActivity + + + Correctness + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Multiple Versions Gradle Dependency

+
+
+
+
+../../../gradle/libs.versions.toml:27: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 24 
+ 25 [libraries]
+ 26 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+ 27 androidx-datastore-core-v111 = { module = "androidx.datastore:datastore-core", version.ref = "datastoreCoreVersion" }
+ 28 androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
+ 29 androidx-foundation = { module = "androidx.compose.foundation:foundation" }
+ 30 junit = { group = "junit", name = "junit", version.ref = "junit" }
+
+ +../../../gradle/libs.versions.toml:27: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 24 
+ 25 [libraries]
+ 26 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+ 27 androidx-datastore-core-v111 = { module = "androidx.datastore:datastore-core", version.ref = "datastoreCoreVersion" }
+ 28 androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
+ 29 androidx-foundation = { module = "androidx.compose.foundation:foundation" }
+ 30 junit = { group = "junit", name = "junit", version.ref = "junit" }
+
+ +../../../gradle/libs.versions.toml:27: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 24 
+ 25 [libraries]
+ 26 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+ 27 androidx-datastore-core-v111 = { module = "androidx.datastore:datastore-core", version.ref = "datastoreCoreVersion" }
+ 28 androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
+ 29 androidx-foundation = { module = "androidx.compose.foundation:foundation" }
+ 30 junit = { group = "junit", name = "junit", version.ref = "junit" }
+
+ +../../../gradle/libs.versions.toml:46: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 43 lib-meshrabiya = { module = "com.github.UstadMobile.Meshrabiya:lib-meshrabiya", version.ref = "libMeshrabiya" }
+ 44 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+ 45 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+ 46 androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
+ 47 androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
+ 48 androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastorePreferencesCore" }
+ 49 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+
+ +../../../gradle/libs.versions.toml:46: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 43 lib-meshrabiya = { module = "com.github.UstadMobile.Meshrabiya:lib-meshrabiya", version.ref = "libMeshrabiya" }
+ 44 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+ 45 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+ 46 androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
+ 47 androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
+ 48 androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastorePreferencesCore" }
+ 49 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+
+ +../../../gradle/libs.versions.toml:46: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 43 lib-meshrabiya = { module = "com.github.UstadMobile.Meshrabiya:lib-meshrabiya", version.ref = "libMeshrabiya" }
+ 44 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+ 45 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+ 46 androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
+ 47 androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
+ 48 androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastorePreferencesCore" }
+ 49 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+
+ +
+ +
+
+ + SimilarGradleDependency + + + Correctness + + + Information + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Using discouraged APIs

+
+
+
+
+../../src/main/AndroidManifest.xml:96: Should not restrict activity to fixed orientation. This may not be suitable for different form factors, causing the app to be letterboxed.
+  93   </provider>
+  94   <activity
+  95       android:name="com.journeyapps.barcodescanner.CaptureActivity"
+  96       android:screenOrientation="portrait"                                                    
+  97       android:stateNotNeeded="true"
+  98       tools:replace="android:screenOrientation" />
+  99   <activity android:name=".debug.CrashScreenActivity" android:process=":error_handler" android:exported="false"></activity>
+ +
+ +
+
+ + DiscouragedApi + + + Correctness + + + Warning + + + Priority 2/10 + +
+
+
+
+
+
+ + +
+
+
+

Insecure TLS/SSL trust manager

+
+
+
+
+../../../../../../.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers
+../../../../../../.gradle/caches/modules-2/files-2.1/com.athaydes.rawhttp/rawhttp-core/2.5.2/cbd0e0de9c307aeb5b1931221e70599c064fa20a/rawhttp-core-2.5.2.jar: checkClientTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers
+../../../../../../.gradle/caches/modules-2/files-2.1/com.athaydes.rawhttp/rawhttp-core/2.5.2/cbd0e0de9c307aeb5b1931221e70599c064fa20a/rawhttp-core-2.5.2.jar: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers
+
+ +
+
+ + TrustAllX509TrustManager + + + Security + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+ + +
+
+
+

Obsolete SDK_INT Version Check

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/util/NotificationHelper.kt:19: Unnecessary; SDK_INT is always >= 26
+ 16     private const val CHANNEL_NAME = "File Receive Notifications"
+ 17 
+ 18     fun createNotificationChannel(context: Context) {
+ 19         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {                                       
+ 20             val channel = NotificationChannel(
+ 21                 CHANNEL_ID,
+ 22                 CHANNEL_NAME,
+
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:67: Unnecessary; SDK_INT is always >= 26
+  64   if (currentStep == 6) return@LaunchedEffect
+  65   when (currentStep) {
+  66       0 -> { // Request Nearby Wi-Fi Permission
+  67           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&                               
+  68               !hasPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)) {
+  69               nearbyWifiPermissionLauncher.launch(Manifest.permission.NEARBY_WIFI_DEVICES)
+  70           } else {
+
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:75: Unnecessary; SDK_INT is always >= 26
+  72        }
+  73    }
+  74    1 -> { // Request Location Permission
+  75        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&                               
+  76            !hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
+  77            locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
+  78        } else {
+
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:135: Unnecessary; SDK_INT is always >= 26
+ 132 /** Function to Check If Battery Optimization is Disabled */
+ 133 fun isBatteryOptimizationDisabled(context: Context): Boolean {
+ 134     val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+ 135     return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                                    
+ 136         powerManager.isIgnoringBatteryOptimizations(context.packageName)
+ 137     } else {
+ 138         true // Battery optimization doesn't apply below Android 6.0
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:144: Unnecessary; SDK_INT is always >= 26
+ 141 
+ 142 /** Function to Prompt User to Disable Battery Optimization */
+ 143 fun promptDisableBatteryOptimization(context: Context) {
+ 144     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                                           
+ 145         val message = SpannableString(
+ 146             "To ensure uninterrupted background functionality and maintain a stable connection, " +
+ 147                     "please disable battery optimization for this app.\n\n" +
+
+ +
+ +
+
+ + ObsoleteSdkInt + + + Performance + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

State<T> will autobox values assigned to this state. Use a specialized state type instead.

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/MainActivity.kt:132: Prefer mutableIntStateOf instead of mutableStateOf
+ 129       mutableStateOf(settingPref.getString(
+ 130           "language", "en") ?: "en")
+ 131   }
+ 132   var restartServerKey by remember {mutableStateOf(0)}                                    
+ 133   var deviceName by remember {
+ 134       mutableStateOf(settingPref.getString("device_name", Build.MODEL) ?: Build.MODEL)
+ 135   }
+
+ +
+ +
+
+Vendor: Jetpack Compose
+Identifier: androidx.compose.runtime
+Feedback: https://issuetracker.google.com/issues/new?component=612128
+
+
+ + AutoboxingStateCreation + + + Performance + + + Information + + + Priority 3/10 + +
+
+
+
+
+
+
+
+
+

Unused resources

+
+
+
+
+../../src/main/res/layout/activity_crash_screen.xml:2: The resource R.layout.activity_crash_screen appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  3     xmlns:app="http://schemas.android.com/apk/res-auto"
+  4     xmlns:tools="http://schemas.android.com/tools"
+  5     android:id="@+id/main"
+ +../../src/main/res/layout/activity_main.xml:2: The resource R.layout.activity_main appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <androidx.constraintlayout.widget.ConstraintLayout                                                  
+  3     xmlns:android="http://schemas.android.com/apk/res/android"
+  4     xmlns:app="http://schemas.android.com/apk/res-auto"
+  5     xmlns:tools="http://schemas.android.com/tools"
+ +../../src/main/res/values/colors.xml:3: The resource R.color.purple_200 appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <resources>
+  3     <color name="purple_200">#FFBB86FC</color>                                                      
+  4     <color name="purple_500">#FF6200EE</color>
+  5     <color name="purple_700">#FF3700B3</color>
+  6     <color name="teal_200">#FF03DAC5</color>
+ +../../src/main/res/values/colors.xml:4: The resource R.color.purple_500 appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <resources>
+  3     <color name="purple_200">#FFBB86FC</color>
+  4     <color name="purple_500">#FF6200EE</color>                                                      
+  5     <color name="purple_700">#FF3700B3</color>
+  6     <color name="teal_200">#FF03DAC5</color>
+  7     <color name="teal_700">#FF018786</color>
+ +../../src/main/res/values/colors.xml:5: The resource R.color.purple_700 appears to be unused
+  2 <resources>
+  3     <color name="purple_200">#FFBB86FC</color>
+  4     <color name="purple_500">#FF6200EE</color>
+  5     <color name="purple_700">#FF3700B3</color>                                                      
+  6     <color name="teal_200">#FF03DAC5</color>
+  7     <color name="teal_700">#FF018786</color>
+  8     <color name="black">#FF000000</color>
+ + + +
+ +
+
+ + UnusedResources + + + Performance + + + Warning + + + Priority 3/10 + +
+
+
+
+
+
+ + +
+
+
+

Use TOML Version Catalog Instead

+
+
+
+
+../../build.gradle.kts:64: Use version catalog instead
+  61 }
+  62 
+  63 dependencies {
+  64     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")                          
+  65     implementation(libs.androidx.core.ktx)
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+
+ +../../build.gradle.kts:68: Use version catalog instead
+  65     implementation(libs.androidx.core.ktx)
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+  68     implementation("ch.acra:acra-http:5.11.0")                                                      
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+
+ +../../build.gradle.kts:69: Use version catalog instead
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+  68     implementation("ch.acra:acra-http:5.11.0")
+  69     implementation("ch.acra:acra-dialog:5.11.0")                                                    
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+
+ +../../build.gradle.kts:71: Use version catalog instead
+  68     implementation("ch.acra:acra-http:5.11.0")
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")                                    
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+
+ +../../build.gradle.kts:72: Use version catalog instead
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")                           
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+  75     implementation(libs.androidx.ui)
+
+ + + +
+ +
+
+ + UseTomlInstead + + + Productivity + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+ + +
+
+
+

Hardcoded text

+
+
+
+
+../../src/main/res/layout/activity_crash_screen.xml:18: Hardcoded string "TextView", should use @string resource
+ 15         android:layout_marginTop="9dp"
+ 16         android:layout_marginEnd="13dp"
+ 17         android:layout_marginBottom="9dp"
+ 18         android:text="TextView"                                                                     
+ 19         app:layout_constraintBottom_toBottomOf="parent"
+ 20         app:layout_constraintEnd_toEndOf="parent"
+ 21         app:layout_constraintStart_toStartOf="parent"
+ +../../src/main/res/layout/activity_main.xml:15: Hardcoded string "Project Mesh", should use @string resource
+ 12         android:layout_height="wrap_content"
+ 13         android:layout_marginStart="24dp"
+ 14         android:layout_marginTop="24dp"
+ 15         android:text="Project Mesh"                                                                 
+ 16         android:textSize="24sp"
+ 17         app:layout_constraintStart_toStartOf="parent"
+ 18         app:layout_constraintTop_toTopOf="parent" />
+
+ +
+ +
+
+ + HardcodedText + + + Internationalization + + + Warning + + + Priority 5/10 + +
+
+
+
+
+
+ +
+
+
+

Included Additional Checks

+
+
+This card lists all the extra checks run by lint, provided from libraries, +build configuration and extra flags. This is included to help you verify +whether a particular check is included in analysis when configuring builds. +(Note that the list does not include the hundreds of built-in checks into lint, +only additional ones.) + +
+
+
+
+
+ +
+
+
+

Disabled Checks

+
+
+One or more issues were not run by lint, either +because the check is not enabled by default, or because +it was disabled with a command line flag or via one or +more lint.xml configuration files in the project directories. + +
+
+
+
+
+ +
+
+
+

Suppressing Warnings and Errors

+
+
+Lint errors can be suppressed in a variety of ways:
+
+1. With a @SuppressLint annotation in the Java code
+2. With a tools:ignore attribute in the XML file
+3. With a //noinspection comment in the source code
+4. With ignore flags specified in the build.gradle file, as explained below
+5. With a lint.xml configuration file in the project
+6. With a lint.xml configuration file passed to lint via the --config flag
+7. With the --ignore flag passed to lint.
+
+To suppress a lint warning with an annotation, add a @SuppressLint("id") annotation on the class, method or variable declaration closest to the warning instance you want to disable. The id can be one or more issue id's, such as "UnusedResources" or {"UnusedResources","UnusedIds"}, or it can be "all" to suppress all lint warnings in the given scope.
+
+To suppress a lint warning with a comment, add a //noinspection id comment on the line before the statement with the error.
+
+To suppress a lint warning in an XML file, add a tools:ignore="id" attribute on the element containing the error, or one of its surrounding elements. You also need to define the namespace for the tools prefix on the root element in your document, next to the xmlns:android declaration:
+xmlns:tools="http://schemas.android.com/tools"
+
+To suppress a lint warning in a build.gradle file, add a section like this:
+ +
+android {
+    lintOptions {
+        disable 'TypographyFractions','TypographyQuotes'
+    }
+}
+
+
+Here we specify a comma separated list of issue id's after the disable command. You can also use warning or error instead of disable to change the severity of issues.
+
+To suppress lint warnings with a configuration XML file, create a file named lint.xml and place it at the root directory of the module in which it applies.
+
+The format of the lint.xml file is something like the following:
+ +
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+    <!-- Ignore everything in the test source set -->
+    <issue id="all">
+        <ignore path="\*/test/\*" />
+    </issue>
+
+    <!-- Disable this given check in this project -->
+    <issue id="IconMissingDensityFolder" severity="ignore" />
+
+    <!-- Ignore the ObsoleteLayoutParam issue in the given files -->
+    <issue id="ObsoleteLayoutParam">
+        <ignore path="res/layout/activation.xml" />
+        <ignore path="res/layout-xlarge/activation.xml" />
+        <ignore regexp="(foo|bar)\.java" />
+    </issue>
+
+    <!-- Ignore the UselessLeaf issue in the given file -->
+    <issue id="UselessLeaf">
+        <ignore path="res/layout/main.xml" />
+    </issue>
+
+    <!-- Change the severity of hardcoded strings to "error" -->
+    <issue id="HardcodedText" severity="error" />
+</lint>
+
+
+To suppress lint checks from the command line, pass the --ignore flag with a comma separated list of ids to be suppressed, such as:
+$ lint --ignore UnusedResources,UselessLeaf /my/project/path
+
+For more information, see https://developer.android.com/studio/write/lint.html#config
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/build_reports/lint_report_release/lint_report_release.html b/build_reports/lint_report_release/lint_report_release.html new file mode 100644 index 000000000..5195e93a4 --- /dev/null +++ b/build_reports/lint_report_release/lint_report_release.html @@ -0,0 +1,3738 @@ + + + + + +Lint Report + + + + + + + + +
+
+
+ Lint Report: 124 warnings +
+ +
+
+ +
+
+ +
+
+
+

Overview

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Correctness +
2warning +ScopedStorage: Affected by scoped storage
1warning +InlinedApi: Using inlined constants on older versions
1warning +OldTargetApi: Target SDK attribute is not targeting latest version
2warning +SimpleDateFormat: Implied locale in date format
1warning +UnusedAttribute: Attribute unused on older versions
1warning +AppBundleLocaleChanges: App Bundle handling of runtime locale changes
1warning +RedundantLabel: Redundant label on activity
2warning +AndroidGradlePluginVersion: Obsolete Android Gradle Plugin Version
52warning +GradleDependency: Obsolete Gradle Dependency
1warning +LockedOrientationActivity: Incompatible screenOrientation value
4warning +SimilarGradleDependency: Multiple Versions Gradle Dependency
1warning +DiscouragedApi: Using discouraged APIs
Security +
3warning +TrustAllX509TrustManager: Insecure TLS/SSL trust manager
Performance +
5warning +ObsoleteSdkInt: Obsolete SDK_INT Version Check
1warning +AutoboxingStateCreation: State<T> will autobox values assigned to this state. Use a specialized state type instead.
19warning +UnusedResources: Unused resources
Productivity +
30warning +UseTomlInstead: Use TOML Version Catalog Instead
Internationalization +
2warning +HardcodedText: Hardcoded text
Included Additional Checks (67) +
Disabled Checks (41) +
+
+
+
+
+
+ + +
+
+
+

Affected by scoped storage

+
+
+
+
+../../src/main/AndroidManifest.xml:40: READ_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to query or interact with MediaStore or media files on the shared storage, you should instead use one or more new storage permissions: READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READ_MEDIA_AUDIO.
+  37     <uses-permission
+  38         android:name="android.permission.ACCESS_COARSE_LOCATION" />
+  39 
+  40     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />                     
+  41     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+  42     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+
+ +../../src/main/AndroidManifest.xml:41: WRITE_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to write to shared storage, use the MediaStore.createWriteRequest intent.
+  38         android:name="android.permission.ACCESS_COARSE_LOCATION" />
+  39 
+  40     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+  41     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />                    
+  42     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
+  43 
+  44     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+
+ +
+ +
+
+ + ScopedStorage + + + Correctness + + + Warning + + + Priority 8/10 + +
+
+
+
+
+
+
+
+
+

Using inlined constants on older versions

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:69: Field requires API level 33 (current min is 26): android.Manifest.permission#NEARBY_WIFI_DEVICES
+  66   0 -> { // Request Nearby Wi-Fi Permission
+  67       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
+  68           !hasPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)) {
+  69           nearbyWifiPermissionLauncher.launch(Manifest.permission.NEARBY_WIFI_DEVICES)    
+  70       } else {
+  71           currentStep = 1
+  72       }
+
+ +
+ +
+
+ + InlinedApi + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

Target SDK attribute is not targeting latest version

+
+
+
+
+../../build.gradle.kts:17: Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details.
+  14     defaultConfig {
+  15         applicationId = "com.greybox.projectmesh"
+  16         minSdk = 26
+  17         targetSdk = 34                                                                              
+  18         versionCode = 1
+  19         versionName = "1.0"
+
+ +
+ +
+
+ + OldTargetApi + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

Implied locale in date format

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt:506: To get local formatting use getDateInstance(), getDateTimeInstance(), or getTimeInstance(), or use new SimpleDateFormat(String template, Locale locale) with for example Locale.US for ASCII dates.
+ 503           horizontalArrangement = Arrangement.End
+ 504       ) {
+ 505           Text(
+ 506               text = SimpleDateFormat("HH:mm").format(Date(chatMessage.dateReceived)),        
+ 507               style = MaterialTheme.typography.labelSmall
+ 508           )
+ 509       }
+
+ +../../src/main/java/com/greybox/projectmesh/messaging/utils/MessageUtils.kt:6: To get local formatting use getDateInstance(), getDateTimeInstance(), or getTimeInstance(), or use new SimpleDateFormat(String template, Locale locale) with for example Locale.US for ASCII dates.
+  3 object MessageUtils {
+  4     fun formatTimestamp(timestamp: Long): String {
+  5         //Adding timestamp formatting logic
+  6         return java.text.SimpleDateFormat("HH:mm").format(timestamp)                                
+  7     }
+  8 
+  9     fun generateChatId(sender: String, receiver: String): String {
+
+ +
+ +
+
+ + SimpleDateFormat + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

Attribute unused on older versions

+
+
+
+
+../../src/main/AndroidManifest.xml:34: Attribute usesPermissionFlags is only used in API level 31 and higher (current min is 26)
+  31           or higher, you must declare the NEARBY_WIFI_DEVICES permission.
+  32     -->
+  33     <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+  34         android:usesPermissionFlags="neverForLocation" />                                           
+  35     <uses-permission
+  36         android:name="android.permission.ACCESS_FINE_LOCATION" />
+  37     <uses-permission
+ +
+ +
+
+ + UnusedAttribute + + + Correctness + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

App Bundle handling of runtime locale changes

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/MainActivity.kt:245: Found dynamic locale changes, but did not find corresponding Play Core library calls for downloading languages and splitting by language is not disabled in the bundle configuration
+ 242     private fun updateLocale(languageCode: String): Locale {
+ 243         val locale = Locale(languageCode)
+ 244         val config = resources.configuration
+ 245         config.setLocale(locale)                                                                    
+ 246         @Suppress("DEPRECATION")
+ 247         resources.updateConfiguration(config, resources.displayMetrics)
+ 248         return locale
+
+ +
+ +
+
+ + AppBundleLocaleChanges + + + Correctness + + + Warning + + + Priority 5/10 + +
+
+
+
+
+
+
+
+
+

Redundant label on activity

+
+
+
+
+../../src/main/AndroidManifest.xml:76: Redundant label can be removed
+  73         <activity
+  74             android:name=".MainActivity"
+  75             android:exported="true"
+  76             android:label="@string/app_name"                                                        
+  77             android:theme="@style/Theme.ProjectMesh.Launcher">
+  78             <intent-filter>
+  79                 <action android:name="android.intent.action.MAIN" />
+
+ +
+ +
+
+ + RedundantLabel + + + Correctness + + + Warning + + + Priority 5/10 + +
+
+
+
+
+
+
+
+
+

Obsolete Android Gradle Plugin Version

+
+
+
+
+../../../gradle/libs.versions.toml:2: A newer version of com.android.application than 8.5.1 is available: 9.0.1. (There is also a newer version of 8.5.�� available, if upgrading to 9.0.1 is difficult: 8.5.2)
+  1 [versions]
+  2 agp = "8.5.1"                                                                                       
+  3 datastoreCoreVersion = "1.1.1"
+  4 datastorePreferences = "1.1.1"
+  5 kotlin = "1.9.0"
+
+ +../../../gradle/libs.versions.toml:2: A newer version of com.android.application than 8.5.1 is available: 9.0.1. (There is also a newer version of 8.5.�� available, if upgrading to 9.0.1 is difficult: 8.5.2)
+  1 [versions]
+  2 agp = "8.5.1"                                                                                       
+  3 datastoreCoreVersion = "1.1.1"
+  4 datastorePreferences = "1.1.1"
+  5 kotlin = "1.9.0"
+
+ +
+ +
+
+ + AndroidGradlePluginVersion + + + Correctness + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Obsolete Gradle Dependency

+
+
+
+
+../../build.gradle.kts:64: A newer version of androidx.lifecycle:lifecycle-viewmodel-compose than 2.8.6 is available: 2.10.0
+  61 }
+  62 
+  63 dependencies {
+  64     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")                          
+  65     implementation(libs.androidx.core.ktx)
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+
+ +../../build.gradle.kts:71: A newer version of androidx.compose.material3:material3 than 1.2.1 is available: 1.4.0
+  68     implementation("ch.acra:acra-http:5.11.0")
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")                                    
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+
+ +../../build.gradle.kts:72: A newer version of androidx.compose.material:material-icons-core than 1.6.8 is available: 1.7.8
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")                           
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+  75     implementation(libs.androidx.ui)
+
+ +../../build.gradle.kts:73: A newer version of androidx.compose.material:material-icons-extended-android than 1.6.8 is available: 1.7.8
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")               
+  74     implementation(libs.androidx.foundation)
+  75     implementation(libs.androidx.ui)
+  76     implementation(libs.androidx.ui.graphics)
+
+ +../../build.gradle.kts:96: A newer version of com.github.seancfoley:ipaddress than 5.3.3 is available: 5.4.0
+  93     debugImplementation(libs.androidx.ui.tooling)
+  94     debugImplementation(libs.androidx.ui.test.manifest)
+  95     implementation("com.github.UstadMobile.Meshrabiya:lib-meshrabiya:0.1d10-snapshot")
+  96     implementation("com.github.seancfoley:ipaddress:5.3.3")                                         
+  97     implementation("com.squareup.okhttp3:okhttp:4.10.0")
+  98     implementation("org.nanohttpd:nanohttpd:2.3.1")
+  99     implementation (libs.material)
+
+ + + +
+ +
+
+ + GradleDependency + + + Correctness + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Incompatible screenOrientation value

+
+
+
+
+../../src/main/AndroidManifest.xml:96: Expecting android:screenOrientation="unspecified" or "fullSensor" for this activity so the user can use the application in any orientation and provide a great experience on Chrome OS devices
+  93   </provider>
+  94   <activity
+  95       android:name="com.journeyapps.barcodescanner.CaptureActivity"
+  96       android:screenOrientation="portrait"                                                    
+  97       android:stateNotNeeded="true"
+  98       tools:replace="android:screenOrientation" />
+  99   <activity android:name=".debug.CrashScreenActivity" android:process=":error_handler" android:exported="false"></activity>
+ +
+ +
+
+ + LockedOrientationActivity + + + Correctness + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Multiple Versions Gradle Dependency

+
+
+
+
+../../../gradle/libs.versions.toml:27: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 24 
+ 25 [libraries]
+ 26 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+ 27 androidx-datastore-core-v111 = { module = "androidx.datastore:datastore-core", version.ref = "datastoreCoreVersion" }
+ 28 androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
+ 29 androidx-foundation = { module = "androidx.compose.foundation:foundation" }
+ 30 junit = { group = "junit", name = "junit", version.ref = "junit" }
+
+ +../../../gradle/libs.versions.toml:27: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 24 
+ 25 [libraries]
+ 26 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
+ 27 androidx-datastore-core-v111 = { module = "androidx.datastore:datastore-core", version.ref = "datastoreCoreVersion" }
+ 28 androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
+ 29 androidx-foundation = { module = "androidx.compose.foundation:foundation" }
+ 30 junit = { group = "junit", name = "junit", version.ref = "junit" }
+
+ +../../../gradle/libs.versions.toml:46: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 43 lib-meshrabiya = { module = "com.github.UstadMobile.Meshrabiya:lib-meshrabiya", version.ref = "libMeshrabiya" }
+ 44 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+ 45 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+ 46 androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
+ 47 androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
+ 48 androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastorePreferencesCore" }
+ 49 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+
+ +../../../gradle/libs.versions.toml:46: There are multiple dependencies androidx.datastore:datastore-core but with different version
+ 43 lib-meshrabiya = { module = "com.github.UstadMobile.Meshrabiya:lib-meshrabiya", version.ref = "libMeshrabiya" }
+ 44 androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+ 45 material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+ 46 androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
+ 47 androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
+ 48 androidx-datastore-preferences-core = { group = "androidx.datastore", name = "datastore-preferences-core", version.ref = "datastorePreferencesCore" }
+ 49 androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+
+ +
+ +
+
+ + SimilarGradleDependency + + + Correctness + + + Information + + + Priority 4/10 + +
+
+
+
+
+
+
+
+
+

Using discouraged APIs

+
+
+
+
+../../src/main/AndroidManifest.xml:96: Should not restrict activity to fixed orientation. This may not be suitable for different form factors, causing the app to be letterboxed.
+  93   </provider>
+  94   <activity
+  95       android:name="com.journeyapps.barcodescanner.CaptureActivity"
+  96       android:screenOrientation="portrait"                                                    
+  97       android:stateNotNeeded="true"
+  98       tools:replace="android:screenOrientation" />
+  99   <activity android:name=".debug.CrashScreenActivity" android:process=":error_handler" android:exported="false"></activity>
+ +
+ +
+
+ + DiscouragedApi + + + Correctness + + + Warning + + + Priority 2/10 + +
+
+
+
+
+
+ + +
+
+
+

Insecure TLS/SSL trust manager

+
+
+
+
+../../../../../../.gradle/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers
+../../../../../../.gradle/caches/modules-2/files-2.1/com.athaydes.rawhttp/rawhttp-core/2.5.2/cbd0e0de9c307aeb5b1931221e70599c064fa20a/rawhttp-core-2.5.2.jar: checkClientTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers
+../../../../../../.gradle/caches/modules-2/files-2.1/com.athaydes.rawhttp/rawhttp-core/2.5.2/cbd0e0de9c307aeb5b1931221e70599c064fa20a/rawhttp-core-2.5.2.jar: checkServerTrusted is empty, which could cause insecure network traffic due to trusting arbitrary TLS/SSL certificates presented by peers
+
+ +
+
+ + TrustAllX509TrustManager + + + Security + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+ + +
+
+
+

Obsolete SDK_INT Version Check

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/util/NotificationHelper.kt:19: Unnecessary; SDK_INT is always >= 26
+ 16     private const val CHANNEL_NAME = "File Receive Notifications"
+ 17 
+ 18     fun createNotificationChannel(context: Context) {
+ 19         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {                                       
+ 20             val channel = NotificationChannel(
+ 21                 CHANNEL_ID,
+ 22                 CHANNEL_NAME,
+
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:67: Unnecessary; SDK_INT is always >= 26
+  64   if (currentStep == 6) return@LaunchedEffect
+  65   when (currentStep) {
+  66       0 -> { // Request Nearby Wi-Fi Permission
+  67           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&                               
+  68               !hasPermission(context, Manifest.permission.NEARBY_WIFI_DEVICES)) {
+  69               nearbyWifiPermissionLauncher.launch(Manifest.permission.NEARBY_WIFI_DEVICES)
+  70           } else {
+
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:75: Unnecessary; SDK_INT is always >= 26
+  72        }
+  73    }
+  74    1 -> { // Request Location Permission
+  75        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&                               
+  76            !hasPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
+  77            locationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
+  78        } else {
+
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:135: Unnecessary; SDK_INT is always >= 26
+ 132 /** Function to Check If Battery Optimization is Disabled */
+ 133 fun isBatteryOptimizationDisabled(context: Context): Boolean {
+ 134     val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
+ 135     return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                                    
+ 136         powerManager.isIgnoringBatteryOptimizations(context.packageName)
+ 137     } else {
+ 138         true // Battery optimization doesn't apply below Android 6.0
+ +../../src/main/java/com/greybox/projectmesh/views/RequestPermissionScreen.kt:144: Unnecessary; SDK_INT is always >= 26
+ 141 
+ 142 /** Function to Prompt User to Disable Battery Optimization */
+ 143 fun promptDisableBatteryOptimization(context: Context) {
+ 144     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {                                           
+ 145         val message = SpannableString(
+ 146             "To ensure uninterrupted background functionality and maintain a stable connection, " +
+ 147                     "please disable battery optimization for this app.\n\n" +
+
+ +
+ +
+
+ + ObsoleteSdkInt + + + Performance + + + Warning + + + Priority 6/10 + +
+
+
+
+
+
+
+
+
+

State<T> will autobox values assigned to this state. Use a specialized state type instead.

+
+
+
+
+../../src/main/java/com/greybox/projectmesh/MainActivity.kt:132: Prefer mutableIntStateOf instead of mutableStateOf
+ 129       mutableStateOf(settingPref.getString(
+ 130           "language", "en") ?: "en")
+ 131   }
+ 132   var restartServerKey by remember {mutableStateOf(0)}                                    
+ 133   var deviceName by remember {
+ 134       mutableStateOf(settingPref.getString("device_name", Build.MODEL) ?: Build.MODEL)
+ 135   }
+
+ +
+ +
+
+Vendor: Jetpack Compose
+Identifier: androidx.compose.runtime
+Feedback: https://issuetracker.google.com/issues/new?component=612128
+
+
+ + AutoboxingStateCreation + + + Performance + + + Information + + + Priority 3/10 + +
+
+
+
+
+
+
+
+
+

Unused resources

+
+
+
+
+../../src/main/res/layout/activity_crash_screen.xml:2: The resource R.layout.activity_crash_screen appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  3     xmlns:app="http://schemas.android.com/apk/res-auto"
+  4     xmlns:tools="http://schemas.android.com/tools"
+  5     android:id="@+id/main"
+ +../../src/main/res/layout/activity_main.xml:2: The resource R.layout.activity_main appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <androidx.constraintlayout.widget.ConstraintLayout                                                  
+  3     xmlns:android="http://schemas.android.com/apk/res/android"
+  4     xmlns:app="http://schemas.android.com/apk/res-auto"
+  5     xmlns:tools="http://schemas.android.com/tools"
+ +../../src/main/res/values/colors.xml:3: The resource R.color.purple_200 appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <resources>
+  3     <color name="purple_200">#FFBB86FC</color>                                                      
+  4     <color name="purple_500">#FF6200EE</color>
+  5     <color name="purple_700">#FF3700B3</color>
+  6     <color name="teal_200">#FF03DAC5</color>
+ +../../src/main/res/values/colors.xml:4: The resource R.color.purple_500 appears to be unused
+  1 <?xml version="1.0" encoding="utf-8"?>
+  2 <resources>
+  3     <color name="purple_200">#FFBB86FC</color>
+  4     <color name="purple_500">#FF6200EE</color>                                                      
+  5     <color name="purple_700">#FF3700B3</color>
+  6     <color name="teal_200">#FF03DAC5</color>
+  7     <color name="teal_700">#FF018786</color>
+ +../../src/main/res/values/colors.xml:5: The resource R.color.purple_700 appears to be unused
+  2 <resources>
+  3     <color name="purple_200">#FFBB86FC</color>
+  4     <color name="purple_500">#FF6200EE</color>
+  5     <color name="purple_700">#FF3700B3</color>                                                      
+  6     <color name="teal_200">#FF03DAC5</color>
+  7     <color name="teal_700">#FF018786</color>
+  8     <color name="black">#FF000000</color>
+ + + +
+ +
+
+ + UnusedResources + + + Performance + + + Warning + + + Priority 3/10 + +
+
+
+
+
+
+ + +
+
+
+

Use TOML Version Catalog Instead

+
+
+
+
+../../build.gradle.kts:64: Use version catalog instead
+  61 }
+  62 
+  63 dependencies {
+  64     implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")                          
+  65     implementation(libs.androidx.core.ktx)
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+
+ +../../build.gradle.kts:68: Use version catalog instead
+  65     implementation(libs.androidx.core.ktx)
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+  68     implementation("ch.acra:acra-http:5.11.0")                                                      
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+
+ +../../build.gradle.kts:69: Use version catalog instead
+  66     implementation(libs.androidx.lifecycle.runtime.ktx)
+  67     implementation(libs.androidx.activity.compose)
+  68     implementation("ch.acra:acra-http:5.11.0")
+  69     implementation("ch.acra:acra-dialog:5.11.0")                                                    
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+
+ +../../build.gradle.kts:71: Use version catalog instead
+  68     implementation("ch.acra:acra-http:5.11.0")
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")                                    
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+
+ +../../build.gradle.kts:72: Use version catalog instead
+  69     implementation("ch.acra:acra-dialog:5.11.0")
+  70     implementation(platform(libs.androidx.compose.bom))
+  71     implementation("androidx.compose.material3:material3:1.2.1")
+  72     implementation("androidx.compose.material:material-icons-core:1.6.8")                           
+  73     implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
+  74     implementation(libs.androidx.foundation)
+  75     implementation(libs.androidx.ui)
+
+ + + +
+ +
+
+ + UseTomlInstead + + + Productivity + + + Warning + + + Priority 4/10 + +
+
+
+
+
+
+ + +
+
+
+

Hardcoded text

+
+
+
+
+../../src/main/res/layout/activity_crash_screen.xml:18: Hardcoded string "TextView", should use @string resource
+ 15         android:layout_marginTop="9dp"
+ 16         android:layout_marginEnd="13dp"
+ 17         android:layout_marginBottom="9dp"
+ 18         android:text="TextView"                                                                     
+ 19         app:layout_constraintBottom_toBottomOf="parent"
+ 20         app:layout_constraintEnd_toEndOf="parent"
+ 21         app:layout_constraintStart_toStartOf="parent"
+ +../../src/main/res/layout/activity_main.xml:15: Hardcoded string "Project Mesh", should use @string resource
+ 12         android:layout_height="wrap_content"
+ 13         android:layout_marginStart="24dp"
+ 14         android:layout_marginTop="24dp"
+ 15         android:text="Project Mesh"                                                                 
+ 16         android:textSize="24sp"
+ 17         app:layout_constraintStart_toStartOf="parent"
+ 18         app:layout_constraintTop_toTopOf="parent" />
+
+ +
+ +
+
+ + HardcodedText + + + Internationalization + + + Warning + + + Priority 5/10 + +
+
+
+
+
+
+ +
+
+
+

Included Additional Checks

+
+
+This card lists all the extra checks run by lint, provided from libraries, +build configuration and extra flags. This is included to help you verify +whether a particular check is included in analysis when configuring builds. +(Note that the list does not include the hundreds of built-in checks into lint, +only additional ones.) + +
+
+
+
+
+ +
+
+
+

Disabled Checks

+
+
+One or more issues were not run by lint, either +because the check is not enabled by default, or because +it was disabled with a command line flag or via one or +more lint.xml configuration files in the project directories. + +
+
+
+
+
+ +
+
+
+

Suppressing Warnings and Errors

+
+
+Lint errors can be suppressed in a variety of ways:
+
+1. With a @SuppressLint annotation in the Java code
+2. With a tools:ignore attribute in the XML file
+3. With a //noinspection comment in the source code
+4. With ignore flags specified in the build.gradle file, as explained below
+5. With a lint.xml configuration file in the project
+6. With a lint.xml configuration file passed to lint via the --config flag
+7. With the --ignore flag passed to lint.
+
+To suppress a lint warning with an annotation, add a @SuppressLint("id") annotation on the class, method or variable declaration closest to the warning instance you want to disable. The id can be one or more issue id's, such as "UnusedResources" or {"UnusedResources","UnusedIds"}, or it can be "all" to suppress all lint warnings in the given scope.
+
+To suppress a lint warning with a comment, add a //noinspection id comment on the line before the statement with the error.
+
+To suppress a lint warning in an XML file, add a tools:ignore="id" attribute on the element containing the error, or one of its surrounding elements. You also need to define the namespace for the tools prefix on the root element in your document, next to the xmlns:android declaration:
+xmlns:tools="http://schemas.android.com/tools"
+
+To suppress a lint warning in a build.gradle file, add a section like this:
+ +
+android {
+    lintOptions {
+        disable 'TypographyFractions','TypographyQuotes'
+    }
+}
+
+
+Here we specify a comma separated list of issue id's after the disable command. You can also use warning or error instead of disable to change the severity of issues.
+
+To suppress lint warnings with a configuration XML file, create a file named lint.xml and place it at the root directory of the module in which it applies.
+
+The format of the lint.xml file is something like the following:
+ +
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+    <!-- Ignore everything in the test source set -->
+    <issue id="all">
+        <ignore path="\*/test/\*" />
+    </issue>
+
+    <!-- Disable this given check in this project -->
+    <issue id="IconMissingDensityFolder" severity="ignore" />
+
+    <!-- Ignore the ObsoleteLayoutParam issue in the given files -->
+    <issue id="ObsoleteLayoutParam">
+        <ignore path="res/layout/activation.xml" />
+        <ignore path="res/layout-xlarge/activation.xml" />
+        <ignore regexp="(foo|bar)\.java" />
+    </issue>
+
+    <!-- Ignore the UselessLeaf issue in the given file -->
+    <issue id="UselessLeaf">
+        <ignore path="res/layout/main.xml" />
+    </issue>
+
+    <!-- Change the severity of hardcoded strings to "error" -->
+    <issue id="HardcodedText" severity="error" />
+</lint>
+
+
+To suppress lint checks from the command line, pass the --ignore flag with a comma separated list of ids to be suppressed, such as:
+$ lint --ignore UnusedResources,UselessLeaf /my/project/path
+
+For more information, see https://developer.android.com/studio/write/lint.html#config
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html new file mode 100644 index 000000000..8f1c57524 --- /dev/null +++ b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class com.greybox.projectmesh.ExampleUnitTest + + + + + +
+

Class com.greybox.projectmesh.ExampleUnitTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
addition_isCorrect0.002spassed
+
+
+ +
+ + diff --git a/build_reports/test_report_debug/css/base-style.css b/build_reports/test_report_debug/css/base-style.css new file mode 100644 index 000000000..4afa73e3d --- /dev/null +++ b/build_reports/test_report_debug/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/build_reports/test_report_debug/css/style.css b/build_reports/test_report_debug/css/style.css new file mode 100644 index 000000000..3dc4913e7 --- /dev/null +++ b/build_reports/test_report_debug/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/build_reports/test_report_debug/index.html b/build_reports/test_report_debug/index.html new file mode 100644 index 000000000..aa73f5d9a --- /dev/null +++ b/build_reports/test_report_debug/index.html @@ -0,0 +1,133 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+com.greybox.projectmesh +1000.002s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+com.greybox.projectmesh.ExampleUnitTest +1000.002s100%
+
+
+ +
+ + diff --git a/build_reports/test_report_debug/js/report.js b/build_reports/test_report_debug/js/report.js new file mode 100644 index 000000000..83bab4a19 --- /dev/null +++ b/build_reports/test_report_debug/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html new file mode 100644 index 000000000..a7ed02e0b --- /dev/null +++ b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package com.greybox.projectmesh + + + + + +
+

Package com.greybox.projectmesh

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.002s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+ExampleUnitTest +1000.002s100%
+
+
+ +
+ + diff --git a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html new file mode 100644 index 000000000..cc61c7308 --- /dev/null +++ b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -0,0 +1,96 @@ + + + + + +Test results - Class com.greybox.projectmesh.ExampleUnitTest + + + + + +
+

Class com.greybox.projectmesh.ExampleUnitTest

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.004s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Tests

+ + + + + + + + + + + + + +
TestDurationResult
addition_isCorrect0.004spassed
+
+
+ +
+ + diff --git a/build_reports/test_report_release/css/base-style.css b/build_reports/test_report_release/css/base-style.css new file mode 100644 index 000000000..4afa73e3d --- /dev/null +++ b/build_reports/test_report_release/css/base-style.css @@ -0,0 +1,179 @@ + +body { + margin: 0; + padding: 0; + font-family: sans-serif; + font-size: 12pt; +} + +body, a, a:visited { + color: #303030; +} + +#content { + padding-left: 50px; + padding-right: 50px; + padding-top: 30px; + padding-bottom: 30px; +} + +#content h1 { + font-size: 160%; + margin-bottom: 10px; +} + +#footer { + margin-top: 100px; + font-size: 80%; + white-space: nowrap; +} + +#footer, #footer a { + color: #a0a0a0; +} + +#line-wrapping-toggle { + vertical-align: middle; +} + +#label-for-line-wrapping-toggle { + vertical-align: middle; +} + +ul { + margin-left: 0; +} + +h1, h2, h3 { + white-space: nowrap; +} + +h2 { + font-size: 120%; +} + +ul.tabLinks { + padding-left: 0; + padding-top: 10px; + padding-bottom: 10px; + overflow: auto; + min-width: 800px; + width: auto !important; + width: 800px; +} + +ul.tabLinks li { + float: left; + height: 100%; + list-style: none; + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: 0; + -moz-border-radius: 7px; + border-radius: 7px; + margin-right: 25px; + border: solid 1px #d4d4d4; + background-color: #f0f0f0; +} + +ul.tabLinks li:hover { + background-color: #fafafa; +} + +ul.tabLinks li.selected { + background-color: #c5f0f5; + border-color: #c5f0f5; +} + +ul.tabLinks a { + font-size: 120%; + display: block; + outline: none; + text-decoration: none; + margin: 0; + padding: 0; +} + +ul.tabLinks li h2 { + margin: 0; + padding: 0; +} + +div.tab { +} + +div.selected { + display: block; +} + +div.deselected { + display: none; +} + +div.tab table { + min-width: 350px; + width: auto !important; + width: 350px; + border-collapse: collapse; +} + +div.tab th, div.tab table { + border-bottom: solid #d0d0d0 1px; +} + +div.tab th { + text-align: left; + white-space: nowrap; + padding-left: 6em; +} + +div.tab th:first-child { + padding-left: 0; +} + +div.tab td { + white-space: nowrap; + padding-left: 6em; + padding-top: 5px; + padding-bottom: 5px; +} + +div.tab td:first-child { + padding-left: 0; +} + +div.tab td.numeric, div.tab th.numeric { + text-align: right; +} + +span.code { + display: inline-block; + margin-top: 0em; + margin-bottom: 1em; +} + +span.code pre { + font-size: 11pt; + padding-top: 10px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + margin: 0; + background-color: #f7f7f7; + border: solid 1px #d0d0d0; + min-width: 700px; + width: auto !important; + width: 700px; +} + +span.wrapped pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: break-all; +} + +label.hidden { + display: none; +} \ No newline at end of file diff --git a/build_reports/test_report_release/css/style.css b/build_reports/test_report_release/css/style.css new file mode 100644 index 000000000..3dc4913e7 --- /dev/null +++ b/build_reports/test_report_release/css/style.css @@ -0,0 +1,84 @@ + +#summary { + margin-top: 30px; + margin-bottom: 40px; +} + +#summary table { + border-collapse: collapse; +} + +#summary td { + vertical-align: top; +} + +.breadcrumbs, .breadcrumbs a { + color: #606060; +} + +.infoBox { + width: 110px; + padding-top: 15px; + padding-bottom: 15px; + text-align: center; +} + +.infoBox p { + margin: 0; +} + +.counter, .percent { + font-size: 120%; + font-weight: bold; + margin-bottom: 8px; +} + +#duration { + width: 125px; +} + +#successRate, .summaryGroup { + border: solid 2px #d0d0d0; + -moz-border-radius: 10px; + border-radius: 10px; +} + +#successRate { + width: 140px; + margin-left: 35px; +} + +#successRate .percent { + font-size: 180%; +} + +.success, .success a { + color: #008000; +} + +div.success, #successRate.success { + background-color: #bbd9bb; + border-color: #008000; +} + +.failures, .failures a { + color: #b60808; +} + +.skipped, .skipped a { + color: #c09853; +} + +div.failures, #successRate.failures { + background-color: #ecdada; + border-color: #b60808; +} + +ul.linkList { + padding-left: 0; +} + +ul.linkList li { + list-style: none; + margin-bottom: 5px; +} diff --git a/build_reports/test_report_release/index.html b/build_reports/test_report_release/index.html new file mode 100644 index 000000000..598e9a967 --- /dev/null +++ b/build_reports/test_report_release/index.html @@ -0,0 +1,133 @@ + + + + + +Test results - Test Summary + + + + + +
+

Test Summary

+
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.004s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Packages

+ + + + + + + + + + + + + + + + + + + + + +
PackageTestsFailuresIgnoredDurationSuccess rate
+com.greybox.projectmesh +1000.004s100%
+
+
+

Classes

+ + + + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+com.greybox.projectmesh.ExampleUnitTest +1000.004s100%
+
+
+ +
+ + diff --git a/build_reports/test_report_release/js/report.js b/build_reports/test_report_release/js/report.js new file mode 100644 index 000000000..83bab4a19 --- /dev/null +++ b/build_reports/test_report_release/js/report.js @@ -0,0 +1,194 @@ +(function (window, document) { + "use strict"; + + var tabs = {}; + + function changeElementClass(element, classValue) { + if (element.getAttribute("className")) { + element.setAttribute("className", classValue); + } else { + element.setAttribute("class", classValue); + } + } + + function getClassAttribute(element) { + if (element.getAttribute("className")) { + return element.getAttribute("className"); + } else { + return element.getAttribute("class"); + } + } + + function addClass(element, classValue) { + changeElementClass(element, getClassAttribute(element) + " " + classValue); + } + + function removeClass(element, classValue) { + changeElementClass(element, getClassAttribute(element).replace(classValue, "")); + } + + function initTabs() { + var container = document.getElementById("tabs"); + + tabs.tabs = findTabs(container); + tabs.titles = findTitles(tabs.tabs); + tabs.headers = findHeaders(container); + tabs.select = select; + tabs.deselectAll = deselectAll; + tabs.select(0); + + return true; + } + + function getCheckBox() { + return document.getElementById("line-wrapping-toggle"); + } + + function getLabelForCheckBox() { + return document.getElementById("label-for-line-wrapping-toggle"); + } + + function findCodeBlocks() { + var spans = document.getElementById("tabs").getElementsByTagName("span"); + var codeBlocks = []; + for (var i = 0; i < spans.length; ++i) { + if (spans[i].className.indexOf("code") >= 0) { + codeBlocks.push(spans[i]); + } + } + return codeBlocks; + } + + function forAllCodeBlocks(operation) { + var codeBlocks = findCodeBlocks(); + + for (var i = 0; i < codeBlocks.length; ++i) { + operation(codeBlocks[i], "wrapped"); + } + } + + function toggleLineWrapping() { + var checkBox = getCheckBox(); + + if (checkBox.checked) { + forAllCodeBlocks(addClass); + } else { + forAllCodeBlocks(removeClass); + } + } + + function initControls() { + if (findCodeBlocks().length > 0) { + var checkBox = getCheckBox(); + var label = getLabelForCheckBox(); + + checkBox.onclick = toggleLineWrapping; + checkBox.checked = false; + + removeClass(label, "hidden"); + } + } + + function switchTab() { + var id = this.id.substr(1); + + for (var i = 0; i < tabs.tabs.length; i++) { + if (tabs.tabs[i].id === id) { + tabs.select(i); + break; + } + } + + return false; + } + + function select(i) { + this.deselectAll(); + + changeElementClass(this.tabs[i], "tab selected"); + changeElementClass(this.headers[i], "selected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var h2 = document.createElement("H2"); + + h2.appendChild(document.createTextNode(this.titles[i])); + this.headers[i].appendChild(h2); + } + + function deselectAll() { + for (var i = 0; i < this.tabs.length; i++) { + changeElementClass(this.tabs[i], "tab deselected"); + changeElementClass(this.headers[i], "deselected"); + + while (this.headers[i].firstChild) { + this.headers[i].removeChild(this.headers[i].firstChild); + } + + var a = document.createElement("A"); + + a.setAttribute("id", "ltab" + i); + a.setAttribute("href", "#tab" + i); + a.onclick = switchTab; + a.appendChild(document.createTextNode(this.titles[i])); + + this.headers[i].appendChild(a); + } + } + + function findTabs(container) { + return findChildElements(container, "DIV", "tab"); + } + + function findHeaders(container) { + var owner = findChildElements(container, "UL", "tabLinks"); + return findChildElements(owner[0], "LI", null); + } + + function findTitles(tabs) { + var titles = []; + + for (var i = 0; i < tabs.length; i++) { + var tab = tabs[i]; + var header = findChildElements(tab, "H2", null)[0]; + + header.parentNode.removeChild(header); + + if (header.innerText) { + titles.push(header.innerText); + } else { + titles.push(header.textContent); + } + } + + return titles; + } + + function findChildElements(container, name, targetClass) { + var elements = []; + var children = container.childNodes; + + for (var i = 0; i < children.length; i++) { + var child = children.item(i); + + if (child.nodeType === 1 && child.nodeName === name) { + if (targetClass && child.className.indexOf(targetClass) < 0) { + continue; + } + + elements.push(child); + } + } + + return elements; + } + + // Entry point. + + window.onload = function() { + initTabs(); + initControls(); + }; +} (window, window.document)); \ No newline at end of file diff --git a/build_reports/test_report_release/packages/com.greybox.projectmesh.html b/build_reports/test_report_release/packages/com.greybox.projectmesh.html new file mode 100644 index 000000000..6b7ecd9ee --- /dev/null +++ b/build_reports/test_report_release/packages/com.greybox.projectmesh.html @@ -0,0 +1,103 @@ + + + + + +Test results - Package com.greybox.projectmesh + + + + + +
+

Package com.greybox.projectmesh

+ +
+ + + + + +
+
+ + + + + + + +
+
+
1
+

tests

+
+
+
+
0
+

failures

+
+
+
+
0
+

ignored

+
+
+
+
0.004s
+

duration

+
+
+
+
+
+
100%
+

successful

+
+
+
+
+ +
+

Classes

+ + + + + + + + + + + + + + + + + + + +
ClassTestsFailuresIgnoredDurationSuccess rate
+ExampleUnitTest +1000.004s100%
+
+
+ +
+ + From 9598b21c715a44703e58c81f1c7b6451e668686a Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:39:46 -0700 Subject: [PATCH 13/87] Implement task failure for gradle lint warnings goal is to improve visibility and encourage early corrections --- .github/README.md | 19 ++++++++++++------- .github/workflows/format_and_lint.yml | 12 ++++++++++-- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/README.md b/.github/README.md index f8593bec3..064c3022a 100644 --- a/.github/README.md +++ b/.github/README.md @@ -2,9 +2,11 @@ --- -## NOTE +## NOTES + +- To run the orchestrator to test on branches outside of main, run ```gh workflow run reporting_orchestrator.yml --ref feature/ras-cicd```, replacing "feature/ras-cicd" whichever branch you wish to run the workflow against. +- Super-Linter summary can only be added on pushes and PRs, not manual runs. Super-Linter is still running, you just need to dig into the workflow details to view useful results. -To run the orchestrator to test on branches outside of main, run ```gh workflow run reporting_orchestrator.yml --ref feature/ras-cicd```, replacing "feature/ras-cicd" whichever branch you wish to run the workflow against. --- ## Pipeline Flow @@ -15,7 +17,7 @@ The CI pipeline behaves as follows: a. Do not use prettier-plugin-kotlin, it is not maintained and errors regularly 2. On push, run super-linter. For all possible cases, run autofix (this covers Kotlin) -- Needs updating +- This Section Needs updating --- @@ -24,9 +26,8 @@ The CI pipeline behaves as follows: ### Ongoing - For all workflows, add current build summary as commit/pr comments -- Alter system to use GitHub Pages instead of github HTML preview. This will fix CSS on previews - - Add script to change iFrame title and onscreen title + pass/fail indicator -- Add orchestrator workflow for all reporting tasks +- Add script to preview to change iFrame title and onscreen title + pass/fail indicator +- For orchestrator - determine how to allow multiple reusable workflows to share build cache - add link to super-linter actions output - onsider whether to delete reports files after use @@ -41,4 +42,8 @@ The CI pipeline behaves as follows: - Break workflows out into multiple intelligently-grouped jobs for improved execution visibility - Add graceful error handling if specific preview reports cannot be generated - Add graceful error handling to note that coverage reporting can only be generated on PRs and pushes, not workflow dispatches -- Research if it's possible to ask to run workflows on any push \ No newline at end of file +- Research if it's possible to ask to run workflows on any push + +### Integration +- add dokka +- add uml to previewer \ No newline at end of file diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index cf5bf1d18..c5f5c384b 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -49,8 +49,16 @@ jobs: - name: Call Debug and Release Linters # ostensibly, ./gradlew lint should run both, but in practice on my (Thalia Wood's), it only runs the Debug linter run: | - ./gradlew lintDebug - ./gradlew lintRelease + output1=$(./gradlew lintDebug) + output2=$(./gradlew lintRelease) + echo "$output1" + echo "---" + echo "$output2" + if (echo "$output1" | grep -q " Task :app:compileDebugKotlin\nw: file:") || (echo "$output2" | grep -q " Task :app:compileDebugKotlin\nw: file:"); then + echo "Linting warnings detected. View details at https://grey-box.github.io/Project-Mesh/ once the workflow is finished." + exit 1 + fi + continue-on-error: true - name: Configure Git Identity (if called individually) if: ${{ inputs.skip_commit != true }} From 2a52722478004fdaaefda736f3318890049bccea Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 16 Feb 2026 23:50:31 +0000 Subject: [PATCH 14/87] Relocate reports for viewing --- build_reports/lint_report_debug/lint_report_debug.html | 2 +- .../lint_report_release/lint_report_release.html | 2 +- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 6 +++--- build_reports/test_report_debug/index.html | 8 ++++---- .../packages/com.greybox.projectmesh.html | 6 +++--- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 6 +++--- build_reports/test_report_release/index.html | 8 ++++---- .../packages/com.greybox.projectmesh.html | 6 +++--- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/build_reports/lint_report_debug/lint_report_debug.html b/build_reports/lint_report_debug/lint_report_debug.html index fd821dab2..b0df8b37e 100644 --- a/build_reports/lint_report_debug/lint_report_debug.html +++ b/build_reports/lint_report_debug/lint_report_debug.html @@ -179,7 +179,7 @@
Lint Report: 143 warnings
- +
diff --git a/build_reports/lint_report_release/lint_report_release.html b/build_reports/lint_report_release/lint_report_release.html index 5195e93a4..9ed449ae9 100644 --- a/build_reports/lint_report_release/lint_report_release.html +++ b/build_reports/lint_report_release/lint_report_release.html @@ -179,7 +179,7 @@
Lint Report: 124 warnings
- +
diff --git a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html index 8f1c57524..7e27b350a 100644 --- a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -41,7 +41,7 @@

Class com.greybox.projectmesh.ExampleUnitTest

-
0.002s
+
0.003s

duration

@@ -76,7 +76,7 @@

Tests

addition_isCorrect -0.002s +0.003s passed @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 16, 2026, 11:21:17 PM

+Gradle 8.9 at Feb 16, 2026, 11:48:31 PM

diff --git a/build_reports/test_report_debug/index.html b/build_reports/test_report_debug/index.html index aa73f5d9a..8b31dd7f6 100644 --- a/build_reports/test_report_debug/index.html +++ b/build_reports/test_report_debug/index.html @@ -38,7 +38,7 @@

Test Summary

-
0.002s
+
0.003s

duration

@@ -85,7 +85,7 @@

Packages

1 0 0 -0.002s +0.003s 100% @@ -112,7 +112,7 @@

Classes

1 0 0 -0.002s +0.003s 100% @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:21:17 PM

+Gradle 8.9 at Feb 16, 2026, 11:48:31 PM

diff --git a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html index a7ed02e0b..fc39036ad 100644 --- a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html @@ -40,7 +40,7 @@

Package com.greybox.projectmesh

-
0.002s
+
0.003s

duration

@@ -83,7 +83,7 @@

Classes

1 0 0 -0.002s +0.003s 100% @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:21:17 PM

+Gradle 8.9 at Feb 16, 2026, 11:48:31 PM

diff --git a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html index cc61c7308..bc77628fd 100644 --- a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -41,7 +41,7 @@

Class com.greybox.projectmesh.ExampleUnitTest

-
0.004s
+
0.003s

duration

@@ -76,7 +76,7 @@

Tests

addition_isCorrect -0.004s +0.003s passed @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 16, 2026, 11:21:19 PM

+Gradle 8.9 at Feb 16, 2026, 11:48:32 PM

diff --git a/build_reports/test_report_release/index.html b/build_reports/test_report_release/index.html index 598e9a967..19633fc7d 100644 --- a/build_reports/test_report_release/index.html +++ b/build_reports/test_report_release/index.html @@ -38,7 +38,7 @@

Test Summary

-
0.004s
+
0.003s

duration

@@ -85,7 +85,7 @@

Packages

1 0 0 -0.004s +0.003s 100% @@ -112,7 +112,7 @@

Classes

1 0 0 -0.004s +0.003s 100% @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:21:19 PM

+Gradle 8.9 at Feb 16, 2026, 11:48:32 PM

diff --git a/build_reports/test_report_release/packages/com.greybox.projectmesh.html b/build_reports/test_report_release/packages/com.greybox.projectmesh.html index 6b7ecd9ee..732d58d39 100644 --- a/build_reports/test_report_release/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_release/packages/com.greybox.projectmesh.html @@ -40,7 +40,7 @@

Package com.greybox.projectmesh

-
0.004s
+
0.003s

duration

@@ -83,7 +83,7 @@

Classes

1 0 0 -0.004s +0.003s 100% @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:21:19 PM

+Gradle 8.9 at Feb 16, 2026, 11:48:32 PM

From 272d44e73e998f2949795a70d7586a52d2659fed Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:25:12 -0700 Subject: [PATCH 15/87] Update format_and_lint.yml --- .github/README.md | 1 + .github/workflows/format_and_lint.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/README.md b/.github/README.md index 064c3022a..f37c76cd8 100644 --- a/.github/README.md +++ b/.github/README.md @@ -43,6 +43,7 @@ The CI pipeline behaves as follows: - Add graceful error handling if specific preview reports cannot be generated - Add graceful error handling to note that coverage reporting can only be generated on PRs and pushes, not workflow dispatches - Research if it's possible to ask to run workflows on any push +- Alter pages commit process to commit build_reports to gh-pages and not main, so it stays off main and avoids double-commits and extra fetches ### Integration - add dokka diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index c5f5c384b..5ae95d699 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -54,7 +54,7 @@ jobs: echo "$output1" echo "---" echo "$output2" - if (echo "$output1" | grep -q " Task :app:compileDebugKotlin\nw: file:") || (echo "$output2" | grep -q " Task :app:compileDebugKotlin\nw: file:"); then + if (echo "$output1" | grep -Ezq " Task :app:compileDebugKotlin\nw: file:") || (echo "$output2" | grep -Ezq " Task :app:compileReleaseKotlin\nw: file:"); then echo "Linting warnings detected. View details at https://grey-box.github.io/Project-Mesh/ once the workflow is finished." exit 1 fi From c9de3f42072ff9a2a37d1d8cf460c5620aa25ad4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Feb 2026 00:36:40 +0000 Subject: [PATCH 16/87] Relocate reports for viewing --- build_reports/lint_report_debug/lint_report_debug.html | 2 +- .../lint_report_release/lint_report_release.html | 2 +- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 2 +- build_reports/test_report_debug/index.html | 2 +- .../packages/com.greybox.projectmesh.html | 2 +- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 6 +++--- build_reports/test_report_release/index.html | 8 ++++---- .../packages/com.greybox.projectmesh.html | 6 +++--- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build_reports/lint_report_debug/lint_report_debug.html b/build_reports/lint_report_debug/lint_report_debug.html index b0df8b37e..db75aed4a 100644 --- a/build_reports/lint_report_debug/lint_report_debug.html +++ b/build_reports/lint_report_debug/lint_report_debug.html @@ -179,7 +179,7 @@
Lint Report: 143 warnings
- +
diff --git a/build_reports/lint_report_release/lint_report_release.html b/build_reports/lint_report_release/lint_report_release.html index 9ed449ae9..73de6d580 100644 --- a/build_reports/lint_report_release/lint_report_release.html +++ b/build_reports/lint_report_release/lint_report_release.html @@ -179,7 +179,7 @@
Lint Report: 124 warnings
- +
diff --git a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html index 7e27b350a..a92eb34d8 100644 --- a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 16, 2026, 11:48:31 PM

+Gradle 8.9 at Feb 17, 2026, 12:33:51 AM

diff --git a/build_reports/test_report_debug/index.html b/build_reports/test_report_debug/index.html index 8b31dd7f6..af35234a8 100644 --- a/build_reports/test_report_debug/index.html +++ b/build_reports/test_report_debug/index.html @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:48:31 PM

+Gradle 8.9 at Feb 17, 2026, 12:33:51 AM

diff --git a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html index fc39036ad..0566b739a 100644 --- a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:48:31 PM

+Gradle 8.9 at Feb 17, 2026, 12:33:51 AM

diff --git a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html index bc77628fd..16a2890b8 100644 --- a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -41,7 +41,7 @@

Class com.greybox.projectmesh.ExampleUnitTest

-
0.003s
+
0.002s

duration

@@ -76,7 +76,7 @@

Tests

addition_isCorrect -0.003s +0.002s passed @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 16, 2026, 11:48:32 PM

+Gradle 8.9 at Feb 17, 2026, 12:33:52 AM

diff --git a/build_reports/test_report_release/index.html b/build_reports/test_report_release/index.html index 19633fc7d..94fe5fc8d 100644 --- a/build_reports/test_report_release/index.html +++ b/build_reports/test_report_release/index.html @@ -38,7 +38,7 @@

Test Summary

-
0.003s
+
0.002s

duration

@@ -85,7 +85,7 @@

Packages

1 0 0 -0.003s +0.002s 100% @@ -112,7 +112,7 @@

Classes

1 0 0 -0.003s +0.002s 100% @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:48:32 PM

+Gradle 8.9 at Feb 17, 2026, 12:33:52 AM

diff --git a/build_reports/test_report_release/packages/com.greybox.projectmesh.html b/build_reports/test_report_release/packages/com.greybox.projectmesh.html index 732d58d39..cdc057aa2 100644 --- a/build_reports/test_report_release/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_release/packages/com.greybox.projectmesh.html @@ -40,7 +40,7 @@

Package com.greybox.projectmesh

-
0.003s
+
0.002s

duration

@@ -83,7 +83,7 @@

Classes

1 0 0 -0.003s +0.002s 100% @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 16, 2026, 11:48:32 PM

+Gradle 8.9 at Feb 17, 2026, 12:33:52 AM

From b63d2de4b8ebf75a0ac3c913ffff71c63283939b Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:03:44 -0700 Subject: [PATCH 17/87] Update format_and_lint.yml --- .github/workflows/format_and_lint.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index 5ae95d699..4e9da75ab 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -54,9 +54,11 @@ jobs: echo "$output1" echo "---" echo "$output2" - if (echo "$output1" | grep -Ezq " Task :app:compileDebugKotlin\nw: file:") || (echo "$output2" | grep -Ezq " Task :app:compileReleaseKotlin\nw: file:"); then + if [[ (echo "$output1" | grep -Ezq " Task :app:compileDebugKotlin\nw: file:") || (echo "$output2" | grep -Ezq " Task :app:compileReleaseKotlin\nw: file:") ]]; then echo "Linting warnings detected. View details at https://grey-box.github.io/Project-Mesh/ once the workflow is finished." exit 1 + else + echo "Linting returned clean" fi continue-on-error: true From b11a1578c1700e4a9be305bff3f837964b17f11d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Feb 2026 03:10:00 +0000 Subject: [PATCH 18/87] Relocate reports for viewing --- build_reports/lint_report_debug/lint_report_debug.html | 2 +- .../lint_report_release/lint_report_release.html | 2 +- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 6 +++--- build_reports/test_report_debug/index.html | 8 ++++---- .../packages/com.greybox.projectmesh.html | 6 +++--- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 6 +++--- build_reports/test_report_release/index.html | 8 ++++---- .../packages/com.greybox.projectmesh.html | 6 +++--- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/build_reports/lint_report_debug/lint_report_debug.html b/build_reports/lint_report_debug/lint_report_debug.html index db75aed4a..70572bf92 100644 --- a/build_reports/lint_report_debug/lint_report_debug.html +++ b/build_reports/lint_report_debug/lint_report_debug.html @@ -179,7 +179,7 @@
Lint Report: 143 warnings
- +
diff --git a/build_reports/lint_report_release/lint_report_release.html b/build_reports/lint_report_release/lint_report_release.html index 73de6d580..228ac1af6 100644 --- a/build_reports/lint_report_release/lint_report_release.html +++ b/build_reports/lint_report_release/lint_report_release.html @@ -179,7 +179,7 @@
Lint Report: 124 warnings
- +
diff --git a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html index a92eb34d8..0e995d7aa 100644 --- a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -41,7 +41,7 @@

Class com.greybox.projectmesh.ExampleUnitTest

-
0.003s
+
0.002s

duration

@@ -76,7 +76,7 @@

Tests

addition_isCorrect -0.003s +0.002s passed @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 17, 2026, 12:33:51 AM

+Gradle 8.9 at Feb 17, 2026, 3:07:01 AM

diff --git a/build_reports/test_report_debug/index.html b/build_reports/test_report_debug/index.html index af35234a8..67151140a 100644 --- a/build_reports/test_report_debug/index.html +++ b/build_reports/test_report_debug/index.html @@ -38,7 +38,7 @@

Test Summary

-
0.003s
+
0.002s

duration

@@ -85,7 +85,7 @@

Packages

1 0 0 -0.003s +0.002s 100% @@ -112,7 +112,7 @@

Classes

1 0 0 -0.003s +0.002s 100% @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 12:33:51 AM

+Gradle 8.9 at Feb 17, 2026, 3:07:01 AM

diff --git a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html index 0566b739a..963541868 100644 --- a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html @@ -40,7 +40,7 @@

Package com.greybox.projectmesh

-
0.003s
+
0.002s

duration

@@ -83,7 +83,7 @@

Classes

1 0 0 -0.003s +0.002s 100% @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 12:33:51 AM

+Gradle 8.9 at Feb 17, 2026, 3:07:01 AM

diff --git a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html index 16a2890b8..f8e6db619 100644 --- a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -41,7 +41,7 @@

Class com.greybox.projectmesh.ExampleUnitTest

-
0.002s
+
0.003s

duration

@@ -76,7 +76,7 @@

Tests

addition_isCorrect -0.002s +0.003s passed @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 17, 2026, 12:33:52 AM

+Gradle 8.9 at Feb 17, 2026, 3:07:02 AM

diff --git a/build_reports/test_report_release/index.html b/build_reports/test_report_release/index.html index 94fe5fc8d..1813f6f79 100644 --- a/build_reports/test_report_release/index.html +++ b/build_reports/test_report_release/index.html @@ -38,7 +38,7 @@

Test Summary

-
0.002s
+
0.003s

duration

@@ -85,7 +85,7 @@

Packages

1 0 0 -0.002s +0.003s 100% @@ -112,7 +112,7 @@

Classes

1 0 0 -0.002s +0.003s 100% @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 12:33:52 AM

+Gradle 8.9 at Feb 17, 2026, 3:07:02 AM

diff --git a/build_reports/test_report_release/packages/com.greybox.projectmesh.html b/build_reports/test_report_release/packages/com.greybox.projectmesh.html index cdc057aa2..02f60acfe 100644 --- a/build_reports/test_report_release/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_release/packages/com.greybox.projectmesh.html @@ -40,7 +40,7 @@

Package com.greybox.projectmesh

-
0.002s
+
0.003s

duration

@@ -83,7 +83,7 @@

Classes

1 0 0 -0.002s +0.003s 100% @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 12:33:52 AM

+Gradle 8.9 at Feb 17, 2026, 3:07:02 AM

From 8c7007e8cea1ba452a7155ffef21321a73b0db69 Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 16 Feb 2026 21:07:34 -0700 Subject: [PATCH 19/87] Update format_and_lint.yml --- .github/workflows/format_and_lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index 4e9da75ab..fbc7b6e5b 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -49,6 +49,7 @@ jobs: - name: Call Debug and Release Linters # ostensibly, ./gradlew lint should run both, but in practice on my (Thalia Wood's), it only runs the Debug linter run: | + set -o pipefail output1=$(./gradlew lintDebug) output2=$(./gradlew lintRelease) echo "$output1" From b4fbbebe10511eab146010941c64a9126faccfb3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 17 Feb 2026 04:14:52 +0000 Subject: [PATCH 20/87] Relocate reports for viewing --- build_reports/lint_report_debug/lint_report_debug.html | 2 +- .../lint_report_release/lint_report_release.html | 2 +- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 2 +- build_reports/test_report_debug/index.html | 2 +- .../packages/com.greybox.projectmesh.html | 2 +- .../classes/com.greybox.projectmesh.ExampleUnitTest.html | 6 +++--- build_reports/test_report_release/index.html | 8 ++++---- .../packages/com.greybox.projectmesh.html | 6 +++--- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build_reports/lint_report_debug/lint_report_debug.html b/build_reports/lint_report_debug/lint_report_debug.html index 70572bf92..6d45bd1ae 100644 --- a/build_reports/lint_report_debug/lint_report_debug.html +++ b/build_reports/lint_report_debug/lint_report_debug.html @@ -179,7 +179,7 @@
Lint Report: 143 warnings
- +
diff --git a/build_reports/lint_report_release/lint_report_release.html b/build_reports/lint_report_release/lint_report_release.html index 228ac1af6..3a13e3b68 100644 --- a/build_reports/lint_report_release/lint_report_release.html +++ b/build_reports/lint_report_release/lint_report_release.html @@ -179,7 +179,7 @@
Lint Report: 124 warnings
- +
diff --git a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html index 0e995d7aa..3167a20b8 100644 --- a/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_debug/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 17, 2026, 3:07:01 AM

+Gradle 8.9 at Feb 17, 2026, 4:12:55 AM

diff --git a/build_reports/test_report_debug/index.html b/build_reports/test_report_debug/index.html index 67151140a..6080dc161 100644 --- a/build_reports/test_report_debug/index.html +++ b/build_reports/test_report_debug/index.html @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 3:07:01 AM

+Gradle 8.9 at Feb 17, 2026, 4:12:55 AM

diff --git a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html index 963541868..6ec9d3138 100644 --- a/build_reports/test_report_debug/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_debug/packages/com.greybox.projectmesh.html @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 3:07:01 AM

+Gradle 8.9 at Feb 17, 2026, 4:12:55 AM

diff --git a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html index f8e6db619..c125da7b4 100644 --- a/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html +++ b/build_reports/test_report_release/classes/com.greybox.projectmesh.ExampleUnitTest.html @@ -41,7 +41,7 @@

Class com.greybox.projectmesh.ExampleUnitTest

-
0.003s
+
0.004s

duration

@@ -76,7 +76,7 @@

Tests

addition_isCorrect -0.003s +0.004s passed @@ -89,7 +89,7 @@

Tests

Generated by -Gradle 8.9 at Feb 17, 2026, 3:07:02 AM

+Gradle 8.9 at Feb 17, 2026, 4:12:56 AM

diff --git a/build_reports/test_report_release/index.html b/build_reports/test_report_release/index.html index 1813f6f79..7a9316fb7 100644 --- a/build_reports/test_report_release/index.html +++ b/build_reports/test_report_release/index.html @@ -38,7 +38,7 @@

Test Summary

-
0.003s
+
0.004s

duration

@@ -85,7 +85,7 @@

Packages

1 0 0 -0.003s +0.004s 100% @@ -112,7 +112,7 @@

Classes

1 0 0 -0.003s +0.004s 100% @@ -126,7 +126,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 3:07:02 AM

+Gradle 8.9 at Feb 17, 2026, 4:12:56 AM

diff --git a/build_reports/test_report_release/packages/com.greybox.projectmesh.html b/build_reports/test_report_release/packages/com.greybox.projectmesh.html index 02f60acfe..6aa519933 100644 --- a/build_reports/test_report_release/packages/com.greybox.projectmesh.html +++ b/build_reports/test_report_release/packages/com.greybox.projectmesh.html @@ -40,7 +40,7 @@

Package com.greybox.projectmesh

-
0.003s
+
0.004s

duration

@@ -83,7 +83,7 @@

Classes

1 0 0 -0.003s +0.004s 100% @@ -96,7 +96,7 @@

Classes

Generated by -Gradle 8.9 at Feb 17, 2026, 3:07:02 AM

+Gradle 8.9 at Feb 17, 2026, 4:12:56 AM

From 8fba811c1877a45d58f1af1b51fc7734ea010d2b Mon Sep 17 00:00:00 2001 From: Thalia Wood <42354895+polygeist111@users.noreply.github.com> Date: Mon, 23 Feb 2026 16:05:03 -0700 Subject: [PATCH 21/87] Migrate Dokka functionality into staged CI development branch (#44) * Dokka containing Added KDoc Comments * Adding all Kdoc comments, previously written --------- Co-authored-by: flow733 --- .../java/com/greybox/projectmesh/GlobalApp.kt | 5 - .../com/greybox/projectmesh/MainActivity.kt | 21 - .../projectmesh/components/WifiConnection.kt | 43 +- .../greybox/projectmesh/db/MeshDatabase.kt | 24 + .../greybox/projectmesh/debug/CrashHandler.kt | 72 ++- .../extension/ContentResolverExtension.kt | 21 +- .../projectmesh/extension/ContextExt.kt | 53 +- .../projectmesh/extension/ListExtension.kt | 12 +- .../projectmesh/extension/NetworkUtils.kt | 8 +- .../projectmesh/extension/WifiListItem.kt | 14 +- .../messaging/data/dao/ConversationDao.kt | 61 ++- .../messaging/data/dao/MessageDao.kt | 54 +- .../messaging/data/entities/Conversation.kt | 19 +- .../messaging/data/entities/FileEncoder.kt | 96 ++-- .../messaging/data/entities/JSONSchema.kt | 25 +- .../messaging/data/entities/Message.kt | 64 ++- .../network/MessageNetworkHandler.kt | 38 +- .../messaging/network/MessageService.kt | 21 +- .../repository/ConversationRepository.kt | 49 +- .../messaging/repository/MessageRepository.kt | 35 +- .../messaging/ui/models/ChatScreenModel.kt | 10 +- .../ui/models/ConversationsHomeScreenModel.kt | 9 +- .../ui/screens/ChatNodeListScreen.kt | 9 +- .../messaging/ui/screens/ChatScreen.kt | 33 +- .../ui/screens/ConversationsHomeScreen.kt | 29 +- .../ui/viewmodels/ChatScreenViewModel.kt | 30 +- .../ConversationsHomeScreenViewModel.kt | 23 +- .../projectmesh/messaging/utils/Logger.kt | 36 +- .../messaging/utils/MessageMigrationUtils.kt | 36 +- .../messaging/utils/MessageUtils.kt | 23 +- .../projectmesh/navigation/BottomNavHost.kt | 22 +- .../projectmesh/navigation/BottomNavItem.kt | 24 +- .../projectmesh/testing/TestDeviceEntry.kt | 28 +- .../projectmesh/testing/TestDeviceService.kt | 44 +- .../projectmesh/testing/TestMNetLogger.kt | 23 +- .../projectmesh/testing/TestVirtualRouter.kt | 48 +- .../projectmesh/ui/theme/CustomButton.kt | 61 ++- .../com/greybox/projectmesh/ui/theme/Theme.kt | 23 +- .../com/greybox/projectmesh/user/UserDao.kt | 46 +- .../greybox/projectmesh/user/UserEntity.kt | 15 +- .../projectmesh/user/UserRepository.kt | 35 +- .../projectmesh/util/NotificationHelper.kt | 32 +- index.html | 477 ++++++++++++++++++ 43 files changed, 1633 insertions(+), 218 deletions(-) create mode 100644 index.html diff --git a/app/src/main/java/com/greybox/projectmesh/GlobalApp.kt b/app/src/main/java/com/greybox/projectmesh/GlobalApp.kt index 7f22e1494..0156a677c 100644 --- a/app/src/main/java/com/greybox/projectmesh/GlobalApp.kt +++ b/app/src/main/java/com/greybox/projectmesh/GlobalApp.kt @@ -5,7 +5,6 @@ import android.app.Application import android.content.Context import android.content.SharedPreferences import android.os.Build -import android.os.Environment import android.util.Log import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.intPreferencesKey @@ -19,11 +18,9 @@ import com.greybox.projectmesh.extension.networkDataStore import com.greybox.projectmesh.server.AppServer import com.ustadmobile.meshrabiya.ext.addressToDotNotation import com.ustadmobile.meshrabiya.ext.asInetAddress -import com.ustadmobile.meshrabiya.ext.requireAddressAsInt import com.ustadmobile.meshrabiya.vnet.AndroidVirtualNode import com.ustadmobile.meshrabiya.vnet.randomApipaAddr import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -38,8 +35,6 @@ import org.kodein.di.singleton import java.io.File import java.net.InetAddress import java.time.Duration -import java.util.UUID -import java.util.concurrent.ConcurrentHashMap import com.greybox.projectmesh.user.UserRepository import com.greybox.projectmesh.messaging.data.entities.Message diff --git a/app/src/main/java/com/greybox/projectmesh/MainActivity.kt b/app/src/main/java/com/greybox/projectmesh/MainActivity.kt index 468680675..ed7f8df55 100644 --- a/app/src/main/java/com/greybox/projectmesh/MainActivity.kt +++ b/app/src/main/java/com/greybox/projectmesh/MainActivity.kt @@ -1,17 +1,9 @@ package com.greybox.projectmesh -import android.annotation.SuppressLint -import android.app.AlertDialog -import android.content.Context -import android.content.Context.MODE_PRIVATE -import android.content.Intent import android.content.SharedPreferences -import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment -import android.os.PowerManager -import android.provider.Settings import android.util.Log import android.widget.Toast import androidx.activity.ComponentActivity @@ -21,7 +13,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -63,30 +54,18 @@ import org.kodein.di.instance import java.io.File import java.util.Locale import java.net.InetAddress -import com.greybox.projectmesh.messaging.ui.screens.ChatNodeListScreen import com.greybox.projectmesh.messaging.ui.screens.ConversationsHomeScreen import com.greybox.projectmesh.user.UserRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.runtime.remember -import kotlinx.coroutines.launch import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.launch import com.greybox.projectmesh.messaging.data.entities.Conversation import com.greybox.projectmesh.messaging.ui.viewmodels.ChatScreenViewModel import com.greybox.projectmesh.views.LogScreen diff --git a/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt b/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt index 864274d4d..a40fc29d4 100644 --- a/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt +++ b/app/src/main/java/com/greybox/projectmesh/components/WifiConnection.kt @@ -28,7 +28,6 @@ import com.ustadmobile.meshrabiya.vnet.wifi.WifiConnectException import java.util.regex.Pattern import android.util.Log - // This File is to pre-check the wifi connection, reusing from Meshrabiya test app /* WorkFlow: @@ -41,24 +40,62 @@ WorkFlow: 4. It handles the result (successful connection or error) and updates the UI as needed. */ -fun interface ConnectWifiLauncher{ +/** + * Functional interface representing a launcher for Wi-Fi connections. + */ +fun interface ConnectWifiLauncher { + /** + * Launch a connection attempt with the specified Wi-Fi configuration. + * + * @param config The Wi-Fi configuration to connect to. + */ fun launch(config: WifiConnectConfig) } +/** + * Represents a request to connect to a Wi-Fi network. + * + * @property receivedTime The timestamp when the request was created. + * @property connectConfig The configuration of the Wi-Fi network to connect to. + */ data class ConnectRequest( val receivedTime: Long = 0, val connectConfig: WifiConnectConfig, ) +/** + * Result of a Wi-Fi connection attempt. + * + * @property hotspotConfig The configuration of the hotspot connected to, or null if failed. + * @property exception Any exception that occurred during connection, or null if successful. + * @property isWifiConnected True if the connection was successful, false otherwise. + */ data class ConnectWifiLauncherResult( val hotspotConfig: WifiConnectConfig?, val exception: Exception? = null, val isWifiConnected: Boolean = false, ) + +/** + * Status of the ConnectWifiLauncher during a Wi-Fi connection attempt. + */ enum class ConnectWifiLauncherStatus { INACTIVE, REQUESTING_PERMISSION, LOOKING_FOR_NETWORK, REQUESTING_LINK, } +/** + * Composable function that provides a [ConnectWifiLauncher] for managing Wi-Fi connections. + * + * It handles permission requests, network association via [CompanionDeviceManager], + * and provides status updates and connection results. + * + * @param node The [AndroidVirtualNode] representing the local virtual node. + * @param logger Optional logger for debugging messages. + * @param onStatusChange Optional callback invoked when the launcher status changes. + * @param onResult Callback invoked with the result of the Wi-Fi connection attempt. + * + * @return A [ConnectWifiLauncher] that can be used to initiate Wi-Fi connections. + */ @Composable fun meshrabiyaConnectLauncher( node: AndroidVirtualNode, @@ -236,4 +273,4 @@ fun meshrabiyaConnectLauncher( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/db/MeshDatabase.kt b/app/src/main/java/com/greybox/projectmesh/db/MeshDatabase.kt index 1f3dba4cc..05dfebed9 100644 --- a/app/src/main/java/com/greybox/projectmesh/db/MeshDatabase.kt +++ b/app/src/main/java/com/greybox/projectmesh/db/MeshDatabase.kt @@ -9,6 +9,12 @@ import com.greybox.projectmesh.messaging.data.entities.Conversation import com.greybox.projectmesh.user.UserDao import com.greybox.projectmesh.user.UserEntity +/** + * Room database for the ProjectMesh application. + * + * This database stores messages, conversations, and user entities. + * It provides DAOs to access and manipulate each type of data. + */ @Database( entities = [ Message::class, @@ -19,7 +25,25 @@ import com.greybox.projectmesh.user.UserEntity exportSchema = false ) abstract class MeshDatabase : RoomDatabase() { + + /** + * Provides access to message-related database operations. + * + * @return A [MessageDao] instance for querying and modifying messages. + */ abstract fun messageDao(): MessageDao + + /** + * Provides access to user-related database operations. + * + * @return A [UserDao] instance for querying and modifying user entities. + */ abstract fun userDao(): UserDao + + /** + * Provides access to conversation-related database operations. + * + * @return A [ConversationDao] instance for querying and modifying conversations. + */ abstract fun conversationDao(): ConversationDao } diff --git a/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt b/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt index 3cc15d6a9..5c7cf28d7 100644 --- a/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt +++ b/app/src/main/java/com/greybox/projectmesh/debug/CrashHandler.kt @@ -12,24 +12,48 @@ import java.lang.Exception import java.lang.Thread.UncaughtExceptionHandler import kotlin.system.exitProcess +/** + * Custom [Thread.UncaughtExceptionHandler] to handle uncaught exceptions in the app. + * + * This handler launches a specified activity when a crash occurs, passing the exception + * details via an Intent, and then terminates the app. + * + * @param context The application context used to launch the crash activity. + * @param defaultHandler The default uncaught exception handler to fallback on. + * @param activityToBeLaunched The activity class to be launched when a crash occurs. + */ +class CrashHandler( + private val context: Context, + private val defaultHandler: UncaughtExceptionHandler, + private val activityToBeLaunched: Class<*> +) : Thread.UncaughtExceptionHandler { -class CrashHandler(private val context: Context, private val defaultHandler: UncaughtExceptionHandler, private val activityToBeLaunched: Class<*>) : Thread.UncaughtExceptionHandler { - + /** + * Handles uncaught exceptions thrown by any thread. + * + * @param thread The thread where the exception occurred. + * @param throwable The uncaught exception. + */ override fun uncaughtException(thread: Thread, throwable: Throwable) { try { - launchActivity(context,activityToBeLaunched,throwable) + launchActivity(context, activityToBeLaunched, throwable) exitProcess(status = 1) - } catch (e: Exception) - { - defaultHandler.uncaughtException(thread,throwable) + } catch (e: Exception) { + defaultHandler.uncaughtException(thread, throwable) } } - private fun launchActivity(applicationContext: Context, activity: Class<*>, exception: Throwable) - { + /** + * Launches the crash reporting activity with the exception details. + * + * @param applicationContext The context used to start the activity. + * @param activity The activity class to launch. + * @param exception The exception to pass to the activity. + */ + private fun launchActivity(applicationContext: Context, activity: Class<*>, exception: Throwable) { val crashIntent = Intent(applicationContext, activity).also { it.putExtra("CrashData", Gson().toJson(exception)) - Log.e("Project Mesh Error","Error: ",exception); + Log.e("Project Mesh Error", "Error: ", exception) } crashIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) @@ -38,22 +62,34 @@ class CrashHandler(private val context: Context, private val defaultHandler: Unc } companion object { - fun init(applicationContext: Context, activityToBeLaunched: Class<*>) - { - val handler = CrashHandler(applicationContext,Thread.getDefaultUncaughtExceptionHandler() as UncaughtExceptionHandler, activityToBeLaunched) + /** + * Initializes the [CrashHandler] and sets it as the default uncaught exception handler. + * + * @param applicationContext The application context used to create the handler. + * @param activityToBeLaunched The activity class to launch on crash. + */ + fun init(applicationContext: Context, activityToBeLaunched: Class<*>) { + val handler = CrashHandler( + applicationContext, + Thread.getDefaultUncaughtExceptionHandler() as UncaughtExceptionHandler, + activityToBeLaunched + ) Thread.setDefaultUncaughtExceptionHandler(handler) } - fun getThrowableFromIntent(intent: Intent): Throwable? - { + /** + * Retrieves a [Throwable] from an intent containing crash data. + * + * @param intent The intent containing serialized crash data. + * @return The deserialized [Throwable], or null if parsing fails. + */ + fun getThrowableFromIntent(intent: Intent): Throwable? { return try { Gson().fromJson(intent.getStringExtra("CrashData"), Throwable::class.java) - } - catch (e: Exception) { - Log.e("CrashHandler","getThrowableFromIntent: ",e); + } catch (e: Exception) { + Log.e("CrashHandler", "getThrowableFromIntent: ", e) null } - } } } diff --git a/app/src/main/java/com/greybox/projectmesh/extension/ContentResolverExtension.kt b/app/src/main/java/com/greybox/projectmesh/extension/ContentResolverExtension.kt index bf474affe..2f3c4a0da 100644 --- a/app/src/main/java/com/greybox/projectmesh/extension/ContentResolverExtension.kt +++ b/app/src/main/java/com/greybox/projectmesh/extension/ContentResolverExtension.kt @@ -5,6 +5,12 @@ import android.net.Uri import android.provider.OpenableColumns import androidx.core.net.toFile +/** + * Represents the name and size of a file referenced by a [Uri]. + * + * @property name The display name of the file, or null if it cannot be determined. + * @property size The size of the file in bytes, or -1 if unknown. + */ data class UriNameAndSize( val name: String?, val size: Long, @@ -16,12 +22,23 @@ It will return a UriNameAndSize object that contains the name and size of the fi Two Condition: 1. The uri is a file uri 2. The uri is a content uri +*/ + +/** + * Retrieves the name and size of a file referenced by the given [uri]. + * + * Supports both "file" scheme URIs and "content" scheme URIs. + * + * @receiver The [ContentResolver] used to query content URIs. + * @param uri The [Uri] pointing to the file. + * @return A [UriNameAndSize] object containing the file's name and size, or + * null name and -1 size if the information cannot be determined. */ fun ContentResolver.getUriNameAndSize(uri: Uri): UriNameAndSize { return if(uri.scheme == "file") { val uriFile = uri.toFile() UriNameAndSize(uriFile.name, uriFile.length()) - }else { + } else { query( uri, null, null, null, null )?.use { cursor -> @@ -35,7 +52,7 @@ fun ContentResolver.getUriNameAndSize(uri: Uri): UriNameAndSize { cursor.getString(sizeIndex) } UriNameAndSize(cursor.getString(nameIndex), size?.toLong() ?: -1L) - }else { + } else { null } } ?: UriNameAndSize(null, -1) diff --git a/app/src/main/java/com/greybox/projectmesh/extension/ContextExt.kt b/app/src/main/java/com/greybox/projectmesh/extension/ContextExt.kt index c2cfa4632..beb3159c6 100644 --- a/app/src/main/java/com/greybox/projectmesh/extension/ContextExt.kt +++ b/app/src/main/java/com/greybox/projectmesh/extension/ContextExt.kt @@ -14,11 +14,13 @@ import com.ustadmobile.meshrabiya.MeshrabiyaConstants /* context is a class that provides access to application-specific resources and classes. This File contains several context related extension functions that will use in this app. - */ +*/ /** - * On Android 13+ we can use the NEARBY_WIFI_DEVICES permission instead of the location permission. - * On earlier versions, we need fine location permission + * The permission string required for accessing nearby Wi-Fi devices. + * + * On Android 13+ (SDK 33+), uses [Manifest.permission.NEARBY_WIFI_DEVICES]. + * On earlier versions, falls back to [Manifest.permission.ACCESS_FINE_LOCATION]. */ val NEARBY_WIFI_PERMISSION_NAME = if(Build.VERSION.SDK_INT >= 33){ Manifest.permission.NEARBY_WIFI_DEVICES @@ -26,32 +28,65 @@ val NEARBY_WIFI_PERMISSION_NAME = if(Build.VERSION.SDK_INT >= 33){ Manifest.permission.ACCESS_FINE_LOCATION } -// check if the app has the nearby wifi devices permission +/** + * Checks whether the app has permission to access nearby Wi-Fi devices (Android 13+) + * or fine location (pre-Android 13). + * + * @receiver The [Context] used to check permissions. + * @return `true` if the required permission is granted, `false` otherwise. + */ fun Context.hasNearbyWifiDevicesOrLocationPermission(): Boolean { return ContextCompat.checkSelfPermission( this, NEARBY_WIFI_PERMISSION_NAME ) == PackageManager.PERMISSION_GRANTED } -// check if the app has the bluetooth connect permission +/** + * Checks whether the app has permission to connect to Bluetooth devices. + * + * On Android 12+ (SDK 31+), uses [Manifest.permission.BLUETOOTH_CONNECT]. + * On earlier versions, always returns `true`. + * + * @receiver The [Context] used to check permissions. + * @return `true` if the permission is granted or not required, `false` otherwise. + */ fun Context.hasBluetoothConnectPermission(): Boolean { return if(Build.VERSION.SDK_INT >= 31) { ContextCompat.checkSelfPermission( this, Manifest.permission.BLUETOOTH_CONNECT ) == PackageManager.PERMISSION_GRANTED - }else { + } else { true } } -// create a DataStore instance that Meshrabiya can use to remember networks +/** + * Provides a [DataStore] instance named "meshr_settings" for storing persistent + * network-related preferences used by Meshrabiya. + */ val Context.networkDataStore: DataStore by preferencesDataStore(name = "meshr_settings") -// Check if the device supports WiFi STA/AP Concurrency +/** + * Checks if the device supports Wi-Fi STA/AP concurrency (simultaneous station and access point mode). + * + * Requires Android 11+ (SDK 30+). + * + * @receiver The [Context] used to access [WifiManager]. + * @return `true` if STA/AP concurrency is supported, `false` otherwise. + */ fun Context.hasStaApConcurrency(): Boolean { return Build.VERSION.SDK_INT >= 30 && getSystemService(WifiManager::class.java).isStaApConcurrencySupported } +/** + * Returns a detailed string describing the device and Wi-Fi capabilities. + * + * Includes Meshrabiya version, Android version, device manufacturer/model, + * 5GHz support, local-only station concurrency, STA/AP concurrency, and Wi-Fi Aware support. + * + * @receiver The [Context] used to access system services and package manager. + * @return A formatted [String] describing the device and Wi-Fi features. + */ fun Context.deviceInfo(): String { val wifiManager = getSystemService(WifiManager::class.java) val hasStaConcurrency = Build.VERSION.SDK_INT >= 31 && @@ -69,4 +104,4 @@ fun Context.deviceInfo(): String { append("Station-AP concurrency: $hasStaApConcurrency\n") append("WifiAware support: $hasWifiAwareSupport\n") } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/extension/ListExtension.kt b/app/src/main/java/com/greybox/projectmesh/extension/ListExtension.kt index 598d619f5..52282b949 100644 --- a/app/src/main/java/com/greybox/projectmesh/extension/ListExtension.kt +++ b/app/src/main/java/com/greybox/projectmesh/extension/ListExtension.kt @@ -3,6 +3,16 @@ package com.greybox.projectmesh.extension /* This is an extension function on Kotlin's List class, allowing to apply an update to the first element in a list that matches a given condition, then returning a updated list +*/ + +/** + * Returns a new list with the first element that satisfies [condition] updated by [function]. + * + * If no element matches [condition], the original list is returned unchanged. + * + * @param condition A predicate to identify which element to update. + * @param function A transformation function applied to the matching element. + * @return A new [List] with the updated element, or the original list if no element matches. */ inline fun List.updateItem( condition: (T) -> Boolean, @@ -20,4 +30,4 @@ inline fun List.updateItem( newList -> newList[index] = function(this[index]) }.toList() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/extension/NetworkUtils.kt b/app/src/main/java/com/greybox/projectmesh/extension/NetworkUtils.kt index d9b8ae0ef..1765ea6bd 100644 --- a/app/src/main/java/com/greybox/projectmesh/extension/NetworkUtils.kt +++ b/app/src/main/java/com/greybox/projectmesh/extension/NetworkUtils.kt @@ -4,8 +4,14 @@ import com.ustadmobile.meshrabiya.vnet.AndroidVirtualNode import org.kodein.di.DI import org.kodein.di.instance +/** + * Retrieves the local IP address of the [AndroidVirtualNode] from a Kodein [DI] container. + * + * @param di The [DI] instance used to obtain the [AndroidVirtualNode]. + * @return The host IP address of the node as a [String]. + */ fun getLocalIpFromDI(di: DI): String { // Retrieve the AndroidVirtualNode from DI and return its IP address val node: AndroidVirtualNode by di.instance() return node.address.hostAddress -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/extension/WifiListItem.kt b/app/src/main/java/com/greybox/projectmesh/extension/WifiListItem.kt index d8992dfc3..ab94ae781 100644 --- a/app/src/main/java/com/greybox/projectmesh/extension/WifiListItem.kt +++ b/app/src/main/java/com/greybox/projectmesh/extension/WifiListItem.kt @@ -24,8 +24,18 @@ import com.ustadmobile.meshrabiya.ext.addressToDotNotation import com.ustadmobile.meshrabiya.vnet.VirtualNode import kotlinx.coroutines.runBlocking import com.greybox.projectmesh.user.UserRepository + +/** + * Displays a single Wi-Fi node in a list with device information and mesh network status. + * + * This composable shows the device icon, name (from IP address), IP in dot notation, + * and mesh network details including ping time and hop count. + * + * @param wifiAddress The integer IP address of the Wi-Fi node. + * @param wifiEntry The [VirtualNode.LastOriginatorMessage] containing the node's mesh message data. + * @param onClick Optional lambda invoked when the list item is clicked, providing the node's IP in dot notation. + */ @Composable -// Display a single connected wifi station fun WifiListItem( wifiAddress: Int, wifiEntry: VirtualNode.LastOriginatorMessage, @@ -89,4 +99,4 @@ fun WifiListItem( } ) HorizontalDivider() -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/ConversationDao.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/ConversationDao.kt index 8e1439369..5616eedd6 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/ConversationDao.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/ConversationDao.kt @@ -8,32 +8,91 @@ import androidx.room.Update import com.greybox.projectmesh.messaging.data.entities.Conversation import kotlinx.coroutines.flow.Flow +/** + * Data Access Object for [Conversation] entities. + * + * Provides methods to query, insert, and update conversations in the Room database. + */ @Dao interface ConversationDao { + + /** + * Returns a flow of all conversations, sorted by the timestamp of the last message in descending order. + * + * @return [Flow] emitting a list of [Conversation] objects whenever the data changes. + */ @Query("SELECT * FROM conversations ORDER BY last_message_time DESC") fun getAllConversationsFlow(): Flow> + /** + * Retrieves a conversation by its unique ID. + * + * @param conversationId The unique ID of the conversation. + * @return The [Conversation] if found, or `null` if no matching conversation exists. + */ @Query("SELECT * FROM conversations WHERE id = :conversationId LIMIT 1") suspend fun getConversationById(conversationId: String): Conversation? + /** + * Retrieves a conversation associated with a specific user UUID. + * + * @param userUuid The UUID of the user. + * @return The [Conversation] if found, or `null` if no matching conversation exists. + */ @Query("SELECT * FROM conversations WHERE user_uuid = :userUuid LIMIT 1") suspend fun getConversationByUserUuid(userUuid: String): Conversation? + /** + * Inserts a new conversation into the database. + * + * If a conversation with the same ID already exists, it will be replaced. + * + * @param conversation The [Conversation] to insert. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertConversation(conversation: Conversation) + /** + * Updates an existing conversation in the database. + * + * @param conversation The [Conversation] to update. + */ @Update suspend fun updateConversation(conversation: Conversation) + /** + * Updates the online status and user address for a conversation based on the user UUID. + * + * @param userUuid The UUID of the user. + * @param isOnline Whether the user is currently online. + * @param userAddress The user's network address (nullable). + */ @Query("UPDATE conversations SET is_online = :isOnline, user_address = :userAddress WHERE user_uuid = :userUuid") suspend fun updateUserConnectionStatus(userUuid: String, isOnline: Boolean, userAddress: String?) + /** + * Updates the last message and its timestamp for a specific conversation. + * + * @param conversationId The unique ID of the conversation. + * @param lastMessage The latest message text. + * @param timestamp The time when the last message was sent. + */ @Query("UPDATE conversations SET last_message = :lastMessage, last_message_time = :timestamp WHERE id = :conversationId") suspend fun updateLastMessage(conversationId: String, lastMessage: String, timestamp: Long) + /** + * Increments the unread message count for a specific conversation by 1. + * + * @param conversationId The unique ID of the conversation. + */ @Query("UPDATE conversations SET unread_count = unread_count + 1 WHERE id = :conversationId") suspend fun incrementUnreadCount(conversationId: String) + /** + * Clears the unread message count for a specific conversation, setting it to 0. + * + * @param conversationId The unique ID of the conversation. + */ @Query("UPDATE conversations SET unread_count = 0 WHERE id = :conversationId") suspend fun clearUnreadCount(conversationId: String) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/MessageDao.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/MessageDao.kt index 7afe14e0a..6dbe684e4 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/MessageDao.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/dao/MessageDao.kt @@ -7,34 +7,84 @@ import androidx.room.Query import com.greybox.projectmesh.messaging.data.entities.Message import kotlinx.coroutines.flow.Flow +/** + * Data Access Object for [Message] entities. + * + * Provides methods to query, insert, and delete messages in the Room database. + */ @Dao interface MessageDao { + + /** + * Retrieves all messages as a synchronous list. + * + * @return A [List] of all [Message] objects in the database. + */ @Query("SELECT * FROM message") fun getAll(): List + /** + * Returns a flow of all messages. + * + * @return [Flow] emitting a list of [Message] objects whenever the data changes. + */ @Query("SELECT * FROM message") fun getAllFlow(): Flow> + /** + * Returns a flow of messages for a specific chat, ordered by date received ascending. + * + * @param chat The chat identifier. + * @return [Flow] emitting a list of [Message] objects for the given chat. + */ @Query("SELECT * FROM message WHERE chat = :chat ORDER BY dateReceived ASC") fun getChatMessagesFlow(chat: String): Flow> + /** + * Deletes all messages from the database. + */ @Query("DELETE FROM message") fun clearTable() + /** + * Returns a flow of messages for multiple chat names, ordered by date received ascending. + * + * @param chatNames A list of chat identifiers. + * @return [Flow] emitting a list of [Message] objects for the given chats. + */ @Query("SELECT * FROM message WHERE chat IN (:chatNames) ORDER BY dateReceived ASC") fun getChatMessagesFlowMultipleNames(chatNames: List): Flow> - //Synchronously Query to get messages immediately + /** + * Synchronously retrieves messages for a specific chat, ordered by date received ascending. + * + * @param chat The chat identifier. + * @return A [List] of [Message] objects for the given chat. + */ @Query("SELECT * FROM message WHERE chat = :chat ORDER BY dateReceived ASC") fun getChatMessagesSync(chat: String): List + /** + * Inserts a new message into the database. + * + * @param m The [Message] to add. + */ @Insert suspend fun addMessage(m: Message) + /** + * Deletes a single message from the database. + * + * @param m The [Message] to delete. + */ @Delete fun delete(m: Message) + /** + * Deletes multiple messages from the database. + * + * @param messages The list of [Message] objects to delete. + */ @Delete suspend fun deleteAll(messages: List) - } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Conversation.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Conversation.kt index aca71676b..4d649ca63 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Conversation.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Conversation.kt @@ -5,8 +5,20 @@ import androidx.room.Entity import androidx.room.PrimaryKey import java.util.UUID -//Conversation Entity, representing a chat thread with another user - +/** + * Represents a conversation (chat thread) with another user. + * + * Each conversation tracks the other user's info, the last message, unread count, and online status. + * + * @property id The unique ID of the conversation, typically a composite of the two users' IDs. + * @property userUuid The UUID of the other user in the conversation. + * @property userName The display name of the other user. + * @property userAddress The IP address of the other user (nullable). + * @property lastMessage The text of the last message in the conversation (nullable). + * @property lastMessageTime Timestamp of when the last message was sent. + * @property unreadCount The number of unread messages in this conversation (default 0). + * @property isOnline Indicates whether the other user is currently online (default false). + */ @Entity(tableName = "conversations") data class Conversation( @PrimaryKey val id: String, //Composite id of the two users @@ -17,5 +29,4 @@ data class Conversation( @ColumnInfo(name = "last_message_time") val lastMessageTime: Long, //Timestamp of last message @ColumnInfo(name = "unread_count") val unreadCount: Int = 0, //count of unread messages @ColumnInfo(name = "is_online") val isOnline: Boolean = false //whether the user is online - -) \ No newline at end of file +) diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt index 25bcfbf44..45a452a3c 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/FileEncoder.kt @@ -53,13 +53,24 @@ import java.net.URL import java.net.URLConnection import java.net.URLDecoder -//Use this to encode files not just images -//Needs to be tested sometime -//Can I modify this so that Http transfer does the majority of the encoding? -class FileEncoder {//Made by Craig. Encodes via base64. +/** + * Utility class for encoding and decoding files using Base64. + * + * This class provides functions to encode files to Base64 strings, decode + * Base64 strings back to files, and send files over HTTP as encoded strings. + */ +class FileEncoder { //Made by Craig. Encodes via base64. + /** + * Encodes the file located at the given URI into a Base64 string. + * + * @param ctxt The application context used to access the content resolver. + * @param inputuri The URI of the file to encode. + * @return The Base64-encoded string of the file contents, or a message + * indicating encoding failure. + */ @OptIn(ExperimentalEncodingApi::class) - fun encodebase64(ctxt: Context, inputuri: Uri): String?{ + fun encodebase64(ctxt: Context, inputuri: Uri): String? { try { val encodedstrm: InputStream? = ctxt.contentResolver.openInputStream(inputuri) val bytes = encodedstrm?.readBytes() @@ -71,50 +82,67 @@ class FileEncoder {//Made by Craig. Encodes via base64. } } catch(e: Exception){ e.printStackTrace() - return "Cannot encode file"} - + return "Cannot encode file" + } } - @OptIn(ExperimentalEncodingApi::class)//Made by Craig - fun decodeBase64(inputbase64:String, output: File): File{//Decodes to a file. Uses base64 + /** + * Decodes a Base64-encoded string and writes it to the specified file. + * + * @param inputbase64 The Base64 string to decode. + * @param output The file to write the decoded bytes to. + * @return The file containing the decoded data. + */ + @OptIn(ExperimentalEncodingApi::class) //Made by Craig + fun decodeBase64(inputbase64:String, output: File): File { //Decodes to a file. Uses base64 val decodedfilebytes = Base64.decode(inputbase64) val decodedstrm = FileOutputStream(output) decodedstrm.write(decodedfilebytes) decodedstrm.close() return output } - fun sendImage(imageURI: Uri?, tgtaddress: InetAddress, tgtport:Int, appctxt: Context): Boolean{//Testing sending images - try{//we can utilize this if we opt not to use JSON + + /** + * Sends an image file to a target host and port using HTTP POST with Base64 encoding. + * + * @param imageURI The URI of the image to send. If null, the function returns false. + * @param tgtaddress The target host's InetAddress. + * @param tgtport The target port to send the image to. + * @param appctxt The application context used to access content resolver streams. + * @return True if the image was successfully sent, false otherwise. + */ + fun sendImage(imageURI: Uri?, tgtaddress: InetAddress, tgtport:Int, appctxt: Context): Boolean { //Testing sending images + try { //we can utilize this if we opt not to use JSON if(imageURI != null){ - val fp = encodebase64(appctxt, imageURI)//encodes file to base64 - if(!fp.equals("Cannot encode file")) { - val efp = URLEncoder.encode(fp, "UTF-8")//ensures that the file URI is utf-8 encoded - val connection = - URL("http://${tgtaddress.hostAddress}:${tgtport}/upload?file=$efp").openConnection() as HttpURLConnection - val request = "POST"//Specifies the request as a POST - connection.doOutput = true - connection.requestMethod = request - connection.setChunkedStreamingMode(0) - val instream = appctxt.contentResolver.openInputStream(imageURI) - val outstream = connection.outputStream - val readingbuffer = ByteArray(1024) - var finishedreading: Int - while (instream?.read(readingbuffer).also { finishedreading = it!! } != -1) { - outstream.write(readingbuffer, 0, finishedreading) - } - outstream.close() - instream?.close() + val fp = encodebase64(appctxt, imageURI) //encodes file to base64 + if(!fp.equals("Cannot encode file")) { + val efp = URLEncoder.encode(fp, "UTF-8") //ensures that the file URI is utf-8 encoded + val connection = + URL("http://${tgtaddress.hostAddress}:${tgtport}/upload?file=$efp").openConnection() as HttpURLConnection + val request = "POST" //Specifies the request as a POST + connection.doOutput = true + connection.requestMethod = request + connection.setChunkedStreamingMode(0) + val instream = appctxt.contentResolver.openInputStream(imageURI) + val outstream = connection.outputStream + val readingbuffer = ByteArray(1024) + var finishedreading: Int + while (instream?.read(readingbuffer).also { finishedreading = it!! } != -1) { + outstream.write(readingbuffer, 0, finishedreading) + } + outstream.close() + instream?.close() + } else { + return false + } } else { return false - }} else { - return false } - } - catch(e: Exception){ + } catch(e: Exception) { e.printStackTrace() return false } return true } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt index 69ce291f0..a14022939 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/JSONSchema.kt @@ -4,6 +4,11 @@ import android.util.Log import org.json.JSONObject import org.json.JSONException +/** + * Utility class to validate JSON strings against a predefined JSON schema. + * + * The schema enforces required fields and data types for messages. + */ class JSONSchema { private val schemaString = """ @@ -20,7 +25,13 @@ class JSONSchema { } } """ - //Takes JSON string and validates it against JSON Schema + + /** + * Validates a JSON string against the internal schema. + * + * @param json The JSON string representing a message. + * @return True if the JSON is valid according to the schema, false otherwise. + */ fun schemaValidation(json: String): Boolean { //Log.d("JSONSchema", "Validating JSON: $json") //Log.d("JSONSchema", "Against schema: $schemaString") @@ -30,13 +41,19 @@ class JSONSchema { validate(jsonObject, schemaJson) return true - }catch (e: JSONException) { + } catch (e: JSONException) { Log.e("JSONSchema", "JSON schema validation failed: ${e.message}") return false } } - //Validates JSON object against schema + /** + * Checks that the given JSON object contains all required fields as per the schema. + * + * @param json The JSON object to validate. + * @param schema The JSON schema object defining required fields. + * @throws JSONException If any required field is missing. + */ private fun validate(json: JSONObject, schema: JSONObject) { val requiredFields = schema.getJSONArray("required") for (i in 0 until requiredFields.length()) { @@ -46,4 +63,4 @@ class JSONSchema { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Message.kt b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Message.kt index f48afdd8f..f01f5f705 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Message.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/data/entities/Message.kt @@ -13,34 +13,80 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import java.net.URI -class URIConverter{ + +/** + * Room type converter to convert between [URI] and [String] for database storage. + */ +class URIConverter { + /** + * Converts a [URI] to a [String] for database storage. + * + * @param theuri The URI to convert. + * @return The string representation of the URI, or null if input is null. + */ @TypeConverter - fun convfromURI(theuri: URI?): String?{ + fun convfromURI(theuri: URI?): String? { return theuri?.toString() } + + /** + * Converts a [String] back to a [URI]. + * + * @param uristring The string to convert. + * @return The corresponding URI, or null if input is null. + */ @TypeConverter - fun convtoURI(uristring: String?): URI?{ - return uristring?.let{URI.create(it)} + fun convtoURI(uristring: String?): URI? { + return uristring?.let { URI.create(it) } } } -object URISerializable : KSerializer {//This makes the URI serializable, can be used in JSON + +/** + * Serializer to make [URI] serializable for Kotlinx serialization (e.g., JSON). + */ +object URISerializable : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("URI", PrimitiveKind.STRING) + + /** + * Serializes a [URI] into a string. + * + * @param enc The encoder. + * @param vals The URI to serialize. + */ override fun serialize(enc: Encoder, vals: URI) { enc.encodeString(vals.toString()) } + + /** + * Deserializes a string into a [URI]. + * + * @param dec The decoder. + * @return The deserialized URI. + */ override fun deserialize(dec: Decoder): URI { return URI.create(dec.decodeString()) } } + +/** + * Room entity representing a message in a chat. + * + * @property id Unique message ID (auto-generated). + * @property dateReceived Timestamp when the message was received. + * @property content The text content of the message. + * @property sender The identifier of the sender. + * @property chat The chat/conversation ID this message belongs to. + * @property file Optional file attached to the message, stored as a [URI]. + */ @Serializable @Entity(tableName = "message") @TypeConverters(URIConverter::class) -data class Message(// +data class Message( @PrimaryKey(autoGenerate = true) val id: Int, @ColumnInfo(name = "dateReceived") val dateReceived: Long, @ColumnInfo(name = "content") val content: String, @ColumnInfo(name = "sender") val sender: String, @ColumnInfo(name = "chat") val chat: String, - @ColumnInfo(name= "file") @Serializable(with=URISerializable::class) val file: URI? = null - //@ColumnInfo(name = "file") @Serializable(with=URISerializable::class) val file: List -) \ No newline at end of file + @ColumnInfo(name = "file") @Serializable(with = URISerializable::class) val file: URI? = null + // @ColumnInfo(name = "file") @Serializable(with=URISerializable::class) val file: List +) diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt index 1a15b2f4f..01239b0c8 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageNetworkHandler.kt @@ -36,6 +36,13 @@ import android.os.Parcel import android.os.Parcelable import java.net.URI +/** + * Handles sending and receiving chat messages over the network. + * + * @property httpClient The OkHttpClient used to make HTTP requests. + * @property localVirtualAddr The local device's virtual network IP address. + * @property di The Kodein DI container instance for retrieving dependencies. + */ class MessageNetworkHandler( private val httpClient: OkHttpClient, private val localVirtualAddr: InetAddress, @@ -45,7 +52,14 @@ class MessageNetworkHandler( private val conversationRepository: ConversationRepository by di.instance() private val settingsPrefs: SharedPreferences by di.instance(tag = "settings") - //function sendChatMessage(address: InetAddress, time: Long, message: String) { + /** + * Sends a chat message to a remote device over HTTP. + * + * @param address The target device's IP address. + * @param time The timestamp of the message in milliseconds. + * @param message The message text to send. + * @param file Optional URI of a file to send along with the message. + */ fun sendChatMessage(address: InetAddress, time: Long, message: String, file: URI?/* test this*/) { scope.launch { try { @@ -103,8 +117,17 @@ class MessageNetworkHandler( } } } + companion object { - //process incoming messages and route them to the correct conversation + /** + * Processes an incoming message and routes it to the correct conversation. + * + * @param chatMessage The message content received. + * @param time The timestamp when the message was received. + * @param senderIp The IP address of the sender. + * @param incomingfile Optional file URI attached to the message. + * @return The created [Message] object representing the incoming message. + */ fun handleIncomingMessage( chatMessage: String?, time: Long, @@ -176,8 +199,13 @@ class MessageNetworkHandler( return message } - - // New helper function to show notifications that route to chat screen + /** + * Shows a notification for an incoming message and routes to the chat screen. + * + * @param conversation The conversation to which the message belongs. + * @param message The message to display in the notification. + * @param senderIp The IP address of the sender. + */ private fun showMessageNotification( conversation: Conversation, message: Message, @@ -235,4 +263,4 @@ class MessageNetworkHandler( } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt index 87317a376..ade62a252 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/network/MessageService.kt @@ -15,6 +15,12 @@ import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.instance +/** + * Service layer for handling message operations including sending messages + * and updating conversations. + * + * @property di Kodein DI container for retrieving required dependencies. + */ class MessageService( override val di: DI ) : DIAware { @@ -24,6 +30,13 @@ class MessageService( private val userRepository: UserRepository by di.instance() private val settingsPrefs: SharedPreferences by di.instance(tag = "settings") + /** + * Sends a message to a given IP address. + * First saves the message locally, then sends it over the network. + * + * @param address The target device's IP address. + * @param message The [Message] object to be sent. + */ suspend fun sendMessage(address: InetAddress, message: Message) { //First save locally messageRepository.addMessage(message) @@ -37,6 +50,12 @@ class MessageService( ) } + /** + * Updates the conversation associated with a given user IP with a new message. + * + * @param address The IP address of the remote user. + * @param message The [Message] object to update in the conversation. + */ private suspend fun updateConversationWithMessage(address: InetAddress, message: Message){ try { //find user by ip address @@ -64,4 +83,4 @@ class MessageService( Log.e("MessageService", "Error updating conversation with message", e) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt b/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt index 81e8e79c5..450d264a2 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/repository/ConversationRepository.kt @@ -10,17 +10,33 @@ import kotlinx.coroutines.flow.Flow import org.kodein.di.DI import org.kodein.di.DIAware +/** + * Repository for managing conversations. + * Handles retrieval, creation, updating, and user status tracking for conversations. + * + * @property conversationDao DAO for database operations related to conversations. + * @property di Kodein DI container for dependency injection. + */ class ConversationRepository( private val conversationDao: ConversationDao, override val di: DI ) : DIAware { - //get all conversations as a flow + /** + * Returns a [Flow] emitting the list of all conversations. + * + * @return A [Flow] of [List] of [Conversation]. + */ fun getAllConversations(): Flow>{ return conversationDao.getAllConversationsFlow() } - //get specific convo by id + /** + * Retrieves a conversation by its unique ID. + * + * @param conversationId The unique ID of the conversation. + * @return The [Conversation] if found, null otherwise. + */ suspend fun getConversationById(conversationId: String): Conversation? { Log.d("ConversationRepository", "Getting conversation by ID: $conversationId") val result = conversationDao.getConversationById(conversationId) @@ -28,6 +44,13 @@ class ConversationRepository( return result } + /** + * Retrieves an existing conversation or creates a new one if it does not exist. + * + * @param localUuid The UUID of the local user. + * @param remoteUser The remote user entity to associate with the conversation. + * @return The existing or newly created [Conversation]. + */ suspend fun getOrCreateConversation(localUuid: String, remoteUser: UserEntity): Conversation { //create a unique conversation ID using both UUIDs in order to ensure consistency val conversationId = ConversationUtils.createConversationId(localUuid, remoteUser.uuid) @@ -60,7 +83,13 @@ class ConversationRepository( return conversation } - //update conversation with the latest message + /** + * Updates the conversation with the latest message. + * Increments unread count if the message is from another user. + * + * @param conversationId The unique ID of the conversation. + * @param message The [Message] to update in the conversation. + */ suspend fun updateWithMessage(conversationId: String, message: Message) { conversationDao.updateLastMessage( @@ -82,12 +111,22 @@ class ConversationRepository( } - //mark conversation as read + /** + * Marks a conversation as read by clearing its unread count. + * + * @param conversationId The unique ID of the conversation. + */ suspend fun markAsRead(conversationId: String) { conversationDao.clearUnreadCount(conversationId) } - //update a user's online status + /** + * Updates a user's online status and associated address. + * + * @param userUuid The UUID of the user. + * @param isOnline True if the user is online, false otherwise. + * @param userAddress The current address of the user, if available. + */ suspend fun updateUserStatus(userUuid: String, isOnline: Boolean, userAddress: String?) { try { // Update in database diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/repository/MessageRepository.kt b/app/src/main/java/com/greybox/projectmesh/messaging/repository/MessageRepository.kt index adf8b069f..4ca0b759b 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/repository/MessageRepository.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/repository/MessageRepository.kt @@ -1,4 +1,3 @@ -// Path: app/src/main/java/com/greybox/projectmesh/messaging/repository/MessageRepository.kt package com.greybox.projectmesh.messaging.repository import com.greybox.projectmesh.messaging.data.dao.MessageDao @@ -8,27 +7,51 @@ import org.kodein.di.DI import org.kodein.di.DIAware // Changed to use Kodein instead of javax.inject + +/** + * Repository for managing messages in the app. + * Handles retrieval, insertion, and clearing of messages for chats. + * + * @property messageDao DAO for database operations related to messages. + * @property di Kodein DI container for dependency injection. + */ class MessageRepository( private val messageDao: MessageDao, override val di: DI ) : DIAware { - // Get all messages for a chat + + /** + * Retrieves all messages for a specific chat as a [Flow]. + * + * @param chatId The ID of the chat to retrieve messages for. + * @return A [Flow] emitting a [List] of [Message] objects for the chat. + */ fun getChatMessages(chatId: String): Flow> { return messageDao.getChatMessagesFlow(chatId) } - // Add a new message + /** + * Adds a new message to the database. + * + * @param message The [Message] to add. + */ suspend fun addMessage(message: Message) { messageDao.addMessage(message) } - // Get all messages + /** + * Retrieves all messages from the database as a [Flow]. + * + * @return A [Flow] emitting a [List] of all [Message] objects. + */ fun getAllMessages(): Flow> { return messageDao.getAllFlow() } - // Clear all messages + /** + * Clears all messages from the database. + */ suspend fun clearMessages() { messageDao.clearTable() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ChatScreenModel.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ChatScreenModel.kt index a0e5b60bf..67a7c358e 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ChatScreenModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ChatScreenModel.kt @@ -3,9 +3,17 @@ package com.greybox.projectmesh.messaging.ui.models import com.greybox.projectmesh.messaging.data.entities.Message import java.net.InetAddress +/** + * Data model representing the state of a chat screen in the UI. + * + * @property deviceName Optional name of the device or user. + * @property virtualAddress Virtual network address of the device; defaults to 192.168.0.1. + * @property allChatMessages List of all messages to display on the chat screen; defaults to empty list. + * @property offlineWarning Optional warning message to show if the device/user is offline. + */ data class ChatScreenModel( val deviceName: String? = null, val virtualAddress: InetAddress = InetAddress.getByName("192.168.0.1"), val allChatMessages: List = emptyList(), val offlineWarning: String? = null -) \ No newline at end of file +) diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ConversationsHomeScreenModel.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ConversationsHomeScreenModel.kt index 2c3fd30d5..227b2b69e 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ConversationsHomeScreenModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/models/ConversationsHomeScreenModel.kt @@ -2,8 +2,15 @@ package com.greybox.projectmesh.messaging.ui.models import com.greybox.projectmesh.messaging.data.entities.Conversation +/** + * Data model representing the state of the home screen showing all conversations. + * + * @property isLoading Indicates whether conversation data is currently being loaded. + * @property conversations List of conversations to display on the home screen; defaults to empty list. + * @property error Optional error message to display if loading or retrieving conversations fails. + */ data class ConversationsHomeScreenModel ( val isLoading: Boolean = false, val conversations: List = emptyList(), val error: String? = null -) \ No newline at end of file +) diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatNodeListScreen.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatNodeListScreen.kt index 90c1b3d03..78fdfea1b 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatNodeListScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatNodeListScreen.kt @@ -13,6 +13,13 @@ import org.kodein.di.compose.localDI import androidx.compose.ui.platform.LocalSavedStateRegistryOwner import com.greybox.projectmesh.viewModel.NetworkScreenModel +/** + * Composable that displays a list of network nodes as clickable items. + * + * @param onNodeSelected Lambda invoked when a node is selected; passes the node's IP address as a [String]. + * @param viewModel Optional [NetworkScreenViewModel] instance to provide network node data. + * Defaults to a ViewModel created with [ViewModelFactory] using the local DI context. + */ @Composable fun ChatNodeListScreen( onNodeSelected: (String) -> Unit, @@ -46,4 +53,4 @@ fun ChatNodeListScreen( ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt index 24f690dd4..7b0ad6d64 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ChatScreen.kt @@ -74,6 +74,15 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +/** + * Composable function representing the main chat screen. + * + * @param virtualAddress The IP address of the chat participant. + * @param userName Optional username for the chat participant. + * @param isOffline Boolean flag indicating if the user is offline. + * @param onClickButton Callback for button click events. + * @param viewModel The [ChatScreenViewModel] providing UI state and actions. + */ @Composable fun ChatScreen( virtualAddress: InetAddress, @@ -89,7 +98,6 @@ fun ChatScreen( }, defaultArgs = Bundle().apply { putSerializable("virtualAddress", virtualAddress) - } ) ) @@ -276,6 +284,13 @@ fun ChatScreen( } } +/** + * Composable function showing the user's status bar at the top of the chat. + * + * @param userName Name of the chat participant. + * @param isOnline Boolean flag indicating online/offline status. + * @param userAddress IP address of the chat participant. + */ @Composable fun UserStatusBar( userName: String, @@ -366,6 +381,12 @@ fun UserStatusBar( } } +/** + * Composable function displaying all messages in the chat. + * + * @param uiState [ChatScreenModel] representing the current state of the chat. + * @param onClickButton Callback for any button interactions within the messages list. + */ @Composable fun DisplayAllMessages(uiState: ChatScreenModel, onClickButton: () -> Unit) { val context = LocalContext.current @@ -441,6 +462,15 @@ fun DisplayAllMessages(uiState: ChatScreenModel, onClickButton: () -> Unit) { } } +/** + * Composable function displaying an individual message bubble. + * + * @param chatMessage The [Message] object containing message data. + * @param sentBySelf Boolean indicating whether the message was sent by the current user. + * @param messageContent Composable lambda for rendering the message content. + * @param sender Name of the sender of the message. + * @param modifier Modifier to apply to the message bubble. + */ @Composable fun MessageBubble( chatMessage: Message, @@ -510,4 +540,3 @@ fun MessageBubble( } } } - diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ConversationsHomeScreen.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ConversationsHomeScreen.kt index 371fe36f1..4af4200a9 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ConversationsHomeScreen.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/screens/ConversationsHomeScreen.kt @@ -32,6 +32,12 @@ import com.greybox.projectmesh.messaging.ui.viewmodels.ConversationsHomeScreenVi import com.greybox.projectmesh.messaging.utils.MessageUtils import org.kodein.di.compose.localDI +/** + * Main Composable for the Conversations Home screen. + * + * @param onConversationSelected Callback when a conversation is selected. + * @param viewModel [ConversationsHomeScreenViewModel] providing the UI state. + */ @Composable fun ConversationsHomeScreen( onConversationSelected: (String) -> Unit, @@ -88,6 +94,12 @@ fun ConversationsHomeScreen( } } +/** + * Displays a scrollable list of conversations. + * + * @param conversations List of [Conversation] objects to display. + * @param onConversationClick Callback when a conversation item is clicked. + */ @Composable fun ConversationsList( conversations: List, @@ -110,6 +122,12 @@ fun ConversationsList( } } +/** + * Displays an individual conversation item with avatar, status, last message, and unread count. + * + * @param conversation The [Conversation] to display. + * @param onClick Callback for when the conversation item is clicked. + */ @Composable fun ConversationItem( conversation: Conversation, @@ -276,6 +294,9 @@ fun ConversationItem( } } +/** + * Displays a placeholder view when there are no conversations. + */ @Composable fun EmptyConversationsView() { Column( @@ -310,6 +331,12 @@ fun EmptyConversationsView() { } } +/** + * Displays an error view with retry button when conversation loading fails. + * + * @param errorMessage The error message to display. + * @param onRetry Callback triggered when retry button is pressed. + */ @Composable fun ErrorView( errorMessage: String, @@ -359,4 +386,4 @@ fun ErrorView( Text("Retry") } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt index 68448f703..0f52fe6f7 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ChatScreenViewModel.kt @@ -38,10 +38,19 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.withTimeoutOrNull import java.net.URI +/** + * ViewModel for the Chat Screen. + * + * Responsible for managing chat messages, device status, and conversation information. + * + * @param di Dependency Injection container to provide required services and repositories. + * @param savedStateHandle Handles saved state, including virtualAddress and conversationId. + */ class ChatScreenViewModel( di: DI, savedStateHandle: SavedStateHandle ) : ViewModel() { + private val virtualAddress: InetAddress = savedStateHandle.get("virtualAddress")!! // _uiState will be updated whenever there is a change in the UI state @@ -78,18 +87,15 @@ class ChatScreenViewModel( //Log.d("ChatDebug", "GOT CONVERSATION ID FROM SAVED STATE: $savedConversationId") private val conversationId = passedConversationId ?: - ConversationUtils.createConversationId(localUuid, userUuid) + ConversationUtils.createConversationId(localUuid, userUuid) private val chatName = savedConversationId ?: conversationId //Log.d("ChatDebug", "USING CHAT NAME: $chatName (saved: $savedConversationId, generated: $conversationId)") - - private val addressDotNotation = virtualAddress.requireAddressAsInt().addressToDotNotation() private val conversationRepository: ConversationRepository by di.instance() - private val _uiState = MutableStateFlow( ChatScreenModel( deviceName = deviceName, @@ -254,7 +260,13 @@ class ChatScreenViewModel( } } - + /** + * Sends a chat message to a virtual device. + * + * @param virtualAddress IP address of the target device. + * @param message Message content as String. + * @param file Optional file attachment as [URI]. + */ fun sendChatMessage( virtualAddress: InetAddress, message: String, @@ -341,7 +353,13 @@ class ChatScreenViewModel( } } - //handles outgoing file transfer to fix unresolved reference error crash + /** + * Adds an outgoing file transfer for a given device. + * + * @param fileUri [Uri] of the file to send. + * @param toAddress IP address of the target device. + * @return [OutgoingTransferInfo] containing details of the transfer. + */ fun addOutgoingTransfer(fileUri: Uri, toAddress: InetAddress): OutgoingTransferInfo { return appServer.addOutgoingTransfer(fileUri, toAddress) } diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt index 9fd2933e1..7fcf248c9 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/ui/viewmodels/ConversationsHomeScreenViewModel.kt @@ -17,6 +17,14 @@ import kotlinx.coroutines.launch import org.kodein.di.DI import org.kodein.di.instance +/** + * ViewModel for the Conversations Home Screen. + * + * Manages the list of conversations, updates device online/offline statuses, + * and provides functions for refreshing and marking conversations as read. + * + * @param di Dependency Injection container to provide required repositories and settings. + */ class ConversationsHomeScreenViewModel( di: DI ) : ViewModel() { @@ -88,7 +96,6 @@ class ConversationsHomeScreenViewModel( } } - private fun loadConversations() { viewModelScope.launch { try { @@ -139,12 +146,20 @@ class ConversationsHomeScreenViewModel( } } - //function to refresh conversations manually + /** + * Refreshes the conversations list manually. + * + * Reloads the conversations from the repository. + */ fun refreshConversations(){ loadConversations() } - //Function to mark a conversation as read + /** + * Marks a conversation as read. + * + * @param conversationId The ID of the conversation to mark as read. + */ fun markConversationAsRead(conversationId: String) { viewModelScope.launch { try { @@ -154,4 +169,4 @@ class ConversationsHomeScreenViewModel( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt b/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt index 9000bc113..a308b8c26 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/utils/Logger.kt @@ -6,29 +6,53 @@ import android.util.Log * Centralized logging utility for the app. * Provides consistent logging with standardized tags and can be disabled in production. */ - object Logger { private const val LOGGING_ENABLED = true private const val TAG_PREFIX = "MeshChat_" + /** + * Logs a debug-level message. + * + * @param tag The log tag used to identify the source. + * @param message The message to log. + */ fun d(tag: String, message: String) { if (LOGGING_ENABLED) { Log.d("$TAG_PREFIX$tag", message) } } + /** + * Logs an info-level message. + * + * @param tag The log tag used to identify the source. + * @param message The message to log. + */ fun i(tag: String, message: String) { if (LOGGING_ENABLED) { Log.i("$TAG_PREFIX$tag", message) } } + /** + * Logs a warning-level message. + * + * @param tag The log tag used to identify the source. + * @param message The message to log. + */ fun w(tag: String, message: String) { if (LOGGING_ENABLED) { Log.w("$TAG_PREFIX$tag", message) } } + /** + * Logs an error-level message. + * + * @param tag The log tag used to identify the source. + * @param message The message to log. + * @param throwable Optional exception to include in the log output. + */ fun e(tag: String, message: String, throwable: Throwable? = null) { if (LOGGING_ENABLED) { if (throwable != null) { @@ -39,7 +63,13 @@ object Logger { } } - // Log important events that should be visible even in production + /** + * Logs high-importance errors that should always appear even in production. + * + * @param tag The log tag used to identify the source. + * @param message The message to log. + * @param throwable Optional exception to include in the log output. + */ fun critical(tag: String, message: String, throwable: Throwable? = null) { if (throwable != null) { Log.e("$TAG_PREFIX${tag}_CRITICAL", message, throwable) @@ -47,4 +77,4 @@ object Logger { Log.e("$TAG_PREFIX${tag}_CRITICAL", message) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt b/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt index a2bb342f4..f6fba57eb 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageMigrationUtils.kt @@ -12,15 +12,35 @@ import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.instance +/** + * Utility responsible for migrating legacy messages to the newer + * conversation-ID–based chat naming format. + * + * It inspects all existing messages, infers correct conversation IDs, + * and rewrites their `chat` field when necessary. + * + * This allows older installations to transition cleanly to the + * standardized conversation model. + */ class MessageMigrationUtils( override val di: DI ): DIAware { + private val db: MeshDatabase by di.instance() /** - * Migrates existing messages to use in converstion IDs as chatNames + * Migrates all historical messages so that each message's `chat` + * value follows the modern conversation ID format. + * + * Steps performed: + * - Loads all messages + * - Groups them by legacy chat name + * - Determines correct UUID association for each chat group + * - Generates a conversation ID using local + remote UUIDs + * - Rewrites messages with updated chat names + * + * Errors are logged but do not stop the migration process. */ - suspend fun migrateMessagesToChatIds() { withContext(Dispatchers.IO){ try { @@ -93,6 +113,16 @@ class MessageMigrationUtils( } } + /** + * Generates a consistent conversation ID using two UUIDs. + * + * Special cases: + * - Test device UUIDs map to fixed, readable conversation IDs. + * + * @param uuid1 Local UUID. + * @param uuid2 Remote UUID. + * @return A stable, sorted, hyphen-joined conversation ID. + */ private fun createConversationId(uuid1: String, uuid2: String): String { // Special cases for test devices if (uuid2 == "test-device-uuid") { @@ -103,4 +133,4 @@ class MessageMigrationUtils( } return listOf(uuid1, uuid2).sorted().joinToString("-") } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageUtils.kt b/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageUtils.kt index af2d20275..e9db5b825 100644 --- a/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageUtils.kt +++ b/app/src/main/java/com/greybox/projectmesh/messaging/utils/MessageUtils.kt @@ -1,13 +1,34 @@ package com.greybox.projectmesh.messaging.utils +/** + * Utility functions for formatting message metadata and generating stable + * chat identifiers used throughout the messaging system. + */ object MessageUtils { + + /** + * Formats a Unix timestamp into a human-readable time string. + * + * @param timestamp The timestamp in milliseconds. + * @return A formatted time string in `"HH:mm"` format. + */ fun formatTimestamp(timestamp: Long): String { //Adding timestamp formatting logic return java.text.SimpleDateFormat("HH:mm").format(timestamp) } + /** + * Generates a stable, deterministic chat ID from two user identifiers. + * + * The two identifiers are sorted alphabetically so both users + * will always compute the same ID for the same pair. + * + * @param sender The identifier of the sender. + * @param receiver The identifier of the receiver. + * @return A hyphen-joined chat ID such as `"userA-userB"`. + */ fun generateChatId(sender: String, receiver: String): String { //Create a consistent chat ID for two users return listOf(sender, receiver).sorted().joinToString("-") } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt b/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt index 959ed9433..a5ba77f43 100644 --- a/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt +++ b/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavHost.kt @@ -13,6 +13,13 @@ import androidx.compose.ui.unit.sp import androidx.navigation.compose.rememberNavController import com.greybox.projectmesh.R +/** + * Represents a single item inside the bottom navigation bar. + * + * @param route The navigation route associated with this item. + * @param label The text label shown beneath the icon. + * @param icon The vector icon displayed for this item. + */ data class NavigationItem( val route: String, val label: String, @@ -20,6 +27,9 @@ data class NavigationItem( ) //Preview is to show the bottom navigation bar in the preview and notice what it looks like +/** + * Preview for displaying the bottom navigation bar inside the design tools. + */ @Preview(showBackground = true) @Composable fun BottomNavigationBarPreview() { @@ -27,6 +37,14 @@ fun BottomNavigationBarPreview() { BottomNavigationBar(navController = navController) } +/** + * Displays the application's bottom navigation bar. + * + * Automatically highlights the currently selected destination and handles + * navigation state restoration and avoiding duplicate destinations. + * + * @param navController The controller used to perform navigation actions. + */ @Composable fun BottomNavigationBar(navController: NavHostController) { val items = listOf( @@ -66,8 +84,8 @@ fun BottomNavigationBar(navController: NavHostController) { launchSingleTop = true } }, - + ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavItem.kt b/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavItem.kt index 83e8b9351..437cd8fff 100644 --- a/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavItem.kt +++ b/app/src/main/java/com/greybox/projectmesh/navigation/BottomNavItem.kt @@ -5,13 +5,31 @@ import androidx.compose.material.icons.automirrored.filled.Send import androidx.compose.material.icons.filled.* import androidx.compose.ui.graphics.vector.ImageVector - -sealed class BottomNavItem(val route: String, val title: String, val icon: ImageVector){ +/** + * Represents a single item in the bottom navigation bar. + * + * Each item has a route (for navigation), a title (displayed as text), + * and an icon (displayed visually in the bar). + */ +sealed class BottomNavItem(val route: String, val title: String, val icon: ImageVector) { + /** Home tab item */ data object Home : BottomNavItem("home", "Home", Icons.Default.Home) + + /** Network tab item */ data object Network : BottomNavItem("network", "Network", Icons.Default.Wifi) + + /** Send tab item */ data object Send : BottomNavItem("send", "Send", Icons.AutoMirrored.Filled.Send) + + /** Receive tab item */ data object Receive : BottomNavItem("receive", "Receive", Icons.Default.Download) + + /** Log tab item */ data object Log: BottomNavItem("log", "Log", Icons.Default.History) + + /** Settings tab item */ data object Settings : BottomNavItem("settings", "Settings", Icons.Default.Settings) + + /** Chat tab item */ data object Chat : BottomNavItem("chat", "Chat", Icons.Default.ChatBubble) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt index d78b4a0c1..8a80ce495 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceEntry.kt @@ -9,14 +9,25 @@ import java.net.DatagramSocket import java.net.InetAddress import java.util.concurrent.Executors +/** + * Utility class to create test device entries for the mesh network. + * + * This simulates a device with a virtual node, logger, and mock network socket, + * allowing for testing without real devices. + */ class TestDeviceEntry { companion object { - // Create a test logger + /** Test logger used for capturing logs during testing */ private val testLogger = TestMNetLogger() + /** + * Creates a simulated test device entry. + * + * @return a pair containing the device's integer address and its LastOriginatorMessage + */ fun createTestEntry(): Pair { try { - //convert string IP to bytes + // Convert the string IP to a byte array val testAddressBytes = TestDeviceService.TEST_DEVICE_IP .split(".") .map { it.toInt().toByte() } @@ -24,7 +35,7 @@ class TestDeviceEntry { val testAddress = InetAddress.getByAddress(testAddressBytes) - // Convert IP address to Int manually + // Convert IP address bytes to an Int manually val testAddressInt = testAddressBytes.foldIndexed(0) { index, acc, byte -> acc or ((byte.toInt() and 0xFF) shl (24 - (index * 8))) } @@ -32,8 +43,7 @@ class TestDeviceEntry { Log.d("TestDeviceEntry", "Creating test entry with IP: ${TestDeviceService.TEST_DEVICE_IP}") Log.d("TestDeviceEntry", "Test address as int: $testAddressInt") - - //create basic MmcpOriginatorMessage + // Create a basic MmcpOriginatorMessage val mockOriginatorMessage = MmcpOriginatorMessage( messageId = 1, pingTimeSum = 50.toShort(), @@ -41,10 +51,10 @@ class TestDeviceEntry { sentTime = System.currentTimeMillis() ) - //create a virtual router for testing + // Create a virtual router for testing val testRouter = TestVirtualRouter() - //create a mock VirtualNodeDatagramSocket with our test router + // Create a mock VirtualNodeDatagramSocket using our test router val mockSocket = VirtualNodeDatagramSocket( socket = DatagramSocket(), ioExecutorService = Executors.newSingleThreadExecutor(), @@ -53,7 +63,7 @@ class TestDeviceEntry { logger = testLogger ) - // Create LastOriginatorMessage with all required parameters + // Build the LastOriginatorMessage object val lastOriginatorMessage = VirtualNode.LastOriginatorMessage( originatorMessage = mockOriginatorMessage, timeReceived = System.currentTimeMillis(), @@ -71,4 +81,4 @@ class TestDeviceEntry { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt index 34754bebb..86579c6e1 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestDeviceService.kt @@ -7,23 +7,36 @@ import com.greybox.projectmesh.messaging.data.entities.Message import kotlinx.coroutines.runBlocking import java.net.InetAddress +/** + * Service for managing test devices in the mesh network. + * + * Handles initialization of both online and offline test devices, + * provides utility methods for identifying test devices, and + * generating echo responses for testing purposes. + */ class TestDeviceService { companion object { + /** IP and name for the online test device */ const val TEST_DEVICE_IP = "192.168.0.99" const val TEST_DEVICE_NAME = "Test Echo Device (Online)" + + /** IP and name for the offline test device */ const val TEST_DEVICE_IP_OFFLINE = "192.168.0.98" const val TEST_DEVICE_NAME_OFFLINE = "Test Echo Device (Offline)" private var isInitialized = false private var offlineDeviceInitialized = false + /** + * Initializes the online test device if it hasn't been set up already. + */ fun initialize() { try { if (!isInitialized) { runBlocking { val existingUser = userRepository.getUserByIp(TEST_DEVICE_IP) if (existingUser == null) { - // If there's no user with this IP, insert one with a "temp" UUID + // Insert a new user with a temporary UUID val pseudoUuid = "temp-$TEST_DEVICE_IP" userRepository.insertOrUpdateUser( uuid = pseudoUuid, @@ -31,8 +44,7 @@ class TestDeviceService { address = TEST_DEVICE_IP ) } else { - // If a user with this IP already exists, just update the name - // (keeping the same uuid and address) + // Update the name of an existing user with this IP userRepository.insertOrUpdateUser( uuid = existingUser.uuid, name = TEST_DEVICE_NAME, @@ -43,7 +55,7 @@ class TestDeviceService { isInitialized = true Log.d("TestDeviceService", "Test device initialized successfully with IP: $TEST_DEVICE_IP") - //initialize offline test device + // Initialize the offline test device initializeOfflineDevice() } } catch (e: Exception) { @@ -51,25 +63,28 @@ class TestDeviceService { } } + /** + * Initializes the offline test device if it hasn't been set up already. + */ fun initializeOfflineDevice() { try { if (!offlineDeviceInitialized) { runBlocking { val existingUser = userRepository.getUserByIp(TEST_DEVICE_IP_OFFLINE) if (existingUser == null) { - // Create a new offline test device + // Create a new offline test device with null address val pseudoUuid = "temp-offline-$TEST_DEVICE_IP_OFFLINE" userRepository.insertOrUpdateUser( uuid = pseudoUuid, name = TEST_DEVICE_NAME_OFFLINE, - address = null // NULL address means offline + address = null // null address indicates offline ) } else { - // Update existing offline device + // Update existing offline device to ensure it remains offline userRepository.insertOrUpdateUser( uuid = existingUser.uuid, name = TEST_DEVICE_NAME_OFFLINE, - address = null // Make sure it's offline + address = null ) } } @@ -81,23 +96,32 @@ class TestDeviceService { } } + /** Checks if the given address is the online test device */ fun isOnlineTestDevice(address: InetAddress): Boolean { return address.hostAddress == TEST_DEVICE_IP } + /** Checks if the given address is the offline test device */ fun isOfflineTestDevice(address: InetAddress): Boolean { return address.hostAddress == TEST_DEVICE_IP_OFFLINE } - + /** Returns the InetAddress of the online test device */ fun getTestDeviceAddress(): InetAddress { return InetAddress.getByName(TEST_DEVICE_IP) } + /** Checks if the given address matches the online test device */ fun isTestDevice(address: InetAddress): Boolean { return address.hostAddress == TEST_DEVICE_IP } + /** + * Generates an echo response message for testing purposes. + * + * @param originalMessage the original message to echo + * @return a new Message object containing the echo content + */ fun createEchoResponse(originalMessage: Message): Message { return Message( id = 0, @@ -108,4 +132,4 @@ class TestDeviceService { ) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt index 2aabc3bef..98a59c65a 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestMNetLogger.kt @@ -3,7 +3,21 @@ package com.greybox.projectmesh.testing import android.util.Log import com.ustadmobile.meshrabiya.log.MNetLogger +/** + * Logger implementation for test devices. + * + * Redirects all log messages to Android's Log system with a fixed "TestDevice" tag. + * Supports both direct message strings and lambda message providers. + */ class TestMNetLogger : MNetLogger() { + + /** + * Logs a message and optional exception with a given priority. + * + * @param priority the log priority (Log.VERBOSE, Log.DEBUG, etc.) + * @param message the message to log + * @param exception optional exception to log + */ override fun invoke(priority: Int, message: String, exception: Exception?) { Log.println(priority, "TestDevice", message) exception?.let { @@ -11,10 +25,17 @@ class TestMNetLogger : MNetLogger() { } } + /** + * Logs a lazily evaluated message and optional exception with a given priority. + * + * @param priority the log priority (Log.VERBOSE, Log.DEBUG, etc.) + * @param message lambda returning the message to log + * @param exception optional exception to log + */ override fun invoke(priority: Int, message: () -> String, exception: Exception?) { Log.println(priority, "TestDevice", message()) exception?.let { Log.println(priority, "TestDevice", it.toString()) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/testing/TestVirtualRouter.kt b/app/src/main/java/com/greybox/projectmesh/testing/TestVirtualRouter.kt index 3b843098f..19c2ef7cf 100644 --- a/app/src/main/java/com/greybox/projectmesh/testing/TestVirtualRouter.kt +++ b/app/src/main/java/com/greybox/projectmesh/testing/TestVirtualRouter.kt @@ -7,32 +7,70 @@ import com.ustadmobile.meshrabiya.vnet.socket.ChainSocketNextHop import java.net.DatagramPacket import java.net.InetAddress +/** + * Test implementation of a VirtualRouter for use with test devices. + * + * Provides dummy routing behavior and predictable network parameters for testing purposes. + */ class TestVirtualRouter : VirtualRouter { + + /** Fixed test device address */ override val address: InetAddress = InetAddress.getByName(TestDeviceService.TEST_DEVICE_IP) + + /** Fixed port for local datagram operations */ override val localDatagramPort: Int = 4242 + + /** Fixed network prefix length for testing */ override val networkPrefixLength: Int = 16 - override fun route(packet: VirtualPacket, datagramPacket: DatagramPacket?, virtualNodeDatagramSocket: com.ustadmobile.meshrabiya.vnet.VirtualNodeDatagramSocket?) { + /** + * Route a packet. + * + * No-op implementation for test purposes. + */ + override fun route( + packet: VirtualPacket, + datagramPacket: DatagramPacket?, + virtualNodeDatagramSocket: com.ustadmobile.meshrabiya.vnet.VirtualNodeDatagramSocket? + ) { // no-op for test implementation } - override fun allocateUdpPortOrThrow(virtualDatagramSocketImpl: com.ustadmobile.meshrabiya.vnet.datagram.VirtualDatagramSocketImpl, portNum: Int): Int { + /** + * Allocate a UDP port or throw exception if unavailable. + * + * Always returns the requested port number in the test implementation. + */ + override fun allocateUdpPortOrThrow( + virtualDatagramSocketImpl: com.ustadmobile.meshrabiya.vnet.datagram.VirtualDatagramSocketImpl, + portNum: Int + ): Int { return portNum } + /** + * Deallocate a port. + * + * No-op implementation for test purposes. + */ override fun deallocatePort(protocol: Protocol, portNum: Int) { // no-op for test implementation } + /** + * Look up the next hop for a chain socket. + * + * Returns a dummy next hop with isFinalDest = true for testing. + */ override fun lookupNextHopForChainSocket(address: InetAddress, port: Int): ChainSocketNextHop { - //Return dummy next hop for testing with all required parameters return ChainSocketNextHop( address = address, port = port, isFinalDest = true, - network = null //For testing purposes, we can pass null for the network + network = null // network is null for test purposes ) } + /** Returns a constant MMCP message ID for testing */ override fun nextMmcpMessageId(): Int = 1 -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/ui/theme/CustomButton.kt b/app/src/main/java/com/greybox/projectmesh/ui/theme/CustomButton.kt index f08814f39..6a7c05451 100644 --- a/app/src/main/java/com/greybox/projectmesh/ui/theme/CustomButton.kt +++ b/app/src/main/java/com/greybox/projectmesh/ui/theme/CustomButton.kt @@ -27,7 +27,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.delay -// This is a pre-defined button with white background and black text +/** + * A transparent button with white background and black text. + * Optional rounded corners and full-width by default. + */ @Composable fun TransparentButton( onClick: () -> Unit, @@ -38,35 +41,46 @@ fun TransparentButton( Button( onClick = onClick, colors = ButtonDefaults.buttonColors( - containerColor = Color.White, // Background color - contentColor = Color.Black // Text color + containerColor = Color.White, // White background + contentColor = Color.Black // Black text ), border = BorderStroke(1.dp, Color.Black), // Black border - shape = RoundedCornerShape(8.dp), // Optional: Rounded corners - modifier = modifier.fillMaxWidth(), + shape = RoundedCornerShape(8.dp), // Rounded corners + modifier = modifier.fillMaxWidth(), // Fill max width by default enabled = enabled ) { Text(text = text) } } +/** + * A gradient button with press animation. + * + * @param text The label for the button. + * @param gradientColors Colors to use for horizontal gradient background. + * @param textColor Color of the text. + * @param maxWidth Maximum width of the button. + * @param onClick Action to perform when the button is clicked. + */ @Composable fun GradientButton( text: String, modifier: Modifier = Modifier, - gradientColors: List = listOf(Color(0xFF4CAF50), Color(0xFF81C784)), // Default gradient colors + gradientColors: List = listOf(Color(0xFF4CAF50), Color(0xFF81C784)), // Default gradient textColor: Color = Color.White, maxWidth: Dp = 120.dp, onClick: () -> Unit ) { var isPressed by remember { mutableStateOf(false) } - val scale by animateFloatAsState(if (isPressed) 0.85f else 1f) // Scale down when pressed + val scale by animateFloatAsState(if (isPressed) 0.85f else 1f) // Scale down on press + LaunchedEffect(isPressed) { if (isPressed) { - delay(100) // Wait for 100 ms + delay(100) // Short delay to show pressed effect isPressed = false } } + Box( modifier = modifier .scale(scale) @@ -75,14 +89,14 @@ fun GradientButton( brush = Brush.horizontalGradient(gradientColors), shape = RoundedCornerShape(12.dp) ) - .height(50.dp) // Height of the button - .widthIn(min = 120.dp, max = maxWidth) // Width of the button + .height(50.dp) + .widthIn(min = 120.dp, max = maxWidth) .padding(horizontal = 16.dp) .clickable { isPressed = true onClick() }, - contentAlignment = Alignment.Center // Center content in the box + contentAlignment = Alignment.Center // Center the text ) { Text( text = text, @@ -90,42 +104,49 @@ fun GradientButton( fontSize = 14.sp, fontWeight = FontWeight.Bold, maxLines = 1, - overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, // Truncate text if it overflows + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis ) } } +/** + * A full-width gradient button with press animation. + * + * Similar to GradientButton but fills the available width. + */ @Composable fun GradientLongButton( text: String, modifier: Modifier = Modifier, - gradientColors: List = listOf(Color(0xFF4CAF50), Color(0xFF81C784)), // Default gradient colors + gradientColors: List = listOf(Color(0xFF4CAF50), Color(0xFF81C784)), // Default gradient textColor: Color = Color.White, onClick: () -> Unit ) { var isPressed by remember { mutableStateOf(false) } - val scale by animateFloatAsState(if (isPressed) 0.85f else 1f) // Scale down when pressed + val scale by animateFloatAsState(if (isPressed) 0.85f else 1f) // Scale down on press + LaunchedEffect(isPressed) { if (isPressed) { - delay(100) // Wait for 100 ms + delay(100) isPressed = false } } + Box( modifier = modifier - .fillMaxWidth() + .fillMaxWidth() // Fill the full width .scale(scale) - .shadow(8.dp, RoundedCornerShape(12.dp)) // Shadow effect + .shadow(8.dp, RoundedCornerShape(12.dp)) .background( brush = Brush.horizontalGradient(gradientColors), shape = RoundedCornerShape(12.dp) ) - .height(50.dp) // Height of the button + .height(50.dp) .clickable { isPressed = true onClick() }, - contentAlignment = Alignment.Center // Center content in the box + contentAlignment = Alignment.Center ) { Text( text = text, @@ -133,7 +154,7 @@ fun GradientLongButton( fontSize = 14.sp, fontWeight = FontWeight.Bold, maxLines = 1, - overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, // Truncate text if it overflows + overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis ) } } diff --git a/app/src/main/java/com/greybox/projectmesh/ui/theme/Theme.kt b/app/src/main/java/com/greybox/projectmesh/ui/theme/Theme.kt index 3232e0b31..680c8a13a 100644 --- a/app/src/main/java/com/greybox/projectmesh/ui/theme/Theme.kt +++ b/app/src/main/java/com/greybox/projectmesh/ui/theme/Theme.kt @@ -5,7 +5,9 @@ import androidx.compose.runtime.Composable import androidx.compose.material3.* import androidx.compose.ui.graphics.Color -// Define the color schemes for light and dark themes +/** + * Dark color scheme for the app. + */ private val DarkColorScheme = darkColorScheme( primary = Color(0xFFBB86FC), secondary = Color(0xFF03DAC5), @@ -17,6 +19,9 @@ private val DarkColorScheme = darkColorScheme( onSurface = Color.White ) +/** + * Light color scheme for the app. + */ private val LightColorScheme = lightColorScheme( primary = Color(0xFF6200EE), secondary = Color(0xFF03DAC5), @@ -28,23 +33,33 @@ private val LightColorScheme = lightColorScheme( onSurface = Color.Black ) +/** + * Enum to represent the app's theme choice. + */ enum class AppTheme { SYSTEM, LIGHT, DARK } +/** + * Apply ProjectMesh theme with the chosen AppTheme. + * + * @param appTheme The selected theme (System, Light, Dark) + * @param content The composable content to wrap with this theme + */ @Composable fun ProjectMeshTheme( appTheme: AppTheme, content: @Composable () -> Unit ) { val darkTheme = when (appTheme) { - AppTheme.SYSTEM -> isSystemInDarkTheme() + AppTheme.SYSTEM -> isSystemInDarkTheme() // Follow system setting AppTheme.LIGHT -> false AppTheme.DARK -> true } + MaterialTheme( colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme, - content = content, - typography = Typography, + typography = Typography, // Apply predefined typography + content = content ) } diff --git a/app/src/main/java/com/greybox/projectmesh/user/UserDao.kt b/app/src/main/java/com/greybox/projectmesh/user/UserDao.kt index 82f1c8de3..e2e94383e 100644 --- a/app/src/main/java/com/greybox/projectmesh/user/UserDao.kt +++ b/app/src/main/java/com/greybox/projectmesh/user/UserDao.kt @@ -6,25 +6,69 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Update +/** + * Data Access Object for user-related database operations. + */ @Dao interface UserDao { + + /** + * Get a user by their UUID. + * + * @param uuid The UUID of the user + * @return The matching UserEntity or null if not found + */ @Query("SELECT * FROM users WHERE uuid = :uuid LIMIT 1") suspend fun getUserByUuid(uuid: String): UserEntity? + /** + * Insert a new user into the database. + * Replaces existing entry if there is a conflict. + * + * @param user The user entity to insert + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUser(user: UserEntity) + /** + * Update an existing user in the database. + * + * @param user The user entity to update + */ @Update suspend fun updateUser(user: UserEntity) + /** + * Check if a user with a given UUID exists. + * + * @param uuid The UUID to check + * @return True if a user exists, false otherwise + */ @Query("SELECT EXISTS(SELECT 1 FROM users WHERE uuid = :uuid)") suspend fun hasWithID(uuid: String): Boolean + /** + * Get a user by their IP address. + * + * @param ip The IP address of the user + * @return The matching UserEntity or null if not found + */ @Query("SELECT * FROM users WHERE address = :ip LIMIT 1") suspend fun getUserByIp(ip: String): UserEntity? + + /** + * Get all users with a non-null address (i.e., currently connected users). + * + * @return List of connected users + */ @Query("SELECT * FROM users WHERE address IS NOT NULL") suspend fun getAllConnectedUsers(): List + /** + * Get all users in the database. + * + * @return List of all users + */ @Query("SELECT * FROM users") suspend fun getAllUsers(): List -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/user/UserEntity.kt b/app/src/main/java/com/greybox/projectmesh/user/UserEntity.kt index 9b4e27dd8..b1455eecb 100644 --- a/app/src/main/java/com/greybox/projectmesh/user/UserEntity.kt +++ b/app/src/main/java/com/greybox/projectmesh/user/UserEntity.kt @@ -3,11 +3,20 @@ package com.greybox.projectmesh.user import androidx.room.Entity import androidx.room.PrimaryKey import kotlinx.serialization.Serializable + +/** + * Represents a user in the system. + * + * @property uuid Unique identifier for the user + * @property name Display name of the user + * @property address Optional network address (null if offline) + * @property lastSeen Optional timestamp of the last time the user was seen + */ @Serializable @Entity(tableName = "users") data class UserEntity( @PrimaryKey val uuid: String, val name: String, - val address: String? = null, // Default value provided - val lastSeen: Long? = null -) \ No newline at end of file + val address: String? = null, // Optional: null if offline + val lastSeen: Long? = null // Optional: timestamp in milliseconds +) diff --git a/app/src/main/java/com/greybox/projectmesh/user/UserRepository.kt b/app/src/main/java/com/greybox/projectmesh/user/UserRepository.kt index 2b11edd51..4a19fb3d6 100644 --- a/app/src/main/java/com/greybox/projectmesh/user/UserRepository.kt +++ b/app/src/main/java/com/greybox/projectmesh/user/UserRepository.kt @@ -2,8 +2,20 @@ package com.greybox.projectmesh.user import android.util.Log +/** + * Repository layer that wraps UserDao calls and applies simple business logic. + * + * @property userDao Data access object for interacting with the UserEntity table + */ class UserRepository(private val userDao: UserDao) { + /** + * Inserts a new user or updates an existing one. + * + * @param uuid Unique identifier for the user + * @param name Display name of the user + * @param address Optional network address + */ suspend fun insertOrUpdateUser(uuid: String, name: String, address: String?) { val existing = userDao.getUserByUuid(uuid) if (existing == null) { @@ -26,23 +38,44 @@ class UserRepository(private val userDao: UserDao) { } } + /** + * Retrieves a user by their IP address. + * + * @param ip IP address to search by + * @return Matching UserEntity or null + */ suspend fun getUserByIp(ip: String): UserEntity? { return userDao.getUserByIp(ip) } + + /** + * Retrieves a user by their UUID. + */ suspend fun getUser(uuid: String): UserEntity? { return userDao.getUserByUuid(uuid) } + + /** + * Retrieves all users that have a non-null address, + * meaning users currently considered connected. + */ suspend fun getAllConnectedUsers(): List { return userDao.getAllConnectedUsers() } + /** + * Retrieves all users stored in the database. + */ suspend fun getAllUsers(): List { return userDao.getAllUsers() } + /** + * Checks if a user exists by UUID. + */ suspend fun hasUser(uuid: String): Boolean { return userDao.hasWithID(uuid) } // Add more methods as needed -} \ No newline at end of file +} diff --git a/app/src/main/java/com/greybox/projectmesh/util/NotificationHelper.kt b/app/src/main/java/com/greybox/projectmesh/util/NotificationHelper.kt index ca9b8d53d..9158975da 100644 --- a/app/src/main/java/com/greybox/projectmesh/util/NotificationHelper.kt +++ b/app/src/main/java/com/greybox/projectmesh/util/NotificationHelper.kt @@ -11,10 +11,20 @@ import com.greybox.projectmesh.MainActivity import com.greybox.projectmesh.R import com.greybox.projectmesh.navigation.BottomNavItem +/** + * Utility object for creating and showing system notifications related to file receiving. + */ object NotificationHelper { + + // Notification channel identifiers private const val CHANNEL_ID = "file_receive_channel" private const val CHANNEL_NAME = "File Receive Notifications" + /** + * Creates the notification channel if the device runs Android O or higher. + * + * @param context Application context used to access the system NotificationManager + */ fun createNotificationChannel(context: Context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( @@ -29,18 +39,32 @@ object NotificationHelper { } } + /** + * Displays a notification that informs the user a file has been received. + * Tapping the notification opens the Receive screen inside MainActivity. + * + * @param context Context for creating intents and notifications + * @param fileName Name of the received file to display in the message + */ fun showFileReceivedNotification(context: Context, fileName: String) { + + // Intent that navigates user into MainActivity → Receive screen val intent = Intent(context, MainActivity::class.java).apply { action = "OPEN_RECEIVE_SCREEN" putExtra("navigateTo", BottomNavItem.Receive.route) // Set target screen - putExtra("from_notification", true) // Tell MainActivity to skip permission requests + putExtra("from_notification", true) // Skip permission prompt when opened from notif flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } + // PendingIntent for launching the activity from the notification val pendingIntent = PendingIntent.getActivity( - context, 1003, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + context, + 1003, + intent, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) + // Construct the actual notification UI val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_launcher_foreground) .setContentTitle("File Received") @@ -51,7 +75,9 @@ object NotificationHelper { .setAutoCancel(true) .build() - val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + // Trigger the notification + val notificationManager = + context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(1003, notification) } } diff --git a/index.html b/index.html new file mode 100644 index 000000000..0b139f2b5 --- /dev/null +++ b/index.html @@ -0,0 +1,477 @@ + + + + + app + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ +
+

app

+
+

Packages

+
+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+ + From b4c5a56ff1a1ae233eaddfca4a1c931138d867ed Mon Sep 17 00:00:00 2001 From: Thalia Wood Date: Mon, 23 Feb 2026 17:02:19 -0700 Subject: [PATCH 22/87] Dokka Workflow Initial Integration --- .github/workflows/build_documentation.yml | 77 +++ .github/workflows/format_and_lint.yml | 2 +- .github/workflows/reporting_orchestrator.yml | 12 +- app/build.gradle.kts | 1 + build.gradle.kts | 1 + build_reports/index.html | 3 + index.html | 477 ------------------- 7 files changed, 94 insertions(+), 479 deletions(-) create mode 100644 .github/workflows/build_documentation.yml delete mode 100644 index.html diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml new file mode 100644 index 000000000..1169dadd0 --- /dev/null +++ b/.github/workflows/build_documentation.yml @@ -0,0 +1,77 @@ +name: Build Dokka Docs +run-name: ${{ github.event.inputs.custom_run_name || github.workflow }} + +on: + # run when called by another workflow + workflow_call: + inputs: + skip_commit: + description: "Skip committing reports" + required: false + type: boolean + default: false + + # run on demand + workflow_dispatch: + inputs: + custom-run-name: + description: "Custom name for this Actions run" + required: false + type: string + +# cancel any previously-started, yet still active runs of this workflow on the same branch +concurrency: + group: ${{ github.ref }}-Build Dokka Docs + cancel-in-progress: true + +permissions: read-all + +# adapted from https://mskelton.medium.com/auto-formatting-code-using-prettier-and-github-actions-ed458f58b7df +jobs: + call-dokka: + name: Call Dokka Html + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: "17" + + - name: Set Up Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Call Dokka Task + run: ./gradlew dokkaGenerate + continue-on-error: true + + - name: Configure Git Identity (if called individually) + if: ${{ inputs.skip_commit != true }} + run: | + git config user.email "github-actions[bot]@users.noreply.github.com" + git config user.name "github-actions[bot]" + + - name: Copy Documentation to Viewing Directory (if called individually) + if: ${{ inputs.skip_commit != true }} + run: | + mkdir ./build_reports/documentation + cp ./app/build/reports/lint-results-debug.html ./build_reports/lint_report_debug/lint_report_debug.html + cp -r ./app/build/reports/dokka/html/ ./build_reports/documentation + git add --force ./build_reports/ + git commit -m "Relocate documentation for viewing" + git push + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload Documentation Artifact (if called by orchestrator) + if: ${{ inputs.skip_commit == true }} + uses: actions/upload-artifact@v5 + with: + name: dokka_documentation_artifact + path: ./app/build/reports/dokka/html + retention-days: 1 \ No newline at end of file diff --git a/.github/workflows/format_and_lint.yml b/.github/workflows/format_and_lint.yml index fbc7b6e5b..b09f9cbb5 100644 --- a/.github/workflows/format_and_lint.yml +++ b/.github/workflows/format_and_lint.yml @@ -77,7 +77,7 @@ jobs: mkdir ./build_reports/lint_results_release cp ./app/build/reports/lint-results-release.html ./build_reports/lint_report_release/lint_report_release.html git add --force ./build_reports/ - git commit -m "Relocate coverage report for viewing" + git commit -m "Relocate linting report for viewing" git push env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reporting_orchestrator.yml b/.github/workflows/reporting_orchestrator.yml index 4232fee55..e1395943d 100644 --- a/.github/workflows/reporting_orchestrator.yml +++ b/.github/workflows/reporting_orchestrator.yml @@ -35,6 +35,12 @@ jobs: with: skip_commit: true + build-documentation: + name: Build Dokka Documentation + uses: ./.github/workflows/build_documentation.yml + with: + skip_commit: true + upload-apks: name: Upload APKs # needs: build-and-test # uses cached build from prior job @@ -69,7 +75,7 @@ jobs: path: ./build_reports/ # TODO: edit to never pull APKs - - name: Relocate Linting Reports to Dirs + - name: Relocate CI Artifacts to Dirs run: | rm -rf ./build_reports/app-debug-apk 2> /dev/null rm -rf ./build_reports/app-release-apk 2> /dev/null @@ -106,6 +112,10 @@ jobs: cp -a ./build_reports/test_report_release_artifact/. ./build_reports/test_report_release/ rm -r ./build_reports/test_report_release_artifact + mkdir ./build_reports/documentation + cp -a ./build_reports/dokka_documentation_artifact/. ./build_reports/documentation/ + rm -r ./build_reports/dokka_documentation_artifact + - name: Configure Git Identity run: | git config user.email "github-actions[bot]@users.noreply.github.com" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d68a8183c..a9b9ad262 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,6 +5,7 @@ plugins { id("com.google.devtools.ksp") version "1.9.0-1.0.13" kotlin("plugin.serialization") version "1.9.0" id("org.jetbrains.kotlinx.kover") version "0.9.3" + id("org.jetbrains.dokka") version "2.2.0-Beta" } android { diff --git a/build.gradle.kts b/build.gradle.kts index cde24fd78..98df12f5b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { kotlin("jvm") version "1.9.0" id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false id("org.jetbrains.kotlinx.kover") version "0.9.3" apply false + id("org.jetbrains.dokka") version "2.2.0-Beta" apply false } java { diff --git a/build_reports/index.html b/build_reports/index.html index 33c9616a1..2d3e7b26b 100644 --- a/build_reports/index.html +++ b/build_reports/index.html @@ -27,6 +27,9 @@ >