├── Activity Tracker.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── kgerstner.xcuserdatad │ └── xcschemes │ ├── Activity Tracker.xcscheme │ └── xcschememanagement.plist ├── Activity Tracker.xcworkspace └── contents.xcworkspacedata ├── Activity Tracker ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon-120.png │ │ ├── icon-57.png │ │ └── icon-57@2x.png │ ├── LaunchImage.launchimage │ │ └── Contents.json │ ├── icon-action-checkmark.imageset │ │ ├── Contents.json │ │ ├── icon-action-checkmark.png │ │ └── icon-action-checkmark@2x.png │ ├── icon-action-flag-disabled.imageset │ │ ├── Contents.json │ │ ├── icon-action-flag-disabled.png │ │ └── icon-action-flag-disabled@2x.png │ ├── icon-action-flag.imageset │ │ ├── Contents.json │ │ ├── icon-action-flag.png │ │ └── icon-action-flag@2x.png │ ├── icon-action-note-disabled.imageset │ │ ├── Contents.json │ │ ├── icon-action-note-disabled.png │ │ └── icon-action-note-disabled@2x.png │ ├── icon-action-note.imageset │ │ ├── Contents.json │ │ ├── icon-action-note.png │ │ └── icon-action-note@2x.png │ ├── icon-action-phone-disabled.imageset │ │ ├── Contents.json │ │ ├── icon-action-phone-disabled.png │ │ └── icon-action-phone-disabled@2x.png │ ├── icon-action-phone.imageset │ │ ├── Contents.json │ │ ├── icon-action-phone.png │ │ └── icon-action-phone@2x.png │ ├── icon-activity-appt.imageset │ │ ├── Contents.json │ │ ├── icon-activity-appt.png │ │ └── icon-activity-appt@2x.png │ ├── icon-activity-check.imageset │ │ ├── Contents.json │ │ ├── icon-activity-check.png │ │ └── icon-activity-check@2x.png │ ├── icon-activity-generic.imageset │ │ ├── Contents.json │ │ ├── icon-activity-generic.png │ │ └── icon-activity-generic@2x.png │ ├── icon-activity-note.imageset │ │ ├── Contents.json │ │ ├── icon-activity-note.png │ │ └── icon-activity-note@2x.png │ ├── icon-activity-phone.imageset │ │ ├── Contents.json │ │ ├── icon-activity-phone.png │ │ └── icon-activity-phone@2x.png │ ├── icon-back.imageset │ │ ├── Contents.json │ │ ├── icon-back.png │ │ └── icon-back@2x.png │ ├── icon-clear.imageset │ │ ├── Contents.json │ │ └── icon-clear.png │ ├── icon-contact-email.imageset │ │ ├── Contents.json │ │ ├── icon-contact-email.png │ │ └── icon-contact-email@2x.png │ ├── icon-contact-map.imageset │ │ ├── Contents.json │ │ ├── icon-contact-map.png │ │ └── icon-contact-map@2x.png │ ├── icon-contact-phone.imageset │ │ ├── Contents.json │ │ ├── icon-contact-phone.png │ │ └── icon-contact-phone@2x.png │ ├── icon-contact.imageset │ │ ├── Contents.json │ │ ├── icon-contact.png │ │ └── icon-contact@2x.png │ ├── icon-search.imageset │ │ ├── Contents.json │ │ └── icon-search.png │ ├── icon-settings.imageset │ │ ├── Contents.json │ │ ├── icon-settings.png │ │ └── icon-settings@2x.png │ └── spinner.imageset │ │ ├── Contents.json │ │ ├── spinner.png │ │ └── spinner@2x.png ├── Base.lproj │ └── LaunchScreen.storyboard ├── CRMConnector │ ├── CRMClient.swift │ └── LocalStorage.swift ├── ClassCategories │ └── String+StringFormatting.swift ├── ColorsAndFonts.swift ├── Info.plist ├── Objects │ ├── CRMActivity.swift │ ├── CRMContact.swift │ └── CRMObject.swift ├── SupportingFiles │ └── BridgingHeader.h └── UI │ ├── HomeView.xib │ ├── HomeViewController.swift │ ├── NewActivityView.xib │ ├── NewActivityViewController.swift │ ├── ObjectDetailsView.xib │ ├── ObjectDetailsViewController.swift │ ├── SettingsView.xib │ └── SettingsViewController.swift ├── Details.png ├── Home.png ├── License.txt ├── NewActivity.png ├── PodFile ├── Podfile.lock ├── README.md └── Settings.png /Activity Tracker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 274FDFA21CC51333000D26BD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274FDFA11CC51333000D26BD /* AppDelegate.swift */; }; 11 | 274FDFA91CC51333000D26BD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 274FDFA81CC51333000D26BD /* Assets.xcassets */; }; 12 | 274FDFAC1CC51333000D26BD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 274FDFAA1CC51333000D26BD /* LaunchScreen.storyboard */; }; 13 | 274FDFB61CC80AA9000D26BD /* CRMClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 274FDFB51CC80AA9000D26BD /* CRMClient.swift */; }; 14 | 5EF1A69C599AA1E0D65F7CB4 /* Pods_Activity_Tracker.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0BF168065A22930CA16F1F8B /* Pods_Activity_Tracker.framework */; }; 15 | BC7726F21CC54383004A23FC /* String+StringFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7726F11CC54383004A23FC /* String+StringFormatting.swift */; }; 16 | BC7726FE1CC57CE1004A23FC /* CRMActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7726FB1CC57CE1004A23FC /* CRMActivity.swift */; }; 17 | BC7726FF1CC57CE1004A23FC /* CRMContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7726FC1CC57CE1004A23FC /* CRMContact.swift */; }; 18 | BC7727001CC57CE1004A23FC /* CRMObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7726FD1CC57CE1004A23FC /* CRMObject.swift */; }; 19 | BC7727031CC57F3A004A23FC /* LocalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7727021CC57F3A004A23FC /* LocalStorage.swift */; }; 20 | BC7727091CC58B06004A23FC /* HomeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC7727051CC58B06004A23FC /* HomeView.xib */; }; 21 | BC77270A1CC58B06004A23FC /* NewActivityView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC7727061CC58B06004A23FC /* NewActivityView.xib */; }; 22 | BC77270B1CC58B06004A23FC /* ObjectDetailsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC7727071CC58B06004A23FC /* ObjectDetailsView.xib */; }; 23 | BC77270C1CC58B06004A23FC /* SettingsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = BC7727081CC58B06004A23FC /* SettingsView.xib */; }; 24 | BC77270E1CC58B41004A23FC /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC77270D1CC58B41004A23FC /* HomeViewController.swift */; }; 25 | BC7727101CC58F2B004A23FC /* NewActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC77270F1CC58F2B004A23FC /* NewActivityViewController.swift */; }; 26 | BC7727121CC58F40004A23FC /* ObjectDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7727111CC58F40004A23FC /* ObjectDetailsViewController.swift */; }; 27 | BC7727141CC58F52004A23FC /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7727131CC58F52004A23FC /* SettingsViewController.swift */; }; 28 | BC7727161CC6A95A004A23FC /* ColorsAndFonts.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC7727151CC6A95A004A23FC /* ColorsAndFonts.swift */; }; 29 | /* End PBXBuildFile section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 0BF168065A22930CA16F1F8B /* Pods_Activity_Tracker.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Activity_Tracker.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 274FDF9E1CC51333000D26BD /* Activity Tracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Activity Tracker.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 274FDFA11CC51333000D26BD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 35 | 274FDFA81CC51333000D26BD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 274FDFAB1CC51333000D26BD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37 | 274FDFAD1CC51333000D26BD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 38 | 274FDFB51CC80AA9000D26BD /* CRMClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CRMClient.swift; path = CRMConnector/CRMClient.swift; sourceTree = ""; }; 39 | 274FE05B1CC81C73000D26BD /* BridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; 40 | BC7726F11CC54383004A23FC /* String+StringFormatting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+StringFormatting.swift"; path = "ClassCategories/String+StringFormatting.swift"; sourceTree = ""; }; 41 | BC7726FB1CC57CE1004A23FC /* CRMActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CRMActivity.swift; path = Objects/CRMActivity.swift; sourceTree = ""; }; 42 | BC7726FC1CC57CE1004A23FC /* CRMContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CRMContact.swift; path = Objects/CRMContact.swift; sourceTree = ""; }; 43 | BC7726FD1CC57CE1004A23FC /* CRMObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CRMObject.swift; path = Objects/CRMObject.swift; sourceTree = ""; }; 44 | BC7727021CC57F3A004A23FC /* LocalStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LocalStorage.swift; path = CRMConnector/LocalStorage.swift; sourceTree = ""; }; 45 | BC7727051CC58B06004A23FC /* HomeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = HomeView.xib; sourceTree = ""; }; 46 | BC7727061CC58B06004A23FC /* NewActivityView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = NewActivityView.xib; sourceTree = ""; }; 47 | BC7727071CC58B06004A23FC /* ObjectDetailsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ObjectDetailsView.xib; sourceTree = ""; }; 48 | BC7727081CC58B06004A23FC /* SettingsView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = SettingsView.xib; sourceTree = ""; }; 49 | BC77270D1CC58B41004A23FC /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; 50 | BC77270F1CC58F2B004A23FC /* NewActivityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewActivityViewController.swift; sourceTree = ""; }; 51 | BC7727111CC58F40004A23FC /* ObjectDetailsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectDetailsViewController.swift; sourceTree = ""; }; 52 | BC7727131CC58F52004A23FC /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; 53 | BC7727151CC6A95A004A23FC /* ColorsAndFonts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorsAndFonts.swift; sourceTree = ""; }; 54 | C2A182384057B48892B6166A /* Pods-Activity Tracker.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Activity Tracker.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Activity Tracker/Pods-Activity Tracker.debug.xcconfig"; sourceTree = ""; }; 55 | D83F72DF4E0D808084499DEA /* Pods-Activity Tracker.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Activity Tracker.release.xcconfig"; path = "Pods/Target Support Files/Pods-Activity Tracker/Pods-Activity Tracker.release.xcconfig"; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 274FDF9B1CC51333000D26BD /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | 5EF1A69C599AA1E0D65F7CB4 /* Pods_Activity_Tracker.framework in Frameworks */, 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | /* End PBXFrameworksBuildPhase section */ 68 | 69 | /* Begin PBXGroup section */ 70 | 274FDF951CC51333000D26BD = { 71 | isa = PBXGroup; 72 | children = ( 73 | 274FDFA01CC51333000D26BD /* Activity Tracker */, 74 | 274FE05A1CC81C46000D26BD /* SupportingFiles */, 75 | 274FDF9F1CC51333000D26BD /* Products */, 76 | 67E95442188DC8FAE9D2E705 /* Pods */, 77 | 6045BCEB130B37DE102B7DBB /* Frameworks */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 274FDF9F1CC51333000D26BD /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 274FDF9E1CC51333000D26BD /* Activity Tracker.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 274FDFA01CC51333000D26BD /* Activity Tracker */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | BC7726EE1CC5358D004A23FC /* ClassCategories */, 93 | BC7727011CC57F20004A23FC /* CRMConnector */, 94 | BC7726F31CC54873004A23FC /* Objects */, 95 | BC7727041CC58B06004A23FC /* UI */, 96 | 274FDFA11CC51333000D26BD /* AppDelegate.swift */, 97 | BC7727151CC6A95A004A23FC /* ColorsAndFonts.swift */, 98 | 274FDFA81CC51333000D26BD /* Assets.xcassets */, 99 | 274FDFAA1CC51333000D26BD /* LaunchScreen.storyboard */, 100 | 274FDFAD1CC51333000D26BD /* Info.plist */, 101 | ); 102 | path = "Activity Tracker"; 103 | sourceTree = ""; 104 | }; 105 | 274FE05A1CC81C46000D26BD /* SupportingFiles */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 274FE05B1CC81C73000D26BD /* BridgingHeader.h */, 109 | ); 110 | name = SupportingFiles; 111 | path = "Activity Tracker/SupportingFiles"; 112 | sourceTree = ""; 113 | }; 114 | 6045BCEB130B37DE102B7DBB /* Frameworks */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 0BF168065A22930CA16F1F8B /* Pods_Activity_Tracker.framework */, 118 | ); 119 | name = Frameworks; 120 | sourceTree = ""; 121 | }; 122 | 67E95442188DC8FAE9D2E705 /* Pods */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | C2A182384057B48892B6166A /* Pods-Activity Tracker.debug.xcconfig */, 126 | D83F72DF4E0D808084499DEA /* Pods-Activity Tracker.release.xcconfig */, 127 | ); 128 | name = Pods; 129 | sourceTree = ""; 130 | }; 131 | BC7726EE1CC5358D004A23FC /* ClassCategories */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | BC7726F11CC54383004A23FC /* String+StringFormatting.swift */, 135 | ); 136 | name = ClassCategories; 137 | sourceTree = ""; 138 | }; 139 | BC7726F31CC54873004A23FC /* Objects */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | BC7726FB1CC57CE1004A23FC /* CRMActivity.swift */, 143 | BC7726FC1CC57CE1004A23FC /* CRMContact.swift */, 144 | BC7726FD1CC57CE1004A23FC /* CRMObject.swift */, 145 | ); 146 | name = Objects; 147 | sourceTree = ""; 148 | }; 149 | BC7727011CC57F20004A23FC /* CRMConnector */ = { 150 | isa = PBXGroup; 151 | children = ( 152 | BC7727021CC57F3A004A23FC /* LocalStorage.swift */, 153 | 274FDFB51CC80AA9000D26BD /* CRMClient.swift */, 154 | ); 155 | name = CRMConnector; 156 | sourceTree = ""; 157 | }; 158 | BC7727041CC58B06004A23FC /* UI */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | BC77270D1CC58B41004A23FC /* HomeViewController.swift */, 162 | BC7727051CC58B06004A23FC /* HomeView.xib */, 163 | BC77270F1CC58F2B004A23FC /* NewActivityViewController.swift */, 164 | BC7727061CC58B06004A23FC /* NewActivityView.xib */, 165 | BC7727111CC58F40004A23FC /* ObjectDetailsViewController.swift */, 166 | BC7727071CC58B06004A23FC /* ObjectDetailsView.xib */, 167 | BC7727131CC58F52004A23FC /* SettingsViewController.swift */, 168 | BC7727081CC58B06004A23FC /* SettingsView.xib */, 169 | ); 170 | path = UI; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXGroup section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | 274FDF9D1CC51333000D26BD /* Activity Tracker */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 274FDFB01CC51333000D26BD /* Build configuration list for PBXNativeTarget "Activity Tracker" */; 179 | buildPhases = ( 180 | 0F4F5C7778D180AABBE93D13 /* Check Pods Manifest.lock */, 181 | 274FDF9A1CC51333000D26BD /* Sources */, 182 | 274FDF9B1CC51333000D26BD /* Frameworks */, 183 | 274FDF9C1CC51333000D26BD /* Resources */, 184 | 8291374A0A99E0F68D4482EB /* Embed Pods Frameworks */, 185 | 1663B54DD1EDE3D17B3D9609 /* Copy Pods Resources */, 186 | ); 187 | buildRules = ( 188 | ); 189 | dependencies = ( 190 | ); 191 | name = "Activity Tracker"; 192 | productName = "Activity Tracker"; 193 | productReference = 274FDF9E1CC51333000D26BD /* Activity Tracker.app */; 194 | productType = "com.apple.product-type.application"; 195 | }; 196 | /* End PBXNativeTarget section */ 197 | 198 | /* Begin PBXProject section */ 199 | 274FDF961CC51333000D26BD /* Project object */ = { 200 | isa = PBXProject; 201 | attributes = { 202 | LastSwiftUpdateCheck = 0720; 203 | LastUpgradeCheck = 0720; 204 | ORGANIZATIONNAME = Microsoft; 205 | TargetAttributes = { 206 | 274FDF9D1CC51333000D26BD = { 207 | CreatedOnToolsVersion = 7.2; 208 | DevelopmentTeam = ZV9NBBH9QE; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = 274FDF991CC51333000D26BD /* Build configuration list for PBXProject "Activity Tracker" */; 213 | compatibilityVersion = "Xcode 3.2"; 214 | developmentRegion = English; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | Base, 219 | ); 220 | mainGroup = 274FDF951CC51333000D26BD; 221 | productRefGroup = 274FDF9F1CC51333000D26BD /* Products */; 222 | projectDirPath = ""; 223 | projectRoot = ""; 224 | targets = ( 225 | 274FDF9D1CC51333000D26BD /* Activity Tracker */, 226 | ); 227 | }; 228 | /* End PBXProject section */ 229 | 230 | /* Begin PBXResourcesBuildPhase section */ 231 | 274FDF9C1CC51333000D26BD /* Resources */ = { 232 | isa = PBXResourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | BC7727091CC58B06004A23FC /* HomeView.xib in Resources */, 236 | BC77270B1CC58B06004A23FC /* ObjectDetailsView.xib in Resources */, 237 | 274FDFAC1CC51333000D26BD /* LaunchScreen.storyboard in Resources */, 238 | BC77270A1CC58B06004A23FC /* NewActivityView.xib in Resources */, 239 | 274FDFA91CC51333000D26BD /* Assets.xcassets in Resources */, 240 | BC77270C1CC58B06004A23FC /* SettingsView.xib in Resources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXResourcesBuildPhase section */ 245 | 246 | /* Begin PBXShellScriptBuildPhase section */ 247 | 0F4F5C7778D180AABBE93D13 /* Check Pods Manifest.lock */ = { 248 | isa = PBXShellScriptBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | ); 252 | inputPaths = ( 253 | ); 254 | name = "Check Pods Manifest.lock"; 255 | outputPaths = ( 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | shellPath = /bin/sh; 259 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 260 | showEnvVarsInLog = 0; 261 | }; 262 | 1663B54DD1EDE3D17B3D9609 /* Copy Pods Resources */ = { 263 | isa = PBXShellScriptBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | ); 267 | inputPaths = ( 268 | ); 269 | name = "Copy Pods Resources"; 270 | outputPaths = ( 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | shellPath = /bin/sh; 274 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Activity Tracker/Pods-Activity Tracker-resources.sh\"\n"; 275 | showEnvVarsInLog = 0; 276 | }; 277 | 8291374A0A99E0F68D4482EB /* Embed Pods Frameworks */ = { 278 | isa = PBXShellScriptBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ); 282 | inputPaths = ( 283 | ); 284 | name = "Embed Pods Frameworks"; 285 | outputPaths = ( 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | shellPath = /bin/sh; 289 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Activity Tracker/Pods-Activity Tracker-frameworks.sh\"\n"; 290 | showEnvVarsInLog = 0; 291 | }; 292 | /* End PBXShellScriptBuildPhase section */ 293 | 294 | /* Begin PBXSourcesBuildPhase section */ 295 | 274FDF9A1CC51333000D26BD /* Sources */ = { 296 | isa = PBXSourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | BC7726FF1CC57CE1004A23FC /* CRMContact.swift in Sources */, 300 | BC7727161CC6A95A004A23FC /* ColorsAndFonts.swift in Sources */, 301 | BC7727141CC58F52004A23FC /* SettingsViewController.swift in Sources */, 302 | 274FDFB61CC80AA9000D26BD /* CRMClient.swift in Sources */, 303 | BC7727101CC58F2B004A23FC /* NewActivityViewController.swift in Sources */, 304 | BC7726F21CC54383004A23FC /* String+StringFormatting.swift in Sources */, 305 | BC77270E1CC58B41004A23FC /* HomeViewController.swift in Sources */, 306 | BC7727001CC57CE1004A23FC /* CRMObject.swift in Sources */, 307 | 274FDFA21CC51333000D26BD /* AppDelegate.swift in Sources */, 308 | BC7727031CC57F3A004A23FC /* LocalStorage.swift in Sources */, 309 | BC7727121CC58F40004A23FC /* ObjectDetailsViewController.swift in Sources */, 310 | BC7726FE1CC57CE1004A23FC /* CRMActivity.swift in Sources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXSourcesBuildPhase section */ 315 | 316 | /* Begin PBXVariantGroup section */ 317 | 274FDFAA1CC51333000D26BD /* LaunchScreen.storyboard */ = { 318 | isa = PBXVariantGroup; 319 | children = ( 320 | 274FDFAB1CC51333000D26BD /* Base */, 321 | ); 322 | name = LaunchScreen.storyboard; 323 | sourceTree = ""; 324 | }; 325 | /* End PBXVariantGroup section */ 326 | 327 | /* Begin XCBuildConfiguration section */ 328 | 274FDFAE1CC51333000D26BD /* Debug */ = { 329 | isa = XCBuildConfiguration; 330 | buildSettings = { 331 | ALWAYS_SEARCH_USER_PATHS = NO; 332 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 333 | CLANG_CXX_LIBRARY = "libc++"; 334 | CLANG_ENABLE_MODULES = YES; 335 | CLANG_ENABLE_OBJC_ARC = YES; 336 | CLANG_WARN_BOOL_CONVERSION = YES; 337 | CLANG_WARN_CONSTANT_CONVERSION = YES; 338 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_UNREACHABLE_CODE = YES; 344 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 345 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 346 | COPY_PHASE_STRIP = NO; 347 | DEBUG_INFORMATION_FORMAT = dwarf; 348 | ENABLE_STRICT_OBJC_MSGSEND = YES; 349 | ENABLE_TESTABILITY = YES; 350 | GCC_C_LANGUAGE_STANDARD = gnu99; 351 | GCC_DYNAMIC_NO_PIC = NO; 352 | GCC_NO_COMMON_BLOCKS = YES; 353 | GCC_OPTIMIZATION_LEVEL = 0; 354 | GCC_PREPROCESSOR_DEFINITIONS = ( 355 | "DEBUG=1", 356 | "$(inherited)", 357 | ); 358 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 359 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 360 | GCC_WARN_UNDECLARED_SELECTOR = YES; 361 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 362 | GCC_WARN_UNUSED_FUNCTION = YES; 363 | GCC_WARN_UNUSED_VARIABLE = YES; 364 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 365 | MTL_ENABLE_DEBUG_INFO = YES; 366 | ONLY_ACTIVE_ARCH = YES; 367 | SDKROOT = iphoneos; 368 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 369 | }; 370 | name = Debug; 371 | }; 372 | 274FDFAF1CC51333000D26BD /* Release */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | ALWAYS_SEARCH_USER_PATHS = NO; 376 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 377 | CLANG_CXX_LIBRARY = "libc++"; 378 | CLANG_ENABLE_MODULES = YES; 379 | CLANG_ENABLE_OBJC_ARC = YES; 380 | CLANG_WARN_BOOL_CONVERSION = YES; 381 | CLANG_WARN_CONSTANT_CONVERSION = YES; 382 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INT_CONVERSION = YES; 386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 387 | CLANG_WARN_UNREACHABLE_CODE = YES; 388 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 389 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 390 | COPY_PHASE_STRIP = NO; 391 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 392 | ENABLE_NS_ASSERTIONS = NO; 393 | ENABLE_STRICT_OBJC_MSGSEND = YES; 394 | GCC_C_LANGUAGE_STANDARD = gnu99; 395 | GCC_NO_COMMON_BLOCKS = YES; 396 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 397 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 398 | GCC_WARN_UNDECLARED_SELECTOR = YES; 399 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 400 | GCC_WARN_UNUSED_FUNCTION = YES; 401 | GCC_WARN_UNUSED_VARIABLE = YES; 402 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 403 | MTL_ENABLE_DEBUG_INFO = NO; 404 | SDKROOT = iphoneos; 405 | VALIDATE_PRODUCT = YES; 406 | }; 407 | name = Release; 408 | }; 409 | 274FDFB11CC51333000D26BD /* Debug */ = { 410 | isa = XCBuildConfiguration; 411 | baseConfigurationReference = C2A182384057B48892B6166A /* Pods-Activity Tracker.debug.xcconfig */; 412 | buildSettings = { 413 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 414 | CODE_SIGN_IDENTITY = "iPhone Developer"; 415 | INFOPLIST_FILE = "Activity Tracker/Info.plist"; 416 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 417 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 418 | PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.Activity-Tracker"; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PROJECT_NAME)/SupportingFiles/BridgingHeader.h"; 421 | }; 422 | name = Debug; 423 | }; 424 | 274FDFB21CC51333000D26BD /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | baseConfigurationReference = D83F72DF4E0D808084499DEA /* Pods-Activity Tracker.release.xcconfig */; 427 | buildSettings = { 428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 429 | CODE_SIGN_IDENTITY = "iPhone Developer"; 430 | INFOPLIST_FILE = "Activity Tracker/Info.plist"; 431 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 433 | PRODUCT_BUNDLE_IDENTIFIER = "com.microsoft.Activity-Tracker"; 434 | PRODUCT_NAME = "$(TARGET_NAME)"; 435 | SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PROJECT_NAME)/SupportingFiles/BridgingHeader.h"; 436 | }; 437 | name = Release; 438 | }; 439 | /* End XCBuildConfiguration section */ 440 | 441 | /* Begin XCConfigurationList section */ 442 | 274FDF991CC51333000D26BD /* Build configuration list for PBXProject "Activity Tracker" */ = { 443 | isa = XCConfigurationList; 444 | buildConfigurations = ( 445 | 274FDFAE1CC51333000D26BD /* Debug */, 446 | 274FDFAF1CC51333000D26BD /* Release */, 447 | ); 448 | defaultConfigurationIsVisible = 0; 449 | defaultConfigurationName = Release; 450 | }; 451 | 274FDFB01CC51333000D26BD /* Build configuration list for PBXNativeTarget "Activity Tracker" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | 274FDFB11CC51333000D26BD /* Debug */, 455 | 274FDFB21CC51333000D26BD /* Release */, 456 | ); 457 | defaultConfigurationIsVisible = 0; 458 | defaultConfigurationName = Release; 459 | }; 460 | /* End XCConfigurationList section */ 461 | }; 462 | rootObject = 274FDF961CC51333000D26BD /* Project object */; 463 | } 464 | -------------------------------------------------------------------------------- /Activity Tracker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Activity Tracker.xcodeproj/xcuserdata/kgerstner.xcuserdatad/xcschemes/Activity Tracker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Activity Tracker.xcodeproj/xcuserdata/kgerstner.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Activity Tracker.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 274FDF9D1CC51333000D26BD 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Activity Tracker.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Activity Tracker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Activity Tracker 4 | // 5 | // Created by Kyle Gerstner on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func setupColorsAndFonts() { 17 | let titleTextAttributes = [NSForegroundColorAttributeName:UIColor.whiteColor()]; 18 | UINavigationBar.appearance().titleTextAttributes = titleTextAttributes; 19 | UINavigationBar.appearance().barTintColor = DARK_BLUE; 20 | UINavigationBar.appearance().tintColor = UIColor.whiteColor(); 21 | UINavigationBar.appearance().backIndicatorImage = UIImage(named: "icon-back"); 22 | UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "icon-back"); 23 | 24 | UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent; 25 | } 26 | 27 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 28 | // Override point for customization after application launch. 29 | setupColorsAndFonts(); 30 | self.window = UIWindow.init(frame: UIScreen.mainScreen().bounds); 31 | self.window?.backgroundColor = UIColor.whiteColor(); 32 | 33 | let homeView = HomeViewController(), 34 | mainNav = UINavigationController(rootViewController: homeView); 35 | mainNav.navigationBar.translucent = false; 36 | 37 | self.window?.rootViewController = mainNav; 38 | self.window?.makeKeyAndVisible(); 39 | return true 40 | } 41 | 42 | func applicationWillResignActive(application: UIApplication) { 43 | // 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. 44 | // 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. 45 | } 46 | 47 | func applicationDidEnterBackground(application: UIApplication) { 48 | // 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. 49 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 50 | } 51 | 52 | func applicationWillEnterForeground(application: UIApplication) { 53 | // 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. 54 | } 55 | 56 | func applicationDidBecomeActive(application: UIApplication) { 57 | // 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. 58 | } 59 | 60 | func applicationWillTerminate(application: UIApplication) { 61 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 62 | } 63 | 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "3x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "3x" 27 | }, 28 | { 29 | "size" : "57x57", 30 | "idiom" : "iphone", 31 | "filename" : "icon-57.png", 32 | "scale" : "1x" 33 | }, 34 | { 35 | "size" : "57x57", 36 | "idiom" : "iphone", 37 | "filename" : "icon-57@2x.png", 38 | "scale" : "2x" 39 | }, 40 | { 41 | "size" : "60x60", 42 | "idiom" : "iphone", 43 | "filename" : "icon-120.png", 44 | "scale" : "2x" 45 | }, 46 | { 47 | "idiom" : "iphone", 48 | "size" : "60x60", 49 | "scale" : "3x" 50 | } 51 | ], 52 | "info" : { 53 | "version" : 1, 54 | "author" : "xcode" 55 | }, 56 | "properties" : { 57 | "pre-rendered" : true 58 | } 59 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/AppIcon.appiconset/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/AppIcon.appiconset/icon-120.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/AppIcon.appiconset/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/AppIcon.appiconset/icon-57.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-checkmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-checkmark.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-checkmark@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-checkmark.imageset/icon-action-checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-checkmark.imageset/icon-action-checkmark.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-checkmark.imageset/icon-action-checkmark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-checkmark.imageset/icon-action-checkmark@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-flag-disabled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-flag-disabled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-flag-disabled@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-flag-disabled.imageset/icon-action-flag-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-flag-disabled.imageset/icon-action-flag-disabled.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-flag-disabled.imageset/icon-action-flag-disabled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-flag-disabled.imageset/icon-action-flag-disabled@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-flag.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-flag.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-flag@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-flag.imageset/icon-action-flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-flag.imageset/icon-action-flag.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-flag.imageset/icon-action-flag@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-flag.imageset/icon-action-flag@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-note-disabled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-note-disabled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-note-disabled@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-note-disabled.imageset/icon-action-note-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-note-disabled.imageset/icon-action-note-disabled.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-note-disabled.imageset/icon-action-note-disabled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-note-disabled.imageset/icon-action-note-disabled@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-note.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-note.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-note@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-note.imageset/icon-action-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-note.imageset/icon-action-note.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-note.imageset/icon-action-note@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-note.imageset/icon-action-note@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-phone-disabled.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-phone-disabled.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-phone-disabled@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-phone-disabled.imageset/icon-action-phone-disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-phone-disabled.imageset/icon-action-phone-disabled.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-phone-disabled.imageset/icon-action-phone-disabled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-phone-disabled.imageset/icon-action-phone-disabled@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-phone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-action-phone.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-action-phone@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-phone.imageset/icon-action-phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-phone.imageset/icon-action-phone.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-action-phone.imageset/icon-action-phone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-action-phone.imageset/icon-action-phone@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-appt.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-activity-appt.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-activity-appt@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-appt.imageset/icon-activity-appt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-appt.imageset/icon-activity-appt.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-appt.imageset/icon-activity-appt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-appt.imageset/icon-activity-appt@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-check.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-activity-check.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-activity-check@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-check.imageset/icon-activity-check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-check.imageset/icon-activity-check.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-check.imageset/icon-activity-check@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-check.imageset/icon-activity-check@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-generic.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-activity-generic.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-activity-generic@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-generic.imageset/icon-activity-generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-generic.imageset/icon-activity-generic.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-generic.imageset/icon-activity-generic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-generic.imageset/icon-activity-generic@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-note.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-activity-note.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-activity-note@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-note.imageset/icon-activity-note.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-note.imageset/icon-activity-note.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-note.imageset/icon-activity-note@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-note.imageset/icon-activity-note@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-phone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-activity-phone.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-activity-phone@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-phone.imageset/icon-activity-phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-phone.imageset/icon-activity-phone.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-activity-phone.imageset/icon-activity-phone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-activity-phone.imageset/icon-activity-phone@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-back.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-back@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-back.imageset/icon-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-back.imageset/icon-back.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-back.imageset/icon-back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-back.imageset/icon-back@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-clear.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-clear.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-clear.imageset/icon-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-clear.imageset/icon-clear.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-email.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-contact-email.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-contact-email@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-email.imageset/icon-contact-email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact-email.imageset/icon-contact-email.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-email.imageset/icon-contact-email@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact-email.imageset/icon-contact-email@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-map.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-contact-map.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-contact-map@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-map.imageset/icon-contact-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact-map.imageset/icon-contact-map.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-map.imageset/icon-contact-map@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact-map.imageset/icon-contact-map@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-phone.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-contact-phone.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-contact-phone@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-phone.imageset/icon-contact-phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact-phone.imageset/icon-contact-phone.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact-phone.imageset/icon-contact-phone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact-phone.imageset/icon-contact-phone@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-contact.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-contact@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact.imageset/icon-contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact.imageset/icon-contact.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-contact.imageset/icon-contact@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-contact.imageset/icon-contact@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-search.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-search.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-search.imageset/icon-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-search.imageset/icon-search.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-settings.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-settings.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-settings@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-settings.imageset/icon-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-settings.imageset/icon-settings.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/icon-settings.imageset/icon-settings@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/icon-settings.imageset/icon-settings@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/spinner.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "spinner.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "spinner@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/spinner.imageset/spinner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/spinner.imageset/spinner.png -------------------------------------------------------------------------------- /Activity Tracker/Assets.xcassets/spinner.imageset/spinner@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Activity Tracker/Assets.xcassets/spinner.imageset/spinner@2x.png -------------------------------------------------------------------------------- /Activity Tracker/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Activity Tracker/CRMConnector/CRMClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRMClient.swift 3 | // Activity Tracker 4 | // 5 | // Created by Kyle Gerstner on 4/20/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ADAL 11 | 12 | //TODO: Update the clientID and redirectUri after you register a new app with AD 13 | var CLIENT_ID: String { get {return "1dc3cd16-85f4-449e-9145-98c996ea6a85";}} 14 | var REDIRECT_URI: String { get { return "http://crm.codesamples/";}} 15 | 16 | class CRMClient : NSObject 17 | { 18 | static let sharedClient = CRMClient(); 19 | 20 | var accessToken:String = ""; 21 | var endpointURL:NSURL = NSURL(); 22 | var authority:String?; 23 | var tokenExpirationDate:NSDate?; 24 | 25 | private func oDataRequestForEndpoint(endpoint:String) -> NSMutableURLRequest 26 | { 27 | let path = String(format: "/api/data/v8.0/%@", arguments: [endpoint]); 28 | 29 | let retVal = NSMutableURLRequest(URL: NSURL(string: path.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())!, relativeToURL: self.endpointURL)!); 30 | retVal.setValue(String(format: "Bearer %@", arguments: [self.accessToken]), forHTTPHeaderField: "Authorization"); 31 | retVal.setValue("application/json", forHTTPHeaderField: "Accept"); 32 | retVal.setValue("4.0", forHTTPHeaderField: "OData-MaxVersion"); 33 | retVal.setValue("4.0", forHTTPHeaderField: "OData-Version"); 34 | 35 | return retVal; 36 | } 37 | 38 | private func oDataGetRequestForNSURLResponse(response:NSURLResponse?) -> NSURLRequest? { 39 | if (response == nil) { 40 | return nil; 41 | } 42 | 43 | let httpRepsonse = response as! NSHTTPURLResponse, 44 | headers = httpRepsonse.allHeaderFields, 45 | path = headers["OData-EntityId"] as! String, 46 | url = NSURL(string: path.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())!, relativeToURL: endpointURL), 47 | retVal = NSMutableURLRequest(URL: url!); 48 | 49 | retVal.HTTPMethod = "GET"; 50 | retVal.setValue(String(format: "Bearer %@", arguments: [self.accessToken]), forHTTPHeaderField: "Authorization"); 51 | retVal.setValue("application/json", forHTTPHeaderField: "Accept"); 52 | retVal.setValue("4.0", forHTTPHeaderField: "OData-MaxVersion"); 53 | retVal.setValue("4.0", forHTTPHeaderField: "OData-Version"); 54 | 55 | return retVal; 56 | } 57 | 58 | private func oDataGetRequestForEndpoint(endpoint:String) -> NSURLRequest 59 | { 60 | let retVal = self.oDataRequestForEndpoint(endpoint); 61 | 62 | retVal.HTTPMethod = "GET"; 63 | 64 | return retVal; 65 | } 66 | 67 | private func oDataPostRequestForEndpoint(endpoint:String, body:NSData) -> NSURLRequest 68 | { 69 | let retVal = self.oDataRequestForEndpoint(endpoint); 70 | 71 | retVal.HTTPMethod = "POST"; 72 | retVal.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type"); 73 | retVal.HTTPBody = body; 74 | 75 | return retVal; 76 | } 77 | 78 | private func oDataPutRequestForEndpoint(endpoint:String, body:NSData) -> NSURLRequest 79 | { 80 | let retVal = self.oDataRequestForEndpoint(endpoint); 81 | 82 | retVal.HTTPMethod = "PUT"; 83 | retVal.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type"); 84 | retVal.HTTPBody = body; 85 | 86 | return retVal; 87 | } 88 | 89 | private func refreshAuthToken(completionBlock:(success:Bool) -> Void) 90 | { 91 | if let tokenExpirationDate = self.tokenExpirationDate 92 | { 93 | if (NSDate().compare(tokenExpirationDate) == NSComparisonResult.OrderedAscending) 94 | { 95 | completionBlock(success: true); 96 | return; 97 | } 98 | } 99 | 100 | if let authority = self.authority 101 | { 102 | let authContext = ADAuthenticationContext (authority: authority, validateAuthority: false, error: nil); 103 | 104 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 105 | authContext.acquireTokenWithResource(self.endpointURL.absoluteString, clientId: CLIENT_ID, redirectUri: NSURL(string: REDIRECT_URI), completionBlock: { (result: ADAuthenticationResult!) -> Void in 106 | if (result.status != AD_SUCCEEDED) 107 | { 108 | completionBlock(success: false); 109 | return; 110 | } 111 | 112 | self.accessToken = result.accessToken; 113 | self.tokenExpirationDate = result.tokenCacheItem.expiresOn; 114 | 115 | completionBlock(success: true); 116 | }); 117 | 118 | }); 119 | } 120 | else 121 | { 122 | completionBlock(success: false); 123 | } 124 | 125 | } 126 | 127 | func setNewEndpoint(endpoint:String, completionBlock:(success:Bool) -> Void) 128 | { 129 | self.endpointURL = NSURL(string: endpoint)!; 130 | 131 | let path = "/api/data/v8.0/"; 132 | 133 | let authorityRequest = NSMutableURLRequest(URL: NSURL(string: path, relativeToURL: self.endpointURL)!); 134 | authorityRequest.HTTPMethod = "GET"; 135 | authorityRequest.setValue("Bearer", forHTTPHeaderField: "Authorization"); 136 | authorityRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Accept"); 137 | 138 | let authorityTask = NSURLSession.sharedSession().dataTaskWithRequest(authorityRequest) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 139 | if (response == nil) { 140 | completionBlock(success: false); 141 | return; 142 | } 143 | let httpResponse = response as! NSHTTPURLResponse; 144 | var authenticationHeader:NSString = (httpResponse.allHeaderFields as NSDictionary).objectForKey("WWW-Authenticate") as! NSString; 145 | 146 | let commaRange = authenticationHeader.rangeOfString(","); 147 | if (commaRange.location != NSNotFound) 148 | { 149 | authenticationHeader = authenticationHeader.substringToIndex(commaRange.location); 150 | } 151 | 152 | let equalRange = authenticationHeader.rangeOfString("="); 153 | if (equalRange.location != NSNotFound) 154 | { 155 | authenticationHeader = NSString(format: "%@\"%@\"", authenticationHeader.substringToIndex(equalRange.location+1), authenticationHeader.substringFromIndex(equalRange.location+1)); 156 | } 157 | 158 | //var adError:ADAuthenticationError?; 159 | let authParams = ADAuthenticationParameters(fromResponseAuthenticateHeader: authenticationHeader as String, error: nil); 160 | 161 | self.authority = authParams.authority; 162 | 163 | let authContext = ADAuthenticationContext (authority: authParams.authority, validateAuthority: false, error: nil); 164 | 165 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 166 | authContext.acquireTokenWithResource(endpoint, clientId: CLIENT_ID, redirectUri: NSURL(string: REDIRECT_URI), completionBlock: { (result: ADAuthenticationResult!) -> Void in 167 | if (result.status != AD_SUCCEEDED) 168 | { 169 | completionBlock(success: false); 170 | return; 171 | } 172 | 173 | self.accessToken = result.accessToken; 174 | self.tokenExpirationDate = result.tokenCacheItem.expiresOn; 175 | 176 | LocalStorage.localStorage.saveHost(endpoint); 177 | 178 | completionBlock(success: true); 179 | }); 180 | 181 | }); 182 | 183 | } 184 | authorityTask.resume(); 185 | } 186 | 187 | func searchFor(searchString: String, completionBlock:(results:NSArray?, error:NSError? ) -> Void) 188 | { 189 | 190 | self.refreshAuthToken { (success:Bool) -> Void in 191 | if (success) 192 | { 193 | let path = "contacts"; 194 | let selectString = CRMContact.selectString(false); 195 | let search = String(format: "$filter=contains(fullname, '%@')", arguments: [searchString]) 196 | let fullPath = String(format: "%@?$select=%@&%@", arguments: [path, selectString, search]); 197 | 198 | let fetchRequest = self.oDataGetRequestForEndpoint(fullPath); 199 | let fetchTask = NSURLSession.sharedSession().dataTaskWithRequest(fetchRequest) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 200 | if (error == nil) 201 | { 202 | do 203 | { 204 | let resultDict:NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary; 205 | let results:NSArray = resultDict.objectForKey("value") as! NSArray; 206 | 207 | let retVal:NSMutableArray = NSMutableArray(); 208 | for result in results 209 | { 210 | let contact = CRMContact(); 211 | contact.updateFromDict(result as! [String : AnyObject]); 212 | retVal.addObject(contact); 213 | } 214 | 215 | completionBlock(results: retVal, error: nil); 216 | } 217 | catch 218 | { 219 | completionBlock(results: nil, error: nil); 220 | return; 221 | } 222 | } 223 | else 224 | { 225 | completionBlock(results: nil, error: error); 226 | } 227 | } 228 | 229 | fetchTask.resume(); 230 | } 231 | else 232 | { 233 | completionBlock(results: nil, error: NSError(domain: "Could not refresh authentication token", code: 0, userInfo: nil)); 234 | } 235 | } 236 | } 237 | 238 | func getContactDetails(contact: CRMContact, completionBlock:(success:Bool) -> Void) 239 | { 240 | self.refreshAuthToken { (success:Bool) -> Void in 241 | if (success) 242 | { 243 | let path = String(format: "contacts(%@)", arguments: [contact.crmID]); 244 | let selectString = CRMContact.selectString(true); 245 | let fullPath = String(format: "%@?$select=%@", arguments: [path, selectString]); 246 | 247 | let fetchRequest = self.oDataGetRequestForEndpoint(fullPath); 248 | let fetchTask = NSURLSession.sharedSession().dataTaskWithRequest(fetchRequest) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 249 | if (error == nil) 250 | { 251 | do 252 | { 253 | let resultDict:NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary; 254 | contact.updateFromDict(resultDict as! [String : AnyObject]); 255 | completionBlock (success: true); 256 | } 257 | catch 258 | { 259 | completionBlock(success: false); 260 | return; 261 | } 262 | } 263 | else { 264 | completionBlock(success: false); 265 | } 266 | } 267 | 268 | fetchTask.resume(); 269 | } 270 | else 271 | { 272 | completionBlock(success: false); 273 | } 274 | } 275 | } 276 | 277 | func getRecentActivities(contact: CRMContact, completionBlock:(success:Bool) -> Void) 278 | { 279 | self.refreshAuthToken { (success:Bool) -> Void in 280 | if (success) 281 | { 282 | let path = String(format: "/contacts(%@)/Contact_ActivityPointers", arguments: [contact.crmID]), 283 | selectString = CRMActivity.selectString(false), 284 | fullPath = String(format: "%@?$select=%@&$filter=actualend ne null&$top=5", arguments: [path, selectString]), 285 | fetchRequest = self.oDataGetRequestForEndpoint(fullPath); 286 | 287 | contact.recentActivities = []; 288 | 289 | let fetchTask = NSURLSession.sharedSession().dataTaskWithRequest(fetchRequest) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 290 | if (error == nil) 291 | { 292 | do 293 | { 294 | let resultDict:NSDictionary = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary; 295 | contact.parseActivities(resultDict["value"] as! [[String : AnyObject]]); 296 | completionBlock (success: true); 297 | } 298 | catch 299 | { 300 | completionBlock(success: false); 301 | return; 302 | } 303 | } 304 | else { 305 | completionBlock(success: false); 306 | } 307 | } 308 | 309 | fetchTask.resume(); 310 | } 311 | else 312 | { 313 | completionBlock(success: false); 314 | } 315 | } 316 | } 317 | 318 | func create(object: CRMActivity, relatedTo: CRMContact, completionBlock:(success:Bool) -> Void) 319 | { 320 | self.refreshAuthToken { (success:Bool) -> Void in 321 | if (success) 322 | { 323 | let path = String(format: "tasks"); 324 | 325 | var bodyData:NSData? = nil; 326 | do 327 | { 328 | bodyData = try NSJSONSerialization.dataWithJSONObject(object.createDictionary(relatedTo), options: NSJSONWritingOptions.PrettyPrinted); 329 | } 330 | catch 331 | { 332 | completionBlock(success: false); 333 | } 334 | 335 | let createRequest = self.oDataPostRequestForEndpoint(path, body: bodyData!); 336 | let createTask = NSURLSession.sharedSession().dataTaskWithRequest(createRequest) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 337 | 338 | if (error == nil) 339 | { 340 | let fetchRequest = self.oDataGetRequestForNSURLResponse(response); 341 | let fetchTask = NSURLSession.sharedSession().dataTaskWithRequest(fetchRequest!) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 342 | if (error == nil) 343 | { 344 | do 345 | { 346 | let resultDict:[String:AnyObject] = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! [String:AnyObject]; 347 | let newobject = relatedTo.addActivity(resultDict); 348 | relatedTo.recentActivities = relatedTo.recentActivities.sort({$0.activityDate.compare($1.activityDate) == .OrderedDescending}); 349 | self.complete(newobject, completionBlock: completionBlock); 350 | } 351 | catch 352 | { 353 | completionBlock(success: false); 354 | return; 355 | } 356 | } 357 | } 358 | fetchTask.resume(); 359 | } 360 | else { 361 | completionBlock(success: false); 362 | } 363 | } 364 | createTask.resume(); 365 | 366 | } 367 | else 368 | { 369 | completionBlock(success: false); 370 | } 371 | } 372 | 373 | } 374 | 375 | func complete(object: CRMActivity, completionBlock:(success:Bool) -> Void) 376 | { 377 | self.refreshAuthToken { (success:Bool) -> Void in 378 | if (success) 379 | { 380 | let path = String(format: "tasks(%@)/statecode", arguments: [object.crmID]); 381 | 382 | var bodyData:NSData? = nil; 383 | do 384 | { 385 | bodyData = try NSJSONSerialization.dataWithJSONObject(object.markAsCompleteDictionary(), options: NSJSONWritingOptions.PrettyPrinted); 386 | } 387 | catch 388 | { 389 | completionBlock(success: false); 390 | } 391 | 392 | let createRequest = self.oDataPutRequestForEndpoint(path, body: bodyData!); 393 | let createTask = NSURLSession.sharedSession().dataTaskWithRequest(createRequest) { (data: NSData?, response:NSURLResponse?, error:NSError?) -> Void in 394 | 395 | completionBlock (success: error == nil); 396 | 397 | } 398 | createTask.resume(); 399 | } 400 | else 401 | { 402 | completionBlock(success: false); 403 | } 404 | } 405 | } 406 | } -------------------------------------------------------------------------------- /Activity Tracker/CRMConnector/LocalStorage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocalStorage.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | var HOST_KEY: String { get {return "CRMHost";}} 12 | var RECENTS_KEY: String { get { return "RecentRecords";}} 13 | 14 | class LocalStorage { 15 | 16 | static let localStorage = LocalStorage(); 17 | 18 | func getHost() -> String? { 19 | let defaults = NSUserDefaults.standardUserDefaults(); 20 | return defaults.stringForKey(HOST_KEY); 21 | } 22 | 23 | func saveHost(host: String) { 24 | let defaults = NSUserDefaults.standardUserDefaults(), 25 | currHost = defaults.stringForKey(HOST_KEY); 26 | 27 | if (currHost != nil && currHost != host) 28 | { 29 | defaults.removeObjectForKey(RECENTS_KEY); 30 | } 31 | 32 | defaults.setObject(host, forKey: HOST_KEY); 33 | defaults.synchronize(); 34 | } 35 | 36 | // Recent records need to use an Archiver and Unarchiver to properly store the objects 37 | func getRecents() -> [AnyObject] { 38 | var results = NSMutableOrderedSet(); 39 | 40 | let defaults = NSUserDefaults.standardUserDefaults(), 41 | recentData = defaults.objectForKey(RECENTS_KEY) as? NSData; 42 | 43 | if (recentData != nil) 44 | { 45 | let recents = NSKeyedUnarchiver.unarchiveObjectWithData(recentData!); 46 | if (recents != nil) { 47 | results = recents as! NSMutableOrderedSet!; 48 | } 49 | } 50 | 51 | return results.array; 52 | } 53 | 54 | func addToRecents(recent:CRMObject) { 55 | 56 | let defaults = NSUserDefaults.standardUserDefaults(), 57 | recentData = defaults.objectForKey(RECENTS_KEY) as? NSData; 58 | 59 | var recents = NSMutableOrderedSet?(); 60 | if (recentData != nil) 61 | { 62 | recents = NSKeyedUnarchiver.unarchiveObjectWithData(recentData!) as? NSMutableOrderedSet; 63 | let filtered = recents!.filter() {$0.crmID == recent.crmID;}; 64 | recents!.removeObjectsInArray(filtered); 65 | } 66 | 67 | if (recents == nil) { 68 | recents = NSMutableOrderedSet(object: recent); 69 | } 70 | else { 71 | recents!.insertObject(recent, atIndex: 0); 72 | } 73 | 74 | if (recents?.count > 10) 75 | { 76 | recents?.removeObjectAtIndex(10); 77 | } 78 | 79 | defaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(recents!), forKey: RECENTS_KEY); 80 | defaults.synchronize(); 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /Activity Tracker/ClassCategories/String+StringFormatting.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+StringFormatting.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String 12 | { 13 | func dashesForEmpty () -> String 14 | { 15 | return self.characters.count > 0 ? self : "--"; 16 | } 17 | } -------------------------------------------------------------------------------- /Activity Tracker/ColorsAndFonts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorsAndFonts.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/19/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | func COLOR(r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat) -> UIColor { 13 | return UIColor(red: r/255.0, green: g/255.0, blue: b/255.0, alpha: a); 14 | } 15 | 16 | let DARK_BLUE = COLOR(37.0, g: 61.0, b: 93.0, a: 1.0); 17 | let LIGHT_BLUE = COLOR(2.0, g: 162.0, b: 216.0, a: 1.0); 18 | let VERY_LIGHT_GREY = COLOR(245.0, g: 245.0, b: 245.0, a: 1.0); 19 | let DARK_PURPLE = COLOR(107.0, g: 0.0, b: 216.0, a: 1.0); 20 | let MEDIUM_GREY = COLOR(153.0, g: 153.0, b: 153.0, a: 1.0); 21 | let DARK_GREY = COLOR(102.0, g: 102.0, b: 102.0, a: 1.0); 22 | let BLUE_HIGHLIGHT = COLOR(242.0, g: 251.0, b: 253.0, a: 1.0); 23 | let PURPLE_HIGHLIGHT = COLOR(247.0, g: 242.0, b: 253.0, a: 1.0); 24 | let SUBMIT_HIGHLIGHT = COLOR(42.0, g: 152.0, b: 198.0, a: 1.0); 25 | let LINE_COLOR = COLOR(221.0, g: 221.0, b: 221.0, a: 1.0); -------------------------------------------------------------------------------- /Activity Tracker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Activity Tracker/Objects/CRMActivity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRMActivity.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class CRMActivity: CRMObject { 13 | 14 | var activityType = ""; 15 | var activitySubject = ""; 16 | var activityDate = NSDate(); 17 | var activityNotes = ""; 18 | 19 | // Determine which image to show in recent list 20 | func activityImage() -> UIImage { 21 | if (self.activityType == "task") 22 | { 23 | return UIImage(named:"icon-activity-check")!; 24 | } 25 | else if (self.activityType == "phonecall") 26 | { 27 | return UIImage(named:"icon-activity-phone")!; 28 | } 29 | else if (self.activityType == "appointment") 30 | { 31 | return UIImage(named:"icon-activity-appt")!; 32 | } 33 | return UIImage(named:"icon-activity-generic")!; 34 | } 35 | 36 | // This determines which fields are returned from the OData endpoint 37 | class override func selectString(includeRelated:Bool) -> String { 38 | return "activityid,subject,actualend,description,activitytypecode,statecode,statuscode"; 39 | } 40 | 41 | // Update the record with the results from the OData endpoint 42 | func updateFromDict(dict:Dictionary) { 43 | 44 | if let value = dict["activityid"] { 45 | if !(value is NSNull) { 46 | self.crmID = value as! String; 47 | } 48 | } 49 | if let value = dict["subject"] { 50 | if !(value is NSNull) { 51 | self.activitySubject = value as! String; 52 | } 53 | } 54 | if let value = dict["actualend"] { 55 | if !(value is NSNull) { 56 | let dateFormatter = NSDateFormatter(); 57 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"; 58 | 59 | self.activityDate = dateFormatter.dateFromString(value as! String)!; 60 | } 61 | } 62 | if let value = dict["description"] { 63 | if !(value is NSNull) { 64 | self.activityNotes = value as! String; 65 | } 66 | } 67 | if let value = dict["activitytypecode"] { 68 | if !(value is NSNull) { 69 | self.activityType = value as! String; 70 | } 71 | } 72 | } 73 | 74 | // Return a dictionary to send to the OData endpoint in order to create a new activity record 75 | func createDictionary(relatedTo: CRMObject) -> Dictionary { 76 | 77 | let dateFormatter = NSDateFormatter(); 78 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"; 79 | 80 | var retVal = Dictionary(); 81 | retVal["subject"] = self.activitySubject; 82 | retVal["actualend"] = dateFormatter.stringFromDate(self.activityDate); 83 | retVal["description"] = self.activityNotes; 84 | retVal["regardingobjectid_contact_task@odata.bind"] = String(format:"/contacts(%@)",relatedTo.crmID); 85 | 86 | return retVal; 87 | } 88 | 89 | func markAsCompleteDictionary () -> Dictionary 90 | { 91 | return ["value":1]; 92 | } 93 | } -------------------------------------------------------------------------------- /Activity Tracker/Objects/CRMContact.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRMContact.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class CRMContact: CRMObject { 12 | 13 | var fullName = ""; 14 | var jobTitle = ""; 15 | var email = ""; 16 | var phoneNumber = ""; 17 | var accountName = ""; 18 | 19 | var addressLine1 = ""; 20 | var addressCity = ""; 21 | var addressState = ""; 22 | var addressZip = ""; 23 | 24 | 25 | override func encodeWithCoder(aCoder: NSCoder) { 26 | super.encodeWithCoder(aCoder); 27 | aCoder.encodeObject(fullName, forKey: "fullName"); 28 | aCoder.encodeObject(jobTitle, forKey: "jobTitle"); 29 | aCoder.encodeObject(email, forKey: "email"); 30 | aCoder.encodeObject(phoneNumber, forKey: "phoneNumber"); 31 | aCoder.encodeObject(accountName, forKey: "accountName"); 32 | aCoder.encodeObject(addressLine1, forKey: "addressLine1"); 33 | aCoder.encodeObject(addressCity, forKey: "addressCity"); 34 | aCoder.encodeObject(addressState, forKey: "addressState"); 35 | aCoder.encodeObject(addressZip, forKey: "addressZip"); 36 | } 37 | 38 | override init() { 39 | super.init(); 40 | } 41 | 42 | required init(coder aDecoder: NSCoder) { 43 | super.init(coder: aDecoder); 44 | self.crmID = aDecoder.decodeObjectForKey("crmID") as! String; 45 | self.fullName = aDecoder.decodeObjectForKey("fullName") as! String; 46 | self.jobTitle = aDecoder.decodeObjectForKey("jobTitle") as! String; 47 | self.email = aDecoder.decodeObjectForKey("email") as! String; 48 | self.phoneNumber = aDecoder.decodeObjectForKey("phoneNumber") as! String; 49 | self.accountName = aDecoder.decodeObjectForKey("accountName") as! String; 50 | self.addressLine1 = aDecoder.decodeObjectForKey("addressLine1") as! String; 51 | self.addressCity = aDecoder.decodeObjectForKey("addressCity") as! String; 52 | self.addressState = aDecoder.decodeObjectForKey("addressState") as! String; 53 | self.addressZip = aDecoder.decodeObjectForKey("addressZip") as! String; 54 | 55 | } 56 | 57 | override func supportsSecureEncoding() -> Bool { 58 | return true; 59 | } 60 | 61 | override var resultLine1 : String { 62 | return self.fullName; 63 | } 64 | 65 | override var resultLine2 : String { 66 | var retVal = ""; 67 | 68 | if (self.accountName.characters.count > 0) 69 | { 70 | retVal.appendContentsOf(self.accountName); 71 | } 72 | 73 | if (self.jobTitle.characters.count > 0) 74 | { 75 | if (retVal.characters.count != 0) 76 | { 77 | retVal.appendContentsOf(" - "); 78 | } 79 | 80 | retVal.appendContentsOf(self.jobTitle); 81 | } 82 | return retVal; 83 | } 84 | 85 | override var detailLine1 : String { 86 | return self.fullName; 87 | } 88 | 89 | override var detailLine2: String { 90 | return self.jobTitle; 91 | } 92 | 93 | override var detailLine3: String { 94 | return self.accountName; 95 | } 96 | 97 | override var addressInfo: String { 98 | if (self.addressLine1.characters.count == 0 && self.addressCity.characters.count == 0 && self.addressState.characters.count == 0 && self.addressZip.characters.count == 0) 99 | { 100 | return ""; 101 | } 102 | return String(format: "%@\n%@ %@ %@", self.addressLine1.dashesForEmpty(),self.addressCity.dashesForEmpty(),self.addressState.dashesForEmpty(),self.addressZip.dashesForEmpty()); 103 | } 104 | 105 | override var mainPhone: String { 106 | return self.phoneNumber; 107 | } 108 | 109 | override var mainEmail: String { 110 | return self.email; 111 | } 112 | 113 | // This determines which fields are returned from the OData endpoint 114 | class override func selectString(includeRelated:Bool) -> String { 115 | var retVal = "contactid,fullname,address1_line1,address1_city,address1_stateorprovince,address1_postalcode,emailaddress1,jobtitle,telephone1"; 116 | if (includeRelated) { 117 | retVal = retVal.stringByAppendingString("&$expand=parentcustomerid_account($select=name)"); 118 | } 119 | return retVal; 120 | } 121 | 122 | func parseActivities(activities:[[String:AnyObject]]) { 123 | self.recentActivities = []; 124 | for objectDict in activities { 125 | self.addActivity(objectDict); 126 | } 127 | self.recentActivities = self.recentActivities.sort({$0.activityDate.compare($1.activityDate) == .OrderedDescending}); 128 | } 129 | 130 | func addActivity(objectDict:Dictionary) -> CRMActivity{ 131 | let result = CRMActivity(); 132 | result.updateFromDict(objectDict); 133 | self.recentActivities.append(result); 134 | return result; 135 | } 136 | 137 | // I dont think we actually need this any more, the web api returns the actual objects as contacts 138 | // class func parseResults(results:[AnyObject]) -> [CRMContact] { 139 | // 140 | // var retVal = [CRMContact](); 141 | // 142 | // for (var i = 0; i < results.count; i++) 143 | // { 144 | // var objectDict = results[i] as [String:AnyObject]; 145 | // let result = CRMContact(); 146 | // let attributes = objectDict["Attributes"] as! [AnyObject]; 147 | // 148 | // for attributeDict in attributes { 149 | // result.updateFromSOAPDict(attributeDict as! [String:AnyObject]); 150 | // } 151 | // retVal.append(result); 152 | // } 153 | // 154 | // return retVal; 155 | // } 156 | 157 | // Update the record with the results from the OData endpoint 158 | func updateFromDict(dict:[String:AnyObject]) { 159 | 160 | if let value = dict["contactid"] { 161 | if !(value is NSNull) { 162 | self.crmID = value as! String; 163 | } 164 | } 165 | if let value = dict["fullname"] { 166 | if !(value is NSNull) { 167 | self.fullName = value as! String; 168 | } 169 | } 170 | if let value = dict["jobtitle"] { 171 | if !(value is NSNull) { 172 | self.jobTitle = value as! String; 173 | } 174 | } 175 | if let value = dict["emailaddress1"] { 176 | if !(value is NSNull) { 177 | self.email = value as! String; 178 | } 179 | } 180 | if let value = dict["telephone1"] { 181 | if !(value is NSNull) { 182 | self.phoneNumber = value as! String; 183 | } 184 | } 185 | if let value = dict["address1_line1"] { 186 | if !(value is NSNull) { 187 | self.addressLine1 = value as! String; 188 | } 189 | } 190 | if let value = dict["address1_city"] { 191 | if !(value is NSNull) { 192 | self.addressCity = value as! String; 193 | } 194 | } 195 | if let value = dict["address1_stateorprovince"] { 196 | if !(value is NSNull) { 197 | self.addressState = value as! String; 198 | } 199 | } 200 | if let value = dict["address1_postalcode"] { 201 | if !(value is NSNull) { 202 | self.addressZip = value as! String; 203 | } 204 | } 205 | if let value = dict["parentcustomerid_account"] { 206 | if !(value is NSNull) { 207 | if let val = value["name"] { 208 | if !(val is NSNull) { 209 | self.accountName = val as! String; 210 | } 211 | } 212 | } 213 | } 214 | } 215 | 216 | } -------------------------------------------------------------------------------- /Activity Tracker/Objects/CRMObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CRMObject.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | class CRMObject: NSObject, NSCoding { 13 | 14 | var crmID = ""; 15 | var recentActivities = [CRMActivity](); 16 | 17 | var resultLine1 : String { return ""; }; 18 | var resultLine2 : String { return ""; }; 19 | 20 | var detailLine1 : String { return ""; }; 21 | var detailLine2 : String { return ""; }; 22 | var detailLine3 : String { return ""; }; 23 | 24 | var addressInfo : String { return ""; }; 25 | var mainPhone : String { return ""; }; 26 | var mainEmail : String { return ""; }; 27 | 28 | override init() 29 | { 30 | recentActivities = []; 31 | super.init(); 32 | } 33 | 34 | required init(coder aDecoder: NSCoder) { 35 | super.init(); 36 | self.crmID = aDecoder.decodeObjectForKey("crmID") as! String; 37 | } 38 | 39 | func encodeWithCoder(aCoder:NSCoder) { 40 | aCoder.encodeObject(crmID, forKey: "crmID"); 41 | } 42 | 43 | func supportsSecureEncoding() -> Bool { 44 | return true; 45 | } 46 | 47 | class func selectString(includeRelated:Bool) -> String { 48 | return ""; 49 | } 50 | } -------------------------------------------------------------------------------- /Activity Tracker/SupportingFiles/BridgingHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgingHeader.h 3 | // Activity Tracker 4 | // 5 | // Created by Kyle Gerstner on 4/20/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | #import 10 | -------------------------------------------------------------------------------- /Activity Tracker/UI/HomeView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Activity Tracker/UI/HomeViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | enum HomeViewDisplayMode { 12 | case HomeViewDisplayRecents 13 | case HomeViewDisplaySearch 14 | } 15 | 16 | 17 | class HomeViewController: UIViewController, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate { 18 | 19 | @IBOutlet weak var theSearchBar: UISearchBar! 20 | @IBOutlet weak var resultsView: UITableView! 21 | @IBOutlet weak var loadingIndicator: UIImageView! 22 | 23 | var CellIdentifier = "ResultsCell", 24 | currMode = HomeViewDisplayMode.HomeViewDisplayRecents, 25 | recents = [AnyObject]?(), 26 | searchResults = [AnyObject]?(); 27 | 28 | convenience init() { 29 | self.init(nibName:"HomeView", bundle:nil); 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad(); 34 | 35 | self.title = "Activity Tracker"; 36 | 37 | self.navigationItem.backBarButtonItem = UIBarButtonItem.init(title: "", style:.Plain, target: nil, action: nil); 38 | self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(image: UIImage(named: "icon-settings"), style:.Plain, target: self, action: #selector(HomeViewController.settingsTapped(_:))); 39 | 40 | 41 | if (LocalStorage.localStorage.getHost() != nil) { 42 | CRMClient.sharedClient.setNewEndpoint(LocalStorage.localStorage.getHost()!) { (success) -> Void in }; 43 | } 44 | } 45 | 46 | override func viewWillAppear(animated: Bool) { 47 | super.viewWillAppear(animated); 48 | recents = LocalStorage.localStorage.getRecents(); 49 | resultsView.reloadData(); 50 | } 51 | 52 | override func viewDidAppear(animated: Bool) { 53 | super.viewDidAppear(animated); 54 | if (LocalStorage.localStorage.getHost() == nil) { 55 | settingsTapped(nil); 56 | } 57 | // else { 58 | // CRMClient.sharedClient.setNewEndpoint(LocalStorage.localStorage.getHost()!) { (success) -> Void in }; 59 | // } 60 | } 61 | 62 | override func viewDidDisappear(animated: Bool) { 63 | super.viewDidDisappear(animated); 64 | if let indexPath = resultsView.indexPathForSelectedRow { 65 | resultsView.deselectRowAtIndexPath(indexPath, animated: false); 66 | } 67 | } 68 | 69 | func settingsTapped(sender:AnyObject?) { 70 | let settingsView = SettingsViewController(); 71 | self.navigationController?.pushViewController(settingsView, animated: true); 72 | } 73 | 74 | // The spinner methods show a custom progress indicator 75 | func animateSpinner() { 76 | loadingIndicator.hidden = false; 77 | let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z"); 78 | rotationAnimation.toValue = M_PI; 79 | rotationAnimation.duration = 0.5; 80 | rotationAnimation.cumulative = true; 81 | rotationAnimation.repeatCount = HUGE; 82 | 83 | loadingIndicator.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation"); 84 | } 85 | 86 | func stopSpinner() { 87 | loadingIndicator.hidden = true; 88 | loadingIndicator.layer.removeAnimationForKey("rotationAnimation"); 89 | } 90 | 91 | // MARK: UISearchBar Delegate 92 | func searchBarSearchButtonClicked(searchBar: UISearchBar) { 93 | if (searchBar.text?.characters.count != 0) { 94 | currMode = .HomeViewDisplaySearch; 95 | searchResults = nil; 96 | resultsView.reloadData(); 97 | searchBar.resignFirstResponder(); 98 | animateSpinner(); 99 | 100 | CRMClient.sharedClient.searchFor(searchBar.text!, completionBlock: { (results, error) -> Void in 101 | if (error != nil) { 102 | let errorAlert = UIAlertController(title: "Error", message: "There was an error performing your search. Please ensure you are connected to the internet.", preferredStyle:UIAlertControllerStyle.Alert); 103 | errorAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)); 104 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 105 | self.presentViewController(errorAlert, animated: true, completion: nil); 106 | }); 107 | } 108 | else { 109 | self.searchResults = results as? [AnyObject]; 110 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 111 | self.resultsView.reloadData(); 112 | searchBar.setShowsCancelButton(true, animated: true); 113 | self.stopSpinner(); 114 | }); 115 | } 116 | }); 117 | } 118 | else { 119 | currMode = .HomeViewDisplayRecents; 120 | recents = LocalStorage.localStorage.getRecents(); 121 | searchBar.setShowsCancelButton(false, animated: true); 122 | 123 | resultsView.reloadData(); 124 | } 125 | } 126 | 127 | func searchBarTextDidBeginEditing(searchBar: UISearchBar) { 128 | searchBar.setShowsCancelButton(true, animated: true); 129 | } 130 | 131 | func searchBarCancelButtonClicked(searchBar: UISearchBar) { 132 | searchBar.resignFirstResponder(); 133 | searchBar.text = ""; 134 | currMode = .HomeViewDisplayRecents; 135 | recents = LocalStorage.localStorage.getRecents(); 136 | searchBar.setShowsCancelButton(false, animated: true); 137 | 138 | resultsView.reloadData(); 139 | } 140 | 141 | // MARK: UITableView DataSource 142 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 143 | switch (currMode) { 144 | case .HomeViewDisplayRecents: 145 | return recents != nil ? recents!.count : 0; 146 | case .HomeViewDisplaySearch: 147 | return searchResults != nil ? searchResults!.count : 0; 148 | } 149 | } 150 | 151 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 152 | 153 | var retVal = tableView.dequeueReusableCellWithIdentifier(CellIdentifier); 154 | if (retVal == nil) 155 | { 156 | retVal = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: CellIdentifier); 157 | retVal!.selectedBackgroundView = UIView(); 158 | } 159 | 160 | var currObject = CRMObject(); 161 | if (currMode == .HomeViewDisplayRecents) 162 | { 163 | currObject = recents?[indexPath.row] as! CRMObject; 164 | } 165 | else 166 | { 167 | currObject = searchResults?[indexPath.row] as! CRMObject; 168 | } 169 | 170 | retVal!.textLabel!.text = currObject.resultLine1; 171 | retVal!.detailTextLabel!.text = currObject.resultLine2; 172 | retVal!.imageView!.image = UIImage(named:"icon-contact"); 173 | retVal!.selectedBackgroundView!.backgroundColor = BLUE_HIGHLIGHT; 174 | retVal!.textLabel!.textColor = LIGHT_BLUE; 175 | retVal!.detailTextLabel!.textColor = MEDIUM_GREY; 176 | 177 | return retVal!; 178 | } 179 | 180 | func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 181 | if (currMode == .HomeViewDisplayRecents) 182 | { 183 | return "RECENT RECORDS"; 184 | } 185 | else 186 | { 187 | return "SEARCH RESULTS"; 188 | } 189 | } 190 | 191 | // MARK: UITableView Delegate 192 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 193 | 194 | var currObject = CRMObject(); 195 | if (currMode == .HomeViewDisplayRecents) 196 | { 197 | currObject = recents?[indexPath.row] as! CRMObject; 198 | } 199 | else 200 | { 201 | currObject = searchResults?[indexPath.row] as! CRMObject; 202 | } 203 | 204 | let details = ObjectDetailsViewController(); 205 | details.displayObject = currObject; 206 | 207 | LocalStorage.localStorage.addToRecents(currObject); 208 | 209 | self.navigationController?.pushViewController(details, animated: true); 210 | } 211 | 212 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 213 | return 26.0; 214 | } 215 | 216 | func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 217 | 218 | let header = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: tableView.delegate!.tableView!(tableView, heightForHeaderInSection: section))); 219 | header.backgroundColor = UIColor.whiteColor(); 220 | 221 | let textLabel = UILabel(); 222 | textLabel.text = tableView.dataSource?.tableView!(tableView, titleForHeaderInSection: section); 223 | textLabel.textColor = DARK_GREY; 224 | textLabel.font = textLabel.font.fontWithSize(14.0); 225 | textLabel.sizeToFit(); 226 | textLabel.frame = CGRectMake(20, 227 | (header.frame.size.height - textLabel.frame.size.height), 228 | textLabel.frame.size.width, 229 | textLabel.frame.size.height); 230 | header.addSubview(textLabel); 231 | 232 | return header; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /Activity Tracker/UI/NewActivityView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 44 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /Activity Tracker/UI/NewActivityViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewActivityViewController.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NewActivityViewController: UIViewController, UITextViewDelegate { 12 | var displayObject = CRMObject(); 13 | var activityType = String(); 14 | 15 | @IBOutlet weak var loadingIndicator : UIImageView!; 16 | @IBOutlet weak var detailsView : UIView!; 17 | @IBOutlet weak var mainLabel : UILabel!; 18 | @IBOutlet weak var secondaryLabel : UILabel!; 19 | @IBOutlet weak var thirdLabel : UILabel!; 20 | 21 | @IBOutlet weak var scroller : UIScrollView!; 22 | @IBOutlet weak var scrollerBackground : UIView!; 23 | 24 | @IBOutlet weak var subjectField : UITextField!; 25 | @IBOutlet weak var dateField : UITextField!; 26 | @IBOutlet weak var notesView : UITextView!; 27 | 28 | @IBOutlet var fieldLabels : [UILabel]!; 29 | 30 | var activityDate = NSDate(); 31 | var dateFormatter = NSDateFormatter(); 32 | 33 | convenience init() { 34 | self.init(nibName:"NewActivityView", bundle:nil); 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad(); 39 | 40 | for label in fieldLabels { 41 | label.textColor = DARK_GREY; 42 | } 43 | 44 | detailsView.backgroundColor = VERY_LIGHT_GREY; 45 | scrollerBackground.backgroundColor = LINE_COLOR; 46 | 47 | mainLabel.text = displayObject.detailLine1; 48 | mainLabel.textColor = LIGHT_BLUE; 49 | 50 | secondaryLabel.text = displayObject.detailLine2; 51 | secondaryLabel.textColor = MEDIUM_GREY; 52 | 53 | thirdLabel.text = displayObject.detailLine3; 54 | thirdLabel.textColor = MEDIUM_GREY; 55 | 56 | activityDate = NSDate(); 57 | dateFormatter.dateFormat = "M/d/yyyy"; 58 | 59 | dateField.text = dateFormatter.stringFromDate(activityDate); 60 | 61 | let datePicker = UIDatePicker(frame: CGRectZero); 62 | datePicker.date = activityDate; 63 | datePicker.datePickerMode = .Date; 64 | datePicker.addTarget(self, action:#selector(NewActivityViewController.dateChanged(_:)), forControlEvents:.ValueChanged); 65 | 66 | dateField.inputView = datePicker; 67 | 68 | self.title = self.activityType; 69 | 70 | subjectField.text = String(format:"%@ with %@ on %@", self.activityType, self.displayObject.resultLine1, dateFormatter.stringFromDate(activityDate)); 71 | 72 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit", style:.Plain, target:self, action:#selector(NewActivityViewController.submitTapped(_:))); 73 | self.navigationItem.rightBarButtonItem!.tintColor = LIGHT_BLUE; 74 | } 75 | 76 | override func viewDidAppear(animated: Bool) { 77 | super.viewDidAppear(animated); 78 | 79 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewActivityViewController.keyboardShow(_:)), name: UIKeyboardWillShowNotification, object: nil); 80 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(NewActivityViewController.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil); 81 | } 82 | 83 | override func viewWillDisappear(animated: Bool) { 84 | super.viewWillDisappear(animated); 85 | 86 | NSNotificationCenter.defaultCenter().removeObserver(self); 87 | } 88 | 89 | // The spinner methods show a custom progress indicator 90 | func animateSpinner() { 91 | loadingIndicator.hidden = false; 92 | let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z"); 93 | rotationAnimation.toValue = M_PI; 94 | rotationAnimation.duration = 0.5; 95 | rotationAnimation.cumulative = true; 96 | rotationAnimation.repeatCount = HUGE; 97 | 98 | loadingIndicator.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation"); 99 | } 100 | 101 | func stopSpinner() { 102 | loadingIndicator.hidden = true; 103 | loadingIndicator.layer.removeAnimationForKey("rotationAnimation"); 104 | } 105 | 106 | func dateChanged(sender:AnyObject) { 107 | 108 | let picker = sender as! UIDatePicker; 109 | if (subjectField.text == String(format:"%@ with %@ on %@", self.activityType, self.displayObject.resultLine1, dateFormatter.stringFromDate(activityDate))) 110 | { 111 | activityDate = picker.date; 112 | dateField.text = dateFormatter.stringFromDate(activityDate); 113 | subjectField.text = String(format:"%@ with %@ on %@", self.activityType, self.displayObject.resultLine1, dateFormatter.stringFromDate(activityDate)); 114 | } 115 | else 116 | { 117 | activityDate = picker.date; 118 | dateField.text = dateFormatter.stringFromDate(activityDate); 119 | } 120 | } 121 | 122 | func submitTapped(sender:AnyObject) { 123 | if (subjectField.text?.characters.count == 0) 124 | { 125 | let errorAlert = UIAlertController(title: "Subject Required", message: "A subject is required.", preferredStyle:.Alert); 126 | errorAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)); 127 | self.presentViewController(errorAlert, animated: true, completion: nil); 128 | return; 129 | } 130 | self.navigationItem.rightBarButtonItem!.enabled = false; 131 | self.navigationItem.setHidesBackButton(true, animated: true); 132 | self.view.endEditing(true); 133 | 134 | let newActivity = CRMActivity(); 135 | newActivity.activityType = "task"; 136 | newActivity.activityDate = activityDate; 137 | newActivity.activitySubject = subjectField.text!; 138 | newActivity.activityNotes = notesView.text; 139 | 140 | self.displayObject.recentActivities.insert(newActivity, atIndex: 0); 141 | 142 | animateSpinner(); 143 | 144 | CRMClient.sharedClient.create(newActivity, relatedTo: self.displayObject as! CRMContact) { (success) -> Void in 145 | if (success) 146 | { 147 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 148 | self.navigationController!.popViewControllerAnimated(true); 149 | self.stopSpinner(); 150 | }); 151 | } 152 | else 153 | { 154 | let errorAlert = UIAlertController(title: "Error", message: "There was an error creating the new activity. Please ensure you are connected to the internet.", preferredStyle:UIAlertControllerStyle.Alert); 155 | errorAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)); 156 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 157 | self.presentViewController(errorAlert, animated: true, completion: nil); 158 | self.navigationItem.rightBarButtonItem!.enabled = true; 159 | self.navigationItem.setHidesBackButton(false, animated: true); 160 | self.stopSpinner(); 161 | }); 162 | } 163 | }; 164 | } 165 | 166 | // MARK: Keyboard Notifications 167 | func keyboardShow(notification:NSNotification) { 168 | if let userInfo = notification.userInfo { 169 | let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue(), 170 | contentSize = CGSizeMake(scroller.frame.size.width, scroller.frame.size.height + keyboardFrame.size.height); 171 | scroller.contentSize = contentSize; 172 | scroller.scrollEnabled = true; 173 | } 174 | } 175 | 176 | func keyboardWillHide(notification:NSNotification) { 177 | scroller.scrollEnabled = false; 178 | scroller.setContentOffset(CGPointZero, animated:true); 179 | } 180 | 181 | // MARK: UITextView Delegate 182 | func textViewDidBeginEditing(textView: UITextView) { 183 | 184 | let scrollPoint = textView.superview!.frame.origin; 185 | scroller.setContentOffset(scrollPoint, animated:true); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /Activity Tracker/UI/ObjectDetailsView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 40 | 45 | 54 | 63 | 72 | 85 | 98 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 151 | 162 | 173 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /Activity Tracker/UI/ObjectDetailsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectDetailsViewController.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ObjectDetailsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { 12 | var displayObject = CRMObject(), 13 | CellIdentifier = "ResultsCell"; 14 | 15 | @IBOutlet weak var detailsView : UIView!; 16 | @IBOutlet weak var mainLabel : UILabel!; 17 | @IBOutlet weak var secondaryLabel : UILabel!; 18 | @IBOutlet weak var thirdLabel : UILabel!; 19 | @IBOutlet weak var addressLabel : UILabel!; 20 | @IBOutlet weak var phoneLabel : UILabel!; 21 | @IBOutlet weak var emailLabel : UILabel!; 22 | 23 | @IBOutlet weak var activitiesList : UITableView!; 24 | @IBOutlet weak var loadingIndicator : UIImageView!; 25 | 26 | convenience init() { 27 | self.init(nibName:"ObjectDetailsView", bundle:nil); 28 | } 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad(); 32 | 33 | self.title = "Contact"; 34 | 35 | detailsView.backgroundColor = VERY_LIGHT_GREY; 36 | mainLabel.textColor = LIGHT_BLUE; 37 | secondaryLabel.textColor = MEDIUM_GREY; 38 | thirdLabel.textColor = MEDIUM_GREY; 39 | addressLabel.textColor = DARK_GREY; 40 | phoneLabel.textColor = DARK_GREY; 41 | emailLabel.textColor = DARK_GREY; 42 | 43 | self.navigationItem.backBarButtonItem = UIBarButtonItem(title:"", style:.Plain, target:nil, action:nil); 44 | 45 | activitiesList.tableFooterView = UIView(frame: CGRectZero); 46 | } 47 | 48 | override func viewWillAppear(animated: Bool) { 49 | super.viewWillAppear(animated); 50 | 51 | refreshObject(); 52 | } 53 | 54 | override func viewDidAppear(animated: Bool) { 55 | super.viewDidAppear(animated); 56 | if (displayObject.recentActivities.count == 0) 57 | { 58 | animateSpinner(); 59 | } 60 | 61 | CRMClient.sharedClient.getRecentActivities(self.displayObject as! CRMContact) { (success) -> Void in 62 | if (success) 63 | { 64 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 65 | self.activitiesList.reloadData(); 66 | self.stopSpinner() 67 | }); 68 | } 69 | else 70 | { 71 | let errorAlert = UIAlertController(title: "Error", message: "There was an error retrieving the recent activities. Please ensure you are connected to the internet.", preferredStyle:UIAlertControllerStyle.Alert); 72 | errorAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)); 73 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 74 | self.presentViewController(errorAlert, animated: true, completion: nil); 75 | }); 76 | } 77 | }; 78 | 79 | CRMClient.sharedClient.getContactDetails(self.displayObject as! CRMContact) { (success) -> Void in 80 | if (success) 81 | { 82 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 83 | self.refreshObject(); 84 | }); 85 | 86 | LocalStorage.localStorage.addToRecents(self.displayObject); 87 | } 88 | else 89 | { 90 | let errorAlert = UIAlertController(title: "Error", message: "There was an error retrieving the contact's details. Please ensure you are connected to the internet.", preferredStyle:UIAlertControllerStyle.Alert); 91 | errorAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)); 92 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 93 | self.presentViewController(errorAlert, animated: true, completion: nil); 94 | }); 95 | } 96 | } 97 | } 98 | 99 | func refreshObject() { 100 | mainLabel.text = displayObject.detailLine1.dashesForEmpty(); 101 | secondaryLabel.text = displayObject.detailLine2.dashesForEmpty(); 102 | thirdLabel.text = displayObject.detailLine3.dashesForEmpty(); 103 | addressLabel.text = displayObject.addressInfo.dashesForEmpty(); 104 | phoneLabel.text = displayObject.mainPhone.dashesForEmpty(); 105 | emailLabel.text = displayObject.mainEmail.dashesForEmpty(); 106 | 107 | activitiesList.setContentOffset(CGPointZero, animated: false); 108 | } 109 | 110 | 111 | @IBAction func actionTapped(sender: AnyObject) { 112 | let activityView = NewActivityViewController(); 113 | activityView.displayObject = displayObject; 114 | 115 | let activityButton = sender as! UIButton; 116 | 117 | switch (activityButton.tag) 118 | { 119 | case 10: 120 | activityView.activityType = "Check In"; 121 | break; 122 | case 11: 123 | activityView.activityType = "Note"; 124 | break; 125 | case 12: 126 | activityView.activityType = "Follow Up"; 127 | break; 128 | case 13: 129 | activityView.activityType = "Phone Call"; 130 | break; 131 | default: 132 | break; 133 | } 134 | 135 | self.navigationController!.pushViewController(activityView, animated: true); 136 | } 137 | 138 | @IBAction func addressTapped(sender: AnyObject) { 139 | if (displayObject.addressInfo.characters.count == 0) 140 | { 141 | return; 142 | } 143 | 144 | let addressString = addressLabel.text?.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()), 145 | mapsURL = NSURL(string: String(format: "https://maps.apple.com?q=%@", arguments: [addressString!])); 146 | 147 | UIApplication.sharedApplication().openURL(mapsURL!); 148 | } 149 | 150 | @IBAction func phoneTapped(sender: AnyObject) { 151 | 152 | if (displayObject.mainPhone.characters.count == 0) 153 | { 154 | return; 155 | } 156 | 157 | let cleanedString = displayObject.mainPhone.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "0123456789-+()").invertedSet).joinWithSeparator(""), 158 | escapedPhoneNumber = cleanedString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet()), 159 | phoneURL = NSURL(string: String(format: "tel:%@", arguments: [escapedPhoneNumber!])); 160 | 161 | UIApplication.sharedApplication().openURL(phoneURL!); 162 | } 163 | 164 | @IBAction func emailTapped(sender: AnyObject) { 165 | 166 | if (displayObject.mainEmail.characters.count == 0) 167 | { 168 | return; 169 | } 170 | let emailURL = NSURL(string: String(format: "mailto:%@", arguments: [displayObject.mainEmail.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())!])); 171 | 172 | UIApplication.sharedApplication().openURL(emailURL!); 173 | } 174 | 175 | // The spinner methods show a custom progress indicator 176 | func animateSpinner() { 177 | loadingIndicator.hidden = false; 178 | let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z"); 179 | rotationAnimation.toValue = M_PI; 180 | rotationAnimation.duration = 0.5; 181 | rotationAnimation.cumulative = true; 182 | rotationAnimation.repeatCount = HUGE; 183 | 184 | loadingIndicator.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation"); 185 | } 186 | 187 | func stopSpinner() { 188 | loadingIndicator.hidden = true; 189 | loadingIndicator.layer.removeAnimationForKey("rotationAnimation"); 190 | } 191 | 192 | // MARK: UITableView DataSource 193 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 194 | return displayObject.recentActivities.count; 195 | } 196 | 197 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 198 | 199 | let dateFormatter = NSDateFormatter(); 200 | dateFormatter.dateFormat = "MMM d, yyyy"; 201 | 202 | var retVal = tableView.dequeueReusableCellWithIdentifier(CellIdentifier); 203 | if (retVal == nil) 204 | { 205 | retVal = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: CellIdentifier); 206 | } 207 | 208 | let currActivity = displayObject.recentActivities[indexPath.row]; 209 | 210 | retVal!.textLabel!.text = currActivity.activitySubject; 211 | retVal!.textLabel!.textColor = DARK_PURPLE; 212 | 213 | retVal!.detailTextLabel!.text = dateFormatter.stringFromDate(currActivity.activityDate); 214 | retVal!.detailTextLabel!.textColor = MEDIUM_GREY; 215 | 216 | retVal!.imageView!.image = currActivity.activityImage(); 217 | 218 | return retVal!; 219 | } 220 | 221 | func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 222 | return "RECENTLY COMPLETED ACTIVITIES"; 223 | } 224 | 225 | // MARK: UITableView Delegate 226 | func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? { 227 | return nil; 228 | } 229 | 230 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 231 | return 26.0; 232 | } 233 | 234 | func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 235 | 236 | let header = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.size.width, height: tableView.delegate!.tableView!(tableView, heightForHeaderInSection: section))); 237 | header.backgroundColor = UIColor.whiteColor(); 238 | 239 | let textLabel = UILabel(); 240 | textLabel.text = tableView.dataSource?.tableView!(tableView, titleForHeaderInSection: section); 241 | textLabel.textColor = DARK_GREY; 242 | textLabel.font = textLabel.font.fontWithSize(14.0); 243 | textLabel.sizeToFit(); 244 | textLabel.frame = CGRectMake(20, 245 | (header.frame.size.height - textLabel.frame.size.height), 246 | textLabel.frame.size.width, 247 | textLabel.frame.size.height); 248 | header.addSubview(textLabel); 249 | 250 | return header; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /Activity Tracker/UI/SettingsView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Activity Tracker/UI/SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // Activity Tracker 4 | // 5 | // Created by Ben Toepke on 4/18/16. 6 | // Copyright © 2016 Microsoft. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import ADAL 11 | 12 | class SettingsViewController: UIViewController { 13 | 14 | @IBOutlet weak var label: UILabel! 15 | @IBOutlet weak var endpointField: UITextField! 16 | 17 | convenience init() { 18 | self.init(nibName:"SettingsView", bundle:nil); 19 | } 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad(); 23 | 24 | self.title = "Settings"; 25 | 26 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(title:"Save", style:.Plain, target:self, action:#selector(SettingsViewController.saveTapped(_:))); 27 | self.navigationItem.rightBarButtonItem!.tintColor = LIGHT_BLUE; 28 | 29 | label.textColor = DARK_GREY; 30 | } 31 | 32 | override func viewWillAppear(animated: Bool) { 33 | super.viewWillAppear(animated); 34 | 35 | let hostString = LocalStorage.localStorage.getHost(); 36 | 37 | if (hostString != nil) { 38 | endpointField.text = hostString; 39 | } 40 | } 41 | 42 | func saveTapped(sender:AnyObject?) { 43 | 44 | let hostString = LocalStorage.localStorage.getHost(); 45 | 46 | if (hostString == endpointField.text) 47 | { 48 | self.navigationController!.popViewControllerAnimated(true); 49 | } 50 | else 51 | { 52 | CRMClient.sharedClient.setNewEndpoint(endpointField.text!, completionBlock: { (success) -> Void in 53 | if (success) 54 | { 55 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 56 | self.navigationController?.popViewControllerAnimated(true); 57 | }); 58 | } 59 | else 60 | { 61 | let errorAlert = UIAlertController(title: "Error", message: "There was an error logging you in. Check your endpoint URL and credentials.", preferredStyle:UIAlertControllerStyle.Alert); 62 | errorAlert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)); 63 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 64 | self.presentViewController(errorAlert, animated: true, completion: nil); 65 | }); 66 | } 67 | }); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Details.png -------------------------------------------------------------------------------- /Home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Home.png -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Microsoft Corporation 3 | 4 | All rights reserved.  5 | 6 | MIT License 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 9 | and associated documentation files (the ""Software""), to deal in the Software without restriction, 10 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all copies or substantial 15 | portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 18 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 19 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 21 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /NewActivity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/NewActivity.png -------------------------------------------------------------------------------- /PodFile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, ‘9.0’ 3 | # Uncomment this line if you're using Swift 4 | use_frameworks! 5 | 6 | target 'Activity Tracker' do 7 | 8 | pod 'ADAL', '~> 2.1.2' 9 | 10 | end 11 | 12 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - ADAL (2.1.2): 3 | - ADAL/iosinternalheaders (= 2.1.2) 4 | - ADAL/no-arc (= 2.1.2) 5 | - ADAL/tokencacheheader (= 2.1.2) 6 | - ADAL/no-arc (2.1.2) 7 | - ADAL/tokencacheheader (2.1.2) 8 | 9 | DEPENDENCIES: 10 | - ADAL (~> 2.1.2) 11 | 12 | SPEC CHECKSUMS: 13 | ADAL: 97f694ee4bea1090ffbc4cad26a7958f6d98d936 14 | 15 | COCOAPODS: 0.39.0 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Activity Tracker # 2 | 3 | The Activity Tracker sample demonstrates how to write a working iOS/Swift application that can access Microsoft Dynamics CRM 2016 business data by using the CRM Web API. When run, the sample allows you to query contacts and add a quick activity record related to a contact. Use this sample code as a starting point to build a more complete application, or refer to it when building a new project. 4 | 5 | ## Prerequisites ## 6 | 1. This code sample targets an iPhone running iOS 9 or later and XCode 7.3 or later 7 | 2. Your CRM instance must be running Dynamics CRM 2016 or later. 8 | 3. Your CRM instance must be configured for OAuth authentication. 9 | 4. This application requires [Cocoapods](https://cocoapods.org) to add the required ADAL (Azure Active Directory Authentication Library) module. 10 | 11 | ## Quick Start ## 12 | 1. Ensure your Active Directory setup meets the prerequisites listed on [MSDN](http://msdn.microsoft.com/en-us/library/dn531010.aspx). 13 | 2. Follow the steps in that article to register a new application with Azure Active Directory. 14 | If you are using an IFD/On-premises instance, ensure that you run the Powershell commands from the above article. 15 | 3. In a terminal window, navigate to the folder containing the PodFile. Run the commands 'pod setup' and then 'pod install' to add ADAL to the project. 16 | 4. Modify the values of these two constants in the Activity Tracker/CRMConnector/CRMClient.swift file. You obtain these values from when you registered an application in the Azure portal. 17 | 18 | var CLIENT_ID: String { get {return "1dc3cd16-85f4-449e-9145-98c996ea6a85";}} 19 | 20 | var REDIRECT_URI: String { get { return "http://crm.codesamples/";}}" 21 | 22 | 5. Build and run the Activity Tracker application. 23 | 6. On the settings screen of the app, type your CRM organization URL and select **Save**. You will be prompted to logon to your Dynamics CRM organization. 24 | 7. After logon, start searching for contacts by entering one or more letters in the search box. 25 | 26 | ## CRM Connector ## 27 | The classes in this folder are responsible for communicating with CRM, parsing the results, and storing recent records. The CRMClient class leverages the CRM Web API. 28 | 29 | You should register a new app ID with Microsoft Azure AD and replace these constants in CRMClient.swift with the clientID and redirect URL you choose. Relevent instructions can be found on [MSDN](http://msdn.microsoft.com/en-us/library/dn531010.aspx). 30 | 31 | var CLIENT_ID: String { get {return "1dc3cd16-85f4-449e-9145-98c996ea6a85";}} 32 | 33 | var REDIRECT_URI: String { get { return "http://crm.codesamples/";}}" 34 | 35 | The CRMClient class uses generic error handlers. You may want to expand on these and include more specific messages for your environment. 36 | 37 | ### Local Storage ### 38 | This class handles storing the recently viewed records and the current CRM organization URL that is being used. The class uses NSUserDefaults to store that information. If you are creating an app that is going to store information for offline use, you should leverage other storage options such as Core Data. This sample focuses on the interaction with CRM and is not intended to be a sample showing the other storage options. 39 | 40 | ## Objects ## 41 | The CRMObject class is the base class that defines the methods and properties that are common to all objects. 42 | 43 | The CRMContact class handles all instances of contacts that will be shown in the application. This class also starts to parse the contact's related activities list. 44 | 45 | The CRMActivity class handles both the recently completed activities related to a contact and the new activity records that can be created in this app. 46 | 47 | ## Views ## 48 | 49 | ### Settings View ### 50 | 51 | 52 | 55 | 58 | 59 |
53 | Unless you choose to hardcode an endpoint URL, you will want to display the settings page to the user on first launch. The settings view allows the user to enter their CRM server URL and then begins the authentication process. 54 | 56 | 57 |
60 | 61 | ### Home View ### 62 | 63 | 64 | 67 | 70 | 71 |
65 | This view allows you to access the records you have recently viewed or search for other records. 66 | 68 | 69 |
72 | 73 | ### Object Details View ### 74 | 75 | 76 | 79 | 82 | 83 |
77 | This view shows more details about the selected contact including recently completed records. The view also allows you to launch into a new completed task form. 78 | 80 | 81 |
84 | 85 | ### New Activity View ### 86 | 87 | 88 | 91 | 94 | 95 |
89 | This view allows you to create a completed task related to the selected contact. You can edit the subject, date, and add notes. When submitting, the task is created in CRM, and then marked completed. 90 | 92 | 93 |
96 | 97 | 98 | ### UI Styling ### 99 | There is a file called ColorsAndFonts that defines the colors that are used throughout this application. You can tweak the colors in the application or add new colors and use them to skin the application to suit your needs. 100 | 101 | ## Class Categories ## 102 | There is a class category in this folder on NSString. The StringFormatting category is used to ensure there is always something displayed in the UI where a string should be. -------------------------------------------------------------------------------- /Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DynamicsCRM/iOS-Activity-Tracker-for-Dynamics-CRM-Web-API/15836d0411b167d3b2d7ca0f0ff9d5f0c594ecbe/Settings.png --------------------------------------------------------------------------------