blob: ec19e06779aa1cce2f01694e6166717a1dd74f5b [file] [log] [blame]
// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart = 2.9
part of swarmlib;
/// The top-level class for the UI state. UI state is essentially a "model" from
/// the view's perspective but whose data just describes the UI itself. It
/// contains data like the currently selected story, etc.
// TODO(jimhug): Split the two classes here into framework and app-specific.
class SwarmState extends UIState {
/// Core data source for the app. */
final Sections _dataModel;
/// Which article the user is currently viewing, or null if they aren't
/// viewing an Article.
final ObservableValue<Article> currentArticle;
/// Which article the user currently has selected (for traversing articles
/// via keyboard shortcuts).
final ObservableValue<Article> selectedArticle;
/// True if the story view is maximized and the top and bottom UI elements
/// are hidden.
final ObservableValue<bool> storyMaximized;
/// True if the maximized story, if any, is being displayed in text mode
/// rather than as an embedded web-page.
final ObservableValue<bool> storyTextMode;
/// Which article the user currently has selected (by keyboard shortcuts),
/// or null if an article isn't selected by the keyboard.
BiIterator<Article> _articleIterator;
/// Which feed is currently selected (for keyboard shortcuts).
BiIterator<Feed> _feedIterator;
/// Which section is currently selected (for keyboard shortcuts).
BiIterator<Section> _sectionIterator;
SwarmState(this._dataModel)
: currentArticle = ObservableValue<Article>(null),
selectedArticle = ObservableValue<Article>(null),
storyMaximized = ObservableValue<bool>(false),
storyTextMode = ObservableValue<bool>(true) {
startHistoryTracking();
// TODO(efortuna): consider having this class just hold observable
// currentIndecies instead of iterators with observablevalues..
_sectionIterator = BiIterator<Section>(_dataModel.sections);
_feedIterator = BiIterator<Feed>(_sectionIterator.current.feeds);
_articleIterator = BiIterator<Article>(_feedIterator.current.articles);
currentArticle.addChangeListener((e) {
_articleIterator.jumpToValue(currentArticle.value);
});
}
/// Registers an event to fire on any state change
///
/// TODO(jmesserly): fix this so we don't have to enumerate all of our fields
/// again. One idea here is UIState becomes Observable, Observables have
/// parents and notifications bubble up the parent chain.
void addChangeListener(ChangeListener listener) {
_sectionIterator.currentIndex.addChangeListener(listener);
_feedIterator.currentIndex.addChangeListener(listener);
_articleIterator.currentIndex.addChangeListener(listener);
currentArticle.addChangeListener(listener);
}
@override
Map<String, String> toHistory() {
final data = {};
data['section'] = currentSection.id;
data['feed'] = currentFeed.id;
if (currentArticle.value != null) {
data['article'] = currentArticle.value.id;
}
return data;
}
@override
void loadFromHistory(Map values) {
// TODO(jimhug): There's a better way of doing this...
if (values['section'] != null) {
_sectionIterator
.jumpToValue(_dataModel.findSectionById(values['section']));
} else {
_sectionIterator = BiIterator<Section>(_dataModel.sections);
}
if (values['feed'] != null && currentSection != null) {
_feedIterator.jumpToValue(currentSection.findFeed(values['feed']));
} else {
_feedIterator = BiIterator<Feed>(_sectionIterator.current.feeds);
}
if (values['article'] != null && currentFeed != null) {
currentArticle.value = currentFeed.findArticle(values['article']);
_articleIterator.jumpToValue(currentArticle.value);
} else {
_articleIterator = BiIterator<Article>(_feedIterator.current.articles);
currentArticle.value = null;
}
storyMaximized.value = false;
}
/// Move the currentArticle pointer to the next item in the Feed.
void goToNextArticle() {
currentArticle.value = _articleIterator.next();
selectedArticle.value = _articleIterator.current;
}
/// Move the currentArticle pointer to the previous item in the Feed.
void goToPreviousArticle() {
currentArticle.value = _articleIterator.previous();
selectedArticle.value = _articleIterator.current;
}
/// Move the selectedArticle pointer to the next item in the Feed.
void goToNextSelectedArticle() {
selectedArticle.value = _articleIterator.next();
}
/// Move the selectedArticle pointer to the previous item in the Feed.
void goToPreviousSelectedArticle() {
selectedArticle.value = _articleIterator.previous();
}
/// Move the pointers for selectedArticle to point to the next
/// Feed.
void goToNextFeed() {
var newFeed = _feedIterator.next();
int oldIndex = _articleIterator.currentIndex.value;
_articleIterator = BiIterator<Article>(
newFeed.articles, _articleIterator.currentIndex.listeners);
_articleIterator.currentIndex.value = oldIndex;
selectedArticle.value = _articleIterator.current;
}
/// Move the pointers for selectedArticle to point to the previous
/// DataSource.
void goToPreviousFeed() {
var newFeed = _feedIterator.previous();
int oldIndex = _articleIterator.currentIndex.value;
_articleIterator = BiIterator<Article>(
newFeed.articles, _articleIterator.currentIndex.listeners);
_articleIterator.currentIndex.value = oldIndex;
selectedArticle.value = _articleIterator.current;
}
/// Move to the next section (page) of feeds in the UI.
/// @param index the previous index (how far down in a given feed)
/// from the Source we are moving from.
/// This method takes sliderMenu in the event that it needs to move
/// to a previous section, it can notify the UI to update.
void goToNextSection(SliderMenu sliderMenu) {
//TODO(efortuna): move sections?
var oldSection = currentSection;
int oldIndex = _articleIterator.currentIndex.value;
sliderMenu.selectNext(true);
// This check prevents our selector from wrapping around when we try to
// go to the "next section", but we're already at the last section.
if (oldSection != _sectionIterator.current) {
_feedIterator = BiIterator<Feed>(
_sectionIterator.current.feeds, _feedIterator.currentIndex.listeners);
_articleIterator = BiIterator<Article>(_feedIterator.current.articles,
_articleIterator.currentIndex.listeners);
_articleIterator.currentIndex.value = oldIndex;
selectedArticle.value = _articleIterator.current;
}
}
/// Move to the previous section (page) of feeds in the UI.
/// @param index the previous index (how far down in a given feed)
/// from the Source we are moving from.
/// @param oldSection the original starting section (before the slider
/// menu moved)
/// This method takes sliderMenu in the event that it needs to move
/// to a previous section, it can notify the UI to update.
void goToPreviousSection(SliderMenu sliderMenu) {
//TODO(efortuna): don't pass sliderMenu here. Just update in view!
var oldSection = currentSection;
int oldIndex = _articleIterator.currentIndex.value;
sliderMenu.selectPrevious(true);
// This check prevents our selector from wrapping around when we try to
// go to the "previous section", but we're already at the first section.
if (oldSection != _sectionIterator.current) {
_feedIterator = BiIterator<Feed>(
_sectionIterator.current.feeds, _feedIterator.currentIndex.listeners);
// Jump to back of feed set if we are moving backwards through sections.
_feedIterator.currentIndex.value = _feedIterator.list.length - 1;
_articleIterator = BiIterator<Article>(_feedIterator.current.articles,
_articleIterator.currentIndex.listeners);
_articleIterator.currentIndex.value = oldIndex;
selectedArticle.value = _articleIterator.current;
}
}
/// Set the selected story as the current story (for viewing in the larger
/// Story View.)
void selectStoryAsCurrent() {
currentArticle.value = _articleIterator.current;
selectedArticle.value = _articleIterator.current;
}
/// Remove our currentArticle selection, to move back to the Main Grid view.
void clearCurrentArticle() {
currentArticle.value = null;
}
/// Set the selectedArticle as the first item in that section (UI page).
void goToFirstArticleInSection() {
selectedArticle.value = _articleIterator.current;
}
/// Returns true if the UI is currently in the Story View state.
bool get inMainView => currentArticle.value == null;
/// Returns true if we currently have an Article selected (for keyboard
/// shortcuts browsing).
bool get hasArticleSelected => selectedArticle.value != null;
/// Mark the current article as read
bool markCurrentAsRead() {
currentArticle.value.unread.value = false;
}
/// The user has moved to a new section (page). This can occur either
/// if the user clicked on a section page, or used keyboard shortcuts.
/// The default behavior is to move to the first article in the first
/// column. The location of the selected item depends on the previous
/// selected item location if the user used keyboard shortcuts. These
/// are manipulated in goToPrevious/NextSection().
void moveToNewSection(String sectionTitle) {
_sectionIterator.currentIndex.value =
_dataModel.findSectionIndex(sectionTitle);
_feedIterator = BiIterator<Feed>(
_sectionIterator.current.feeds, _feedIterator.currentIndex.listeners);
_articleIterator = BiIterator<Article>(_feedIterator.current.articles,
_articleIterator.currentIndex.listeners);
}
Section get currentSection => _sectionIterator.current;
Feed get currentFeed => _feedIterator.current;
}