/** * Author: Timothy Prepscius * License: BSD + keep my name in the code! */ #import "ComposeViewController.h" #include "mailiverse/Lib.h" #include "MMUtilities.h" #include "MasterSingleton.h" #include #import "ComposeHeaderLineCell.h" using namespace mailiverse::utilities; using namespace mailiverse::mail::model; using namespace mailiverse; const int MODE_NORMAL = 0; const int MODE_ADDRESS = 1; const int SUBJECT_SECTION = 3; const int BODY_SECTION = 4; @interface ComposeViewController () { ConversationPtr conversation; MailPtr mail; int mode; Vector> sections; Vector sectionLabels; NSIndexPath *editPath; UITextField *editText; } @end @implementation ComposeViewController @synthesize loadedTableCell; @synthesize body; @synthesize bodyCell; @synthesize tableFooter; @synthesize autoCompleteTableView; @synthesize autoCompleteTableCell; + (id)instantiate { return [[ComposeViewController alloc] initWithNibName:@"ComposeViewController" bundle:nil]; } - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { LogDebug(ui::MailComposeViewController, "init"); self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { } return self; } - (void)viewDidLoad { [super viewDidLoad]; [self hackToolbarColor]; const char* labels[] = { "To:", "Cc:", "Bcc:", "Subject:", "Body:" }; for (auto &i : labels) { self->sectionLabels.add(i); Vector text; text.push_back(""); self->sections.push_back(text); } self->mode = MODE_NORMAL; self.autoCompleteTableView.autoCompleteDelegate = self; [self hackAddNavigationBarItems]; self.tableView.tableFooterView = tableFooter; } - (void)viewDidUnload { [self setLoadedTableCell:nil]; [self setBody:nil]; [self setBodyCell:nil]; [self setAutoCompleteTableCell:nil]; [self setAutoCompleteTableView:nil]; [self setTableFooter:nil]; [super viewDidUnload]; self.autoCompleteTableView.autoCompleteDelegate = nil; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) return (interfaceOrientation == UIInterfaceOrientationPortrait); return YES; } #pragma mark - Table view data source - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { bool isAutoComplete = mode == MODE_ADDRESS && (indexPath.section == editPath.section) && (indexPath.row == editPath.row+1); bool isBodyRow = mode == MODE_NORMAL && indexPath.section == BODY_SECTION; if (isAutoComplete) { return ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) ? 165 : 500; } else if (isBodyRow) { float rowHeight = std::min( std::max(body.contentSize.height, 100.0f), 2000.0f ); body.scrollEnabled = rowHeight < body.contentSize.height; return rowHeight; } return 43; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if (mode == MODE_NORMAL) return sections.size(); // mode == MODE_ADDRESS return editPath.section+1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (mode == MODE_NORMAL) return sections[section].size(); // mode == MODE_ADDRESS if (section < editPath.section) return sections[section].size(); return editPath.row+2; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { LogDebug (ui::ComposeViewController, "tableView:cellForRowAtIndexPath " << indexPath.section << " " << indexPath.row); bool isAutoComplete = mode == MODE_ADDRESS && (indexPath.section == editPath.section) && (indexPath.row == editPath.row+1); bool isBodyRow = mode == MODE_NORMAL && indexPath.section == BODY_SECTION; if (isBodyRow) { return bodyCell; } else if (isAutoComplete) { return autoCompleteTableCell; } else { LogDebug (ui::ComposeViewController, "cellForRowAtIndexPath " << indexPath.section << " " << indexPath.row << sections[indexPath.section][indexPath.row]); ComposeHeaderLineCell *cell = (ComposeHeaderLineCell *)loadTableViewCell( self, tableView, @"ComposeHeaderLineCell", ^(){ id result = loadedTableCell; loadedTableCell = nil; return result; } ); [cell onData:(sections[indexPath.section][indexPath.row]) withLabel:sectionLabels[indexPath.section] isFirst:indexPath.row==0 isLast:(indexPath.row==sections[indexPath.section].size()-1) isSubject:(indexPath.section==SUBJECT_SECTION) ]; cell.tag = indexPath.section; return cell; } } #pragma mark - AutoCompleteEditing - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string { if (textField.tag == SUBJECT_SECTION) return YES; if ([[textField.text substringWithRange:range] compare:@">"]==0) { textField.text = @""; if (mode == MODE_ADDRESS) [self performSelector:@selector(hackMoveEverythingBackDownAndStopAutoComplete:) withObject:textField afterDelay:0.1]; else [self removeRowIfEmptyAndReload:textField]; return NO; } return YES; } - (void)textFieldDidChange:(UITextField *)textField { NSIndexPath *path = getIndexPathForView(self.tableView, textField); LogDebug (ui::MailComposeViewController, "textFieldDidChange " << path.section << " " << path.row << " " << textField.text.UTF8String); sections[path.section][path.row] = textField.text.UTF8String; if (path.section == SUBJECT_SECTION) return; ComposeHeaderLineCell *cell = (ComposeHeaderLineCell *)[self.tableView cellForRowAtIndexPath:editPath]; [cell onIdentityChange]; if (mode == MODE_ADDRESS) [self.autoCompleteTableView onKeyChange:textField.text]; if (mode == MODE_NORMAL && !sections[path.section][path.row].empty()) [self performSelector:@selector(hackMoveEverythingUpAndStartTheAutoComplete:) withObject:textField afterDelay:0.1]; } - (void)hackMoveEverythingUpAndStartTheAutoComplete:(UITextField *)textField { editPath = getIndexPathForView(self.tableView, textField); LogDebug (ui::MailComposeViewController, "hackMoveEverythingUpAndStartTheAutoComplete " << editPath.section << " " << editPath.row << textField.text.UTF8String); [autoCompleteTableView onKeyChange:textField.text]; mode = MODE_ADDRESS; [self.tableView reloadData]; // we set the editText after the reload, so it is current ComposeHeaderLineCell *cell = (ComposeHeaderLineCell *)[self.tableView cellForRowAtIndexPath:editPath]; [cell onAutoComplete]; editText = cell.text; [editText becomeFirstResponder]; [self.tableView scrollToRowAtIndexPath:editPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; self.tableView.scrollEnabled = FALSE; } - (void)hackMoveEverythingBackDownAndStopAutoComplete:(UITextField *)textField { LogDebug (ui::MailComposeViewController, "hackMoveEverythingBackDownAndStopAutoComplete " << textField.text.UTF8String); [self removeRowIfEmpty:textField]; editPath = nil; editText = nil; mode = MODE_NORMAL; [self.tableView reloadData]; NSIndexPath *top = [NSIndexPath indexPathForRow:0 inSection:0]; self.tableView.scrollEnabled = TRUE; [self.tableView scrollToRowAtIndexPath:top atScrollPosition:UITableViewScrollPositionTop animated:NO]; } - (void)removeRowIfEmptyAndReload:(UITextField *)textField { [self removeRowIfEmpty:textField]; [self.tableView reloadData]; } - (bool)removeRowIfEmpty:(UITextField *)textField { NSIndexPath *path = getIndexPathForView(self.tableView, textField); LogDebug (ui::MailComposeViewController, "removeRowIfEmpty " << path.section << " " << path.row << textField.text.UTF8String); if (([textField.text length] == 0) && (sections[path.section].size() > 1)) { sections[path.section].erase(sections[path.section].begin()+path.row); return true; } return false; } - (NSArray *)valuesFor:(NSString *)_key { std::string key = _key.UTF8String; NSMutableArray *array = [[NSMutableArray alloc] init]; Vector addresses = masterSingleton->getAddressBook()->findPossibilities(key, 100); for (auto i : addresses) { NSString *name = [NSString stringWithFormat:@"%s", i->getName().c_str()]; NSString *email = [NSString stringWithFormat:@"%s", i->getEmail().c_str()]; [array addObject:[NSMutableArray arrayWithObjects:name, email, nil]]; } return array; } - (void) onSelected:(NSArray *)text { editText.text = [NSString stringWithFormat:@"%@ <%@>", [text objectAtIndex:0], [text objectAtIndex:1]]; [self textFieldDidChange:editText]; [self performSelector:@selector(hackMoveEverythingBackDownAndStopAutoComplete:) withObject:editText afterDelay:0.1]; } - (BOOL)textFieldShouldReturn:(UITextField *)textField { if (mode == MODE_ADDRESS) [self performSelector:@selector(hackMoveEverythingBackDownAndStopAutoComplete:) withObject:textField afterDelay:0.1]; else [self removeRowIfEmptyAndReload:textField]; return TRUE; } #pragma mark - Editing - (IBAction)onAdd:(id)sender { UIButton *button = sender; NSIndexPath *path = getIndexPathForView(self.tableView, button); LogDebug (ui::MailComposeViewController, "onAdd " << path.section << " " << path.row); if (!sections[path.section].back().empty()) sections[path.section].push_back(""); [self.tableView reloadData]; } - (void)textViewDidChange:(UITextView *)textView { if (textView == body) [self bodyDidChange]; } - (void)bodyDidChange { [UIView setAnimationsEnabled:NO]; [self.tableView beginUpdates]; [self.tableView endUpdates]; [UIView setAnimationsEnabled:YES]; } #pragma mark - Data - (Vector)calculateSectionItems:(const IdentityList &)list { Vector items; for (auto i : list) items.push_back(i->getFull()); return items; } - (void) onData:(Mail *)_mail withConversation:(Conversation *)_conversation { mail = _mail; conversation = _conversation; if (mail) { Recipients *recipients = mail->getHeader()->getRecipients(); sections[0] = [self calculateSectionItems:recipients->getTo()]; sections[1] = [self calculateSectionItems:recipients->getCc()]; sections[2] = [self calculateSectionItems:recipients->getBcc()]; for (auto &i : sections) if (i.empty()) i.push_back(""); Vector subject; if (mail->getHeader()->getSubject()) subject.push_back(*mail->getHeader()->getSubject()); sections[3] = subject; if (mail->getBody()->getText()) body.text = [NSString stringWithUTF8String:mail->getBody()->getText()->c_str()]; } else { BodyPtr synthBody = masterSingleton->getActions()->calculateSignaturedBody(""); body.text = toNSString(*synthBody->getText()); } } #pragma mark - #pragma mark actions - (void)dismiss { if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { [self.navigationController popViewControllerAnimated:TRUE]; } else { [self dismissModalViewControllerAnimated:TRUE]; } } - (IBAction)onClose:(id)sender { UIActionSheet *actionSheet = [[UIActionSheet alloc] initWithTitle:@"" delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Discard" otherButtonTitles:@"Save", @"Revert", nil]; actionSheet.actionSheetStyle = UIActionSheetStyleDefault; [actionSheet showInView:self.view]; } - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex { const int DISCARD = 0, SAVE = 1, REVERT = 2; if (buttonIndex == DISCARD) [self onDiscard]; else if (buttonIndex == SAVE) [self onSave]; else if (buttonIndex == REVERT) [self dismiss]; } - (IBAction)onSend:(id)sender { [self transferFromUI]; masterSingleton->getActions()->sendMail(conversation, mail); [self dismiss]; } - (void)onDiscard { if (mail) masterSingleton->getActions()->deleteMail(conversation, mail); [self dismiss]; } - (void)onSave { LogDebug(ui::MailComposeViewController, "onSave"); [self transferFromUI]; masterSingleton->getActions()->saveMail(conversation, mail); [self dismiss]; } - (void)transferFromUI { if (mail == NULL) { Pair pair = masterSingleton->getActions()->newMail(); conversation = pair.first; mail = pair.second; } Recipients *r = mail->getHeader()->getRecipients(); r->setTo(r->toIdentityList(masterSingleton->getCacheManager(), sections[0])); r->setCc(r->toIdentityList(masterSingleton->getCacheManager(), sections[1])); r->setBcc(r->toIdentityList(masterSingleton->getCacheManager(), sections[2])); mail->getHeader()->setSubject(new String(sections[3][0])); mail->getBody()->setText(new String(body.text.UTF8String)); mail->getHeader()->setBrief(mail->getBody()->calculateBrief()); } #pragma mark - #pragma mark Hacks - (void)hackToolbarColor { self.navigationController.navigationBar.tintColor = [UIColor blackColor]; } - (void)hackAddNavigationBarItems { UIBarButtonItem *close = [[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStylePlain target:self action:@selector(onClose:) ]; UIBarButtonItem *send = [[UIBarButtonItem alloc] initWithTitle:@"Send" style:UIBarButtonItemStylePlain target:self action:@selector(onSend:) ]; self.navigationItem.leftBarButtonItem = close; self.navigationItem.rightBarButtonItem = send; } @end