| // 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; |
| } |