vehicle_frame_capture

A guided, multi-angle vehicle photo capture flow for Flutter apps — a live camera preview with a rectangular framing guide that turns green when the device is held level, walking the user through every angle a vehicle listing typically needs.

It's deliberately unopinionated: no review UI, colors, fonts, or step list are forced on you — every one of them is a parameter with a sensible default.

Features

  • šŸ“ø Configurable capture steps: defaults to 12 angles (6 exterior + 6 interior, see VehicleSide.defaultValues), or pass your own subset/order — including any of the ~35 additional angles in VehicleSide.catalog, or fully custom ones via VehicleSide.custom(...)
  • šŸ—‚ļø Categorized: every angle is tagged exterior or interior via VehicleCaptureCategory, for tabbed capture UIs
  • šŸŽÆ Rectangular frame overlay: a simple, universal framing guide used across every step
  • šŸ“± Device leveling: accelerometer-driven level detection, visualized with a two-axis crosshair/bubble-level indicator, with tunable tolerance — and skippable per angle (VehicleSide.requiresLevel) for shots that are inherently tilted up/down (roof, engine bay, wheels, ...)
  • šŸŽØ Themeable: colors and text styles are all overridable via VehicleCaptureTheme; text falls back to your app's ambient Theme when not overridden — no font package is forced on you
  • šŸ”„ Capture flow management: automatic progression, with onPhotoCaptured/onStepChanged callbacks to react live
  • šŸ–¼ļø Review UI is optional: returns the captured images directly by default; opt into the bundled review screen with showSummary: true only if you want it
  • šŸ“· Camera controls: pick front/back camera and resolution preset
  • ⚔ Self-contained: locks to landscape while active and restores portrait on exit — no setup required from the calling app

Installation

Add this to your app's pubspec.yaml:

dependencies:
  vehicle_frame_capture: ^4.0.0

Platform setup

This package uses the camera and sensors_plus plugins, which need camera permissions declared in your app:

Android (android/app/src/main/AndroidManifest.xml):

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>

iOS (ios/Runner/Info.plist):

<key>NSCameraUsageDescription</key>
<string>Camera access is needed to capture vehicle photos.</string>

Usage

Bare minimum — no review screen, full default step list

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:vehicle_frame_capture/vehicle_frame_capture.dart';

Future<void> startCapture(BuildContext context) async {
  final images = await Navigator.push<List<File>>(
    context,
    MaterialPageRoute(builder: (context) => const CameraScreen()),
  );

  if (images != null) {
    // images.length == 12, one per VehicleSide.defaultValues, in flow order.
    // Build your own review/upload UI with these — CameraScreen doesn't
    // show one unless you ask it to (see below).
  }
}

With the bundled review screen, a custom step list, and theming

final images = await Navigator.push<List<File>>(
  context,
  MaterialPageRoute(
    builder: (context) => CameraScreen(
      showSummary: true, // shows the bundled SummaryScreen before returning
      steps: const [
        VehicleSide.front,
        VehicleSide.frontRight,
        VehicleSide.rearRight,
        VehicleSide.rear,
      ],
      theme: const VehicleCaptureTheme(
        readyColor: Colors.orangeAccent,
        primaryColor: Colors.deepPurple,
      ),
      resolutionPreset: ResolutionPreset.high,
      preferredLensDirection: CameraLensDirection.back,
      onStepChanged: (side, index, total) =>
          debugPrint('Step ${index + 1}/$total: ${side.label}'),
      onPhotoCaptured: (side, file) =>
          debugPrint('Captured ${side.label} -> ${file.path}'),
    ),
  ),
);

CameraScreen handles the entire flow internally — camera preview, leveling, the frame overlay, and per-step progression — and returns the captured List<File> (or null if the user backs out) via the standard Navigator.pop result.

Picking angles from the full catalog, or adding a custom one

CameraScreen(
  steps: [
    ...VehicleSide.defaultExterior,
    VehicleSide.wheelFrontLeft,     // from the extended catalog
    VehicleSide.vinPlate,
    VehicleSide.custom(              // or define your own
      label: 'Spare Tire',
      instruction: 'Open the trunk and capture the spare tire compartment',
      category: VehicleCaptureCategory.interior,
      requiresLevel: false,
    ),
  ],
)

Components

CameraScreen

The capture flow as a single, self-contained screen. Push it and await its result.

Parameter Default Purpose
steps VehicleSide.defaultValues (6 exterior + 6 interior) Which angles to capture, in order
theme VehicleCaptureTheme() Colors and text styles
showSummary false Show the bundled SummaryScreen before returning, instead of returning immediately
resolutionPreset ResolutionPreset.max Camera capture quality (highest the device supports)
preferredLensDirection CameraLensDirection.back Which camera to use (falls back to the first available)
levelYTolerance / levelZTolerance 2.0 / 3.0 How strict the "level" check is (m/s², roll/pitch)
onPhotoCaptured null (VehicleSide, File) called after each photo is saved
onStepChanged null (VehicleSide, int index, int total) called on each step transition

Each step's VehicleSide.requiresLevel overrides the tolerance check entirely for that step — when false, the level indicator is hidden and the shutter is always enabled, regardless of levelYTolerance/levelZTolerance.

VehicleCaptureTheme

Colors (readyColor, idleColor, dangerColor, primaryColor, summaryBackgroundColor) and optional text styles (titleTextStyle, instructionTextStyle, labelTextStyle) — anything left null falls back to the ambient Theme.

SummaryScreen

The optional review grid shown when showSummary: true. Exported standalone too, in case you want to reuse it outside the main flow.

VehicleFramePainter

The CustomPainter that draws the rectangular frame guide.

CustomPaint(
  painter: VehicleFramePainter(isReady: true, readyColor: Colors.green),
  size: Size.infinite,
)

CaptureFlow

Manages capture state and progression over a configurable list of VehicleSide steps.

final captureFlow = CaptureFlow(sides: [VehicleSide.front, VehicleSide.rear]);
final currentStep = captureFlow.currentStep;
captureFlow.nextStep();

VehicleSide

A plain class rather than an enum, so it isn't limited to a fixed set of angles:

class VehicleSide {
  final String id;                        // stable identity (used for equality/Map keys)
  final VehicleCaptureCategory category;  // exterior or interior
  final String label;                     // shown in the header and on tiles
  final String instruction;               // one-line guidance while active
  final bool requiresLevel;               // false for angles shot tilted up/down
}
  • VehicleSide.defaultValues — the 12 angles (defaultExterior + defaultInterior) used when no steps/sides are given.
  • VehicleSide.catalog (catalogExterior + catalogInterior) — every built-in angle, ~35 more beyond the defaults: wheels, windshield, headlights/taillights, license plate, VIN plate, undercarriage, engine bay, instrument cluster, glove box, seat belts, cargo area, and more.
  • VehicleSide.custom({label, instruction, category, requiresLevel, id}) — define a one-off angle not in the catalog.

VehicleCaptureCategory

enum VehicleCaptureCategory { exterior, interior }

Groups angles for tabbed UIs — every VehicleSide.category is one of these.

What this package does not do

It does not run any on-device object/vehicle detection — the "ready" (green) state reflects device level only, not image content. If you need contents-aware detection, layer it on top by watching the returned images.

License

MIT — see LICENSE.

Libraries

vehicle_frame_capture
A guided, multi-angle vehicle photo capture flow: a rectangular frame overlay, live device-level feedback, and camera integration in one widget. See CameraScreen for the main entry point.