Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 2 additions & 4 deletions apps/agrimesh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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
Expand Down
Binary file added apps/agrimesh/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions apps/eventfloor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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
Expand Down
Binary file added apps/eventfloor/demo-gate.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/eventfloor/demo-staff.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 26 additions & 1 deletion apps/eventfloor/src/flows/GateFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 (
<ConnectingScreen
message={message}
error={room.status === 'error' ? room.error : undefined}
onLeave={leaveSession}
/>
);
}
27 changes: 26 additions & 1 deletion apps/eventfloor/src/flows/StaffFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 (
<ConnectingScreen
message={message}
error={room.status === 'error' ? room.error : undefined}
onLeave={leaveSession}
/>
);
}
29 changes: 29 additions & 0 deletions apps/eventfloor/src/ui/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -43,6 +44,34 @@ export function TopBar({
);
}

export function ConnectingScreen({
title = 'Connecting…',
message,
error,
onLeave,
}: {
title?: string;
message?: string;
error?: string;
onLeave: () => void;
}) {
return (
<SafeAreaView className="bg-background flex-1" edges={['top', 'left', 'right']}>
<View className="border-foreground flex-row justify-end border-b-2 px-5 pb-3 pt-2">
<Button variant="outline" size="sm" onPress={onLeave}>
<Text>Leave</Text>
</Button>
</View>
<View className="flex-1 items-center justify-center px-8">
<Text className="font-serif text-center text-2xl">{error ? 'Could not connect' : title}</Text>
<Text className="text-muted-foreground mt-3 text-center text-base leading-6">
{error ?? message ?? 'Turn on Bluetooth and keep devices in range.'}
</Text>
</View>
</SafeAreaView>
);
}

export function DebugStrip({ lines }: { lines: string[] }) {
return (
<View className="border-foreground bg-muted/40 border-t-2 px-4 py-2">
Expand Down
2 changes: 2 additions & 0 deletions apps/outagenet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Binary file added apps/outagenet/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 2 additions & 10 deletions apps/outagenet/ios/OutageNet.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand All @@ -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 = "<group>"; };
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 = "<group>"; };
B2E8F1A02CA45701006654EE /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SceneDelegate.swift; path = OutageNet/SceneDelegate.swift; sourceTree = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion apps/outagenet/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2383,4 +2383,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 511362855f8d78cc3f03e1db12273c7a024db43d

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
2 changes: 2 additions & 0 deletions apps/yardgate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
Binary file added apps/yardgate/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/yardgate/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2357,4 +2357,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: 2410de8918b31340e61afe18d4d24a52d58b6378

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
Loading