OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * The top-level class for the UI state. UI state is essentially a "model" from | |
7 * the view's perspective but whose data just describes the UI itself. It | |
8 * contains data like the currently selected story, etc. | |
9 */ | |
10 // TODO(jimhug): Split the two classes here into framework and app-specific. | |
11 class SwarmState extends UIState { | |
12 /** Core data source for the app. */ | |
13 final Sections _dataModel; | |
14 | |
15 /** | |
16 * Which article the user is currently viewing, or null if they aren't | |
17 * viewing an Article. | |
18 */ | |
19 final ObservableValue<Article> currentArticle; | |
20 /** | |
21 * Which article the user currently has selected (for traversing articles | |
22 * via keyboard shortcuts). | |
23 */ | |
24 final ObservableValue<Article> selectedArticle; | |
25 | |
26 /** | |
27 * True if the story view is maximized and the top and bottom UI elements | |
28 * are hidden. | |
29 */ | |
30 final ObservableValue<bool> storyMaximized; | |
31 | |
32 /** | |
33 * True if the maximized story, if any, is being displayed in text mode | |
34 * rather than as an embedded web-page. | |
35 */ | |
36 final ObservableValue<bool> storyTextMode; | |
37 | |
38 /** | |
39 * Which article the user currently has selected (by keyboard shortcuts), | |
40 * or null if an article isn't selected by the keyboard. | |
41 */ | |
42 BiIterator<Article> _articleIterator; | |
43 | |
44 /** | |
45 * Which feed is currently selected (for keyboard shortcuts). | |
46 */ | |
47 BiIterator<Feed> _feedIterator; | |
48 | |
49 /** | |
50 * Which section is currently selected (for keyboard shortcuts). | |
51 */ | |
52 BiIterator<Section> _sectionIterator; | |
53 | |
54 SwarmState(this._dataModel) | |
55 : super(), | |
56 currentArticle = new ObservableValue<Article>(null), | |
57 selectedArticle = new ObservableValue<Article>(null), | |
58 storyMaximized = new ObservableValue<bool>(false), | |
59 storyTextMode = new ObservableValue<bool>(true) { | |
60 startHistoryTracking(); | |
61 // TODO(efortuna): consider having this class just hold observable | |
62 // currentIndecies instead of iterators with observablevalues.. | |
63 _sectionIterator = new BiIterator<Section>(_dataModel.sections); | |
64 _feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds); | |
65 _articleIterator = | |
66 new BiIterator<Article>(_feedIterator.current.articles); | |
67 | |
68 currentArticle.addChangeListener((e) { | |
69 _articleIterator.jumpToValue(currentArticle.value); | |
70 }); | |
71 } | |
72 | |
73 /** | |
74 * Registers an event to fire on any state change | |
75 * | |
76 * TODO(jmesserly): fix this so we don't have to enumerate all of our fields | |
77 * again. One idea here is UIState becomes Observable, Observables have | |
78 * parents and notifications bubble up the parent chain. | |
79 */ | |
80 void addChangeListener(ChangeListener listener) { | |
81 _sectionIterator.currentIndex.addChangeListener(listener); | |
82 _feedIterator.currentIndex.addChangeListener(listener); | |
83 _articleIterator.currentIndex.addChangeListener(listener); | |
84 currentArticle.addChangeListener(listener); | |
85 } | |
86 | |
87 Map<String, String> toHistory() { | |
88 final data = {}; | |
89 data['section'] = currentSection.id; | |
90 data['feed'] = currentFeed.id; | |
91 if (currentArticle.value != null) { | |
92 data['article'] = currentArticle.value.id; | |
93 } | |
94 return data; | |
95 } | |
96 | |
97 void loadFromHistory(Map values) { | |
98 // TODO(jimhug): There's a better way of doing this... | |
99 if (values['section'] != null) { | |
100 _sectionIterator.jumpToValue(_dataModel. | |
101 findSectionById(values['section'])); | |
102 } else { | |
103 _sectionIterator = new BiIterator<Section>(_dataModel.sections); | |
104 } | |
105 if (values['feed'] != null && currentSection != null) { | |
106 _feedIterator.jumpToValue(currentSection.findFeed(values['feed'])); | |
107 } else { | |
108 _feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds); | |
109 } | |
110 if (values['article'] != null && currentFeed != null) { | |
111 currentArticle.value = currentFeed.findArticle(values['article']); | |
112 _articleIterator.jumpToValue(currentArticle.value); | |
113 } else { | |
114 _articleIterator = | |
115 new BiIterator<Article>(_feedIterator.current.articles); | |
116 currentArticle.value = null; | |
117 } | |
118 | |
119 storyMaximized.value = false; | |
120 } | |
121 | |
122 /** | |
123 * Move the currentArticle pointer to the next item in the Feed. | |
124 */ | |
125 void goToNextArticle() { | |
126 currentArticle.value = _articleIterator.next(); | |
127 selectedArticle.value = _articleIterator.current; | |
128 } | |
129 | |
130 /** | |
131 * Move the currentArticle pointer to the previous item in the Feed. | |
132 */ | |
133 void goToPreviousArticle() { | |
134 currentArticle.value = _articleIterator.previous(); | |
135 selectedArticle.value = _articleIterator.current; | |
136 } | |
137 | |
138 /** | |
139 * Move the selectedArticle pointer to the next item in the Feed. | |
140 */ | |
141 void goToNextSelectedArticle() { | |
142 selectedArticle.value = _articleIterator.next(); | |
143 } | |
144 | |
145 /** | |
146 * Move the selectedArticle pointer to the previous item in the Feed. | |
147 */ | |
148 void goToPreviousSelectedArticle() { | |
149 selectedArticle.value = _articleIterator.previous(); | |
150 } | |
151 | |
152 /** | |
153 * Move the pointers for selectedArticle to point to the next | |
154 * Feed. | |
155 */ | |
156 void goToNextFeed() { | |
157 var newFeed = _feedIterator.next(); | |
158 int oldIndex = _articleIterator.currentIndex.value; | |
159 | |
160 _articleIterator = new BiIterator<Article>(newFeed.articles, | |
161 _articleIterator.currentIndex.listeners); | |
162 | |
163 _articleIterator.currentIndex.value = oldIndex; | |
164 selectedArticle.value = _articleIterator.current; | |
165 } | |
166 | |
167 /** | |
168 * Move the pointers for selectedArticle to point to the previous | |
169 * DataSource. | |
170 */ | |
171 void goToPreviousFeed() { | |
172 var newFeed = _feedIterator.previous(); | |
173 int oldIndex = _articleIterator.currentIndex.value; | |
174 | |
175 _articleIterator = new BiIterator<Article>(newFeed.articles, | |
176 _articleIterator.currentIndex.listeners); | |
177 _articleIterator.currentIndex.value = oldIndex; | |
178 selectedArticle.value = _articleIterator.current; | |
179 } | |
180 | |
181 /** | |
182 * Move to the next section (page) of feeds in the UI. | |
183 * @param index the previous index (how far down in a given feed) | |
184 * from the Source we are moving from. | |
185 * This method takes sliderMenu in the event that it needs to move | |
186 * to a previous section, it can notify the UI to update. | |
187 */ | |
188 void goToNextSection(SliderMenu sliderMenu) { | |
189 //TODO(efortuna): move sections? | |
190 var oldSection = currentSection; | |
191 int oldIndex = _articleIterator.currentIndex.value; | |
192 sliderMenu.selectNext(true); | |
193 // This check prevents our selector from wrapping around when we try to | |
194 // go to the "next section", but we're already at the last section. | |
195 if (oldSection != _sectionIterator.current) { | |
196 _feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds, | |
197 _feedIterator.currentIndex.listeners); | |
198 _articleIterator = | |
199 new BiIterator<Article>(_feedIterator.current.articles, | |
200 _articleIterator.currentIndex.listeners); | |
201 _articleIterator.currentIndex.value = oldIndex; | |
202 selectedArticle.value = _articleIterator.current; | |
203 } | |
204 } | |
205 | |
206 /** | |
207 * Move to the previous section (page) of feeds in the UI. | |
208 * @param index the previous index (how far down in a given feed) | |
209 * from the Source we are moving from. | |
210 * @param oldSection the original starting section (before the slider | |
211 * menu moved) | |
212 * This method takes sliderMenu in the event that it needs to move | |
213 * to a previous section, it can notify the UI to update. | |
214 */ | |
215 void goToPreviousSection(SliderMenu sliderMenu) { | |
216 //TODO(efortuna): don't pass sliderMenu here. Just update in view! | |
217 var oldSection = currentSection; | |
218 int oldIndex = _articleIterator.currentIndex.value; | |
219 sliderMenu.selectPrevious(true); | |
220 | |
221 // This check prevents our selector from wrapping around when we try to | |
222 // go to the "previous section", but we're already at the first section. | |
223 if (oldSection != _sectionIterator.current) { | |
224 _feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds, | |
225 _feedIterator.currentIndex.listeners); | |
226 // Jump to back of feed set if we are moving backwards through sections. | |
227 _feedIterator.currentIndex.value = _feedIterator.list.length - 1; | |
228 _articleIterator = | |
229 new BiIterator<Article>(_feedIterator.current.articles, | |
230 _articleIterator.currentIndex.listeners); | |
231 _articleIterator.currentIndex.value = oldIndex; | |
232 selectedArticle.value = _articleIterator.current; | |
233 } | |
234 } | |
235 | |
236 /** | |
237 * Set the selected story as the current story (for viewing in the larger | |
238 * Story View.) | |
239 */ | |
240 void selectStoryAsCurrent() { | |
241 currentArticle.value = _articleIterator.current; | |
242 selectedArticle.value = _articleIterator.current; | |
243 } | |
244 | |
245 /** | |
246 * Remove our currentArticle selection, to move back to the Main Grid view. | |
247 */ | |
248 void clearCurrentArticle() { | |
249 currentArticle.value = null; | |
250 } | |
251 | |
252 /** | |
253 * Set the selectedArticle as the first item in that section (UI page). | |
254 */ | |
255 void goToFirstArticleInSection() { | |
256 selectedArticle.value = _articleIterator.current; | |
257 } | |
258 | |
259 /** | |
260 * Returns true if the UI is currently in the Story View state. | |
261 */ | |
262 bool get inMainView() => currentArticle.value == null; | |
263 | |
264 /** | |
265 * Returns true if we currently have an Article selected (for keyboard | |
266 * shortcuts browsing). | |
267 */ | |
268 bool get hasArticleSelected() => selectedArticle.value != null; | |
269 | |
270 /** | |
271 * Mark the current article as read | |
272 */ | |
273 bool markCurrentAsRead() { | |
274 currentArticle.value.unread.value = false; | |
275 } | |
276 | |
277 /** | |
278 * The user has moved to a new section (page). This can occur either | |
279 * if the user clicked on a section page, or used keyboard shortcuts. | |
280 * The default behavior is to move to the first article in the first | |
281 * column. The location of the selected item depends on the previous | |
282 * selected item location if the user used keyboard shortcuts. These | |
283 * are manipulated in goToPrevious/NextSection(). | |
284 */ | |
285 void moveToNewSection(String sectionTitle) { | |
286 _sectionIterator.currentIndex.value = | |
287 _dataModel.findSectionIndex(sectionTitle); | |
288 _feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds, | |
289 _feedIterator.currentIndex.listeners); | |
290 _articleIterator = | |
291 new BiIterator<Article>(_feedIterator.current.articles, | |
292 _articleIterator.currentIndex.listeners); | |
293 } | |
294 | |
295 Section get currentSection() => _sectionIterator.current; | |
296 Feed get currentFeed() => _feedIterator.current; | |
297 } | |
OLD | NEW |