shlist

share and manage lists between multiple people
Log | Files | Refs

MainTableViewController.m (23636B)


      1 #import "AddressBook.h"
      2 #import "MainTableViewController.h"
      3 #import "NewListTableViewController.h"
      4 #import "Network.h"
      5 #import "ListTableViewController.h"
      6 #import "MsgTypes.h"
      7 
      8 #import <AddressBook/AddressBook.h>
      9 #include "libkern/OSAtomic.h"
     10 
     11 @interface MainTableViewController () {
     12 	NSString *phone_number;
     13 	Network *network_connection;
     14 	NSString *phone_num_file;
     15 }
     16 
     17 // main data structure, [0] holds lists you're in, [1] is other lists
     18 @property NSMutableArray *lists;
     19 
     20 @property NSMutableDictionary *phnum_to_name_map;
     21 @property (strong, retain) AddressBook *address_book;
     22 
     23 @end
     24 
     25 @implementation MainTableViewController
     26 
     27 - (void) dealloc
     28 {
     29 	[[NSNotificationCenter defaultCenter] removeObserver:self];
     30 }
     31 
     32 - (void) viewDidLoad
     33 {
     34 	[super viewDidLoad];
     35 
     36 	NSNotificationCenter *default_center = [NSNotificationCenter defaultCenter];
     37 	NSString *notification_name;
     38 
     39 	// Listen for push notifications
     40 	[default_center addObserver:self selector:@selector(push_friend_added_list:)
     41 			       name:@"PushNotification_friend_added_list" object:nil];
     42 
     43 	/*
     44 	[default_center addObserver:self selector:@selector(push_updated_list:)
     45 			       name:@"PushNotification_updated_list" object:nil];
     46 	 */
     47 
     48 	const SEL selectors[] = {
     49 		@selector(lists_get_finished:),
     50 		@selector(lists_get_other_finished:),
     51 		@selector(finished_new_list_request:),
     52 		@selector(finished_join_list_request:),
     53 		@selector(finished_leave_list_request:)
     54 	};
     55 	NSUInteger count = 0;
     56 
     57 	// This object handles responses for these types of messages
     58 	for (id str in @[@"lists_get", @"lists_get_other", @"list_add", @"list_join", @"list_leave"]) {
     59 		notification_name = [NSString stringWithFormat:@"NetworkResponseFor_%@", str];
     60 		[default_center addObserver:self selector:selectors[count] name:notification_name object:nil];
     61 		count++;
     62 	}
     63 
     64 	// display an Edit button in the navigation bar for this view controller
     65 	self.navigationItem.leftBarButtonItem = self.editButtonItem;
     66 
     67 	network_connection = [Network shared_network_connection];
     68 
     69 	_lists = [[NSMutableArray alloc] init];
     70 	[_lists addObject:[[NSMutableArray alloc] init]];
     71 	[_lists addObject:[[NSMutableArray alloc] init]];
     72 
     73 	// store the path to the phone number file
     74 	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
     75 	NSString *documentsDirectory = [paths objectAtIndex:0];
     76 	phone_num_file = [documentsDirectory stringByAppendingPathComponent:@"phone_num"];
     77 
     78 	phone_number = nil;
     79 	if ([self load_phone_number]) {
     80 		// phone number loaded, try loading device id
     81 		if ([network_connection load_device_id:phone_number]) {
     82 
     83 			// Send lists_get request, no arguments required here
     84 			NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
     85 			[network_connection send_message:lists_get contents:dict];
     86 
     87 			// Send lists_get_other request, no arguments here either
     88 			[dict removeAllObjects];
     89 			[network_connection send_message:lists_get_other contents:dict];
     90 		}
     91 		// else, device id request sent
     92 	}
     93 	// else, phone number entry is on screen
     94 
     95 	_phnum_to_name_map = [[NSMutableDictionary alloc] init];
     96 
     97 	// get instance and wait for privacy window to clear
     98 	_address_book = [AddressBook shared_address_book];
     99 	_address_book.main_tvc = self;
    100 }
    101 
    102 // Handle 'friend_added_list' message from notification service
    103 - (void) push_friend_added_list:(NSNotification *) notification
    104 {
    105 	NSDictionary *json_list = notification.userInfo;
    106 
    107 	// Server will only send back partial list information because this will
    108 	// always be put in the other lists section
    109 	SharedList *tmp = [self deserialize_light_list:json_list];
    110 
    111 	NSMutableArray *other_lists = [_lists objectAtIndex:1];
    112 	[other_lists addObject:tmp];
    113 
    114 	NSLog(@"notify: new other list '%@', num '%@'", tmp.name, tmp.num);
    115 
    116 	NSIndexPath *new_path = [NSIndexPath indexPathForRow:[other_lists count] - 1 inSection:1];
    117 	[self.tableView insertRowsAtIndexPaths:@[new_path] withRowAnimation:UITableViewRowAnimationAutomatic];
    118 
    119 	[self update_section_headers];
    120 }
    121 
    122 - (bool) load_phone_number
    123 {
    124 	if ([[NSFileManager defaultManager] fileExistsAtPath:phone_num_file]) {
    125 		// file exists, read what it has
    126 		// XXX: validate length of file too
    127 		NSError *error = nil;
    128 		phone_number = [NSString stringWithContentsOfFile:phone_num_file encoding:NSASCIIStringEncoding error:&error];
    129 		return true;
    130 	}
    131 
    132 	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Important"
    133 		message:@"We need this phone's number to find your mutual friends."
    134 		delegate:self cancelButtonTitle:@"Nope" otherButtonTitles:@"Ok", nil];
    135 
    136 	alert.alertViewStyle = UIAlertViewStylePlainTextInput;
    137 
    138 	// it's a phone number, so only show the number pad
    139 	UITextField * alertTextField = [alert textFieldAtIndex:0];
    140 	alertTextField.keyboardType = UIKeyboardTypeNumberPad;
    141 	alertTextField.placeholder = @"Enter your phone number";
    142 
    143 	[alert show];
    144 	return false;
    145 }
    146 
    147 - (void)alertView:(UIAlertView *)alertView
    148 clickedButtonAtIndex:(NSInteger)buttonIndex
    149 {
    150 	NSString *entered_phone_num = [[alertView textFieldAtIndex:0] text];
    151 	NSLog(@"warn: main: writing phone num '%@' to disk", entered_phone_num);
    152 	NSError *error;
    153 	[entered_phone_num writeToFile:phone_num_file atomically:YES encoding:NSASCIIStringEncoding error:&error];
    154 
    155 	if (error)
    156 		NSLog(@"warn: main: writing phone number file: %@", error);
    157 
    158 	if ([entered_phone_num compare:@""] == NSOrderedSame) {
    159 		NSLog(@"warn: load phone number: entered emtpy phone number");
    160 	}
    161 
    162 	phone_number = entered_phone_num;
    163 
    164 	if ([network_connection load_device_id:phone_number]) {
    165 		NSLog(@"info: network: connection ready");
    166 		// bulk update, doesn't take a payload
    167 		[network_connection send_message:3 contents:nil];
    168 	}
    169 	// else, device id request sent
    170 }
    171 
    172 - (void) update_address_book
    173 {
    174 	[_phnum_to_name_map removeAllObjects];
    175 	// XXX: it'd be nice to resize phnum_to_name_map to num_contacts here
    176 
    177 	for (Contact *contact in _address_book.contacts) {
    178 		NSString *disp_name;
    179 		// show first name and last initial if possible, otherwise
    180 		// just show the first name or the last name or the phone number
    181 		if (contact.first_name && contact.last_name)
    182 			disp_name = [NSString stringWithFormat:@"%@ %@",
    183 				     contact.first_name, [contact.last_name substringToIndex:1]];
    184 		else if (contact.first_name)
    185 			disp_name = contact.first_name;
    186 		else if (contact.last_name)
    187 			disp_name = contact.last_name;
    188 		else if ([contact.phone_numbers count])
    189 			disp_name = [contact.phone_numbers objectAtIndex:0];
    190 		else
    191 			disp_name = @"No Name";
    192 
    193 		// map the persons known phone number to their massaged name
    194 		for (NSString *tmp_phone_number in contact.phone_numbers)
    195 			[_phnum_to_name_map setObject:disp_name forKey:tmp_phone_number];
    196 	}
    197 }
    198 
    199 - (void) didReceiveMemoryWarning
    200 {
    201 	[super didReceiveMemoryWarning];
    202 	// Dispose of any resources that can be recreated.
    203 }
    204 
    205 - (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
    206 {
    207 	// "lists you're in" and "other lists"
    208 	return 2;
    209 }
    210 
    211 - (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    212 {
    213 	if (section > 1)
    214 		// should never happen
    215 		return 0;
    216 
    217 	return [[_lists objectAtIndex:section] count];
    218 }
    219 
    220 // new list dialogue has been saved
    221 - (IBAction) unwindToList:(UIStoryboardSegue *)segue
    222 {
    223 }
    224 
    225 - (void) lists_get_finished:(NSNotification *)notification
    226 {
    227 	NSDictionary *response_dict = notification.userInfo;
    228 	// Already checked for existence before this function was called
    229 	NSArray *json_lists = response_dict[@"data"];
    230 
    231 	NSLog(@"lists_get: got %lu lists from server", (unsigned long)[json_lists count]);
    232 
    233 	NSMutableArray *lists = [_lists objectAtIndex:0];
    234 	[lists removeAllObjects];
    235 
    236 	for (NSDictionary *list in json_lists) {
    237 		SharedList *tmp = [self deserialize_full_list:list];
    238 		[lists addObject:tmp];
    239 
    240 		NSLog(@"adding list '%@', num '%@'", tmp.name, tmp.num);
    241 	}
    242 
    243 	NSIndexSet *section = [NSIndexSet indexSetWithIndex:0];
    244 	[self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone];
    245 }
    246 
    247 - (void) lists_get_other_finished:(NSNotification *)notification;
    248 {
    249 	NSDictionary *response_dict = notification.userInfo;
    250 	// Already checked for existence before this function was called
    251 	NSArray *other_json_lists = response_dict[@"data"];
    252 
    253 	NSLog(@"lists_get_other: got %lu other lists from server", (unsigned long)[other_json_lists count]);
    254 
    255 	NSMutableArray *other_lists = [_lists objectAtIndex:1];
    256 	[other_lists removeAllObjects];
    257 
    258 	for (NSDictionary *list in other_json_lists) {
    259 		SharedList *tmp = [self deserialize_light_list:list];
    260 		[other_lists addObject:tmp];
    261 
    262 		NSLog(@"adding other list '%@', num '%@'", tmp.name, tmp.num);
    263 	}
    264 
    265 	NSIndexSet *section = [NSIndexSet indexSetWithIndex:1];
    266 	[self.tableView reloadSections:section withRowAnimation:UITableViewRowAnimationNone];
    267 }
    268 
    269 - (void) finished_new_list_request:(NSNotification *) notification
    270 {
    271 	NSDictionary *response = notification.userInfo;
    272 	NSDictionary *list = [response objectForKey:@"data"];
    273 
    274 	SharedList *shlist = [self deserialize_full_list:list];
    275 
    276 	NSMutableArray *lists = [_lists objectAtIndex:0];
    277 	[lists addObject:shlist];
    278 
    279 	// response looks good, insert the new list
    280 	NSUInteger new_row_pos = [lists count] - 1;
    281 	NSIndexPath *index_path = [NSIndexPath indexPathForRow:new_row_pos inSection:0];
    282 	[self.tableView insertRowsAtIndexPaths:@[index_path] withRowAnimation:UITableViewRowAnimationFade];
    283 
    284 	[self update_section_headers];
    285 }
    286 
    287 - (SharedList *) deserialize_full_list:(NSDictionary *)json_list
    288 {
    289 	SharedList *shlist = [[SharedList alloc] init];
    290 	shlist.num = [json_list objectForKey:@"num"];
    291 
    292 	// We need some careful decoding to get a usable Unicode string
    293 	NSData *name_data = [json_list[@"name"] dataUsingEncoding:NSISOLatin1StringEncoding];
    294 	shlist.name = [[NSString alloc] initWithData:name_data encoding:NSUTF8StringEncoding];
    295 
    296 	NSNumber *date = json_list[@"date"];
    297 	if ([date intValue] != 0) {
    298 		shlist.date = [NSDate dateWithTimeIntervalSince1970:[date floatValue]];
    299 	}
    300 	else {
    301 		shlist.date = nil;
    302 	}
    303 
    304 	shlist.members_phone_nums = [json_list objectForKey:@"members"];
    305 	shlist.items_ready = [json_list objectForKey:@"items_complete"];
    306 	shlist.items_total = [json_list objectForKey:@"items_total"];
    307 
    308 	return shlist;
    309 }
    310 
    311 - (SharedList *) deserialize_light_list:(NSDictionary *)json_list
    312 {
    313 	SharedList *shlist = [[SharedList alloc] init];
    314 	shlist.num = [json_list objectForKey:@"num"];
    315 
    316 	// We need some careful decoding to get a usable Unicode string
    317 	NSData *name_data = [json_list[@"name"] dataUsingEncoding:NSISOLatin1StringEncoding];
    318 	shlist.name = [[NSString alloc] initWithData:name_data encoding:NSUTF8StringEncoding];
    319 
    320 	shlist.members_phone_nums = [json_list objectForKey:@"members"];
    321 
    322 	return shlist;
    323 }
    324 
    325 // major thing here is join list requests
    326 - (void)tableView:(UITableView *)tableView
    327 	didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    328 {
    329 	[tableView deselectRowAtIndexPath:indexPath animated:YES];
    330 
    331 	// section 0 is going to segue to the list items screen
    332 	if ([indexPath section] == 0)
    333 		return;
    334 
    335 	// we're in section 1 now, a tap down here means we're doing a join list request
    336 	SharedList *list = [[_lists objectAtIndex:1] objectAtIndex:[indexPath row]];
    337 	NSLog(@"info: joining list '%@'", list.name);
    338 
    339 	// the response for this does all of the heavy row moving work
    340 	NSMutableDictionary *request = [[NSMutableDictionary alloc] init];
    341 	[request setObject:list.num forKey:@"list_num"];
    342 	[network_connection send_message:list_join contents:request];
    343 }
    344 
    345 - (void) finished_join_list_request:(NSNotification *) notification
    346 {
    347 	NSDictionary *response = notification.userInfo;
    348 	NSDictionary *json_list = response[@"list"];
    349 	NSLog(@"network: joined list %@", json_list[@"num"]);
    350 
    351 	NSMutableArray *lists = [_lists objectAtIndex:0];
    352 	NSMutableArray *other_lists = [_lists objectAtIndex:1];
    353 
    354 	// Find the list number we received a response for
    355 	SharedList *needle = nil;
    356 	for (SharedList *temp in other_lists) {
    357 		if (temp.num == json_list[@"num"]) {
    358 			needle = temp;
    359 			break;
    360 		}
    361 	}
    362 
    363 	// If we received an update from a list id we don't know about, do nothing
    364 	if (needle == nil)
    365 		return;
    366 
    367 	// The server sent us a full list object, make sure to copy cell reference
    368 	SharedList *joined_list = [self deserialize_full_list:json_list];
    369 	joined_list.cell = needle.cell;
    370 
    371 	// Add completely new object to lists section and remove the old list
    372 	[lists addObject:joined_list];
    373 	[other_lists removeObject:needle];
    374 
    375 	// Get the cell index path from the matched list cell
    376 	NSIndexPath *orig_index_path = [self.tableView indexPathForCell:joined_list.cell];
    377 
    378 	// Compute new position and start moving row as soon as possible
    379 	// XXX: sorting
    380 	NSUInteger new_row_pos = [lists count] - 1;
    381 	NSIndexPath *new_index_path = [NSIndexPath indexPathForRow:new_row_pos inSection:0];
    382 	[self.tableView moveRowAtIndexPath:orig_index_path toIndexPath:new_index_path];
    383 
    384 	[self update_section_headers];
    385 
    386 	// Update members in list row
    387 	[self process_members_array:joined_list.members_phone_nums cell:joined_list.cell];
    388 
    389 	// Add > accessory indicator
    390 	joined_list.cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    391 
    392 	// Find fraction UILAbel, populate it and then show it
    393 	UILabel *fraction = (UILabel *)[joined_list.cell viewWithTag:4];
    394 	fraction.text = [self fraction:joined_list.items_ready denominator:joined_list.items_total];
    395 	fraction.hidden = NO;
    396 
    397 	// Show date label if date has been set to something
    398 	if (joined_list.date != nil) {
    399 		UILabel *deadline_label = (UILabel *)[joined_list.cell viewWithTag:3];
    400 		deadline_label.hidden = NO;
    401 	}
    402 }
    403 
    404 
    405 // section header titles
    406 - (NSString *)tableView:(UITableView *)tableView
    407 titleForHeaderInSection:(NSInteger)section
    408 {
    409 	if (section > 1)
    410 		// should not happen
    411 		return @"";
    412 
    413 	unsigned long total = [[_lists objectAtIndex:section] count];
    414 	if (section == 0)
    415 		return [NSString stringWithFormat:@"Your Lists (%lu)", total];
    416 	else if (section == 1)
    417 		return [NSString stringWithFormat:@"Other Lists (%lu)", total];
    418 	return @"";
    419 }
    420 
    421 - (void) update_section_headers
    422 {
    423 	NSMutableArray *lists = [_lists objectAtIndex:0];
    424 	NSMutableArray *other_lists = [_lists objectAtIndex:1];
    425 
    426 	UITableViewHeaderFooterView *sectionZeroHeader = [self.tableView headerViewForSection:0];
    427 	UITableViewHeaderFooterView *sectionOneHeader = [self.tableView headerViewForSection:1];
    428 	NSString *sectionZeroLabel = [NSString stringWithFormat:@"Your Lists (%lu)", (unsigned long)[lists count]];
    429 	NSString *sectionOneLabel = [NSString stringWithFormat:@"Other Lists (%lu)", (unsigned long)[other_lists count]];
    430 
    431 	[sectionZeroHeader.textLabel setText:sectionZeroLabel];
    432 	[sectionZeroHeader setNeedsLayout];
    433 	[sectionOneHeader.textLabel setText:sectionOneLabel];
    434 	[sectionOneHeader setNeedsLayout];
    435 }
    436 
    437 - (void) finished_leave_list_request:(NSNotification *) notification
    438 {
    439 	NSDictionary *response = notification.userInfo;
    440 	NSNumber *list_num = response[@"list_num"];
    441 	NSLog(@"network: left list %@", list_num);
    442 
    443 	NSMutableArray *lists = [_lists objectAtIndex:0];
    444 	NSMutableArray *other_lists = [_lists objectAtIndex:1];
    445 
    446 	SharedList *list = nil;
    447 	for (SharedList *temp in lists) {
    448 		if (temp.num == response[@"list_num"]) {
    449 			list = temp;
    450 			break;
    451 		}
    452 	}
    453 	if (list == nil)
    454 		return;
    455 
    456 	NSNumber *list_empty = response[@"list_empty"];
    457 	if ([list_empty intValue] == 1) {
    458 		// List was empty, delete instead of moving it
    459 		[lists removeObject:list];
    460 
    461 		NSIndexPath *old_path = [self.tableView indexPathForCell:list.cell];
    462 		[self.tableView deleteRowsAtIndexPaths:@[old_path] withRowAnimation:UITableViewRowAnimationAutomatic];
    463 
    464 		[self update_section_headers];
    465 		return;
    466 	}
    467 
    468 	// Insert the new object at the beginning to match gui moving below
    469 	[other_lists insertObject:list atIndex:0];
    470 	[lists removeObject:list];
    471 
    472 	// Perform row move, the destination is the top of "other lists"
    473 	NSIndexPath *old_path = [self.tableView indexPathForCell:list.cell];
    474 	NSIndexPath *new_path = [NSIndexPath indexPathForRow:0 inSection:1];
    475 	[self.tableView moveRowAtIndexPath:old_path toIndexPath:new_path];
    476 
    477 	// Make sure section headers are accurate
    478 	[self update_section_headers];
    479 
    480 	// Remove yourself from the members array
    481 	NSMutableArray *members = [list.members_phone_nums mutableCopy];
    482 	NSUInteger index = [members indexOfObject:@"4037082094"];
    483 	if (index != NSNotFound) {
    484 		[members removeObjectAtIndex:index];
    485 	}
    486 	[self process_members_array:members cell:list.cell];
    487 
    488 	// Remove > accessory
    489 	list.cell.accessoryType = UITableViewCellAccessoryNone;
    490 
    491 	// Hide completion fraction
    492 	UILabel *fraction = (UILabel *)[list.cell viewWithTag:4];
    493 	fraction.hidden = YES;
    494 
    495 	// Hide date
    496 	UILabel *deadline_label = (UILabel *)[list.cell viewWithTag:3];
    497 	deadline_label.hidden = YES;
    498 
    499 	// XXX: update members array to disclude yourself (maybe send it back in response?)
    500 	// XXX: Maybe clear out list data that's no longer needed
    501 	// XXX: give some visual feedback here what's happening
    502 }
    503 
    504 
    505 - (UITableViewCell *) tableView:(UITableView *)tableView
    506 	  cellForRowAtIndexPath:(NSIndexPath *)indexPath
    507 {
    508 	UITableViewCell *cell;
    509 	cell = [tableView dequeueReusableCellWithIdentifier:@"SharedListPrototypeCell" forIndexPath:indexPath];
    510 
    511 	NSInteger section = [indexPath section];
    512 	NSInteger row = [indexPath row];
    513 	SharedList *shared_list = [[_lists objectAtIndex:section] objectAtIndex:row];
    514 
    515 	UILabel *deadline_label = (UILabel *)[cell viewWithTag:3];
    516 	UILabel *fraction_label = (UILabel *)[cell viewWithTag:4];
    517 
    518 	if (section == 0) {
    519 		// your lists section
    520 
    521 		if (shared_list.date == nil) {
    522 			deadline_label.hidden = YES;
    523 		} else {
    524 			// XXX: calculate how long until deadline
    525 			// NSDate *date = shared_list.date;
    526 			deadline_label.text = @"date";
    527 		}
    528 
    529 		float frac = [shared_list.items_ready floatValue]  / [shared_list.items_total floatValue];
    530 		if (frac > 0.80f)
    531 			fraction_label.textColor = [UIColor greenColor];
    532 		fraction_label.hidden = NO;
    533 		fraction_label.text = [self fraction:shared_list.items_ready
    534 					      denominator:shared_list.items_total];
    535 
    536 		// Add ">" accessory to indicate you can "enter" this list
    537 		cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    538 
    539 	}
    540 	else if (section == 1) {
    541 		// "other lists" section
    542 
    543 		// no deadline
    544 		deadline_label.text = @"";
    545 
    546 		// remove the > accessory and the completion fraction
    547 		cell.accessoryType = UITableViewCellAccessoryNone;
    548 		fraction_label.hidden = YES;
    549 	}
    550 
    551 	UILabel *main_label = (UILabel *)[cell viewWithTag:1];
    552 	main_label.text = shared_list.name;
    553 
    554 	[self process_members_array:shared_list.members_phone_nums cell:cell];
    555 
    556 	// hang on to a reference, this is needed in the networking gui callbacks
    557 	shared_list.cell = cell;
    558 	return cell;
    559 }
    560 
    561 - (void) process_members_array:(NSArray *)phnum_array cell:(UITableViewCell *)cell
    562 {
    563 	UILabel *members_label = (UILabel *)[cell viewWithTag:2];
    564 
    565 	if (!OSAtomicAnd32(0xffff, &_address_book->ready)) {
    566 		// not ready
    567 		NSMutableString *output = [[NSMutableString alloc] init];
    568 		for (id tmp_phone_number in phnum_array) {
    569 			if ([tmp_phone_number compare:phone_number] == NSOrderedSame) {
    570 				[output appendString:@"You"];
    571 				continue;
    572 			}
    573 
    574 			[output appendFormat:@", %@", tmp_phone_number];
    575 		}
    576 		members_label.text = output;
    577 		return;
    578 	}
    579 
    580 	// we can do phone number to name mappings
    581 	NSMutableArray *members = [[NSMutableArray alloc] init];
    582 	int others = 0;
    583 
    584 	// anything past the second field are list members
    585 	for (id tmp_phone_number in phnum_array) {
    586 
    587 		// try to find the list member in our address book
    588 		NSString *name = _phnum_to_name_map[tmp_phone_number];
    589 
    590 		if (name)
    591 			[members addObject:name];
    592 		else if (phone_number && ([phone_number compare:tmp_phone_number] == NSOrderedSame))
    593 			[members addObject:@"You"];
    594 		else
    595 			// didn't find it, you don't know this person
    596 			others++;
    597 	}
    598 
    599 	NSMutableString *members_str =
    600 	[[members componentsJoinedByString:@", "] mutableCopy];
    601 
    602 	if (others) {
    603 		char *plural;
    604 		if (others == 1)
    605 			plural = "other";
    606 		else
    607 			plural = "others";
    608 
    609 		NSString *buf = [NSString stringWithFormat:@" + %i %s",
    610 				 others, plural];
    611 		[members_str appendString:buf];
    612 	}
    613 
    614 	members_label.text = members_str;
    615 }
    616 
    617 
    618 // only section 0 lists can be edited
    619 - (BOOL) tableView:(UITableView *)tableView
    620 	canEditRowAtIndexPath:(NSIndexPath *)indexPath
    621 {
    622 	if ([indexPath section] == 0)
    623 		return YES;
    624 	return NO;
    625 }
    626 
    627 // what editing style should be applied to this indexpath
    628 - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView
    629 	   editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
    630 {
    631 	// don't have to check the section here because canEditRowAtIndexPath
    632 	// already said the section can't be edited
    633 	return UITableViewCellEditingStyleDelete;
    634 }
    635 
    636 // This functions called when delete has been prompted and ok'd
    637 - (void) tableView:(UITableView *)tableView
    638 	commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
    639 	forRowAtIndexPath:(NSIndexPath *)indexPath
    640 {
    641 	// we don't need to check for !section 0 because of canEditRowAtIndexPath
    642 	SharedList *list = [[_lists objectAtIndex:0] objectAtIndex:[indexPath row]];
    643 	NSLog(@"info: leaving '%@' list num '%@'", list.name, list.num);
    644 
    645 	// Send leave list message, response will do all heavy lifting
    646 	NSMutableDictionary *request = [[NSMutableDictionary alloc] init];
    647 	[request setObject:list.num forKey:@"list_num"];
    648 	[network_connection send_message:list_leave contents:request];
    649 
    650 	// Reset editing state back to the default
    651 	[self.tableView setEditing:FALSE animated:TRUE];
    652 }
    653 
    654 // customize deletion label text
    655 - (NSString *)tableView:(UITableView *)tableView
    656 	titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath
    657 {
    658 	return @"Leave";
    659 }
    660 
    661 // tell incoming controllers about their environment
    662 - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    663 {
    664 	if ([[segue identifier] isEqualToString:@"show list segue"]) {
    665 		// a shared list was selected, transfer into detailed view
    666 
    667 		NSIndexPath *path = [self.tableView indexPathForSelectedRow];
    668 		SharedList *list = [[_lists objectAtIndex:0] objectAtIndex:[path row]];
    669 
    670 		// make sure incoming view controller knows about itself
    671 		[segue.destinationViewController setMetadata:list];
    672 
    673 		// send update list items message
    674 		// network_connection->shlist_ldvc = segue.destinationViewController;
    675 		//[network_connection send_message:6 contents:list.id];
    676 	}
    677 
    678 	// DetailObject *detail = [self detailForIndexPath:path];
    679 	NSLog(@"info: main: preparing for segue");
    680 }
    681 
    682 // prevent segues from occurring when non member lists are selected
    683 - (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
    684 {
    685 	NSIndexPath *path = [self.tableView indexPathForSelectedRow];
    686 
    687 	if ([path section] == 0)
    688 		return YES;
    689 	return NO;
    690 }
    691 
    692 // taken from http://stackoverflow.com/questions/30859359/display-fraction-number-in-uilabel
    693 -(NSString *)fraction:(NSNumber *)numerator denominator:(NSNumber *)denominator
    694 {
    695 
    696 	NSMutableString *result = [NSMutableString string];
    697 
    698 	NSString *one = [numerator stringValue];
    699 	for (int i = 0; i < one.length; i++) {
    700 		[result appendString:[self superscript:[[one substringWithRange:NSMakeRange(i, 1)] intValue]]];
    701 	}
    702 
    703 	[result appendString:@"/"];
    704 
    705 	NSString *two = [denominator stringValue];
    706 	for (int i = 0; i < two.length; i++) {
    707 		[result appendString:[self subscript:[[two substringWithRange:NSMakeRange(i, 1)] intValue]]];
    708 	}
    709 
    710 	return result;
    711 }
    712 
    713 -(NSString *)superscript:(int)num
    714 {
    715 	NSDictionary *superscripts = @{@0: @"\u2070", @1: @"\u00B9", @2: @"\u00B2", @3: @"\u00B3", @4: @"\u2074", @5: @"\u2075", @6: @"\u2076", @7: @"\u2077", @8: @"\u2078", @9: @"\u2079"};
    716 	return superscripts[@(num)];
    717 }
    718 
    719 -(NSString *)subscript:(int)num
    720 {
    721 	NSDictionary *subscripts = @{@0: @"\u2080", @1: @"\u2081", @2: @"\u2082", @3: @"\u2083", @4: @"\u2084", @5: @"\u2085", @6: @"\u2086", @7: @"\u2087", @8: @"\u2088", @9: @"\u2089"};
    722 	return subscripts[@(num)];
    723 }
    724 
    725 @end