eWall displays one static image across 2-4 nearby iPhone and Android phones. One phone hosts the session and also acts as a wall tile; the others join by scanning a QR code.
- iOS 16 or later and Android API 24 or later
- 2-4 matching-size phones in any iOS/Android combination on shared Wi-Fi
- 1x2, 1x3, and 2x2 layouts
- Portrait or landscape across the entire wall
- Generated calibration pattern or a Photo Library image
- HEIC/JPEG/PNG input normalized to a bounded JPEG on the host
- Global fit/fill, pan, zoom, and uniform bezel-gap compensation
- SHA-256 verified image transfer and edge-to-edge presentation
Personal Hotspot, guest networks with client isolation, mixed physical display sizes, video, and automatic calibration are not part of this milestone.
Install Flutter stable and CocoaPods, then run:
flutter pub get
flutter analyze
flutter test
flutter runFor a physical iPhone, select an Apple development team for the Runner target in
Xcode. The bundle identifier is com.jpolguin.ewall.
For Android, install Android Studio with an API 36 SDK and accept the Android
licenses, then run flutter doctor -v. Android hosts and clients use the same
application ID, com.jpolguin.ewall.
The first create/join operation triggers iOS local-network permission. Clients also need camera permission to scan the QR. All devices must be on a LAN that allows direct TCP traffic between peers.
Debug builds expose a camera-free join path. Start the app on two to four
simulators, create a wall on one, then use Copy join code on the host and
Paste join code on each client. The paste action validates the same
authenticated ewall:// URI used by the QR scanner, so it exercises the real
WebSocket session, image download, readiness, layout, and presentation paths.
List and boot simulator devices:
flutter devices
open -a Simulator
xcrun simctl boot <SIMULATOR_UDID>Run one terminal process per simulator:
flutter run -d <HOST_SIMULATOR_UDID>
flutter run -d <CLIENT_SIMULATOR_UDID>Simulator pasteboards do not always synchronize with one another. After using Copy join code on the host, move its pasteboard to a client explicitly when needed:
xcrun simctl pbpaste <HOST_SIMULATOR_UDID> | xcrun simctl pbcopy <CLIENT_SIMULATOR_UDID>Repeat that command for each client. The copy/paste controls are compiled out of release builds; physical-device joining continues to use the QR scanner.
The primary mixed test uses four total phones, not four Android phones:
iPhone host | Android client
------------+---------------
iPhone | Android client
On physical phones, connect every device to the same Wi-Fi, create the session on either platform, and scan the same QR code from every client. The host layout cards show each screen number and platform.
For an iPhone Simulator host plus Android Emulator clients, the iPhone join code advertises the Mac's Wi-Fi address and can normally be pasted directly into the Android emulators. Android emulators have isolated virtual networks, so an Android Emulator host needs a debug forwarding configuration:
adb -s <ANDROID_HOST_SERIAL> forward tcp:41414 tcp:41414
flutter run -d <ANDROID_HOST_SERIAL> \
--dart-define=EWALL_BIND_PORT=41414 \
--dart-define=EWALL_ADVERTISED_HOST=10.0.2.2Android emulator clients use the advertised 10.0.2.2 code. An iPhone
Simulator client uses the same code with its host query parameter changed to
127.0.0.1, because 10.0.2.2 is an Android-emulator-only host alias. These
overrides are ignored by release builds.
lib/wall/contains the pure virtual-canvas geometry and renderer.lib/session/contains the versioned protocol, host/client transport, and authoritative session state.lib/media/selects, normalizes, hashes, caches, and validates images.ios/Runner/AppDelegate.swiftprovides ImageIO-based HEIC/JPEG/PNG normalization.android/app/src/main/kotlin/provides Android image normalization and forward-compatible local-network permission handling.
The host uses one authenticated dart:io HTTP server and port for both WebSocket
control and image download. Traffic is local and bearer-authenticated but is not
encrypted, so this milestone does not provide confidentiality against passive
observers on the Wi-Fi network. Android explicitly permits cleartext traffic
because the host address is a dynamic LAN IP that cannot be allow-listed as a
fixed domain.