diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index af6122f808..1e3ba557a1 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -1,7 +1,6 @@ # Summary: # -# Creates a production build of the app that is eventually submitted for distribution. -# This workflow DOES NOT automatically perform the submission - that must still be handled manually. +# Creates a production build of the app and submits it for distribution. # # Triggers: # @@ -13,6 +12,9 @@ # - Creates a comment on the (merged) Release Candidate PR with a link to the EAS build process. # - Creates a release tag named `v${version}` on the merge commit in the targeted `release/**` branch. # `version` is `.` e.g. `1.2.0` is truncated to `2.0`. +# - Waits for EAS build to complete, then: +# - Creates a GitHub Release with the APK downloaded from R2 and release notes attached +# - Submits the AAB to the Google Play Store production track via `eas submit` name: Build Release on: @@ -21,14 +23,21 @@ on: - closed branches: - 'release/**' + workflow_dispatch: + inputs: + build_id: + description: 'EAS build ID (UUID from expo.dev build URL)' + required: true + type: string jobs: build: name: Build runs-on: ubuntu-latest - if: github.event.pull_request.merged == true + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true outputs: eas_build_url: ${{ steps.build.outputs.eas_build_url }} + build_id: ${{ steps.build.outputs.build_id }} steps: - name: Checkout repository @@ -60,12 +69,14 @@ jobs: exit 1 fi echo "Build ID: $build_id" + echo "build_id=$build_id" >> $GITHUB_OUTPUT eas_build_url="$EAS_PROJECT_URL/builds/$build_id" echo "eas_build_url=$eas_build_url" >> $GITHUB_OUTPUT comment: name: Comment runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true needs: build steps: @@ -89,6 +100,7 @@ jobs: tag: name: Create release tag runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true needs: build steps: @@ -119,3 +131,136 @@ jobs: git config --global user.email '${{ vars.RELEASE_BOT_USER_ID }}+awana-release-bot[bot]@users.noreply.github.com' git tag v${{ steps.version.outputs.release_version_short }} git push origin v${{ steps.version.outputs.release_version_short }} + + github-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [build, tag] + if: always() && (github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && needs.tag.result == 'success')) + timeout-minutes: 60 + + steps: + - name: Resolve inputs + id: inputs + run: | + echo "build_id=${{ inputs.build_id || needs.build.outputs.build_id }}" >> $GITHUB_OUTPUT + + - name: Create GitHub App Token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.RELEASE_BOT_APP_ID }} + private-key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }} + + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Set up Expo and EAS + uses: expo/expo-github-action@v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Install dependencies + run: npm ci + + - name: Wait for EAS build to finish + env: + BUILD_ID: ${{ steps.inputs.outputs.build_id }} + run: | + echo "Waiting for EAS build $BUILD_ID to complete..." + while true; do + status=$(eas build:view "$BUILD_ID" --json | jq -r '.status') + echo "Build status: $status" + if [ "$status" = "FINISHED" ]; then + echo "Build finished successfully" + break + elif [ "$status" = "ERRORED" ] || [ "$status" = "CANCELED" ]; then + echo "Build failed with status: $status" + exit 1 + fi + sleep 60 + done + + - name: Extract version from EAS build + id: eas_version + env: + BUILD_ID: ${{ steps.inputs.outputs.build_id }} + run: | + version=$(eas build:view "$BUILD_ID" --json | jq -r '.appVersion') + echo "version=$version" >> $GITHUB_OUTPUT + + - name: Download APK from R2 + env: + VERSION: ${{ steps.eas_version.outputs.version }} + run: | + APK_URL="https://downloads.comapeo.app/android/release/comapeo-v${VERSION}.apk" + echo "Downloading APK from $APK_URL" + curl -fL "$APK_URL" -o "comapeo-v${VERSION}.apk" + + - name: Read release notes + id: release_notes + env: + VERSION: ${{ steps.eas_version.outputs.version }} + run: | + MINOR_VERSION=$(echo "$VERSION" | cut -d. -f1) + NOTES_FILE="release-notes/closed-prs-v${MINOR_VERSION}.md" + if [ -f "$NOTES_FILE" ]; then + { + echo "body<> $GITHUB_OUTPUT + else + echo "body=No release notes found." >> $GITHUB_OUTPUT + fi + + - name: Create GitHub Release + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + VERSION: ${{ steps.eas_version.outputs.version }} + RELEASE_BODY: ${{ steps.release_notes.outputs.body }} + run: | + gh release create "v${VERSION}" \ + --title "CoMapeo v${VERSION}" \ + --notes "$RELEASE_BODY" \ + "comapeo-v${VERSION}.apk#CoMapeo Android APK" + + submit: + name: Submit to Play Store + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true + needs: build + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + + - name: Set up Expo and EAS + uses: expo/expo-github-action@v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: Install dependencies + run: npm ci + + - name: Submit to Google Play Store + env: + EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }} + run: | + eas submit --platform android --profile production --id ${{ needs.build.outputs.build_id }} --non-interactive diff --git a/eas.json b/eas.json index ea140857f4..a9025f0987 100644 --- a/eas.json +++ b/eas.json @@ -56,6 +56,11 @@ } }, "submit": { - "production": {} + "production": { + "android": { + "track": "production", + "releaseStatus": "completed" + } + } } } diff --git a/messages/de-DE/primary.json b/messages/de-DE/primary.json index 055b8e690e..9c58144881 100644 --- a/messages/de-DE/primary.json +++ b/messages/de-DE/primary.json @@ -23,9 +23,6 @@ "$1Navigation.Menu.currentProject": { "message": "Aktuelles Projekt" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "Sie befinden sich im Frühzugriffsmodus." - }, "$1Navigation.Menu.exchange": { "message": "Umtausch" }, diff --git a/messages/de-DE/secondary.json b/messages/de-DE/secondary.json index aa3db9405b..acd0503d97 100644 --- a/messages/de-DE/secondary.json +++ b/messages/de-DE/secondary.json @@ -403,12 +403,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo respektiert Ihre Privatsphäre und Unabhängigkeit" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Teilen Sie es hier" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "Haben Sie Feedback?" - }, "screens.EarlyAccess.infoBox": { "message": "Daten von Early Access sind für Mitarbeiter, die sich nicht für diesen Zugang entschieden haben, nicht sichtbar." }, diff --git a/messages/en-US/primary.json b/messages/en-US/primary.json index 1b25be60cb..bd59ff5296 100644 --- a/messages/en-US/primary.json +++ b/messages/en-US/primary.json @@ -23,8 +23,11 @@ "$1Navigation.Menu.currentProject": { "message": "Current Project" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "You are in Early Access Mode." + "$1Navigation.Menu.earlyAccessOn": { + "message": "Early Access ON" + }, + "$1Navigation.Menu.earlyAccessTurnOff": { + "message": "Turn Off" }, "$1Navigation.Menu.exchange": { "message": "Exchange" @@ -137,14 +140,20 @@ "$1Screens.Settings.AppSettings.coordinateDms": { "message": "Degrees/Min/Sec" }, + "$1Screens.Settings.AppSettings.coordinateSystem": { + "message": "Coordinate System" + }, "$1Screens.Settings.AppSettings.coordinateUtm": { "message": "UTM Coordinates" }, - "$1Screens.Settings.AppSettings.createTestData": { - "message": "Create Test Data" + "$1Screens.Settings.AppSettings.dataAndPrivacy": { + "message": "Data & Privacy" }, - "$1Screens.Settings.AppSettings.diagnosticInformation": { - "message": "Diagnostic Information" + "$1Screens.Settings.AppSettings.deviceName": { + "message": "Device Name" + }, + "$1Screens.Settings.AppSettings.earlyAccess": { + "message": "Early Access" }, "$1Screens.Settings.AppSettings.earlyAccessOff": { "message": "Early Access OFF" @@ -158,6 +167,12 @@ "$1Screens.Settings.AppSettings.imperial": { "message": "Imperial" }, + "$1Screens.Settings.AppSettings.language": { + "message": "Language" + }, + "$1Screens.Settings.AppSettings.learnMore": { + "message": "Learn More" + }, "$1Screens.Settings.AppSettings.metric": { "message": "Metric" }, @@ -167,12 +182,6 @@ "$1Screens.Settings.AppSettings.passcode": { "message": "Passcode" }, - "$1Screens.Settings.AppSettings.sharingPermissions": { - "message": "SHARING PERMISSIONS" - }, - "$1Screens.Settings.AppSettings.thisDevice": { - "message": "THIS DEVICE" - }, "$1Screens.Settings.AppSettings.title": { "message": "CoMapeo Settings" }, @@ -182,6 +191,9 @@ "$1Screens.Settings.AppSettings.turnOn": { "message": "Turn On" }, + "$1Screens.Settings.AppSettings.unitSystem": { + "message": "Unit System" + }, "$1TrackBottomSheet.DidNotMove.delete": { "message": "Exit Tracks" }, diff --git a/messages/en-US/secondary.json b/messages/en-US/secondary.json index 466c06cc3c..325b4d23ea 100644 --- a/messages/en-US/secondary.json +++ b/messages/en-US/secondary.json @@ -149,6 +149,12 @@ "Screens.ProjectSettings.viewDetails": { "message": "View Details" }, + "Screens.Settings.AppSettings.createTestData": { + "message": "Create Test Data" + }, + "Screens.Settings.AppSettings.testData": { + "message": "Test Data" + }, "Settings.ProjectSettings.RemoteArchive.RemoveRemoteArchive.cancel": { "description": "Text for cancel action button.", "message": "Cancel" @@ -438,11 +444,8 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo Respects Your Privacy & Autonomy" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Share it here" - }, "screens.EarlyAccess.feedbackPrefix": { - "message": "Have feedback?" + "message": "Have feedback? Share it at" }, "screens.EarlyAccess.infoBox": { "message": "Data from Early Access won't be visible to collaborators who haven't opted in." diff --git a/messages/es-419/primary.json b/messages/es-419/primary.json index b3599a7343..a8465b8354 100644 --- a/messages/es-419/primary.json +++ b/messages/es-419/primary.json @@ -23,9 +23,6 @@ "$1Navigation.Menu.currentProject": { "message": "Proyecto actual" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "Estás en modo de acceso anticipado." - }, "$1Navigation.Menu.exchange": { "message": "Intercambiar" }, diff --git a/messages/es-419/secondary.json b/messages/es-419/secondary.json index 38af5a33f2..c514fa6dd9 100644 --- a/messages/es-419/secondary.json +++ b/messages/es-419/secondary.json @@ -422,12 +422,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo respeta tu privacidad y autonomía" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Compartir aquí" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "¿Tienes comentarios?" - }, "screens.EarlyAccess.infoBox": { "message": "Los datos de acceso anticipado no serán visibles para los colaboradores que no hayan optado por participar." }, diff --git a/messages/fr-FR/primary.json b/messages/fr-FR/primary.json index acf0b057b2..98dfcbadf5 100644 --- a/messages/fr-FR/primary.json +++ b/messages/fr-FR/primary.json @@ -20,9 +20,6 @@ "$1Navigation.Menu.currentProject": { "message": "Projet actuel" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "Vous êtes en mode accès anticipé." - }, "$1Navigation.Menu.exchange": { "message": "Échanger" }, diff --git a/messages/fr-FR/secondary.json b/messages/fr-FR/secondary.json index f063a3c82c..12495379f7 100644 --- a/messages/fr-FR/secondary.json +++ b/messages/fr-FR/secondary.json @@ -403,12 +403,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo respecte votre vie privée et votre autonomie" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Partagez-le ici" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "Vous avez des commentaires ?" - }, "screens.EarlyAccess.infoBox": { "message": "Les données de l'accès anticipé ne seront pas visibles pour les collaborateurs qui ne s'y sont pas engagés." }, diff --git a/messages/id-ID/primary.json b/messages/id-ID/primary.json index 18b8595c58..ef17396200 100644 --- a/messages/id-ID/primary.json +++ b/messages/id-ID/primary.json @@ -23,9 +23,6 @@ "$1Navigation.Menu.currentProject": { "message": "Proyek Saat Ini" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "Anda berada dalam Mode Akses Awal." - }, "$1Navigation.Menu.exchange": { "message": "Pertukaran" }, diff --git a/messages/id-ID/secondary.json b/messages/id-ID/secondary.json index 2485914367..9a6023c4f3 100644 --- a/messages/id-ID/secondary.json +++ b/messages/id-ID/secondary.json @@ -410,12 +410,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo Menghormati Privasi & Otonomi Anda" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Bagikan di sini" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "Punya umpan balik?" - }, "screens.EarlyAccess.infoBox": { "message": "Data dari Akses Awal tidak akan terlihat oleh kolaborator yang belum ikut serta." }, diff --git a/messages/ja-JP/primary.json b/messages/ja-JP/primary.json index 0c7bd08237..cbbeced09f 100644 --- a/messages/ja-JP/primary.json +++ b/messages/ja-JP/primary.json @@ -17,9 +17,6 @@ "$1Navigation.Menu.currentProject": { "message": "現在のプロジェクト" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "アーリーアクセスモードに入っています。" - }, "$1Navigation.Menu.exchange": { "message": "交換" }, diff --git a/messages/ja-JP/secondary.json b/messages/ja-JP/secondary.json index 2203575965..e1a372e5cc 100644 --- a/messages/ja-JP/secondary.json +++ b/messages/ja-JP/secondary.json @@ -403,12 +403,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeoはあなたのプライバシーと自律性を尊重します" }, - "screens.EarlyAccess.feedbackLink": { - "message": "ここで共有" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "フィードバックがありますか?" - }, "screens.EarlyAccess.infoBox": { "message": "アーリーアクセスからのデータは、未登録のコラボレーターには表示されません。" }, diff --git a/messages/nl-NL/primary.json b/messages/nl-NL/primary.json index 6c9b38e1ec..edc87486af 100644 --- a/messages/nl-NL/primary.json +++ b/messages/nl-NL/primary.json @@ -20,9 +20,6 @@ "$1Navigation.Menu.currentProject": { "message": "Huidig project" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "U bent in de Vroegtijdige toegangsmodus." - }, "$1Navigation.Menu.exchange": { "message": "Ruil" }, diff --git a/messages/nl-NL/secondary.json b/messages/nl-NL/secondary.json index 10ed24f9a8..80089fc121 100644 --- a/messages/nl-NL/secondary.json +++ b/messages/nl-NL/secondary.json @@ -405,12 +405,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo Respecteert uw Privacy & Autonomie" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Deel het hier" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "Heeft u feedback?" - }, "screens.EarlyAccess.infoBox": { "message": "Gegevens uit Vroegtijdige Toegang zullen niet zichtbaar zijn voor medewerkers die niet hebben gekozen." }, diff --git a/messages/pt-BR/primary.json b/messages/pt-BR/primary.json index 9ce97ab9c7..c7e8e05014 100644 --- a/messages/pt-BR/primary.json +++ b/messages/pt-BR/primary.json @@ -23,9 +23,6 @@ "$1Navigation.Menu.currentProject": { "message": "Projeto Atual" }, - "$1Navigation.Menu.earlyAccessLabel": { - "message": "Você está no Modo de Acesso Antecipado." - }, "$1Navigation.Menu.exchange": { "message": "Trocar" }, diff --git a/messages/pt-BR/secondary.json b/messages/pt-BR/secondary.json index d7b259e5af..801acacb44 100644 --- a/messages/pt-BR/secondary.json +++ b/messages/pt-BR/secondary.json @@ -422,12 +422,6 @@ "screens.DataAndPrivacy.respectsPrivacy": { "message": "CoMapeo respeita suas privacidade e autonomia" }, - "screens.EarlyAccess.feedbackLink": { - "message": "Compartilhe aqui" - }, - "screens.EarlyAccess.feedbackPrefix": { - "message": "Algum feedback?" - }, "screens.EarlyAccess.infoBox": { "message": "Os dados do Acesso Antecipado não serão visíveis para os colaboradores que não optaram." }, diff --git a/package-lock.json b/package-lock.json index 380511c255..f542d3f42a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "comapeo-mobile", - "version": "1.12.0-pre", + "version": "1.12.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "comapeo-mobile", - "version": "1.12.0-pre", + "version": "1.12.0", "hasInstallScript": true, "dependencies": { "@comapeo/core-react": "11.0.4", diff --git a/package.json b/package.json index 408fd0e378..10a9d0093f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "comapeo-mobile", - "version": "1.12.0-pre", + "version": "1.12.0", "private": true, "main": "index.js", "scripts": { diff --git a/release-notes/closed-issues-v12.md b/release-notes/closed-issues-v12.md new file mode 100644 index 0000000000..a4e66e60f5 --- /dev/null +++ b/release-notes/closed-issues-v12.md @@ -0,0 +1,37 @@ +# Closed Issues for v12 + +[closed #1570](https://github.com/digidem/comapeo-mobile/issues/1570): Create new Waiting for Map Accept + +[closed #1736](https://github.com/digidem/comapeo-mobile/issues/1736): Switch to Map Libre + +[closed #1744](https://github.com/digidem/comapeo-mobile/issues/1744): Color dot on map not the same as in detail view + +[closed #1755](https://github.com/digidem/comapeo-mobile/issues/1755): Map Black after receiving map file + +[closed #1756](https://github.com/digidem/comapeo-mobile/issues/1756): `ReplaceBackgroundMap` actions not working + +[closed #1757](https://github.com/digidem/comapeo-mobile/issues/1757): Map Reciever Cannot properly cancel a map being shared + +[closed #1784](https://github.com/digidem/comapeo-mobile/issues/1784): Upgrade Map work to latest core deps + +[closed #1815](https://github.com/digidem/comapeo-mobile/issues/1815): Photo Capture Improvements + +[closed #1816](https://github.com/digidem/comapeo-mobile/issues/1816): Settings Menu Improvements + +[closed #1818](https://github.com/digidem/comapeo-mobile/issues/1818): GPS / metric and imperial + +[closed #1819](https://github.com/digidem/comapeo-mobile/issues/1819): Possible Invisible Text in Dark Mode + +[closed #1834](https://github.com/digidem/comapeo-mobile/issues/1834): “Something went wrong” screen/ Restart app button + +[closed #1835](https://github.com/digidem/comapeo-mobile/issues/1835): Delete Track or Delete Observation / Update UI + +[closed #1843](https://github.com/digidem/comapeo-mobile/issues/1843): Settings/ Language Selection + +[closed #1853](https://github.com/digidem/comapeo-mobile/issues/1853): Editing Observation or Track/ bottom sheet hidden by keyboard when canceling + +[closed #1873](https://github.com/digidem/comapeo-mobile/issues/1873): Large size font/ Observation title truncated + +[closed #1906](https://github.com/digidem/comapeo-mobile/issues/1906): Scale Bar not updating + +[closed #290](https://github.com/digidem/comapeo-mobile/issues/290): Regression in navigation when returning to home tabs after discarding observation flow diff --git a/release-notes/closed-prs-v12.md b/release-notes/closed-prs-v12.md new file mode 100644 index 0000000000..5d825fd195 --- /dev/null +++ b/release-notes/closed-prs-v12.md @@ -0,0 +1,70 @@ +# Closed Prs for v12 + +PRs merged between: + +> - chore: start v12 development iteration (2026-04-21T20:47:39Z, SHA: e233b4f8) +> - 2026-05-26T19:19:16Z + +PR #1855: fix: close keyboard on discard observation +[closed #1853](https://github.com/digidem/comapeo-mobile/issues/1853) + +PR #1858: Hotfix Release Candidate v10.5 + +PR #1693: feat: map sharing +[closed #1570](https://github.com/digidem/comapeo-mobile/issues/1570) +[closed #1757](https://github.com/digidem/comapeo-mobile/issues/1757) +[closed #1755](https://github.com/digidem/comapeo-mobile/issues/1755) +[closed #1756](https://github.com/digidem/comapeo-mobile/issues/1756) +[closed #1784](https://github.com/digidem/comapeo-mobile/issues/1784) + +PR #1861: fix: something went wrong navigation bar padding +[closed #1834](https://github.com/digidem/comapeo-mobile/issues/1834) + +PR #1866: feat: updated language menu +[closed #1843](https://github.com/digidem/comapeo-mobile/issues/1843) + +PR #1862: chore: photo capture improvements +[closed #1815](https://github.com/digidem/comapeo-mobile/issues/1815) + +PR #1864: chore: new deleting observation and track UI +[closed #1835](https://github.com/digidem/comapeo-mobile/issues/1835) + +PR #1870: New Crowdin updates + +PR #1868: fix: invisible text privacy policy +[closed #1819](https://github.com/digidem/comapeo-mobile/issues/1819) + +PR #1877: fix: color dot on inset map +[closed #1744](https://github.com/digidem/comapeo-mobile/issues/1744) + +PR #1839: chore: switch to map libre +[closed #1736](https://github.com/digidem/comapeo-mobile/issues/1736) + +PR #1876: chore: update settings menu +[closed #1816](https://github.com/digidem/comapeo-mobile/issues/1816) + +PR #1892: fix: queuing browserstack tests in github actions + +PR #1893: chore: documents for shared ethos and coding guidelines + +PR #1895: chore: remove conditional navigators + +PR #1897: chore: update testing practices doc + +PR #1890: fix: observation title truncated +[closed #1873](https://github.com/digidem/comapeo-mobile/issues/1873) + +PR #1867: feature: metric imperial choice +[closed #1818](https://github.com/digidem/comapeo-mobile/issues/1818) + +PR #1894: chore: update jest setup + +PR #1888: fix: nav back to camera after discarding observation +[closed #290](https://github.com/digidem/comapeo-mobile/issues/290) + +PR #1898: feat: enable foreground service for background tracks + +PR #1901: New Crowdin updates + +PR #1907: fix: update scale bar with zoom +[closed #1906](https://github.com/digidem/comapeo-mobile/issues/1906) diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index 19d5049e4c..69ef6d2e4b 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {Logger} from '@maplibre/maplibre-react-native'; +import {Logger, setConnected} from '@maplibre/maplibre-react-native'; import {getLocales} from 'expo-localization'; // Maplibre logs when tile requests are cancelled, which is often. @@ -13,6 +13,12 @@ Logger.setLogCallback(log => { } return false; }); + +// All styles are served via localhost and we need to bypass the internal connectivity manager in MapLibre React Native +// in order for things to work while the app is offline. +// https://github.com/maplibre/maplibre-react-native/blob/6f99de530eec2e06de485ef86f4be61f941e0e09/docs/content/modules/mlrn-module.md#setconnectedconnected +setConnected(true); + import {QueryClient} from '@tanstack/react-query'; import {AppNavigator} from './AppNavigator'; import {initializeNodejs} from './initializeNodejs'; diff --git a/src/frontend/screens/BackgroundMaps/ReceivingBackgroundMap.tsx b/src/frontend/screens/BackgroundMaps/ReceivingBackgroundMap.tsx index c6273cafad..0bed0dafe9 100644 --- a/src/frontend/screens/BackgroundMaps/ReceivingBackgroundMap.tsx +++ b/src/frontend/screens/BackgroundMaps/ReceivingBackgroundMap.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {StyleSheet, View, Pressable} from 'react-native'; +import {StyleSheet, View, Pressable, AppState} from 'react-native'; import {defineMessages, useIntl} from 'react-intl'; import MaterialIcon from '@react-native-vector-icons/material-icons'; import * as Sentry from '@sentry/react-native'; @@ -85,6 +85,15 @@ export function ReceivingBackgroundMap({ ); }, [abortDownload, shareId, navigation]); + React.useEffect(() => { + const subscription = AppState.addEventListener('change', nextState => { + if (nextState === 'background') { + handleCancel(); + } + }); + return () => subscription.remove(); + }, [handleCancel]); + const handleDone = () => { navigation.goBack(); }; diff --git a/src/frontend/screens/BackgroundMaps/SendingBackgroundMap.tsx b/src/frontend/screens/BackgroundMaps/SendingBackgroundMap.tsx index f18ee22aac..832025c1fa 100644 --- a/src/frontend/screens/BackgroundMaps/SendingBackgroundMap.tsx +++ b/src/frontend/screens/BackgroundMaps/SendingBackgroundMap.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {StyleSheet, View, Pressable} from 'react-native'; +import {AppState, StyleSheet, View, Pressable} from 'react-native'; import {defineMessages, useIntl} from 'react-intl'; import * as Sentry from '@sentry/react-native'; import MaterialIcon from '@react-native-vector-icons/material-icons'; @@ -120,6 +120,15 @@ export function SendingBackgroundMap({ ); }, [navigation, cancelMapShare, shareId]); + React.useEffect(() => { + const subscription = AppState.addEventListener('change', nextState => { + if (nextState === 'background') { + cancelShare(); + } + }); + return () => subscription.remove(); + }, [cancelShare]); + const handleClose = () => { navigation.goBack(); }; diff --git a/src/frontend/screens/ComapeoSettings/EarlyAccess.tsx b/src/frontend/screens/ComapeoSettings/EarlyAccess.tsx index fe82deab6d..8e38c6072b 100644 --- a/src/frontend/screens/ComapeoSettings/EarlyAccess.tsx +++ b/src/frontend/screens/ComapeoSettings/EarlyAccess.tsx @@ -38,11 +38,7 @@ const m = defineMessages({ }, feedbackPrefix: { id: 'screens.EarlyAccess.feedbackPrefix', - defaultMessage: 'Have feedback? ', - }, - feedbackLink: { - id: 'screens.EarlyAccess.feedbackLink', - defaultMessage: 'Share it here', + defaultMessage: 'Have feedback? Share it at ', }, }); @@ -103,14 +99,10 @@ export const EarlyAccess = ({ {formatMessage(m.feedbackPrefix)} - Linking.openURL( - 'https://docs.google.com/forms/d/10r8wJ7WuZ5PMR7Gbx9aKcWTq-IFXz2ZcPKvk7bP0K4Q/edit', - ) - } + onPress={() => Linking.openURL('mailto:feedback@comapeo.app')} accessibilityRole="link"> - {formatMessage(m.feedbackLink)} + {'feedback@comapeo.app'} diff --git a/src/frontend/screens/ComapeoSettings/index.tsx b/src/frontend/screens/ComapeoSettings/index.tsx index d52f69237d..d765e17f75 100644 --- a/src/frontend/screens/ComapeoSettings/index.tsx +++ b/src/frontend/screens/ComapeoSettings/index.tsx @@ -7,7 +7,6 @@ import {NativeNavigationComponent} from '../../sharedTypes/navigation'; import {useAuthContext} from '../../contexts/AuthContext'; import {useEarlyAccessState} from '../../contexts/EarlyAccessContext'; import {useCoordinateFormat} from '../../contexts/CoordinateFormatStoreContext'; -import {useMetricsDiagnosticsEnabled} from '../../contexts/MetricsDiagnosticsStoreContext'; import {useAppLanguageTag} from '../../hooks/useAppLanguageTag'; import {USABLE_LANGUAGES} from '../../lib/intl'; import {useOwnDeviceInfo} from '@comapeo/core-react'; @@ -24,14 +23,6 @@ const m = defineMessages({ id: '$1Screens.Settings.AppSettings.title', defaultMessage: 'CoMapeo Settings', }, - thisDevice: { - id: '$1Screens.Settings.AppSettings.thisDevice', - defaultMessage: 'THIS DEVICE', - }, - sharingPermissions: { - id: '$1Screens.Settings.AppSettings.sharingPermissions', - defaultMessage: 'SHARING PERMISSIONS', - }, edit: { id: '$1Screens.Settings.AppSettings.edit', defaultMessage: 'Edit', @@ -77,12 +68,12 @@ const m = defineMessages({ defaultMessage: 'About CoMapeo', }, createTestData: { - id: '$1Screens.Settings.AppSettings.createTestData', + id: 'Screens.Settings.AppSettings.createTestData', defaultMessage: 'Create Test Data', }, - diagnosticInformation: { - id: '$1Screens.Settings.AppSettings.diagnosticInformation', - defaultMessage: 'Diagnostic Information', + dataAndPrivacy: { + id: '$1Screens.Settings.AppSettings.dataAndPrivacy', + defaultMessage: 'Data & Privacy', }, metric: { id: '$1Screens.Settings.AppSettings.metric', @@ -92,6 +83,34 @@ const m = defineMessages({ id: '$1Screens.Settings.AppSettings.imperial', defaultMessage: 'Imperial', }, + deviceName: { + id: '$1Screens.Settings.AppSettings.deviceName', + defaultMessage: 'Device Name', + }, + language: { + id: '$1Screens.Settings.AppSettings.language', + defaultMessage: 'Language', + }, + coordinateSystem: { + id: '$1Screens.Settings.AppSettings.coordinateSystem', + defaultMessage: 'Coordinate System', + }, + unitSystem: { + id: '$1Screens.Settings.AppSettings.unitSystem', + defaultMessage: 'Unit System', + }, + earlyAccess: { + id: '$1Screens.Settings.AppSettings.earlyAccess', + defaultMessage: 'Early Access', + }, + learnMore: { + id: '$1Screens.Settings.AppSettings.learnMore', + defaultMessage: 'Learn More', + }, + testData: { + id: 'Screens.Settings.AppSettings.testData', + defaultMessage: 'Test Data', + }, }); export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ @@ -101,7 +120,6 @@ export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ const {authState} = useAuthContext(); const isEarlyAccess = useEarlyAccessState(s => s.isEarlyAccessEnabled); const coordinateFormat = useCoordinateFormat(); - const diagnosticsEnabled = useMetricsDiagnosticsEnabled(); const appLocale = useAppLanguageTag(); const passcode = useSecurityState(s => s.passcode); const {data: deviceInfo} = useOwnDeviceInfo(); @@ -122,7 +140,7 @@ export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ return ( - {formatMessage(m.thisDevice)} + {formatMessage(m.deviceName)} = ({ } /> - + + {formatMessage(m.language)} + navigation.navigate('LanguageSettings')} @@ -148,7 +168,9 @@ export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ } /> - + + {formatMessage(m.coordinateSystem)} + navigation.navigate('CoordinateFormat')} @@ -158,7 +180,9 @@ export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ } /> - + + {formatMessage(m.unitSystem)} + { @@ -174,27 +198,64 @@ export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ } /> + {process.env.EXPO_PUBLIC_FEATURE_TEST_DATA_UI && ( + <> + + {formatMessage(m.testData)} + + navigation.navigate('CreateTestData')} + label={formatMessage(m.createTestData)} + Icon={ + + } + EndContent={ + + } + /> + + )} + + {authState !== 'obscured' && ( - navigation.navigate('Security')} - label={ - hasPasscode - ? formatMessage(m.passcode) - : formatMessage(m.noPasscode) - } - Icon={ - - } - EndContent={ - - {hasPasscode ? formatMessage(m.turnOff) : formatMessage(m.turnOn)} - - } - /> + <> + + {formatMessage(m.passcode)} + + navigation.navigate('Security')} + label={ + hasPasscode + ? formatMessage(m.passcode) + : formatMessage(m.noPasscode) + } + Icon={ + + } + EndContent={ + + {hasPasscode + ? formatMessage(m.turnOff) + : formatMessage(m.turnOn)} + + } + /> + )} + + {formatMessage(m.earlyAccess)} + navigation.navigate('EarlyAccess')} @@ -211,56 +272,37 @@ export const AppSettings: NativeNavigationComponent<'AppSettings'> = ({ } /> + + + + {formatMessage(m.dataAndPrivacy)} + + navigation.navigate('AboutSettings')} - label={formatMessage(m.aboutCoMapeo)} - Icon={ - - } + testID="dataAndPrivacyButton" + onPress={() => navigation.navigate('DataAndPrivacy')} + label={formatMessage(m.dataAndPrivacy)} + Icon={} EndContent={ } /> - {process.env.EXPO_PUBLIC_FEATURE_TEST_DATA_UI && ( - navigation.navigate('CreateTestData')} - label={formatMessage(m.createTestData)} - Icon={ - - } - EndContent={ - - } - /> - )} - - {formatMessage(m.sharingPermissions)} + {formatMessage(m.learnMore)} navigation.navigate('DataAndPrivacy')} - label={formatMessage(m.diagnosticInformation)} - Icon={} + testID="aboutSettingsButton" + onPress={() => navigation.navigate('AboutSettings')} + label={formatMessage(m.aboutCoMapeo)} + Icon={ + + } EndContent={ - - {diagnosticsEnabled - ? formatMessage(m.turnOff) - : formatMessage(m.turnOn)} - + } /> diff --git a/src/frontend/screens/MapScreen/CurrentTrack/UserTooltipMarker.tsx b/src/frontend/screens/MapScreen/CurrentTrack/UserTooltipMarker.tsx index 577762fdd4..4bb116587f 100644 --- a/src/frontend/screens/MapScreen/CurrentTrack/UserTooltipMarker.tsx +++ b/src/frontend/screens/MapScreen/CurrentTrack/UserTooltipMarker.tsx @@ -1,4 +1,4 @@ -import {PointAnnotation} from '@maplibre/maplibre-react-native'; +import {MarkerView} from '@maplibre/maplibre-react-native'; import {StyleSheet, Text, View} from 'react-native'; import {useTrackState} from '../../../contexts/TrackStoreContext'; @@ -21,7 +21,7 @@ export const UserTooltipMarker = () => { return ( // We dont want to put this check in the parent because it will cause the parent (the map) to render too often location?.coords && ( - { - + ) ); }; diff --git a/src/frontend/sharedComponents/DrawerMenu.tsx b/src/frontend/sharedComponents/DrawerMenu.tsx index 2eca3180ae..271784d787 100644 --- a/src/frontend/sharedComponents/DrawerMenu.tsx +++ b/src/frontend/sharedComponents/DrawerMenu.tsx @@ -1,5 +1,11 @@ import React from 'react'; -import {View, StyleSheet, ScrollView, TouchableOpacity} from 'react-native'; +import { + View, + StyleSheet, + ScrollView, + TouchableOpacity, + Pressable, +} from 'react-native'; import {useIntl, defineMessages} from 'react-intl'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; import IonIcon from '@react-native-vector-icons/ionicons'; @@ -11,7 +17,13 @@ import Exchange from '../images/Exchange.svg'; import CollaborateIcon from '../images/ProjectParticipant.svg'; import {BodyText} from '../sharedComponents/Text/BodyText.tsx'; import {useProjectRoleAndDetails} from '../hooks/useProjectRoleAndDetails.ts'; -import {BLUE_GREY, COMAPEO_BLUE, NEW_DARK_GREY, WHITE} from '../lib/styles.ts'; +import { + BLUE_GREY, + COMAPEO_BLUE, + LIGHT_ORANGE, + NEW_DARK_GREY, + WHITE, +} from '../lib/styles.ts'; import {useActiveProject} from '../contexts/ActiveProjectContext.tsx'; import {MenuLowStorageAlert} from '../sharedComponents/Storage/MenuLowStorageAlert.tsx'; import {useStorageReadingQuery} from '../hooks/useStorageReadingQuery.ts'; @@ -64,9 +76,13 @@ const m = defineMessages({ id: '$1Navigation.Menu.switchProject', defaultMessage: 'Switch Project', }, - earlyAccessLabel: { - id: '$1Navigation.Menu.earlyAccessLabel', - defaultMessage: 'You are in Early Access Mode.', + earlyAccessOn: { + id: '$1Navigation.Menu.earlyAccessOn', + defaultMessage: 'Early Access ON', + }, + earlyAccessTurnOff: { + id: '$1Navigation.Menu.earlyAccessTurnOff', + defaultMessage: 'Turn Off', }, team: { id: '$1Navigation.Menu.team', @@ -101,13 +117,34 @@ export function DrawerMenu({closeMenu}: {closeMenu: () => void}) { return ( - {isLow && ( - - )} - + + {isLow && ( + + )} + {isEarly ? ( + + + + + + {formatMessage(m.earlyAccessOn)} + + navigation.navigate('EarlyAccess')} + hitSlop={{top: 12, bottom: 12, left: 12, right: 12}}> + + {formatMessage(m.earlyAccessTurnOff)} + + + + + + ) : null} {projectHeader} @@ -171,13 +208,6 @@ export function DrawerMenu({closeMenu}: {closeMenu: () => void}) { - {isEarly ? ( - - - {formatMessage(m.earlyAccessLabel)} - - ) : null} - {role !== 'solo' && ( { await appSettingsOption.click(); const aboutComapeoOption = await $(byResourceId('aboutSettingsButton')); + await aboutComapeoOption.scrollIntoView(); await aboutComapeoOption.click(); }); diff --git a/tests/e2e/specs/settings/early-access.test.ts b/tests/e2e/specs/settings/early-access.test.ts index 0d65d280ef..f52f640777 100644 --- a/tests/e2e/specs/settings/early-access.test.ts +++ b/tests/e2e/specs/settings/early-access.test.ts @@ -28,7 +28,7 @@ describe('Settings - Early Access Mode', () => { ), ).toBeDisplayed(); await expect($(byTextMatches('Have feedback?'))).toBeDisplayed(); - await expect($(byTextMatches('Share it here'))).toBeDisplayed(); + await expect($(byTextMatches('Share it at'))).toBeDisplayed(); }); it('should show ON in the App Settings list item text', async () => { @@ -45,12 +45,11 @@ describe('Settings - Early Access Mode', () => { await backBtn.click(); const backBtnAgain = await $(byResourceId('MAIN.header-back-btn')); await backBtnAgain.click(); - await expect( - $(byTextMatches('You are in Early Access Mode')), - ).toBeDisplayed(); + await expect($(byTextMatches('Early Access ON'))).toBeDisplayed(); const appSettingsOption = await $('~Go to app settings screen.'); await appSettingsOption.click(); const aboutComapeoOption = await $(byResourceId('aboutSettingsButton')); + await aboutComapeoOption.scrollIntoView(); await aboutComapeoOption.click(); await expect( $(byTextMatches('You are in Early Access Mode.')),