Skip to content

feat: static & live location sharing (draft) #6694

Open
yiweigao0226 wants to merge 46 commits intoRocketChat:developfrom
yiweigao0226:new/location-sharing
Open

feat: static & live location sharing (draft) #6694
yiweigao0226 wants to merge 46 commits intoRocketChat:developfrom
yiweigao0226:new/location-sharing

Conversation

@yiweigao0226
Copy link

@yiweigao0226 yiweigao0226 commented Sep 30, 2025

Description

This PR introduces live location sharing to Rocket.Chat React Native, allowing users to share and track real-time locations in chat. It supports both one-time static sharing and continuous live updates every 10 seconds. Users can minimize the modal while keeping tracking active, with a persistent status bar indicating the session. The design clearly separates owners, who can start or stop sharing, from viewers, who can only view. The feature also includes deep linking, session persistence across minimization, and a polished, intuitive UI.

Proposed changes

  • Keep the live session running when the modal is minimized; reopen it from the status bar or the chat link.
  • Add a global Live Location status bar with a pulsing indicator and “tap to reopen.”
  • Make the chat message’s “View Live Location” link point to the exact running session (room/thread-aware).
  • Handle live-location deep links on cold start and while the app is open; resume if active, otherwise open a viewer.
  • Show Stop Sharing only to the session owner and enforce that rule in the stop flow.
  • Unify map behavior across iOS/Android and Google/OSM; consistent Open in Maps.
  • Improve the preview: static map image, coordinates, accuracy, last-updated timestamp; clearer logs.
  • Use expo-location for location retrieval with a last-known fast path plus a safe timeout.
  • Minor UI tidy-ups (copy, layout, testIDs).

Issue(s)

How to test or reproduce

  1. Checkout and run the server-side code from the branch yiweigao0226:new/location-sharing in https://github.com/RocketChat/Rocket.Chat.
  2. The corresponding pull request can be found here: feat: static & live location sharing (draft) Rocket.Chat#36746.
  3. Start the server locally and ensure it is running at http://localhost:3000.
  4. Use this local server as the backend for the mobile app workspace to conduct functional testing of the implementation.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • New Features

    • Share current location, start/stop live-location sessions with map previews, viewer modal, in-chat live-location cards/attachments, and a live-location status bar.
  • Settings

    • New Location Preferences: choose default map provider (OpenStreetMap or Google Maps) and supply provider API keys.
  • Localization

    • Many new UI strings for location flows, providers, errors, and API key guidance.
  • Chores

    • Platform location permissions, foreground service support, and location dependency added.

@CLAassistant
Copy link

CLAassistant commented Sep 30, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
0 out of 2 committers have signed the CLA.

❌ yiwei gao
❌ yiweigao0226


yiwei gao seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 30, 2025

Walkthrough

Adds full location features: permissions and foreground service on Android/iOS, map provider utilities, static/current/live location acquisition and live-tracking services, REST endpoints, UI screens/components for sharing/viewing locations, message/attachment integrations, localization, and navigation/type updates.

Changes

Cohort / File(s) Summary
Android manifest & service
android/app/src/main/AndroidManifest.xml, android/app/src/main/java/chat/rocket/reactnative/LocationService.kt
Add coarse/fine location and foreground-service permissions; declare foreground LocationService and add minimal service implementation.
iOS config & build
ios/.ruby-version, ios/RocketChatRN/Info.plist, ios/NotificationService/Info.plist, ios/RocketChatRN.xcodeproj/project.pbxproj
Add Ruby version file; add location usage descriptions, background location mode, comgooglemaps scheme; relocate IS_OFFICIAL plist key; adjust some project path literals.
App config & deps
app.json, package.json
Add plugins: ["expo-web-browser"] in app.json; add expo-location dependency.
Constants
app/lib/constants/keys.ts
Add MAP_PROVIDER and API key preference constants and default provider.
Localization
app/i18n/locales/en.json
Add many localization keys for location, maps, live-location, errors, and UI strings.
Navigation & types
app/stacks/types.ts, app/stacks/InsideStack.tsx, app/stacks/MasterDetailStack/index.tsx, app/stacks/MasterDetailStack/types.ts
Register LocationPreferencesView and modal screens (LocationPreviewModal, LiveLocationPreviewModal, LiveLocationViewerModal); add corresponding param typings.
REST API & definitions
app/definitions/rest/v1/liveLocation.ts, app/definitions/rest/v1/index.ts, app/lib/services/restApi.ts
Add liveLocation endpoint types and client wrappers: liveLocation.start, liveLocation.update, liveLocation.stop, liveLocation.get.
Attachment & message models
app/definitions/IAttachment.ts, app/containers/message/interfaces.ts
Add optional live object to IAttachment; extend IMessageAttachments with style?, isReply?, id, rid.
Messaging send flow
app/lib/methods/sendMessage.ts
Extend sendMessage signature to accept optional attachments and persist attachments into created message records.
Location services (core)
app/views/LocationShare/services/staticLocation.ts, app/views/LocationShare/services/liveLocation.ts, app/views/LocationShare/services/liveLocationApi.ts
Add getCurrentPositionOnce, LiveLocationTracker (foreground tracking, watch/update/stop), LiveLocationApi and mobile↔server coordinate converters.
Map provider utilities
app/views/LocationShare/services/mapProviders.ts
Add static map URL generation, platform-aware deep links, provider labels and attribution.
Deep-link & ended tracking
app/views/LocationShare/services/handleLiveLocationUrl.ts
Add live-link parsing/handling, ended-session persistence/listeners, and navigation logic for live-location URLs.
UI — sharing & viewing
app/views/LocationShare/LocationPreviewModal.tsx, app/views/LocationShare/LiveLocationPreviewModal.tsx, app/views/LocationShare/LiveLocationViewerModal.tsx
Add modals/screens for static location preview, live-location session management (start/stop/minimize), and viewing live locations; expose several public helpers/APIs.
UI — status & prefs
app/views/LocationPreferencesView/index.tsx, app/views/LocationPreferencesView/ListPicker.tsx, app/views/LocationShare/LiveLocationStatusBar.tsx
Add LocationPreferences screen and provider picker; add LiveLocationStatusBar with minimized/active handling and deep-link listener.
Message UI integration
app/containers/message/Components/CurrentLocationCard.tsx, app/containers/message/Components/LiveLocationCard.tsx, app/containers/message/Components/Attachments/LiveLocationAttachment.tsx, app/containers/message/Components/Attachments/Attachments.tsx, app/containers/message/Components/Attachments/Reply.tsx, app/containers/message/Content.tsx, app/containers/message/Message.tsx
Add current-location and live-location card/attachment components; detect/render live/location attachments; forward id/rid to Attachments; adjust Content/Message rendering.
Composer & action flows
app/containers/MessageComposer/components/Buttons/ActionsButton.tsx
Integrate provider-aware location share flows, permission checks, sheet-safety helpers, and deferred navigation via InteractionManager.
Room & preferences integration
app/views/RoomView/index.tsx, app/views/UserPreferencesView/index.tsx
Render LiveLocationStatusBar in RoomView; add Location Preferences entry in UserPreferences.
Tests
app/containers/message/Components/CurrentLocationCard.test.ts, app/containers/message/Components/LiveLocationCard.test.tsx
Add tests for coordinate parsing and LiveLocationCard behavior (status, navigation, events).

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant UI as Composer UI
    participant P as Permissions
    participant Loc as StaticLocation
    participant Map as MapProvider
    participant Msg as Messaging / REST

    U->>UI: Tap "Share Location"
    UI->>P: Request location permission
    alt permission granted
        P-->>UI: granted
        UI->>Loc: getCurrentPositionOnce()
        Loc-->>UI: coords
        UI->>Map: build staticMapUrl / preview
        Map-->>UI: preview image
        U->>UI: Confirm share
        UI->>Msg: sendMessage(..., attachments)
        Msg->>Msg: call liveLocation.start (if live)
        Msg-->>UI: success
    else permission denied
        P-->>UI: denied
        UI-->>U: show permission error
    end
Loading
sequenceDiagram
    participant Owner as Sender
    participant Tracker as LiveLocationTracker
    participant Pos as watchPositionAsync
    participant API as LiveLocation API
    participant Viewer as Receiver

    Owner->>Tracker: startTracking()
    Tracker->>API: liveLocation.start(rid, initial)
    API-->>Tracker: msgId (active)
    Tracker->>Pos: subscribe to position updates
    loop every 10s
        Pos-->>API: liveLocation.update(msgId, coords)
        API-->>Tracker: ok
    end
    Viewer->>API: liveLocation.get(msgId)
    API-->>Viewer: current coords / status
    Owner->>Tracker: stopTracking()
    Tracker->>API: liveLocation.stop(msgId, finalCoords)
    API-->>Tracker: stopped
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas needing extra attention:

  • app/views/LocationShare/services/liveLocation.ts — tracker lifecycle, permission fallbacks, watch/interval cleanup, server vs local fallback.
  • app/views/LocationShare/LiveLocationPreviewModal.tsx & LiveLocationViewerModal.tsx — global state/listeners, polling and cleanup, navigation edge cases.
  • Message/attachment integration — IMessageAttachments changes, Attachments rendering branches, Message/Content rendering conditionals.
  • mapProviders.ts and deep-link builders — platform encodings and URL correctness.
  • handleLiveLocationUrl persistence/ended-session logic and concurrency with active tracker.

Possibly related PRs

Suggested reviewers

  • diegolmello
  • OtavioStasiak

"🐰 I hopped through maps and keys so bright,

Pushed pins and trackers into the night.
Prefs set, modals primed, and previews in view,
Live dots hop forward — off I chew! 🗺️✨"

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature being added: static and live location sharing. It is concise, specific, and directly reflects the primary change in the changeset.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4e7a86f and d458659.

📒 Files selected for processing (1)
  • app/i18n/locales/en.json (24 hunks)
🔇 Additional comments (2)
app/i18n/locales/en.json (2)

127-128: Looks good. Error message strings for "Cannot_View_Live_Location" are clear and user-friendly.


20-24: Comprehensive location sharing localization coverage with resolved past issue.

The duplicate key issue flagged in the previous review (old "Share_Loading" vs "Sharing_Loading") has been resolved—only "Sharing_Loading" now exists (line 864).

The new localization entries are well-structured with consistent naming (e.g., "Live_Location_", "OSM_", "Google_Maps_*"), proper interpolation placeholders ("{{provider}}", "{{lat}}, {{lon}}"), and comprehensive error handling. API instructions include actionable URLs (Google Cloud Console, LocationIQ). Terminal and UI strings use appropriate punctuation (ellipsis for "Sharing…", bullets for visual hierarchy).

Also applies to: 62-66, 172-172, 196-196, 199-201, 228-228, 332-338, 358-358, 398-400, 411-411, 422-422, 474-489, 495-495, 509-513, 525-528, 632-632, 661-661, 666-666, 669-671, 689-689, 699-699, 706-706, 857-864, 888-892, 897-897, 909-910, 980-980, 1022-1023


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 19

🧹 Nitpick comments (22)
ios/.ruby-version (1)

1-1: Update .ruby-version to Ruby 3.2.9

Ruby 3.2.2 has no reported vulnerabilities, but upgrading to the latest 3.2.x patch (3.2.9) ensures you benefit from all bug fixes and performance improvements.

app/views/LocationShare/services/staticLocation.ts (4)

35-35: Redundant nullish coalescing.

Line 35 uses accuracy ?? undefined, but if accuracy is already null or undefined, the result is undefined anyway. This is redundant.

Simplify to:

-accuracy: accuracy ?? undefined,
+accuracy,

39-41: Silent error swallowing in fast path.

The try-catch block silently swallows errors from getLastKnownPositionAsync without logging. This makes debugging difficult if the fast path fails unexpectedly.

Add minimal logging:

 } catch (e) {
-	// Use fresh location if cached fails
+	// Use fresh location if cached fails (expected in some cases)
+	log('getLastKnownPositionAsync failed, falling back to fresh location', e);
 }

55-55: Same redundant nullish coalescing.

Line 55 has the same redundant pattern as line 35.

-accuracy: accuracy ?? undefined,
+accuracy,

58-60: Redundant error re-throw.

The catch block on lines 58-60 catches and immediately re-throws the error without any transformation or logging. This adds no value.

Remove the try-catch wrapper around the entire function body, or add meaningful error handling:

-	} catch (error) {
-		throw error;
-	}
app/views/LocationPreferencesView/ListPicker.tsx (2)

37-37: Redundant ternary - simplify initialization.

The initialization value ? OPTIONS.find(option => option.value === value) : OPTIONS[0] is redundant because the find will return undefined if no match is found, and the useEffect on lines 39-42 will immediately correct it to OPTIONS[0].

Simplify:

-const [option, setOption] = useState(value ? OPTIONS.find(option => option.value === value) : OPTIONS[0]);
+const [option, setOption] = useState<typeof OPTIONS[0]>(OPTIONS.find(option => option.value === value) || OPTIONS[0]);

