diff --git a/README.md b/README.md index 369d4a0..0a9acae 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,24 @@ Each app's README explains how to run it, how its sync works and what its tests ![Tic Tac Together demo: iOS and Android playing a game over Bluetooth](./apps/tictactogether/demo.gif) +### OutageNet + +![OutageNet demo: Android field unit and iOS command post coordinating over Bluetooth mesh](./apps/outagenet/demo.gif) + +### YardGate + +![YardGate demo: Android and iOS yard check-in with gate officer approve/deny over Bluetooth mesh](./apps/yardgate/demo.gif) + +### AgriMesh + +![AgriMesh demo: Android office edge and iOS field collector with readings over Bluetooth mesh](./apps/agrimesh/demo.gif) + +### Event Floor + +![Event Floor Flow B — Android lead scanner and iOS gate scanner syncing gate admissions over Bluetooth mesh](./apps/eventfloor/demo-gate.gif) + +![Event Floor Flow B — iOS gate scanner (ticket scans and duplicate detection)](./apps/eventfloor/demo-staff.gif) + ## Shared packages | Package | Path | Description | @@ -81,9 +99,6 @@ pnpm --filter @offline-app-examples/stocksync android Replace `stocksync` with `tictactogether`, `orderup`, `cowrite`, `outagenet`, `yardgate`, `eventfloor`, or `agrimesh` for the other apps. -### Event Floor - -Record `demo-staff.gif` and `demo-gate.gif` in [`apps/eventfloor`](./apps/eventfloor) and embed here when catalogue-ready (see app README). For iOS you can also open the app's `ios/*.xcworkspace` in Xcode, choose your team under Signing & Capabilities, and run on each phone. Debug builds load JavaScript from Metro; install a Release build to use an app away from your computer. diff --git a/apps/agrimesh/README.md b/apps/agrimesh/README.md index 7899505..74ba377 100644 --- a/apps/agrimesh/README.md +++ b/apps/agrimesh/README.md @@ -4,6 +4,8 @@ Agriculture demo: **store-and-forward** field readings over **Bluetooth mesh** u Built on `@offline-app-examples/mesh` (nearby rooms, mesh relay) and `@offline-app-examples/ui`. +![AgriMesh demo: Android office edge and iOS field collector](./demo.gif) + ## Roles | Role | Device | Behavior | @@ -43,10 +45,6 @@ Use **physical phones** with Bluetooth on. Debug builds need Metro. Clear app data or reinstall. Seeded plots and reading templates are fixed in `src/domain/plots.ts`. -## Catalogue GIF - -Add `demo.gif` in this folder after recording on devices (root README embed when ready). - ## Tests ```sh diff --git a/apps/agrimesh/demo.gif b/apps/agrimesh/demo.gif new file mode 100644 index 0000000..976c901 Binary files /dev/null and b/apps/agrimesh/demo.gif differ diff --git a/apps/eventfloor/README.md b/apps/eventfloor/README.md index feada8c..873bae4 100644 --- a/apps/eventfloor/README.md +++ b/apps/eventfloor/README.md @@ -4,6 +4,10 @@ Live-events demo app with **two flows** in one binary: **staff dispatch** when c Built on `@offline-app-examples/mesh` (rooms, MLS group mode, mesh relay) and `@offline-app-examples/ui`. +![Event Floor Flow B — Android lead scanner and iOS gate scanner over Bluetooth mesh](./demo-gate.gif) + +![Event Floor Flow B — iOS gate scanner (ticket scans and duplicate detection)](./demo-staff.gif) + ## Flow A — Staff dispatch - **Staff sender** hosts an MLS **group** room and broadcasts encrypted staff alerts (demo roster + PIN stand in for OfflineID sign-in). @@ -56,10 +60,6 @@ Use **physical phones** with Bluetooth on. Debug builds need Metro. Clear app data or reinstall. Seeded tickets and staff PINs are fixed in `src/domain/`. -## Catalogue GIFs - -Add `demo-staff.gif` and `demo-gate.gif` in this folder after recording on devices (root README embeds them). - ## Tests ```sh diff --git a/apps/eventfloor/demo-gate.gif b/apps/eventfloor/demo-gate.gif new file mode 100644 index 0000000..4684fcc Binary files /dev/null and b/apps/eventfloor/demo-gate.gif differ diff --git a/apps/eventfloor/demo-staff.gif b/apps/eventfloor/demo-staff.gif new file mode 100644 index 0000000..faeba12 Binary files /dev/null and b/apps/eventfloor/demo-staff.gif differ diff --git a/apps/eventfloor/src/flows/GateFlow.tsx b/apps/eventfloor/src/flows/GateFlow.tsx index f50b6db..75b7504 100644 --- a/apps/eventfloor/src/flows/GateFlow.tsx +++ b/apps/eventfloor/src/flows/GateFlow.tsx @@ -15,6 +15,7 @@ import { import { ingestToPlatform, unsyncedScanIds } from '../domain/platform'; import { DEMO_EVENT } from '../domain/tickets'; import { loadPersisted, savePersisted, sanitizeHost, sanitizeScanner } from '../services/persist'; +import { ConnectingScreen } from '../ui/common'; import { HostGateScreen } from '../ui/HostGateScreen'; import { RelayScreen } from '../ui/RelayScreen'; import { ScannerGateScreen } from '../ui/ScannerGateScreen'; @@ -271,5 +272,29 @@ export function GateFlow({ ); } - return null; + const leaveSession = () => { + void room.leave(); + onLeave(); + }; + + let message = 'Turn on Bluetooth and keep devices in range.'; + if (role === 'host') { + message = 'Starting the lead scanner and advertising over Bluetooth…'; + } else if (role === 'scanner') { + if (room.status === 'discovering' && room.hosts.length === 0) { + message = 'Looking for the lead scanner nearby. Start the lead gate on another phone first.'; + } else if (room.status === 'joining') { + message = 'Joining the lead scanner…'; + } else { + message = 'Connecting to the lead scanner…'; + } + } + + return ( + + ); } diff --git a/apps/eventfloor/src/flows/StaffFlow.tsx b/apps/eventfloor/src/flows/StaffFlow.tsx index 862a6a7..90e9879 100644 --- a/apps/eventfloor/src/flows/StaffFlow.tsx +++ b/apps/eventfloor/src/flows/StaffFlow.tsx @@ -6,6 +6,7 @@ import { type StaffAlert, type StaffSession, } from '../domain/staff'; +import { ConnectingScreen } from '../ui/common'; import { RelayScreen } from '../ui/RelayScreen'; import { StaffReceiverScreen } from '../ui/StaffReceiverScreen'; import { StaffSenderScreen } from '../ui/StaffSenderScreen'; @@ -156,5 +157,29 @@ export function StaffFlow({ ); } - return null; + const leaveSession = () => { + void room.leave(); + onLeave(); + }; + + let message = 'Turn on Bluetooth and keep devices in range.'; + if (role === 'sender') { + message = 'Starting the staff channel…'; + } else if (role === 'receiver') { + if (room.status === 'discovering' && room.hosts.length === 0) { + message = 'Looking for the staff sender nearby. Start the sender on another phone first.'; + } else if (room.status === 'joining') { + message = 'Joining the staff channel…'; + } else { + message = 'Connecting to staff dispatch…'; + } + } + + return ( + + ); } diff --git a/apps/eventfloor/src/ui/common.tsx b/apps/eventfloor/src/ui/common.tsx index eed50c3..6ed8420 100644 --- a/apps/eventfloor/src/ui/common.tsx +++ b/apps/eventfloor/src/ui/common.tsx @@ -2,6 +2,7 @@ import { Button, cn, Icon, PersonAvatar, Text } from '@offline-app-examples/ui'; import { LogOut } from 'lucide-react-native'; import React, { useEffect, useState } from 'react'; import { View } from 'react-native'; +import { SafeAreaView } from 'react-native-safe-area-context'; type Person = { id: string; name: string }; @@ -43,6 +44,34 @@ export function TopBar({ ); } +export function ConnectingScreen({ + title = 'Connecting…', + message, + error, + onLeave, +}: { + title?: string; + message?: string; + error?: string; + onLeave: () => void; +}) { + return ( + + + + + + {error ? 'Could not connect' : title} + + {error ?? message ?? 'Turn on Bluetooth and keep devices in range.'} + + + + ); +} + export function DebugStrip({ lines }: { lines: string[] }) { return ( diff --git a/apps/outagenet/README.md b/apps/outagenet/README.md index 750e800..b72e61b 100644 --- a/apps/outagenet/README.md +++ b/apps/outagenet/README.md @@ -4,6 +4,8 @@ Field coordination when towers are down. One phone runs the **command post**; ot Built on `@offline-app-examples/mesh` and `@offline-app-examples/ui`, Mesh SDK 0.28. +![OutageNet demo: Android field unit and iOS command post](./demo.gif) + ## What it demonstrates - Host-authoritative ops log with **stable `operationId`** on every record (retries and mesh resends dedupe). diff --git a/apps/outagenet/demo.gif b/apps/outagenet/demo.gif new file mode 100644 index 0000000..e2b785e Binary files /dev/null and b/apps/outagenet/demo.gif differ diff --git a/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj b/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj index af092f8..57dc9a2 100644 --- a/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj +++ b/apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj @@ -13,10 +13,10 @@ 4DADA4D9FCF7ACF7CD308512 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; 59FA85C7DABD4C424CC92A6D /* dm_serif_display_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0DDFFF2E5319DC6CB07FB356 /* dm_serif_display_regular.ttf */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; - B2E8F1A12CA45701006654EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E8F1A02CA45701006654EE /* SceneDelegate.swift */; }; 77425900FDE6A239FBDE1587 /* space_grotesk_semibold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 16DDDA6F0AE168DD28C46529 /* space_grotesk_semibold.ttf */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; ACF648FCD514FC7967494DCF /* space_grotesk_regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */; }; + B2E8F1A12CA45701006654EE /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2E8F1A02CA45701006654EE /* SceneDelegate.swift */; }; C79EC88C8F01E8069AAC7EB9 /* space_grotesk_bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */; }; /* End PBXBuildFile section */ @@ -31,11 +31,11 @@ 5709B34CF0A7D63546082F79 /* Pods-OutageNet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OutageNet.release.xcconfig"; path = "Target Support Files/Pods-OutageNet/Pods-OutageNet.release.xcconfig"; sourceTree = ""; }; 5DCACB8F33CDC322A6C60F78 /* libPods-OutageNet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OutageNet.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = OutageNet/AppDelegate.swift; sourceTree = ""; }; - B2E8F1A02CA45701006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OutageNet/SceneDelegate.swift; sourceTree = ""; }; 7D7566AB5B03E0DD2AE8D975 /* space_grotesk_medium.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_medium.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_medium.ttf; sourceTree = SOURCE_ROOT; }; 8077B93F1BA98961C21AE16E /* space_grotesk_regular.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_regular.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_regular.ttf; sourceTree = SOURCE_ROOT; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = OutageNet/LaunchScreen.storyboard; sourceTree = ""; }; A7E51F5E996CDBEB4D2962DC /* space_grotesk_bold.ttf */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = file; name = space_grotesk_bold.ttf; path = ../../../packages/ui/fonts/font/space_grotesk_bold.ttf; sourceTree = SOURCE_ROOT; }; + B2E8F1A02CA45701006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OutageNet/SceneDelegate.swift; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -222,14 +222,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-frameworks.sh\"\n"; @@ -265,14 +261,10 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-resources-${CONFIGURATION}-output-files.xcfilelist", ); - outputPaths = ( - ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-OutageNet/Pods-OutageNet-resources.sh\"\n"; diff --git a/apps/outagenet/ios/Podfile.lock b/apps/outagenet/ios/Podfile.lock index 5a2dd3d..b1ea17d 100644 --- a/apps/outagenet/ios/Podfile.lock +++ b/apps/outagenet/ios/Podfile.lock @@ -2383,4 +2383,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 511362855f8d78cc3f03e1db12273c7a024db43d -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/apps/yardgate/README.md b/apps/yardgate/README.md index 38fddac..6a0e622 100644 --- a/apps/yardgate/README.md +++ b/apps/yardgate/README.md @@ -4,6 +4,8 @@ Logistics yard check-in when there is no cellular: a **driver** discovers a **ga Mesh SDK **0.28** — `MeshServices` discover + invoke only (no Proof of Location; invoke bodies are signed plaintext — no secrets). Service responses use SDK status strings **`ok`**, **`error`**, or **`not_found`** (not HTTP codes like `200`). +![YardGate demo: Android and iOS yard check-in over Bluetooth mesh](./demo.gif) + ## What it demonstrates - **`yardgate-gate` service** — register on the gate device, discover from the driver (multi-hop when relay is in the middle). diff --git a/apps/yardgate/demo.gif b/apps/yardgate/demo.gif new file mode 100644 index 0000000..e0203c4 Binary files /dev/null and b/apps/yardgate/demo.gif differ diff --git a/apps/yardgate/ios/Podfile.lock b/apps/yardgate/ios/Podfile.lock index b426b1a..9ff07da 100644 --- a/apps/yardgate/ios/Podfile.lock +++ b/apps/yardgate/ios/Podfile.lock @@ -2357,4 +2357,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 2410de8918b31340e61afe18d4d24a52d58b6378 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2