syncslides/flutter: Few bug fixes and improvements.
-Splash screen.
-SdCard importer.
-Simplified stop/start/resume presentation flow.
-Using v23 mojo profile
-Updating to latest Flutter
Change-Id: I15b6addbc2542ee6c12e86427ffa9fb4893a083b
diff --git a/.gitignore b/.gitignore
index c9f8a00..0ef0e2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,10 @@
-/.jiri
-/dart/.pub
+.atom
+/dart/app.flx
+/dart/bin
+/dart/build
/dart/packages
/dart/.packages
-/dart/snapshot_blob.bin
-/dart/app.flx
-/dart/build
+/dart/.pub
/dart/shortcut_commands
-.atom
+/dart/snapshot_blob.bin
+/.jiri
\ No newline at end of file
diff --git a/dart/FLUTTER_VERSION b/dart/FLUTTER_VERSION
index 09f48c6..4142ce8 100644
--- a/dart/FLUTTER_VERSION
+++ b/dart/FLUTTER_VERSION
@@ -1 +1 @@
-519b190c0f2e87f2e03326c0d41566c9d32fc4f6
+e1b16729bfe369bdfd2c21027087f7d0181566e8
diff --git a/dart/MOJO_VERSION b/dart/MOJO_VERSION
deleted file mode 100644
index 3d22a47..0000000
--- a/dart/MOJO_VERSION
+++ /dev/null
@@ -1 +0,0 @@
-4503cee3f5f2d3bc3ad46636af0a1029fb22e108
diff --git a/dart/Makefile b/dart/Makefile
index 9893527..6ae7cd9 100644
--- a/dart/Makefile
+++ b/dart/Makefile
@@ -3,14 +3,14 @@
endif
SYNCBASE_DATA_DIR=/data/data/org.chromium.mojo.shell/app_home/syncbasedata
+# Mounttable address on SyncSlides-Alpha network
+# TODO(aghassemi): Now that BLE discovery support is added and sync uses neighbourhood,
+# we should no longer need a mounttable after upgrading and testing the latest code.
+MOUNTTABLE_ADDR := /192.168.86.254:8101
+
DEVICE_NUM_PLUS_ONE := $(shell echo $(DEVICE_NUM) \+ 1 | bc)
DEVICE_ID := $(shell adb devices | sed -n $(DEVICE_NUM_PLUS_ONE)p | awk '{ print $$1; }')
DEVICE_FLAG := --target-device $(DEVICE_ID)
-MOUNTTABLE_ADDR := /192.168.86.254:8101
-
-# Currently the only way to pass arguments to the app is by using a file.
-SETTINGS_FILE := /sdcard/syncslides_settings.json
-SETTINGS_JSON := {\"deviceid\": \"$(DEVICE_ID)\", \"mounttable\": \"$(MOUNTTABLE_ADDR)\"}
ifneq ($(DEVICE_NUM), 1)
REUSE_FLAG := --reuse-servers
@@ -22,6 +22,32 @@
SYNCBASE_ARGS := https://syncbase.syncslides.mojo.v.io/syncbase_server.mojo --root-dir=$(SYNCBASE_DATA_DIR) --v23.namespace.root=$(MOUNTTABLE_ADDR) --name=$(DEVICE_ID) $(VLOG_FLAGS)
+SETTINGS_FILE := /sdcard/syncslides_settings.json
+SETTINGS_JSON := {\"deviceid\": \"$(DEVICE_ID)\", \"mounttable\": \"$(MOUNTTABLE_ADDR)\"}
+
+SAMPLE_DECKS_PATH := $(PWD)/assets/sample_decks
+SD_CARD_DECKS_PATH = '/sdcard/syncslides/decks'
+
+MOJO_DEVTOOLS := $(shell jiri v23-profile env --profiles=mojo --target=arm-android MOJO_DEVTOOLS=)
+MOJO_SHELL := $(shell jiri v23-profile env --profiles=mojo --target=arm-android MOJO_SHELL=)
+
+GS_BUCKET_PATH := gs://mojo_services/syncslides
+GS_BUCKET_URL := storage.googleapis.com/mojo_services/syncslides
+SYNCSLIDES_URL = mojo://$(GS_BUCKET_URL)/app.flx
+
+APP_FLX_FILE := $(PWD)/build/app.flx
+SYNCBASE_MOJO_DIR := $(PWD)/packages/syncbase/mojo_services/android
+DISCOVERY_MOJO_DIR := $(PWD)/packages/v23discovery/mojo_services/android
+MOJO_SHELL_CMD_PATH := /data/local/tmp/org.chromium.mojo.shell.cmd
+
+SYNCSLIDES_SHORTCUT_NAME := SyncSlides
+SYNCSLIDES_ICON := https://avatars0.githubusercontent.com/u/9374332?v=3&s=200
+
+define GENERATE_SHORTCUT_FILE
+ sed -e "s;%GS_BUCKET_URL%;$1;g" -e "s;%SYNCBASE_FLAGS%;$2;g" \
+ shortcut_template > shortcut_commands
+endef
+
default: run
.PHONY: dartanalyzer
@@ -43,36 +69,25 @@
build: packages
pub run flutter_tools build
-GS_BUCKET_PATH := gs://mojo_services/syncslides
-GS_BUCKET_URL := storage.googleapis.com/mojo_services/syncslides
-SYNCSLIDES_URL = mojo://$(GS_BUCKET_URL)/app.flx
-
-APP_FLX_FILE := $(PWD)/build/app.flx
-SYNCBASE_MOJO_DIR := $(PWD)/packages/syncbase/mojo_services/android
-DISCOVERY_MOJO_DIR := $(PWD)/packages/v23discovery/mojo_services/android
-MOJO_SHELL_CMD_PATH := /data/local/tmp/org.chromium.mojo.shell.cmd
-
-SYNCSLIDES_SHORTCUT_NAME := SyncSlides
-SYNCSLIDES_ICON := https://avatars0.githubusercontent.com/u/9374332?v=3&s=200
-
-define GENERATE_SHORTCUT_FILE
- sed -e "s;%GS_BUCKET_URL%;$1;g" -e "s;%SYNCBASE_FLAGS%;$2;g" \
- shortcut_template > shortcut_commands
-endef
-
-.PHONY: install
-install: build
+.PHONY: create-shortcut
+create-shortcut: clear-shortcut build install-shell
$(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)
adb -s $(DEVICE_ID) shell 'echo $(SETTINGS_JSON) > $(SETTINGS_FILE)'
- $(MOJO_DIR)/src/mojo/devtools/common/mojo_run --android $(DEVICE_FLAG) "mojo:shortcut $(SYNCSLIDES_SHORTCUT_NAME) $(SYNCSLIDES_URL) $(SYNCSLIDES_ICON)"
+ $(MOJO_DEVTOOLS)/mojo_run --shell-path $(MOJO_SHELL) --android --target-device=$(DEVICE_ID) "https://$(GS_BUCKET_URL)/shortcut.mojo $(SYNCSLIDES_SHORTCUT_NAME) $(SYNCSLIDES_URL) $(SYNCSLIDES_ICON)"
-.PHONY: uninstall
-uninstall:
- adb -s $(DEVICE_ID) uninstall org.chromium.mojo.shell
+.PHONY: clear-shortcut
+clear-shortcut:
adb -s $(DEVICE_ID) shell rm -f $(MOJO_SHELL_CMD_PATH)
-# TODO(aghassemi): Is there a way to remove the shortcut via adb?
+
+.PHONY: install-shell
+install-shell: clear-shortcut
+ adb -s $(DEVICE_ID) install $(MOJO_SHELL)
+
+.PHONY: uninstall-shell
+uninstall-shell: clear-shortcut
+ adb -s $(DEVICE_ID) uninstall org.chromium.mojo.shell
.PHONY: deploy
deploy: build
@@ -80,14 +95,19 @@
gsutil cp $(SYNCBASE_MOJO_DIR)/syncbase_server.mojo $(GS_BUCKET_PATH)
gsutil cp $(DISCOVERY_MOJO_DIR)/discovery.mojo $(GS_BUCKET_PATH)
gsutil -m acl set -R -a public-read $(GS_BUCKET_PATH)
+ gsutil setmeta -h "Cache-Control:private, max-age=0, no-transform" $(GS_BUCKET_PATH)/*.*
+
+.PHONY: copy-sample-deck
+copy-sample-deck:
+ adb -s $(DEVICE_ID) push -p $(SAMPLE_DECKS_PATH) $(SD_CARD_DECKS_PATH)
# Usage example:
# DEVICE_NUM=1 make run
# DEVICE_NUM=2 make run
-run: build
+run: build install-shell copy-sample-deck
adb -s $(DEVICE_ID) shell 'echo $(SETTINGS_JSON) > $(SETTINGS_FILE)'
pub run flutter_tools run_mojo \
- --mojo-path $(MOJO_DIR)/src \
+ --devtools-path $(MOJO_DEVTOOLS)/mojo_run \
--android --mojo-debug -- --enable-multiprocess \
--map-origin="https://syncbase.syncslides.mojo.v.io/=$(SYNCBASE_MOJO_DIR)" \
--map-origin="https://discovery.syncslides.mojo.v.io/=$(DISCOVERY_MOJO_DIR)" \
@@ -110,11 +130,11 @@
clean:
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
+ -adb -s $(DEVICE_ID) shell run-as org.chromium.mojo.shell rm $(SETTINGS_FILE)
.PHONY: clean-syncbase
clean-syncbase:
- adb -s $(DEVICE_ID) shell run-as org.chromium.mojo.shell rm -rf $(SYNCBASE_DATA_DIR)
+ -adb -s $(DEVICE_ID) shell run-as org.chromium.mojo.shell rm -rf $(SYNCBASE_DATA_DIR)
.PHONY: very-clean
.very-clean: clean clean-syncbase
diff --git a/dart/README.md b/dart/README.md
index fc704b7..94ea25e 100644
--- a/dart/README.md
+++ b/dart/README.md
@@ -4,28 +4,18 @@
# Prerequisites
-## Mojo
-
-Currently, development is heavily tied to an existing installation of [Mojo](https://github.com/domokit/mojo). Please ensure that your Mojo checkout is located at `$MOJO_DIR` and has been build for Android. Instructions are available [here](https://github.com/domokit/mojo#mojo).
-
-We only guarantee compatibility with the version of Mojo as specified in MOJO_VERSION.
-
## Flutter
A clone of https://github.com/flutter/flutter/ at the commit # specified in FLUTTER_VERSION file must be available in a directory
called `flutter` at the same level as $V23_ROOT directory.
+## Mojo
+
+Mojo profile for Android target must be installed. You can run `jiri v23-profile install --target=arm-android mojo` to install it.
+
## Dart
-Flutter depends on a relatively new version of the Dart SDK. Therefore, please ensure that you have installed the following version or greater:
-
-```
-Dart VM version: 1.13.0-dev.3.1 (Thu Sep 17 10:54:54 2015) on "linux_x64"
-```
-
-If you are unsure what version you are on, use `dart --version`.
-
-To install Dart, visit the [download page](https://www.dartlang.org/downloads/).
+Mojo profile must be installed. You can run `jiri v23-profile install dart` to install it.
## Android Setup
diff --git a/dart/assets/images/sample_decks/baku/thumb.png b/dart/assets/images/sample_decks/baku/thumb.png
deleted file mode 100644
index e8c8a09..0000000
--- a/dart/assets/images/sample_decks/baku/thumb.png
+++ /dev/null
Binary files differ
diff --git a/dart/assets/images/sample_decks/pitch/thumb.png b/dart/assets/images/sample_decks/pitch/thumb.png
deleted file mode 100644
index 85b9ff8..0000000
--- a/dart/assets/images/sample_decks/pitch/thumb.png
+++ /dev/null
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/thumb.png b/dart/assets/images/sample_decks/vanadium/thumb.png
deleted file mode 100644
index 11ecb19..0000000
--- a/dart/assets/images/sample_decks/vanadium/thumb.png
+++ /dev/null
Binary files differ
diff --git a/dart/assets/images/splash/background.png b/dart/assets/images/splash/background.png
new file mode 100644
index 0000000..4812cbb
--- /dev/null
+++ b/dart/assets/images/splash/background.png
Binary files differ
diff --git a/dart/assets/images/splash/flutter.png b/dart/assets/images/splash/flutter.png
new file mode 100644
index 0000000..0f468b6
--- /dev/null
+++ b/dart/assets/images/splash/flutter.png
Binary files differ
diff --git a/dart/assets/images/splash/vanadium.png b/dart/assets/images/splash/vanadium.png
new file mode 100644
index 0000000..6da0b2a
--- /dev/null
+++ b/dart/assets/images/splash/vanadium.png
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/1.jpg b/dart/assets/sample_decks/Vanadium Overview/1.jpg
similarity index 100%
rename from dart/assets/images/sample_decks/vanadium/1.jpg
rename to dart/assets/sample_decks/Vanadium Overview/1.jpg
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/2.jpg b/dart/assets/sample_decks/Vanadium Overview/2.jpg
similarity index 100%
rename from dart/assets/images/sample_decks/vanadium/2.jpg
rename to dart/assets/sample_decks/Vanadium Overview/2.jpg
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/3.jpg b/dart/assets/sample_decks/Vanadium Overview/3.jpg
similarity index 100%
rename from dart/assets/images/sample_decks/vanadium/3.jpg
rename to dart/assets/sample_decks/Vanadium Overview/3.jpg
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/4.jpg b/dart/assets/sample_decks/Vanadium Overview/4.jpg
similarity index 100%
rename from dart/assets/images/sample_decks/vanadium/4.jpg
rename to dart/assets/sample_decks/Vanadium Overview/4.jpg
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/5.jpg b/dart/assets/sample_decks/Vanadium Overview/5.jpg
similarity index 100%
rename from dart/assets/images/sample_decks/vanadium/5.jpg
rename to dart/assets/sample_decks/Vanadium Overview/5.jpg
Binary files differ
diff --git a/dart/assets/images/sample_decks/vanadium/6.jpg b/dart/assets/sample_decks/Vanadium Overview/6.jpg
similarity index 100%
rename from dart/assets/images/sample_decks/vanadium/6.jpg
rename to dart/assets/sample_decks/Vanadium Overview/6.jpg
Binary files differ
diff --git a/dart/flutter.yaml b/dart/flutter.yaml
index f7279fd..ac78010 100644
--- a/dart/flutter.yaml
+++ b/dart/flutter.yaml
@@ -4,6 +4,7 @@
- name: action/delete
- name: action/perm_device_information
- name: av/loop
+ - name: av/stop
- name: av/play_arrow
- name: communication/live_help
- name: content/add
@@ -15,12 +16,6 @@
- name: maps/layers
assets:
- assets/images/defaults/thumbnail.png
- - assets/images/sample_decks/vanadium/1.jpg
- - assets/images/sample_decks/vanadium/2.jpg
- - assets/images/sample_decks/vanadium/3.jpg
- - assets/images/sample_decks/vanadium/4.jpg
- - assets/images/sample_decks/vanadium/5.jpg
- - assets/images/sample_decks/vanadium/6.jpg
- - assets/images/sample_decks/baku/thumb.png
- - assets/images/sample_decks/vanadium/thumb.png
- - assets/images/sample_decks/pitch/thumb.png
+ - assets/images/splash/background.png
+ - assets/images/splash/vanadium.png
+ - assets/images/splash/flutter.png
diff --git a/dart/lib/components/deckgrid.dart b/dart/lib/components/deckgrid.dart
index b87e395..04199f5 100644
--- a/dart/lib/components/deckgrid.dart
+++ b/dart/lib/components/deckgrid.dart
@@ -18,19 +18,14 @@
// DeckGridPage is the full page view of the list of decks.
class DeckGridPage extends SyncSlidesPage {
- initState(AppState appState, AppActions appActions) {
- appActions.stopAllPresentations();
- }
-
Widget build(BuildContext context, AppState appState, AppActions appActions) {
// Advertised decks.
List<model.PresentationAdvertisement> presentations =
appState.presentationAdvertisements.values;
- // Local decks that are not presented or advertised.
+ // Local decks that are not 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);
@@ -40,11 +35,11 @@
toolBar: new ToolBar(center: new Text('SyncSlides')),
floatingActionButton: new FloatingActionButton(
child: new Icon(icon: 'content/add'), onPressed: () {
- appActions.loadDemoDeck();
+ appActions.loadDeckFromSdCard();
}),
drawer: _buildDrawer(context, appState),
body: new Material(
- child: new DeckGrid(decks, presentations, appActions)));
+ child: new DeckGrid(decks, presentations, appState, appActions)));
}
Widget _buildDrawer(BuildContext context, AppState appState) {
@@ -65,18 +60,22 @@
// 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._appActions);
+ 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 Grid(allBoxes, maxChildExtent: style.Size.gridbox);
- return new ScrollableViewport(child: grid);
+ var grid = new ScrollableGrid(
+ children: allBoxes,
+ delegate:
+ new MaxTileWidthGridDelegate(maxTileWidth: style.Size.gridbox));
+ return grid;
}
Widget _buildDeckBox(BuildContext context, model.Deck deckData) {
@@ -84,7 +83,18 @@
provider: imageProvider.getDeckThumbnailImage(deckData),
fit: ImageFit.scaleDown);
- var footer = _buildBoxFooter(deckData.name);
+ 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,
diff --git a/dart/lib/components/slidelist.dart b/dart/lib/components/slidelist.dart
index 5fa2422..82758e8 100644
--- a/dart/lib/components/slidelist.dart
+++ b/dart/lib/components/slidelist.dart
@@ -39,7 +39,7 @@
onPressed: () => Navigator.pop(context)),
center: new Text(deckState.deck.name),
right: toolbarActions),
- floatingActionButton: _buildPresentFab(context, appState, appActions),
+ floatingActionButton: _buildFab(context, appState, appActions),
body: new Material(child: new SlideList(_deckId, slides, appActions)));
}
@@ -56,31 +56,47 @@
});
}
- _buildPresentFab(
- BuildContext context, AppState appState, AppActions appActions) {
+ _buildFab(BuildContext context, AppState appState, AppActions appActions) {
var deckState = appState.decks[_deckId];
- if (deckState.presentation != null) {
- // Can't present when already in a presentation.
- return null;
+
+ if (deckState.presentation == null) {
+ return new FloatingActionButton(child: new Icon(icon: 'av/play_arrow'),
+ onPressed: () async {
+ toast.info(_scaffoldKey, 'Starting presentation...',
+ duration: toast.Durations.permanent);
+
+ try {
+ await appActions.startPresentation(_deckId);
+ toast.info(_scaffoldKey, 'Presentation started.');
+
+ Navigator.push(
+ context,
+ new MaterialPageRoute(
+ builder: (context) => new SlideshowPage(_deckId)));
+ } catch (e) {
+ toast.error(_scaffoldKey, 'Failed to start presentation.', e);
+ }
+ });
}
- return new FloatingActionButton(child: new Icon(icon: 'av/play_arrow'),
- onPressed: () async {
- toast.info(_scaffoldKey, 'Starting presentation...',
- duration: toast.Durations.permanent);
+ // Already presenting own deck, allow for stopping it.
+ if (deckState.presentation != null && deckState.presentation.isOwner) {
+ var key = deckState.presentation.key;
+ return new FloatingActionButton(child: new Icon(icon: 'av/stop'),
+ onPressed: () async {
+ toast.info(_scaffoldKey, 'Stopping presentation...',
+ duration: toast.Durations.permanent);
- try {
- await appActions.startPresentation(_deckId);
- toast.info(_scaffoldKey, 'Presentation started.');
+ try {
+ await appActions.stopPresentation(key);
+ toast.info(_scaffoldKey, 'Presentation stopped.');
+ } catch (e) {
+ toast.error(_scaffoldKey, 'Failed to stop presentation.', e);
+ }
+ });
+ }
- Navigator.push(
- context,
- new MaterialPageRoute(
- builder: (context) => new SlideshowPage(_deckId)));
- } catch (e) {
- toast.error(_scaffoldKey, 'Failed to start presentation.', e);
- }
- });
+ return null;
}
}
@@ -91,18 +107,18 @@
SlideList(this._deckId, this._slides, this._appActions);
Widget build(BuildContext context) {
- return new ScrollableList(
- itemExtent: style.Size.listHeight,
- items: _slides,
- itemBuilder: (context, value, index) =>
- _buildSlide(context, _deckId, index, value, onTap: () {
- _appActions.setCurrSlideNum(_deckId, index);
+ Iterable<Widget> items = _slides.map(
+ (slide) => _buildSlide(context, _deckId, slide.num, slide, onTap: () {
+ _appActions.setCurrSlideNum(_deckId, slide.num);
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => new SlideshowPage(_deckId)));
}));
+
+ return new ScrollableList(
+ itemExtent: style.Size.listHeight, children: items);
}
}
@@ -126,7 +142,10 @@
padding: style.Spacing.normalPadding));
var card = new Container(
- child: new Card(child: new Row([thumbnail, titleAndNotes])),
+ child: new Container(
+ margin: style.Spacing.cardMargin,
+ child: new Material(
+ elevation: 2, child: new Row([thumbnail, titleAndNotes]))),
margin: style.Spacing.listItemMargin);
var listItem = new InkWell(
diff --git a/dart/lib/components/slideshow.dart b/dart/lib/components/slideshow.dart
index a3f3aee..921181d 100644
--- a/dart/lib/components/slideshow.dart
+++ b/dart/lib/components/slideshow.dart
@@ -10,7 +10,7 @@
import '../utils/image_provider.dart' as imageProvider;
import 'askquestion.dart';
import 'questionlist.dart';
-import 'slideshow_immersive.dart';
+import 'slideshow_fullscreen.dart';
import 'syncslides_page.dart';
final GlobalKey _scaffoldKey = new GlobalKey();
@@ -135,7 +135,7 @@
var image = new AsyncImage(provider: provider, fit: ImageFit.scaleDown);
- // If not driving the presentation, tapping the image navigates to the immersive mode.
+ // If not driving the presentation, tapping the image navigates to fullscreen mode.
if (_deckState.presentation == null ||
!_deckState.presentation.isDriving(_appState.user)) {
image = new InkWell(child: image, onTap: () {
@@ -143,7 +143,7 @@
context,
new MaterialPageRoute(
builder: (context) =>
- new SlideshowImmersivePage(_deckState.deck.key)));
+ new SlideshowFullscreenPage(_deckState.deck.key)));
});
}
diff --git a/dart/lib/components/slideshow_immersive.dart b/dart/lib/components/slideshow_fullscreen.dart
similarity index 77%
rename from dart/lib/components/slideshow_immersive.dart
rename to dart/lib/components/slideshow_fullscreen.dart
index b6528c0..009592c 100644
--- a/dart/lib/components/slideshow_immersive.dart
+++ b/dart/lib/components/slideshow_fullscreen.dart
@@ -9,10 +9,10 @@
import '../utils/image_provider.dart' as imageProvider;
import 'slideshow.dart';
-class SlideshowImmersivePage extends SlideshowPage {
+class SlideshowFullscreenPage extends SlideshowPage {
String _deckId;
- SlideshowImmersivePage(String deckId) : super(deckId) {
+ SlideshowFullscreenPage(String deckId) : super(deckId) {
_deckId = deckId;
}
@@ -30,7 +30,8 @@
}
var provider = imageProvider.getSlideImage(
deckState.deck.key, deckState.slides[currSlideNum]);
-
- return new AsyncImage(provider: provider);
+ return new GestureDetector(
+ child: new AsyncImage(provider: provider, fit: ImageFit.contain),
+ onTap: () => Navigator.pop(context));
}
}
diff --git a/dart/lib/loaders/demo_loader.dart b/dart/lib/loaders/demo_loader.dart
deleted file mode 100644
index 4585668..0000000
--- a/dart/lib/loaders/demo_loader.dart
+++ /dev/null
@@ -1,87 +0,0 @@
-// 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 'dart:async';
-import 'dart:math';
-
-import '../models/all.dart' as model;
-import '../stores/store.dart';
-import '../stores/utils/key.dart' as keyutil;
-import '../utils/asset.dart' as assetutil;
-import '../utils/uuid.dart' as uuidutil;
-import 'loader.dart';
-
-// DemoLoader loads some sample decks and slides and randomly adds/removes
-// decks based on a timer.
-class DemoLoader implements Loader {
- final Store _store = new Store.singleton();
- final Random _rand = new Random();
-
- static final List<String> thumbnails = [
- 'assets/images/sample_decks/baku/thumb.png',
- 'assets/images/sample_decks/vanadium/thumb.png',
- 'assets/images/sample_decks/pitch/thumb.png'
- ];
- static final List<String> slides = [
- 'assets/images/sample_decks/vanadium/1.jpg',
- 'assets/images/sample_decks/vanadium/2.jpg',
- 'assets/images/sample_decks/vanadium/3.jpg',
- 'assets/images/sample_decks/vanadium/4.jpg',
- 'assets/images/sample_decks/vanadium/5.jpg',
- 'assets/images/sample_decks/vanadium/6.jpg'
- ];
- static final List<String> firstWords = [
- 'Today\'s',
- 'Yesterday\'s',
- 'Ali\'s',
- 'Adam\'s',
- 'Misha\'s'
- ];
- static final List<String> secondWords = [
- 'Presentation',
- 'Slideshow',
- 'Meeting',
- 'Pitch',
- 'Discussion',
- 'Demo',
- 'All Hands'
- ];
-
- static const int maxNumSlides = 20;
-
- Future<model.Deck> _getRandomDeck() async {
- var firstWord = firstWords[_rand.nextInt(firstWords.length)];
- var secondWord = secondWords[_rand.nextInt(secondWords.length)];
- var thumbnail = await assetutil
- .getRawBytes(thumbnails[_rand.nextInt(thumbnails.length)]);
-
- var deckId = uuidutil.createUuid();
- var blobRef = new model.BlobRef(
- keyutil.getDeckBlobKey(deckId, uuidutil.createUuid()));
-
- await _store.actions.putBlob(blobRef.key, thumbnail);
-
- return new model.Deck(deckId, '$firstWord $secondWord', blobRef);
- }
-
- Stream<model.Slide> _getRandomSlides(model.Deck deck) async* {
- var numSlides = _rand.nextInt(maxNumSlides);
- for (var i = 0; i < numSlides; i++) {
- var slideIndex = i % slides.length;
- var blobRef = new model.BlobRef(
- keyutil.getDeckBlobKey(deck.key, uuidutil.createUuid()));
- var image = await assetutil.getRawBytes(
- 'assets/images/sample_decks/vanadium/${slideIndex + 1}.jpg');
- await _store.actions.putBlob(blobRef.key, image);
- yield new model.Slide(i, blobRef);
- }
- }
-
- Future loadDeck() async {
- var deck = await _getRandomDeck();
- List<model.Slide> slides = await _getRandomSlides(deck).toList();
- await _store.actions.addDeck(deck);
- await _store.actions.setSlides(deck.key, slides);
- }
-}
diff --git a/dart/lib/loaders/factory.dart b/dart/lib/loaders/factory.dart
deleted file mode 100644
index 354dd93..0000000
--- a/dart/lib/loaders/factory.dart
+++ /dev/null
@@ -1,16 +0,0 @@
-// 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 'demo_loader.dart';
-import 'loader.dart';
-import 'sdcard_loader.dart';
-
-// Factory method to create a concrete loader instance.
-Loader createDemoLoader() {
- return new DemoLoader();
-}
-
-Loader createSdCardLoader() {
- return new SdCardLoader();
-}
diff --git a/dart/lib/loaders/loader.dart b/dart/lib/loaders/loader.dart
index 8113924..4d6a58e 100644
--- a/dart/lib/loaders/loader.dart
+++ b/dart/lib/loaders/loader.dart
@@ -3,18 +3,105 @@
// license that can be found in the LICENSE file.
import 'dart:async';
+import 'dart:io';
-import 'factory.dart' as factory;
+import 'package:logging/logging.dart';
+import 'package:path/path.dart' as pathutil;
+
+import '../models/all.dart' as model;
+import '../stores/store.dart';
+import '../stores/utils/key.dart' as keyutil;
+import '../utils/uuid.dart' as uuidutil;
+
+final Logger log = new Logger('loader');
+
+const String _baseDecksPath = '/sdcard/syncslides/decks';
+Directory _baseDecksDir = new Directory(_baseDecksPath);
// Loader is responsible for importing existing decks and slides into the store.
-abstract class Loader {
- factory Loader.demo() {
- return factory.createDemoLoader();
+class Loader {
+ final Store _store = new Store.singleton();
+ static Loader _singletonLoader = new Loader._internal();
+
+ factory Loader.singleton() {
+ return _singletonLoader;
}
- factory Loader.sdcard() {
- return factory.createSdCardLoader();
+ Loader._internal() {}
+
+ // Loads all the decks in /sdcard/syncslides/decks
+ // Name of each deck is the name of its directory.
+ // Slide image files must be numbered.
+ // First slide is used as the thumbnail.
+ // Example:
+ // /sdcard/syncslides/decks/Foo
+ // /sdcard/syncslides/decks/Foo/1.png
+ // /sdcard/syncslides/decks/Foo/2.png
+ // /sdcard/syncslides/decks/Bar
+ // /sdcard/syncslides/decks/Bar/1.gif
+ //
+ // TODO(aghassemi): Replace with a Path Selector dialog.
+ Future loadDeck() async {
+ if (!await _baseDecksDir.exists()) {
+ log.warning('Default $_baseDecksPath directory does not exist.');
+ return;
+ }
+
+ Stream allDecksDir = _baseDecksDir.list();
+ await for (FileSystemEntity fsEntity in allDecksDir) {
+ if (!(fsEntity is Directory)) {
+ log.warning(
+ 'Ignoring non-directory ${pathutil.basename(fsEntity.path)} in $_baseDecksPath');
+ continue;
+ }
+ _loadDeck(fsEntity.path);
+ }
}
- Future loadDeck();
+ Future _loadDeck(String path) async {
+ var deckName = pathutil.basename(path);
+ var deckId = uuidutil.createUuid();
+
+ Directory deckDir = new Directory(path);
+ List<model.Slide> slides = new List();
+ await for (FileSystemEntity fsEntity in deckDir.list()) {
+ if (!(fsEntity is File)) {
+ log.warning(
+ 'Ignoring non-file ${pathutil.basename(fsEntity.path)} in $path');
+ continue;
+ }
+ File slideFile = fsEntity as File;
+ var slideNum;
+ try {
+ String slideName = pathutil.basenameWithoutExtension(slideFile.path);
+ slideNum = int.parse(slideName);
+ slideNum--; // Zero based index.
+ } catch (e) {
+ throw new ArgumentError(
+ "Filename ${pathutil.basename(slideFile.path)} for a slide must be a number.");
+ }
+
+ // Create the slide object.
+ List<int> slideBytes = await slideFile.readAsBytes();
+ var blobRef = new model.BlobRef(
+ keyutil.getDeckBlobKey(deckId, uuidutil.createUuid()));
+ await _store.actions.putBlob(blobRef.key, slideBytes);
+ model.Slide slide = new model.Slide(slideNum, blobRef);
+ slides.add(slide);
+ }
+
+ if (slides.isEmpty) {
+ log.warning('No image files found in $path.');
+ return;
+ }
+
+ slides.sort((model.Slide s1, model.Slide s2) => s1.num.compareTo(s2.num));
+
+ // Use the first slide as thumbnail.
+ model.BlobRef thumbnailBlobRef = slides.first.image;
+ var deck = new model.Deck(deckId, deckName, thumbnailBlobRef);
+
+ await _store.actions.addDeck(deck);
+ await _store.actions.setSlides(deck.key, slides);
+ }
}
diff --git a/dart/lib/loaders/sdcard_loader.dart b/dart/lib/loaders/sdcard_loader.dart
deleted file mode 100644
index 60aa269..0000000
--- a/dart/lib/loaders/sdcard_loader.dart
+++ /dev/null
@@ -1,13 +0,0 @@
-// 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 'dart:async';
-
-import 'loader.dart';
-
-class SdCardLoader implements Loader {
- Future loadDeck() {
- throw new UnimplementedError();
- }
-}
diff --git a/dart/lib/main.dart b/dart/lib/main.dart
index efcdbe4..29c09f5 100644
--- a/dart/lib/main.dart
+++ b/dart/lib/main.dart
@@ -1,6 +1,7 @@
// 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 'dart:async';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@@ -9,26 +10,75 @@
import 'stores/store.dart';
import 'styles/common.dart' as style;
import 'utils/back_button.dart' as backButtonUtil;
+import 'utils/image_provider.dart' as imageProvider;
NavigatorState _navigator;
+final Completer storeStatus = new Completer();
void main() {
- var store = new Store.singleton();
+ Store store = new Store.singleton();
+ store.init().then((_) {
+ storeStatus.complete();
+ });
_initLogging();
_initBackButtonHandler();
- // TODO(aghassemi): Display splash screen while store is initializing.
- store.init().then((_) => runApp(new MaterialApp(
+ runApp(new MaterialApp(
theme: style.theme,
title: 'SyncSlides',
- routes: {'/': (RouteArguments args) => new LandingPage()})));
+ routes: {'/': (RouteArguments args) => new LandingPage()}));
}
-class LandingPage extends StatelessComponent {
+class LandingPage extends StatefulComponent {
+ _LandingPage createState() => new _LandingPage();
+}
+
+class _LandingPage extends State<LandingPage> {
+ bool _initialized = false;
+
+ @override
+ void initState() {
+ if (storeStatus.isCompleted) {
+ _initialized = true;
+ } else {
+ storeStatus.future.then((_) {
+ setState(() {
+ _initialized = true;
+ });
+ });
+ }
+ }
+
Widget build(BuildContext context) {
+ if (!_initialized) {
+ return _buildSplashScreen();
+ }
_navigator = context.ancestorStateOfType(NavigatorState);
return new DeckGridPage();
}
+
+ Widget _buildSplashScreen() {
+ var stack = new Stack([
+ new AsyncImage(
+ provider: imageProvider.splashBackgroundImageProvider,
+ fit: ImageFit.cover),
+ new Row([
+ new AsyncImage(
+ provider: imageProvider.splashFlutterImageProvider,
+ width: style.Size.splashLogo),
+ new AsyncImage(
+ provider: imageProvider.splashVanadiumImageProvider,
+ width: style.Size.splashLogo)
+ ], justifyContent: FlexJustifyContent.center),
+ new Container(
+ child: new Row(
+ [new Text('Loading SyncSlides...', style: style.Text.splash)],
+ alignItems: FlexAlignItems.end,
+ justifyContent: FlexJustifyContent.center),
+ padding: style.Spacing.normalPadding)
+ ]);
+ return stack;
+ }
}
void _initLogging() {
diff --git a/dart/lib/stores/actions.dart b/dart/lib/stores/actions.dart
index faca6ab..ab49bbe 100644
--- a/dart/lib/stores/actions.dart
+++ b/dart/lib/stores/actions.dart
@@ -15,9 +15,6 @@
// Removes a deck given its key.
Future removeDeck(String key);
- // Loads a demo deck.
- Future loadDemoDeck();
-
// Loads a deck from SdCard.
Future loadDeckFromSdCard();
@@ -39,9 +36,6 @@
// Stops the given presentation.
Future stopPresentation(String presentationId);
- // Stops all presentations.
- Future stopAllPresentations();
-
// If viewer has started navigating on their own, this will align the navigation
// back up with the presentation.
Future followPresentation(String deckId);
diff --git a/dart/lib/stores/syncbase/actions.dart b/dart/lib/stores/syncbase/actions.dart
index 2a3f2fe..040700f 100644
--- a/dart/lib/stores/syncbase/actions.dart
+++ b/dart/lib/stores/syncbase/actions.dart
@@ -55,7 +55,7 @@
await tb.put(
keyutil.getPresentationCurrSlideNumKey(
deckId, deckState.presentation.key),
- [slideNum]);
+ UTF8.encode(slideNum.toString()));
} else {
// User is not driving the presentation so they are navigating on their own.
deckState.presentation._isFollowingPresentation = false;
@@ -64,12 +64,8 @@
_emitChange();
}
- Future loadDemoDeck() {
- return new Loader.demo().loadDeck();
- }
-
Future loadDeckFromSdCard() {
- return new Loader.demo().loadDeck();
+ return new Loader.singleton().loadDeck();
}
//////////////////////////////////////
@@ -127,7 +123,7 @@
sb.SyncbaseTable tb = _getPresentationsTable();
await tb.put(
keyutil.getPresentationCurrSlideNumKey(deckId, presentation.key),
- [0]);
+ UTF8.encode('0'));
// Set the current user as the driver.
await _setPresentationDriver(deckId, presentation.key, _state.user);
@@ -140,7 +136,7 @@
// Wait for join. If it fails, remove the presentation state from the deck.
await setDefaultsAndJoin();
} catch (e) {
- deckState._presentation = null;
+ deckState._isPresenting = false;
throw e;
}
@@ -154,6 +150,8 @@
_DeckState deckState = _state._getOrCreateDeckState(deckId);
deckState._getOrCreatePresentationState(presentation.key);
+ deckState._isPresenting = true;
+
// Wait until at least the current slide number, driver and the slide for current slide number is synced.
join() async {
bool isMyOwnPresentation =
@@ -166,9 +164,9 @@
new Timer.periodic(new Duration(milliseconds: 30), (Timer timer) {
if (_state._decks.containsKey(deckId) &&
_state._decks[deckId].deck != null &&
- _state._decks[deckId].slides.length >
- _state._decks[deckId].currSlideNum &&
_state._decks[deckId].presentation != null &&
+ _state._decks[deckId].slides.length >
+ _state._decks[deckId].presentation.currSlideNum &&
_state._decks[deckId].presentation.driver != null &&
!completer.isCompleted) {
timer.cancel();
@@ -182,7 +180,7 @@
// Wait for join. If it fails, remove the presentation state from the deck.
await join();
} catch (e) {
- deckState._presentation = null;
+ deckState._isPresenting = false;
throw e;
}
@@ -195,20 +193,13 @@
_state._decks.values.forEach((_DeckState deck) {
if (deck.presentation != null &&
deck.presentation.key == presentationId) {
- deck._presentation = null;
+ deck._isPresenting = false;
}
});
+ _emitChange();
log.info('Presentation $presentationId stopped');
}
- Future stopAllPresentations() async {
- // Stop all presentations in parallel.
- return Future.wait(_state._advertisedPresentations
- .map((model.PresentationAdvertisement p) {
- return stopPresentation(p.key);
- }));
- }
-
Future followPresentation(String deckId) async {
var deckState = _state._getOrCreateDeckState(deckId);
diff --git a/dart/lib/stores/syncbase/state.dart b/dart/lib/stores/syncbase/state.dart
index f859fd0..484a014 100644
--- a/dart/lib/stores/syncbase/state.dart
+++ b/dart/lib/stores/syncbase/state.dart
@@ -43,18 +43,25 @@
List<model.Slide> _slides = new List();
UnmodifiableListView<model.Slide> slides;
- PresentationState _presentation = null;
- PresentationState get presentation => _presentation;
+ _PresentationState _presentation = null;
+ PresentationState get presentation {
+ if (_isPresenting) {
+ return _presentation;
+ }
+ return null;
+ }
int _currSlideNum = 0;
int get currSlideNum => _currSlideNum;
+ bool _isPresenting = false;
+
_DeckState() {
slides = new UnmodifiableListView(_slides);
}
_PresentationState _getOrCreatePresentationState(String presentationId) {
- if (_presentation == null) {
+ if (_presentation == null || _presentation.key != presentationId) {
_presentation = new _PresentationState(presentationId);
}
return _presentation;
diff --git a/dart/lib/stores/syncbase/store.dart b/dart/lib/stores/syncbase/store.dart
index 5f27c96..6d08108 100644
--- a/dart/lib/stores/syncbase/store.dart
+++ b/dart/lib/stores/syncbase/store.dart
@@ -92,7 +92,7 @@
_state._decks.values.forEach((_DeckState deck) {
if (deck.presentation != null &&
deck.presentation.key == presentationId) {
- deck._presentation = null;
+ deck._isPresenting = false;
}
});
_triggerStateChange();
@@ -183,14 +183,15 @@
_onPresentationSlideNumChange(
int changeType, String rowKey, List<int> value) {
String deckId = keyutil.presentationCurrSlideNumKeyToDeckId(rowKey);
+ String presentationId =
+ keyutil.presentationCurrSlideNumKeyToPresentationId(rowKey);
_DeckState deckState = _state._getOrCreateDeckState(deckId);
- _PresentationState presentationState = deckState.presentation;
- if (presentationState == null) {
- return;
- }
+ _PresentationState presentationState =
+ deckState._getOrCreatePresentationState(presentationId);
+
if (changeType == sb.WatchChangeTypes.put) {
- int currSlideNum = value[0];
+ int currSlideNum = int.parse(UTF8.decode(value));
presentationState._currSlideNum = currSlideNum;
} else {
presentationState._currSlideNum = 0;
@@ -199,11 +200,12 @@
_onPresentationDriverChange(int changeType, String rowKey, List<int> value) {
String deckId = keyutil.presentationDriverKeyToDeckId(rowKey);
+ String presentationId =
+ keyutil.presentationDriverKeyToPresentationId(rowKey);
+
_DeckState deckState = _state._getOrCreateDeckState(deckId);
- _PresentationState presentationState = deckState.presentation;
- if (presentationState == null) {
- return;
- }
+ _PresentationState presentationState =
+ deckState._getOrCreatePresentationState(presentationId);
if (changeType == sb.WatchChangeTypes.put) {
model.User driver = new model.User.fromJson(UTF8.decode(value));
diff --git a/dart/lib/stores/utils/key.dart b/dart/lib/stores/utils/key.dart
index 3c0f154..b666b6f 100644
--- a/dart/lib/stores/utils/key.dart
+++ b/dart/lib/stores/utils/key.dart
@@ -57,19 +57,21 @@
return key.contains('/slides/');
}
-// Gets the deck id given a slide key.
-String currSlideKeyToDeckId(String key) {
+void checkValidSlideKey(String key) {
if ((!isSlideKey(key))) {
throw new ArgumentError('$key is not a valid slide key.');
}
+}
+
+// Gets the deck id given a slide key.
+String currSlideKeyToDeckId(String key) {
+ checkValidSlideKey(key);
return key.substring(0, key.indexOf('/slides/'));
}
// Gets the slide index given a slide key.
int currSlideKeyToIndex(String key) {
- if ((!isSlideKey(key))) {
- throw new ArgumentError('$key is not a valid slide key.');
- }
+ checkValidSlideKey(key);
var indexStr = key.substring(key.lastIndexOf('/') + 1);
return int.parse(indexStr);
}
@@ -77,7 +79,7 @@
// TODO(aghassemi): Don't use regex, just regular split should be fine.
const String _uuidPattern = '[a-zA-Z0-9-]+';
final RegExp _currPresentationSlideNumPattern =
- new RegExp('($_uuidPattern)(?:/$_uuidPattern)(?:/currentslide)');
+ new RegExp('($_uuidPattern)/($_uuidPattern)(?:/currentslide)');
// Constructs a current slide number key.
String getPresentationCurrSlideNumKey(String deckId, String presentationId) {
@@ -86,21 +88,31 @@
// Gets the deck id given a current slide number key.
String presentationCurrSlideNumKeyToDeckId(String currSlideNumKey) {
- if ((!isPresentationCurrSlideNumKey(currSlideNumKey))) {
- throw new ArgumentError(
- '$currSlideNumKey is not a valid presentation current slide number key.');
- }
+ checkValidPresentationCurrSlideNumKey(currSlideNumKey);
return _currPresentationSlideNumPattern.firstMatch(currSlideNumKey).group(1);
}
+// Gets the presentation id given a current slide number key.
+String presentationCurrSlideNumKeyToPresentationId(String currSlideNumKey) {
+ checkValidPresentationCurrSlideNumKey(currSlideNumKey);
+ return _currPresentationSlideNumPattern.firstMatch(currSlideNumKey).group(2);
+}
+
// Returns true if a key is a current slide number key.
bool isPresentationCurrSlideNumKey(String key) {
return _currPresentationSlideNumPattern.hasMatch(key);
}
+void checkValidPresentationCurrSlideNumKey(String key) {
+ if ((!isPresentationCurrSlideNumKey(key))) {
+ throw new ArgumentError(
+ '$key is not a valid presentation current slide number key.');
+ }
+}
+
// TODO(aghassemi): Don't use regex, just regular split should be fine.
final RegExp _presentationDriverPattern =
- new RegExp('($_uuidPattern)(?:/$_uuidPattern)(?:/driver)');
+ new RegExp('($_uuidPattern)/($_uuidPattern)(?:/driver)');
// Constructs a presentation driver key.
String getPresentationDriverKey(String deckId, String presentationId) {
return '$deckId/$presentationId/driver';
@@ -108,18 +120,27 @@
// Gets the deck id given a presentation driver key.
String presentationDriverKeyToDeckId(String driverKey) {
- if ((!isPresentationDriverKey(driverKey))) {
- throw new ArgumentError(
- '$driverKey is not a valid presentation driver key.');
- }
+ checkValidPresentationDriverKey(driverKey);
return _presentationDriverPattern.firstMatch(driverKey).group(1);
}
+// Gets the presentation id given a presentation driver key.
+String presentationDriverKeyToPresentationId(String driverKey) {
+ checkValidPresentationDriverKey(driverKey);
+ return _presentationDriverPattern.firstMatch(driverKey).group(2);
+}
+
// Returns true if a key is a presentation driver key.
bool isPresentationDriverKey(String key) {
return _presentationDriverPattern.hasMatch(key);
}
+void checkValidPresentationDriverKey(String key) {
+ if ((!isPresentationDriverKey(key))) {
+ throw new ArgumentError('$key is not a valid presentation driver key.');
+ }
+}
+
// TODO(aghassemi): Don't use regex, just regular split should be fine.
final RegExp _presentationQuestionPattern = new RegExp(
'($_uuidPattern)(?:/$_uuidPattern)(?:/questions/)($_uuidPattern)');
@@ -129,16 +150,12 @@
}
String presentationQuestionKeyToDeckId(String key) {
- if ((!isPresentationQuestionKey(key))) {
- throw new ArgumentError('$key is not a valid presentation question key.');
- }
+ checkValidPresentationQuestionKey(key);
return _presentationQuestionPattern.firstMatch(key).group(1);
}
String presentationQuestionKeyToQuestionId(String key) {
- if ((!isPresentationQuestionKey(key))) {
- throw new ArgumentError('$key is not a valid presentation question key.');
- }
+ checkValidPresentationQuestionKey(key);
return _presentationQuestionPattern.firstMatch(key).group(2);
}
@@ -147,6 +164,12 @@
return _presentationQuestionPattern.hasMatch(key);
}
+void checkValidPresentationQuestionKey(String key) {
+ if ((!isPresentationQuestionKey(key))) {
+ throw new ArgumentError('$key is not a valid presentation question key.');
+ }
+}
+
// Constructs a blob key specific to a deck.
String getDeckBlobKey(String deckId, String blobId) {
return '$deckId/$blobId';
diff --git a/dart/lib/styles/common.dart b/dart/lib/styles/common.dart
index 71668a0..fbd8017 100644
--- a/dart/lib/styles/common.dart
+++ b/dart/lib/styles/common.dart
@@ -8,6 +8,8 @@
static final Color secondaryTextColor = Colors.grey[500];
static final Color errorTextColor = Colors.red[500];
static final TextStyle titleStyle = new TextStyle(fontSize: 18.0);
+ static final TextStyle splash =
+ new TextStyle(fontSize: 16.0, color: Colors.white);
static final TextStyle subtitleStyle =
new TextStyle(fontSize: 12.0, color: secondaryTextColor);
static final TextStyle liveNow =
@@ -17,11 +19,12 @@
class Size {
static const double gridbox = 250.0;
- static const double boxFooterHeight = 55.0;
+ static const double boxFooterHeight = 65.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;
+ static const double splashLogo = 75.0;
}
class Spacing {
@@ -29,6 +32,7 @@
static final EdgeDims smallPadding = new EdgeDims.all(5.0);
static final EdgeDims normalPadding = new EdgeDims.all(10.0);
static final EdgeDims normalMargin = new EdgeDims.all(2.0);
+ static final EdgeDims cardMargin = new EdgeDims.all(4.0);
static final EdgeDims listItemMargin = new EdgeDims.TRBL(3.0, 6.0, 0.0, 6.0);
static final EdgeDims actionsMargin =
new EdgeDims.symmetric(horizontal: 10.0);
diff --git a/dart/lib/utils/asset.dart b/dart/lib/utils/asset.dart
index fbeaa73..6d94d9e 100644
--- a/dart/lib/utils/asset.dart
+++ b/dart/lib/utils/asset.dart
@@ -17,3 +17,6 @@
}
String defaultThumbnailAssetKey = 'assets/images/defaults/thumbnail.png';
+String splashBackgroundAssetKey = 'assets/images/splash/background.png';
+String splashVanadiumAssetKey = 'assets/images/splash/vanadium.png';
+String splashFlutterAssetKey = 'assets/images/splash/flutter.png';
diff --git a/dart/lib/utils/image_provider.dart b/dart/lib/utils/image_provider.dart
index 78490a5..50777cf 100644
--- a/dart/lib/utils/image_provider.dart
+++ b/dart/lib/utils/image_provider.dart
@@ -18,6 +18,16 @@
'default_image',
() => assetutil.getRawBytes(assetutil.defaultThumbnailAssetKey));
+final ImageProvider splashBackgroundImageProvider = new _RawImageProvider(
+ 'splash_background',
+ () => assetutil.getRawBytes(assetutil.splashBackgroundAssetKey));
+final ImageProvider splashFlutterImageProvider = new _RawImageProvider(
+ 'splash_flutter',
+ () => assetutil.getRawBytes(assetutil.splashFlutterAssetKey));
+final ImageProvider splashVanadiumImageProvider = new _RawImageProvider(
+ 'splash_vanadium',
+ () => assetutil.getRawBytes(assetutil.splashVanadiumAssetKey));
+
ImageProvider getDeckThumbnailImage(model.Deck deck) {
if (deck == null) {
throw new ArgumentError.notNull('deck');
diff --git a/dart/pubspec.lock b/dart/pubspec.lock
index d803339..8cef40f 100644
--- a/dart/pubspec.lock
+++ b/dart/pubspec.lock
@@ -4,7 +4,7 @@
analyzer:
description: analyzer
source: hosted
- version: "0.26.3"
+ version: "0.27.1+2"
archive:
description: archive
source: hosted
@@ -20,7 +20,7 @@
async:
description: async
source: hosted
- version: "1.4.0"
+ version: "1.5.0"
barback:
description: barback
source: hosted
@@ -47,6 +47,10 @@
description: collection
source: hosted
version: "1.2.0"
+ contrast:
+ description: contrast
+ source: hosted
+ version: "0.1.1"
convert:
description: convert
source: hosted
@@ -59,6 +63,10 @@
description: csslib
source: hosted
version: "0.12.2"
+ den_api:
+ description: den_api
+ source: hosted
+ version: "0.1.0"
fixnum:
description: fixnum
source: hosted
@@ -68,7 +76,7 @@
path: "../../../../../flutter/packages/flutter"
relative: true
source: path
- version: "0.0.20"
+ version: "0.0.21"
flutter_tools:
description:
path: "../../../../../flutter/packages/flutter_tools"
@@ -81,6 +89,10 @@
relative: true
source: path
version: "0.0.10"
+ github:
+ description: github
+ source: hosted
+ version: "2.3.1"
glob:
description: glob
source: hosted
@@ -88,7 +100,11 @@
html:
description: html
source: hosted
- version: "0.12.2"
+ version: "0.12.2+1"
+ http:
+ description: http
+ source: hosted
+ version: "0.11.3+3"
http_multi_server:
description: http_multi_server
source: hosted
@@ -100,7 +116,7 @@
intl:
description: intl
source: hosted
- version: "0.12.4+2"
+ version: "0.12.5"
logging:
description: logging
source: hosted
@@ -120,23 +136,23 @@
mojo:
description: mojo
source: hosted
- version: "0.4.6"
+ version: "0.4.8"
mojo_apptest:
description: mojo_apptest
source: hosted
- version: "0.2.9"
+ version: "0.2.12"
mojo_sdk:
description: mojo_sdk
source: hosted
- version: "0.2.5"
+ version: "0.2.7"
mojo_services:
description: mojo_services
source: hosted
- version: "0.4.8"
+ version: "0.4.10"
mojom:
description: mojom
source: hosted
- version: "0.2.10"
+ version: "0.2.12"
mustache4dart:
description: mustache4dart
source: hosted
@@ -167,30 +183,38 @@
description: pool
source: hosted
version: "1.2.1"
+ pub_package_data:
+ description: pub_package_data
+ source: hosted
+ version: "0.0.1"
pub_semver:
description: pub_semver
source: hosted
version: "1.2.3"
+ quiver:
+ description: quiver
+ source: hosted
+ version: "0.21.4"
shelf:
description: shelf
source: hosted
- version: "0.6.4+2"
+ version: "0.6.4+3"
shelf_static:
description: shelf_static
source: hosted
- version: "0.2.3+1"
+ version: "0.2.3+2"
shelf_web_socket:
description: shelf_web_socket
source: hosted
- version: "0.0.1+4"
+ version: "0.0.1+5"
sky_engine:
description: sky_engine
source: hosted
- version: "0.0.67"
+ version: "0.0.75"
sky_services:
description: sky_services
source: hosted
- version: "0.0.67"
+ version: "0.0.75"
source_map_stack_trace:
description: source_map_stack_trace
source: hosted
@@ -214,11 +238,11 @@
syncbase:
description: syncbase
source: hosted
- version: "0.0.20"
+ version: "0.0.23"
test:
description: test
source: hosted
- version: "0.12.6"
+ version: "0.12.6+1"
typed_data:
description: typed_data
source: hosted
@@ -234,15 +258,27 @@
v23discovery:
description: v23discovery
source: hosted
- version: "0.0.6"
+ version: "0.0.7"
vector_math:
description: vector_math
source: hosted
- version: "1.4.3"
+ version: "1.4.4"
watcher:
description: watcher
source: hosted
version: "0.9.7"
+ when:
+ description: when
+ source: hosted
+ version: "0.2.0"
+ which:
+ description: which
+ source: hosted
+ version: "0.1.3"
+ xml:
+ description: xml
+ source: hosted
+ version: "2.4.0"
yaml:
description: yaml
source: hosted
diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml
index a3c99b1..67ad8a3 100644
--- a/dart/pubspec.yaml
+++ b/dart/pubspec.yaml
@@ -5,7 +5,7 @@
path: "../../../../../flutter/packages/flutter"
logging: ">=0.11.2 <0.12.0"
mojo_services: ">=0.4.5 <0.5.0"
- syncbase: ">=0.0.20 <0.1.0"
+ syncbase: ">=0.0.23 <0.1.0"
v23discovery: ">=0.0.4 < 0.1.0"
uuid: ">=0.5.0 <0.6.0"
dev_dependencies:
diff --git a/dart/shortcut_template b/dart/shortcut_template
index 5330e32..c553beb 100644
--- a/dart/shortcut_template
+++ b/dart/shortcut_template
@@ -1,4 +1,4 @@
---map-origin=http://flutter/=https://storage.googleapis.com/mojo/flutter/e80be08b5794930731171c151a73140b8f75b0f7/android-arm/
+--map-origin=http://flutter/=https://storage.googleapis.com/mojo/flutter/97bf8464d2c342a919a80949b7b43c403db6cf6c/android-arm/
--url-mappings=mojo:flutter=http://flutter/flutter.mojo
--enable-multiprocess
--map-origin=https://syncbase.syncslides.mojo.v.io=https://%GS_BUCKET_URL%/