import 'product_profile.dart'; import 'state/camera_state.dart'; /// GO 4's four exposure modes (plan P4 quick exposure panel). The wire value is /// the SDK's `ExposureMode` name, sent through `setCameraValue('exposureMode')`. enum ExposureMode { auto('PROGRAM', 'Auto'), aperture('APERTURE_PRIORITY', 'A'), shutter('SHUTTER_PRIORITY', 'S'), manual('MANUAL', 'M'); const ExposureMode(this.wire, this.label); final String wire; final String label; static ExposureMode? fromWire(String? wire) { for (final ExposureMode m in values) { if (m.wire == wire) return m; } return null; } /// What the pilot may set by hand in this mode. In M the EV readout is the /// meter's verdict rather than a setting. bool get isoEditable => this != auto; bool get shutterEditable => this == shutter || this == manual; bool get apertureEditable => this == aperture || this == manual; bool get evEditable => this != manual; } /// The option lists the exposure controls offer, from the camera's own /// capability ranges (P0) and a conservative fallback while those are still /// empty (before the camera's handshake, or with nothing connected). abstract final class ExposureOptions { static const List _isos = ['100', '200', '400', '800', '1600', '3200']; static const List _shutters = [ '1/8000', '1/4000', '1/2000', '1/1000', '1/500', '1/240', '1/120', '1/60', '1/30', '1/15', '1/8', '1/4', '1/2', '1', ]; static const List _evs = [ '-3.0', '-2.7', '-2.3', '-2.0', '-1.7', '-1.3', '-1.0', '-0.7', '-0.3', '0.0', // '+0.3', '+0.7', '+1.0', '+1.3', '+1.7', '+2.0', '+2.3', '+2.7', '+3.0', ]; /// The modes the segmented control shows: the camera's own list, with A only /// on adjustable-aperture lenses. static List modes(CameraProfile? c) { final List reported = [ for (final String w in c?.exposureModes ?? const []) if (ExposureMode.fromWire(w) case final ExposureMode m) m, ]; final List all = reported.isEmpty ? ExposureMode.values : reported; final bool aperture = c == null || c.adjustableApertureSupported; return [ for (final ExposureMode m in ExposureMode.values) if (all.contains(m) && (m != ExposureMode.aperture || aperture)) m, ]; } /// Numeric ISOs for the slider; `AUTO` is its own toggle. static List isos(CameraProfile? c) { final List l = [for (final String v in c?.isos ?? const []) if (int.tryParse(v) != null) v]; return l.isEmpty ? _isos : l; } static bool isoAutoAvailable(CameraProfile? c) => c == null || c.isos.isEmpty || c.isos.contains('AUTO'); /// Fastest first, as the SDK lists them. static List shutters(CameraProfile? c) => (c?.shutterSpeeds.isNotEmpty ?? false) ? c!.shutterSpeeds : _shutters; static List apertures(CameraProfile? c) => c?.apertures ?? const []; /// Lowest first. static List evs(CameraProfile? c) { final List l = (c?.exposureCompensations.isNotEmpty ?? false) ? c!.exposureCompensations : _evs; return List.of(l)..sort((String a, String b) => (evValue(a) ?? 0).compareTo(evValue(b) ?? 0)); } /// The EV one press of − / + lands on, or null at the end of the range. static String? stepEv(String? current, List evs, int direction) { if (evs.isEmpty) return null; final double now = evValue(current) ?? 0; if (direction > 0) { for (final String e in evs) { if ((evValue(e) ?? 0) > now + 0.05) return e; } } else { for (final String e in evs.reversed) { if ((evValue(e) ?? 0) < now - 0.05) return e; } } return null; } /// `+0.7` → 0.7, `0.0` / `0` → 0. static double? evValue(String? label) => label == null ? null : double.tryParse(label.replaceFirst('+', '')); /// The index in [values] closest to [current], for a wheel to start on. static int nearest(List values, String? current) { if (values.isEmpty) return 0; final int exact = current == null ? -1 : values.indexOf(current); if (exact >= 0) return exact; final double? v = _numeric(current); if (v == null) return 0; int best = 0; double bestD = double.infinity; for (int i = 0; i < values.length; i++) { final double? x = _numeric(values[i]); if (x == null) continue; final double d = (x - v).abs(); if (d < bestD) { bestD = d; best = i; } } return best; } /// `1/240` → 0.0041, `2.5` → 2.5, `f/2.8` → 2.8, `400` → 400. static double? _numeric(String? s) { if (s == null) return null; final String t = s.replaceFirst('f/', '').replaceAll('"', ''); final int slash = t.indexOf('/'); if (slash > 0) { final double? n = double.tryParse(t.substring(0, slash)), d = double.tryParse(t.substring(slash + 1)); return n == null || d == null || d == 0 ? null : n / d; } return double.tryParse(t); } } /// How far the meter is from a correct exposure in M, for the indicator: /// within a third of a stop is fine, past two-thirds is worth a colour. enum MeterVerdict { ok, under, over } MeterVerdict meterVerdict(CameraState c) { final double ev = ExposureOptions.evValue(c.ev) ?? 0; if (ev <= -0.7) return MeterVerdict.under; if (ev >= 0.7) return MeterVerdict.over; return MeterVerdict.ok; }