syncslides: Ability to delete a deck and some polish / bug fixes
Closes: https://github.com/vanadium/syncslides/issues/21
Change-Id: I9a08c09240abd552c2d42f273fbcc06627fcc104
diff --git a/dart/Makefile b/dart/Makefile
index 670dcc3..9893527 100644
--- a/dart/Makefile
+++ b/dart/Makefile
@@ -61,7 +61,7 @@
endef
.PHONY: install
-install: build deploy
+install: build
$(call GENERATE_SHORTCUT_FILE,$(GS_BUCKET_URL),$(SYNCBASE_ARGS))
adb -s $(DEVICE_ID) push -p shortcut_commands $(MOJO_SHELL_CMD_PATH)
adb -s $(DEVICE_ID) shell chmod 555 $(MOJO_SHELL_CMD_PATH)
@@ -75,7 +75,7 @@
# TODO(aghassemi): Is there a way to remove the shortcut via adb?
.PHONY: deploy
-deploy: packages
+deploy: build
gsutil cp $(APP_FLX_FILE) $(GS_BUCKET_PATH)
gsutil cp $(SYNCBASE_MOJO_DIR)/syncbase_server.mojo $(GS_BUCKET_PATH)
gsutil cp $(DISCOVERY_MOJO_DIR)/discovery.mojo $(GS_BUCKET_PATH)
@@ -108,7 +108,7 @@
.PHONY: clean
clean:
- rm -f app.flx snapshot_blob.bin
+ rm -f app.flx snapshot_blob.bin shortcut_commands
rm -rf packages
adb -s $(DEVICE_ID) shell run-as org.chromium.mojo.shell rm $(SETTINGS_FILE) settings_commands
diff --git a/dart/flutter.yaml b/dart/flutter.yaml
index 05af4c9..f7279fd 100644
--- a/dart/flutter.yaml
+++ b/dart/flutter.yaml
@@ -1,6 +1,7 @@
name: syncslides
material-design-icons:
- name: action/account_circle
+ - name: action/delete
- name: action/perm_device_information
- name: av/loop
- name: av/play_arrow
diff --git a/dart/lib/components/deckgrid.dart b/dart/lib/components/deckgrid.dart
index 0874c53..fc7b71a 100644
--- a/dart/lib/components/deckgrid.dart
+++ b/dart/lib/components/deckgrid.dart
@@ -23,15 +23,18 @@
}
Widget build(BuildContext context, AppState appState, AppActions appActions) {
- // Local decks.
- List<model.Deck> decks = appState.decks.values
- .where((DeckState d) => d.deck != null && d.presentation == null)
- .map((DeckState d) => d.deck);
-
// Advertised decks.
List<model.PresentationAdvertisement> presentations =
appState.presentationAdvertisements.values;
+ // Local decks that are not presented or advertised.
+ List<model.Deck> decks = appState.decks.values
+ .where((DeckState d) => d.deck != null &&
+ d.presentation == null &&
+ !presentations.any((model.PresentationAdvertisement p) =>
+ p.deck.key == d.deck.key))
+ .map((DeckState d) => d.deck);
+
Widget title = new Text('SyncSlides');
Widget drawer = new IconButton(icon: "navigation/menu", onPressed: () {
showDrawer(
@@ -73,19 +76,17 @@
List<Widget> presentationBoxes = _presentations
.map((presentation) => _buildPresentationBox(context, presentation));
var allBoxes = new List.from(presentationBoxes)..addAll(deckBoxes);
- var grid = new Grid(allBoxes, maxChildExtent: style.Size.thumbnailWidth);
+ var grid = new Grid(allBoxes, maxChildExtent: style.Size.gridbox);
return new ScrollableViewport(child: grid);
}
Widget _buildDeckBox(BuildContext context, model.Deck deckData) {
- var thumbnail =
- new AsyncImage(provider: imageProvider.getDeckThumbnailImage(deckData));
- // TODO(aghassemi): Add "Opened on" data.
- var subtitleWidget =
- new Text("Opened on Sep 12, 2015", style: style.Text.subtitleStyle);
- subtitleWidget = stopWrapping(subtitleWidget);
- var footer = _buildBoxFooter(deckData.name, subtitleWidget);
- var box = _buildCard(deckData.key, [thumbnail, footer], () {
+ var thumbnail = new AsyncImage(
+ provider: imageProvider.getDeckThumbnailImage(deckData),
+ fit: ImageFit.scaleDown);
+
+ var footer = _buildBoxFooter(deckData.name);
+ var box = _buildCard(deckData.key, thumbnail, footer, () {
Navigator.push(
context,
new MaterialPageRoute(
@@ -98,16 +99,17 @@
Widget _buildPresentationBox(
BuildContext context, model.PresentationAdvertisement presentationData) {
var thumbnail = new AsyncImage(
- provider: imageProvider.getDeckThumbnailImage(presentationData.deck));
+ 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,
- margin: style.Spacing.normalMargin,
padding: style.Spacing.extraSmallPadding)
]);
- var footer = _buildBoxFooter(presentationData.deck.name, liveBox);
- var box = _buildCard(presentationData.key, [thumbnail, footer], () async {
+
+ 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);
@@ -136,18 +138,32 @@
return box;
}
- Widget _buildBoxFooter(String title, Widget subtitle) {
- var titleWidget = new Text(title, style: style.Text.titleStyle);
- titleWidget = stopWrapping(titleWidget);
+ Widget _buildBoxFooter(String title, {Widget subtitle}) {
+ var titleChildren = [new Text(title, style: style.Text.titleStyle)];
+ if (subtitle != null) {
+ titleChildren.add(subtitle);
+ }
- var titleAndSubtitle = new BlockBody([titleWidget, subtitle]);
- return new Container(
- child: titleAndSubtitle, padding: style.Spacing.normalPadding);
+ var titleContainer = new Container(
+ child: new BlockBody(titleChildren),
+ padding: style.Spacing.normalPadding);
+
+ titleContainer = stopWrapping(titleContainer);
+
+ return titleContainer;
}
- Widget _buildCard(String key, List<Widget> children, Function onTap) {
+ 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 BlockBody(children)),
+ 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);
diff --git a/dart/lib/components/slidelist.dart b/dart/lib/components/slidelist.dart
index 86d0ac6..dfcf945 100644
--- a/dart/lib/components/slidelist.dart
+++ b/dart/lib/components/slidelist.dart
@@ -26,17 +26,36 @@
}
var deckState = appState.decks[_deckId];
var slides = deckState.slides;
+ var toolbarActions = [];
+ var deleteAction = _buildDelete(context, appState, appActions);
+ if (deleteAction != null) {
+ toolbarActions.add(deleteAction);
+ }
return new Scaffold(
key: _scaffoldKey,
toolBar: new ToolBar(
left: new IconButton(
icon: 'navigation/arrow_back',
onPressed: () => Navigator.pop(context)),
- center: new Text(deckState.deck.name)),
+ center: new Text(deckState.deck.name),
+ right: toolbarActions),
floatingActionButton: _buildPresentFab(context, appState, appActions),
body: new Material(child: new SlideList(_deckId, slides, appActions)));
}
+ _buildDelete(BuildContext context, AppState appState, AppActions appActions) {
+ var deckState = appState.decks[_deckId];
+ if (deckState.presentation != null) {
+ // Can't delete while in a presentation.
+ return null;
+ }
+
+ return new IconButton(icon: 'action/delete', onPressed: () async {
+ await appActions.removeDeck(deckState.deck.key);
+ Navigator.of(context).pop();
+ });
+ }
+
_buildPresentFab(
BuildContext context, AppState appState, AppActions appActions) {
var deckState = appState.decks[_deckId];
@@ -92,7 +111,8 @@
{Function onTap}) {
var thumbnail = new AsyncImage(
provider: imageProvider.getSlideImage(deckId, slideData),
- fit: ImageFit.scaleDown);
+ fit: ImageFit.cover,
+ width: style.Size.slideListThumbnailWidth);
thumbnail = new Flexible(child: new Container(child: thumbnail), flex: 0);
diff --git a/dart/lib/components/slideshow.dart b/dart/lib/components/slideshow.dart
index fee3b05..a3f3aee 100644
--- a/dart/lib/components/slideshow.dart
+++ b/dart/lib/components/slideshow.dart
@@ -80,7 +80,7 @@
Widget _buildPortraitLayout(BuildContext context) {
var image = new Flexible(child: _buildImage(context), flex: 5);
- var actions = new Flexible(child: _buildActions(context), flex: 1);
+ var actions = new Flexible(child: _buildActions(context), flex: 0);
var notes = new Flexible(child: _buildNotes(), flex: 3);
var nav = new Flexible(child: new Row(_buildThumbnailNavs()), flex: 3);
@@ -101,7 +101,7 @@
var nav = new Flexible(child: new Column(_buildThumbnailNavs()), flex: 8);
var image = new Flexible(child: _buildImage(context), flex: 11);
- var actions = new Flexible(child: _buildActions(context), flex: 2);
+ var actions = new Flexible(child: _buildActions(context), flex: 0);
var notesAndNavColumn = new Flexible(
child: new Column([notes, nav], alignItems: FlexAlignItems.stretch),
diff --git a/dart/lib/components/utils/stop_wrapping.dart b/dart/lib/components/utils/stop_wrapping.dart
index 7c4bd17..7b89e5d 100644
--- a/dart/lib/components/utils/stop_wrapping.dart
+++ b/dart/lib/components/utils/stop_wrapping.dart
@@ -4,7 +4,7 @@
import 'package:flutter/material.dart';
-Widget stopWrapping(Text child) {
+Widget stopWrapping(Widget child) {
// TODO(aghassemi): There is no equivalent of CSS's white-space: nowrap,
// overflow: hidden or text-overflow: ellipsis in Flutter yet.
// This workaround simulates white-space: nowrap and overflow: hidden.
diff --git a/dart/lib/main.dart b/dart/lib/main.dart
index cb32ae3..039f701 100644
--- a/dart/lib/main.dart
+++ b/dart/lib/main.dart
@@ -17,7 +17,7 @@
_initLogging();
_initBackButtonHandler();
- // TODO(aghassemi): Splash screen while store is initializing.
+ // TODO(aghassemi): Display splash screen while store is initializing.
store.init().then((_) => runApp(new MaterialApp(
theme: style.theme,
title: 'SyncSlides',
diff --git a/dart/lib/stores/syncbase/actions.dart b/dart/lib/stores/syncbase/actions.dart
index 86b1b7d..2a3f2fe 100644
--- a/dart/lib/stores/syncbase/actions.dart
+++ b/dart/lib/stores/syncbase/actions.dart
@@ -100,7 +100,8 @@
// Syncgroup for deck and presentation data, including blobs.
await sb.createSyncgroup(_state.settings.mounttable, syncgroupName, [
sb.SyncbaseClient.syncgroupPrefix(decksTableName, deckId),
- sb.SyncbaseClient.syncgroupPrefix(presentationsTableName, deckId),
+ sb.SyncbaseClient.syncgroupPrefix(presentationsTableName,
+ keyutil.getPresentationPrefix(deckId, presentationId)),
sb.SyncbaseClient.syncgroupPrefix(blobsTableName, deckId)
]);
diff --git a/dart/lib/stores/syncbase/store.dart b/dart/lib/stores/syncbase/store.dart
index 64e705f..5f27c96 100644
--- a/dart/lib/stores/syncbase/store.dart
+++ b/dart/lib/stores/syncbase/store.dart
@@ -159,7 +159,7 @@
_state._getOrCreateDeckState(deckId)._deck =
new model.Deck.fromJson(deckId, UTF8.decode(value));
} else if (changeType == sb.WatchChangeTypes.delete) {
- _state.decks.remove(deckId);
+ _state._decks.remove(deckId);
}
}
@@ -199,7 +199,6 @@
_onPresentationDriverChange(int changeType, String rowKey, List<int> value) {
String deckId = keyutil.presentationDriverKeyToDeckId(rowKey);
-
_DeckState deckState = _state._getOrCreateDeckState(deckId);
_PresentationState presentationState = deckState.presentation;
if (presentationState == null) {
diff --git a/dart/lib/stores/utils/key.dart b/dart/lib/stores/utils/key.dart
index 367dee9..3c0f154 100644
--- a/dart/lib/stores/utils/key.dart
+++ b/dart/lib/stores/utils/key.dart
@@ -42,6 +42,11 @@
return deckId + '/';
}
+// Constructs a key prefix for a presentation.
+String getPresentationPrefix(String deckId, String presentationId) {
+ return '$deckId/$presentationId';
+}
+
// Returns true if a key is for a deck.
bool isDeckKey(String key) {
return !key.contains('/');
diff --git a/dart/lib/styles/common.dart b/dart/lib/styles/common.dart
index 01bd5ff..71668a0 100644
--- a/dart/lib/styles/common.dart
+++ b/dart/lib/styles/common.dart
@@ -16,10 +16,12 @@
}
class Size {
- static const double thumbnailWidth = 250.0;
+ static const double gridbox = 250.0;
+ static const double boxFooterHeight = 55.0;
static const double listHeight = 120.0;
static const double thumbnailNavHeight = 250.0;
static const double questionListThumbnailWidth = 100.0;
+ static const double slideListThumbnailWidth = 200.0;
}
class Spacing {