Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5098f26
chore: remove obsolete webview patch
Aug 25, 2025
bd531f2
feat(mobile): implement static location sharing
Sep 1, 2025
c91c399
feat(mobile): implement static location sharing
Sep 8, 2025
7c01380
feat(mobile): implement live location sharing
Sep 22, 2025
06de63b
feat(mobile): implement live location sharing
Sep 22, 2025
f985d4d
feat(mobile): implement live location sharing
Sep 23, 2025
5057700
Merge remote-tracking branch 'origin/develop' into new/location-sharing
Sep 24, 2025
1b35e25
feat: static & live location sharing (draft)
Sep 25, 2025
72dc234
action: organized translations
yiweigao0226 Sep 25, 2025
5450781
feat: static & live location sharing (draft)
Sep 29, 2025
6f44e91
action: organized translations
yiweigao0226 Sep 29, 2025
a991844
feat: static & live location sharing (draft)
Sep 29, 2025
db4f0f3
feat: static & live location sharing (draft)
Sep 29, 2025
3ef9f97
action: organized translations
yiweigao0226 Sep 29, 2025
f59c26d
feat: static & live location sharing (draft)
Sep 30, 2025
fdd3834
feat: static & live location sharing (draft)
Sep 30, 2025
2152119
feat: static & live location sharing (draft)
yiweigao0226 Sep 30, 2025
c4e2514
password show/hide
yiweigao0226 Apr 1, 2018
f4b2aec
[FIX] Bottom border style on DirectoryView (#1963)
yiweigao0226 Apr 1, 2020
bd47d05
feat: static & live location sharing (draft)
yiweigao0226 Oct 3, 2025
366cd18
feat: static & live location sharing (draft)
yiweigao0226 Oct 13, 2025
23ae666
Merge branch 'develop' into new/location-sharing
yiweigao0226 Oct 13, 2025
7cc84af
Merge branch 'RocketChat:develop' into new/location-sharing
yiweigao0226 Oct 14, 2025
43c0adc
feat: static & live location sharing (draft)
yiweigao0226 Oct 14, 2025
8625c64
Merge branch 'develop' into new/location-sharing
yiweigao0226 Oct 20, 2025
8712efc
Merge branch 'develop' into new/location-sharing
yiweigao0226 Oct 22, 2025
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
10 changes: 10 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@
<!-- android 13 media permission -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<!-- Location -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<!-- If you stream/live share or track in background -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
Comment on lines +27 to +28
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

FOREGROUND_SERVICE_LOCATION requires service declaration in manifest.

The FOREGROUND_SERVICE_LOCATION permission requires a corresponding <service> declaration with android:foregroundServiceType="location" in the manifest. Without this declaration, the permission is ineffective and location access from a foreground service will fail.

Add a service declaration if live location tracking uses a foreground service:

<application>
  ...
  <service
    android:name=".LocationService"
    android:foregroundServiceType="location"
    android:exported="false" />
  ...
</application>

If the app uses Expo or a library that handles this automatically, verify the service is properly configured.

#!/bin/bash
# Description: Check for service declarations and location service implementation

echo "Checking for service declarations in AndroidManifest..."
rg -n 'android:foregroundServiceType="location"' android/

echo "Checking for location service implementation..."
fd -e java -e kt LocationService android/app/src/
🤖 Prompt for AI Agents
In android/app/src/main/AndroidManifest.xml around lines 27-28, the
FOREGROUND_SERVICE_LOCATION permission is declared but there's no corresponding
<service> entry with android:foregroundServiceType="location", so foreground
location access will fail; add a service declaration inside the <application>
(e.g., a LocationService with android:foregroundServiceType="location" and
android:exported=\"false\") or ensure your Expo/library-provided service is
configured to expose that attribute, then verify by searching the manifest for
android:foregroundServiceType="location" and checking your LocationService
implementation files.

<!-- Optional: only if you truly need background updates on Android 10+ -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
Comment on lines +29 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Background location permission requires strong justification.

The ACCESS_BACKGROUND_LOCATION permission is highly sensitive and triggers additional user prompts on Android 10+. Google Play requires apps using this permission to justify its necessity and may require special declaration forms.

Verify that:

  1. The PR truly requires background location access (the PR description mentions "sessions continue when modal is minimized" but doesn't clearly state background tracking is needed when the app is not visible).
  2. If background access is only needed for foreground services (while the app is visible but the modal is minimized), the FOREGROUND_SERVICE_LOCATION permission alone may suffice.
  3. The app's privacy policy is updated to explain background location usage.
  4. The runtime permission flow properly explains why background access is requested (required by Play Store policies).

Consider whether this permission can be removed if the feature only needs location while the app is in the foreground or in a foreground service state.


🏁 Script executed:

#!/bin/bash
# Description: Check if background location subscription is actually used in the codebase

echo "Searching for background location configuration or task registration..."
rg -n --type typescript --type javascript -C3 'Background|TaskManager|startLocationUpdates.*background' app/

Length of output: 106


🏁 Script executed:

#!/bin/bash
echo "Searching for background location usage in codebase..."
rg -n --ignore-case -C3 'backgroundLocation' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' .
rg -n --ignore-case -C3 'startLocationUpdates' .
rg -n --ignore-case -C3 'registerTask' .
rg -n --ignore-case -C3 'TaskManager' .

Length of output: 54


🏁 Script executed:

#!/bin/bash
sed -n '1,200p' android/app/src/main/AndroidManifest.xml

Length of output: 5160


Remove unused ACCESS_BACKGROUND_LOCATION permission
No background‐location tasks (e.g. via TaskManager or startLocationUpdates) are registered anywhere in the codebase—drop <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/> unless you’re actually implementing background updates.

🤖 Prompt for AI Agents
In android/app/src/main/AndroidManifest.xml around lines 29-30, remove the
unused ACCESS_BACKGROUND_LOCATION permission entry; since the codebase does not
register any background location tasks, delete the <uses-permission
android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> line from the
manifest (or guard it behind a build flag only when background updates are
implemented) to eliminate unnecessary permission requests.


<application
android:name="chat.rocket.reactnative.MainApplication"
android:allowBackup="false"
Expand Down
9 changes: 8 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
{
"name": "RocketChatRN"
"name": "RocketChatRN",
"plugins": ["expo-web-browser"],
"expo": {
"extra": {
"GOOGLE_MAPS_API_KEY": "",
"OSM_API_KEY": ""
}
}
Comment on lines +4 to +9
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure runtime handling of empty API keys.

The GOOGLE_MAPS_API_KEY and OSM_API_KEY are initialized as empty strings, which is appropriate for an open-source repository. However, verify that the application gracefully handles missing API keys at runtime (e.g., shows a configuration prompt, disables features, or provides clear error messages).


🏁 Script executed:

#!/bin/bash
# Description: Check how the app handles empty API keys in map provider logic

rg -nP --type=ts --type=tsx -C3 'GOOGLE_MAPS_API_KEY|OSM_API_KEY' -g '!node_modules' -g '!*.json'

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for API key references across code
rg -n 'GOOGLE_MAPS_API_KEY|OSM_API_KEY' --glob '**/*.{ts,tsx,js,jsx}' --glob '!node_modules/**'

Length of output: 1264


🏁 Script executed:

#!/bin/bash
rg -n "import .* from 'expo-constants'" -C3 --glob "**/*.{ts,tsx,js,jsx}" --glob "!node_modules/**"

Length of output: 0


Implement runtime loading and graceful fallback for map API keys
I didn’t find any code consuming the GOOGLE_MAPS_API_KEY/OSM_API_KEY values from your app.json extras. Import Constants from expo-constants, read Constants.expoConfig.extra?.GOOGLE_MAPS_API_KEY (or Constants.manifest.extra for SDK < 48), merge with your useUserPreferences values, and disable map features or prompt for key entry when both are empty.

}
205 changes: 193 additions & 12 deletions app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,19 @@
import { useAppSelector, usePermissions } from '../../../../lib/hooks';
import { useCanUploadFile, useChooseMedia } from '../../hooks';
import { useRoomContext } from '../../../../views/RoomView/context';
import { Platform, PermissionsAndroid, InteractionManager } from 'react-native';

Check failure on line 12 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

There should be at least one empty line between import groups

Check failure on line 12 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

There should be at least one empty line between import groups
import * as Location from 'expo-location';

Check failure on line 13 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

`react-native` import should occur before import of `../../../../lib/database/services/Subscription`

Check failure on line 13 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

`react-native` import should occur before import of `../../../../lib/database/services/Subscription`
import { showErrorAlert } from '../../../../lib/methods/helpers';

Check failure on line 14 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

`expo-location` import should occur before import of `../../../../lib/database/services/Subscription`

Check failure on line 14 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

There should be at least one empty line between import groups

Check failure on line 14 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

`expo-location` import should occur before import of `../../../../lib/database/services/Subscription`

Check failure on line 14 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

There should be at least one empty line between import groups
import { getCurrentPositionOnce } from '../../../../views/LocationShare/services/staticLocation';
import { MapProviderName } from '../../../../views/LocationShare/services/mapProviders';
import { useUserPreferences } from '../../../../lib/methods';
import {

Check failure on line 18 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Missing file extension for "../../../../lib/methods"

Check failure on line 18 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Unable to resolve path to module '../../../../lib/methods'

Check failure on line 18 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Missing file extension for "../../../../lib/methods"

Check failure on line 18 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Unable to resolve path to module '../../../../lib/methods'
MAP_PROVIDER_PREFERENCE_KEY,
GOOGLE_MAPS_API_KEY_PREFERENCE_KEY,
OSM_API_KEY_PREFERENCE_KEY,
MAP_PROVIDER_DEFAULT
} from '../../../../lib/constants';

Check failure on line 24 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Missing file extension for "../../../../lib/constants"

Check failure on line 24 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Unable to resolve path to module '../../../../lib/constants'

Check failure on line 24 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Missing file extension for "../../../../lib/constants"

Check failure on line 24 in app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

View workflow job for this annotation

GitHub Actions / ESLint and Test / run-eslint-and-test

Unable to resolve path to module '../../../../lib/constants'
export const ActionsButton = () => {
const { rid, tmid, t } = useRoomContext();
const { closeEmojiKeyboardAndAction } = useContext(MessageInnerContext);
Expand All @@ -22,6 +34,30 @@
});
const { showActionSheet, hideActionSheet } = useActionSheet();
const isMasterDetail = useAppSelector(state => state.app.isMasterDetail);
const userId = useAppSelector(state => state.login.user.id);

const [mapProvider] = useUserPreferences<MapProviderName>(`${MAP_PROVIDER_PREFERENCE_KEY}_${userId}`, MAP_PROVIDER_DEFAULT);
const [googleApiKey] = useUserPreferences<string>(`${GOOGLE_MAPS_API_KEY_PREFERENCE_KEY}_${userId}`, '');
const [osmApiKey] = useUserPreferences<string>(`${OSM_API_KEY_PREFERENCE_KEY}_${userId}`, '');

// --- Sheet transition helpers ---
const sheetBusyRef = React.useRef(false);
/** Safely close the current ActionSheet and then run `fn` (open next sheet) */
const openSheetSafely = (fn: () => void, delayMs = 350) => {
if (sheetBusyRef.current) return;
sheetBusyRef.current = true;

hideActionSheet();
InteractionManager.runAfterInteractions(() => {
setTimeout(() => {
try {
fn();
} finally {
sheetBusyRef.current = false;
}
}, delayMs);
});
};

const createDiscussion = async () => {
if (!rid) return;
Expand All @@ -34,62 +70,207 @@
}
};

const openCurrentPreview = async (provider: MapProviderName) => {
try {
if (!rid) {
showErrorAlert(I18n.t('Room_not_available'), I18n.t('Oops'));
return;
}

if (Platform.OS === 'android') {
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION);
if (granted !== PermissionsAndroid.RESULTS.GRANTED) {
showErrorAlert(I18n.t('Location_permission_required'), I18n.t('Oops'));
return;
}
} else {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
showErrorAlert(I18n.t('Location_permission_required'), I18n.t('Oops'));
return;
}
}

const coords = await getCurrentPositionOnce();

const params = {
rid,
tmid,
provider,
coords,
googleKey: provider === 'google' ? googleApiKey : undefined,
osmKey: provider === 'osm' ? osmApiKey : undefined
};

InteractionManager.runAfterInteractions(() => {
if (isMasterDetail) {
Navigation.navigate('ModalStackNavigator', { screen: 'LocationPreviewModal', params });
} else {
Navigation.navigate('LocationPreviewModal', params);
}
});
} catch (e: any) {
showErrorAlert(e?.message || I18n.t('Could_not_get_location'), I18n.t('Oops'));
}
};

