Android host: reachable joins + background hosting + icon + guidance#41
Android host: reachable joins + background hosting + icon + guidance#41JosephMaynard wants to merge 2 commits into
Conversation
…spot guidance The main issue: joiners connected to the hotspot but the Step-2 QR (the URL to open LOAM) wouldn't load. It was hardcoded to http://192.168.49.1:3000 — the stock-Android LocalOnlyHotspot gateway, which isn't guaranteed across devices, so on a device that uses a different subnet every joiner fails identically. Fix — report the host's real addresses instead of guessing: - The launcher (main.js) enumerates os.networkInterfaces() and posts the host's non-internal IPv4 addresses over a new `loam-hostinfo` channel — once at ready, on a 5s refresh (the hotspot's AP interface only appears once the hotspot is up), and on demand when the Share overlay opens. - The host screen builds the Step-2 QR from the reported 192.168.49.x address when present (falling back to the documented default), and lists every detected address under the QR so a joiner can try an alternative — and tell us which worked, so we can harden the picker. App icon: - Use apps/client/src/assets/loam-app-icon.png (copied to assets/images/ loam-icon.png) for the launcher icon, adaptive icon (ember #d14900 background), iOS icon, web favicon, and splash. Replaces the Expo scaffold defaults. Guidance: - In-app hint (Step 1) + docs/04: don't enable the phone's own hotspot while hosting (Android runs one or the other, not both); keep the app foregrounded; how to pick a working Step-2 address; note client-isolation as a possible cause. Validated with `npx tsc --noEmit` (apps/app has no test script). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe app now reports launcher-discovered host IP addresses, uses them to build join URLs and fallback hints in the host UI, adds an Android foreground host service, and updates Expo branding assets and Android hosting documentation. ChangesHotspot address reporting and UI wiring
Foreground host service
App icon and splash assets
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant RNHost as React Native host
participant NodeTemplate as nodejs-project-template
participant OS as os.networkInterfaces
RNHost->>NodeTemplate: loam-hostinfo-request
NodeTemplate->>OS: enumerate IPv4 addresses
OS-->>NodeTemplate: host addresses
NodeTemplate-->>RNHost: loam-hostinfo { port, addresses }
loop every 5 seconds after ready
NodeTemplate->>OS: enumerate IPv4 addresses
NodeTemplate-->>RNHost: loam-hostinfo { port, addresses }
end
sequenceDiagram
participant JS as loam-hotspot JS
participant Module as LoamHotspotModule
participant Service as LoamHostService
participant Android as Android system
JS->>Module: startHostService()
Module->>Service: start(context)
Android->>Service: onStartCommand
Service->>Android: start foreground notification
Service->>Android: acquire wake lock
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/app/app.json`:
- Around line 25-32: The splash branding change is only partial because the
post-splash animation still uses the Expo asset. Update the asset reference in
animated-icon.tsx (the AnimatedIcon component) so it matches the LOAM branding
used in app.json, and make sure the overlay shown after hideAsync() no longer
points to expo-logo.png. Keep the native splash and the post-splash transition
consistent by swapping that image source to the LOAM icon asset.
In `@apps/app/src/app/index.tsx`:
- Around line 27-30: The hotspotJoinUrl helper is still inferring the hotspot
host by matching 192.168.49.x and falling back to a hardcoded gateway, which can
produce the wrong QR target on modern Android. Replace the IP-prefix logic in
hotspotJoinUrl with launcher-provided hotspot metadata from loam-hostinfo, and
use that source to build the Step-2 URL instead of guessing from raw addresses.
Keep the change localized to hotspotJoinUrl and the app startup flow that
consumes its result so the QR always points at the advertised host.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9332c4b4-fafc-4b22-9e49-6ec170da968c
⛔ Files ignored due to path filters (1)
apps/app/assets/images/loam-icon.pngis excluded by!**/*.png
📒 Files selected for processing (6)
apps/app/app.jsonapps/app/nodejs-project-template/main.jsapps/app/src/app/index.tsxapps/app/src/components/host-panel.tsxapps/app/src/components/host-share-overlay.tsxdocs/04-android-host-app.md
| "plugins": [ | ||
| "expo-router", | ||
| [ | ||
| "expo-splash-screen", | ||
| { | ||
| "backgroundColor": "#208AEF", | ||
| "image": "./assets/images/splash-icon.png", | ||
| "imageWidth": 76 | ||
| "backgroundColor": "#d14900", | ||
| "image": "./assets/images/loam-icon.png", | ||
| "imageWidth": 200 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep the post-splash overlay on LOAM branding too.
This updates the native splash to loam-icon.png, but apps/app/src/components/animated-icon.tsx still shows expo-logo.png after hideAsync(). Users will still see Expo branding immediately after launch, so the asset swap is only partial.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/app/app.json` around lines 25 - 32, The splash branding change is only
partial because the post-splash animation still uses the Expo asset. Update the
asset reference in animated-icon.tsx (the AnimatedIcon component) so it matches
the LOAM branding used in app.json, and make sure the overlay shown after
hideAsync() no longer points to expo-logo.png. Keep the native splash and the
post-splash transition consistent by swapping that image source to the LOAM icon
asset.
| function hotspotJoinUrl(addresses: string[]): string { | ||
| const apAddress = addresses.find((address) => address.startsWith('192.168.49.')); | ||
| return apAddress ? `http://${apAddress}:${SERVER_PORT}` : HOTSPOT_GATEWAY_FALLBACK; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- files ---\n'
git ls-files | rg 'apps/app/src/app/index.tsx|main\.js|lanAddresses|HostPanel|qr|hotspot'
printf '\n--- hotspots ---\n'
rg -n "hotspotJoinUrl|lanAddresses|HOTSPOT_GATEWAY_FALLBACK|192\.168\.49\.|Step 2|HostPanel" apps/app/src -S
printf '\n--- index.tsx excerpt ---\n'
sed -n '1,120p' apps/app/src/app/index.tsx
printf '\n--- main.js excerpt if present ---\n'
if [ -f apps/app/src/main.js ]; then
sed -n '1,220p' apps/app/src/main.js
fiRepository: JosephMaynard/loam
Length of output: 247
🌐 Web query:
Android local only hotspot gateway subnet randomized third octet Android 9 11 hotspot IP 192.168.49 192.168.43 documentation
💡 Result:
In Android versions 9 through 11 (and continuing into later versions), the IP address assigned to the mobile hotspot gateway is no longer fixed to 192.168.43.1 [1]. Instead, Android uses a randomized mechanism to select a private subnet within the 192.168.0.0/16 address space to avoid conflicts with upstream networks [2]. Technical Implementation: The randomization is handled by a component known as the PrivateAddressCoordinator [2]. When a hotspot (or other tethering interface) is activated, this coordinator dynamically selects an available /24 subnet from the 192.168.0.0/16 range [2]. The selection logic ensures that the chosen subnet does not conflict with currently connected upstream networks [2]. Key behavioral changes include: - Randomized Subnet: The third octet (e.g., in 192.168.x.1) is randomized [2][1]. While legacy Android versions predominantly used 192.168.43.x, modern implementations select dynamically to improve connectivity reliability and prevent routing conflicts [2][3]. - Local-Only Hotspot: Local-only hotspots follow this same dynamic assignment pattern, as they share the same underlying tethering infrastructure [4][5]. - Hardcoded Defaults vs. Dynamic Allocation: While older versions of the Android source code contained hardcoded defaults (e.g., 192.168.42.x for USB, 192.168.43.x for Wi-Fi) [6][7], these are now treated as fallback values or configuration references rather than fixed assignments [2][8]. Because the gateway IP changes upon turning the hotspot off and on, hardcoding static IP configurations on client devices will frequently fail to maintain connectivity [1]. Users requiring specific network configurations typically must either accept the dynamic IP assignment or use rooted devices/custom ROMs to override these system-level behaviors [9][3].
Citations:
- 1: https://engsig.com/tethering-vpn-on-android/
- 2: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-11.0.0_r45/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java?autodive=0%2F
- 3: https://android.stackexchange.com/questions/214044/how-to-change-the-default-hotspot-dhcp-ip-address-range-on-android
- 4: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-9.0.0_r42/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
- 5: https://android.googlesource.com/platform/frameworks/base/+/d12e150f7a1921bae738791e37950fb8ffdeafb3/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
- 6: https://android.googlesource.com/platform/frameworks/base/+/b3eeb859f7760785fee6d65fc0bad6ac27f7c614/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
- 7: https://android.googlesource.com/platform/prebuilts/fullsdk/sources/android-28/+/b8042fc9b036db0a6692ca853428fc6ab1e60892/com/android/server/connectivity/tethering/TetheringConfiguration.java
- 8: https://android.googlesource.com/platform/packages/modules/Connectivity/+/refs/tags/android-mainline-11.0.0_r4/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
- 9: https://android.stackexchange.com/questions/46499/how-configure-the-dhcp-settings-of-wifi-tethering-hotspot-on-android
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- files ---'
git ls-files | rg '^(apps/app/src/app/index\.tsx|apps/app/src/main\.js|apps/app/src/.*HostPanel.*|apps/app/src/.*hotspot.*|apps/app/src/.*qr.*)$'
echo
echo '--- search ---'
rg -n "hotspotJoinUrl|lanAddresses|HOTSPOT_GATEWAY_FALLBACK|192\.168\.49\.|HostPanel|Step 2|qr" apps/app/src -S
echo
echo '--- index.tsx ---'
sed -n '1,140p' apps/app/src/app/index.tsx
echo
echo '--- main.js ---'
if [ -f apps/app/src/main.js ]; then
sed -n '1,260p' apps/app/src/main.js
else
echo 'apps/app/src/main.js not found'
fiRepository: JosephMaynard/loam
Length of output: 8989
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- hostinfo references ---'
rg -n "loam-hostinfo|hostAddresses|addresses: string\[]|name, address|interface" apps/app/src -S
echo
echo '--- host-panel ---'
sed -n '1,220p' apps/app/src/components/host-panel.tsx
echo
echo '--- host-share-overlay ---'
sed -n '1,180p' apps/app/src/components/host-share-overlay.tsxRepository: JosephMaynard/loam
Length of output: 10266
Stop guessing the hotspot IP by 192.168.49.x
hotspotJoinUrl still falls back to 192.168.49.1, but modern Android can assign a different hotspot subnet, so the Step-2 QR can point at the wrong host. loam-hostinfo only exposes raw addresses today, so this needs launcher metadata instead of IP-prefix matching.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/app/src/app/index.tsx` around lines 27 - 30, The hotspotJoinUrl helper
is still inferring the hotspot host by matching 192.168.49.x and falling back to
a hardcoded gateway, which can produce the wrong QR target on modern Android.
Replace the IP-prefix logic in hotspotJoinUrl with launcher-provided hotspot
metadata from loam-hostinfo, and use that source to build the Step-2 URL instead
of guessing from raw addresses. Keep the change localized to hotspotJoinUrl and
the app startup flow that consumes its result so the QR always points at the
advertised host.
…tionale, smaller icon Follow-ups from on-device testing: - Join address: the picker fell back to the hardcoded 192.168.49.1 whenever no 192.168.49.x was reported — but on a host that's on an existing WiFi the working address is its LAN IP (e.g. 192.168.0.197, which is what actually loaded in testing). Now prefer, in order: the stock hotspot AP address, a regular LAN address, any private address, then anything reported — only falling back to the hardcoded default when nothing has been reported yet. - Background hosting: a foreground service (LoamHostService) with a persistent notification + partial wake lock keeps the embedded server and hotspot alive when the screen locks. Started when the host becomes ready; declared via the config plugin with the FOREGROUND_SERVICE / _CONNECTED_DEVICE / WAKE_LOCK / POST_NOTIFICATIONS permissions and a `connectedDevice` service type. Coded defensively (every native entry point is try/caught) so a device that refuses the service degrades to foreground-only hosting rather than crashing. - Location prompt: added a rationale on the host screen — Android requires location permission to create a WiFi hotspot; LOAM never uses or stores it. - App icon: use the owner's smaller-text version for the launcher/adaptive/iOS/ favicon; keep the previous (larger-text) image for the splash, which was fine. Validated: app `tsc --noEmit`, plugin `node --check`, app.json parse. The Kotlin service + manifest are validated by the gradle release build. Runtime background behaviour needs on-device confirmation. CodeRabbit not run — quota exhausted. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.kt (1)
66-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNotification small icon likely to render incorrectly.
setSmallIcon(applicationInfo.icon)uses the full launcher icon (now the branded LOAM icon per this PR stack) as the status-bar/notification icon. Android requires small icons to be flat, single-color/alpha silhouettes; a full-color launcher icon is typically auto-tinted to a solid white blob by the system, degrading the "LOAM is hosting" notification's appearance for the entire duration of hosting.Add a dedicated monochrome notification icon drawable and reference that instead of
applicationInfo.icon.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.kt` around lines 66 - 90, The hosting notification in LoamHostService uses the app launcher icon as the small icon, which will render poorly in the status bar. Update the notification setup in LoamHostService’s builder chain to use a dedicated monochrome notification drawable instead of applicationInfo.icon, and keep the existing content/title/ongoing/foreground-service behavior unchanged.apps/app/app.json (1)
7-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReusing the full-bleed icon as the adaptive-icon foreground risks clipping.
loam-icon.pngis used forexpo.icon(full-bleed, no safe zone needed) and also forandroid.adaptiveIcon.foregroundImage(Line 17). Android adaptive icons only guarantee the centered 528×528 region of a 1024×1024 canvas is always visible; different launcher masks (circle, squircle, teardrop) crop the rest. Ifloam-icon.pngwasn't designed with that safe zone in mind, the foreground will be cropped on many devices/launchers.Verify the asset has sufficient padding for the adaptive-icon safe zone, or provide a dedicated foreground asset with the logo confined to the centered safe area.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/app/app.json` around lines 7 - 24, The Android adaptive icon is reusing the same full-bleed image as the app icon, which can cause launcher mask clipping. Update the expo config in app.json under android.adaptiveIcon.foregroundImage to use an asset designed for the adaptive-icon safe zone, or confirm loam-icon.png has enough centered padding for Android’s 528×528 visible region. Keep the existing expo.icon usage for the regular app icon, and only change the adaptiveIcon foreground asset/reference.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.kt`:
- Line 47: The foreground service setup in
LoamHostService.startForegroundNotification needs a runtime notification
permission check before posting the foreground notification. Add the
POST_NOTIFICATIONS request into the hotspot flow so it is requested on Android
13+ before the service is started, and make sure the start path only proceeds
after permission is granted; use the existing startForegroundNotification method
and the hotspot service entry points to place the permission request in the
right flow.
---
Nitpick comments:
In `@apps/app/app.json`:
- Around line 7-24: The Android adaptive icon is reusing the same full-bleed
image as the app icon, which can cause launcher mask clipping. Update the expo
config in app.json under android.adaptiveIcon.foregroundImage to use an asset
designed for the adaptive-icon safe zone, or confirm loam-icon.png has enough
centered padding for Android’s 528×528 visible region. Keep the existing
expo.icon usage for the regular app icon, and only change the adaptiveIcon
foreground asset/reference.
In
`@apps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.kt`:
- Around line 66-90: The hosting notification in LoamHostService uses the app
launcher icon as the small icon, which will render poorly in the status bar.
Update the notification setup in LoamHostService’s builder chain to use a
dedicated monochrome notification drawable instead of applicationInfo.icon, and
keep the existing content/title/ongoing/foreground-service behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 08fb7256-e9d1-427b-942b-febe0c7c6402
⛔ Files ignored due to path filters (2)
apps/app/assets/images/loam-icon.pngis excluded by!**/*.pngapps/app/assets/images/loam-splash.pngis excluded by!**/*.png
📒 Files selected for processing (8)
apps/app/app.jsonapps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.ktapps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHotspotModule.ktapps/app/modules/loam-hotspot/index.tsapps/app/modules/loam-hotspot/src/LoamHotspotModule.tsapps/app/plugins/with-loam-host.jsapps/app/src/app/index.tsxapps/app/src/components/host-panel.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/app/src/components/host-panel.tsx
- apps/app/src/app/index.tsx
| super.onDestroy() | ||
| } | ||
|
|
||
| private fun startForegroundNotification() { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether POST_NOTIFICATIONS is requested at runtime anywhere in the app
rg -n "POST_NOTIFICATIONS" -g '*.kt' -g '*.tsx' -g '*.ts'Repository: JosephMaynard/loam
Length of output: 156
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== files =='
git ls-files | rg '(^|/)(LoamHostService\.kt|with-loam-host\.js|AndroidManifest\.xml|MainActivity\.(kt|java)|.*Permission.*\.(kt|java|ts|tsx|js)|.*notification.*\.(kt|java|ts|tsx|js))$' || true
echo
echo '== search for permission request APIs =='
rg -n "POST_NOTIFICATIONS|requestPermissions|ActivityCompat\.requestPermissions|registerForActivityResult|RequestPermission|NotificationManager|notification permission|Permission" apps . -g '*.kt' -g '*.java' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.xml' || true
echo
echo '== LoamHostService.kt =='
sed -n '1,180p' apps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.kt
echo
echo '== with-loam-host.js =='
sed -n '1,220p' apps/app/modules/loam-hotspot/with-loam-host.js
echo
echo '== manifests =='
for f in $(git ls-files | rg 'AndroidManifest\.xml$'); do
echo "--- $f ---"
sed -n '1,220p' "$f"
doneRepository: JosephMaynard/loam
Length of output: 8094
🌐 Web query:
Android 13 POST_NOTIFICATIONS foreground service notification exemption POST_NOTIFICATIONS required for foreground service notification
💡 Result:
In Android 13 (API level 33) and higher, the POST_NOTIFICATIONS runtime permission affects how foreground service notifications are presented to the user, but it does not exempt a foreground service from requiring a notification [1][2][3]. Key details regarding this requirement: 1. Mandatory Notification: When you start a foreground service by calling startForeground, you must still provide a notification object [4][5][6]. This requirement remains unchanged from previous versions of Android [4]. 2. Visibility Impact: The POST_NOTIFICATIONS permission determines where the user sees that notification [2][3]. - If the user grants the permission, the foreground service notification appears in the notification drawer as expected [2][3]. - If the user denies the permission, the notification is not shown in the notification drawer [2][3]. However, the system ensures the user is still aware of the service by displaying it in the Foreground Services (FGS) Task Manager [2][3]. 3. No Exemption: There is no exemption that allows an app to run a foreground service without providing a notification [4]. The system design ensures that background processes are always discoverable by the user, either through the notification panel or the dedicated task manager [2][3]. While apps must declare POST_NOTIFICATIONS in their AndroidManifest.xml, they do not strictly need to be granted this permission to successfully launch a foreground service [4]. If the permission is not granted, the service will still run, but the associated notification will not be visible to the user in the notification shade [2][4][3].
Citations:
- 1: https://developer.android.com/develop/ui/compose/notifications/notification-permission
- 2: https://developer.android.com/about/versions/13/behavior-changes-13
- 3: https://stackoverflow.com/questions/72664186/why-did-android-13-remove-the-foreground-service-notification
- 4: https://stackoverflow.com/questions/76791913/android-13-why-do-we-still-need-to-include-post-notifications-permission-in-an
- 5: https://developer.android.com/develop/ui/views/notifications/time-sensitive
- 6: https://developer.android.com/develop/background-work/services/fgs/launch
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '== with-loam-host.js =='
sed -n '1,180p' apps/app/plugins/with-loam-host.js
echo
echo '== use-hotspot.ts =='
sed -n '1,180p' apps/app/src/hooks/use-hotspot.ts
echo
echo '== repo-wide POST_NOTIFICATIONS search =='
rg -n "POST_NOTIFICATIONS|requestMultiple\\(|requestPermissions|RequestPermission|registerForActivityResult|ActivityCompat\\.requestPermissions" \
apps/app -g '*.kt' -g '*.java' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.xml' || trueRepository: JosephMaynard/loam
Length of output: 12717
Request POST_NOTIFICATIONS before starting the foreground service. The manifest entry alone won’t make the notification visible on Android 13+, and no runtime request exists in the hotspot flow.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/app/modules/loam-hotspot/android/src/main/java/expo/modules/loamhotspot/LoamHostService.kt`
at line 47, The foreground service setup in
LoamHostService.startForegroundNotification needs a runtime notification
permission check before posting the foreground notification. Add the
POST_NOTIFICATIONS request into the hotspot flow so it is requested on Android
13+ before the service is started, and make sure the start path only proceeds
after permission is granted; use the existing startForegroundNotification method
and the hotspot service entry points to place the permission request in the
right flow.
Android host: reachable joins, background hosting, icon, guidance
On-device testing (thanks!) confirmed the two-step join works and surfaced the real issues. This PR covers both rounds.
Join address (the main bug — fixed twice)
The Step-2 QR was hardcoded to
http://192.168.49.1:3000(the stock LocalOnlyHotspot gateway, not guaranteed).main.js) now reports the host's real IPv4 addresses over aloam-hostinfochannel, and the host screen lists them under the QR.192.168.0.197) when the host is on an existing WiFi — but the picker still fell back to the hardcoded192.168.49.1because no192.168.49.xwas reported. The picker now prefers, in order: the stock hotspot AP address → a regular LAN address → any private address → anything reported → the hardcoded default only when nothing's been reported. So the common "everyone on the same WiFi" case now works out of the box.Background hosting (survives screen-lock)
A foreground service (
LoamHostService) with a persistent notification + partial wake lock keeps the embedded server and hotspot alive when the screen locks. Started when the host becomes ready; declared via the config plugin withFOREGROUND_SERVICE/_CONNECTED_DEVICE/WAKE_LOCK/POST_NOTIFICATIONSand aconnectedDeviceservice type. Coded defensively — every native entry point is try/caught, so a device that refuses the service degrades to foreground-only hosting rather than crashing. (Runtime behaviour needs your on-device confirmation.)Location prompt
Added a rationale on the host screen: Android requires location permission to create a WiFi hotspot; LOAM never uses, requests, or stores your location.
App icon
Uses your icon for the launcher / adaptive (ember
#d14900bg) / iOS / favicon. Round 2 swaps in your smaller-text version and keeps the previous (larger-text) image for the splash, which you said was fine.Guidance
In-app hint (Step 1) +
docs/04: don't enable the phone's own hotspot while hosting; keep the app foregrounded; how to pick a working Step-2 address.Validation
App
tsc --noEmit, pluginnode --check, app.json parse — all clean. The Kotlin service + manifest are validated by the gradle release build. CodeRabbit not run — quota exhausted; self-reviewed.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation