Skip to content

Commit 5b79d7e

Browse files
authored
Merge pull request #176 from Resgrid/develop
Develop
2 parents 916a3df + 0a7d9e3 commit 5b79d7e

21 files changed

+1704
-166
lines changed

.DS_Store

0 Bytes
Binary file not shown.

.github/workflows/react-native-cicd.yml

Lines changed: 130 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ env:
4747
UNIT_SENTRY_DSN: ${{ secrets.UNIT_SENTRY_DSN }}
4848
UNIT_ANDROID_KS: ${{ secrets.UNIT_ANDROID_KS }}
4949
UNIT_GOOGLE_SERVICES: ${{ secrets.UNIT_GOOGLE_SERVICES }}
50+
UNIT_IOS_GOOGLE_SERVICES: ${{ secrets.UNIT_IOS_GOOGLE_SERVICES }}
5051
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
5152
APP_STORE_CONNECT_KEY_ID: ${{ secrets.APP_STORE_CONNECT_KEY_ID }}
5253
APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
@@ -71,6 +72,8 @@ env:
7172
UNIT_APP_KEY: ${{ secrets.UNIT_APP_KEY }}
7273
APP_KEY: ${{ secrets.APP_KEY }}
7374
NODE_OPTIONS: --openssl-legacy-provider
75+
CHANGERAWR_API_KEY: ${{ secrets.CHANGERAWR_API_KEY }}
76+
CHANGERAWR_API_URL: ${{ secrets.CHANGERAWR_API_URL }}
7477

