reveal method

Future<PatchbayInvocation> reveal({
  1. required String identifier,
  2. String? container,
  3. PatchbayRevealDirection direction = PatchbayRevealDirection.both,
  4. int maxSteps = PatchbayRevealBudget.defaultSteps,
  5. int timeoutMs = PatchbayRevealBudget.defaultTimeoutMs,
  6. String? requestId,
})

identifier 驱动到已挂载且露出,返回后续写命令该带的 generation 与该走哪条 tap 通道的 reachability

Implementation

Future<PatchbayInvocation> reveal({
  required String identifier,
  String? container,
  PatchbayRevealDirection direction = PatchbayRevealDirection.both,
  int maxSteps = PatchbayRevealBudget.defaultSteps,
  int timeoutMs = PatchbayRevealBudget.defaultTimeoutMs,
  String? requestId,
}) async {
  final String id = requestId ?? _newRequestId();
  final DateTime started = DateTime.now();

  final List<String> invalid = <String>[
    if (identifier.isEmpty) 'identifier',
    if (container != null && container.isEmpty) 'container',
    if (maxSteps < 1 || maxSteps > PatchbayRevealBudget.maxSteps) 'maxSteps',
    if (timeoutMs < 1 || timeoutMs > PatchbayRevealBudget.maxDurationMs)
      'timeoutMs',
  ];
  if (invalid.isNotEmpty) {
    return _rejected(
      id,
      'invalidUiArguments',
      notice:
          'identifier must be non-empty, maxSteps must be '
          '1..${PatchbayRevealBudget.maxSteps} and timeoutMs must be '
          '1..${PatchbayRevealBudget.maxDurationMs}.',
      details: <String, Object?>{'invalid': invalid},
    );
  }

  final PatchbayRevealPolicy? policy = _policy;
  if (policy == null) return _rejected(id, 'uiRevealDisabled');
  if (!_isAppResumed()) {
    return _rejected(
      id,
      'uiLifecycleNotResumed',
      details: patchbayLifecycleDetails(_lifecycleState),
    );
  }

  final SemanticsOwner? owner = await _semantics.ensureOwner();
  final SemanticsNode? root = owner?.rootSemanticsNode;
  if (owner == null || root == null) {
    return _rejected(id, 'uiSemanticsUnavailable');
  }
  final int beforeTreeRevision = _semantics.treeRevision;

  final List<SemanticsNode> targets = patchbaySemanticsNodesWithIdentifier(
    root,
    identifier,
  );
  if (targets.length > 1) {
    // 目标未挂载是本命令存在的理由,不是错误;多个挂载实例才是。
    return _rejected(
      id,
      'uiSemanticsIdentifierAmbiguous',
      details: <String, Object?>{
        'identifier': identifier,
        'role': 'target',
        'matchCount': targets.length,
      },
    );
  }
  final SemanticsNode? target = targets.isEmpty ? null : targets.single;

  // PB-050-35 / DG-060-05:可达性分类发生在第一次 scroll 派发前,不消耗 step。
  final _Admission admission = _admit(
    id: id,
    root: root,
    identifier: identifier,
    container: container,
    direction: direction,
    target: target,
    classified: target == null
        ? null
        : patchbayRevealTargetAdmission(
            owner: owner,
            node: target,
            semantics: _semantics,
          ),
  );
  if (admission.rejection case final PatchbayInvocation rejection) {
    return rejection;
  }
  if (admission.shortcut case final PatchbayRevealOutcome shortcut) {
    return _accepted(
      id,
      identifier: identifier,
      outcome: shortcut,
      steps: 0,
      containers: const <PatchbayRevealContainerRecord>[],
      started: started,
      beforeTreeRevision: beforeTreeRevision,
    );
  }

  return _run(
    id: id,
    started: started,
    owner: owner,
    policy: policy,
    anchor: admission.anchor!,
    anchorIdentifier: container,
    identifier: identifier,
    direction: direction,
    maxSteps: maxSteps,
    timeoutMs: timeoutMs,
    beforeTreeRevision: beforeTreeRevision,
  );
}