Skip to content
Browse files

Initial Commit

  • Loading branch information...
0 parents commit 0def3a57e1df4ade5bd03eb90096415ac4ce75da @thajcak committed Jul 19, 2012
Showing with 14,986 additions and 0 deletions.
  1. +15 −0 .gitignore
  2. +31 −0 CoreData/Address.h
  3. +22 −0 CoreData/Address.m
  4. +32 −0 CoreData/Contact.h
  5. +33 −0 CoreData/Contact.m
  6. +8 −0 CoreData/CoreDataStore.xcdatamodeld/.xccurrentversion
  7. +99 −0 CoreData/CoreDataStore.xcdatamodeld/CoreDataStore.xcdatamodel/contents
  8. +43 −0 CoreData/Lead.h
  9. +42 −0 CoreData/Lead.m
  10. +39 −0 CoreData/Opportunity.h
  11. +31 −0 CoreData/Opportunity.m
  12. +43 −0 CoreData/SalesAccount.h
  13. +30 −0 CoreData/SalesAccount.m
  14. BIN Default-Landscape@2x~ipad.png
  15. BIN Default-Landscape~ipad.png
  16. BIN Default-Portrait@2x~ipad.png
  17. BIN Default-Portrait~ipad.png
  18. BIN Icon-72.png
  19. BIN Icon-72@2x.png
  20. +21 −0 README.md
  21. BIN Resource/Images/compass.png
  22. BIN Resource/Images/compass@2x.png
  23. BIN Resource/Images/contact.png
  24. BIN Resource/Images/contact@2x.png
  25. BIN Resource/Images/favorite.png
  26. BIN Resource/Images/favorite@2x.png
  27. BIN Resource/Images/frame.png
  28. BIN Resource/Images/frame@2x.png
  29. BIN Resource/Images/gplaypattern.png
  30. BIN Resource/Images/gplaypattern@2x.png
  31. BIN Resource/Images/money_bag_dollars.png
  32. BIN Resource/Images/money_bag_dollars@2x.png
  33. BIN Resource/Images/navigation.png
  34. BIN Resource/Images/navigation@2x.png
  35. BIN Resource/Images/px_by_Gre3g.png
  36. BIN Resource/Images/px_by_Gre3g@2x.png
  37. BIN Resource/Images/random_grey_variations.png
  38. BIN Resource/Images/random_grey_variations@2x.png
  39. BIN Resource/Images/strange_bullseyes.png
  40. BIN Resource/Images/strange_bullseyes@2x.png
  41. BIN Resource/Images/tactile_noise.png
  42. BIN Resource/Images/tactile_noise@2x.png
  43. BIN Resource/Images/weather_clear.png
  44. BIN Resource/Images/weather_fog.png
  45. BIN Resource/Images/weather_overcast.png
  46. BIN Resource/Images/weather_showers_scattered.png
  47. +1,295 −0 Sales Mate.xcodeproj/project.pbxproj
  48. +16 −0 Sales Mate/Accounts/AccountContactsTableViewController.h
  49. +113 −0 Sales Mate/Accounts/AccountContactsTableViewController.m
  50. +15 −0 Sales Mate/Accounts/AccountDetailsViewController.h
  51. +294 −0 Sales Mate/Accounts/AccountDetailsViewController.m
  52. +16 −0 Sales Mate/Accounts/AccountOpportunitiesTableViewController.h
  53. +115 −0 Sales Mate/Accounts/AccountOpportunitiesTableViewController.m
  54. +32 −0 Sales Mate/AppDelegate.h
  55. +199 −0 Sales Mate/AppDelegate.m
  56. +16 −0 Sales Mate/Contacts/ContactDetailsViewController.h
  57. +321 −0 Sales Mate/Contacts/ContactDetailsViewController.m
  58. +29 −0 Sales Mate/DataHandler.h
  59. +531 −0 Sales Mate/DataHandler.m
  60. +16 −0 Sales Mate/Leads/LeadDetailsViewController.h
  61. +510 −0 Sales Mate/Leads/LeadDetailsViewController.m
  62. +17 −0 Sales Mate/Leads/LeadsViewController.h
  63. +419 −0 Sales Mate/Leads/LeadsViewController.m
  64. +13 −0 Sales Mate/LoginViewController.h
  65. +43 −0 Sales Mate/LoginViewController.m
  66. +13 −0 Sales Mate/Near Me/NearMeAnnotationView.h
  67. +31 −0 Sales Mate/Near Me/NearMeAnnotationView.m
  68. +16 −0 Sales Mate/Near Me/NearMeViewController.h
  69. +137 −0 Sales Mate/Near Me/NearMeViewController.m
  70. +15 −0 Sales Mate/Opportunities/OpportunitiesViewController.h
  71. +357 −0 Sales Mate/Opportunities/OpportunitiesViewController.m
  72. +16 −0 Sales Mate/Opportunities/OpportunityDetailsViewController.h
  73. +421 −0 Sales Mate/Opportunities/OpportunityDetailsViewController.m
  74. +16 −0 Sales Mate/RootTabBarController.h
  75. +256 −0 Sales Mate/RootTabBarController.m
  76. +54 −0 Sales Mate/Sales Mate-Info.plist
  77. +18 −0 Sales Mate/Sales Mate-Prefix.pch
  78. +94 −0 Sales Mate/Streaming/StreamingApiClient.h
  79. +221 −0 Sales Mate/Streaming/StreamingApiClient.m
  80. +57 −0 Sales Mate/Streaming/UrlConnectionDelegate.h
  81. +99 −0 Sales Mate/Streaming/UrlConnectionDelegate.m
  82. +2 −0 Sales Mate/en.lproj/InfoPlist.strings
  83. +18 −0 Sales Mate/main.m
  84. +21 −0 Third Party/BBlock/UIImage+BBlock.h
  85. +52 −0 Third Party/BBlock/UIImage+BBlock.m
  86. +7 −0 Third Party/BBlock/license
  87. +16 −0 Third Party/DCIntrospect/DCCrossHairView.h
  88. +43 −0 Third Party/DCIntrospect/DCCrossHairView.m
  89. +55 −0 Third Party/DCIntrospect/DCFrameView.h
  90. +234 −0 Third Party/DCIntrospect/DCFrameView.m
  91. +155 −0 Third Party/DCIntrospect/DCIntrospect.h
  92. +1,642 −0 Third Party/DCIntrospect/DCIntrospect.m
  93. +56 −0 Third Party/DCIntrospect/DCIntrospectSettings.h
  94. +34 −0 Third Party/DCIntrospect/DCStatusBarOverlay.h
  95. +107 −0 Third Party/DCIntrospect/DCStatusBarOverlay.m
  96. +19 −0 Third Party/DCIntrospect/license.txt
  97. +875 −0 Third Party/InnerBand/InnerBand.h
  98. +3,002 −0 Third Party/InnerBand/InnerBand.m
  99. +224 −0 Third Party/InnerBand/README.md
  100. +20 −0 Third Party/NGTabBarController/LICENSE
  101. +44 −0 Third Party/NGTabBarController/NGTabBar.h
  102. +404 −0 Third Party/NGTabBarController/NGTabBar.m
  103. +51 −0 Third Party/NGTabBarController/NGTabBarController.h
  104. +713 −0 Third Party/NGTabBarController/NGTabBarController.m
  105. +18 −0 Third Party/NGTabBarController/NGTabBarControllerAnimation.h
  106. +36 −0 Third Party/NGTabBarController/NGTabBarControllerDelegate.h
  107. +24 −0 Third Party/NGTabBarController/NGTabBarItem.h
  108. +191 −0 Third Party/NGTabBarController/NGTabBarItem.m
  109. +25 −0 Third Party/NGTabBarController/NGTabBarPosition.h
  110. +8 −0 Third Party/NGTabBarController/Prefix.pch
  111. +15 −0 Third Party/NGTabBarController/UINavigationController+NGTabBarNavigationDelegate.h
  112. +26 −0 Third Party/NGTabBarController/UINavigationController+NGTabBarNavigationDelegate.m
  113. +21 −0 Third Party/NGTabBarController/UIViewController+NGTabBarItem.h
  114. +30 −0 Third Party/NGTabBarController/UIViewController+NGTabBarItem.m
  115. +9 −0 Third Party/PSStackedView/LICENSE
  116. +61 −0 Third Party/PSStackedView/PSSVContainerView.h
  117. +231 −0 Third Party/PSStackedView/PSSVContainerView.m
  118. +21 −0 Third Party/PSStackedView/PSStackedView.h
  119. +156 −0 Third Party/PSStackedView/PSStackedViewController.h
