blob: 15160818aa21838adaf7a0c1681126b0817482d8 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'fuchsia_views_service.dart';
class FocusState {
// FocusState should be singleton, since we need to ensure that
// HostView.getNextFocusState method calls are not duplicated.
static final FocusState instance = FocusState._();
// Private constructor.
FocusState._();
// Make sure _focusStateChanges does not buffer, since we already prepend the
// current focus state to stream().
final Stream<bool> _focusStateChanges =
_watchFocusState().asBroadcastStream();
/// Gets the current focus state of the root viewRef.
Future<bool> isFocused() async =>
await FuchsiaViewsService.instance.platformViewChannel
.invokeMethod('View.focus.getCurrent') as bool;
/// Listen for focus state events on the host viewRef. When listening, users
/// will get the current focus state, followed by any future focus states.
/// The returned stream instance should be cancelled whenever users are done
/// listening to prevent memory leaks.
///
/// See //sdk/fidl/fuchsia.ui.views/view_ref_focused.fidl for additional
/// documentation on what certain focus state transitions mean.
Stream<bool> stream() {
late final StreamController<bool> controller;
controller = StreamController<bool>(
// ignore: unnecessary_lambdas
onCancel: () => controller.close(),
);
isFocused().then((state) => controller
..add(state)
..addStream(_focusStateChanges));
return controller.stream;
}
// Async stream generator that recursively calls HostView.getNextFocusState.
// We need to make sure that these calls are not duplicated.
static Stream<bool> _watchFocusState() async* {
yield await FuchsiaViewsService.instance.platformViewChannel
.invokeMethod('View.focus.getNext') as bool;
yield* _watchFocusState();
}
/// Requests that focus be transferred to the remote Scene represented by
/// this connection.
Future<void> requestFocus(int viewRef) async {
final args = <String, dynamic>{
'viewRef': viewRef,
};
final result = await FuchsiaViewsService.instance.platformViewChannel
.invokeMethod('View.focus.request', args);
// Throw OSError if result is non-zero.
if (result != 0) {
throw OSError(
'Failed to request focus for view: $viewRef with $result',
result,
);
}
}
/// Requests that focus be transferred to the remote Scene by ViewId
/// This method is indended for use by FuchsiaViewConnection only.
Future<void> requestFocusById(int viewId) async {
final args = <String, dynamic>{
'viewId': viewId,
};
final result = await FuchsiaViewsService.instance.platformViewChannel
.invokeMethod('View.focus.requestById', args);
// Throw OSError if result is non-zero. This may be because the ViewId is
// invalid (i.e. the view has not been created or destroyed yet). If you are
// trying to set focus on a FuchsiaViewConnection immediately after creating
// it and it is failing you may be racing against scenic providing a view ref
// back to the flutter engine, and it may be effective to wait a moment and retry.
if (result != 0) {
throw OSError(
'Failed to request focus for view: $viewId with $result',
result,
);
}
}
}