16-25: Hardcoded options - consider making configurable.

The OPTIONS array is hardcoded with only two providers. If the app needs to support additional map providers in the future or customize the available providers per deployment, this will require code changes.

Consider accepting options as a prop or importing from a shared configuration:

interface IListPickerProps {
	title: string;
	value: MapProviderName;
	onChangeValue: (value: MapProviderName) => void;
	testID?: string;
	options?: { label: string; value: MapProviderName }[];
}

const ListPicker = ({ title, value, onChangeValue, testID, options = DEFAULT_OPTIONS }: IListPickerProps) => {
	// ...
app/views/LocationPreferencesView/ApiKeyModal.tsx (3)

48-48: secureTextEntry hides API keys - consider trade-offs.

Using secureTextEntry for API keys prevents users from verifying what they typed. API keys are typically long alphanumeric strings that are error-prone to enter blind. Unlike passwords (which users memorize), API keys are usually copied from external sources.

Consider adding a "show/hide" toggle to let users verify their input:

const [isSecure, setIsSecure] = useState(true);

// In TextInput
secureTextEntry={isSecure}

// Add toggle button
<TouchableOpacity onPress={() => setIsSecure(!isSecure)}>
  <CustomIcon name={isSecure ? 'eye' : 'eye-off'} />
</TouchableOpacity>

25-27: Missing input validation on save.

The handleSave function only trims whitespace but doesn't validate whether the API key format is reasonable (e.g., minimum length, character set). Users could save empty strings or invalid keys.

Add basic validation:

const handleSave = () => {
	const trimmed = value.trim();
	if (trimmed.length === 0) {
		// Show error or disable save
		return;
	}
	onSave(trimmed);
};

Consider validating against known API key patterns for Google Maps (starts with certain prefixes, specific length) if feasible.


76-76: Hardcoded overlay color - use theme.

Line 76 hardcodes backgroundColor: 'rgba(0, 0, 0, 0.5)' instead of using the theme's backdrop color. This is inconsistent with the theming system.

Based on the relevant code snippet showing colors.backdropColor and colors.backdropOpacity exist in the theme:

-backgroundColor: 'rgba(0, 0, 0, 0.5)',
+backgroundColor: colors.backdropColor,
+opacity: colors.backdropOpacity,

Or apply the backdrop styling via the theme's predefined overlay values.

app/views/LocationShare/services/handleLiveLocationUrl.ts (2)

22-29: Log or handle AsyncStorage failures in saveEndedSet.

The empty catch block silently swallows storage errors. While non-critical for this feature, consider logging the error for debugging purposes or telemetry.

Apply this diff to add minimal error logging:

 	} catch (e) {
-		
+		console.warn('[handleLiveLocationUrl] Failed to persist ended IDs:', e);
 	}

46-84: Consider consistent return values for handleLiveLocationUrl.

The function returns true at line 72 when navigating on cold start, but implicitly returns undefined in other execution paths (lines 60, 77). For better consistency, consider either:

  1. Making the return type explicit (Promise<boolean> or Promise<void>)
  2. Returning a boolean in all paths to indicate whether the URL was handled

Otherwise, the logic is sound: proper validation, user-friendly alerts for ended sessions, and correct routing for active vs. cold-start scenarios.

Apply this diff to make returns consistent:

-		if (liveLocationId && (await isLiveLocationEnded(liveLocationId))) {
+		if (liveLocationId && (await isLiveLocationEnded(liveLocationId))) {
 			Alert.alert(I18n.t('Live_Location_Ended_Title'), I18n.t('Live_Location_Ended_Message'), [{ text: I18n.t('OK') }]);
-			return;
+			return false;
 		}

 		if (!isLiveLocationActive()) {
 			// Handle cold start - navigate to live location viewer
 			Navigation.navigate('LiveLocationPreviewModal', {
 				provider,
 				...(rid ? { rid } : {}),
 				...(tmid ? { tmid } : {}),
 				liveLocationId,
 				isTracking: false
 			});
 			return true;
 		}

 		const params = getCurrentLiveParams();
 		if (params?.liveLocationId && liveLocationId && params.liveLocationId !== liveLocationId) {
-			return;
+			return false;
 		}

 		reopenLiveLocationModal();
+		return true;
 	} catch (e) {
 		// Invalid URL format
+		return false;
 	}
app/views/LocationShare/services/liveLocation.ts (3)

1-2: Minor: Add empty line between import groups.

ESLint flagged the missing empty line between external and internal imports. While minor, following the linting rule improves consistency.

Apply this diff:

 import * as Location from 'expo-location';
+
 import { MapProviderName } from './mapProviders';

121-141: Consider omitting empty rid/tmid parameters from the URL.

Lines 131-132 set rid and tmid to empty strings when not provided. This results in URL query strings like rid=&tmid= which are unnecessary. Consider conditionally adding parameters only when they have values.

Apply this diff to omit empty parameters:

-	const params = new URLSearchParams({
-		liveLocationId,
-		rid: rid || '',
-		tmid: tmid || '',
-		provider,
-		action: 'reopen'
-	});
+	const params = new URLSearchParams({
+		liveLocationId,
+		provider,
+		action: 'reopen'
+	});
+	if (rid) params.set('rid', rid);
+	if (tmid) params.set('tmid', tmid);

143-149: Unused parameters in createLiveLocationStopMessage.

The function accepts _liveLocationId, _provider, and _lastCoords but doesn't use them. If these are reserved for future enhancement (e.g., showing final coordinates or session details in the stop message), consider adding a comment to clarify intent. Otherwise, they could be removed to reduce confusion.

app/i18n/locales/en.json (1)

322-322: Standardize capitalization of "API key" in user-facing strings.

There's an inconsistency in the capitalization of "API key":

  • Line 322: "Enter your OpenStreetMap API key" (lowercase "key")
  • Line 629: "OpenStreetMap API Key" (uppercase "Key")

For consistency across the UI, standardize to one form (typically lowercase "key" is more common in modern UX writing).

Apply this diff to standardize:

-  "OSM_API_Key": "OpenStreetMap API Key",
+  "OSM_API_Key": "OpenStreetMap API key",

Similarly for Google Maps:

-  "Google_Maps_API_Key": "Google Maps API Key",
+  "Google_Maps_API_Key": "Google Maps API key",

Also applies to: 629-629

app/views/LocationShare/LocationPreviewModal.tsx (1)

13-13: Consider removing duplicate Coords type definition.

The Coords type is already defined in app/views/LocationShare/services/mapProviders.ts (line 11) and app/views/LocationShare/services/staticLocation.ts (line 2). Consider importing it instead to maintain a single source of truth.

-type Coords = { latitude: number; longitude: number; accuracy?: number; timestamp?: number };
+import { Coords } from './services/staticLocation';
app/views/LocationShare/LiveLocationStatusBar.tsx (2)

128-144: Consider safe area insets for the status bar positioning.

The status bar uses absolute positioning with top: 0, which may overlap with the device's status bar or notch on iOS/Android. Consider using SafeAreaView or useSafeAreaInsets to respect device safe areas.