const openLivePreview = async (provider: MapProviderName) => {
try {
if (!rid) {
showErrorAlert(I18n.t('Room_not_available'), I18n.t('Oops'));
return;
}

if (Platform.OS === 'android') {
const res = await PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION
]);
const fine = res[PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION];
const coarse = res[PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION];
if (fine !== PermissionsAndroid.RESULTS.GRANTED && coarse !== PermissionsAndroid.RESULTS.GRANTED) {
throw new Error(I18n.t('Permission_denied'));
}
} else {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
throw new Error(I18n.t('Location_permission_required'));
}
}

const params = {
rid,
tmid,
provider,
googleKey: provider === 'google' ? googleApiKey : undefined,
osmKey: provider === 'osm' ? osmApiKey : undefined
};

// Defer navigation until after sheets/animations are done
InteractionManager.runAfterInteractions(() => {
// @ts-ignore
if (isMasterDetail) {
Navigation.navigate('ModalStackNavigator', { screen: 'LiveLocationPreviewModal', params });
} else {
Navigation.navigate('LiveLocationPreviewModal', params);
}
});
} catch (e: any) {
showErrorAlert(e?.message || I18n.t('Could_not_get_location'), I18n.t('Oops'));
}
};
Comment on lines +120 to +164
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove @ts-ignore by updating navigation types.

