Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .DS_Store
Binary file not shown.
140 changes: 130 additions & 10 deletions .github/workflows/react-native-cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ env:
UNIT_SENTRY_DSN: ${{ secrets.UNIT_SENTRY_DSN }}
UNIT_ANDROID_KS: ${{ secrets.UNIT_ANDROID_KS }}
UNIT_GOOGLE_SERVICES: ${{ secrets.UNIT_GOOGLE_SERVICES }}
UNIT_IOS_GOOGLE_SERVICES: ${{ secrets.UNIT_IOS_GOOGLE_SERVICES }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
Expand All @@ -71,6 +72,8 @@ env:
UNIT_APP_KEY: ${{ secrets.UNIT_APP_KEY }}
APP_KEY: ${{ secrets.APP_KEY }}
NODE_OPTIONS: --openssl-legacy-provider
CHANGERAWR_API_KEY: ${{ secrets.CHANGERAWR_API_KEY }}
CHANGERAWR_API_URL: ${{ secrets.CHANGERAWR_API_URL }}

jobs:
check-skip:
Expand Down Expand Up @@ -153,6 +156,11 @@ jobs:
run: |
echo $UNIT_GOOGLE_SERVICES | base64 -d > google-services.json

- name: 📋 Create Google Json File for iOS
if: ${{ matrix.platform == 'ios' }}
run: |
echo $UNIT_IOS_GOOGLE_SERVICES | base64 -d > GoogleService-Info.plist

- name: 📋 Update package.json Versions
run: |
# Ensure jq exists on both Linux and macOS
Expand Down Expand Up @@ -287,33 +295,145 @@ jobs:

- name: 📋 Prepare Release Notes file
if: ${{ matrix.platform == 'android' }}
env:
RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
set -eo pipefail
# Determine source of release notes: workflow input, PR body, or recent commits
if [ -n "$RELEASE_NOTES_INPUT" ]; then
NOTES="$RELEASE_NOTES_INPUT"
elif [ -n "$PR_BODY" ]; then
NOTES="$(printf '%s\n' "$PR_BODY" \

# Function to extract release notes from PR body
extract_release_notes() {
local body="$1"
# Try to extract content under "## Release Notes" heading
local notes="$(printf '%s\n' "$body" \
| awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
else

# If no specific section found, use the entire body (up to first 500 chars for safety)
if [ -z "$notes" ]; then
notes="$(printf '%s\n' "$body" | head -c 500)"
fi

printf '%s\n' "$notes"
}

# Determine source of release notes
NOTES=""

# Check if this was triggered by a push event (likely a merge)
if [ "${{ github.event_name }}" = "push" ]; then
echo "Fetching PR body for merged commit..."

# First, try to find PR number from commit message (most reliable)
PR_FROM_COMMIT=$(git log -1 --pretty=%B | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")

if [ -n "$PR_FROM_COMMIT" ]; then
echo "Found PR #$PR_FROM_COMMIT from commit message"
PR_BODY=$(gh pr view "$PR_FROM_COMMIT" --json body --jq '.body' 2>/dev/null || echo "")

if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
else
echo "No PR reference in commit message, searching by commit SHA..."
# Get PRs that contain this commit (using GitHub API to search by commit)
PR_NUMBERS=$(gh api \
"repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
--jq '.[].number' 2>/dev/null || echo "")

if [ -n "$PR_NUMBERS" ]; then
# Take the first PR found (most recently merged)
PR_NUMBER=$(echo "$PR_NUMBERS" | head -n 1)
echo "Found PR #$PR_NUMBER associated with commit"

# Fetch the PR body
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null || echo "")

if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
else
echo "No associated PR found for this commit"
fi
fi
fi

# Fallback to recent commits if no PR body found
if [ -z "$NOTES" ]; then
echo "No PR body found, using recent commits..."
NOTES="$(git log -n 5 --pretty=format:'- %s')"
fi

# Fail if no notes extracted
if [ -z "$NOTES" ]; then
echo "Error: No release notes extracted" >&2
exit 1
fi

# Write header and notes to file
{
echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)"
echo
printf '%s\n' "$NOTES"
} > RELEASE_NOTES.md

