| /// 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 'dart:math' as math; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:fuchsia_logger/logger.dart'; |
| import 'package:xi_client/client.dart'; |
| |
| import 'document.dart'; |
| import 'editor.dart'; |
| import 'editor_host.dart'; |
| |
| /// Widget that displays multiple editors in a tab view. |
| class EditorTabs extends EditorHost { |
| const EditorTabs({ |
| @required coreProxy, |
| debugBackground = false, |
| Key key, |
| }) : assert(coreProxy != null), |
| super(key: key, coreProxy: coreProxy, debugBackground: debugBackground); |
| |
| @override |
| State<EditorTabs> createState() => new EditorTabsState(); |
| } |
| |
| class EditorTabsState extends State<EditorTabs> |
| with TickerProviderStateMixin |
| implements XiHandler { |
| /// the order of views as displayed in tabs |
| final List<String> _viewIds = []; |
| |
| final Map<String, Document> _documents = {}; |
| |
| /// We display "Untitled 1" in the tab instead of an internal view id |
| final Map<String, String> _fakeDocumentTitles = {}; |
| |
| int _nextDocumentTitleNumber = 1; |
| |
| TabController _tabController; |
| |
| @override |
| void initState() { |
| super.initState(); |
| widget.coreProxy.handler = this; |
| widget.coreProxy.clientStarted().then((_) => newView()); |
| _tabController = new TabController(vsync: this, length: 0); |
| } |
| |
| void newView() { |
| widget.coreProxy.newView().then((viewId) { |
| setState(() { |
| _viewIds.add(viewId); |
| _fakeDocumentTitles[viewId] = 'Untitled $_nextDocumentTitleNumber'; |
| _nextDocumentTitleNumber += 1; |
| _documents.putIfAbsent(viewId, () => new Document()); |
| _documents[viewId].finalizeViewProxy(widget.coreProxy.view(viewId)); |
| int prevIndex = _tabController.index; |
| _tabController = new TabController( |
| vsync: this, length: _viewIds.length, initialIndex: prevIndex) |
| ..animateTo(_viewIds.length - 1); |
| }); |
| }); |
| } |
| |
| void closeView(String viewId) { |
| setState(() { |
| log.info('closing $viewId, views: $_viewIds'); |
| widget.coreProxy.closeView(viewId); |
| _viewIds.remove(viewId); |
| _documents.remove(viewId); |
| int prevIndex = _tabController.index; |
| _tabController = new TabController( |
| vsync: this, |
| length: _viewIds.length, |
| initialIndex: math.max(0, math.min(prevIndex, _viewIds.length - 1))); |
| }); |
| } |
| |
| @override |
| XiViewHandler getView(String viewId) { |
| // TODO: we create a new document here if one is missing, because this |
| // can get called before `newView`'s future resolves. However this does mean |
| // races are possible if this is called with a `viewId` of a closed view, in which case |
| // we could have some zombie documents sitting around. |
| _documents.putIfAbsent(viewId, () => new Document()); |
| return _documents[viewId]; |
| } |
| |
| @override |
| List<List<double>> measureWidths(List<Map<String, dynamic>> args) { |
| if (_viewIds.first != null) { |
| return _documents[_viewIds.first].measureWidths(args); |
| } |
| log.warning('measureWidths called with no view'); |
| return []; |
| } |
| |
| @override |
| void alert(String text) { |
| // TODO: show an alert dialog? |
| log.warning('core alert: $text'); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| List<Widget> actions = [ |
| new IconButton(icon: Icon(Icons.add), onPressed: newView) |
| ]; |
| if (_viewIds.length > 1) { |
| actions.add(new IconButton( |
| icon: Icon(Icons.remove), |
| onPressed: () => closeView(_viewIds[_tabController.index]))); |
| } |
| |
| return MaterialApp( |
| theme: new ThemeData( |
| primaryColor: Colors.pink[300], |
| ), |
| home: Scaffold( |
| appBar: AppBar( |
| actions: actions, |
| bottom: _viewIds.length > 1 |
| ? TabBar( |
| indicatorColor: Colors.white, |
| indicatorWeight: 4.0, |
| labelStyle: const TextStyle(fontSize: 16.0), |
| isScrollable: true, |
| controller: _tabController, |
| tabs: _viewIds |
| .map((id) => new Tab(text: _fakeDocumentTitles[id])) |
| .toList(), |
| ) |
| : null, |
| ), |
| body: makeMainWidget(), |
| ), |
| ); |
| } |
| |
| Widget makeMainWidget() { |
| if (_viewIds.isEmpty) { |
| return Container(); |
| } |
| |
| if (_documents.length == 1) { |
| return Editor( |
| document: _documents[_viewIds[0]], |
| key: Key(_viewIds[0]), |
| debugBackground: widget.debugBackground); |
| } |
| |
| return TabBarView( |
| physics: const NeverScrollableScrollPhysics(), |
| controller: _tabController, |
| children: _viewIds.map((id) { |
| return new Editor( |
| document: _documents[id], |
| key: Key(id), |
| debugBackground: widget.debugBackground, |
| ); |
| }).toList(), |
| ); |
| } |
| } |