The live location preview flow is well-structured, but the @ts-ignore on line 151 indicates missing type definitions for LiveLocationPreviewModal in the navigation param list.

Ensure that the navigation types (e.g., in app/stacks/types.ts or app/stacks/MasterDetailStack/types.ts) include:

LiveLocationPreviewModal: {
  rid: string;
  tmid?: string;
  provider: MapProviderName;
  googleKey?: string;
  osmKey?: string;
  liveLocationId?: string;
  ownerName?: string;
  isTracking?: boolean;
};

This will eliminate the need for @ts-ignore and provide type safety.


const openModeSheetForProvider = (provider: MapProviderName) => {
const modeOptions: TActionSheetOptionsItem[] = [
{
title: I18n.t('Share_current_location'),
icon: 'pin-map',
onPress: () => {
// sheet -> navigation: close current sheet safely, then start flow
openSheetSafely(() => openCurrentPreview(provider));
}
},
{
title: I18n.t('Start_live_location'),
icon: 'live',
onPress: () => {
// sheet -> navigation: close current sheet safely, then start flow
openSheetSafely(() => openLivePreview(provider));
}
}
];
showActionSheet({ options: modeOptions });
};

const onPress = () => {
const options: TActionSheetOptionsItem[] = [];

if (t === 'l' && permissionToViewCannedResponses) {
options.push({
title: I18n.t('Canned_Responses'),
icon: 'canned-response',
onPress: () => Navigation.navigate('CannedResponsesListView', { rid })
onPress: () => {
hideActionSheet();
InteractionManager.runAfterInteractions(() => {
Navigation.navigate('CannedResponsesListView', { rid });
});
}
});
}

if (permissionToUpload) {
options.push(
{
title: I18n.t('Take_a_photo'),
icon: 'camera-photo',
onPress: () => {
hideActionSheet();
// This is necessary because the action sheet does not close properly on Android
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
takePhoto();
}, 250);
});
}
},
{
title: I18n.t('Take_a_video'),
icon: 'camera',
onPress: () => {
hideActionSheet();
// This is necessary because the action sheet does not close properly on Android
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
takeVideo();
}, 250);
});
}
},
{
title: I18n.t('Choose_from_library'),
icon: 'image',
onPress: () => {
hideActionSheet();
// This is necessary because the action sheet does not close properly on Android
setTimeout(() => {
InteractionManager.runAfterInteractions(() => {
chooseFromLibrary();
}, 250);
});
}
},
{
title: I18n.t('Choose_file'),
icon: 'attach',
onPress: () => chooseFile()
onPress: () => {
hideActionSheet();
InteractionManager.runAfterInteractions(() => {
chooseFile();
});
}
}
);
}

options.push({
title: I18n.t('Create_Discussion'),
icon: 'discussions',
onPress: () => createDiscussion()
onPress: () => {
hideActionSheet();
InteractionManager.runAfterInteractions(() => {
createDiscussion();
});
}
});

options.push({
title: I18n.t('Share_Location'),
icon: 'pin-map',
onPress: () => {
// Check if the user has configured API keys for their preferred provider
const needsApiKey = (mapProvider === 'google' && !googleApiKey) || (mapProvider === 'osm' && !osmApiKey);

if (needsApiKey) {
showErrorAlert(
I18n.t('API_key_required', { provider: mapProvider === 'google' ? 'Google Maps' : 'OpenStreetMap' }),
I18n.t('Please_configure_API_key_in_settings')
);
return;
}

openSheetSafely(() => openModeSheetForProvider(mapProvider));
}
});

closeEmojiKeyboardAndAction(showActionSheet, { options });
Expand Down
Loading
Loading