+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
 export default function LiveLocationStatusBar({ onPress }: Props) {
+	const insets = useSafeAreaInsets();
 	// ... rest of component
 
 	return (
-		<View style={styles.container}>
+		<View style={[styles.container, { top: insets.top }]}>

52-66: Clarify deeplink listener comment
In app/views/LocationShare/LiveLocationStatusBar.tsx (line 52), replace “works around openLink.ts” with a note that this hook registers a separate listener for incoming rocketchat://live-location URLs—bypassing the centralized deep‐link parser in app/index.tsx (which doesn’t recognize live-location links)—so the component can handle live-location links directly.

app/views/LocationShare/services/mapProviders.ts (3)

55-75: Consider using template literal for query string.

The function correctly builds Google Static Maps URLs. However, using template literals instead of string concatenation would be more idiomatic and readable.

Apply this diff:

-	const qp =
-		`center=${latitude},${longitude}` +
-		`&zoom=${z}` +
-		`&size=${width}x${height}` +
-		`&scale=${sc}` +
-		`&maptype=${enc(type)}` +
-		`&markers=color:${enc(color)}|${latitude},${longitude}` +
-		(googleApiKey ? `&key=${enc(googleApiKey)}` : '');
+	const qp = `center=${latitude},${longitude}&zoom=${z}&size=${width}x${height}&scale=${sc}&maptype=${enc(type)}&markers=color:${enc(color)}|${latitude},${longitude}${googleApiKey ? `&key=${enc(googleApiKey)}` : ''}`;

Or for better readability across multiple lines:

-	const qp =
-		`center=${latitude},${longitude}` +
-		`&zoom=${z}` +
-		`&size=${width}x${height}` +
-		`&scale=${sc}` +
-		`&maptype=${enc(type)}` +
-		`&markers=color:${enc(color)}|${latitude},${longitude}` +
-		(googleApiKey ? `&key=${enc(googleApiKey)}` : '');
+	const qp = [
+		`center=${latitude},${longitude}`,
+		`zoom=${z}`,
+		`size=${width}x${height}`,
+		`scale=${sc}`,
+		`maptype=${enc(type)}`,
+		`markers=color:${enc(color)}|${latitude},${longitude}`,
+		googleApiKey ? `key=${enc(googleApiKey)}` : ''
+	].filter(Boolean).join('&');

77-93: Consider using template literal for query string.

Similar to buildGoogleStaticUrl, using a template literal or array join would improve readability.

Apply this diff:

-	const qp =
-		(osmApiKey ? `key=${enc(osmApiKey)}` : '') +
-		`&center=${latitude},${longitude}` +
-		`&zoom=${z}` +
-		`&size=${width}x${height}` +
-		`&markers=${enc(marker)}`;
+	const qp = [
+		osmApiKey ? `key=${enc(osmApiKey)}` : '',
+		`center=${latitude},${longitude}`,
+		`zoom=${z}`,
+		`size=${width}x${height}`,
+		`markers=${enc(marker)}`
+	].filter(Boolean).join('&');

119-191: Consider removing async from non-awaiting paths (optional).

The deep link logic is well-structured and platform-aware. The mapsDeepLink function is async to support iosGoogleLink, which awaits Linking.canOpenURL. While ESLint flags "no await" for some code paths, keeping the function async ensures a consistent API and allows for future async operations without breaking changes.

If you prefer to address the linter warning, you can refactor to return a Promise explicitly:

export function mapsDeepLink(provider: AnyProvider, coords: Coords): Promise<string> {
  if (Platform.OS === 'ios') {
    switch (provider) {
      case 'google':
        return iosGoogleLink(coords);
      case 'osm':
        return Promise.resolve(iosOsmLink(coords));
      case 'apple':
        return Promise.resolve(iosAppleLink(coords));
      default:
        return Promise.resolve(iosAppleLink(coords));
    }
  }

  // ANDROID
  switch (provider) {
    case 'google':
    case 'apple':
      return Promise.resolve(androidGoogleLikeLink(coords));
    case 'osm':
      return Promise.resolve(androidOsmLink(coords));
    default:
      return Promise.resolve(androidGoogleLikeLink(coords));
  }
}

However, the current async signature is acceptable for API consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ce36d1d and 2152119.

⛔ Files ignored due to path filters (2)
  • ios/Podfile.lock is excluded by !**/*.lock
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (28)
  • android/app/src/main/AndroidManifest.xml (1 hunks)
  • app.json (1 hunks)
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (3 hunks)
  • app/i18n/locales/en.json (17 hunks)
  • app/index.tsx (0 hunks)
  • app/lib/constants/keys.ts (1 hunks)
  • app/lib/methods/sendMessage.ts (4 hunks)
  • app/stacks/InsideStack.tsx (4 hunks)
  • app/stacks/MasterDetailStack/index.tsx (2 hunks)
  • app/stacks/MasterDetailStack/types.ts (1 hunks)
  • app/stacks/types.ts (2 hunks)
  • app/views/LocationPreferencesView/ApiKeyModal.tsx (1 hunks)
  • app/views/LocationPreferencesView/ListPicker.tsx (1 hunks)
  • app/views/LocationPreferencesView/index.tsx (1 hunks)
  • app/views/LocationShare/LiveLocationPreviewModal.tsx (1 hunks)
  • app/views/LocationShare/LiveLocationStatusBar.tsx (1 hunks)
  • app/views/LocationShare/LocationPreviewModal.tsx (1 hunks)
  • app/views/LocationShare/services/handleLiveLocationUrl.ts (1 hunks)
  • app/views/LocationShare/services/liveLocation.ts (1 hunks)
  • app/views/LocationShare/services/mapProviders.ts (1 hunks)
  • app/views/LocationShare/services/staticLocation.ts (1 hunks)
  • app/views/RoomView/index.tsx (4 hunks)
  • app/views/UserPreferencesView/index.tsx (1 hunks)
  • ios/.ruby-version (1 hunks)
  • ios/NotificationService/Info.plist (1 hunks)
  • ios/RocketChatRN.xcodeproj/project.pbxproj (4 hunks)
  • ios/RocketChatRN/Info.plist (4 hunks)
  • package.json (2 hunks)
💤 Files with no reviewable changes (1)
  • app/index.tsx
🧰 Additional context used
🧬 Code graph analysis (13)
app/stacks/InsideStack.tsx (2)
app/views/LocationShare/LocationPreviewModal.tsx (1)
  • LocationPreviewModal (24-136)
app/views/LocationShare/LiveLocationPreviewModal.tsx (1)
  • LiveLocationPreviewModal (72-365)
app/views/LocationPreferencesView/index.tsx (7)
app/stacks/types.ts (1)
  • ProfileStackParamList (190-203)
app/stacks/MasterDetailStack/types.ts (1)
  • MasterDetailInsideStackParamList (213-232)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/lib/constants/keys.ts (4)
  • MAP_PROVIDER_PREFERENCE_KEY (24-24)
  • MAP_PROVIDER_DEFAULT (27-27)
  • GOOGLE_MAPS_API_KEY_PREFERENCE_KEY (25-25)
  • OSM_API_KEY_PREFERENCE_KEY (26-26)
app/views/LocationShare/services/staticLocation.ts (1)
app/views/LocationShare/services/mapProviders.ts (1)
  • Coords (12-12)
app/views/LocationPreferencesView/ApiKeyModal.tsx (2)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/constants/colors.ts (1)
  • colors (280-302)
app/views/LocationShare/services/liveLocation.ts (1)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/views/LocationShare/services/mapProviders.ts (1)
app/views/LocationShare/services/staticLocation.ts (1)
  • Coords (3-3)
app/views/LocationShare/LiveLocationStatusBar.tsx (4)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/LocationShare/LiveLocationPreviewModal.tsx (6)
  • isLiveLocationActive (367-369)
  • addStatusChangeListener (55-57)
  • removeStatusChangeListener (58-60)
  • reopenLiveLocationModal (371-379)
  • getCurrentLiveParams (52-54)
  • stopGlobalLiveLocation (381-416)
app/views/LocationShare/services/handleLiveLocationUrl.ts (2)
  • isLiveMessageLink (42-44)
  • handleLiveLocationUrl (46-84)
app/views/LocationPreferencesView/ListPicker.tsx (3)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/containers/ActionSheet/Provider.tsx (2)
  • useActionSheet (39-39)
  • TActionSheetOptionsItem (7-15)
app/theme.tsx (1)
  • useTheme (29-29)
app/views/RoomView/index.tsx (1)
app/views/LocationShare/LiveLocationStatusBar.tsx (1)
  • LiveLocationStatusBar (27-125)
app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (7)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/lib/constants/keys.ts (4)
  • MAP_PROVIDER_PREFERENCE_KEY (24-24)
  • MAP_PROVIDER_DEFAULT (27-27)
  • GOOGLE_MAPS_API_KEY_PREFERENCE_KEY (25-25)
  • OSM_API_KEY_PREFERENCE_KEY (26-26)
app/lib/methods/helpers/info.ts (1)
  • showErrorAlert (5-6)
app/views/LocationShare/services/staticLocation.ts (1)
  • getCurrentPositionOnce (24-61)
app/containers/ActionSheet/Provider.tsx (1)
  • TActionSheetOptionsItem (7-15)
app/views/LocationShare/services/handleLiveLocationUrl.ts (1)
app/views/LocationShare/LiveLocationPreviewModal.tsx (3)
  • isLiveLocationActive (367-369)
  • getCurrentLiveParams (52-54)
  • reopenLiveLocationModal (371-379)
app/views/LocationShare/LocationPreviewModal.tsx (5)
app/views/LocationShare/services/staticLocation.ts (1)
  • Coords (3-3)
app/views/LocationShare/services/mapProviders.ts (5)
  • Coords (12-12)
  • MapProviderName (7-7)
  • staticMapUrl (98-113)
  • mapsDeepLink (165-191)
  • providerLabel (196-196)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/lib/methods/sendMessage.ts (1)
  • sendMessage (87-241)
app/views/LocationShare/LiveLocationPreviewModal.tsx (5)
app/views/LocationShare/services/mapProviders.ts (4)
  • MapProviderName (7-7)
  • staticMapUrl (98-113)
  • mapsDeepLink (165-191)
  • providerLabel (196-196)
app/views/LocationShare/services/liveLocation.ts (5)
  • LiveLocationTracker (14-115)
  • LiveLocationState (4-12)
  • generateLiveLocationId (117-119)
  • createLiveLocationMessage (121-141)
  • createLiveLocationStopMessage (143-149)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/LocationShare/services/handleLiveLocationUrl.ts (1)
  • markLiveLocationAsEnded (31-35)
🪛 Biome (2.1.2)
app/views/LocationShare/services/mapProviders.ts

[error] 110-110: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🪛 ESLint
app/views/LocationShare/services/liveLocation.ts

[error] 1-1: There should be at least one empty line between import groups

(import/order)

app/views/LocationShare/services/mapProviders.ts

[error] 66-72: Unexpected string concatenation.

(prefer-template)


[error] 86-90: Unexpected string concatenation.

(prefer-template)


[error] 165-165: Async function 'mapsDeepLink' has no 'await' expression.

(require-await)

app/views/LocationShare/LiveLocationStatusBar.tsx

[error] 2-12: There should be at least one empty line between import groups

(import/order)


[error] 10-10: 'Platform' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 11-11: 'StatusBar' is defined but never used.

(@typescript-eslint/no-unused-vars)

app/views/RoomView/index.tsx

[error] 1498-1498: 'loading' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

app/containers/MessageComposer/components/Buttons/ActionsButton.tsx

[error] 12-12: react-native import should occur before import of ../../../../lib/database/services/Subscription

(import/order)


[error] 13-13: There should be at least one empty line between import groups

(import/order)


[error] 13-13: expo-location import should occur before import of ../../../../lib/database/services/Subscription

(import/order)

app/views/LocationShare/services/handleLiveLocationUrl.ts

[error] 26-28: Empty block statement.

(no-empty)

app/views/LocationShare/LocationPreviewModal.tsx

[error] 9-9: There should be at least one empty line between import groups

(import/order)


[error] 10-10: There should be at least one empty line between import groups

(import/order)


[error] 10-10: react-redux import should occur before import of ../../lib/navigation/appNavigation

(import/order)


[error] 40-40: 'token' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

app/views/LocationShare/LiveLocationPreviewModal.tsx

[error] 3-3: There should be at least one empty line between import groups

(import/order)


[error] 4-4: There should be no empty line within import group

(import/order)


[error] 18-18: There should be at least one empty line between import groups

(import/order)


[error] 19-19: react-redux import should occur before import of ../../i18n

(import/order)


[error] 239-239: Expected 'undefined' and instead saw 'void'.

(no-void)


[error] 395-395: Empty block statement.

(no-empty)

🔇 Additional comments (53)
ios/RocketChatRN.xcodeproj/project.pbxproj (1)

1885-1885: Confirm whether the quote removal is intentional.

The changes remove surrounding quotation marks from path variables in build phase inputPaths (lines 1885, 1971) and HEADER_SEARCH_PATHS (lines 2701, 2778). Both quoted and unquoted forms are valid in Xcode project files, so this has no functional impact. However, it's worth confirming whether this was an intentional style change or an automatic modification by Xcode when the project file was saved.

Also applies to: 1971-1971, 2701-2701, 2778-2778

ios/NotificationService/Info.plist (1)

25-26: LGTM! Key promoted to top-level.

The IS_OFFICIAL key has been correctly moved from a nested position to the top level of the plist dictionary, improving the file structure.

package.json (2)

77-107: Clarify the need for two location libraries.

Both expo-location (line 77) and react-native-geolocation-service (line 107) are added. These libraries serve similar purposes. Please clarify:

  • Why both are needed (e.g., platform-specific fallback, feature coverage)?
  • Whether they might conflict in location permission handling or API usage?

Having redundant location libraries can increase bundle size and create maintenance overhead.


77-77: Confirm expo-location compatibility and acknowledge security audit

  • expo-location@^19.0.7 has no known security advisories. (security.snyk.io)
  • Ensure v19.x aligns with your Expo SDK 53 (React Native 0.79.4) modules.
app/stacks/MasterDetailStack/types.ts (1)

196-196: LGTM! Navigation type correctly added.

The LocationPreferencesView entry is properly added to ModalStackParamList with no required parameters, following the established pattern for similar settings screens.

ios/RocketChatRN/Info.plist (3)

60-60: LGTM! Google Maps scheme added.

The comgooglemaps scheme is correctly added to LSApplicationQueriesSchemes, allowing the app to detect and open Google Maps.


93-96: LGTM! Clear and appropriate location usage descriptions.

The location usage descriptions clearly explain:

  • When-in-use: Location shared only on explicit user action
  • Always/background: Purpose of background access and user control

These descriptions comply with App Store guidelines and respect user privacy.


112-112: LGTM! Background location mode added.

The location background mode is correctly added to UIBackgroundModes, enabling live location sharing while the app is in the background. This aligns with the usage descriptions provided on lines 93-96.

app.json (1)

3-3: expo-web-browser dependency already declared

expo-web-browser is listed in package.json (version 14.1.5), so no further action is required.

app/views/LocationPreferencesView/ApiKeyModal.tsx (1)

49-49: autoFocus may not work reliably on Android.

The autoFocus prop on TextInput is known to have inconsistent behavior on Android, especially in modals. Users may need to manually tap the input.

Test the autoFocus behavior on both iOS and Android devices. If it doesn't work reliably on Android, consider using a ref with a setTimeout to focus programmatically:

const inputRef = useRef<TextInput>(null);

useEffect(() => {
  if (visible) {
    setTimeout(() => inputRef.current?.focus(), 100);
  }
}, [visible]);

// In TextInput
ref={inputRef}
app/views/LocationShare/services/staticLocation.ts (1)

8-22: Race condition in timeout cleanup.

The withTimeout helper has a potential issue: if the promise settles exactly as the timeout fires, both resolve/reject and the timeout's reject could execute. While Promise semantics guarantee only the first settlement wins, the clearTimeout call on line 15 and 18 may execute after the timeout callback on line 11 has already fired, leaving an uncleared timeout in edge cases.

Consider a more robust pattern using Promise.race:

-function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
-	return new Promise((resolve, reject) => {
-		const t = setTimeout(() => {
-			reject(new Error('Location request timed out'));
-		}, ms);
-
-		p.then(v => {
-			clearTimeout(t);
-			resolve(v);
-		}).catch(e => {
-			clearTimeout(t);
-			reject(e);
-		});
-	});
-}
+function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
+	return Promise.race([
+		p,
+		new Promise<T>((_, reject) => 
+			setTimeout(() => reject(new Error('Location request timed out')), ms)
+		)
+	]);
+}

Likely an incorrect or invalid review comment.

app/lib/methods/sendMessage.ts (1)

104-111: I encountered issues running the prior scripts; please verify manually that getRoomInstance returns an EncryptionRoom instance whose encrypt method correctly processes the full IMessage, including its attachments field. Ensure that attachments are encrypted or passed through as intended to avoid data loss.

app/lib/constants/keys.ts (1)

24-27: LGTM! Well-structured constants for map provider preferences.

The new constants follow the existing naming conventions and provide a sensible default of OpenStreetMap. The keys are clearly named for their purposes: storing user preferences, API keys, and defining the default provider.

app/views/LocationShare/services/handleLiveLocationUrl.ts (3)

1-9: LGTM! Clean imports and well-named storage key.

The module-level cache pattern for endedIds is appropriate for avoiding repeated AsyncStorage reads.


31-40: LGTM! Clean and straightforward API.

The async functions properly handle the lazy-loaded set and provide a clear interface for tracking ended live locations.


42-44: LGTM! Appropriate URL validation.

The case-insensitive regex correctly identifies live location deep links.

app/views/LocationShare/services/liveLocation.ts (6)

4-12: LGTM! Well-structured state type.

The LiveLocationState type clearly captures the essential location data with an appropriate optional accuracy field and activity flag.


14-27: LGTM! Clean encapsulation and safe callback handling.

The class structure with private fields and optional-chaining for the callback is well-designed.


29-92: Verify the buffered update strategy meets UX requirements.

The implementation uses a buffered update pattern: position changes from watchPositionAsync update currentState internally but do not immediately emit (line 69-79), while a separate 10-second setInterval emits the current state. This means location updates on the UI can be delayed up to 10 seconds even if the user moves significantly.

Tradeoffs:

  • Pro: Reduces network traffic and battery usage by limiting message frequency.
  • Con: Users may perceive stale location data for up to 10 seconds, which could be confusing during live tracking.

Consider whether this delay is acceptable for the live location sharing UX, or if position changes should trigger immediate emissions (perhaps with a debounce/throttle to balance freshness and efficiency).

Additional notes:

  • Battery-conscious settings (Balanced accuracy, 5m distance interval) are well-chosen.
  • Permission and service checks are thorough and correct.

94-110: LGTM! Thorough cleanup on stop.

The method correctly removes the subscription, clears the interval, and emits a final inactive state while preserving the last-received timestamp.


112-114: LGTM! Simple and correct getter.


117-119: LGTM! Sufficient ID generation for session tracking.

The combination of timestamp and random string provides adequate uniqueness for non-security-critical session IDs.

app/i18n/locales/en.json (2)

51-53: LGTM! Comprehensive i18n coverage for location sharing features.

The new localization keys provide thorough coverage for:

  • Map provider configuration (Google Maps & OpenStreetMap)
  • API key management with clear security messaging
  • Location sharing workflows (static & live)
  • User-facing prompts and status indicators

The keys follow existing naming conventions and provide clear, actionable text for users.

Also applies to: 157-157, 183-184, 211-211, 317-322, 380-383, 392-392, 452-457, 476-478, 489-490, 594-594, 608-608, 657-657, 664-664, 815-817, 843-847


628-628: Ignore the “OS” renaming suggestion.

The “OS” key is exclusively used in Livechat.tsx to label the user’s Operating System (roomUser.os), so there’s no ambiguity with OpenStreetMap and no change is needed.

Likely an incorrect or invalid review comment.

app/views/RoomView/index.tsx (1)

23-23: LGTM! Clean integration of location status bar.

The additions integrate the new live location status feature into RoomView:

  • StatusBar and LiveLocationStatusBar are rendered at the top of the SafeAreaView
  • LiveLocationStatusBar manages its own visibility based on global live location state
  • The component placement is appropriate for a persistent status indicator

Based on the retrieved code snippet, LiveLocationStatusBar handles its own press events and state management, making this a clean, minimal integration.

Also applies to: 106-106, 1539-1540

app/views/UserPreferencesView/index.tsx (1)

83-89: LGTM! Location preferences item properly integrated.

The new list item follows the established pattern:

  • Uses localized title with existing i18n key
  • Navigates via the existing navigateToScreen helper
  • Includes proper accessibility attributes (testID, showActionIndicator)
  • Separator placement is consistent with other items

The positioning after Notifications makes logical sense for user preferences organization.

app/stacks/InsideStack.tsx (3)

6-6: LGTM! Clean imports for location features.

The new imports are well-organized:

  • I18n import added to support localized modal titles
  • Three new views imported for location preferences and previews
  • Import placement follows existing file organization

Also applies to: 79-81


172-172: LGTM! LocationPreferencesView added to profile stack.

The screen is correctly added to ProfileStackNavigator, making it accessible from the user preferences flow. This is consistent with the addition in MasterDetailStack and the navigation from UserPreferencesView.


326-335: LGTM! Modal screens properly configured for location sharing.

Both modal screens are well-configured:

  • LocationPreviewModal and LiveLocationPreviewModal use modal presentation
  • Titles are properly localized using I18n.t
  • Positioning after ModalBlockView is logical
  • Modal presentation mode is appropriate for these transient location-sharing flows

Based on the provided code snippets, these modals handle:

  • Static location preview with map image and share functionality
  • Live location tracking with real-time updates and session management

The configuration aligns with the expected use cases.

app/stacks/types.ts (2)

194-194: LGTM: LocationPreferencesView added to ProfileStack.

The addition of LocationPreferencesView to the profile navigation stack is appropriate for user-level location preferences.


298-314: LGTM: Modal type definitions are well-structured.

Both LocationPreviewModal and LiveLocationPreviewModal have appropriate type definitions with:

  • Room/thread context (rid, tmid)
  • Provider flexibility (Google Maps/OSM)
  • Optional API keys for customization
  • LiveLocationPreviewModal correctly includes ownership tracking fields
app/views/LocationShare/LocationPreviewModal.tsx (2)

73-91: Verify location attachment support in sendMessage.

The sendMessage function is called without the attachments parameter (line 84), but the function signature from app/lib/methods/sendMessage.ts shows it accepts an optional attachments array. For proper location message handling, consider whether the location data should be passed as a structured attachment rather than just markdown text.

If location data should be stored as a structured attachment for better parsing/display in message lists, apply this pattern:

 const onShare = async () => {
 	try {
 		safeSet(() => setSubmitting(true));
 
 		const deep = await mapsDeepLink(provider, coords);
 		const providerName = providerLabel(provider);
 
 		const message = `📍 **Location**
 
 [🗺️ Open in ${providerName}](${deep})`;
 
-		await sendMessage(rid, message, tmid, { id, username }, false);
+		const attachments = [{
+			type: 'location',
+			latitude: coords.latitude,
+			longitude: coords.longitude,
+			accuracy: coords.accuracy,
+			timestamp: coords.timestamp
+		}];
+		await sendMessage(rid, message, tmid, { id, username }, false, attachments);
 		Navigation.back();

This would enable:

  • Better message list previews (show map thumbnails)
  • Structured querying of location messages
  • Consistent location data format

110-121: LGTM: Image caching and error handling.

Good use of expo-image with disk caching, transition animation, and blurhash placeholder to prevent flicker on Android. The error handler is appropriately minimal since the UI gracefully handles missing images.

app/views/LocationPreferencesView/index.tsx (3)

34-70: LGTM: Clean per-user storage pattern with proper state management.

The implementation correctly:

  • Uses per-user storage keys with userId suffix to isolate preferences
  • Provides sensible defaults (OSM as default provider, empty strings for API keys)
  • Implements clean modal handlers with save/cancel flows
  • Separates concerns between provider selection and API key management

72-113: LGTM: Proper use of translation keys in List components.

The code correctly passes translation keys (e.g., 'Map_Provider', 'API_Keys') to List components, which handle I18n.t() internally. The conditional rendering of "Configured" vs "Not_configured" status is appropriate.


32-39: Guard against missing userId
Ensure state.login.user.id is always a non-empty string before constructing preference keys—add a loading state, fallback, or early return if userId isn’t populated to prevent shared or colliding storage keys.

app/views/LocationShare/LiveLocationStatusBar.tsx (2)

86-105: Verify ownership determination logic covers all scenarios.

The ownership check (line 92) uses: params.ownerName === username OR params.isTracking. This logic should be verified:

  • What if ownerName is undefined but isTracking is true? (Viewer mistakenly treated as owner)
  • What if both are undefined/false? (Correct: not owner)
  • Should isTracking alone determine ownership, or is ownerName comparison the source of truth?

Review the ownership model in the live location flow to ensure:

  1. Only the session creator can stop sharing (sends "Ended" message)
  2. Viewers can only dismiss their local UI
  3. The fallback to isTracking is intentional and documented

Based on learnings: The params structure should clearly indicate owner vs viewer role.


44-79: LGTM: Robust state management and animation lifecycle.

The component properly:

  • Uses mounted guard to prevent state updates after unmount
  • Subscribes/unsubscribes to global status changes
  • Manages animation lifecycle tied to active state
  • Implements smooth pulse animation with native driver
app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (6)

37-41: LGTM! User-scoped preferences.

The user-scoped preference keys ensure that each user's map provider and API key settings are isolated, which is appropriate for multi-account scenarios.


43-60: Good sheet serialization pattern.

The openSheetSafely helper effectively prevents overlapping sheet transitions with a busy flag and deferred execution. The fixed 350ms delay is reasonable for most animations.


73-115: LGTM! Solid permission and location flow.

The function correctly handles:

  • Platform-specific permission requests
  • Location retrieval with timeout handling (via getCurrentPositionOnce)
  • Provider-specific API key inclusion
  • Navigation with master-detail awareness
  • Error handling with user-friendly messages

163-183: LGTM! Clear mode selection flow.

The mode sheet provides a clear choice between static and live location sharing, with safe sheet transitions for each option.


192-254: LGTM! Consistent sheet-close-then-action pattern.

All action handlers now properly hide the sheet and defer actions via InteractionManager, ensuring smooth UI transitions and preventing blocking animations.


257-274: LGTM! API key validation prevents broken flows.

The Share Location action properly validates that required API keys are configured before allowing the user to proceed, providing clear error messaging when keys are missing.

app/views/LocationShare/services/mapProviders.ts (4)

7-23: LGTM! Well-defined types.

The type definitions are clear and comprehensive, with provider-specific options appropriately separated (e.g., googleApiKey vs osmApiKey, mapType for Google only).


28-36: LGTM! Sensible defaults and clear provider URLs.

The constants provide reasonable defaults for map rendering, and the comment documenting LocationIQ for OSM static maps is helpful.


41-50: LGTM! Clean helper functions.

The parseSize and enc helpers are simple, defensive, and reusable.


196-198: LGTM! Correct provider labels and attributions.

The labels are user-friendly, and the OSM attribution complies with OpenStreetMap licensing requirements.

app/views/LocationShare/LiveLocationPreviewModal.tsx (5)

21-70: LGTM! Global tracker pattern for persistent sessions.

The global singleton tracker and status listener system are well-designed to support:

  • Keeping live location running when the modal is minimized
  • Notifying the status bar and other components of state changes
  • Safe listener execution with error isolation (lines 64-68)

72-109: LGTM! Defensive state management.

The component setup includes:

  • Mounted guard (safeSet) to prevent state updates after unmount
  • Map image prefetch for improved UX
  • Shallow equality check on Redux selectors to minimize re-renders

These patterns prevent common React Native pitfalls.


111-127: LGTM! Safe location update handler.

The handler safely updates state, generates provider-specific map URLs, and notifies listeners. All state updates use the safeSet guard.


129-153: LGTM! Tracker initialization with reuse logic.

The effect correctly:

  • Reuses globalTracker when reopening an active session
  • Creates a new tracker for initial shares
  • Sets up dual callbacks to support both local UI updates and global state
  • Handles tracking failures with user-friendly alerts

418-527: LGTM! Comprehensive styles.

The styles provide a polished UI with:

  • Clear visual hierarchy
  • Appropriate spacing and shadows
  • Consistent color scheme
  • Blurhash placeholder for smooth image loading

app.json Outdated
Comment on lines 4 to 9
"expo": {
"extra": {
"GOOGLE_MAPS_API_KEY": "",
"OSM_API_KEY": ""
}
}
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.

Comment on lines 117 to 161
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'));
}
};
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 30, 2025

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolve this too

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@@ -0,0 +1,61 @@
import * as Location from 'expo-location';

export type Coords = { latitude: number; longitude: number; accuracy?: number; timestamp?: number };
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

Duplicate type definition - consolidate to avoid drift.

The Coords type is defined here and also in mapProviders.ts (line 11). The definitions differ: this version includes optional accuracy and timestamp fields, while mapProviders.ts only has latitude and longitude.

Consolidate to a single shared type definition to prevent drift and inconsistencies. Consider defining the complete type in a shared location (e.g., a types file or in mapProviders.ts) and importing it here.

-export type Coords = { latitude: number; longitude: number; accuracy?: number; timestamp?: number };
+import { Coords } from './mapProviders';

Then update mapProviders.ts to include the optional fields:

export type Coords = { 
  latitude: number; 
  longitude: number; 
  accuracy?: number; 
  timestamp?: number 
};
🤖 Prompt for AI Agents
In app/views/LocationShare/services/staticLocation.ts around line 3: there's a
duplicate Coords type here that differs from the one in mapProviders.ts;
consolidate by moving the complete Coords definition (latitude, longitude,
optional accuracy and timestamp) into a single shared file (or into
mapProviders.ts), update mapProviders.ts to export the full type with optional
fields, then replace the local type in this file with an import of that shared
Coords type and remove the duplicate definition to prevent drift.

yiweigao0226 and others added 3 commits October 3, 2025 02:56
* [FIX] Border style

* [FIX] Refactoring

* [FIX] fix color of border

* Undo

Co-authored-by: Aroo <azhaubassar@gmail.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2152119 and bd47d05.

📒 Files selected for processing (5)
  • app/containers/message/Components/LiveLocationCard.tsx (1 hunks)
  • app/containers/message/Content.tsx (2 hunks)
  • app/views/LocationShare/LiveLocationPreviewModal.tsx (1 hunks)
  • app/views/LocationShare/services/handleLiveLocationUrl.ts (1 hunks)
  • app/views/LocationShare/services/liveLocation.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
app/containers/message/Components/LiveLocationCard.tsx (8)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/lib/constants/keys.ts (4)
  • MAP_PROVIDER_PREFERENCE_KEY (24-24)
  • MAP_PROVIDER_DEFAULT (27-27)
  • GOOGLE_MAPS_API_KEY_PREFERENCE_KEY (25-25)
  • OSM_API_KEY_PREFERENCE_KEY (26-26)
app/views/LocationShare/LiveLocationPreviewModal.tsx (5)
  • getCurrentLiveParams (58-60)
  • isLiveLocationActive (470-472)
  • addStatusChangeListener (61-63)
  • removeStatusChangeListener (64-66)
  • reopenLiveLocationModal (474-482)
app/views/LocationShare/services/handleLiveLocationUrl.ts (3)
  • isLiveLocationEnded (62-65)
  • addLiveLocationEndedListener (14-16)
  • removeLiveLocationEndedListener (18-20)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/views/LocationShare/services/handleLiveLocationUrl.ts (1)
app/views/LocationShare/LiveLocationPreviewModal.tsx (3)
  • isLiveLocationActive (470-472)
  • getCurrentLiveParams (58-60)
  • reopenLiveLocationModal (474-482)
app/views/LocationShare/services/liveLocation.ts (1)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/containers/message/Content.tsx (3)
app/containers/message/interfaces.ts (1)
  • IMessageContent (46-68)
app/definitions/IMessage.ts (1)
  • IUserMessage (23-27)
app/views/LocationShare/services/handleLiveLocationUrl.ts (1)
  • markLiveLocationAsEnded (52-60)
app/views/LocationShare/LiveLocationPreviewModal.tsx (5)
app/views/LocationShare/services/mapProviders.ts (4)
  • MapProviderName (7-7)
  • staticMapUrl (98-113)
  • mapsDeepLink (165-191)
  • providerLabel (196-196)
app/views/LocationShare/services/liveLocation.ts (5)
  • LiveLocationTracker (14-115)
  • LiveLocationState (4-12)
  • generateLiveLocationId (117-119)
  • createLiveLocationMessage (121-141)
  • createLiveLocationStopMessage (143-149)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/LocationShare/services/handleLiveLocationUrl.ts (4)
  • isLiveLocationEnded (62-65)
  • addLiveLocationEndedListener (14-16)
  • removeLiveLocationEndedListener (18-20)
  • markLiveLocationAsEnded (52-60)
🪛 ESLint
app/containers/message/Components/LiveLocationCard.tsx

[error] 2-2: There should be at least one empty line between import groups

(import/order)


[error] 4-4: '/home/jailuser/git/app/lib/constants/index.ts' imported multiple times.

(import/no-duplicates)


[error] 25-25: '/home/jailuser/git/app/lib/constants/index.ts' imported multiple times.

(import/no-duplicates)

app/views/LocationShare/services/handleLiveLocationUrl.ts

[error] 47-49: Empty block statement.

(no-empty)

app/views/LocationShare/services/liveLocation.ts

[error] 1-1: There should be at least one empty line between import groups

(import/order)

app/containers/message/Content.tsx

[error] 52-52: 'messageTs' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

app/views/LocationShare/LiveLocationPreviewModal.tsx

[error] 3-3: There should be at least one empty line between import groups

(import/order)


[error] 4-4: There should be no empty line within import group

(import/order)


[error] 23-23: There should be at least one empty line between import groups

(import/order)


[error] 24-24: react-redux import should occur before import of ../../i18n

(import/order)


[error] 326-326: Expected 'undefined' and instead saw 'void'.

(no-void)


[error] 437-463: Do not nest ternary expressions.

(no-nested-ternary)


[error] 498-498: Empty block statement.

(no-empty)

Copy link
Member

@diegolmello diegolmello left a comment

Choose a reason for hiding this comment

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

I'm missing recordings and a better documentation on PR description.

<List.Info info='Map_Provider_Info' />
</List.Section>

<List.Section title='API_Keys'>
Copy link
Member

Choose a reason for hiding this comment

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

Asking all users to login on Google Console to get API Keys is not a good approach. We should always try to use native APIs.

package.json Outdated
@@ -103,6 +104,7 @@
"react-native-easy-grid": "0.2.2",
"react-native-easy-toast": "2.3.0",
"react-native-file-viewer": "2.1.4",
"react-native-geolocation-service": "^5.3.1",
Copy link
Member

Choose a reason for hiding this comment

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

It doesn't look like you're using this anymore.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
app/containers/message/Components/LiveLocationCard.tsx (1)

170-175: Fix stale cardIsActive capture in the interval.

Line 172 references cardIsActive captured from the render that created the effect, so once the state flips (e.g., listener marks the card active) the interval still sees the old value and never deactivates the card when the message ages out. Result: ended sessions can stay “Active” indefinitely. Use a ref (or recreate the interval when cardIsActive changes) so the callback reads the latest state before deciding.

 const LiveLocationCard: React.FC<LiveLocationCardProps> = ({ msg, isActive = true, messageTimestamp, author, onPress }) => {
 	const { theme } = useTheme();
 	const [cardIsActive, setCardIsActive] = useState(isActive);
+	const cardIsActiveRef = useRef(cardIsActive);
+
+	useEffect(() => {
+		cardIsActiveRef.current = cardIsActive;
+	}, [cardIsActive]);
@@
-		const staleCheck = setInterval(() => {
-			if (isMessageTooOld(messageTimestamp) && cardIsActive) {
+		const staleCheck = setInterval(() => {
+			if (isMessageTooOld(messageTimestamp) && cardIsActiveRef.current) {
 				setCardIsActive(false);
 			}
 		}, 5 * 60 * 1000);
🧹 Nitpick comments (6)
app/views/LocationShare/services/liveLocation.ts (5)

88-99: Add error logging for debugging and monitoring.

The error is caught but not logged, making it difficult to diagnose failures when the server API is unavailable. Consider logging the error before falling back to non-server mode.

Apply this diff to add error logging:

 		} catch (error: any) {
+		console.error('[LiveLocation] Failed to start server-side tracking:', error);
 		this.useServerApi = false;

151-158: Log error before stopping tracker.

When live-location-not-found errors occur, logging them would help diagnose issues with session management and provide visibility into why tracking was stopped.

Apply this diff:

 					} catch (error: any) {
 						if (error?.error === 'error-live-location-not-found' || 
 							error?.message?.includes('live-location-not-found')) {
+							console.warn('[LiveLocation] Session not found on server, stopping tracker:', error);
 							this.useServerApi = false;

193-203: Add error logging for stop failures.

Failures during stop operations should be logged to help diagnose cleanup issues and potential resource leaks.

Apply this diff:

 				} catch (error) {
-					// Failed to stop on server
+					console.error('[LiveLocation] Failed to stop on server:', error);
 				}
 			} else {
 				// Send stop message for fallback mode
 				const stopMessage = this.createLiveLocationMessage(this.currentState.coords, 'stop');
 				try {
 					await sendMessage(this.rid, stopMessage, this.tmid, this.user, false);
 				} catch (error) {
-					// Failed to send stop message
+					console.error('[LiveLocation] Failed to send stop message:', error);
 				}

257-277: Remove or utilize unused parameters.

Parameters _serverUrl, rid, and tmid are unused in createLiveLocationMessage. The rid and tmid parameters are included in the deep-link query but not used in the message body. If they're truly unnecessary, remove them; otherwise, document their purpose or use them meaningfully.

If these parameters are for future use or external consistency, add a comment explaining why they're present but unused. Otherwise, remove them:

 export function createLiveLocationMessage(
 	liveLocationId: string,
 	provider: MapProviderName,
 	coords: { latitude: number; longitude: number },
-	_serverUrl: string,
-	rid?: string,
-	tmid?: string
 ): string {
 	const params = new URLSearchParams({
 		liveLocationId,
-		rid: rid || '',
-		tmid: tmid || '',
 		provider,
 		action: 'reopen'
 	});

Or, if they're intentional for the deep-link routing, add a comment:

export function createLiveLocationMessage(
	liveLocationId: string,
	provider: MapProviderName,
	coords: { latitude: number; longitude: number },
	_serverUrl: string, // Reserved for future server URL customization
	rid?: string, // Room ID for deep-link routing
	tmid?: string // Thread message ID for deep-link routing
): string {

279-285: Remove unused parameters from stop message helper.

The _provider and _lastCoords parameters are prefixed with underscore but never used. If they're not needed, remove them to simplify the function signature.

Apply this diff:

 export function createLiveLocationStopMessage(
-	liveLocationId: string,
-	_provider: MapProviderName,
-	_lastCoords: { latitude: number; longitude: number }
+	liveLocationId: string
 ): string {
 	return `📍 **Live Location Ended** (ID: ${liveLocationId})`;
 }
app/containers/message/Components/Attachments/Attachments.tsx (1)

100-115: Consider extending IAttachment type for live-location fields.

The type assertion (file as any) at lines 100 and 105 suggests that the IAttachment interface doesn't include the live field or other live-location-specific properties. Consider extending the interface to include these fields for better type safety.

In app/definitions/IAttachment.ts, add live-location fields:

export interface IAttachment {
	// ... existing fields ...
	// Live location specific fields
	live?: {
		id: string;
		ownerId: string;
		ownerUsername: string;
		isActive: boolean;
		coords: {
			lat: number;
			lng: number;
			acc?: number;
		};
		lastUpdateAt: Date;
		// ... other live location fields
	};
}

Then remove the type assertions in this file:

-			if (file.type === 'live-location' && (file as any).live) {
+			if (file.type === 'live-location' && file.live) {
 				return (
 					<LiveLocationAttachment
 						key={`live-location-${index}`}
-						attachment={file as any}
+						attachment={file}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd47d05 and 366cd18.

📒 Files selected for processing (21)
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (4 hunks)
  • app/containers/message/Components/Attachments/Attachments.tsx (2 hunks)
  • app/containers/message/Components/Attachments/LiveLocationAttachment.tsx (1 hunks)
  • app/containers/message/Components/LiveLocationCard.tsx (1 hunks)
  • app/containers/message/Content.tsx (1 hunks)
  • app/containers/message/Message.tsx (3 hunks)
  • app/containers/message/interfaces.ts (1 hunks)
  • app/index.tsx (0 hunks)
  • app/stacks/InsideStack.tsx (4 hunks)
  • app/stacks/types.ts (2 hunks)
  • app/views/LocationShare/LiveLocationPreviewModal.tsx (1 hunks)
  • app/views/LocationShare/LiveLocationStatusBar.tsx (1 hunks)
  • app/views/LocationShare/LiveLocationViewerModal.tsx (1 hunks)
  • app/views/LocationShare/LocationPreviewModal.tsx (1 hunks)
  • app/views/LocationShare/services/handleLiveLocationUrl.ts (1 hunks)
  • app/views/LocationShare/services/liveLocation.ts (1 hunks)
  • app/views/LocationShare/services/liveLocationApi.ts (1 hunks)
  • app/views/LocationShare/services/mapProviders.ts (1 hunks)
  • app/views/LocationShare/services/staticLocation.ts (1 hunks)
  • app/views/RoomView/index.tsx (10 hunks)
  • package.json (1 hunks)
💤 Files with no reviewable changes (1)
  • app/index.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • app/containers/message/Content.tsx
  • app/views/LocationShare/services/staticLocation.ts
  • app/views/LocationShare/services/handleLiveLocationUrl.ts
🧰 Additional context used
🧬 Code graph analysis (13)
app/views/LocationShare/LiveLocationStatusBar.tsx (4)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/LocationShare/LiveLocationPreviewModal.tsx (6)
  • isLiveLocationMinimized (58-60)
  • addMinimizedStatusListener (62-64)
  • removeMinimizedStatusListener (66-68)
  • reopenLiveLocationModal (544-557)
  • getCurrentLiveParams (54-56)
  • stopGlobalLiveLocation (559-595)
app/views/LocationShare/services/handleLiveLocationUrl.ts (2)
  • isLiveMessageLink (67-69)
  • handleLiveLocationUrl (71-110)
app/containers/message/Components/LiveLocationCard.tsx (8)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/lib/constants/keys.ts (4)
  • MAP_PROVIDER_PREFERENCE_KEY (24-24)
  • MAP_PROVIDER_DEFAULT (27-27)
  • GOOGLE_MAPS_API_KEY_PREFERENCE_KEY (25-25)
  • OSM_API_KEY_PREFERENCE_KEY (26-26)
app/views/LocationShare/LiveLocationPreviewModal.tsx (5)
  • getCurrentLiveParams (54-56)
  • isLiveLocationActive (540-542)
  • addStatusChangeListener (80-82)
  • removeStatusChangeListener (83-85)
  • reopenLiveLocationModal (544-557)
app/views/LocationShare/services/handleLiveLocationUrl.ts (3)
  • isLiveLocationEnded (62-65)
  • addLiveLocationEndedListener (14-16)
  • removeLiveLocationEndedListener (18-20)
app/lib/constants/colors.ts (1)
  • themes (304-304)
app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (7)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/lib/constants/keys.ts (4)
  • MAP_PROVIDER_PREFERENCE_KEY (24-24)
  • MAP_PROVIDER_DEFAULT (27-27)
  • GOOGLE_MAPS_API_KEY_PREFERENCE_KEY (25-25)
  • OSM_API_KEY_PREFERENCE_KEY (26-26)
app/lib/methods/helpers/info.ts (1)
  • showErrorAlert (5-6)
app/views/LocationShare/services/staticLocation.ts (1)
  • getCurrentPositionOnce (24-59)
app/containers/ActionSheet/Provider.tsx (1)
  • TActionSheetOptionsItem (7-15)
app/containers/message/Message.tsx (1)
app/containers/message/Components/Attachments/components.ts (1)
  • Attachments (4-4)
app/containers/message/Components/Attachments/LiveLocationAttachment.tsx (4)
app/definitions/IAttachment.ts (1)
  • IAttachment (9-48)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/views/LocationShare/LiveLocationPreviewModal.tsx (2)
  • reopenLiveLocationModal (544-557)
  • isLiveLocationActive (540-542)
app/containers/message/Components/Attachments/Attachments.tsx (2)
app/definitions/IAttachment.ts (1)
  • IAttachment (9-48)
app/containers/message/interfaces.ts (1)
  • IMessageAttachments (10-20)
app/views/LocationShare/LiveLocationViewerModal.tsx (5)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (4)
  • MapProviderName (7-7)
  • staticMapUrl (88-103)
  • mapsDeepLink (143-168)
  • providerLabel (173-173)
app/lib/constants/keys.ts (4)
  • MAP_PROVIDER_PREFERENCE_KEY (24-24)
  • MAP_PROVIDER_DEFAULT (27-27)
  • GOOGLE_MAPS_API_KEY_PREFERENCE_KEY (25-25)
  • OSM_API_KEY_PREFERENCE_KEY (26-26)
app/views/LocationShare/services/liveLocationApi.ts (2)
  • LiveLocationApi (45-89)
  • serverToMobileCoords (105-111)
app/stacks/InsideStack.tsx (2)
app/views/LocationShare/LocationPreviewModal.tsx (1)
  • LocationPreviewModal (24-135)
app/views/LocationShare/LiveLocationPreviewModal.tsx (1)
  • LiveLocationPreviewModal (97-538)
app/views/LocationShare/LocationPreviewModal.tsx (5)
app/views/LocationShare/services/mapProviders.ts (5)
  • Coords (12-12)
  • MapProviderName (7-7)
  • staticMapUrl (88-103)
  • mapsDeepLink (143-168)
  • providerLabel (173-173)
app/views/LocationShare/services/staticLocation.ts (1)
  • Coords (3-3)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/lib/methods/sendMessage.ts (1)
  • sendMessage (87-241)
app/views/LocationShare/services/mapProviders.ts (1)
app/views/LocationShare/services/staticLocation.ts (1)
  • Coords (3-3)
app/views/LocationShare/LiveLocationPreviewModal.tsx (5)
app/views/LocationShare/services/mapProviders.ts (4)
  • MapProviderName (7-7)
  • staticMapUrl (88-103)
  • mapsDeepLink (143-168)
  • providerLabel (173-173)
app/views/LocationShare/services/liveLocation.ts (2)
  • LiveLocationTracker (18-251)
  • LiveLocationState (7-16)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/LocationShare/services/handleLiveLocationUrl.ts (4)
  • isLiveLocationEnded (62-65)
  • addLiveLocationEndedListener (14-16)
  • removeLiveLocationEndedListener (18-20)
  • markLiveLocationAsEnded (52-60)
app/views/LocationShare/services/liveLocation.ts (2)
app/views/LocationShare/services/liveLocationApi.ts (2)
  • LiveLocationApi (45-89)
  • mobileToServerCoords (94-100)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (7-7)
app/views/RoomView/index.tsx (2)
app/lib/services/restApi.ts (5)
  • getUserInfo (1056-1059)
  • editMessage (955-959)
  • setReaction (322-324)
  • joinRoom (272-278)
  • toggleFollowMessage (774-780)
app/views/LocationShare/LiveLocationStatusBar.tsx (1)
  • LiveLocationStatusBar (26-124)
🪛 Biome (2.1.2)
app/views/LocationShare/services/mapProviders.ts

[error] 147-147: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (8)
package.json (1)

77-77: LGTM: Clean dependency addition for location features.

The expo-location package is correctly added and aligns with the PR's location-sharing functionality.

app/containers/message/interfaces.ts (1)

18-19: LGTM: Context fields enable proper attachment linking.

Adding messageId and roomId to IMessageAttachments allows LiveLocationAttachment and other components to navigate correctly while maintaining backward compatibility.

app/views/LocationShare/services/liveLocationApi.ts (1)

1-111: LGTM: Clean API wrapper with good documentation.

The LiveLocationApi class provides a well-structured interface for server-side live location operations. The JSDoc comments clearly document parameters and return types, and the coordinate conversion utilities (mobileToServerCoords and serverToMobileCoords) maintain clean separation between mobile and server coordinate formats.

app/containers/message/Message.tsx (1)

42-42: LGTM: Consistent prop propagation across rendering branches.

The addition of messageId and roomId to all three Attachments usages (preview, default content, and pinned-info branch) ensures LiveLocationAttachment and other components receive the necessary context regardless of the rendering path.

Also applies to: 90-90, 177-177

app/views/RoomView/index.tsx (2)

12-19: LGTM: Clean import consolidation and component addition.

Replacing indirect Services.* calls with direct imports from lib/services/restApi improves clarity and reduces indirection. The addition of LiveLocationStatusBar integrates cleanly with the existing SafeAreaView structure.

Also applies to: 57-57


1538-1538: LGTM: LiveLocationStatusBar properly integrated.

The status bar is correctly placed within SafeAreaView and will display above the room content when live location sharing is active.

app/containers/message/Components/Attachments/Attachments.tsx (1)

17-17: LGTM: removeQuote correctly handles live-location attachments.

Including file?.type === 'live-location' in the removeQuote filter ensures live-location attachments are treated as non-quote content and rendered appropriately.

app/stacks/InsideStack.tsx (1)

79-82: LGTM: Clean navigation structure for location features.

The addition of LocationPreferencesView to the ProfileStack and the three location modals (LocationPreviewModal, LiveLocationPreviewModal, LiveLocationViewerModal) to the InsideStack follows existing navigation patterns. The use of modal presentation and localized titles via I18n.t() is consistent with the rest of the codebase.

Also applies to: 173-173, 327-341

@yiweigao0226
Copy link
Author

Most of Rohit’s comments have been addressed, and I’ve left replies for the ones that are still pending.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
app/containers/message/Message.tsx (1)

43-43: Remove redundant prop forwarding.

Since {...props} already spreads all props including id and rid, explicitly passing id={props.id} rid={props.rid} is redundant.

Apply this diff:

-<Attachments {...props} id={props.id} rid={props.rid} />
+<Attachments {...props} />

Also applies to lines 94 and 168.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 289bcfe and 8bc29d2.

📒 Files selected for processing (3)
  • app/containers/message/Components/Attachments/Reply.tsx (2 hunks)
  • app/containers/message/Message.tsx (4 hunks)
  • app/views/LocationShare/services/liveLocationApi.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/views/LocationShare/services/liveLocationApi.ts (1)
app/lib/services/restApi.ts (4)
  • liveLocationStart (1110-1115)
  • liveLocationUpdate (1117-1118)
  • liveLocationStop (1120-1124)
  • liveLocationGet (1126-1126)
app/containers/message/Message.tsx (1)
app/containers/message/Components/CurrentLocationCard.tsx (1)
  • isCurrentLocationMessage (63-65)
🔇 Additional comments (6)
app/containers/message/Components/Attachments/Reply.tsx (2)

213-213: LGTM! Context destructuring extended correctly.

Adding rid to the destructured MessageContext values is consistent with the existing pattern and necessary for forwarding room context to attachments.


256-263: LGTM! Prop forwarding enables attachment context.

Forwarding id and rid from MessageContext to the Attachments component provides the necessary message and room identifiers for live location attachments to function correctly.

app/containers/message/Message.tsx (2)

19-19: LGTM! Import added for location sharing feature.

The import of CurrentLocationCard and isCurrentLocationMessage is correctly structured and necessary for the new location message rendering logic.


84-95: Conditional rendering logic is sound.

The implementation correctly:

  • Detects location messages via isCurrentLocationMessage
  • Suppresses Content to avoid showing raw coordinates when CurrentLocationCard renders them
  • Suppresses Urls to prevent coordinate text from being parsed as URLs
  • Renders CurrentLocationCard for all messages (component returns null for non-location messages per the implementation)

The conditional rendering addresses the question from the past reviewer and is necessary to prevent duplicate display of coordinates.

app/views/LocationShare/services/liveLocationApi.ts (2)

4-41: LGTM! Type definitions are clear and consistent.

The type definitions are well-structured and provide good type safety for the API surface. The indentation issue from the previous review appears to be resolved.


97-107: LGTM! Coordinate conversion is correct.

The function correctly maps from mobile format (latitude, longitude, accuracy) to server format (lat, lon, acc). The previous issue with lng vs lon has been resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/views/LocationShare/LiveLocationViewerModal.tsx (1)

285-287: Hardcoded 'osm' breaks provider selection across preview, cache, and attribution.

The component computes provider from user preferences (line 228) but ignores it in three places: the static map URL (line 286), the cache key prefix (line 314), and the attribution text (line 525). Users who select Google Maps will see OSM tiles with incorrect attribution and cache collisions.

Apply this diff to honor the selected provider:

 		if (mobileCoords) {
-			const mapResult = staticMapUrl('osm', mobileCoords, { zoom: 15 });
+			const mapResult = staticMapUrl(provider as MapProviderName, mobileCoords, { zoom: 15 });
 			setMapImageUrl(mapResult.url);
 		}
 	const cacheKey = useMemo(() => {
 		const lat = liveLocationData?.coords?.latitude;
 		const lon = liveLocationData?.coords?.longitude;
-		return lat && lon ? `osm-${lat.toFixed(5)}-${lon.toFixed(5)}-z15-v2` : undefined;
-	}, [liveLocationData?.coords?.latitude, liveLocationData?.coords?.longitude]);
+		return lat && lon ? `${provider}-${lat.toFixed(5)}-${lon.toFixed(5)}-z15-v2` : undefined;
+	}, [liveLocationData?.coords?.latitude, liveLocationData?.coords?.longitude, provider]);
-			<Text style={[styles.attribution, { color: colors.fontSecondaryInfo }]}>{providerAttribution('osm')}</Text>
+			<Text style={[styles.attribution, { color: colors.fontSecondaryInfo }]}>{providerAttribution(provider as MapProviderName)}</Text>

Also applies to: 311-315, 525-525

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8bc29d2 and 6ea1090.

📒 Files selected for processing (2)
  • app/views/LocationShare/LiveLocationViewerModal.tsx (1 hunks)
  • app/views/LocationShare/services/liveLocationApi.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
app/views/LocationShare/LiveLocationViewerModal.tsx (7)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (5)
  • MapProviderName (5-5)
  • staticMapUrl (76-91)
  • mapsDeepLink (125-151)
  • providerLabel (153-153)
  • providerAttribution (154-154)
app/lib/constants/keys.ts (2)
  • MAP_PROVIDER_PREFERENCE_KEY (25-25)
  • MAP_PROVIDER_DEFAULT (28-28)
app/views/LocationShare/services/liveLocationApi.ts (2)
  • LiveLocationApi (43-98)
  • serverToMobileCoords (112-125)
app/views/LocationShare/services/handleLiveLocationUrl.ts (2)
  • addLiveLocationEndedListener (13-15)
  • removeLiveLocationEndedListener (17-19)
app/views/LocationShare/services/liveLocationApi.ts (1)
app/lib/services/restApi.ts (4)
  • liveLocationStart (1110-1115)
  • liveLocationUpdate (1117-1118)
  • liveLocationStop (1120-1124)
  • liveLocationGet (1126-1126)

stoppedAt?: string;
coords: { lat: number; lon: number };
expiresAt?: string;
version?: number;
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 | 🟡 Minor

Validation and type mismatch for version field.

Line 81 validates that version is a required number and throws if missing, yet line 40 declares version?: number (optional) in LiveLocationGetResponse. Since the validation guarantees version will always be a number when get() succeeds, the type should reflect that by removing the ?.

Apply this diff to align the type with runtime behavior:

 export type LiveLocationGetResponse = {
 	messageId: string;
 	ownerId: string;
 	ownerUsername?: string;
 	ownerName?: string;
 	isActive: boolean;
 	startedAt: string;
 	lastUpdateAt: string;
 	stoppedAt?: string;
 	coords: { lat: number; lon: number };
 	expiresAt?: string;
-	version?: number;
+	version: number;
 };

Alternatively, if the server can legitimately omit version, remove it from the validation check on line 81 and keep the type optional.

Also applies to: 81-82, 95-95

🤖 Prompt for AI Agents
In app/views/LocationShare/services/liveLocationApi.ts around lines 40, 81-82
and 95, the type for LiveLocationGetResponse declares version as optional but
runtime validation at line 81 requires version to be present; change the
TypeScript declaration at line 40 from version?: number to version: number (and
update any related types/usages at line 95 accordingly) so the static type
matches the runtime guarantee; alternatively, if the server can omit version,
remove the required check at line 81-82 instead—pick one approach and make the
type and validation consistent.

@Rohit3523
Copy link
Contributor

attachments,
timeFormat,
showAttachment,
style: _style,
Copy link
Contributor

Choose a reason for hiding this comment

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

I can't find the use of style in this component

showAttachment,
style: _style,
getCustomEmoji,
isReply: _isReply,
Copy link
Contributor

Choose a reason for hiding this comment

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

I can't find the use of _isReply

<Quote {...props} />
<Content {...props} />
<Attachments {...props} />
<Attachments {...props} id={props.id} rid={props.rid} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not pass id and rid as seperate prop

{!isLocation ? <Content {...props} /> : null}
{/* Render a card for "current location" messages; returns null otherwise */}
<CurrentLocationCard msg={props.msg as string} />
<Attachments {...props} id={props.id} rid={props.rid} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not pass id and rid as separate prop

{props.isInfo && props.type === 'message_pinned' ? (
<View pointerEvents='none'>
<Attachments {...props} />
<Attachments {...props} id={props.id} rid={props.rid} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not pass id and rid as separate prop

})
),
...messagesToUpdate.map((message) => {
...messagesToUpdate.map(message => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR, Revert this

}),
...messagesToCreate.map(message =>
messagesCollection.prepareCreate((m) => {
messagesCollection.prepareCreate(m => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR, Revert this

}
}),
...subsToDelete.map((subscription) => {
...subsToDelete.map(subscription => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR, Revert this

})
),
...subsToUpdate.map((subscription) => {
...subsToUpdate.map(subscription => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR, Revert this

const allRecords = [
...subsToCreate.map(subscription =>
subCollection.prepareCreate((s) => {
subCollection.prepareCreate(s => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unrelated to this PR, Revert this

@Rohit3523
Copy link
Contributor

Also use yarn prettier-lint which will format the code

@yiweigao0226
Copy link
Author

Also use yarn prettier-lint which will format the code

I ran the command to format the code, but it unintentionally modified some unrelated files.

@Rohit3523
Copy link
Contributor

Try to take a pull from develop, i think that will resolve the issue

@yiweigao0226
Copy link
Author

yiweigao0226 commented Oct 31, 2025

Try to take a pull from develop, i think that will resolve the issue

It is already up to date. I can run the yarn prettier-lint and manually revert the files that has been changed. Does this work?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (5)
app/views/LocationShare/services/liveLocationApi.ts (1)

40-40: Type and runtime validation mismatch for version.

Line 40 declares version?: number as optional, but line 81 validates that version must be a number and throws if missing. Since the runtime guarantees version will always be present when get() succeeds, the type should reflect that.

Apply this diff to align the type with runtime behavior:

 export type LiveLocationGetResponse = {
 	messageId: string;
 	ownerId: string;
 	ownerUsername?: string;
 	ownerName?: string;
 	isActive: boolean;
 	startedAt: string;
 	lastUpdateAt: string;
 	stoppedAt?: string;
 	coords: { lat: number; lon: number };
 	expiresAt?: string;
-	version?: number;
+	version: number;
 };

Alternatively, if the server can legitimately omit version, remove it from the validation check on line 81.

app/views/LocationShare/LiveLocationPreviewModal.tsx (3)

266-270: Hardcoded provider in cache key ignores user selection.

The cache key is hardcoded to 'osm' (line 269), so users who choose Google Maps will see OSM tiles and incorrect cache entries. This creates a mismatch between the selected provider and the displayed map.

Apply this diff to respect the chosen provider:

 	const cacheKey = useMemo(() => {
 		const lat = locationState?.coords?.latitude;
 		const lon = locationState?.coords?.longitude;
-		return lat && lon ? `osm-${lat.toFixed(5)}-${lon.toFixed(5)}-z15-v2` : undefined;
-	}, [locationState?.coords?.latitude, locationState?.coords?.longitude]);
+		return lat && lon ? `${provider}-${lat.toFixed(5)}-${lon.toFixed(5)}-z15-v2` : undefined;
+	}, [locationState?.coords?.latitude, locationState?.coords?.longitude, provider]);

274-286: Hardcoded provider in map URL generation ignores user selection.

Line 279 hardcodes 'osm' in the staticMapUrl call, so the preview always shows OSM tiles regardless of the user's chosen provider. This creates a mismatch between the selected provider and the displayed preview.

Apply this diff:

 		(state: LiveLocationState) => {
 			safeSet(() => setLocationState(state));
 
 			if (state.coords) {
-				const { url } = staticMapUrl('osm', state.coords, { zoom: 15 });
+				const { url } = staticMapUrl(provider, state.coords, { zoom: 15 });
 				safeSet(() => setMapImageUrl(url));
 			}
 
 			emitStatusChange();
 		},
 		[safeSet]
 	);

And update the dependency array to include provider:

-	}, [safeSet]);
+	}, [safeSet, provider]);

731-731: Hardcoded OSM attribution shown for all providers.

Line 731 hardcodes 'osm' in the providerAttribution call, so OSM attribution is always displayed even when Google Maps is selected. This is both incorrect and a branding issue.

Apply this diff:

-				<Text style={[styles.attribution, { color: colors.fontSecondaryInfo }]}>{providerAttribution('osm')}</Text>
+				<Text style={[styles.attribution, { color: colors.fontSecondaryInfo }]}>{providerAttribution(provider)}</Text>
app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (1)

149-149: Remove @ts-ignore by adding navigation type definitions.

The @ts-ignore on line 149 indicates missing type definitions for LiveLocationPreviewModal in the navigation param list. Add the route definition to your navigation types (likely in app/stacks/types.ts):

LiveLocationPreviewModal: {
  rid: string;
  tmid?: string;
  provider: MapProviderName;
};

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6ea1090 and 1de7572.

📒 Files selected for processing (5)
  • app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (4 hunks)
  • app/containers/message/Components/Attachments/Attachments.tsx (2 hunks)
  • app/definitions/IAttachment.ts (1 hunks)
  • app/views/LocationShare/LiveLocationPreviewModal.tsx (1 hunks)
  • app/views/LocationShare/services/liveLocationApi.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/containers/message/Components/Attachments/Attachments.tsx (2)
app/definitions/IAttachment.ts (1)
  • IAttachment (9-58)
app/containers/message/interfaces.ts (1)
  • IMessageAttachments (18-28)
app/views/LocationShare/services/liveLocationApi.ts (1)
app/lib/services/restApi.ts (4)
  • liveLocationStart (1110-1115)
  • liveLocationUpdate (1117-1118)
  • liveLocationStop (1120-1124)
  • liveLocationGet (1126-1126)
app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (8)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/views/LocationShare/services/mapProviders.ts (1)
  • MapProviderName (5-5)
app/lib/constants/keys.ts (2)
  • MAP_PROVIDER_PREFERENCE_KEY (25-25)
  • MAP_PROVIDER_DEFAULT (28-28)
app/lib/methods/helpers/info.ts (1)
  • showErrorAlert (5-6)
app/views/LocationShare/services/staticLocation.ts (1)
  • getCurrentPositionOnce (24-59)
app/views/LocationShare/LiveLocationPreviewModal.tsx (2)
  • isLiveLocationActive (745-747)
  • reopenLiveLocationModal (749-761)
app/containers/ActionSheet/Provider.tsx (1)
  • TActionSheetOptionsItem (7-17)
app/views/LocationShare/LiveLocationPreviewModal.tsx (7)
app/views/LocationShare/services/mapProviders.ts (5)
  • MapProviderName (5-5)
  • staticMapUrl (76-91)
  • mapsDeepLink (125-151)
  • providerLabel (153-153)
  • providerAttribution (154-154)
app/views/LocationShare/services/liveLocation.ts (2)
  • LiveLocationTracker (19-277)
  • LiveLocationState (8-17)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/selectors/login.ts (1)
  • getUserSelector (20-20)
app/views/LocationShare/services/handleLiveLocationUrl.ts (4)
  • isLiveLocationEnded (60-63)
  • markLiveLocationAsEnded (51-58)
  • addLiveLocationEndedListener (13-15)
  • removeLiveLocationEndedListener (17-19)
app/views/LocationShare/services/liveLocationApi.ts (2)
  • LiveLocationApi (43-98)
  • serverToMobileCoords (112-125)
🔇 Additional comments (8)
app/views/LocationShare/services/liveLocationApi.ts (2)

43-98: LGTM!

The API wrapper methods are well-structured with consistent error handling, proper type safety, and thorough validation. The coordinate transformation logic correctly maps between client and server representations.


100-125: LGTM!

The coordinate conversion utilities are correctly implemented with proper validation. The explicit check for missing lon prevents silent failures that could result from defaulting to a real coordinate value.

app/views/LocationShare/LiveLocationPreviewModal.tsx (2)

34-98: LGTM!

The global state management and listener pattern are appropriate for maintaining live location session state across navigation. Error handling in the emit functions prevents listener failures from affecting other listeners.


745-791: LGTM!

The exported API functions provide a clean interface for managing live location sessions from external components. The cleanup logic in stopGlobalLiveLocation properly handles all state in a finally block to ensure consistency.

app/containers/MessageComposer/components/Buttons/ActionsButton.tsx (1)

182-260: LGTM!

The action handlers have been consistently refactored to close the action sheet and defer navigation/operations via InteractionManager. This ensures proper UI sequencing and prevents race conditions with sheet transitions. The new location sharing flow integrates cleanly with the existing pattern.

app/containers/message/Components/Attachments/Attachments.tsx (3)

16-22: LGTM!

The removeQuote predicate correctly includes live-location attachments alongside other non-quote attachment types using consistent optional chaining.


25-25: LGTM!

The component signature correctly destructures id and rid from props, which are needed for the LiveLocationAttachment component to function properly.


77-79: LGTM!

The live-location attachment rendering is correctly implemented with proper type checking and props forwarding. The integration follows the existing pattern for other attachment types.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1de7572 and c37b1fa.

📒 Files selected for processing (1)
  • app/definitions/IAttachment.ts (1 hunks)

Comment on lines 25 to 61
function extractCoordsFromMessage(msg?: string | null): Coords | null {
if (!msg) return null;

// geo:lat,lon
let m = msg.match(/geo:([+-]?\d+\.?\d*),([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };
// geo:0,0?q=lat,lon (allow %2C)
m = msg.match(/geo:[^\s?]*\?q=([+-]?\d+\.?\d*)(?:,|%2C)([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };

// Apple Maps: ?ll=lat,lon or ?q=lat,lon
m = msg.match(/maps\.apple\.com\/(?:\?[^\s]*?)(?:[?&]ll=)([+-]?\d+\.?\d*),([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };
m = msg.match(/maps\.apple\.com\/(?:\?[^\s]*?)(?:[?&]q=)([+-]?\d+\.?\d*),([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };

// Google Maps: comgooglemaps://?q=lat,lon or ...?center=lat,lon (allow %2C)
m = msg.match(/comgooglemaps:\/\/\?(?:[^\s)]*?)(?:[?&])(?:q|center)=([+-]?\d+\.?\d*)(?:,|%2C)([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };
// https://www.google.com/maps/... with query|q|center
m = msg.match(/google\.com\/maps\/(?:[^\s)]*?)(?:[?&](?:query|q|center)=)([+-]?\d+\.?\d*)(?:,|%2C)([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };
// https://www.google.com/maps/@lat,lon,zoom
m = msg.match(/google\.com\/maps\/(?:[^\s)]*?)@([+-]?\d+\.?\d*),([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };
// https://maps.google.com/?q=lat,lon (allow %2C)
m = msg.match(/maps\.google\.com\/(?:[^\s)]*?)(?:[?&](?:q|query|center)=)([+-]?\d+\.?\d*)(?:,|%2C)([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };

// OpenStreetMap: ?mlat=lat&mlon=lon or #map=z/lat/lon
m = msg.match(/openstreetmap\.org\/(?:[^\s]*?)(?:[?&]mlat=)([+-]?\d+\.?\d*)(?:[^\s]*?)(?:[?&]mlon=|&mlon=)([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };
m = msg.match(/openstreetmap\.org\/[^\s#]*#map=\d+\/([+-]?\d+\.?\d*)\/([+-]?\d+\.?\d*)/i);
if (m) return { latitude: Number(m[1]), longitude: Number(m[2]) };

return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you write some jest test for this function like passing some url of google maps, apple maps, open street map and check if it return the expected return value or not. Test will help us to make sure that this function is working as expected in the future.

@Rohit3523
Copy link
Contributor

I am trying my best to review this PR but it's really laggy in all the browsers which is taking time.

@yiweigao0226
Copy link
Author

I am trying my best to review this PR but it's really laggy in all the browsers which is taking time.

Thank you for taking the time to review this PR! The lag might be caused by the large number of comments. I’m considering creating another PR with the same code on a temporary branch based on this one. Each time I update the code, I’ll update both branches. For this PR, you only need to finish the final review before merge. Would that approach work better for you to review, or should we keep everything in a single PR for consistency?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
app/containers/message/Components/LiveLocationCard.tsx (3)

30-39: Remove unused extraction logic.

The memo extracts liveLocationId but then simply returns isActive without using it. Either complete the intended logic or simplify to just return isActive.

Apply this diff:

-   const initialActiveState = useMemo(() => {
-       const linkMatch = msg?.match(/rocketchat:\/\/live-location\?([^)]+)/);
-       if (!linkMatch) return isActive;
-
-       const params = new URLSearchParams(linkMatch[1]);
-       const liveLocationId = params.get('liveLocationId');
-       if (!liveLocationId) return isActive;
-
-       return isActive;
-   }, [msg, isActive]);
+   const initialActiveState = isActive;

48-87: Memoize timestamp validation function.

isMessageTooOld is recreated on every render but is used in effects. Wrap it with useCallback to avoid unnecessary recreations.

Apply this diff:

-   const isMessageTooOld = (
+   const isMessageTooOld = useCallback((
        timestamp?: string | Date | number | { $date?: number; valueOf?: () => number; getTime?: () => number }
-   ) => {
+   ) => {
        if (timestamp == null) {
            return false;
        }
        
        // ... rest of function
        
-   };
+   }, []);

113-113: Remove empty comment line.

Line 113 contains an empty comment. Remove it to clean up the code.

Apply this diff:

                   const THIRTY_MINUTES_MS = 30 * 60 * 1000;
                   const ageMs = now - messageTime;

-
                   if (ageMs > THIRTY_MINUTES_MS) {
app/containers/message/Components/CurrentLocationCard.tsx (2)

24-27: Consider importing Coords type from mapProviders.

The Coords type is already exported from app/views/LocationShare/services/mapProviders.ts (with optional accuracy and timestamp fields). Redefining it here creates duplication and potential inconsistency.

Apply this diff to import and use the existing type:

 import {
 	mapsDeepLink,
 	staticMapUrl,
 	providerAttribution,
-	providerLabel
+	providerLabel,
+	type Coords
 } from '../../../views/LocationShare/services/mapProviders';
 import type { MapProviderName } from '../../../views/LocationShare/services/mapProviders';

-type Coords = {
-	latitude: number;
-	longitude: number;
-};

243-243: Consider using theme color for ripple effect.

The android_ripple color is hardcoded as rgba(0,0,0,0.08). While ripple effects typically use semi-transparent overlays, consider using a theme-based color for consistency.

For example:

android_ripple={{ color: colors.strokeLight }}

Or create a dedicated ripple color in the theme if one doesn't exist.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a10295e and 4e7a86f.

📒 Files selected for processing (4)
  • app/containers/message/Components/CurrentLocationCard.test.ts (1 hunks)
  • app/containers/message/Components/CurrentLocationCard.tsx (1 hunks)
  • app/containers/message/Components/LiveLocationCard.test.tsx (1 hunks)
  • app/containers/message/Components/LiveLocationCard.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/containers/message/Components/LiveLocationCard.tsx (3)
app/theme.tsx (1)
  • useTheme (29-29)
app/views/LocationShare/LiveLocationPreviewModal.tsx (5)
  • getCurrentLiveParams (57-59)
  • isLiveLocationActive (745-747)
  • addStatusChangeListener (82-84)
  • removeStatusChangeListener (85-87)
  • reopenLiveLocationModal (749-761)
app/views/LocationShare/services/handleLiveLocationUrl.ts (3)
  • isLiveLocationEnded (60-63)
  • addLiveLocationEndedListener (13-15)
  • removeLiveLocationEndedListener (17-19)
app/containers/message/Components/CurrentLocationCard.tsx (5)
app/views/LocationShare/services/mapProviders.ts (6)
  • Coords (10-10)
  • MapProviderName (5-5)
  • staticMapUrl (76-91)
  • providerLabel (153-153)
  • mapsDeepLink (125-151)
  • providerAttribution (154-154)
app/theme.tsx (1)
  • useTheme (29-29)
app/lib/hooks/useAppSelector.ts (1)
  • useAppSelector (6-6)
app/lib/methods/userPreferences.ts (1)
  • useUserPreferences (10-10)
app/lib/constants/keys.ts (2)
  • MAP_PROVIDER_PREFERENCE_KEY (25-25)
  • MAP_PROVIDER_DEFAULT (28-28)
app/containers/message/Components/CurrentLocationCard.test.ts (1)
app/containers/message/Components/CurrentLocationCard.tsx (1)
  • extractCoordsFromMessage (50-114)
🪛 Biome (2.1.2)
app/containers/message/Components/CurrentLocationCard.tsx

[error] 67-67: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)


[error] 73-73: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)


[error] 79-79: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)


[error] 92-92: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)

🔇 Additional comments (12)
app/containers/message/Components/LiveLocationCard.test.tsx (2)

1-88: LGTM: Comprehensive test infrastructure.

The mocks properly simulate stateful behavior of live location services, and the helper functions provide consistent test data generation. The setup and cleanup logic is appropriate.


90-243: LGTM: Thorough test coverage.

The test suite comprehensively validates active/inactive states, navigation flows, event handling, session synchronization, and edge cases. The async testing with act and flush helpers is properly implemented.

app/containers/message/Components/LiveLocationCard.tsx (3)

1-25: LGTM: Clean imports and interface.

The imports are well-organized and the props interface is clear with appropriate optional fields.


195-229: LGTM: Proper theme integration.

All colors are correctly sourced from the theme hook, ensuring consistent theming across light/dark modes. The conditional rendering and styling logic is clean.


232-291: LGTM: Clean and well-organized styles.

The StyleSheet contains appropriate styles for the card layout and pulse animation effect.

app/containers/message/Components/CurrentLocationCard.test.ts (1)

1-169: Excellent test coverage!

The test suite comprehensively validates URL parsing across all supported map providers and formats. Edge cases like URL encoding, parameter reordering, whitespace handling, and signed coordinates are well covered.

app/containers/message/Components/CurrentLocationCard.tsx (6)

30-48: LGTM!

The utility functions are well-implemented with proper error handling and clear single responsibilities. The use of Number.isFinite prevents NaN and Infinity values, and the try-catch in parsePair safely handles malformed URI components.


116-118: LGTM!

Clean helper function with clear single responsibility.


121-180: LGTM!

Constants and styles are correctly defined outside the component, following React best practices. All color values use theme colors rather than hardcoded values.


192-195: Static map always uses OpenStreetMap.

The static map preview is hardcoded to use OSM (line 194), regardless of the user's map provider preference. This is likely intentional since Google Maps Static API requires an API key, but it means users who prefer Google Maps will see an OSM preview and then open Google Maps when tapping the link. Consider documenting this design decision or ensuring it's acceptable to the team.

Is this behavior intentional? If so, consider adding a comment explaining why the static map provider differs from the deep link provider.


207-223: LGTM!

Error handling is comprehensive and appropriate. Haptics and deep linking are properly wrapped in try-catch blocks, and canOpenURL is checked before attempting to open the URL, addressing previous review feedback. Silent failures are correct for these optional enhancements.


227-276: Well-structured component implementation!

The rendering logic is clean and well-organized:

  • Proper use of accessibility props for both the map and button
  • Effective image caching with custom cache keys
  • Responsive pressed states for user feedback
  • Consistent use of i18n for all user-facing text
  • Theme colors applied throughout (except for the optional ripple improvement noted separately)

if (lat !== undefined && lon !== undefined) return { latitude: lat, longitude: lon };
}

m = s.match(/maps\.apple\.com\/[^]*?[?&]ll=([^&]+)/i);
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 | 🟡 Minor

Address static analysis warnings about [^] pattern.

The regex pattern [^] (negated empty character class) is flagged by Biome. While technically valid in JavaScript (matches any character including newlines), it's considered a code smell. Consider replacing with [\s\S] for better clarity and to satisfy the linter.

Apply this diff to address all four occurrences:

-	m = s.match(/maps\.apple\.com\/[^]*?[?&]ll=([^&]+)/i);
+	m = s.match(/maps\.apple\.com\/[\s\S]*?[?&]ll=([^&]+)/i);

-	m = s.match(/maps\.apple\.com\/[^]*?[?&]q=([^&]+)/i);
+	m = s.match(/maps\.apple\.com\/[\s\S]*?[?&]q=([^&]+)/i);

-	m = s.match(/google\.com\/maps\/[^]*?[?&](?:q|query|center)=([^&]+)/i);
+	m = s.match(/google\.com\/maps\/[\s\S]*?[?&](?:q|query|center)=([^&]+)/i);

-	m = s.match(/comgooglemaps:\/\/[^]*?[?&](?:q|center)=([^&]+)/i);
+	m = s.match(/comgooglemaps:\/\/[\s\S]*?[?&](?:q|center)=([^&]+)/i);

Also applies to: 73-73, 79-79, 92-92

🧰 Tools
🪛 Biome (2.1.2)

[error] 67-67: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)

🤖 Prompt for AI Agents
In app/containers/message/Components/CurrentLocationCard.tsx around lines 67,
73, 79 and 92, replace the `[^]` usage in the regexes (e.g.
`maps\.apple\.com\/[^]*?[?&]ll=...`) with `[\s\S]` to avoid the Biome
static-analysis warning; update each match expression to use `[\s\S]*?` (or
`[\s\S]`-based equivalent) so the regex still matches any character including
newlines while satisfying the linter.

const rid = params.get('rid');
const msgId = params.get('msgId');
if (!rid || !(msgId || liveLocationId)) {
Alert.alert(I18n.t('Error'), I18n.t('Could_not_open_live_location'));
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 | 🟡 Minor

Use specific translation key instead of generic 'Error'.

Lines 182 and 190 use I18n.t('Error') which was flagged in past reviews as an invalid translation key. Use a more specific key that exists in the localization file.

Based on past review comments.

Apply this diff:

            if (!rid || !(msgId || liveLocationId)) {
-               Alert.alert(I18n.t('Error'), I18n.t('Could_not_open_live_location'));
+               Alert.alert(I18n.t('Could_not_open_live_location'), I18n.t('Please_try_again'));
                return;
            }
            // ...
        } else {
-           Alert.alert(I18n.t('Error'), I18n.t('Could_not_open_live_location'));
+           Alert.alert(I18n.t('Could_not_open_live_location'), I18n.t('Please_try_again'));
        }

Also applies to: 190-190

🤖 Prompt for AI Agents
In app/containers/message/Components/LiveLocationCard.tsx around lines 182 and
190, replace the generic I18n.t('Error') title with a specific, existing
translation key from the locale file (for example use a LiveLocation-specific
title key such as I18n.t('LiveLocation.errorTitle') or the exact key used for
live-location errors in your i18n files); update both Alert.alert calls to pass
that specific key so the alert title resolves correctly.

@Rohit3523
Copy link
Contributor

The lag might be caused by the large number of comments
It is because of more changes in the codebase ;-;

I’m considering creating another PR with the same code on a temporary branch based on this one
Duplicate PR will cause more confusion, so let's continue on this one

@Rohit3523
Copy link
Contributor

Hey @yiweigao0226 , I am going to review the pull request in the noon or evening (your local time), right now I am trying to wrap up few things 😅, sorry for the delay 😅.

@yiweigao0226
Copy link
Author

Hey @yiweigao0226 , I am going to review the pull request in the noon or evening (your local time), right now I am trying to wrap up few things 😅, sorry for the delay 😅.

No worries, take your time.

<Quote {...props} />
<Content {...props} />
<Attachments {...props} />
<Attachments {...props} id={props.id} rid={props.rid} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not pass id and rid here as a separate prop. We are passing whole props to Attachments component so will be able to access it from there

{!isLocation ? <Content {...props} /> : null}
{/* Render a card for "current location" messages; returns null otherwise */}
<CurrentLocationCard msg={props.msg as string} />
<Attachments {...props} id={props.id} rid={props.rid} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not pass id and rid here as a separate prop. We are passing whole props to Attachments component so will be able to access it from there

{props.isInfo && props.type === 'message_pinned' ? (
<View pointerEvents='none'>
<Attachments {...props} />
<Attachments {...props} id={props.id} rid={props.rid} />
Copy link
Contributor

Choose a reason for hiding this comment

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

Do not pass id and rid here as a separate prop. We are passing whole props to Attachments component so will be able to access it from there

);
}

/* eslint-disable react-native/no-unused-styles */
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove this comment

@Rohit3523
Copy link
Contributor

Also can you try to add this in the storybook for the UI testing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants