| 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 |