7578
jobs:
7679
check-skip:
@@ -153,6 +156,11 @@ jobs:
153156
run: |
154157
echo $UNIT_GOOGLE_SERVICES | base64 -d > google-services.json
155158
159+
- name: 📋 Create Google Json File for iOS
160+
if: ${{ matrix.platform == 'ios' }}
161+
run: |
162+
echo $UNIT_IOS_GOOGLE_SERVICES | base64 -d > GoogleService-Info.plist
163+
156164
- name: 📋 Update package.json Versions
157165
run: |
158166
# Ensure jq exists on both Linux and macOS
@@ -287,33 +295,145 @@ jobs:
287295
288296
- name: 📋 Prepare Release Notes file
289297
if: ${{ matrix.platform == 'android' }}
290-
env:
291-
RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }}
292-
PR_BODY: ${{ github.event.pull_request.body }}
293298
run: |
294299
set -eo pipefail
295-
# Determine source of release notes: workflow input, PR body, or recent commits
296-
if [ -n "$RELEASE_NOTES_INPUT" ]; then
297-
NOTES="$RELEASE_NOTES_INPUT"
298-
elif [ -n "$PR_BODY" ]; then
299-
NOTES="$(printf '%s\n' "$PR_BODY" \
300+
301+
# Function to extract release notes from PR body
302+
extract_release_notes() {
303+
local body="$1"
304+
# Try to extract content under "## Release Notes" heading
305+
local notes="$(printf '%s\n' "$body" \
300306
| awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
301-
else
307+
308+
# If no specific section found, use the entire body (up to first 500 chars for safety)
309+
if [ -z "$notes" ]; then
310+
notes="$(printf '%s\n' "$body" | head -c 500)"
311+
fi
312+
313+
printf '%s\n' "$notes"
314+
}
315+
316+
# Determine source of release notes
317+
NOTES=""
318+
319+
# Check if this was triggered by a push event (likely a merge)
320+
if [ "${{ github.event_name }}" = "push" ]; then
321+
echo "Fetching PR body for merged commit..."
322+
323+
# First, try to find PR number from commit message (most reliable)
324+
PR_FROM_COMMIT=$(git log -1 --pretty=%B | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
325+
326+
if [ -n "$PR_FROM_COMMIT" ]; then
327+
echo "Found PR #$PR_FROM_COMMIT from commit message"
328+
PR_BODY=$(gh pr view "$PR_FROM_COMMIT" --json body --jq '.body' 2>/dev/null || echo "")
329+
330+
if [ -n "$PR_BODY" ]; then
331+
NOTES="$(extract_release_notes "$PR_BODY")"
332+
fi
333+
else
334+
echo "No PR reference in commit message, searching by commit SHA..."
335+
# Get PRs that contain this commit (using GitHub API to search by commit)
336+
PR_NUMBERS=$(gh api \
337+
"repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
338+
--jq '.[].number' 2>/dev/null || echo "")
339+
340+
if [ -n "$PR_NUMBERS" ]; then
341+
# Take the first PR found (most recently merged)
342+
PR_NUMBER=$(echo "$PR_NUMBERS" | head -n 1)
343+
echo "Found PR #$PR_NUMBER associated with commit"
344+
345+
# Fetch the PR body
346+
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null || echo "")
347+
348+
if [ -n "$PR_BODY" ]; then
349+
NOTES="$(extract_release_notes "$PR_BODY")"
350+
fi
351+
else
352+
echo "No associated PR found for this commit"
353+
fi
354+
fi
355+
fi
356+
357+
# Fallback to recent commits if no PR body found
358+
if [ -z "$NOTES" ]; then
359+
echo "No PR body found, using recent commits..."
302360
NOTES="$(git log -n 5 --pretty=format:'- %s')"
303361
fi
362+
304363
# Fail if no notes extracted
305364
if [ -z "$NOTES" ]; then
306365
echo "Error: No release notes extracted" >&2
307366
exit 1
308367
fi
368+
309369
# Write header and notes to file
310370
{
311371
echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)"
312372
echo
313373
printf '%s\n' "$NOTES"
314374
} > RELEASE_NOTES.md
375+
376+
echo "Release notes prepared:"
377+
cat RELEASE_NOTES.md
378+
env:
379+
GH_TOKEN: ${{ github.token }}
380+
381+
- name: 📝 Send Release Notes to Changerawr
382+
if: ${{ matrix.platform == 'android' }}
383+
run: |
384+
set -eo pipefail
385+
386+
# Check if required secrets are set
387+
if [ -z "$CHANGERAWR_API_URL" ] || [ -z "$CHANGERAWR_API_KEY" ]; then
388+
echo "⚠️ Changerawr API credentials not configured, skipping release notes submission"
389+
exit 0
390+
fi
391+
392+
# Read release notes
393+
RELEASE_NOTES=$(cat RELEASE_NOTES.md)
394+
VERSION="7.${{ github.run_number }}"
395+
396+
# Prepare JSON payload
397+
PAYLOAD=$(jq -n \
398+
--arg version "$VERSION" \
399+
--arg notes "$RELEASE_NOTES" \
400+
--arg platform "android" \
401+
--arg buildNumber "${{ github.run_number }}" \
402+
--arg commitSha "${{ github.sha }}" \
403+
--arg buildUrl "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
404+
'{
405+
version: $version,
406+
releaseNotes: $notes,
407+
platform: $platform,
408+
buildNumber: $buildNumber,
409+
commitSha: $commitSha,
410+
buildUrl: $buildUrl,
411+
timestamp: now | todate
412+
}')
413+
414+
echo "Sending release notes to Changerawr..."
415+
416+
# Send to Changerawr API
417+
RESPONSE=$(curl -X POST "$CHANGERAWR_API_URL" \
418+
-H "Content-Type: application/json" \
419+
-H "Authorization: Bearer $CHANGERAWR_API_KEY" \
420+
-d "$PAYLOAD" \
421+
-w "\n%{http_code}" \
422+
-s)
423+
424+
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
425+
RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d')
426+
427+
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
428+
echo "✅ Successfully sent release notes to Changerawr (HTTP $HTTP_CODE)"
429+
echo "Response: $RESPONSE_BODY"
430+
else
431+
echo "⚠️ Failed to send release notes to Changerawr (HTTP $HTTP_CODE)"
432+
echo "Response: $RESPONSE_BODY"
433+
# Don't fail the build, just warn
434+
fi
315435
316-
- name: 📦 Create Release
436+
- name: 📦 Create Release
317437
if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }}
318438
uses: ncipollo/release-action@v1
319439
with:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ yarn-error.log
2424
/build
2525
/automatic-build
2626
google-services.json
27+
GoogleService-Info.plist
2728
credentials.json
2829
Gemfile.lock
2930
Gemfile