echo "Release notes prepared:"
cat RELEASE_NOTES.md
env:
GH_TOKEN: ${{ github.token }}

- name: 📝 Send Release Notes to Changerawr
if: ${{ matrix.platform == 'android' }}
run: |
set -eo pipefail

# Check if required secrets are set
if [ -z "$CHANGERAWR_API_URL" ] || [ -z "$CHANGERAWR_API_KEY" ]; then
echo "⚠️ Changerawr API credentials not configured, skipping release notes submission"
exit 0
fi

# Read release notes
RELEASE_NOTES=$(cat RELEASE_NOTES.md)
VERSION="7.${{ github.run_number }}"

# Prepare JSON payload
PAYLOAD=$(jq -n \
--arg version "$VERSION" \
--arg notes "$RELEASE_NOTES" \
--arg platform "android" \
--arg buildNumber "${{ github.run_number }}" \
--arg commitSha "${{ github.sha }}" \
--arg buildUrl "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
'{
version: $version,
releaseNotes: $notes,
platform: $platform,
buildNumber: $buildNumber,
commitSha: $commitSha,
buildUrl: $buildUrl,
timestamp: now | todate
}')

echo "Sending release notes to Changerawr..."

# Send to Changerawr API
RESPONSE=$(curl -X POST "$CHANGERAWR_API_URL" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $CHANGERAWR_API_KEY" \
-d "$PAYLOAD" \
-w "\n%{http_code}" \
-s)

HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
echo "✅ Successfully sent release notes to Changerawr (HTTP $HTTP_CODE)"
echo "Response: $RESPONSE_BODY"
else
echo "⚠️ Failed to send release notes to Changerawr (HTTP $HTTP_CODE)"
echo "Response: $RESPONSE_BODY"
# Don't fail the build, just warn
fi

- name: 📦 Create Release
- name: 📦 Create Release
if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }}
uses: ncipollo/release-action@v1
with:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ yarn-error.log
/build
/automatic-build
google-services.json
GoogleService-Info.plist
credentials.json
Gemfile.lock
Gemfile
Expand Down
2 changes: 0 additions & 2 deletions __mocks__/react-native-ble-plx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ export class BleManager {
private static mockDevices: Device[] = [];
private static stateListener: ((state: State) => void) | null = null;

constructor() {}

static setMockState(state: State) {
this.mockState = state;
if (this.stateListener) {
Expand Down
3 changes: 3 additions & 0 deletions app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
supportsTablet: true,
bundleIdentifier: Env.BUNDLE_ID,
requireFullScreen: true,
googleServicesFile: 'GoogleService-Info.plist',
infoPlist: {
UIBackgroundModes: ['remote-notification', 'audio', 'bluetooth-central', 'voip'],
ITSAppUsesNonExemptEncryption: false,
Expand Down Expand Up @@ -214,6 +215,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
},
ios: {
deploymentTarget: '18.1',
useFrameworks: 'static',
},
},
],
Expand Down Expand Up @@ -262,6 +264,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
'@livekit/react-native-expo-plugin',
'@config-plugins/react-native-webrtc',
'@config-plugins/react-native-callkeep',
'@react-native-firebase/app',
'./customGradle.plugin.js',
'./customManifest.plugin.js',
['app-icon-badge', appIconBadgeConfig],
Expand Down
16 changes: 16 additions & 0 deletions eas.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@
"EXPO_NO_DOTENV": "1"
}
},
"dev-sim": {
"developmentClient": false,
"yarn": "1.22.22",
"ios": {
"simulator": true,
"image": "latest"
},
"android": {
"buildType": "apk",
"image": "latest"
},
"env": {
"APP_ENV": "development",
"EXPO_NO_DOTENV": "1"
}
},
"simulator": {
"yarn": "1.22.22",
"ios": {
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@
"@notifee/react-native": "^9.1.8",
"@novu/react-native": "~2.6.6",
"@react-native-community/netinfo": "^11.4.1",
"@react-native-firebase/analytics": "^23.5.0",
"@react-native-firebase/app": "^23.5.0",
"@react-native-firebase/messaging": "^23.5.0",
"@rnmapbox/maps": "10.1.42-rc.0",
"@semantic-release/git": "^10.0.1",
"@sentry/react-native": "~6.14.0",
Expand Down Expand Up @@ -122,7 +125,7 @@
"expo-localization": "~16.1.6",
"expo-location": "~18.1.6",
"expo-navigation-bar": "~4.2.8",
"expo-notifications": "~0.31.4",
"expo-notifications": "0.28.3",
"expo-router": "~5.1.7",
"expo-screen-orientation": "~8.1.7",
"expo-sharing": "~13.1.5",
Expand Down
24 changes: 10 additions & 14 deletions src/components/calls/dispatch-selection-modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CheckIcon, SearchIcon, UsersIcon, X } from 'lucide-react-native';
import { useColorScheme } from 'nativewind';
import React, { useEffect, useMemo } from 'react';
import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { ScrollView, TouchableOpacity, View } from 'react-native';

Expand All @@ -27,7 +27,7 @@
const { data, selection, isLoading, error, searchQuery, fetchDispatchData, setSelection, toggleEveryone, toggleUser, toggleGroup, toggleRole, toggleUnit, setSearchQuery, clearSelection, getFilteredData } =
useDispatchStore();

const filteredData = useMemo(() => getFilteredData(), [data, searchQuery]);
const filteredData = getFilteredData();

useEffect(() => {
if (isVisible) {
Expand Down Expand Up @@ -121,9 +121,8 @@
<TouchableOpacity onPress={() => toggleUser(user.Id)}>
<HStack className="items-center space-x-3">
<Box
className={`size-5 items-center justify-center rounded border-2 ${
selection.users.includes(user.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
}`}
className={`size-5 items-center justify-center rounded border-2 ${selection.users.includes(user.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'

Check warning on line 124 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Insert `⏎··························`
}`}

Check warning on line 125 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
>
{selection.users.includes(user.Id) && <CheckIcon size={12} className="text-white" />}
</Box>
Expand All @@ -148,9 +147,8 @@
<TouchableOpacity onPress={() => toggleGroup(group.Id)}>
<HStack className="items-center space-x-3">
<Box
className={`size-5 items-center justify-center rounded border-2 ${
selection.groups.includes(group.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
}`}
className={`size-5 items-center justify-center rounded border-2 ${selection.groups.includes(group.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'

Check warning on line 150 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Insert `⏎··························`
}`}

Check warning on line 151 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
>
{selection.groups.includes(group.Id) && <CheckIcon size={12} className="text-white" />}
</Box>
Expand All @@ -175,9 +173,8 @@
<TouchableOpacity onPress={() => toggleRole(role.Id)}>
<HStack className="items-center space-x-3">
<Box
className={`size-5 items-center justify-center rounded border-2 ${
selection.roles.includes(role.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
}`}
className={`size-5 items-center justify-center rounded border-2 ${selection.roles.includes(role.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'

Check warning on line 176 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Insert `⏎··························`
}`}

Check warning on line 177 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
>
{selection.roles.includes(role.Id) && <CheckIcon size={12} className="text-white" />}
</Box>
Expand All @@ -202,9 +199,8 @@
<TouchableOpacity onPress={() => toggleUnit(unit.Id)}>
<HStack className="items-center space-x-3">
<Box
className={`size-5 items-center justify-center rounded border-2 ${
selection.units.includes(unit.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
}`}
className={`size-5 items-center justify-center rounded border-2 ${selection.units.includes(unit.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'

Check warning on line 202 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Insert `⏎··························`
}`}

Check warning on line 203 in src/components/calls/dispatch-selection-modal.tsx

View workflow job for this annotation

GitHub Actions / test

Delete `··`
>
{selection.units.includes(unit.Id) && <CheckIcon size={12} className="text-white" />}
</Box>
Expand Down
Loading