diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..50b76a1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,95 @@
+# Contributing to OpenPelo
+
+This guide covers building OpenPelo, updating its app catalog, and refreshing
+the documentation screenshots. For installation and everyday use, see the
+[README](./README.md).
+
+## Build from source
+
+### Requirements
+
+- Flutter 3.44.4 or newer
+- Dart 3.10 or newer
+- Platform prerequisites from Flutter's desktop setup guide
+- Inno Setup 6 when creating the Windows installer
+
+```powershell
+flutter pub get
+flutter analyze
+flutter test
+flutter run -d windows
+```
+
+Use `-d macos` or `-d linux` on those platforms. Release bundles are created
+with:
+
+```powershell
+flutter build windows --release
+flutter build macos --release
+flutter build linux --release
+```
+
+See [DISTRIBUTION.md](./DISTRIBUTION.md) for packaging details.
+
+### Refresh the README screenshots
+
+The [screenshot script](./tool/capture_readme_screenshots_test.dart) renders the
+production screens and dialogs with sample data. It does not initialize ADB or
+connect to a device. From the repository root, run:
+
+```powershell
+flutter test tool/capture_readme_screenshots_test.dart
+```
+
+It writes five PNGs to [`images/screenshots`](./images/screenshots). The script
+uses Windows Segoe UI by default; on another platform, set
+`OPENPELO_SCREENSHOT_FONT` to the absolute path of a local TrueType font.
+Review the generated images before committing them with UI changes.
+
+## Application catalog
+
+Applications are defined in `apps_config.json`:
+
+```json
+{
+ "apps": {
+ "Example App": {
+ "url": "https://github.com/example/app/releases/latest",
+ "asset_name": "example-arm64.apk",
+ "asset_pattern": "example-*-arm64.apk",
+ "package_id": "com.example.app",
+ "sha256": "optional-lowercase-sha256",
+ "description": "Description shown in OpenPelo",
+ "abi": "arm64-v8a"
+ }
+ }
+}
+```
+
+- `url` must use HTTPS. GitHub latest-release URLs and API URLs are resolved
+ to release assets automatically.
+- `asset_name` is the preferred APK filename.
+- `asset_pattern` is an optional glob used when release filenames contain a
+ changing version. Ambiguous matches are rejected.
+- `package_id` is the optional Android application ID used when resolving an
+ incompatible-signature reinstall.
+- `sha256` is an optional integrity checksum. Add it whenever the URL points
+ to an immutable, versioned APK.
+- `abi` is `arm64-v8a` or `armeabi-v7a`.
+
+## Automated builds
+
+GitHub Actions formats, analyzes, and tests the project before building
+Windows, macOS, and Linux artifacts. Pull requests receive unsigned validation
+builds. Trusted pushes to `main` additionally sign and notarize the macOS DMG,
+bump the patch version, tag it, and publish a GitHub release.
+
+The Windows installer version is injected from `version.json` by the workflow.
+Mobile releases require their own signing configuration; Android release
+credentials belong in an untracked `android/key.properties` file.
+
+## Download validation
+
+Review catalog changes carefully. Downloads must use HTTPS and are checked for
+APK/ZIP structure. Immutable downloads can also be pinned with SHA-256.
+Android performs its normal package-signing checks during installation.
diff --git a/README.md b/README.md
index 3751401..6e38ca1 100644
--- a/README.md
+++ b/README.md
@@ -2,35 +2,41 @@
-OpenPelo is a Flutter desktop app for installing Android applications and
-managing ADB-enabled devices. It is designed primarily for Android-based
+OpenPelo lets you install apps, manage files, and control your Android device
+from a Windows, Mac, or Linux computer. It is designed primarily for Android-based
fitness equipment such as Peloton, Echelon, and NordicTrack, but it can work
with other Android tablets, phones, and TVs.
-
+
+
+*Screenshots use sample devices, apps, and files.*
+
+[Install](#install-a-release) · [Connect a device](#connect-a-device) ·
+[Feature walkthrough](#feature-walkthrough) · [Troubleshooting](#troubleshooting) ·
+[For contributors](#for-contributors)
## Features
-- Bundled Android Platform-Tools; users do not need a separate ADB install
-- USB and wireless ADB connections
-- Curated application catalog with ARM64 and ARMv7 variants
-- Local APK installation
-- Screen mirroring, screenshots, and screen recording
-- Device file manager for browsing, uploading, and downloading files
-- Installed-app, launcher, rotation, and developer-setting management
-- Windows, macOS, and Linux desktop builds
+| Feature | What you can do |
+| --- | --- |
+| Device connections | Connect over USB or WiFi, switch between devices, and reconnect automatically. |
+| App installation | Choose from a catalog tailored to your device or install an APK (Android app file) from your computer. |
+| Installed App Manager | Find, open, stop, reset, or uninstall apps, and save a list of what's installed. |
+| File Manager | Browse device storage, transfer files, and organize files and folders. |
+| Remote screen and media | Control the device through a screen preview, save PNG screenshots, and record MP4 videos. |
+| Device tools | Choose a default launcher, rotate the screen, apply developer settings, and access Peloton-specific tools. |
+| Ready to use | Includes the connection tools you need, with no separate ADB installation. |
## Install a release
-Download the appropriate artifact from the
+Download the version for your computer from the
[GitHub Releases](../../releases) page:
- Windows: `OpenPelo_Setup_Windows.exe`
- macOS: `OpenPelo-macOS.dmg`
- Linux: `OpenPelo-Linux.tar.gz`
-On first launch, OpenPelo extracts its bundled copy of ADB into the operating
-system's application-support directory.
+OpenPelo sets up its connection tools automatically the first time you open it.
## Connect a device
@@ -40,122 +46,177 @@ system's application-support directory.
2. Enable USB debugging.
3. Connect the device with a USB data cable.
4. Accept the device's **Allow USB debugging** prompt.
-5. Launch OpenPelo and select the detected device.
+5. Launch OpenPelo, click **Refresh Devices** if needed, and choose the device
+ from **Target Device**. The status banner and **ADB Messages** show connection
+ progress.
+
+The **Developer Mode Guide** on the home screen provides an in-app setup
+walkthrough. Menu locations depend on the device and its firmware.
+
+### WiFi
-### Wireless ADB
+
-Open **Connect via WiFi** and follow the on-screen guide. Android 11 and newer
-normally require a pairing port and six-digit pairing code before connecting.
-The desktop builds support `adb pair`; the experimental mobile ADB client does
-not currently support pairing a new device.
+1. Connect the computer and Android device to the same local network.
+2. Open **Connect via WiFi**, follow the guide, and click
+ **I'm Ready - Connect Device**.
+3. For a new Android 11+ wireless-debugging connection, open the device's
+ **Pair device with pairing code** dialog. Enter its IP address, **Pairing
+ Port**, and six-digit **Pairing Code** in OpenPelo.
+4. Enter the **Connection Port** from the device's main Wireless debugging
+ screen. This is usually different from the pairing port.
+5. Click **Connect** and check the status below the form. Click **Done** to
+ return to the home screen, or **Connect Another** to clear the form and add
+ another device.
+
+Use **Scan for Devices** to find your device and fill in its connection details.
+Enter the pairing code shown on the device yourself. If the scan finds nothing,
+enter the details manually. You only need the pairing code and pairing port
+the first time you pair a device.
+
+### Set up WiFi through USB
+
+With your device connected over USB and on the same WiFi network as your
+computer, open the wireless connection form and click **Pair using USB**.
+Choose your device if prompted. Once **Wireless ADB Enabled** appears, you
+can unplug the cable and continue over WiFi.
### Automatic WiFi connection
-Desktop builds always maintain wireless ADB connections automatically. After
-you connect an Android device wirelessly once, OpenPelo verifies its manufacturer
-and hardware serial, enables legacy ADB on port 5555, and remembers its address.
-Setup may briefly interrupt the original connection. This works with any
-manufacturer whose firmware supports legacy TCP/IP ADB and reports a usable
-hardware serial. Devices connected only by USB are not switched automatically.
-
-While OpenPelo is running, it retries remembered devices when disconnected,
-including after reopening the app. This can reconnect even when the tablet's
-**Wireless debugging** toggle is off. Retry delays increase from 10 seconds to
-five minutes when a device is unavailable. When both wireless connections are
-present, OpenPelo shows the port-5555 connection. Peloton devices appear at the
-top of the device list; all other devices remain available, and your selected
-target is preserved. It verifies the manufacturer and hardware serial
-before using a remembered address, so a reassigned IP cannot silently select a
-different device.
-
-Legacy ADB is **unencrypted**: use this feature only on a trusted local network.
-Rebooting the tablet may
-remove the listener. If that happens, or its IP changes while disconnected,
-connect normally again so OpenPelo can set it up and remember the new address.
-
-Tested on a Peloton PLTN-RB1VO-2 running Android 11: port 5555 accepted a fresh
-connection with Wireless debugging off. Survival of the reported approximately
-24-hour automatic shutdown has not yet been verified.
-
-## Build from source
-
-### Requirements
-
-- Flutter 3.44.4 or newer
-- Dart 3.10 or newer
-- Platform prerequisites from Flutter's desktop setup guide
-- Inno Setup 6 when creating the Windows installer
-
-```powershell
-flutter pub get
-flutter analyze
-flutter test
-flutter run -d windows
-```
-
-Use `-d macos` or `-d linux` on those platforms. Release bundles are created
-with:
-
-```powershell
-flutter build windows --release
-flutter build macos --release
-flutter build linux --release
-```
-
-See [DISTRIBUTION.md](./DISTRIBUTION.md) for packaging details.
-
-## Application catalog
-
-Applications are defined in `apps_config.json`:
-
-```json
-{
- "apps": {
- "Example App": {
- "url": "https://github.com/example/app/releases/latest",
- "asset_name": "example-arm64.apk",
- "asset_pattern": "example-*-arm64.apk",
- "package_id": "com.example.app",
- "sha256": "optional-lowercase-sha256",
- "description": "Description shown in OpenPelo",
- "abi": "arm64-v8a"
- }
- }
-}
-```
-
-- `url` must use HTTPS. GitHub latest-release URLs and API URLs are resolved
- to release assets automatically.
-- `asset_name` is the preferred APK filename.
-- `asset_pattern` is an optional glob used when release filenames contain a
- changing version. Ambiguous matches are rejected.
-- `package_id` is the optional Android application ID used when resolving an
- incompatible-signature reinstall.
-- `sha256` is an optional integrity checksum. Add it whenever the URL points
- to an immutable, versioned APK.
-- `abi` is `arm64-v8a` or `armeabi-v7a`.
-
-## Automated builds
-
-GitHub Actions formats, analyzes, and tests the project before building
-Windows, macOS, and Linux artifacts. Pull requests receive unsigned validation
-builds. Trusted pushes to `main` additionally sign and notarize the macOS DMG,
-bump the patch version, tag it, and publish a GitHub release.
-
-The Windows installer version is injected from `version.json` by the workflow.
-Mobile releases require their own signing configuration; Android release
-credentials belong in an untracked `android/key.properties` file.
-
-## Security
-
-OpenPelo can install third-party software and modify system settings on a
-connected Android device. Review catalog changes carefully. Downloads are
-restricted to HTTPS and checked for APK/ZIP structure; immutable downloads can
-also be pinned with SHA-256. Android still performs its normal package-signing
-checks during installation.
-
-Some device-management operations are destructive. Back up important data and
-read confirmation dialogs before proceeding.
+After your first WiFi connection, OpenPelo remembers supported devices and
+tries to reconnect automatically, including when you reopen the app. If it
+doesn't reconnect after a device restart or network change, use **Connect via
+WiFi** or **Pair using USB** again.
+
+## Feature walkthrough
+
+### 1. Choose the device and follow activity
+
+Use **Target Device** to choose the device you want to manage.
+The list identifies USB and WiFi connections. Peloton devices appear first;
+other Android devices remain available. Check this selection before starting
+an operation when multiple devices are connected.
+
+The status banner shows whether you're connected. **ADB Messages** shows
+progress and details if something goes wrong. Scroll back to read earlier
+messages; the down-arrow control returns to the latest activity. **Refresh
+Devices** checks the connection again and refreshes the compatible app list.
+
+### 2. Install apps
+
+1. Connect and select a device to populate **Available Apps**.
+2. Read the descriptions and check the apps you want. Scroll inside the catalog
+ to see more entries.
+3. Click **Install Selected Apps**. OpenPelo downloads and installs the selected
+ apps; follow **ADB Messages** for progress and results.
+4. To install an APK you already have, click **Install Local APK** and choose
+ the `.apk` file on your computer.
+
+The bundled catalog includes fitness utilities such as SmartSpin2k and Grupetto,
+launchers such as Lawnchair, app stores, browsers, file managers, and media or
+streaming clients. OpenPelo adjusts the list for your device, with older app
+versions available for older hardware. Some apps may still require a newer
+version of Android.
+
+If Android reports a **Signature Mismatch**, OpenPelo offers **Uninstall &
+Reinstall**. That removes the existing app and its local data before reinstalling.
+Choose **Cancel** to keep the current installation.
+
+### 3. Manage installed apps
+
+
+
+Open **Tools → Installed App Manager** to inspect apps on the selected device.
+
+- **Search apps** finds apps by name or Android package name. Enable **System
+ apps** to include built-in apps, and use **Refresh** to reload the list.
+- **Launch** starts an app and opens the remote screen view. **Force stop**
+ stops the app; **App settings** opens its Android settings and the remote
+ screen view so you can interact with them.
+- **Clear data** erases the app's saved settings and local data after
+ confirmation. **Uninstall** removes an app you installed; this action is
+ disabled for built-in apps.
+- **Export visible list** saves the apps shown in the list to a text file in
+ your save folder.
+
+### 4. Browse and transfer device files
+
+
+
+Open **Tools → File Manager** to browse your device's shared storage, with
+shortcuts to **sdcard**, **Download**, **DCIM**, and **Movies**.
+
+1. Click a folder to open it, use **Up one level**, or enter a folder location in
+ **Device path** and press Enter.
+2. Click the **Name**, **Size**, or **Modified** column header to sort. Click
+ the same header again to reverse the order.
+3. Use **Upload files here** to select local files and send them to the current
+ device folder. Use **New folder** to create a folder there.
+4. Use a row's **Download** action to copy a file or folder to your computer.
+ **More actions → Download to...** selects a destination for just that
+ download. **Rename** and **Delete** operate on the device; deletion requires
+ confirmation.
+
+The bottom of the dialog shows the download destination. **Change** updates the
+save folder, while **Ask each time** prompts for a destination for each download.
+The transfer area displays progress and results, and a completed download can
+be located with **Open containing folder**.
+
+### 5. View, control, and capture the device screen
+
+The home screen's **Downloads & Media** section groups the save folder and
+capture controls. Scroll down if it is below the visible area of the window.
+
+| Control | Walkthrough |
+| --- | --- |
+| **Save folder** | Use **Change Location** to choose the computer folder used for media, file downloads, and app-list exports. **Open Folder** opens it in your file browser. |
+| **Take Screenshot** | Save a picture of the device's display to your save folder. The PNG filename includes the date and time. |
+| **View Screen** | Open **Remote Screen View**. Click the preview to tap the device; drag, use the mouse wheel, or press arrow keys to scroll. Use **Back**, **Home**, and **Recents** just as you would on the device. |
+| **Record / Stop Rec** | Click **Record** to start recording the device screen. Click **Stop Rec** to finish and save an MP4 video to your save folder. Keep the device connected while saving. |
+
+The screen preview updates periodically, so movement won't look as smooth as
+it does on the device itself.
+
+### 6. Configure the launcher and device tools
+
+
+
+Open **Tools** in the upper-right corner for these additional controls:
+
+- **Set Default Launcher:** install a launcher (home screen app) first, then
+ open this tool to see available launchers. Click **Set Default** beside
+ a launcher to make it your device's home screen.
+- **Rotate Screen:** use **Rotate** to cycle the orientation in 90-degree steps,
+ or enable **Auto-rotate** to follow how the device is held. Some apps have
+ their own rotation settings.
+- **Enable Developer Settings:** choose **Wireless Debugging** and/or **Stay
+ Awake While Charging**, then click **Apply**. OpenPelo reports results for
+ the requested settings. Connect your device before using this tool.
+- **Enable/Disable Built-in Netflix:** a Peloton-specific tool with **Enable
+ Netflix** and **Restore Defaults** actions. If enabling it affects other
+ Peloton features, choose **Restore Defaults**.
+- **Uninstall Peloton Apps:** choose which Peloton apps to remove. This is an
+ advanced option; removing built-in apps can affect normal Peloton features.
+- **Check For Updates:** checks OpenPelo's GitHub releases. When a newer version
+ is found, the home screen shows **View Release**, which opens the release
+ page for download. A failed check offers **Retry**.
+
+## Troubleshooting
+
+| Symptom | What to check |
+| --- | --- |
+| No device or empty catalog | Enable USB debugging, use a data-capable cable, accept the device's authorization prompt, click **Refresh Devices**, and select **Target Device**. |
+| Wireless pairing fails | Check the current six-digit code and pairing port in the device's pairing dialog. The connection port comes from the main Wireless debugging screen. |
+| Scan finds nothing | Check that both devices are on the same network and wireless debugging is enabled. Try entering the address and ports manually. |
+| A remembered WiFi device stops reconnecting | Make sure it's on the same network as your computer, then try **Connect via WiFi** or **Pair using USB** again. |
+| An APK will not install | Check **ADB Messages** for the reason. Make sure the app supports your device and Android version. If you see **Signature Mismatch**, read the reinstall prompt before continuing. |
+| A file or setting cannot be accessed | Some system folders and settings are protected by Android. Check **ADB Messages** for details. |
+| Capture controls are missing from view | Scroll the home page to **Downloads & Media**, or enlarge the window. |
+
+## For contributors
+
+See [CONTRIBUTING.md](./CONTRIBUTING.md) for building from source, updating the
+app catalog, and regenerating screenshots.
## Disclaimer
diff --git a/images/screenshots/file-manager.png b/images/screenshots/file-manager.png
new file mode 100644
index 0000000..f45b4fe
Binary files /dev/null and b/images/screenshots/file-manager.png differ
diff --git a/images/screenshots/installed-app-manager.png b/images/screenshots/installed-app-manager.png
new file mode 100644
index 0000000..c6c7985
Binary files /dev/null and b/images/screenshots/installed-app-manager.png differ
diff --git a/images/screenshots/overview.png b/images/screenshots/overview.png
new file mode 100644
index 0000000..0f88dbf
Binary files /dev/null and b/images/screenshots/overview.png differ
diff --git a/images/screenshots/tools.png b/images/screenshots/tools.png
new file mode 100644
index 0000000..9b691f7
Binary files /dev/null and b/images/screenshots/tools.png differ
diff --git a/images/screenshots/wireless-connection.png b/images/screenshots/wireless-connection.png
new file mode 100644
index 0000000..96bfc33
Binary files /dev/null and b/images/screenshots/wireless-connection.png differ
diff --git a/tool/capture_readme_screenshots_test.dart b/tool/capture_readme_screenshots_test.dart
new file mode 100644
index 0000000..99a356e
--- /dev/null
+++ b/tool/capture_readme_screenshots_test.dart
@@ -0,0 +1,215 @@
+// Render the production widgets with sample data; never initialize ADB.
+// Run manually: flutter test tool/capture_readme_screenshots_test.dart
+import 'dart:io';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:openpelo/screens/home_screen.dart';
+import 'package:openpelo/theme/app_theme.dart';
+import 'package:openpelo/models/device_file_model.dart';
+import 'package:openpelo/models/device_model.dart';
+import 'package:openpelo/models/installed_app_model.dart';
+import 'package:openpelo/providers/app_provider.dart';
+import 'package:openpelo/services/config_service.dart';
+import 'package:provider/provider.dart';
+
+class _ScreenshotProvider extends AppProvider {
+ _ScreenshotProvider() {
+ final device = DeviceModel(
+ serial: 'documentation-device',
+ status: 'device',
+ transport: 'wifi',
+ ip: '192.0.2.10',
+ port: '5555',
+ name: 'Peloton PLTN-RB1VO-2',
+ abi: 'arm64-v8a',
+ );
+ devices = [device];
+ selectedDevice = device;
+ statusMessage = 'Connected to ${device.displayName}';
+ currentAppVersion = RegExp(
+ r'^version: (.+)$',
+ multiLine: true,
+ ).firstMatch(File('pubspec.yaml').readAsStringSync())!.group(1)!.trim();
+ logs = [
+ LogEntry('[10:30:00]', 'Device connected via WiFi.', 'status'),
+ LogEntry('[10:30:01]', 'Device architecture: arm64-v8a', 'info'),
+ LogEntry('[10:30:01]', 'Compatible application catalog loaded.', 'info'),
+ ];
+ }
+
+ @override
+ String get saveLocation => r'C:\OpenPelo\Downloads';
+
+ @override
+ Future> listInstalledApps({
+ bool includeSystemApps = false,
+ }) async => const [
+ InstalledAppModel(
+ packageName: 'com.aurora.store',
+ label: 'Aurora Store',
+ isSystemApp: false,
+ ),
+ InstalledAppModel(
+ packageName: 'org.mozilla.focus',
+ label: 'Firefox Focus',
+ isSystemApp: false,
+ ),
+ InstalledAppModel(
+ packageName: 'app.lawnchair',
+ label: 'Lawnchair',
+ isSystemApp: false,
+ ),
+ InstalledAppModel(
+ packageName: 'me.zhanghai.android.files',
+ label: 'Material Files',
+ isSystemApp: false,
+ ),
+ ];
+
+ @override
+ Future> listDeviceFiles(
+ String deviceSerial,
+ String path,
+ ) async => [
+ for (final name in ['DCIM', 'Download', 'Movies', 'Music', 'Pictures'])
+ DeviceFileEntry(
+ name: name,
+ path: '$path/$name',
+ isDirectory: true,
+ modified: '2026-09-15 10:30:00',
+ ),
+ DeviceFileEntry(
+ name: 'workout-notes.txt',
+ path: '$path/workout-notes.txt',
+ isDirectory: false,
+ sizeBytes: 2048,
+ modified: '2026-09-15 10:30:00',
+ ),
+ ];
+}
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ testWidgets('capture README screens with sample device data', (tester) async {
+ // Flutter tests otherwise use the unreadable Ahem test font. Override this
+ // path with OPENPELO_SCREENSHOT_FONT on non-Windows hosts.
+ final fontPath =
+ Platform.environment['OPENPELO_SCREENSHOT_FONT'] ??
+ r'C:\Windows\Fonts\segoeui.ttf';
+ final font = ByteData.sublistView(File(fontPath).readAsBytesSync());
+ final emojiFile = File(r'C:\Windows\Fonts\seguiemj.ttf');
+ if (emojiFile.existsSync()) {
+ await (FontLoader('ScreenshotEmoji')..addFont(
+ Future.value(ByteData.sublistView(emojiFile.readAsBytesSync())),
+ ))
+ .load();
+ }
+ for (final family in [
+ 'Ahem',
+ 'Roboto',
+ 'Segoe UI',
+ 'Consolas',
+ 'monospace',
+ ]) {
+ final loader = FontLoader(family)..addFont(Future.value(font));
+ if (emojiFile.existsSync()) {
+ loader.addFont(
+ Future.value(ByteData.sublistView(emojiFile.readAsBytesSync())),
+ );
+ }
+ await loader.load();
+ }
+ await (FontLoader(
+ 'MaterialIcons',
+ )..addFont(rootBundle.load('fonts/MaterialIcons-Regular.otf'))).load();
+ debugDisableShadows = false;
+ addTearDown(() => debugDisableShadows = true);
+
+ tester.view.physicalSize = const Size(1200, 940);
+ tester.view.devicePixelRatio = 1;
+ addTearDown(tester.view.resetPhysicalSize);
+ addTearDown(tester.view.resetDevicePixelRatio);
+
+ final provider = _ScreenshotProvider();
+ provider.availableApps = await ConfigService().loadApps('arm64-v8a');
+ provider.availableApps['Lawnchair Launcher']!.isSelected = true;
+ final boundaryKey = GlobalKey();
+ final theme = AppTheme.light;
+ await tester.pumpWidget(
+ ChangeNotifierProvider.value(
+ value: provider,
+ child: RepaintBoundary(
+ key: boundaryKey,
+ child: MaterialApp(
+ debugShowCheckedModeBanner: false,
+ // A fontless app-bar style otherwise falls back to the test font.
+ theme: theme.copyWith(
+ textTheme: theme.textTheme.apply(
+ fontFamilyFallback: const ['ScreenshotEmoji'],
+ ),
+ appBarTheme: theme.appBarTheme.copyWith(
+ titleTextStyle: theme.appBarTheme.titleTextStyle!.copyWith(
+ fontFamily: 'Segoe UI',
+ ),
+ ),
+ ),
+ home: const HomeScreen(),
+ ),
+ ),
+ ),
+ );
+ await tester.pumpAndSettle();
+
+ Future capture(String name) async {
+ expect(tester.takeException(), isNull);
+ final boundary =
+ boundaryKey.currentContext!.findRenderObject()
+ as RenderRepaintBoundary;
+ await tester.runAsync(() async {
+ final image = await boundary.toImage(pixelRatio: 1.5);
+ final png = await image.toByteData(format: ui.ImageByteFormat.png);
+ final file = File('images/screenshots/$name.png');
+ await file.parent.create(recursive: true);
+ await file.writeAsBytes(png!.buffer.asUint8List());
+ image.dispose();
+ });
+ }
+
+ await capture('overview');
+ await tester.tap(find.text('📶 Connect via WiFi'));
+ await tester.pumpAndSettle();
+ await tester.tap(find.text("I'm Ready - Connect Device"));
+ await tester.pumpAndSettle();
+ await capture('wireless-connection');
+ await tester.tap(find.text('Done'));
+ await tester.pumpAndSettle();
+
+ Future openTool(String label) async {
+ await tester.tap(find.byTooltip('Open tools menu'));
+ await tester.pumpAndSettle();
+ await tester.tap(find.text(label));
+ await tester.pumpAndSettle();
+ }
+
+ await openTool('Installed App Manager');
+ await capture('installed-app-manager');
+ await tester.tap(find.byTooltip('Close'));
+ await tester.pumpAndSettle();
+ await openTool('File Manager');
+ await capture('file-manager');
+ await tester.tap(find.byTooltip('Close'));
+ await tester.pumpAndSettle();
+ await tester.tap(find.byTooltip('Open tools menu'));
+ await tester.pumpAndSettle();
+ await capture('tools');
+
+ await tester.pumpWidget(const SizedBox.shrink());
+ provider.dispose();
+ debugDisableShadows = true;
+ });
+}