OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dartino 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.md file. | |
4 | |
5 #import "TodoListTableViewController.h" | |
6 #import "AddTodoItemViewController.h" | |
7 #import "TodoItem.h" | |
8 | |
9 #include "todomvc_service.h" | |
10 #include "todomvc_presenter.h" | |
11 | |
12 // Host-side implementation of the todo-list presenter. | |
13 // This class is responsible for mapping Dart-side changes to the presenter | |
14 // model of the host implementation, in this case, a table-view presenting a | |
15 // list of TodoItem objects. | |
16 class TodoMVCPresenterImpl : public TodoMVCPresenter { | |
17 public: | |
18 TodoMVCPresenterImpl(TodoListTableViewController* controller) | |
19 : controller_(controller) { | |
20 items_ = [[NSMutableArray alloc] init]; | |
21 } | |
22 | |
23 NSArray* items() { return items_; } | |
24 | |
25 void createItem(NSString* title) { | |
26 int length = title.length; | |
27 int size = 56 + BoxedStringBuilder::kSize + length; | |
28 MessageBuilder builder(size); | |
29 BoxedStringBuilder box = builder.initRoot<BoxedStringBuilder>(); | |
30 List<unichar> chars = box.initStrData(length); | |
31 encodeString(title, chars); | |
32 TodoMVCService::createItemAsync(box, VoidCallback, NULL); | |
33 } | |
34 | |
35 void toggleItem(int id) { | |
36 TodoItem* item = [items_ objectAtIndex:id]; | |
37 if (item.completed) { | |
38 [item dispatchUncompleteEvent]; | |
39 } else { | |
40 [item dispatchCompleteEvent]; | |
41 } | |
42 } | |
43 | |
44 protected: | |
45 // Patch apply callbacks. | |
46 virtual void enterPatch() { | |
47 context_ = IN_LIST; | |
48 index_ = 0; | |
49 } | |
50 | |
51 virtual void enterConsFst() { | |
52 context_ = (context_ == IN_LIST) ? IN_ITEM : IN_TITLE; | |
53 } | |
54 | |
55 virtual void enterConsSnd() { | |
56 if (context_ == IN_LIST) { | |
57 ++index_; | |
58 } else { | |
59 context_ = IN_STATUS; | |
60 } | |
61 } | |
62 | |
63 virtual void enterConsDeleteEvent() { | |
64 assert(context_ == IN_ITEM); | |
65 context_ = IN_DELETE_EVENT; | |
66 } | |
67 | |
68 virtual void enterConsCompleteEvent() { | |
69 assert(context_ == IN_ITEM); | |
70 context_ = IN_COMPLETE_EVENT; | |
71 } | |
72 | |
73 virtual void enterConsUncompleteEvent() { | |
74 assert(context_ == IN_ITEM); | |
75 context_ = IN_UNCOMPLETE_EVENT; | |
76 } | |
77 | |
78 virtual void updateNode(const Node& node) { | |
79 TodoItem *item; | |
80 switch (context_) { | |
81 case IN_TITLE: | |
82 item = [items_ objectAtIndex:index_]; | |
83 item.itemName = decodeString(node.getStrData()); | |
84 break; | |
85 case IN_STATUS: | |
86 item = [items_ objectAtIndex:index_]; | |
87 item.completed = node.getTruth(); | |
88 break; | |
89 case IN_DELETE_EVENT: | |
90 item = [items_ objectAtIndex:index_]; | |
91 item.deleteEvent = node.getNum(); | |
92 break; | |
93 case IN_COMPLETE_EVENT: | |
94 item = [items_ objectAtIndex:index_]; | |
95 item.completeEvent = node.getNum(); | |
96 break; | |
97 case IN_UNCOMPLETE_EVENT: | |
98 item = [items_ objectAtIndex:index_]; | |
99 item.uncompleteEvent = node.getNum(); | |
100 break; | |
101 case IN_ITEM: | |
102 item = newItem(node); | |
103 [items_ insertObject:item atIndex:index_]; | |
104 break; | |
105 case IN_LIST: | |
106 resizeTo(index_); | |
107 addItems(node); | |
108 break; | |
109 default: | |
110 abort(); | |
111 } | |
112 // TODO: Selectively reload only the affected rows. | |
113 [controller_.tableView reloadData]; | |
114 } | |
115 | |
116 private: | |
117 void resizeTo(unsigned long newLength) { | |
118 unsigned long length = [items_ count]; | |
119 if (newLength < length) { | |
120 [items_ removeObjectsInRange:NSMakeRange(newLength, length - newLength)]; | |
121 } | |
122 } | |
123 | |
124 void encodeString(NSString* string, List<unichar> chars) { | |
125 assert(string.length == chars.length()); | |
126 [string getCharacters:chars.data() | |
127 range:NSMakeRange(0, string.length)]; | |
128 } | |
129 | |
130 NSString* decodeString(List<unichar> chars) { | |
131 return [[NSString alloc] initWithCharacters:chars.data() | |
132 length:chars.length()]; | |
133 } | |
134 | |
135 TodoItem* newItem(const Node& node) { | |
136 TodoItem *item = [[TodoItem alloc] init]; | |
137 Cons cons = node.getCons(); | |
138 item.itemName = decodeString(cons.getFst().getStrData()); | |
139 item.completed = cons.getSnd().getTruth(); | |
140 item.deleteEvent = cons.getDeleteEvent(); | |
141 item.completeEvent = cons.getCompleteEvent(); | |
142 item.uncompleteEvent = cons.getUncompleteEvent(); | |
143 return item; | |
144 } | |
145 | |
146 void addItem(const Node& node) { | |
147 TodoItem *item = newItem(node); | |
148 [items_ addObject:item]; | |
149 } | |
150 | |
151 void addItems(const Node& node) { | |
152 if (node.isNil()) return; | |
153 Cons cons = node.getCons(); | |
154 addItem(cons.getFst()); | |
155 addItems(cons.getSnd()); | |
156 } | |
157 | |
158 enum Context { | |
159 IN_LIST, | |
160 IN_ITEM, | |
161 IN_TITLE, | |
162 IN_STATUS, | |
163 IN_DELETE_EVENT, | |
164 IN_COMPLETE_EVENT, | |
165 IN_UNCOMPLETE_EVENT | |
166 }; | |
167 Context context_; | |
168 int index_; | |
169 NSMutableArray* items_; | |
170 TodoListTableViewController* controller_; | |
171 }; | |
172 | |
173 @interface TodoListTableViewController () | |
174 | |
175 @property TodoMVCPresenterImpl *impl; | |
176 @property int ticks; | |
177 @property NSDate* start; | |
178 | |
179 @end | |
180 | |
181 @implementation TodoListTableViewController | |
182 | |
183 - (void)viewDidLoad { | |
184 [super viewDidLoad]; | |
185 // Instantiate host-side implementation of the presenter. | |
186 self.impl = new TodoMVCPresenterImpl(self); | |
187 | |
188 // Do the initial synchronization before linking to the refresh rate. | |
189 NSDate* date = [NSDate date]; | |
190 self.impl->sync(); | |
191 double time = -[date timeIntervalSinceNow]; | |
192 NSLog(@"Initial sync: %f s", time); | |
193 | |
194 self.ticks = 0; | |
195 self.start = [NSDate date]; | |
196 // Link display refresh to synchronization of the presenter. | |
197 CADisplayLink* link = [CADisplayLink | |
198 displayLinkWithTarget:self | |
199 selector:@selector(refreshDisplay:)]; | |
200 [link setFrameInterval:1]; | |
201 [link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; | |
202 } | |
203 | |
204 - (void)refreshDisplay:(CADisplayLink *)sender { | |
205 if (++self.ticks % 60 == 0) { | |
206 double time = -[self.start timeIntervalSinceNow]; | |
207 if (time > 1.1) { | |
208 NSLog(@"60fps miss: %f s", time); | |
209 } | |
210 self.start = [NSDate date]; | |
211 } | |
212 self.impl->sync(); | |
213 } | |
214 | |
215 - (void)didReceiveMemoryWarning { | |
216 [super didReceiveMemoryWarning]; | |
217 // Dispose of any resources that can be recreated. | |
218 } | |
219 | |
220 - (IBAction)unwindToList:(UIStoryboardSegue *)segue { | |
221 AddTodoItemViewController *source = [segue sourceViewController]; | |
222 TodoItem *item = source.todoItem; | |
223 if (item == nil) return; | |
224 self.impl->createItem(item.itemName); | |
225 } | |
226 | |
227 #pragma mark - Table view data source | |
228 | |
229 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { | |
230 return 1; | |
231 } | |
232 | |
233 - (NSInteger)tableView:(UITableView *)tableView | |
234 numberOfRowsInSection:(NSInteger)section { | |
235 return [self.impl->items() count]; | |
236 } | |
237 | |
238 - (UITableViewCell *)tableView:(UITableView *)tableView | |
239 cellForRowAtIndexPath:(NSIndexPath *)indexPath { | |
240 UITableViewCell *cell = | |
241 [tableView dequeueReusableCellWithIdentifier:@"ListPrototypeCell" | |
242 forIndexPath:indexPath]; | |
243 TodoItem *todoItem = [self.impl->items() objectAtIndex:indexPath.row]; | |
244 cell.textLabel.text = todoItem.itemName; | |
245 cell.accessoryType = todoItem.completed | |
246 ? UITableViewCellAccessoryCheckmark | |
247 : UITableViewCellAccessoryNone; | |
248 return cell; | |
249 } | |
250 | |
251 #pragma mark - Table view delegate | |
252 | |
253 - (void)tableView:(UITableView *)tableView | |
254 didSelectRowAtIndexPath:(NSIndexPath *)indexPath { | |
255 [tableView deselectRowAtIndexPath:indexPath animated:NO]; | |
256 self.impl->toggleItem(indexPath.row); | |
257 } | |
258 | |
259 @end | |
OLD | NEW |