| // Copyright 2018 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 'package:flutter/material.dart'; |
| |
| import 'package:fidl/fidl.dart' show InterfaceHandle, InterfaceRequest; |
| import 'package:fidl_fuchsia_ui_views/fidl_async.dart' show ViewHolderToken; |
| import 'package:fidl_fuchsia_ui_viewsv1token/fidl_async.dart' show ViewOwner; |
| import 'package:fidl_fuchsia_modular/fidl_async.dart' |
| show |
| FocusControllerProxy, |
| FocusProviderProxy, |
| FocusRequestWatcher, |
| PuppetMasterProxy, |
| SessionShell, |
| SessionShellBinding, |
| SessionShellContext, |
| StoryControllerProxy, |
| StoryInfo, |
| StoryProviderProxy, |
| StoryProviderWatcher, |
| StoryState, |
| StoryVisibilityState, |
| ViewIdentifier; |
| import 'package:fuchsia_services/services.dart' show StartupContext; |
| import 'package:zircon/zircon.dart'; |
| |
| import 'story_model.dart'; |
| |
| /// Manages the visual state of all stories. |
| class StoryManager extends ChangeNotifier { |
| // Holds the map of [StoryModel] indexed by storyId. |
| final _storyMap = <String, StoryModel>{}; |
| |
| // Holds the list of story ids. |
| final _storyIds = <String>[]; |
| |
| // Holds the list of stories that were stopped (minimized). |
| final _minimizedStories = <String>{}; |
| |
| /// Returns the list of stories that are visible (not minimized). |
| Iterable<StoryModel> get stories => |
| _storyMap.values.where((story) => story.isVisible); |
| |
| /// Returns the index of the story that is currently focused. Returns -1 |
| /// if no story has focus. |
| int get focusedStoryIndex => |
| focusedStoryId != null ? _storyIds.indexOf(focusedStoryId) : -1; |
| |
| /// Holds the id of the story that has focus. |
| String focusedStoryId; |
| |
| bool _isFullscreen = false; |
| bool get isFullscreen => _isFullscreen; |
| |
| final _focusProvider = FocusProviderProxy(); |
| final _focusController = FocusControllerProxy(); |
| final _storyProvider = StoryProviderProxy(); |
| final _sessionShellBinding = SessionShellBinding(); |
| final PuppetMasterProxy _puppetMaster; |
| |
| /// Constructor. |
| StoryManager({SessionShellContext context, PuppetMasterProxy puppetMaster}) |
| : _puppetMaster = puppetMaster { |
| context |
| ..getFocusProvider(_focusProvider.ctrl.request()) |
| ..getFocusController(_focusController.ctrl.request()) |
| ..getStoryProvider(_storyProvider.ctrl.request()); |
| } |
| |
| /// Disconnects from all service proxies. |
| void stop() { |
| _sessionShellBinding.close(); |
| _focusController.ctrl.close(); |
| _focusProvider.ctrl.close(); |
| _storyProvider.ctrl.close(); |
| } |
| |
| /// Advertises this instance as [SessionShell]. |
| void advertise(StartupContext startupContext) { |
| startupContext.outgoing.addPublicService( |
| (InterfaceRequest<SessionShell> request) => |
| _sessionShellBinding.bind(_SessionShellImpl(this), request), |
| SessionShell.$serviceName); |
| } |
| |
| Future<void> onStoryChange( |
| StoryInfo storyInfo, |
| StoryState storyState, |
| StoryVisibilityState storyVisibilityState, |
| ) async { |
| if (_storyMap.containsKey(storyInfo.id)) { |
| // Story may be stopping if StoryController.stop was called. Minimize it. |
| if (storyState == StoryState.stopping) { |
| _storyMap[storyInfo.id].stop(); |
| } else if (storyState == StoryState.running) { |
| // Story visibility state is changing. |
| _storyMap[storyInfo.id].fullscreen = |
| storyVisibilityState == StoryVisibilityState.immersive; |
| } |
| } else if (storyState != StoryState.stopping) { |
| final storyController = StoryControllerProxy(); |
| await _storyProvider.getController( |
| storyInfo.id, |
| storyController.ctrl.request(), |
| ); |
| |
| _storyIds.add(storyInfo.id); |
| _storyMap[storyInfo.id] = StoryModel( |
| storyInfo: storyInfo, |
| storyController: storyController, |
| onStopped: () => _onStopped(storyInfo.id), |
| onDelete: () => _onDelete(storyInfo.id), |
| visualState: storyVisibilityState == StoryVisibilityState.immersive |
| ? StoryVisualState.maximized |
| : StoryVisualState.normal, |
| )..start(); |
| |
| setFocus(storyInfo.id); |
| |
| notifyListeners(); |
| } |
| } |
| |
| Future<void> onStoryDelete(String storyId) async { |
| if (_storyMap.containsKey(storyId)) { |
| int index = _storyIds.indexOf(storyId); |
| _storyMap[storyId].dispose(); |
| _storyIds.remove(storyId); |
| _storyMap.remove(storyId); |
| |
| // Set focus to previous story if present. |
| if (index > 0) { |
| setFocus(_storyIds[index - 1]); |
| } else { |
| focusedStoryId = null; |
| notifyListeners(); |
| } |
| } |
| } |
| |
| Future<void> onStoryFocusRequest(String storyId) async { |
| if (_storyIds.contains(storyId)) { |
| focusedStoryId = storyId; |
| } else { |
| focusedStoryId = null; |
| } |
| await _focusController.set(storyId); |
| notifyListeners(); |
| } |
| |
| /// Called when the user swipes to another story. |
| void onChangeFocus(int index) { |
| // Stories are displayed from second screen onwards, first is empty. |
| focusedStoryId = index > 0 ? _storyIds[index - 1] : null; |
| _focusController.set(focusedStoryId); |
| notifyListeners(); |
| } |
| |
| /// toggles fullscreen mode |
| void toggleFullscreen() { |
| _isFullscreen = !_isFullscreen; |
| notifyListeners(); |
| } |
| |
| /// Requests focus to be set on the story. |
| void setFocus(String storyId) { |
| _focusProvider.request(storyId); |
| } |
| |
| void _onDelete(String storyId) { |
| _puppetMaster.deleteStory(storyId); |
| // Remove from screen immediately. |
| onStoryDelete(storyId); |
| } |
| |
| void _onStopped(String storyId) { |
| _minimizedStories.add(storyId); |
| notifyListeners(); |
| } |
| |
| Future<void> attachView( |
| ViewIdentifier viewId, ViewHolderToken viewHolderToken) async { |
| if (_storyMap.containsKey(viewId.storyId)) { |
| _storyMap[viewId.storyId].attachView(viewHolderToken); |
| } |
| } |
| |
| // SessionShell |
| Future<void> detachView(ViewIdentifier viewId) async { |
| await onStoryDelete(viewId.storyId); |
| } |
| } |
| |
| class StoryProviderWatcherImpl extends StoryProviderWatcher { |
| final StoryManager _storyManager; |
| |
| StoryProviderWatcherImpl(this._storyManager); |
| |
| @override |
| Future<void> onChange(StoryInfo storyInfo, StoryState storyState, |
| StoryVisibilityState storyVisibilityState) async { |
| return _storyManager.onStoryChange( |
| storyInfo, storyState, storyVisibilityState); |
| } |
| |
| @override |
| Future<void> onDelete(String storyId) async { |
| return _storyManager.onStoryDelete(storyId); |
| } |
| } |
| |
| class FocusRequestWatcherImpl extends FocusRequestWatcher { |
| final StoryManager _storyManager; |
| |
| FocusRequestWatcherImpl(this._storyManager); |
| |
| @override |
| Future<void> onFocusRequest(String storyId) { |
| return _storyManager.onStoryFocusRequest(storyId); |
| } |
| } |
| |
| class _SessionShellImpl extends SessionShell { |
| final StoryManager _storyManager; |
| |
| _SessionShellImpl(this._storyManager); |
| |
| @override |
| Future<void> attachView( |
| ViewIdentifier viewId, InterfaceHandle<ViewOwner> viewOwner) async { |
| return attachView2( |
| viewId, |
| ViewHolderToken( |
| value: EventPair(viewOwner.passChannel().passHandle()))); |
| } |
| |
| @override |
| // ignore: override_on_non_overriding_method |
| Future<void> attachView2( |
| ViewIdentifier viewId, ViewHolderToken viewHolderToken) async { |
| return _storyManager.attachView(viewId, viewHolderToken); |
| } |
| |
| @override |
| Future<void> detachView(ViewIdentifier viewId) async { |
| return _storyManager.detachView(viewId); |
| } |
| } |