| // Copyright 2015 The Vanadium 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 '../models/all.dart' as model; |
| import '../stores/store.dart'; |
| import '../styles/common.dart' as style; |
| import '../utils/image_provider.dart' as imageProvider; |
| import 'slidelist.dart'; |
| import 'slideshow.dart'; |
| import 'syncslides_page.dart'; |
| import 'toast.dart' as toast; |
| import 'utils/stop_wrapping.dart'; |
| |
| final GlobalKey _scaffoldKey = new GlobalKey(); |
| |
| // DeckGridPage is the full page view of the list of decks. |
| class DeckGridPage extends SyncSlidesPage { |
| Widget build(BuildContext context, AppState appState, AppActions appActions) { |
| // Advertised decks. |
| List<model.PresentationAdvertisement> presentations = |
| appState.presentationAdvertisements.values; |
| |
| // Local decks that are not advertised. |
| List<model.Deck> decks = appState.decks.values |
| .where((DeckState d) => d.deck != null && |
| !presentations.any((model.PresentationAdvertisement p) => |
| p.deck.key == d.deck.key)) |
| .map((DeckState d) => d.deck); |
| |
| return new Scaffold( |
| key: _scaffoldKey, |
| toolBar: new ToolBar(center: new Text('SyncSlides')), |
| floatingActionButton: new FloatingActionButton( |
| child: new Icon(icon: 'content/add'), onPressed: () { |
| appActions.loadDeckFromSdCard(); |
| }), |
| drawer: _buildDrawer(context, appState), |
| body: new Material( |
| child: new DeckGrid(decks, presentations, appState, appActions))); |
| } |
| |
| Widget _buildDrawer(BuildContext context, AppState appState) { |
| return new Drawer( |
| child: new Block([ |
| new DrawerItem( |
| icon: 'action/account_circle', |
| child: stopWrapping( |
| new Text(appState.user.name, style: style.Text.titleStyle))), |
| new DrawerItem( |
| icon: 'action/perm_device_information', |
| child: stopWrapping(new Text(appState.settings.deviceId, |
| style: style.Text.titleStyle))) |
| ])); |
| } |
| } |
| |
| // DeckGrid is scrollable grid view of decks. |
| class DeckGrid extends StatelessComponent { |
| AppActions _appActions; |
| AppState _appState; |
| List<model.Deck> _decks; |
| List<model.PresentationAdvertisement> _presentations; |
| |
| DeckGrid(this._decks, this._presentations, this._appState, this._appActions); |
| |
| Widget build(BuildContext context) { |
| List<Widget> deckBoxes = _decks.map((deck) => _buildDeckBox(context, deck)); |
| List<Widget> presentationBoxes = _presentations |
| .map((presentation) => _buildPresentationBox(context, presentation)); |
| var allBoxes = new List.from(presentationBoxes)..addAll(deckBoxes); |
| var grid = new ScrollableGrid( |
| children: allBoxes, |
| delegate: |
| new MaxTileWidthGridDelegate(maxTileWidth: style.Size.gridbox)); |
| return grid; |
| } |
| |
| Widget _buildDeckBox(BuildContext context, model.Deck deckData) { |
| var thumbnail = new AsyncImage( |
| provider: imageProvider.getDeckThumbnailImage(deckData), |
| fit: ImageFit.scaleDown); |
| |
| var resumeLiveBox; |
| var presentationState = _appState.decks[deckData.key]?.presentation; |
| if (presentationState != null && presentationState.isOwner) { |
| resumeLiveBox = new Row([ |
| new Container( |
| child: new Text("RESUME PRESENTING", style: style.Text.liveNow), |
| decoration: style.Box.liveNow, |
| padding: style.Spacing.extraSmallPadding) |
| ]); |
| } |
| |
| var footer = _buildBoxFooter(deckData.name, subtitle: resumeLiveBox); |
| var box = _buildCard(deckData.key, thumbnail, footer, () { |
| Navigator.push( |
| context, |
| new MaterialPageRoute( |
| builder: (context) => new SlideListPage(deckData.key))); |
| }); |
| |
| return box; |
| } |
| |
| Widget _buildPresentationBox( |
| BuildContext context, model.PresentationAdvertisement presentationData) { |
| var thumbnail = new AsyncImage( |
| provider: imageProvider.getDeckThumbnailImage(presentationData.deck), |
| fit: ImageFit.scaleDown); |
| var liveBox = new Row([ |
| new Container( |
| child: new Text("LIVE NOW", style: style.Text.liveNow), |
| decoration: style.Box.liveNow, |
| padding: style.Spacing.extraSmallPadding) |
| ]); |
| |
| var footer = _buildBoxFooter(presentationData.deck.name, subtitle: liveBox); |
| var box = _buildCard(presentationData.key, thumbnail, footer, () async { |
| toast.info( |
| _scaffoldKey, 'Joining presentation ${presentationData.deck.name}...', |
| duration: toast.Durations.permanent); |
| try { |
| await _appActions.joinPresentation(presentationData); |
| |
| toast.info( |
| _scaffoldKey, 'Joined presentation ${presentationData.deck.name}.'); |
| |
| Navigator.openTransaction(context, (NavigatorTransaction transaction) { |
| // Push slides list page first before navigating to the slideshow. |
| transaction.push(new MaterialPageRoute( |
| builder: (context) => |
| new SlideListPage(presentationData.deck.key))); |
| transaction.push(new MaterialPageRoute( |
| builder: (context) => |
| new SlideshowPage(presentationData.deck.key))); |
| }); |
| } catch (e) { |
| toast.error(_scaffoldKey, |
| 'Failed to start presentation ${presentationData.deck.name}.', e); |
| } |
| }); |
| return box; |
| } |
| |
| Widget _buildBoxFooter(String title, {Widget subtitle}) { |
| var titleChildren = [new Text(title, style: style.Text.titleStyle)]; |
| if (subtitle != null) { |
| titleChildren.add(subtitle); |
| } |
| |
| var titleContainer = new Container( |
| child: new BlockBody(titleChildren), |
| padding: style.Spacing.normalPadding); |
| |
| titleContainer = stopWrapping(titleContainer); |
| |
| return titleContainer; |
| } |
| |
| Widget _buildCard(String key, Widget image, Widget footer, Function onTap) { |
| image = new Flexible(child: image, flex: 1); |
| footer = new Container( |
| child: footer, |
| constraints: new BoxConstraints.tight( |
| new Size.fromHeight(style.Size.boxFooterHeight))); |
| footer = new Flexible(child: footer, flex: 0); |
| var content = new Container( |
| child: new Card( |
| child: new Column([image, footer], |
| alignItems: FlexAlignItems.stretch)), |
| margin: style.Spacing.normalMargin); |
| |
| return new InkWell(key: new Key(key), child: content, onTap: onTap); |
| } |
| } |