Sorry, we could not display the entire diff because it was too big.
15 .gitignore
@@ -0,0 +1,15 @@
+# Xcode
+build/*
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+*.xcworkspace
+!default.xcworkspace
+xcuserdata
+profile
+*.moved-aside
31 CoreData/Address.h
@@ -0,0 +1,31 @@
+//
+// Address.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/12/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@class Lead;
+
+@interface Address : NSManagedObject
+
+@property (nonatomic, retain) NSString * street;
+@property (nonatomic, retain) NSString * city;
+@property (nonatomic, retain) NSString * state;
+@property (nonatomic, retain) NSString * postalCode;
+@property (nonatomic, retain) NSString * country;
+@property (nonatomic, retain) NSSet *lead;
+@end
+
+@interface Address (CoreDataGeneratedAccessors)
+
+- (void)addLeadObject:(Lead *)value;
+- (void)removeLeadObject:(Lead *)value;
+- (void)addLead:(NSSet *)values;
+- (void)removeLead:(NSSet *)values;
+
+@end
22 CoreData/Address.m
@@ -0,0 +1,22 @@
+//
+// Address.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/12/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "Address.h"
+#import "Lead.h"
+
+
+@implementation Address
+
+@dynamic street;
+@dynamic city;
+@dynamic state;
+@dynamic postalCode;
+@dynamic country;
+@dynamic lead;
+
+@end
32 CoreData/Contact.h
@@ -0,0 +1,32 @@
+//
+// Contact.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/17/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@class Address, Opportunity, SalesAccount;
+
+@interface Contact : NSManagedObject
+
+@property (nonatomic, retain) NSString * descriptionText;
+@property (nonatomic, retain) NSString * email;
+@property (nonatomic, retain) NSString * fax;
+@property (nonatomic, retain) NSNumber * geoLatitude;
+@property (nonatomic, retain) NSNumber * geoLongitude;
+@property (nonatomic, retain) NSString * homePhone;
+@property (nonatomic, retain) NSString * id;
+@property (nonatomic, retain) NSString * mobilePhone;
+@property (nonatomic, retain) NSString * name;
+@property (nonatomic, retain) NSString * phone;
+@property (nonatomic, retain) NSString * role;
+@property (nonatomic, retain) NSString * title;
+@property (nonatomic, retain) SalesAccount *account;
+@property (nonatomic, retain) Address *address;
+@property (nonatomic, retain) Opportunity *opportunity;
+
+@end
33 CoreData/Contact.m
@@ -0,0 +1,33 @@
+//
+// Contact.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/17/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "Contact.h"
+#import "Address.h"
+#import "Opportunity.h"
+#import "SalesAccount.h"
+
+
+@implementation Contact
+
+@dynamic descriptionText;
+@dynamic email;
+@dynamic fax;
+@dynamic geoLatitude;
+@dynamic geoLongitude;
+@dynamic homePhone;
+@dynamic id;
+@dynamic mobilePhone;
+@dynamic name;
+@dynamic phone;
+@dynamic role;
+@dynamic title;
+@dynamic account;
+@dynamic address;
+@dynamic opportunity;
+
+@end
8 CoreData/CoreDataStore.xcdatamodeld/.xccurrentversion
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>_XCCurrentVersionName</key>
+ <string>CoreDataStore.xcdatamodel</string>
+</dict>
+</plist>
99 CoreData/CoreDataStore.xcdatamodeld/CoreDataStore.xcdatamodel/contents
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model name="" userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="1171" systemVersion="11E53" minimumToolsVersion="Xcode 4.1" macOSVersion="Automatic" iOSVersion="Automatic">
+ <entity name="Address" representedClassName="Address" syncable="YES">
+ <attribute name="city" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="country" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="postalCode" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="state" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="street" optional="YES" attributeType="String" syncable="YES"/>
+ <relationship name="contact" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Contact" inverseName="address" inverseEntity="Contact" syncable="YES"/>
+ <relationship name="lead" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Lead" inverseName="address" inverseEntity="Lead" syncable="YES"/>
+ </entity>
+ <entity name="Contact" representedClassName="Contact" syncable="YES">
+ <attribute name="descriptionText" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="email" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="fax" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="geoLatitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="geoLongitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="homePhone" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="mobilePhone" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="phone" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="role" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
+ <relationship name="account" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="SalesAccount" inverseName="contacts" inverseEntity="SalesAccount" syncable="YES"/>
+ <relationship name="address" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Address" inverseName="contact" inverseEntity="Address" syncable="YES"/>
+ <relationship name="opportunity" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="Opportunity" inverseName="contacts" inverseEntity="Opportunity" syncable="YES"/>
+ </entity>
+ <entity name="Lead" representedClassName="Lead" syncable="YES">
+ <attribute name="annualRevenue" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
+ <attribute name="company" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="createdDate" optional="YES" attributeType="Date" syncable="YES"/>
+ <attribute name="descriptionText" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="doNotCall" optional="YES" attributeType="Boolean" syncable="YES"/>
+ <attribute name="email" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="emailOptOut" optional="YES" attributeType="Boolean" syncable="YES"/>
+ <attribute name="fax" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="faxOptOut" optional="YES" attributeType="Boolean" syncable="YES"/>
+ <attribute name="geoLatitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="geoLongitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="industry" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="lastModifiedDate" optional="YES" attributeType="Date" syncable="YES"/>
+ <attribute name="leadSource" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="leadStatus" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="mapImage" optional="YES" attributeType="Binary" syncable="YES"/>
+ <attribute name="mobilePhone" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="numberOfEmployees" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
+ <attribute name="phone" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="rating" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="website" optional="YES" attributeType="String" syncable="YES"/>
+ <relationship name="address" optional="YES" minCount="1" maxCount="1" deletionRule="Cascade" destinationEntity="Address" inverseName="lead" inverseEntity="Address" syncable="YES"/>
+ <relationship name="owner" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="User" inverseName="lead" inverseEntity="User" syncable="YES"/>
+ </entity>
+ <entity name="Opportunity" representedClassName="Opportunity" syncable="YES">
+ <attribute name="amount" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="closeDate" optional="YES" attributeType="Date" syncable="YES"/>
+ <attribute name="descriptionText" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="expectedRevenue" optional="YES" attributeType="Integer 32" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="leadSource" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="nextStep" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="probability" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
+ <attribute name="quantity" optional="YES" attributeType="Integer 16" defaultValueString="0" syncable="YES"/>
+ <attribute name="stage" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="type" optional="YES" attributeType="String" syncable="YES"/>
+ <relationship name="account" optional="YES" minCount="1" maxCount="1" deletionRule="Nullify" destinationEntity="SalesAccount" inverseName="opportunities" inverseEntity="SalesAccount" syncable="YES"/>
+ <relationship name="contacts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Contact" inverseName="opportunity" inverseEntity="Contact" syncable="YES"/>
+ </entity>
+ <entity name="SalesAccount" representedClassName="SalesAccount" syncable="YES">
+ <attribute name="annualRevenue" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
+ <attribute name="descriptionText" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="fax" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="geoLatitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="geoLongitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
+ <attribute name="id" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="industry" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="phone" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="type" optional="YES" attributeType="String" syncable="YES"/>
+ <attribute name="website" optional="YES" attributeType="String" syncable="YES"/>
+ <relationship name="contacts" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Contact" inverseName="account" inverseEntity="Contact" syncable="YES"/>
+ <relationship name="opportunities" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Opportunity" inverseName="account" inverseEntity="Opportunity" syncable="YES"/>
+ </entity>
+ <entity name="User" syncable="YES">
+ <relationship name="lead" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Lead" inverseName="owner" inverseEntity="Lead" syncable="YES"/>
+ </entity>
+ <elements>
+ <element name="Address" positionX="160" positionY="192" width="128" height="150"/>
+ <element name="Contact" positionX="160" positionY="192" width="128" height="270"/>
+ <element name="Lead" positionX="160" positionY="192" width="128" height="435"/>
+ <element name="Opportunity" positionX="160" positionY="192" width="128" height="255"/>
+ <element name="SalesAccount" positionX="160" positionY="192" width="128" height="240"/>
+ <element name="User" positionX="160" positionY="192" width="128" height="60"/>
+ </elements>
+</model>
43 CoreData/Lead.h
@@ -0,0 +1,43 @@
+//
+// Lead.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@class Address;
+
+@interface Lead : NSManagedObject
+
+@property (nonatomic, retain) NSNumber * annualRevenue;
+@property (nonatomic, retain) NSString * company;
+@property (nonatomic, retain) NSString * descriptionText;
+@property (nonatomic, retain) NSNumber * doNotCall;
+@property (nonatomic, retain) NSString * email;
+@property (nonatomic, retain) NSNumber * emailOptOut;
+@property (nonatomic, retain) NSString * fax;
+@property (nonatomic, retain) NSNumber * faxOptOut;
+@property (nonatomic, retain) NSNumber * geoLatitude;
+@property (nonatomic, retain) NSNumber * geoLongitude;
+@property (nonatomic, retain) NSString * id;
+@property (nonatomic, retain) NSString * industry;
+@property (nonatomic, retain) NSDate * lastModifiedDate;
+@property (nonatomic, retain) NSString * leadSource;
+@property (nonatomic, retain) NSString * leadStatus;
+@property (nonatomic, retain) NSData * mapImage;
+@property (nonatomic, retain) NSString * mobilePhone;
+@property (nonatomic, retain) NSString * name;
+@property (nonatomic, retain) NSNumber * numberOfEmployees;
+@property (nonatomic, retain) NSString * phone;
+@property (nonatomic, retain) NSString * rating;
+@property (nonatomic, retain) NSString * title;
+@property (nonatomic, retain) NSString * website;
+@property (nonatomic, retain) NSDate * createdDate;
+@property (nonatomic, retain) Address *address;
+@property (nonatomic, retain) NSManagedObject *owner;
+
+@end
42 CoreData/Lead.m
@@ -0,0 +1,42 @@
+//
+// Lead.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "Lead.h"
+#import "Address.h"
+
+
+@implementation Lead
+
+@dynamic annualRevenue;
+@dynamic company;
+@dynamic descriptionText;
+@dynamic doNotCall;
+@dynamic email;
+@dynamic emailOptOut;
+@dynamic fax;
+@dynamic faxOptOut;
+@dynamic geoLatitude;
+@dynamic geoLongitude;
+@dynamic id;
+@dynamic industry;
+@dynamic lastModifiedDate;
+@dynamic leadSource;
+@dynamic leadStatus;
+@dynamic mapImage;
+@dynamic mobilePhone;
+@dynamic name;
+@dynamic numberOfEmployees;
+@dynamic phone;
+@dynamic rating;
+@dynamic title;
+@dynamic website;
+@dynamic createdDate;
+@dynamic address;
+@dynamic owner;
+
+@end
39 CoreData/Opportunity.h
@@ -0,0 +1,39 @@
+//
+// Opportunity.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/16/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@class Contact, SalesAccount;
+
+@interface Opportunity : NSManagedObject
+
+@property (nonatomic, retain) NSNumber * amount;
+@property (nonatomic, retain) NSDate * closeDate;
+@property (nonatomic, retain) NSString * descriptionText;
+@property (nonatomic, retain) NSNumber * expectedRevenue;
+@property (nonatomic, retain) NSString * id;
+@property (nonatomic, retain) NSString * leadSource;
+@property (nonatomic, retain) NSString * name;
+@property (nonatomic, retain) NSString * nextStep;
+@property (nonatomic, retain) NSNumber * probability;
+@property (nonatomic, retain) NSNumber * quantity;
+@property (nonatomic, retain) NSString * stage;
+@property (nonatomic, retain) NSString * type;
+@property (nonatomic, retain) SalesAccount *account;
+@property (nonatomic, retain) NSSet *contacts;
+@end
+
+@interface Opportunity (CoreDataGeneratedAccessors)
+
+- (void)addContactsObject:(Contact *)value;
+- (void)removeContactsObject:(Contact *)value;
+- (void)addContacts:(NSSet *)values;
+- (void)removeContacts:(NSSet *)values;
+
+@end
31 CoreData/Opportunity.m
@@ -0,0 +1,31 @@
+//
+// Opportunity.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/16/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "Opportunity.h"
+#import "Contact.h"
+#import "SalesAccount.h"
+
+
+@implementation Opportunity
+
+@dynamic amount;
+@dynamic closeDate;
+@dynamic descriptionText;
+@dynamic expectedRevenue;
+@dynamic id;
+@dynamic leadSource;
+@dynamic name;
+@dynamic nextStep;
+@dynamic probability;
+@dynamic quantity;
+@dynamic stage;
+@dynamic type;
+@dynamic account;
+@dynamic contacts;
+
+@end
43 CoreData/SalesAccount.h
@@ -0,0 +1,43 @@
+//
+// SalesAccount.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <CoreData/CoreData.h>
+
+@class Contact, Opportunity;
+
+@interface SalesAccount : NSManagedObject
+
+@property (nonatomic, retain) NSNumber * annualRevenue;
+@property (nonatomic, retain) NSString * descriptionText;
+@property (nonatomic, retain) NSString * fax;
+@property (nonatomic, retain) NSNumber * geoLatitude;
+@property (nonatomic, retain) NSNumber * geoLongitude;
+@property (nonatomic, retain) NSString * id;
+@property (nonatomic, retain) NSString * industry;
+@property (nonatomic, retain) NSString * name;
+@property (nonatomic, retain) NSString * phone;
+@property (nonatomic, retain) NSString * type;
+@property (nonatomic, retain) NSString * website;
+@property (nonatomic, retain) NSSet *contacts;
+@property (nonatomic, retain) NSSet *opportunities;
+@end
+
+@interface SalesAccount (CoreDataGeneratedAccessors)
+
+- (void)addContactsObject:(Contact *)value;
+- (void)removeContactsObject:(Contact *)value;
+- (void)addContacts:(NSSet *)values;
+- (void)removeContacts:(NSSet *)values;
+
+- (void)addOpportunitiesObject:(Opportunity *)value;
+- (void)removeOpportunitiesObject:(Opportunity *)value;
+- (void)addOpportunities:(NSSet *)values;
+- (void)removeOpportunities:(NSSet *)values;
+
+@end
30 CoreData/SalesAccount.m
@@ -0,0 +1,30 @@
+//
+// SalesAccount.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "SalesAccount.h"
+#import "Contact.h"
+#import "Opportunity.h"
+
+
+@implementation SalesAccount
+
+@dynamic annualRevenue;
+@dynamic descriptionText;
+@dynamic fax;
+@dynamic geoLatitude;
+@dynamic geoLongitude;
+@dynamic id;
+@dynamic industry;
+@dynamic name;
+@dynamic phone;
+@dynamic type;
+@dynamic website;
+@dynamic contacts;
+@dynamic opportunities;
+
+@end
BIN Default-Landscape@2x~ipad.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Default-Landscape~ipad.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Default-Portrait@2x~ipad.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Default-Portrait~ipad.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Icon-72.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Icon-72@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 README.md
@@ -0,0 +1,21 @@
+# Sales Mate
+
+Sales Mate is a native iOS application that utilizes the Salesforce Mobile SDK. It authenticates via OAuth and utilizes Core Location, Core Data, Push Notifications, and the Streaming API.
+
+### Overview
+
+Once authenticated, Sales Mate queries Salesforce for all Opportunity and Lead information including related Contacts and Accounts. All data is stored in Core Data allowing offline access to all downloaded information. Sales Mate also connects to the Streaming API and listens for new Lead creations.
+
+### Limitations
+
+- Push Notifications only occur on new Lead creation with a Web source.
+- Streaming API only alerts users to new Leads created with a Web Source.
+- There is currently no editing.
+- For maps to work properly geolocation coordinates must be provided as a field on the Account, Contact, and Lead.
+- Leads Near Me only displays the Lead's Name and Company.
+
+### To Do
+
+- Core Data currently doesn't remove any information. If a lead is deleted it will remain on the device and may be visible to the user.
+- Allow user to edit information.
+- Open Leads from Leads Near Me tab.
BIN Resource/Images/compass.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/compass@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/contact.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/contact@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/favorite.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/favorite@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/frame.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/frame@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/gplaypattern.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/gplaypattern@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/money_bag_dollars.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/money_bag_dollars@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/navigation.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/navigation@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/px_by_Gre3g.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/px_by_Gre3g@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/random_grey_variations.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/random_grey_variations@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/strange_bullseyes.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN Resource/Images/strange_bullseyes@2x.png
Diff not rendered.
BIN Resource/Images/tactile_noise.png
Diff not rendered.
BIN Resource/Images/tactile_noise@2x.png
Diff not rendered.
BIN Resource/Images/weather_clear.png
Diff not rendered.
BIN Resource/Images/weather_fog.png
Diff not rendered.
BIN Resource/Images/weather_overcast.png
Diff not rendered.
BIN Resource/Images/weather_showers_scattered.png
Diff not rendered.
1,295 Sales Mate.xcodeproj/project.pbxproj
1,295 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
16 Sales Mate/Accounts/AccountContactsTableViewController.h
@@ -0,0 +1,16 @@
+//
+// AccountContactsTableViewController.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface AccountContactsTableViewController : UITableViewController
+
+@property (nonatomic, retain) id accountId;
+@property (nonatomic, retain) UINavigationController *navigationController;
+
+@end
113 Sales Mate/Accounts/AccountContactsTableViewController.m
@@ -0,0 +1,113 @@
+//
+// AccountContactsTableViewController.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "AccountContactsTableViewController.h"
+#import "ContactDetailsViewController.h"
+
+#import "Contact.h"
+#import "SalesAccount.h"
+
+@interface AccountContactsTableViewController ()
+{
+ CoreDataStore *_dataStore;
+ SalesAccount *_account;
+
+ NSMutableArray *_accountContactsArray;
+}
+@end
+
+@implementation AccountContactsTableViewController
+
+@synthesize accountId = _accountId;
+@synthesize navigationController = _navigationController;
+
+- (id)initWithStyle:(UITableViewStyle)style
+{
+ self = [super initWithStyle:style];
+ if (self) {
+ // Custom initialization
+ }
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ [self.tableView setFrame:CGRectMake(11.0f, 313.0f, 253.0f, 252.0f)];
+ [self.tableView setAutoresizingMask:UIViewAutoresizingNone];
+ [self.tableView setBackgroundView:nil];
+ [self.tableView setBackgroundColor:[UIColor clearColor]];
+ [self.tableView setSeparatorColor:[UIColor clearColor]];
+
+ _dataStore = [CoreDataStore createStore];
+ _account = (SalesAccount *)[_dataStore entityByObjectID:_accountId];
+
+ _accountContactsArray = [[NSMutableArray alloc] init];
+ for (Contact *contact in [Contact allForPredicate:[NSPredicate predicateWithFormat:@"account == %@", _account] orderBy:@"name" ascending:YES]) {
+ [_accountContactsArray addObject:contact];
+ }
+
+ [self.tableView reloadData];
+}
+
+- (void)viewDidUnload
+{
+ [super viewDidUnload];
+ // Release any retained subviews of the main view.
+ // e.g. self.myOutlet = nil;
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ return YES;
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ // Return the number of sections.
+ return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ // Return the number of rows in the section.
+ return [_accountContactsArray count];;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+
+ if (cell == nil) {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
+ [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
+ }
+
+ Contact *contact = [_accountContactsArray objectAtIndex:indexPath.row];
+
+ [cell.textLabel setText:contact.name];
+
+ return cell;
+}
+
+#pragma mark - Table view delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ ContactDetailsViewController *detailsViewController = [[ContactDetailsViewController alloc] init];
+ [detailsViewController setContactId:((Contact *)[_accountContactsArray objectAtIndex:indexPath.row]).objectID];
+
+ [_navigationController pushViewController:detailsViewController animated:YES];
+ [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+}
+
+@end
15 Sales Mate/Accounts/AccountDetailsViewController.h
@@ -0,0 +1,15 @@
+//
+// AccountDetailsViewController.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/17/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface AccountDetailsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
+
+@property (nonatomic, strong) id accountId;
+
+@end
294 Sales Mate/Accounts/AccountDetailsViewController.m
@@ -0,0 +1,294 @@
+//
+// AccountDetailsViewController.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/17/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "AccountDetailsViewController.h"
+#import "AccountContactsTableViewController.h"
+#import "AccountOpportunitiesTableViewController.h"
+
+#import "SalesAccount.h"
+
+#import "TSMiniWebBrowser.h"
+
+#import <MapKit/MapKit.h>
+
+@interface AccountDetailsViewController ()
+{
+ CoreDataStore *_dataStore;
+ SalesAccount *_account;
+
+ UITableView *_accountDetailsTableView;
+
+ AccountContactsTableViewController *_contactsTableViewController;
+ AccountOpportunitiesTableViewController *_opportunitiesTableViewController;
+}
+@end
+
+@implementation AccountDetailsViewController
+
+@synthesize accountId = _accountId;
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+ if (self) {
+ // Custom initialization
+ }
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ _dataStore = [CoreDataStore createStore];
+ _account = (SalesAccount *)[_dataStore entityByObjectID:_accountId];
+
+ [self setTitle:_account.name];
+ [self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"gplaypattern.png"]]];
+
+ UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
+ target:self
+ action:SEL(dismissModalViewControllerAnimated:)];
+ [self.navigationItem setRightBarButtonItem:closeButton];
+
+ UIImage *backgroundImage = [UIImage imageWithIdentifier:@"Account Details Background" forSize:CGSizeMake(540.0f, 576.0f) andDrawingBlock:^{
+ //// General Declarations
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ //// Color Declarations
+ UIColor* semiTransparentBlack = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.5];
+ UIColor* descriptionBackdrop = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.35];
+
+ //// Shadow Declarations
+ CGColorRef mapInnerShadow = [UIColor blackColor].CGColor;
+ CGSize mapInnerShadowOffset = CGSizeMake(0, -0);
+ CGFloat mapInnerShadowBlurRadius = 10;
+ CGColorRef mapDropShadow = [UIColor blackColor].CGColor;
+ CGSize mapDropShadowOffset = CGSizeMake(3, 3);
+ CGFloat mapDropShadowBlurRadius = 5;
+
+
+ //// Rectangle Drawing
+ UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(240, 0, 300, 300)];
+ [semiTransparentBlack setFill];
+ [rectanglePath fill];
+
+ ////// Rectangle Inner Shadow
+ CGRect rectangleBorderRect = CGRectInset([rectanglePath bounds], -mapInnerShadowBlurRadius, -mapInnerShadowBlurRadius);
+ rectangleBorderRect = CGRectOffset(rectangleBorderRect, -mapInnerShadowOffset.width, -mapInnerShadowOffset.height);
+ rectangleBorderRect = CGRectInset(CGRectUnion(rectangleBorderRect, [rectanglePath bounds]), -1, -1);
+
+ UIBezierPath* rectangleNegativePath = [UIBezierPath bezierPathWithRect: rectangleBorderRect];
+ [rectangleNegativePath appendPath: rectanglePath];
+ rectangleNegativePath.usesEvenOddFillRule = YES;
+
+ CGContextSaveGState(context);
+ {
+ CGFloat xOffset = mapInnerShadowOffset.width + round(rectangleBorderRect.size.width);
+ CGFloat yOffset = mapInnerShadowOffset.height;
+ CGContextSetShadowWithColor(context,
+ CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
+ mapInnerShadowBlurRadius,
+ mapInnerShadow);
+
+ [rectanglePath addClip];
+ CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rectangleBorderRect.size.width), 0);
+ [rectangleNegativePath applyTransform: transform];
+ [[UIColor grayColor] setFill];
+ [rectangleNegativePath fill];
+ }
+ CGContextRestoreGState(context);
+
+
+
+
+ //// Rectangle 2 Drawing
+ UIBezierPath* rectangle2Path = [UIBezierPath bezierPathWithRect: CGRectMake(255, 15, 270, 270)];
+ CGContextSaveGState(context);
+ CGContextSetShadowWithColor(context, mapDropShadowOffset, mapDropShadowBlurRadius, mapDropShadow);
+ [[UIColor blackColor] setFill];
+ [rectangle2Path fill];
+ CGContextRestoreGState(context);
+
+
+
+ //// Rounded Rectangle Drawing
+ UIBezierPath* roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(10, 312, 255, 254) byRoundingCorners: UIRectCornerTopLeft | UIRectCornerBottomLeft cornerRadii: CGSizeMake(4, 4)];
+ CGContextSaveGState(context);
+ CGContextSetShadowWithColor(context, mapDropShadowOffset, mapDropShadowBlurRadius, mapDropShadow);
+ [descriptionBackdrop setFill];
+ [roundedRectanglePath fill];
+ CGContextRestoreGState(context);
+
+ [[UIColor blackColor] setStroke];
+ roundedRectanglePath.lineWidth = 2;
+ [roundedRectanglePath stroke];
+
+
+ //// Rounded Rectangle 2 Drawing
+ UIBezierPath* roundedRectangle2Path = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(276, 312, 255, 254) byRoundingCorners: UIRectCornerTopRight | UIRectCornerBottomRight cornerRadii: CGSizeMake(4, 4)];
+ CGContextSaveGState(context);
+ CGContextSetShadowWithColor(context, mapDropShadowOffset, mapDropShadowBlurRadius, mapDropShadow);
+ [descriptionBackdrop setFill];
+ [roundedRectangle2Path fill];
+ CGContextRestoreGState(context);
+
+ [[UIColor blackColor] setStroke];
+ roundedRectangle2Path.lineWidth = 2;
+ [roundedRectangle2Path stroke];
+
+
+ }];
+
+ UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];
+ [self.view addSubview:backgroundImageView];
+
+ //Map View
+ MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(256.0f, 16.0f, 268.0f, 268.0f)];
+ [mapView setMapType:MKMapTypeStandard];
+ CLLocationCoordinate2D accountCoordinates = CLLocationCoordinate2DMake(UNBOX_DOUBLE(_account.geoLatitude), UNBOX_DOUBLE(_account.geoLongitude));
+ [mapView setRegion:MKCoordinateRegionMake(accountCoordinates, MKCoordinateSpanMake(0.1f, 0.1f))];
+
+ if (_account.geoLatitude != nil && _account.geoLongitude != nil) {
+ MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
+ [annotation setCoordinate:accountCoordinates];
+ [mapView addAnnotation:annotation];
+ }
+
+ [self.view addSubview:mapView];
+
+ // Account Details
+ _accountDetailsTableView = [[UITableView alloc] initWithFrame:CGRectMake(-45.0f, 0.0f, 285.0f, 300.0f) style:UITableViewStylePlain];
+ [_accountDetailsTableView setBackgroundView:nil];
+ [_accountDetailsTableView setBackgroundColor:[UIColor clearColor]];
+ [_accountDetailsTableView setScrollEnabled:NO];
+ [_accountDetailsTableView setSeparatorColor:[UIColor clearColor]];
+ [_accountDetailsTableView setDataSource:self];
+ [_accountDetailsTableView setDelegate:self];
+ [self.view addSubview:_accountDetailsTableView];
+
+ IBButton *websiteButton = [IBButton flatButtonWithTitle:@"View Website" color:[UIColor colorWithHexString:@"404080"]];
+ [websiteButton setFrame:CGRectMake(10.0f, 154.0f, 220.0f, 37.0f)];
+ [websiteButton addTarget:self
+ action:SEL(openWebsite)
+ forControlEvents:UIControlEventTouchUpInside];
+ [self.view addSubview:websiteButton];
+ if (_account.website == nil) {
+ [websiteButton setEnabled:NO];
+ }
+
+ // Other Tables
+ _contactsTableViewController = [[AccountContactsTableViewController alloc] initWithStyle:UITableViewStylePlain];
+ [_contactsTableViewController setAccountId:_accountId];
+ [_contactsTableViewController setNavigationController:self.navigationController];
+ [self.view addSubview:_contactsTableViewController.tableView];
+
+ _opportunitiesTableViewController = [[AccountOpportunitiesTableViewController alloc] initWithStyle:UITableViewStylePlain];
+ [_opportunitiesTableViewController setAccountId:_accountId];
+ [_opportunitiesTableViewController setNavigationController:self.navigationController];
+ [self.view addSubview:_opportunitiesTableViewController.tableView];
+}
+
+- (void)viewDidUnload
+{
+ [super viewDidUnload];
+ // Release any retained subviews of the main view.
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ return YES;
+}
+
+#pragma mark - Table View Data Source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ return 5;
+}
+
+- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return 30.0f;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell Identifier";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+
+ if (cell == nil) {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ [cell.detailTextLabel setNumberOfLines:2];
+ [cell.textLabel setNumberOfLines:2];
+ }
+
+ switch (indexPath.row) {
+ case 0:
+ {
+ [cell.textLabel setText:@"Type"];
+ [cell.detailTextLabel setText:(_account.type != nil ? _account.type : @"Unknown")];
+ break;
+ }
+ case 1:
+ {
+ [cell.textLabel setText:@"Industry"];
+ [cell.detailTextLabel setText:(_account.industry != nil ? _account.industry : @"Unknown")];
+ break;
+ }
+ case 2:
+ {
+ [cell.textLabel setText:@"Annual Revenue"];
+
+ NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
+ [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
+ [numberFormatter setMaximumFractionDigits:0];
+ [cell.detailTextLabel setText:(_account.annualRevenue != nil ? [numberFormatter stringFromNumber:_account.annualRevenue] : @"Unknown")];
+ break;
+ }
+ case 3:
+ {
+ [cell.textLabel setText:@"Phone"];
+ [cell.detailTextLabel setText:(_account.phone != nil ? _account.phone : @"Unknown")];
+ break;
+ }
+ case 4:
+ {
+ [cell.textLabel setText:@"Fax"];
+ [cell.detailTextLabel setText:(_account.fax != nil ? _account.fax : @"Unknown")];
+ break;
+ }
+ case 5:
+ {
+ [cell.textLabel setText:@"Website"];
+ [cell.detailTextLabel setText:(_account.website != nil ? _account.website : @"Unknown")];
+ break;
+ }
+ }
+
+ return cell;
+}
+
+#pragma mark - Actions
+
+- (void)openWebsite
+{
+ TSMiniWebBrowser *browser = [[TSMiniWebBrowser alloc] initWithUrl:[NSURL URLWithString:_account.website]];
+ [browser setMode:TSMiniWebBrowserModeModal];
+ [browser setModalDismissButtonTitle:@"Close"];
+ [browser setBarStyle:UIBarStyleBlack];
+ [self presentModalViewController:browser animated:YES];
+}
+
+@end
16 Sales Mate/Accounts/AccountOpportunitiesTableViewController.h
@@ -0,0 +1,16 @@
+//
+// AccountOpportunitiesTableViewController.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface AccountOpportunitiesTableViewController : UITableViewController
+
+@property (nonatomic, retain) id accountId;
+@property (nonatomic, retain) UINavigationController *navigationController;
+
+@end
115 Sales Mate/Accounts/AccountOpportunitiesTableViewController.m
@@ -0,0 +1,115 @@
+//
+// AccountOpportunitiesTableViewController.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/18/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "AccountOpportunitiesTableViewController.h"
+
+#import "Opportunity.h"
+#import "SalesAccount.h"
+
+@interface AccountOpportunitiesTableViewController ()
+{
+ CoreDataStore *_dataStore;
+ SalesAccount *_account;
+
+ NSMutableArray *_accountOpportunitiesArray;
+}
+@end
+
+@implementation AccountOpportunitiesTableViewController
+
+@synthesize accountId = _accountId;
+@synthesize navigationController = _navigationController;
+
+- (id)initWithStyle:(UITableViewStyle)style
+{
+ self = [super initWithStyle:style];
+ if (self) {
+ // Custom initialization
+ }
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ [self.tableView setFrame:CGRectMake(277.0f, 313.0f, 253.0f, 252.0f)];
+ [self.tableView setAutoresizingMask:UIViewAutoresizingNone];
+ [self.tableView setBackgroundView:nil];
+ [self.tableView setBackgroundColor:[UIColor clearColor]];
+ [self.tableView setSeparatorColor:[UIColor clearColor]];
+
+ _dataStore = [CoreDataStore createStore];
+ _account = (SalesAccount *)[_dataStore entityByObjectID:_accountId];
+
+ _accountOpportunitiesArray = [[NSMutableArray alloc] init];
+ for (Opportunity *opportunity in [Opportunity allForPredicate:[NSPredicate predicateWithFormat:@"account == %@", _account] orderBy:@"name" ascending:YES]) {
+ [_accountOpportunitiesArray addObject:opportunity];
+ }
+
+ [self.tableView reloadData];
+}
+
+- (void)viewDidUnload
+{
+ [super viewDidUnload];
+ // Release any retained subviews of the main view.
+ // e.g. self.myOutlet = nil;
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ return YES;
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ // Return the number of sections.
+ return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ // Return the number of rows in the section.
+ return [_accountOpportunitiesArray count];
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+
+ if (cell == nil) {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ [cell.textLabel setNumberOfLines:2];
+ }
+
+ Opportunity *opportunity = [_accountOpportunitiesArray objectAtIndex:indexPath.row];
+
+ [cell.textLabel setText:opportunity.name];
+
+ return cell;
+}
+
+#pragma mark - Table view delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ // Navigation logic may go here. Create and push another view controller.
+ /*
+ <#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:@"<#Nib name#>" bundle:nil];
+ // ...
+ // Pass the selected object to the new view controller.
+ [self.navigationController pushViewController:detailViewController animated:YES];
+ */
+}
+
+@end
32 Sales Mate/AppDelegate.h
@@ -0,0 +1,32 @@
+//
+// AppDelegate.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 6/30/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "NGTabBarController.h"
+#import "SFOAuthCoordinator.h"
+#import "PSStackedViewController.h"
+
+#define MESSAGE_OPPORTUNITIES_UPDATED @"opportunities updated and saved"
+#define MESSAGE_LEADS_UPDATED @"leads updated and saved"
+#define MESSAGE_STREAMING_UPDATE_RECEIVED @"streaming update received"
+
+@class RootTabBarController;
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate, NGTabBarControllerDelegate, SFOAuthCoordinatorDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+@property (strong, nonatomic) SFOAuthCoordinator *coordinator;
+
+@property (strong, nonatomic) RootTabBarController *tabBarController;
+@property (strong, nonatomic) PSStackedViewController *opportunitiesStackController;
+@property (strong, nonatomic) PSStackedViewController *leadsStackController;
+@property (strong, nonatomic) PSStackedViewController *nearMeStackController;
+
+- (void)performLoginLogout;
+
+@end
199 Sales Mate/AppDelegate.m
@@ -0,0 +1,199 @@
+//
+// AppDelegate.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 6/30/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "AppDelegate.h"
+#import "DataHandler.h"
+
+#import "RootTabBarController.h"
+
+#import "PSStackedViewController.h"
+#import "OpportunitiesViewController.h"
+#import "LeadsViewController.h"
+#import "NearMeViewController.h"
+
+#import "SFOAuthCoordinator.h"
+#import "SFRestAPI.h"
+#import "LoginViewController.h"
+
+#import "DCIntrospect.h"
+
+@interface AppDelegate ()
+{
+ UINavigationController *_loginNavigationController;
+ NSData *_deviceToken;
+}
+@end
+
+@implementation AppDelegate
+
+@synthesize window = _window;
+@synthesize coordinator = _coordinator;
+
+@synthesize tabBarController = _tabBarController;
+@synthesize opportunitiesStackController = _opportunitiesStackController;
+@synthesize leadsStackController = _leadsStackController;
+@synthesize nearMeStackController = _nearMeStackController;
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
+{
+ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+
+ OpportunitiesViewController *opportunitiesViewController = [[OpportunitiesViewController alloc] init];
+ _opportunitiesStackController = [[PSStackedViewController alloc] initWithRootViewController:opportunitiesViewController];
+ [_opportunitiesStackController setDelegate:opportunitiesViewController];
+ [_opportunitiesStackController setLeftInset:0];
+ _opportunitiesStackController.ng_tabBarItem = [NGTabBarItem itemWithTitle:@"Opportunities" image:[UIImage imageNamed:@"money_bag_dollars.png"]];
+
+ LeadsViewController *leadsViewController = [[LeadsViewController alloc] init];
+ _leadsStackController = [[PSStackedViewController alloc] initWithRootViewController:leadsViewController];
+ [_leadsStackController setLeftInset:0];
+ _leadsStackController.ng_tabBarItem = [NGTabBarItem itemWithTitle:@"Leads" image:[UIImage imageNamed:@"favorite.png"]];
+
+ NearMeViewController *nearMeViewController = [[NearMeViewController alloc] init];
+ _nearMeStackController = [[PSStackedViewController alloc] initWithRootViewController:nearMeViewController];
+ [_nearMeStackController setLeftInset:0];
+ _nearMeStackController.ng_tabBarItem = [NGTabBarItem itemWithTitle:@"Leads Near Me" image:[UIImage imageNamed:@"compass.png"]];
+
+ _tabBarController = [[RootTabBarController alloc] initWithDelegate:self];
+ [_tabBarController setViewControllers:[NSArray arrayWithObjects:_opportunitiesStackController, _leadsStackController, _nearMeStackController, nil]];
+ [_tabBarController setTabBarPosition:NGTabBarPositionLeft];
+
+ [self.window setRootViewController:_tabBarController];
+ [self.window makeKeyAndVisible];
+
+#warning - Provide Remove Access ID
+ SFOAuthCredentials *credentials = [[SFOAuthCredentials alloc] initWithIdentifier:@"Sales Mate"
+ clientId:nil
+ encrypted:NO];
+ [credentials setRedirectUri:@"salesmate://success"];
+ _coordinator = [[SFOAuthCoordinator alloc] initWithCredentials:credentials];
+ [_coordinator setDelegate:self];
+
+ [[SFRestAPI sharedInstance] setCoordinator:_coordinator];
+
+ [_coordinator authenticate];
+
+#if TARGET_IPHONE_SIMULATOR
+ [[DCIntrospect sharedIntrospector] start];
+#endif
+
+ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
+ [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
+
+ return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application
+{
+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application
+{
+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application
+{
+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application
+{
+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application
+{
+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
+{
+ _deviceToken = deviceToken;
+}
+
+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
+{
+ NSLog(@"Remote Notifications Registration Error: %@", error);
+}
+
+#pragma mark - Tab Bar Controller
+
+- (CGSize)tabBarController:(NGTabBarController *)tabBarController sizeOfItemForViewController:(UIViewController *)viewController atIndex:(NSUInteger)index position:(NGTabBarPosition)position
+{
+ if (NGTabBarIsVertical(position)) {
+ return CGSizeMake(150.f, 150.f);
+ } else {
+ return CGSizeMake(60.f, 49.f);
+ }
+}
+
+#pragma mark - Salesforce Delegate
+
+- (void)oauthCoordinator:(SFOAuthCoordinator *)coordinator didBeginAuthenticationWithView:(UIWebView *)view
+{
+ LoginViewController *loginViewController = [[LoginViewController alloc] init];
+ [loginViewController setView:view];
+ [loginViewController setTitle:@"Login"];
+
+ _loginNavigationController = [[UINavigationController alloc] initWithRootViewController:loginViewController];
+ [_loginNavigationController setModalPresentationStyle:UIModalPresentationFormSheet];
+ UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
+ target:self
+ action:SEL(cancelAuth)];
+ [loginViewController.navigationItem setLeftBarButtonItem:cancelButton];
+
+ [self.window.rootViewController presentModalViewController:_loginNavigationController animated:YES];
+}
+
+- (void)oauthCoordinator:(SFOAuthCoordinator *)coordinator didFailWithError:(NSError *)error
+{
+
+}
+
+- (void)oauthCoordinatorDidAuthenticate:(SFOAuthCoordinator *)coordinator
+{
+ [[SFRestAPI sharedInstance] setCoordinator:_coordinator];
+ [_loginNavigationController dismissModalViewControllerAnimated:YES];
+
+ [DataHandler getOpportunities];
+ [DataHandler getLeads];
+ [DataHandler authenticateStreamingApi];
+
+ if (_deviceToken != nil) {
+ [DataHandler registerDeviceWithId:_deviceToken];
+ _deviceToken = nil;
+ }
+
+ [_tabBarController.authButton setTitle:@"Log Out" forState:UIControlStateNormal];
+ [_tabBarController.refreshButton setHidden:NO];
+}
+
+#pragma mark - Actions
+
+- (void)performLoginLogout
+{
+ [_coordinator revokeAuthentication];
+
+ [_tabBarController.authButton setTitle:@"Log In" forState:UIControlStateNormal];
+ [_tabBarController.refreshButton setHidden:YES];
+
+ [DataHandler removeAllData];
+ [_coordinator authenticate];
+}
+
+- (void)cancelAuth
+{
+ [_coordinator stopAuthentication];
+ [_loginNavigationController dismissModalViewControllerAnimated:YES];
+}
+
+@end
16 Sales Mate/Contacts/ContactDetailsViewController.h
@@ -0,0 +1,16 @@
+//
+// ContactDetailsViewController.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/16/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import <MessageUI/MessageUI.h>
+
+@interface ContactDetailsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, MFMailComposeViewControllerDelegate>
+
+@property (nonatomic, strong) id contactId;
+
+@end
321 Sales Mate/Contacts/ContactDetailsViewController.m
@@ -0,0 +1,321 @@
+//
+// ContactDetailsViewController.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/16/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "ContactDetailsViewController.h"
+#import "AccountDetailsViewController.h"
+
+#import <MapKit/MapKit.h>
+#import <MessageUI/MessageUI.h>
+
+#import "Contact.h"
+#import "Address.h"
+#import "SalesAccount.h"
+
+@interface ContactDetailsViewController ()
+{
+ CoreDataStore *_dataStore;
+
+ Contact *_contact;
+
+ UITableView *_contactTableView;
+}
+
+@end
+
+@implementation ContactDetailsViewController
+
+@synthesize contactId = _contactId;
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+ if (self) {
+ // Custom initialization
+ }
+ return self;
+}
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ _dataStore = [CoreDataStore createStore];
+ _contact = (Contact *)[_dataStore entityByObjectID:_contactId];
+
+ [self setTitle:_contact.name];
+ [self.view setBackgroundColor:[UIColor colorWithPatternImage:[UIImage imageNamed:@"strange_bullseyes.png"]]];
+
+ UIBarButtonItem *closeButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone
+ target:self
+ action:SEL(dismissModalViewControllerAnimated:)];
+ [self.navigationItem setRightBarButtonItem:closeButton];
+
+ UIImage *backgroundImage = [UIImage imageWithIdentifier:@"Contact Details Background" forSize:CGSizeMake(540.0f, 576.0f) andDrawingBlock:^{
+ //// General Declarations
+ CGContextRef context = UIGraphicsGetCurrentContext();
+
+ //// Color Declarations
+ UIColor* semiTransparentBlack = [UIColor colorWithRed: 0 green: 0 blue: 0 alpha: 0.5];
+ UIColor* descriptionBackdrop = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 0.35];
+
+ //// Shadow Declarations
+ CGColorRef mapInnerShadow = [UIColor blackColor].CGColor;
+ CGSize mapInnerShadowOffset = CGSizeMake(0, -0);
+ CGFloat mapInnerShadowBlurRadius = 10;
+ CGColorRef mapDropShadow = [UIColor blackColor].CGColor;
+ CGSize mapDropShadowOffset = CGSizeMake(3, 3);
+ CGFloat mapDropShadowBlurRadius = 5;
+
+
+ //// Rectangle Drawing
+ UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: CGRectMake(240, 0, 300, 300)];
+ [semiTransparentBlack setFill];
+ [rectanglePath fill];
+
+ ////// Rectangle Inner Shadow
+ CGRect rectangleBorderRect = CGRectInset([rectanglePath bounds], -mapInnerShadowBlurRadius, -mapInnerShadowBlurRadius);
+ rectangleBorderRect = CGRectOffset(rectangleBorderRect, -mapInnerShadowOffset.width, -mapInnerShadowOffset.height);
+ rectangleBorderRect = CGRectInset(CGRectUnion(rectangleBorderRect, [rectanglePath bounds]), -1, -1);
+
+ UIBezierPath* rectangleNegativePath = [UIBezierPath bezierPathWithRect: rectangleBorderRect];
+ [rectangleNegativePath appendPath: rectanglePath];
+ rectangleNegativePath.usesEvenOddFillRule = YES;
+
+ CGContextSaveGState(context);
+ {
+ CGFloat xOffset = mapInnerShadowOffset.width + round(rectangleBorderRect.size.width);
+ CGFloat yOffset = mapInnerShadowOffset.height;
+ CGContextSetShadowWithColor(context,
+ CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
+ mapInnerShadowBlurRadius,
+ mapInnerShadow);
+
+ [rectanglePath addClip];
+ CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(rectangleBorderRect.size.width), 0);
+ [rectangleNegativePath applyTransform: transform];
+ [[UIColor grayColor] setFill];
+ [rectangleNegativePath fill];
+ }
+ CGContextRestoreGState(context);
+
+
+
+
+ //// Rectangle 2 Drawing
+ UIBezierPath* rectangle2Path = [UIBezierPath bezierPathWithRect: CGRectMake(255, 15, 270, 270)];
+ CGContextSaveGState(context);
+ CGContextSetShadowWithColor(context, mapDropShadowOffset, mapDropShadowBlurRadius, mapDropShadow);
+ [[UIColor blackColor] setFill];
+ [rectangle2Path fill];
+ CGContextRestoreGState(context);
+
+
+
+ //// Rounded Rectangle Drawing
+ UIBezierPath* roundedRectanglePath = [UIBezierPath bezierPathWithRoundedRect: CGRectMake(10, 312, 520, 254) byRoundingCorners: UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii: CGSizeMake(4, 4)];
+ CGContextSaveGState(context);
+ CGContextSetShadowWithColor(context, mapDropShadowOffset, mapDropShadowBlurRadius, mapDropShadow);
+ [descriptionBackdrop setFill];
+ [roundedRectanglePath fill];
+ CGContextRestoreGState(context);
+
+ [[UIColor blackColor] setStroke];
+ roundedRectanglePath.lineWidth = 2;
+ [roundedRectanglePath stroke];
+ }];
+
+ UIImageView *backgroundImageView = [[UIImageView alloc] initWithImage:backgroundImage];
+ [self.view addSubview:backgroundImageView];
+
+ //Map View
+ MKMapView *mapView = [[MKMapView alloc] initWithFrame:CGRectMake(256.0f, 16.0f, 268.0f, 268.0f)];
+ [mapView setMapType:MKMapTypeStandard];
+ [mapView setRegion:MKCoordinateRegionMake(CLLocationCoordinate2DMake(UNBOX_DOUBLE(_contact.geoLatitude), UNBOX_DOUBLE(_contact.geoLongitude)), MKCoordinateSpanMake(0.1f, 0.1f))];
+ [self.view addSubview:mapView];
+
+ if (UNBOX_DOUBLE(_contact.geoLatitude) != 0 && UNBOX_DOUBLE(_contact.geoLongitude) != 0) {
+ MKPointAnnotation *pointAnnotation = [[MKPointAnnotation alloc] init];
+ [pointAnnotation setCoordinate:mapView.region.center];
+ [mapView addAnnotation:pointAnnotation];
+ }
+
+ _contactTableView = [[UITableView alloc] initWithFrame:CGRectMake(-50.0f, 0.0f, 290.0f, 300.0f) style:UITableViewStylePlain];
+ [_contactTableView setDelegate:self];
+ [_contactTableView setDataSource:self];
+ [_contactTableView setBackgroundView:nil];
+ [_contactTableView setBackgroundColor:[UIColor clearColor]];
+ [_contactTableView setSeparatorColor:[UIColor clearColor]];
+ [_contactTableView setScrollEnabled:NO];
+ [self.view addSubview:_contactTableView];
+
+ //Description
+ UITextView *descriptionView = [[UITextView alloc] initWithFrame:CGRectMake(12.0f, 313.0f, 516.0f, 252.0f)];
+ [descriptionView setBackgroundColor:[UIColor clearColor]];
+ [descriptionView setText:_contact.descriptionText];
+ [descriptionView setEditable:NO];
+ [self.view addSubview:descriptionView];
+
+ // Email
+ IBButton *emailButton = [IBButton softButtonWithTitle:@"Send Email" color:[UIColor colorWithHexString:@"458040"]];
+ [emailButton setFrame:CGRectMake(10.0f, 263.0f, 220.0f, 37.0f)];
+ [emailButton addTarget:self
+ action:SEL(showMailCompose)
+ forControlEvents:UIControlEventTouchUpInside];
+ [self.view addSubview:emailButton];
+ if (_contact.email == nil || ![MFMailComposeViewController canSendMail]) {
+ [emailButton setEnabled:NO];
+ }
+}
+
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+}
+
+- (void)viewDidUnload
+{
+ [super viewDidUnload];
+ // Release any retained subviews of the main view.
+}
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
+{
+ return YES;
+}
+
+#pragma mark - Table View Data Source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+ return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+ return 7;
+}
+
+- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ return 37.0f;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ static NSString *CellIdentifier = @"Cell Identifier";
+ UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+
+ if (cell == nil) {
+ cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
+ [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
+ [cell.detailTextLabel setNumberOfLines:2];
+ [cell setAccessoryType:UITableViewCellAccessoryNone];
+ }
+
+ switch (indexPath.row) {
+ case 0:
+ {
+ [cell.textLabel setText:@"Role"];
+ [cell.detailTextLabel setText:(_contact.role != nil ? _contact.title : @"Unknown")];
+ break;
+ }
+ case 1:
+ {
+ [cell.textLabel setText:@"Title"];
+ [cell.detailTextLabel setText:(_contact.title != nil ? _contact.title : @"Unknown")];
+ break;
+ }
+ case 2:
+ {
+ [cell.textLabel setText:@"Account"];
+ [cell.detailTextLabel setText:(_contact.account != nil ? ((SalesAccount *)_contact.account).name : @"Unknown")];
+ if (_contact.account != nil) {
+ [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
+ }
+ break;
+ }
+ case 3:
+ {
+ [cell.textLabel setText:@"Phone"];
+ [cell.detailTextLabel setText:(_contact.phone != nil ? _contact.phone : @"Unknown")];
+ break;
+ }
+ case 4:
+ {
+ [cell.textLabel setText:@"Home"];
+ [cell.detailTextLabel setText:(_contact.homePhone != nil ? _contact.homePhone : @"Unknown")];
+ break;
+ }
+ case 5:
+ {
+ [cell.textLabel setText:@"Mobile"];
+ [cell.detailTextLabel setText:(_contact.mobilePhone != nil ? _contact.mobilePhone : @"Unknown")];
+ }
+ case 6:
+ {
+ [cell.textLabel setText:@"Fax"];
+ [cell.detailTextLabel setText:(_contact.fax != nil ? _contact.fax : @"Unknown")];
+ break;
+ }
+ }
+
+ return cell;
+}
+
+#pragma mark - Table View Delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+ if (indexPath.row == 2) {
+ AccountDetailsViewController *accountDetailsView = [[AccountDetailsViewController alloc] init];
+ [accountDetailsView setAccountId:_contact.account.objectID];
+
+ [self.navigationController pushViewController:accountDetailsView animated:YES];
+ }
+}
+
+#pragma mark - Actions
+
+- (void)showMailCompose
+{
+ MFMailComposeViewController *mailController = [[MFMailComposeViewController alloc] init];
+ [mailController setModalPresentationStyle:UIModalPresentationFormSheet];
+ [mailController setToRecipients:[NSArray arrayWithObject:_contact.email]];
+ [mailController setMailComposeDelegate:self];
+ [self presentModalViewController:mailController animated:YES];
+}
+
+#pragma mark - Mail Delegate
+
+- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
+{
+ switch (result)
+ {
+ case MFMailComposeResultCancelled:
+ break;
+ case MFMailComposeResultSaved:
+ break;
+ case MFMailComposeResultSent:
+ break;
+ case MFMailComposeResultFailed:
+ break;
+
+ default:
+ {
+ [IBAlertView showAlertWithTitle:@"Compose Email"
+ message:@"Sending Failed – Unknown Error"
+ dismissTitle:@"Okay"
+ dismissBlock:nil];
+ break;
+ }
+ }
+
+ [controller dismissModalViewControllerAnimated:YES];
+}
+
+@end
29 Sales Mate/DataHandler.h
@@ -0,0 +1,29 @@
+//
+// DataHandler.h
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/2/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "SFRestAPI.h"
+#import "RestKit.h"
+#import "StreamingApiClient.h"
+
+@interface DataHandler : NSObject <SFRestDelegate, StreamingApiClientDelegate, RKRequestDelegate>
+
+@property (retain) StreamingApiClient *client;
+@property (retain) NSString *stateDescription;
+
+
++ (DataHandler *)sharedInstance;
+
++ (void)getOpportunities;
++ (void)getLeads;
++ (void)authenticateStreamingApi;
++ (void)unsubscribeFrom:(NSString *)subscription;
++ (void)registerDeviceWithId:(id)deviceId;
++ (void)removeAllData;
+
+@end
531 Sales Mate/DataHandler.m
@@ -0,0 +1,531 @@
+//
+// DataHandler.m
+// Sales Mate
+//
+// Created by Thomas Hajcak Jr on 7/2/12.
+// Copyright (c) 2012 Mavens Consulting, Inc. All rights reserved.
+//
+
+#import "DataHandler.h"
+#import "SFRestAPI+Blocks.h"
+
+#import "SalesAccount.h"
+#import "Contact.h"
+#import "Address.h"
+#import "Opportunity.h"
+#import "Lead.h"
+
+#import "UrlConnectionDelegate.h"
+
+#import "SBJsonParser.h"
+#import "NSObject+SBJson.h"
+
+#import "RestKit.h"
+
+#import <MapKit/MapKit.h>
+
+@implementation DataHandler
+
+@synthesize client = _client;
+@synthesize stateDescription = _stateDescription;
+
++ (DataHandler *)sharedInstance
+{
+ __strong static id _sharedObject = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ _sharedObject = [[self alloc] init];
+ });
+
+ // [_sharedObject getNewManagedObjectContext];
+
+ return _sharedObject;
+}
+
+- (id)init
+{
+ dispatch_async(dispatch_get_global_queue(0, 0), ^{
+
+ });
+
+ return self;
+}
+
+#pragma mark - Public Methods
+
++ (void)getOpportunities
+{
+ [[DataHandler sharedInstance] getOpportunities];
+}
+
++ (void)getLeads
+{
+ [[DataHandler sharedInstance] getLeads];
+}
+
++ (void)authenticateStreamingApi
+{
+ [[DataHandler sharedInstance] authenticateStreamingApi];
+}
+
++ (void)unsubscribeFrom:(NSString *)subscription
+{
+ [[DataHandler sharedInstance] unsubscribeFrom:subscription];
+}
+
++ (void)registerDeviceWithId:(id)deviceId
+{
+ [[DataHandler sharedInstance] registerDeviceWithId:deviceId];
+}
+
++ (void)removeAllData
+{
+ [[DataHandler sharedInstance] removeAllData];
+}
+
+#pragma mark - Get Data
+
+- (void)getOpportunities
+{
+ // Field Order: Contact Role, Opportunity, Opportunity.Account, Opportunity.Contact, Opportunity.Contact.Account
+ NSString *queryString = [NSString stringWithFormat:@"Select %@, %@, %@, %@, %@ from OpportunityContactRole ocr",
+ @"Role",
+ @"ocr.Opportunity.Id, ocr.Opportunity.Amount, ocr.Opportunity.CloseDate, ocr.Opportunity.Description, ocr.Opportunity.ExpectedRevenue, ocr.Opportunity.LeadSource, ocr.Opportunity.NextStep, ocr.Opportunity.Name, ocr.Opportunity.Probability, ocr.Opportunity.TotalOpportunityQuantity, ocr.Opportunity.StageName, ocr.Opportunity.Type",
+ @"ocr.Opportunity.Account.Id, ocr.Opportunity.Account.AnnualRevenue, ocr.Opportunity.Account.Description, ocr.Opportunity.Account.Fax, ocr.Opportunity.Account.Latitude__c, ocr.Opportunity.Account.Longitude__c, ocr.Opportunity.Account.Industry, ocr.Opportunity.Account.Name, ocr.Opportunity.Account.Phone, ocr.Opportunity.Account.Type, ocr.Opportunity.Account.Website",
+ @"ocr.Contact.Id, ocr.Contact.Description, ocr.Contact.Email, ocr.Contact.Fax, ocr.Contact.HomePhone, ocr.Contact.MobilePhone, ocr.Contact.Name, ocr.Contact.Phone, ocr.Contact.Latitude__c, ocr.Contact.Longitude__c, ocr.Contact.Title",
+ @"ocr.Contact.Account.Id, ocr.Contact.Account.AnnualRevenue, ocr.Contact.Account.Description, ocr.Contact.Account.Fax, ocr.Contact.Account.Latitude__c, ocr.Contact.Account.Longitude__c, ocr.Contact.Account.Industry, ocr.Contact.Account.Name, ocr.Contact.Account.Phone, ocr.Contact.Account.Type, ocr.Contact.Account.Website"];
+ SFRestRequest *request = [[SFRestAPI sharedInstance] requestForQuery:queryString];
+ [[SFRestAPI sharedInstance] sendRESTRequest:request
+ failBlock:^(NSError *error){
+
+ }
+ completeBlock:^(id dictionary){
+ dispatch_async(dispatch_get_global_queue(0, 0), ^{
+ CoreDataStore *dataStore = [CoreDataStore createStore];
+
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setDateFormat:@"yyyy-MM-dd"];
+
+ for (NSDictionary *record in [dictionary objectForKey:@"records"]) {
+
+ // Opportunity Setup
+ NSDictionary *opportunityDictionary = [record objectForKey:@"Opportunity"];
+ Opportunity *opportunity = [Opportunity firstWithKey:@"id"
+ value:[opportunityDictionary valueForKey:@"Id"]
+ inStore:dataStore];
+ if (opportunity == nil) {
+ opportunity = [Opportunity createInStore:dataStore];
+ [opportunity setId:[opportunityDictionary valueForKey:@"Id"]];
+ }
+
+ // Required Fields
+ [opportunity setName:[opportunityDictionary valueForKey:@"Name"]];
+ [opportunity setStage:[opportunityDictionary valueForKey:@"StageName"]];
+ [opportunity setCloseDate:[dateFormatter dateFromString:[opportunityDictionary valueForKey:@"CloseDate"]]];
+
+ // Optional Fields
+ if ([opportunityDictionary valueForKey:@"Description"] != [NSNull null]) {
+ [opportunity setDescriptionText:[opportunityDictionary valueForKey:@"Description"]];
+ }
+ if ([opportunityDictionary valueForKey:@"LeadSource"] != [NSNull null]) {
+ [opportunity setLeadSource:[opportunityDictionary valueForKey:@"LeadSource"]];
+ }
+ if ([opportunityDictionary valueForKey:@"NextStep"] != [NSNull null]) {
+ [opportunity setNextStep:[opportunityDictionary valueForKey:@"NextStep"]];
+ }
+ if ([opportunityDictionary valueForKey:@"Type"] != [NSNull null]) {
+ [opportunity setType:[opportunityDictionary valueForKey:@"Type"]];
+ }
+
+ if ([opportunityDictionary valueForKey:@"Amount"] != [NSNull null]) {
+ [opportunity setAmount:BOX_INT([[opportunityDictionary valueForKey:@"Amount"] integerValue])];
+ }
+ if ([opportunityDictionary valueForKey:@"ExpectedRevenue"] != [NSNull null]) {
+ [opportunity setExpectedRevenue:BOX_INT([[opportunityDictionary valueForKey:@"ExpectedRevenue"] integerValue])];
+ }
+ if ([opportunityDictionary valueForKey:@"Probability"] != [NSNull null]) {
+ [opportunity setProbability:BOX_INT([[opportunityDictionary valueForKey:@"Probability"] integerValue])];
+ }
+
+
+ // Opportunity Account Setup
+ NSDictionary *accountDictionary = [opportunityDictionary valueForKey:@"Account"];
+ SalesAccount *account = [SalesAccount firstWithKey:@"id"
+ value:[accountDictionary valueForKey:@"Id"]
+ inStore:dataStore];
+ if (account == nil) {
+ account = [SalesAccount createInStore:dataStore];
+ [account setId:[accountDictionary valueForKey:@"Id"]];
+ }
+
+ [opportunity setAccount:account];
+
+ if ([accountDictionary valueForKey:@"AnnualRevenue"] != [NSNull null]) {
+ [account setAnnualRevenue:BOX_INT([[accountDictionary valueForKey:@"AnnualRevenue"] integerValue])];
+ }
+ if ([accountDictionary valueForKey:@"Description"] != [NSNull null]) {
+ [account setDescriptionText:[accountDictionary valueForKey:@"Description"]];
+ }
+ if ([accountDictionary valueForKey:@"Fax"] != [NSNull null]) {
+ [account setFax:[accountDictionary valueForKey:@"Fax"]];
+ }
+ if ([accountDictionary valueForKey:@"Latitude__c"] != [NSNull null]) {
+ [account setGeoLatitude:BOX_DOUBLE([[accountDictionary valueForKey:@"Latitude__c"] doubleValue])];
+ }
+ if ([accountDictionary valueForKey:@"Longitude__c"] != [NSNull null]) {
+ [account setGeoLongitude:BOX_DOUBLE([[accountDictionary valueForKey:@"Longitude__c"] doubleValue])];
+ }
+ if ([accountDictionary valueForKey:@"Industry"] != [NSNull null]) {
+ [account setIndustry:[accountDictionary valueForKey:@"Industry"]];
+ }
+ if ([accountDictionary valueForKey:@"Name"] != [NSNull null]) {
+ [account setName:[accountDictionary valueForKey:@"Name"]];
+ }
+ if ([accountDictionary valueForKey:@"Phone"] != [NSNull null]) {
+ [account setPhone:[accountDictionary valueForKey:@"Phone"]];
+ }
+ if ([accountDictionary valueForKey:@"Type"] != [NSNull null]) {
+ [account setType:[accountDictionary valueForKey:@"Type"]];
+ }
+ if ([accountDictionary valueForKey:@"Website"] != [NSNull null]) {
+ [account setWebsite:[accountDictionary valueForKey:@"Website"]];
+ }
+
+
+ // Contact Setup
+ NSDictionary *contactDictionary = [record valueForKey:@"Contact"];
+ Contact *contact = [Contact firstWithKey:@"id"
+ value:[contactDictionary valueForKey:@"Id"]
+ inStore:dataStore];
+ if (contact == nil) {
+ contact = [Contact createInStore:dataStore];
+ [contact setId:[contactDictionary valueForKey:@"Id"]];
+ [opportunity addContactsObject:contact];
+ }
+
+ if ([contactDictionary valueForKey:@"Name"] != [NSNull null]) {
+ [contact setName:[contactDictionary valueForKey:@"Name"]];
+ }
+ if ([contactDictionary valueForKey:@"Description"] != [NSNull null]) {
+ [contact setDescriptionText:[contactDictionary valueForKey:@"Description"]];
+ }
+ if ([contactDictionary valueForKey:@"Email"] != [NSNull null]) {
+ [contact setEmail:[contactDictionary valueForKey:@"Email"]];
+ }
+ if ([contactDictionary valueForKey:@"Fax"] != [NSNull null]) {
+ [contact setFax:[contactDictionary valueForKey:@"Fax"]];
+ }
+ if ([contactDictionary valueForKey:@"HomePhone"] != [NSNull null]) {
+ [contact setHomePhone:[contactDictionary valueForKey:@"HomePhone"]];
+ }
+ if ([contactDictionary valueForKey:@"MobilePhone"] != [NSNull null]) {
+ [contact setMobilePhone:[contactDictionary valueForKey:@"MobilePhone"]];
+ }
+ if ([contactDictionary valueForKey:@"Phone"] != [NSNull null]) {
+ [contact setPhone:[contactDictionary valueForKey:@"Phone"]];
+ }
+ if ([record valueForKey:@"Role"] != [NSNull null]) {
+ [contact setRole:[record valueForKey:@"Role"]];
+ }
+ if ([contactDictionary valueForKey:@"Latitude__c"] != [NSNull null]) {
+ [contact setGeoLatitude:BOX_DOUBLE([[contactDictionary valueForKey:@"Latitude__c"] doubleValue])];
+ }
+ if ([contactDictionary valueForKey:@"Longitude__c"] != [NSNull null]) {
+ [contact setGeoLongitude:BOX_DOUBLE([[contactDictionary valueForKey:@"Longitude__c"] doubleValue])];
+ }
+ if ([contactDictionary valueForKey:@"Title"] != [NSNull null]) {
+ [contact setTitle:[contactDictionary valueForKey:@"Title"]];
+ }
+
+
+ // Contact Account Setup
+ NSDictionary *contactAccountDictionary = [contactDictionary valueForKey:@"Account"];
+ SalesAccount *contactAccount = [SalesAccount firstWithKey:@"id"
+ value:[contactAccountDictionary valueForKey:@"Id"]</