forked from dart-lang/sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SwarmState.dart
296 lines (261 loc) · 10.1 KB
/
SwarmState.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// 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 = new ObservableValue<Article>(null),
selectedArticle = new ObservableValue<Article>(null),
storyMaximized = new ObservableValue<bool>(false),
storyTextMode = new ObservableValue<bool>(true) {
startHistoryTracking();
// TODO(efortuna): consider having this class just hold observable
// currentIndecies instead of iterators with observablevalues..
_sectionIterator = new BiIterator<Section>(_dataModel.sections);
_feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds);
_articleIterator = new 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);
}
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;
}
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 = new BiIterator<Section>(_dataModel.sections);
}
if (values['feed'] != null && currentSection != null) {
_feedIterator.jumpToValue(currentSection.findFeed(values['feed']));
} else {
_feedIterator = new BiIterator<Feed>(_sectionIterator.current.feeds);
}
if (values['article'] != null && currentFeed != null) {
currentArticle.value = currentFeed.findArticle(values['article']);
_articleIterator.jumpToValue(currentArticle.value);
} else {
_articleIterator =
new 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 = new 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 = new 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 = new BiIterator<Feed>(
_sectionIterator.current.feeds, _feedIterator.currentIndex.listeners);
_articleIterator = new 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 = new 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 = new 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 = new BiIterator<Feed>(
_sectionIterator.current.feeds, _feedIterator.currentIndex.listeners);
_articleIterator = new BiIterator<Article>(_feedIterator.current.articles,
_articleIterator.currentIndex.listeners);
}
Section get currentSection => _sectionIterator.current;
Feed get currentFeed => _feedIterator.current;
}