__mocks__/react-native-ble-plx.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ export class BleManager {
4747
private static mockDevices: Device[] = [];
4848
private static stateListener: ((state: State) => void) | null = null;
4949

50-
constructor() {}
51-
5250
static setMockState(state: State) {
5351
this.mockState = state;
5452
if (this.stateListener) {

app.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
4444
supportsTablet: true,
4545
bundleIdentifier: Env.BUNDLE_ID,
4646
requireFullScreen: true,
47+
googleServicesFile: 'GoogleService-Info.plist',
4748
infoPlist: {
4849
UIBackgroundModes: ['remote-notification', 'audio', 'bluetooth-central', 'voip'],
4950
ITSAppUsesNonExemptEncryption: false,
@@ -214,6 +215,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
214215
},
215216
ios: {
216217
deploymentTarget: '18.1',
218+
useFrameworks: 'static',
217219
},
218220
},
219221
],
@@ -262,6 +264,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
262264
'@livekit/react-native-expo-plugin',
263265
'@config-plugins/react-native-webrtc',
264266
'@config-plugins/react-native-callkeep',
267+
'@react-native-firebase/app',
265268
'./customGradle.plugin.js',
266269
'./customManifest.plugin.js',
267270
['app-icon-badge', appIconBadgeConfig],

eas.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@
8080
"EXPO_NO_DOTENV": "1"
8181
}
8282
},
83+
"dev-sim": {
84+
"developmentClient": false,
85+
"yarn": "1.22.22",
86+
"ios": {
87+
"simulator": true,
88+
"image": "latest"
89+
},
90+
"android": {
91+
"buildType": "apk",
92+
"image": "latest"
93+
},
94+
"env": {
95+
"APP_ENV": "development",
96+
"EXPO_NO_DOTENV": "1"
97+
}
98+
},
8399
"simulator": {
84100
"yarn": "1.22.22",
85101
"ios": {

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
"@notifee/react-native": "^9.1.8",
9292
"@novu/react-native": "~2.6.6",
9393
"@react-native-community/netinfo": "^11.4.1",
94+
"@react-native-firebase/analytics": "^23.5.0",
95+
"@react-native-firebase/app": "^23.5.0",
96+
"@react-native-firebase/messaging": "^23.5.0",
9497
"@rnmapbox/maps": "10.1.42-rc.0",
9598
"@semantic-release/git": "^10.0.1",
9699
"@sentry/react-native": "~6.14.0",
@@ -122,7 +125,7 @@
122125
"expo-localization": "~16.1.6",
123126
"expo-location": "~18.1.6",
124127
"expo-navigation-bar": "~4.2.8",
125-
"expo-notifications": "~0.31.4",
128+
"expo-notifications": "0.28.3",
126129
"expo-router": "~5.1.7",
127130
"expo-screen-orientation": "~8.1.7",
128131
"expo-sharing": "~13.1.5",

src/components/calls/dispatch-selection-modal.tsx

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CheckIcon, SearchIcon, UsersIcon, X } from 'lucide-react-native';
22
import { useColorScheme } from 'nativewind';
3-
import React, { useEffect, useMemo } from 'react';
3+
import React, { useEffect } from 'react';
44
import { useTranslation } from 'react-i18next';
55
import { ScrollView, TouchableOpacity, View } from 'react-native';
66

@@ -27,7 +27,7 @@ export const DispatchSelectionModal: React.FC<DispatchSelectionModalProps> = ({
2727
const { data, selection, isLoading, error, searchQuery, fetchDispatchData, setSelection, toggleEveryone, toggleUser, toggleGroup, toggleRole, toggleUnit, setSearchQuery, clearSelection, getFilteredData } =
2828
useDispatchStore();
2929

30-
const filteredData = useMemo(() => getFilteredData(), [data, searchQuery]);
30+
const filteredData = getFilteredData();
3131

3232
useEffect(() => {
3333
if (isVisible) {
@@ -121,9 +121,8 @@ export const DispatchSelectionModal: React.FC<DispatchSelectionModalProps> = ({
121121
<TouchableOpacity onPress={() => toggleUser(user.Id)}>
122122
<HStack className="items-center space-x-3">
123123
<Box
124-
className={`size-5 items-center justify-center rounded border-2 ${
125-
selection.users.includes(user.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
126-
}`}
124+
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'
125+
}`}
127126
>
128127
{selection.users.includes(user.Id) && <CheckIcon size={12} className="text-white" />}
129128
</Box>
@@ -148,9 +147,8 @@ export const DispatchSelectionModal: React.FC<DispatchSelectionModalProps> = ({
148147
<TouchableOpacity onPress={() => toggleGroup(group.Id)}>
149148
<HStack className="items-center space-x-3">
150149
<Box
151-
className={`size-5 items-center justify-center rounded border-2 ${
152-
selection.groups.includes(group.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
153-
}`}
150+
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'
151+
}`}
154152
>
155153
{selection.groups.includes(group.Id) && <CheckIcon size={12} className="text-white" />}
156154
</Box>
@@ -175,9 +173,8 @@ export const DispatchSelectionModal: React.FC<DispatchSelectionModalProps> = ({
175173
<TouchableOpacity onPress={() => toggleRole(role.Id)}>
176174
<HStack className="items-center space-x-3">
177175
<Box
178-
className={`size-5 items-center justify-center rounded border-2 ${
179-
selection.roles.includes(role.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
180-
}`}
176+
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'
177+
}`}
181178
>
182179
{selection.roles.includes(role.Id) && <CheckIcon size={12} className="text-white" />}
183180
</Box>
@@ -202,9 +199,8 @@ export const DispatchSelectionModal: React.FC<DispatchSelectionModalProps> = ({
202199
<TouchableOpacity onPress={() => toggleUnit(unit.Id)}>
203200
<HStack className="items-center space-x-3">
204201
<Box
205-
className={`size-5 items-center justify-center rounded border-2 ${
206-
selection.units.includes(unit.Id) ? 'border-blue-500 bg-blue-500' : colorScheme === 'dark' ? 'border-neutral-600' : 'border-neutral-300'
207-
}`}
202+
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'
203+
}`}
208204
>
209205
{selection.units.includes(unit.Id) && <CheckIcon size={12} className="text-white" />}
210206
</Box>

0 commit comments

Comments
 (0)