├── .gitignore ├── LICENSE ├── README.md ├── SF iOS ├── Coffup.entitlements ├── Gemfile ├── Gemfile.lock ├── Podfile ├── Podfile.lock ├── SF iOS.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── SF iOS.xcscheme ├── SF iOS │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-512x512@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── Contents.json │ │ ├── NotificationsDisable.imageset │ │ │ ├── Contents.json │ │ │ └── NotificationsDisable.pdf │ │ ├── NotificationsEnable.imageset │ │ │ ├── Contents.json │ │ │ └── NotificationsEnable.pdf │ │ ├── coffee-map-glyph.imageset │ │ │ ├── CoffeeMapGlyph.png │ │ │ ├── CoffeeMapGlyph@2x.png │ │ │ ├── CoffeeMapGlyph@3x.png │ │ │ └── Contents.json │ │ ├── icon-launch.imageset │ │ │ ├── Contents.json │ │ │ └── icon-app.pdf │ │ ├── icon-transport-type-automobile.imageset │ │ │ ├── Contents.json │ │ │ └── icon-transport-type-automobile.pdf │ │ ├── icon-transport-type-lyft.imageset │ │ │ ├── Contents.json │ │ │ └── icon-transport-type-lyft.pdf │ │ ├── icon-transport-type-transit.imageset │ │ │ ├── Contents.json │ │ │ └── icon-transport-type-transit.pdf │ │ ├── icon-transport-type-uber.imageset │ │ │ ├── Contents.json │ │ │ └── icon-transport-type-uber.pdf │ │ └── icon-transport-type-walking.imageset │ │ │ ├── Contents.json │ │ │ └── icon-transport-type-walking.pdf │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── EventDetails │ │ ├── DirectionsRequestHandler.h │ │ ├── EventDetailsViewController.h │ │ ├── EventDetailsViewController.m │ │ ├── NSAttributedString+EventDetails.h │ │ ├── NSAttributedString+EventDetails.m │ │ ├── TravelTimeView.h │ │ ├── TravelTimeView.m │ │ ├── TravelTimesView.h │ │ └── TravelTimesView.m │ ├── Feed │ │ ├── EventFeedDelegate.h │ │ ├── EventFeedDelegate.m │ │ ├── EventsFeedTableView.h │ │ ├── EventsFeedTableView.m │ │ ├── EventsFeedViewController.h │ │ ├── EventsFeedViewController.m │ │ ├── FeedItem.h │ │ ├── FeedItem.m │ │ ├── FeedItemCell.h │ │ ├── FeedItemCell.m │ │ ├── GroupAndFeedLoadingCoordinator.h │ │ └── GroupAndFeedLoadingCoordinator.m │ ├── GroupSelection │ │ ├── GroupCollectionView.h │ │ ├── GroupCollectionView.m │ │ ├── GroupCollectionViewController.h │ │ ├── GroupCollectionViewController.m │ │ ├── ImageBasedCollectionViewCell.h │ │ └── ImageBasedCollectionViewCell.m │ ├── HTTPRequestOperation │ │ ├── HTTPRequestAsyncOperation.h │ │ └── HTTPRequestAsyncOperation.m │ ├── Helpers │ │ ├── ActivityController │ │ │ ├── UIActivityViewController+Utilities.h │ │ │ └── UIActivityViewController+Utilities.m │ │ ├── Analytics.h │ │ ├── Analytics.m │ │ ├── Application │ │ │ ├── UIApplication+Metadata.h │ │ │ └── UIApplication+Metadata.m │ │ ├── Concurrency │ │ │ ├── AsyncBlockOperation.h │ │ │ ├── AsyncBlockOperation.m │ │ │ ├── AsyncOperation.h │ │ │ └── AsyncOperation.m │ │ ├── Errors │ │ │ ├── NSError+Constructor.h │ │ │ └── NSError+Constructor.m │ │ ├── MapCameraOverlookingLocations │ │ │ ├── MKMapCamera+OverlookingLocations.h │ │ │ └── MKMapCamera+OverlookingLocations.m │ │ ├── NSAttributedStringKerning │ │ │ ├── NSAttributedString+Kerning.h │ │ │ └── NSAttributedString+Kerning.m │ │ ├── NSDateUtilities │ │ │ ├── NSDate+Utilities.h │ │ │ └── NSDate+Utilities.m │ │ ├── Settings │ │ │ ├── NSUserDefaults+Settings.h │ │ │ └── NSUserDefaults+Settings.m │ │ ├── StackViews │ │ │ ├── UIStackView+ConvenienceInitializer.h │ │ │ └── UIStackView+ConvenienceInitializer.m │ │ ├── Styles │ │ │ ├── AffogatoStyle.h │ │ │ ├── AffogatoStyle.m │ │ │ ├── DynamicTypeFonts.h │ │ │ ├── DynamicTypeFonts.m │ │ │ ├── EspressoStyle.h │ │ │ ├── EspressoStyle.m │ │ │ ├── MochaStyle.h │ │ │ ├── MochaStyle.m │ │ │ ├── Style.h │ │ │ ├── Styleable.h │ │ │ ├── UIColor+SFiOSColors.h │ │ │ └── UIColor+SFiOSColors.m │ │ ├── UIViewController+ErrorHandling.h │ │ ├── UIViewController+ErrorHandling.m │ │ └── UserNotification │ │ │ ├── BackgroundFetchScheduler.h │ │ │ ├── BackgroundFetchScheduler.m │ │ │ ├── BackgroundFetcher.h │ │ │ ├── BackgroundFetcher.m │ │ │ ├── EventNotificationType.h │ │ │ ├── GroupBackgroundFetcher.h │ │ │ ├── GroupBackgroundFetcher.m │ │ │ ├── NSString+EventNotifications.h │ │ │ ├── NSString+EventNotifications.m │ │ │ ├── UNNotificationAttachment+EventNotifications.h │ │ │ ├── UNNotificationAttachment+EventNotifications.m │ │ │ ├── UNNotificationContent+EventNotifications.h │ │ │ ├── UNNotificationContent+EventNotifications.m │ │ │ ├── UNUserNotificationCenter+ConvenienceInitializer.h │ │ │ ├── UNUserNotificationCenter+ConvenienceInitializer.m │ │ │ ├── UNUserNotificationCenter+EventNotifications.h │ │ │ └── UNUserNotificationCenter+EventNotifications.m │ ├── ImageStore │ │ ├── ImageStore.h │ │ └── ImageStore.m │ ├── Info.plist │ ├── Location │ │ ├── DirectionsRequest.h │ │ ├── DirectionsRequest.m │ │ ├── FeedFetch │ │ │ ├── FeedCompletion.h │ │ │ ├── FeedFetchOperation.h │ │ │ ├── FeedFetchOperation.m │ │ │ ├── FeedFetchService.h │ │ │ └── FeedFetchService.m │ │ ├── GroupFetch │ │ │ ├── GroupCompletion.h │ │ │ ├── GroupFetchOperation.h │ │ │ ├── GroupFetchOperation.m │ │ │ ├── GroupFetchService.h │ │ │ └── GroupFetchService.m │ │ ├── MapView.h │ │ ├── MapView.m │ │ ├── TransportType.h │ │ ├── TravelTime │ │ │ ├── LyftTravelTimeEstimateOperation.h │ │ │ ├── LyftTravelTimeEstimateOperation.m │ │ │ ├── TravelTime+Arrival.h │ │ │ ├── TravelTime+Arrival.m │ │ │ ├── TravelTime.h │ │ │ ├── TravelTime.m │ │ │ ├── TravelTimeCalculationCompletion.h │ │ │ ├── TravelTimeService.h │ │ │ ├── TravelTimeService.m │ │ │ ├── UberTravelTimeEstimateOperation.h │ │ │ └── UberTravelTimeEstimateOperation.m │ │ ├── UserLocation.h │ │ └── UserLocation.m │ ├── Models │ │ ├── ApplicationEventNotifications │ │ │ ├── NSNotification+ApplicationEventNotifications.h │ │ │ └── NSNotification+ApplicationEventNotifications.m │ │ ├── Event.h │ │ ├── Event.m │ │ ├── EventDataSource.h │ │ ├── EventDataSource.m │ │ ├── EventType.h │ │ ├── FeedProvider.h │ │ ├── FeedProviderDelegate.h │ │ ├── Group.h │ │ ├── Group.m │ │ ├── GroupDataSource.h │ │ ├── GroupDataSource.m │ │ ├── Location.h │ │ ├── Location.m │ │ ├── Venue.h │ │ └── Venue.m │ ├── RemoteUIImage │ │ ├── UIImage+URL.h │ │ └── UIImage+URL.m │ ├── SF iOS-Debug.entitlements │ ├── SF iOS.entitlements │ ├── Secrets │ │ ├── SecretsStore.h │ │ ├── SecretsStore.m │ │ ├── secrets-example.plist │ │ └── secrets.plist │ ├── Settings │ │ ├── SettingsViewController.h │ │ └── SettingsViewController.m │ ├── SwipableNavigationContainer.h │ ├── SwipableNavigationContainer.m │ ├── UIKit Overrides │ │ ├── ChildViewControllerForwardingNavigationController.h │ │ ├── ChildViewControllerForwardingNavigationController.m │ │ ├── ChildViewControllerForwardingPageViewController.h │ │ └── ChildViewControllerForwardingPageViewController.m │ └── main.m ├── SF iOSTests │ ├── DateTests.m │ ├── EventDataSourceTests.m │ ├── EventNotificationCopyTests.m │ ├── EventTests.m │ ├── FeedItemTests.m │ ├── GroupTests.m │ ├── Info.plist │ ├── LocationTests.m │ ├── SettingsTests.m │ ├── TravelTime+ArrivalTests.m │ ├── UNNotificationContentTests.m │ └── VenueTests.m └── fastlane │ ├── Appfile │ ├── Deliverfile │ ├── Fastfile │ ├── README.md │ ├── metadata │ ├── app_icon.jpg │ ├── copyright.txt │ ├── en-us │ │ ├── apple_tv_privacy_policy.txt │ │ ├── description.txt │ │ ├── keywords.txt │ │ ├── marketing_url.txt │ │ ├── name.txt │ │ ├── privacy_url.txt │ │ ├── promotional_text.txt │ │ ├── release_notes.txt │ │ ├── subtitle.txt │ │ └── support_url.txt │ ├── primary_category.txt │ ├── primary_first_sub_category.txt │ ├── primary_second_sub_category.txt │ ├── review_information │ │ ├── demo_password.txt │ │ ├── demo_user.txt │ │ ├── email_address.txt │ │ ├── first_name.txt │ │ ├── last_name.txt │ │ ├── notes.txt │ │ └── phone_number.txt │ ├── secondary_category.txt │ ├── secondary_first_sub_category.txt │ ├── secondary_second_sub_category.txt │ └── trade_representative_contact_information │ │ ├── address_line1.txt │ │ ├── address_line2.txt │ │ ├── address_line3.txt │ │ ├── city_name.txt │ │ ├── country.txt │ │ ├── email_address.txt │ │ ├── first_name.txt │ │ ├── is_displayed_on_app_store.txt │ │ ├── last_name.txt │ │ ├── phone_number.txt │ │ ├── postal_code.txt │ │ ├── state.txt │ │ └── trade_name.txt │ └── screenshots │ └── en-US │ ├── 10_iphone65_10.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.15.28.png │ ├── 1_iphone65_1.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.14.34.png │ ├── 1_iphone6Plus_1.Simulator Screen Shot - iPhone 8 Plus - 2019-04-22 at 16.09.25.png │ ├── 2_iphone65_2.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.14.37.png │ ├── 2_iphone6Plus_2.Simulator Screen Shot - iPhone 8 Plus - 2019-04-22 at 16.10.48.png │ ├── 3_iphone65_3.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.18.27.png │ ├── 4_iphone65_4.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.14.45.png │ ├── 5_iphone65_5.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.15.11.png │ ├── 6_iphone65_6.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.19.11.png │ ├── 7_iphone65_7.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.15.19.png │ ├── 8_iphone65_8.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.15.25.png │ └── 9_iphone65_9.Simulator Screen Shot - iPhone Xs Max - 2019-05-29 at 22.19.20.png ├── Sketch-IOS-Icons-1.0.1.sketch ├── UX └── User Journey.overflow ├── screenshots.jpg └── sf-ios.sketch /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | report.xml 21 | SF\ iOS.xcworkspace 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | # CocoaPods 35 | SF\ iOS/Pods/ 36 | 37 | # Carthage 38 | # 39 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 40 | # Carthage/Checkouts 41 | 42 | Carthage/Build 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 50 | 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots 54 | fastlane/test_output 55 | 56 | # Code Injection 57 | # 58 | # After new code Injection tools there's a generated folder /iOSInjectionProject 59 | # https://github.com/johnno1962/injectionforxcode 60 | 61 | iOSInjectionProject/ 62 | 63 | # macOS 64 | .DS_Store 65 | 66 | # Secrets 67 | SF\ iOS/SF\ iOS/Secrets/secrets.plist 68 | 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Amit Jain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![screenshot](https://github.com/gravicle/sf-ios/blob/master/screenshots.jpg) 2 | 3 | # sf-ios 4 | An app for #sf-coffee and #sf-beer events from the iOS Folks Slack. 5 | 6 | ## Why Obj-C? 7 | For interviews, I needed to become comfortable again with Obj-C. What better way than to write an app? 8 | 9 | ## Getting the app 10 | 11 | Join the TestFlight: [SF iOS testflight](https://testflight.apple.com/join/9OaIrtAO) 12 | 13 | ## Building from source 14 | 15 | For travel time estimates, it uses Uber and Lyft REST APIs and the corresponding credentials are stored in `Secrets/secrets.plist` file. This file, for obvious reasons, is not checked-in into version control. To build and run: 16 | 17 | 1. Duplicate `Secrets/secrets-example.plist` and rename it to `secrets.plist`. you can build the project now but Uber and Lyft travel times will not be available. 18 | 2. Get [Uber](https://auth.uber.com/login/?next_url=https%3A%2F%2Fdeveloper.uber.com%2Fdashboard%2F&state=jZgX3-jJNzOiN57ly8Tv0uY0ArFXStNvQsjM_mzcYdg%3D) and [Lyft](https://www.lyft.com/developers/manage) `client-id` and `server-token` and populate `secrets.plist`. 19 | 20 | The app fetches data from [CoffeeCoffeeCoffee](http://coffeecoffeecoffee.coffee) which is also an [open source project](https://github.com/jamescmartinez/coffee) 21 | 22 | ## Continuous Integration 23 | 24 | We use [Bitrise](https://app.bitrise.io/referral/558bfe07e92b5498) for CI. If you'd like to help with that or learn more please use the link for referral goodies. 25 | 26 | [![Build Status](https://app.bitrise.io/app/3cef125d856baef9/status.svg?token=OgCXUkVmOo5Sbl4dMSVXVA&branch=master)](https://app.bitrise.io/app/3cef125d856baef9) 27 | -------------------------------------------------------------------------------- /SF iOS/Coffup.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | aps-environment 6 | development 7 | com.apple.developer.icloud-container-identifiers 8 | 9 | iCloud.$(CFBundleIdentifier) 10 | 11 | com.apple.developer.icloud-services 12 | 13 | CloudKit 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /SF iOS/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem "cocoapods" 5 | -------------------------------------------------------------------------------- /SF iOS/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | target 'Coffup' do 5 | # Uncomment the next line if you're using Swift or would like to use dynamic frameworks 6 | # use_frameworks! 7 | 8 | # Pods for Coffup 9 | pod 'Realm', '3.14.2' 10 | 11 | target 'SF iOSTests' do 12 | inherit! :search_paths 13 | pod 'Realm/Headers' 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /SF iOS/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Realm (3.14.2): 3 | - Realm/Headers (= 3.14.2) 4 | - Realm/Headers (3.14.2) 5 | 6 | DEPENDENCIES: 7 | - Realm (= 3.14.2) 8 | - Realm/Headers 9 | 10 | SPEC REPOS: 11 | https://github.com/cocoapods/specs.git: 12 | - Realm 13 | 14 | SPEC CHECKSUMS: 15 | Realm: 179c6706e3a49dbfe112e572fd3c03d3169bea35 16 | 17 | PODFILE CHECKSUM: edd33cc8a0cb0a6c6e3087a725c77467b8db2605 18 | 19 | COCOAPODS: 1.6.2 20 | -------------------------------------------------------------------------------- /SF iOS/SF iOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SF iOS/SF iOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/28/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/28/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "NSNotification+ApplicationEventNotifications.h" 11 | #import "SwipableNavigationContainer.h" 12 | #import 13 | #import "BackgroundFetchScheduler.h" 14 | 15 | @interface AppDelegate () 16 | 17 | @property (nonatomic) SwipableNavigationContainer *navigationContainer; 18 | @property (nonatomic) BackgroundFetchScheduler *backgroundFetchScheduler; 19 | 20 | @end 21 | 22 | @implementation AppDelegate 23 | 24 | - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 25 | [self migrations]; 26 | self.navigationContainer = [[SwipableNavigationContainer alloc] init]; 27 | self.backgroundFetchScheduler = [[BackgroundFetchScheduler alloc] init]; 28 | 29 | // background tasks must be registered before didFinishLaunchingWithOptions: returns 30 | [self.backgroundFetchScheduler registerLaunchHandlers]; 31 | [self.backgroundFetchScheduler schedule]; 32 | 33 | return YES; 34 | } 35 | 36 | - (void)migrations { 37 | RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; 38 | // Set the new schema version. This must be greater than the previously used 39 | // version (if you've never set a schema version before, the version is 0). 40 | config.schemaVersion = 1; 41 | 42 | // Set the block which will be called automatically when opening a Realm with a 43 | // schema version lower than the one set above 44 | config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) { 45 | // We haven’t migrated anything yet, so oldSchemaVersion == 0 46 | if (oldSchemaVersion < 1) { 47 | // Nothing to do! 48 | // Realm will automatically detect new properties and removed properties 49 | // And will update the schema on disk automatically 50 | } 51 | }; 52 | 53 | // Tell Realm to use this new configuration object for the default Realm 54 | [RLMRealmConfiguration setDefaultConfiguration:config]; 55 | 56 | // Now that we've told Realm how to handle the schema change, opening the file 57 | // will automatically perform the migration 58 | [RLMRealm defaultRealm]; 59 | } 60 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 61 | [self setSharedNetworkCacheMemoryMegabytes:5 62 | diskMegabytes:25]; 63 | 64 | [self.navigationContainer.window makeKeyAndVisible]; 65 | return YES; 66 | } 67 | 68 | - (void)applicationDidBecomeActive:(UIApplication *)application { 69 | // 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. 70 | [[UIApplication sharedApplication] setApplicationIconBadgeNumber:0]; 71 | 72 | [[NSNotificationCenter defaultCenter] postNotificationName:NSNotification.applicationBecameActiveNotification object:nil]; 73 | } 74 | 75 | - (UIWindow *)window { 76 | return self.navigationContainer.window; 77 | } 78 | 79 | // MARK: - Background Fetch 80 | - (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { 81 | [self.backgroundFetchScheduler startWithCompletionHandler:completionHandler]; 82 | } 83 | 84 | //MARK: - Configure cache 85 | - (void)setSharedNetworkCacheMemoryMegabytes:(NSInteger)memoryMiB diskMegabytes:(NSInteger)diskMiB { 86 | NSUInteger cashSize = memoryMiB * 1024 * 1024; 87 | NSUInteger cashDiskSize = diskMiB * 1024 * 1024; 88 | NSURLCache *imageCache = [[NSURLCache alloc] initWithMemoryCapacity:cashSize 89 | diskCapacity:cashDiskSize 90 | diskPath:@"networking"]; 91 | [NSURLCache setSharedURLCache:imageCache]; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "size" : "29x29", 15 | "idiom" : "iphone", 16 | "filename" : "Icon-App-29x29@2x.png", 17 | "scale" : "2x" 18 | }, 19 | { 20 | "size" : "29x29", 21 | "idiom" : "iphone", 22 | "filename" : "Icon-App-29x29@3x.png", 23 | "scale" : "3x" 24 | }, 25 | { 26 | "size" : "40x40", 27 | "idiom" : "iphone", 28 | "filename" : "Icon-App-40x40@2x.png", 29 | "scale" : "2x" 30 | }, 31 | { 32 | "size" : "40x40", 33 | "idiom" : "iphone", 34 | "filename" : "Icon-App-40x40@3x.png", 35 | "scale" : "3x" 36 | }, 37 | { 38 | "size" : "60x60", 39 | "idiom" : "iphone", 40 | "filename" : "Icon-App-60x60@2x.png", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "size" : "60x60", 45 | "idiom" : "iphone", 46 | "filename" : "Icon-App-60x60@3x.png", 47 | "scale" : "3x" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "size" : "20x20", 52 | "scale" : "1x" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "size" : "20x20", 57 | "scale" : "2x" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "size" : "29x29", 62 | "scale" : "1x" 63 | }, 64 | { 65 | "idiom" : "ipad", 66 | "size" : "29x29", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "idiom" : "ipad", 71 | "size" : "40x40", 72 | "scale" : "1x" 73 | }, 74 | { 75 | "idiom" : "ipad", 76 | "size" : "40x40", 77 | "scale" : "2x" 78 | }, 79 | { 80 | "size" : "76x76", 81 | "idiom" : "ipad", 82 | "filename" : "Icon-App-76x76@1x.png", 83 | "scale" : "1x" 84 | }, 85 | { 86 | "size" : "76x76", 87 | "idiom" : "ipad", 88 | "filename" : "Icon-App-76x76@2x.png", 89 | "scale" : "2x" 90 | }, 91 | { 92 | "size" : "83.5x83.5", 93 | "idiom" : "ipad", 94 | "filename" : "Icon-App-83.5x83.5@2x.png", 95 | "scale" : "2x" 96 | }, 97 | { 98 | "size" : "1024x1024", 99 | "idiom" : "ios-marketing", 100 | "filename" : "Icon-App-512x512@2x.png", 101 | "scale" : "1x" 102 | } 103 | ], 104 | "info" : { 105 | "version" : 1, 106 | "author" : "xcode" 107 | } 108 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-512x512@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/NotificationsDisable.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "NotificationsDisable.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/NotificationsDisable.imageset/NotificationsDisable.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/NotificationsDisable.imageset/NotificationsDisable.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/NotificationsEnable.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "NotificationsEnable.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/NotificationsEnable.imageset/NotificationsEnable.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/NotificationsEnable.imageset/NotificationsEnable.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/CoffeeMapGlyph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/CoffeeMapGlyph.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/CoffeeMapGlyph@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/CoffeeMapGlyph@2x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/CoffeeMapGlyph@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/CoffeeMapGlyph@3x.png -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/coffee-map-glyph.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "CoffeeMapGlyph.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "CoffeeMapGlyph@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "CoffeeMapGlyph@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-launch.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-app.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-launch.imageset/icon-app.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/icon-launch.imageset/icon-app.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-automobile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-transport-type-automobile.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-automobile.imageset/icon-transport-type-automobile.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/icon-transport-type-automobile.imageset/icon-transport-type-automobile.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-lyft.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-transport-type-lyft.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-lyft.imageset/icon-transport-type-lyft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/icon-transport-type-lyft.imageset/icon-transport-type-lyft.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-transit.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-transport-type-transit.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-transit.imageset/icon-transport-type-transit.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/icon-transport-type-transit.imageset/icon-transport-type-transit.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-uber.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-transport-type-uber.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-uber.imageset/icon-transport-type-uber.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/icon-transport-type-uber.imageset/icon-transport-type-uber.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-walking.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-transport-type-walking.pdf" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } -------------------------------------------------------------------------------- /SF iOS/SF iOS/Assets.xcassets/icon-transport-type-walking.imageset/icon-transport-type-walking.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ThumbWorks/sf-ios/0c313e6ede354271556cabc32810c45f408031df/SF iOS/SF iOS/Assets.xcassets/icon-transport-type-walking.imageset/icon-transport-type-walking.pdf -------------------------------------------------------------------------------- /SF iOS/SF iOS/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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/EventDetails/DirectionsRequestHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // DirectionsRequestHandler.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/5/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #ifndef DirectionsRequestHandler_h 10 | #define DirectionsRequestHandler_h 11 | 12 | typedef void(^DirectionsRequestHandler)(TransportType transportType); 13 | 14 | #endif /* DirectionsRequestHandler_h */ 15 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/EventDetails/EventDetailsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventDetailsViewController.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Event.h" 11 | #import "Styleable.h" 12 | #import "UserLocation.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | @interface EventDetailsViewController : UIViewController 16 | 17 | - (instancetype)initWithEvent:(Event *)event groupName:(nullable NSString *)groupName userLocationService:(nullable UserLocation *)userLocation NS_DESIGNATED_INITIALIZER; 18 | 19 | @end 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/EventDetails/NSAttributedString+EventDetails.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+EventDetails.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/8/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Event.h" 11 | 12 | @interface NSAttributedString (EventDetails) 13 | 14 | + (NSAttributedString *)attributedDetailsStringFromEvent:(Event *)event; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/EventDetails/NSAttributedString+EventDetails.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+EventDetails.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/8/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedString+EventDetails.h" 10 | #import "NSDate+Utilities.h" 11 | #import "NSAttributedString+Kerning.h" 12 | 13 | @implementation NSAttributedString (EventDetails) 14 | 15 | + (NSAttributedString *)attributedDetailsStringFromEvent:(Event *)event { 16 | NSDate *eventDate = event.date; 17 | NSDate *currentDate = [NSDate new]; 18 | NSString *timeLabelText; 19 | 20 | // If the event is ongoing: "Ends in 45mins" 21 | if ([currentDate isBetweenEarlierDate:eventDate laterDate:event.endDate]) { 22 | NSString *remainingtime = [event.endDate abbreviatedTimeintervalFromNow]; 23 | timeLabelText = [NSString stringWithFormat:@"Ends in %@", remainingtime]; 24 | } 25 | // If the event is upcoming today: "in 32min" 26 | else if (eventDate.isInFuture && eventDate.isToday) { 27 | timeLabelText = [NSString stringWithFormat:@"in %@", eventDate.abbreviatedTimeintervalFromNow]; 28 | } 29 | // If the event is upcoming this week: "Wednesday 8:30 - 10:00am" 30 | else if (eventDate.isInFuture && eventDate.isThisWeek) { 31 | NSString *weekday = [eventDate weekdayName]; 32 | NSString *time = [NSDate timeslotStringFromStartDate:eventDate duration:event.duration]; 33 | timeLabelText = [NSString stringWithFormat:@"%@ %@", weekday, time]; 34 | } 35 | // Otherwise: "Oct 11 8:30 - 10:00am" 36 | else { 37 | NSString *date = [eventDate stringWithformat:@"MMM d"]; 38 | NSString *time = [NSDate timeslotStringFromStartDate:eventDate duration:event.duration]; 39 | timeLabelText = [NSString stringWithFormat:@"%@ %@", date, time]; 40 | } 41 | NSString *subTitleText = timeLabelText; 42 | if (event.venueName) { 43 | subTitleText = [event.venueName stringByAppendingFormat:@"\n%@", timeLabelText]; 44 | } 45 | return [NSAttributedString kernedStringFromString:[subTitleText uppercaseString]]; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/EventDetails/TravelTimeView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TravelTimeView.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/3/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Styleable.h" 11 | #import "TravelTime.h" 12 | #import "DirectionsRequestHandler.h" 13 | #import "TravelTime+Arrival.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | @interface TravelTimeView : UIControl 17 | 18 | - (instancetype)initWithTravelTime:(TravelTime *)travelTime arrival:(Arrival)arrival directionsRequestHandler:(DirectionsRequestHandler)directionsRequestHandler noDirectionsAvailableHandler:(DirectionsRequestHandler)noDirectionsAvailableHandler NS_DESIGNATED_INITIALIZER; 19 | 20 | @end 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/EventDetails/TravelTimesView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TravelTimesView.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/3/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Styleable.h" 11 | #import "TravelTime.h" 12 | #import "DirectionsRequestHandler.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | @interface TravelTimesView : UIStackView 16 | 17 | @property (nonatomic, assign) BOOL loading; 18 | 19 | - (instancetype)initWithDirectionsRequestHandler:(DirectionsRequestHandler)directionsRequestHandler noDirectionsAvailableHandler:(DirectionsRequestHandler)noDirectionsAvailableHandler NS_DESIGNATED_INITIALIZER; 20 | 21 | - (void)configureWithTravelTimes:(NSArray *)travelTimes eventStartDate:(NSDate *)startDate endDate:(NSDate *)endDate; 22 | 23 | @end 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/EventFeedDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventFeedDelegate.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import 9 | #import "FeedProviderDelegate.h" 10 | #import 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface EventFeedDelegate : NSObject 14 | - (instancetype)initWithTableView:(UITableView *)tableView; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/EventFeedDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // EventFeedDelegate.m 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import "EventFeedDelegate.h" 9 | #import "EventDataSource.h" 10 | 11 | @interface EventFeedDelegate() 12 | @property (nonatomic) UITableView *tableView; 13 | @end 14 | 15 | @implementation EventFeedDelegate 16 | - (instancetype)initWithTableView:(UITableView *)tableView { 17 | if (self = [super init]) { 18 | self.tableView = tableView; 19 | } 20 | return self; 21 | } 22 | 23 | - (void)willUpdateDataSource:(id)datasource { 24 | [self.tableView.refreshControl beginRefreshing]; 25 | } 26 | 27 | - (void)didChangeDataSource:(id)datasource 28 | withInsertions:(nullable NSArray *)insertions 29 | updates:(nullable NSArray *)updates 30 | deletions:(nullable NSArray *)deletions { 31 | 32 | EventDataSource *eventDataSource = (EventDataSource *)datasource; 33 | // Don’t crash the app by modifying the table while the user is searching 34 | if (eventDataSource.searchQuery.length > 0) { return; } 35 | 36 | // Otherwise update on changes 37 | dispatch_async(dispatch_get_main_queue(), ^{ 38 | [self.tableView.refreshControl endRefreshing]; 39 | // [self updateNotificationButton]; 40 | if (!insertions && !updates && !deletions) { 41 | [self.tableView reloadData]; 42 | NSIndexPath *nextEventIndexPath = [NSIndexPath indexPathForRow:eventDataSource.indexOfCurrentEvent 43 | inSection:0]; 44 | [self.tableView scrollToRowAtIndexPath:nextEventIndexPath 45 | atScrollPosition:UITableViewScrollPositionTop 46 | animated:true]; 47 | return; 48 | } 49 | 50 | [self.tableView beginUpdates]; 51 | [self.tableView deleteRowsAtIndexPaths:deletions 52 | withRowAnimation:UITableViewRowAnimationAutomatic]; 53 | [self.tableView insertRowsAtIndexPaths:insertions 54 | withRowAnimation:UITableViewRowAnimationTop]; 55 | [self.tableView reloadRowsAtIndexPaths:updates 56 | withRowAnimation:UITableViewRowAnimationNone]; 57 | [self.tableView endUpdates]; 58 | [self.tableView.refreshControl endRefreshing]; 59 | 60 | }); 61 | 62 | } 63 | 64 | - (void)didFailToUpdateDataSource:(id)datasource withError:(NSError *)error { 65 | [self.tableView.refreshControl endRefreshing]; 66 | } 67 | @end 68 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/EventsFeedTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventsFeedTableView.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface EventsFeedTableView : UITableView 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/EventsFeedTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // EventsFeedTableView.m 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import "EventsFeedTableView.h" 9 | #import "FeedItemCell.h" 10 | 11 | 12 | @implementation EventsFeedTableView 13 | static CGFloat const eventCellAspectRatio = 1.352; 14 | 15 | - (CGFloat)cellHeight { 16 | return [UIScreen mainScreen].bounds.size.width * eventCellAspectRatio; 17 | } 18 | 19 | - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { 20 | if (self = [super initWithFrame:CGRectZero style:UITableViewStylePlain]) { 21 | [self registerClass:[FeedItemCell class] forCellReuseIdentifier:NSStringFromClass([FeedItemCell class])]; 22 | self.rowHeight = self.cellHeight; 23 | self.separatorStyle = UITableViewCellSeparatorStyleNone; 24 | self.tableHeaderView.backgroundColor = UIColor.clearColor; 25 | self.translatesAutoresizingMaskIntoConstraints = false; 26 | self.delaysContentTouches = NO; 27 | } 28 | return self; 29 | } 30 | @end 31 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/EventsFeedViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // EventsFeedViewController.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "EventDataSource.h" 11 | #import "Styleable.h" 12 | 13 | typedef NS_ENUM(NSInteger, FeedSections) { 14 | FeedSectionsEvents, 15 | FeedSectionsCount 16 | }; 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | @interface EventsFeedViewController : UIViewController 21 | 22 | - (instancetype)initWithDataSource:(EventDataSource *)eventDataSource tableView:(UITableView *)tableView NS_DESIGNATED_INITIALIZER; 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/FeedItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItem.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "Event.h" 11 | @import UIKit; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | @interface FeedItem : NSObject 15 | 16 | @property (nonatomic) NSString *dateString; 17 | @property (nonatomic) NSString *title; 18 | @property (nonatomic) NSAttributedString *subtitle; 19 | @property (nonatomic) UIImage *annotationGlyph; 20 | @property (readonly, assign, nonatomic) BOOL isActive; 21 | @property (nullable, nonatomic) NSURL *coverImageFileURL; 22 | 23 | - (instancetype)initWithEvent:(Event *)event; 24 | 25 | @end 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/FeedItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItem.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "FeedItem.h" 10 | #import "NSDate+Utilities.h" 11 | #import "NSAttributedString+EventDetails.h" 12 | #import "Venue.h" 13 | 14 | @interface FeedItem () 15 | 16 | @property (readwrite, assign, nonatomic) BOOL isActive; 17 | 18 | @end 19 | 20 | @implementation FeedItem 21 | 22 | - (instancetype)initWithEvent:(Event *)event { 23 | self = [super init]; 24 | if (!self) { 25 | return nil; 26 | } 27 | self.dateString = [event.date dateString]; 28 | self.title = event.name; 29 | self.isActive = event.isActive; 30 | if (![event.imageFileURLString isEqual:[NSNull null]]) { 31 | self.coverImageFileURL = event.imageFileURL; 32 | } 33 | self.annotationGlyph = event.annotationGlyph; 34 | 35 | NSString *time; 36 | 37 | if ([[NSDate new] isBetweenEarlierDate:event.date laterDate:event.endDate]) { 38 | time = @"Now"; 39 | } else { 40 | time = [NSDate timeslotStringFromStartDate:event.date duration:event.duration]; 41 | } 42 | 43 | self.subtitle = [NSAttributedString attributedDetailsStringFromEvent:event]; 44 | 45 | return self; 46 | } 47 | 48 | - (BOOL)directionsAreRelevantForEventWithDate:(NSDate *)date { 49 | if (date.isToday || date.isInFuture) { 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/FeedItemCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FeedItemCell.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FeedItem.h" 11 | #import "Styleable.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface FeedItemCell : UITableViewCell 16 | 17 | @property (readonly, assign, nonatomic) CGRect contentFrame; 18 | @property (readonly, assign, nonatomic) CGSize coverImageSize; 19 | 20 | - (void)configureWithFeedItem:(FeedItem *)item; 21 | - (void)setCoverToImage:(UIImage *)image; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/GroupAndFeedLoadingCoordinator.h: -------------------------------------------------------------------------------- 1 | // 2 | // GroupAndFeedLoadingCoordinator.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import 9 | #import "FeedProviderDelegate.h" 10 | #import 11 | 12 | @class GroupDataSource; 13 | @class EventsFeedViewController; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface GroupAndFeedLoadingCoordinator : NSObject 18 | - (instancetype)initWithGroupDataSource:(GroupDataSource *)groupDataSource tableView:(UITableView *)tableView feedViewController:(EventsFeedViewController *)eventsFeedViewController; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Feed/GroupAndFeedLoadingCoordinator.m: -------------------------------------------------------------------------------- 1 | // 2 | // GroupAndFeedLoadingCoordinator.m 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import "GroupAndFeedLoadingCoordinator.h" 9 | #import 10 | #import "Group.h" 11 | #import "GroupDataSource.h" 12 | #import "EventDataSource.h" 13 | #import "EventsFeedViewController.h" 14 | 15 | @interface GroupAndFeedLoadingCoordinator() 16 | @property (nonatomic) UITableView *tableView; 17 | @property (nonatomic) GroupDataSource *groupDataSource; 18 | @property (nonatomic) EventDataSource *eventDataSource; 19 | @property (nonatomic) EventsFeedViewController *eventsFeedViewController; 20 | @end 21 | 22 | @implementation GroupAndFeedLoadingCoordinator 23 | 24 | - (instancetype)initWithGroupDataSource:(GroupDataSource *)groupDataSource tableView:(UITableView *)tableView feedViewController:(EventsFeedViewController *)eventsFeedViewController { 25 | if (self == [super init]) { 26 | _groupDataSource = groupDataSource; 27 | _tableView = tableView; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)willUpdateDataSource:(id)datasource { 33 | [self.tableView.refreshControl beginRefreshing]; 34 | } 35 | 36 | - (void)didChangeDataSource:(id)datasource 37 | withInsertions:(nullable NSArray *)insertions 38 | updates:(nullable NSArray *)updates 39 | deletions:(nullable NSArray *)deletions { 40 | 41 | if (datasource == self.groupDataSource) { 42 | dispatch_async(dispatch_get_main_queue(), ^{ 43 | // [self updateNotificationButton]; 44 | [self.tableView.refreshControl endRefreshing]; 45 | }); 46 | if (!insertions && !updates && !deletions) { 47 | NSLog(@"Empty update"); 48 | return; 49 | } 50 | Group *group = [self.groupDataSource selectedGroup]; 51 | if (!group) { 52 | group = [self.groupDataSource groupAtIndex:0]; 53 | [self.groupDataSource selectGroup:group]; 54 | } 55 | [self.eventsFeedViewController updateWithGroup:group]; 56 | } else if (datasource == self.eventDataSource) { 57 | // Don’t crash the app by modifying the table while the user is searching 58 | if (self.eventDataSource.searchQuery.length > 0) { return; } 59 | 60 | // Otherwise update on changes 61 | dispatch_async(dispatch_get_main_queue(), ^{ 62 | [self.tableView.refreshControl endRefreshing]; 63 | // [self updateNotificationButton]; 64 | if (!insertions && !updates && !deletions) { 65 | NSLog(@"Empty update"); 66 | [self.tableView reloadData]; 67 | return; 68 | } 69 | 70 | [self.tableView beginUpdates]; 71 | [self.tableView deleteRowsAtIndexPaths:deletions 72 | withRowAnimation:UITableViewRowAnimationAutomatic]; 73 | [self.tableView insertRowsAtIndexPaths:insertions 74 | withRowAnimation:UITableViewRowAnimationTop]; 75 | [self.tableView reloadRowsAtIndexPaths:updates 76 | withRowAnimation:UITableViewRowAnimationNone]; 77 | [self.tableView endUpdates]; 78 | [self.tableView.refreshControl endRefreshing]; 79 | }); 80 | } 81 | } 82 | 83 | - (void)didFailToUpdateDataSource:(id)datasource withError:(NSError *)error { 84 | if (datasource == self.groupDataSource) { 85 | 86 | } else if (datasource == self.eventDataSource) { 87 | [self.tableView.refreshControl endRefreshing]; 88 | } 89 | } 90 | @end 91 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/GroupSelection/GroupCollectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GroupCollectionView.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface GroupCollectionView : UICollectionView 13 | 14 | + (GroupCollectionView *)view; 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/GroupSelection/GroupCollectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GroupCollectionView.m 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 5/8/19. 6 | // 7 | 8 | #import "GroupCollectionView.h" 9 | 10 | @implementation GroupCollectionView 11 | 12 | + (GroupCollectionView *)view { 13 | UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; 14 | layout.scrollDirection = UICollectionViewScrollDirectionVertical; 15 | layout.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 16 | layout.minimumInteritemSpacing = 8.0; 17 | layout.minimumLineSpacing = 20.0; 18 | 19 | GroupCollectionView *view = [[self alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; 20 | view.showsHorizontalScrollIndicator = false; 21 | view.translatesAutoresizingMaskIntoConstraints = false; 22 | 23 | return view; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/GroupSelection/GroupCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // GroupCollectionViewController.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 4/18/19. 6 | // 7 | 8 | #import 9 | #import "FeedProvider.h" 10 | #import "FeedProviderDelegate.h" 11 | #import "Styleable.h" 12 | 13 | @class Group; 14 | @class GroupCollectionViewController; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | @protocol GroupCollectionViewControllerDelegate 19 | 20 | - (void)controller:(GroupCollectionViewController *)controller tappedGroup:(Group *)group; 21 | 22 | @end 23 | 24 | @interface GroupCollectionViewController : UIViewController 25 | - (instancetype)initWithDataSource:(id)dataSource; 26 | @property (nonatomic, weak) id selectionDelegate; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/GroupSelection/ImageBasedCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageBasedCollectionViewCell.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 4/17/19. 6 | // 7 | 8 | #import 9 | #import "Styleable.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ImageBasedCollectionViewCell : UICollectionViewCell 14 | - (void)updateWithImage:(UIImage *)image title:(NSString *)title; 15 | + (NSString *)reuseID; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/HTTPRequestOperation/HTTPRequestAsyncOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPRequestAsyncOperation.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/5/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "AsyncOperation.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface HTTPRequestAsyncOperation : AsyncOperation 13 | 14 | typedef NSDictionary JSON; 15 | 16 | typedef void(^HTTPRequestCompletionHandler)(JSON * _Nullable json, NSURLResponse * _Nullable response, NSError * _Nullable error); 17 | 18 | - (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(HTTPRequestCompletionHandler)completionHandler; 19 | 20 | @end 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/HTTPRequestOperation/HTTPRequestAsyncOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPRequestAsyncOperation.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/5/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "HTTPRequestAsyncOperation.h" 10 | 11 | @interface HTTPRequestAsyncOperation () 12 | 13 | @property (nonatomic) NSURLSessionDataTask *task; 14 | 15 | @end 16 | 17 | @implementation HTTPRequestAsyncOperation 18 | 19 | - (instancetype)initWithRequest:(NSURLRequest *)request completionHandler:(HTTPRequestCompletionHandler)completionHandler { 20 | self = [super init]; 21 | if (!self) { 22 | return nil; 23 | } 24 | 25 | __weak typeof(self) welf = self; 26 | self.task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 27 | if (self.isCancelled) { return; } 28 | 29 | if (!error && data) { 30 | NSError *jsonParsingError; 31 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParsingError]; 32 | 33 | if (!jsonParsingError && json) { 34 | completionHandler(json, response, nil); 35 | } else { 36 | completionHandler(nil, response, jsonParsingError); 37 | } 38 | } else { 39 | completionHandler(nil, response, error); 40 | } 41 | 42 | [welf finish]; 43 | }]; 44 | 45 | return self; 46 | } 47 | 48 | - (void)start { 49 | [super start]; 50 | [self.task resume]; 51 | } 52 | 53 | - (void)cancel { 54 | [self.task cancel]; 55 | [super cancel]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/ActivityController/UIActivityViewController+Utilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIActivityViewController+Convenience.h 3 | // Coffup 4 | // 5 | // Created by Jerry Tung on 5/17/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface UIActivityViewController(Convenience) 13 | - (UIActivityViewController*)shareApp; 14 | 15 | @end 16 | 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/ActivityController/UIActivityViewController+Utilities.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIActivityViewController+Utilities.m 3 | // Coffup 4 | // 5 | // Created by Jerry Tung on 5/17/19. 6 | // 7 | 8 | #import "UIActivityViewController+Utilities.h" 9 | #import 10 | 11 | @implementation UIActivityViewController(Utilities) 12 | 13 | - (UIActivityViewController*)shareApp { 14 | NSURL *itunesURL = [NSURL URLWithString:@"https://itunes.apple.com/us/app/coffee-coffee-coffee/id1458031604?mt=8"]; 15 | 16 | NSArray *activityItems = @[itunesURL]; 17 | 18 | return [self initWithActivityItems:activityItems applicationActivities:nil]; 19 | } 20 | 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Analytics.h: -------------------------------------------------------------------------------- 1 | // 2 | // Analytics.h 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 4/22/19. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | typedef void(^TrackingComplete)(NSError *_Nullable error); 13 | 14 | 15 | @interface Analytics : NSObject 16 | - (void)trackEvent:(NSString *)event withProperties:(NSDictionary *)properties onCompletion:(TrackingComplete)onCompletion; 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Analytics.m: -------------------------------------------------------------------------------- 1 | // 2 | // Analytics.m 3 | // Coffup 4 | // 5 | // Created by Roderic Campbell on 4/22/19. 6 | // 7 | 8 | #import "Analytics.h" 9 | #import "SecretsStore.h" 10 | #import 11 | 12 | @implementation Analytics 13 | 14 | - (void)trackEvent:(NSString *)event 15 | withProperties:(NSDictionary *)properties 16 | onCompletion:(TrackingComplete)onCompletion { 17 | 18 | CKDatabase *database = [[CKContainer defaultContainer] publicCloudDatabase]; 19 | CKRecord *record = [[CKRecord alloc] initWithRecordType:@"backgroundFetchComplete"]; 20 | [record setValuesForKeysWithDictionary:properties]; 21 | [database saveRecord:record 22 | completionHandler:^(CKRecord * _Nullable record, 23 | NSError * _Nullable error) { 24 | if (onCompletion) { 25 | onCompletion(error); 26 | } 27 | }]; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Application/UIApplication+Metadata.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Metadata.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UIApplication (Metadata) 12 | 13 | @property (nonnull, nonatomic, readonly) NSString *bundleIdentifier; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Application/UIApplication+Metadata.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIApplication+Metadata.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "UIApplication+Metadata.h" 10 | 11 | @implementation UIApplication (Metadata) 12 | 13 | - (NSString *)bundleIdentifier { 14 | return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIdentifier"]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Concurrency/AsyncBlockOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncBlockOperation.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/3/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | // https://stackoverflow.com/a/26895501/2671390 10 | 11 | #import 12 | #import "AsyncOperation.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | typedef void(^AsyncBlock)(dispatch_block_t completionHandler); 17 | 18 | @interface AsyncBlockOperation : AsyncOperation 19 | 20 | @property (nonatomic, readonly, copy) AsyncBlock block; 21 | 22 | + (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block; 23 | - (instancetype)initWithAsyncBlock:(AsyncBlock)block; 24 | 25 | @end 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Concurrency/AsyncBlockOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncBlockOperation.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/3/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "AsyncBlockOperation.h" 10 | 11 | @interface AsyncBlockOperation () 12 | 13 | @property (nonatomic, copy) AsyncBlock block; 14 | 15 | @end 16 | 17 | 18 | @implementation AsyncBlockOperation 19 | 20 | + (instancetype)asyncBlockOperationWithBlock:(AsyncBlock)block { 21 | return [[AsyncBlockOperation alloc] initWithAsyncBlock:block]; 22 | } 23 | 24 | - (instancetype)initWithAsyncBlock:(AsyncBlock)block { 25 | if (self = [super init]) { 26 | self.block = block; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)start { 32 | [super start]; 33 | 34 | self.block(^{ 35 | [self finish]; 36 | }); 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Concurrency/AsyncOperation.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOperation.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/3/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface AsyncOperation : NSOperation 13 | 14 | - (void)finish; 15 | 16 | @end 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Concurrency/AsyncOperation.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncOperation.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/3/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "AsyncOperation.h" 10 | 11 | @interface AsyncOperation () { 12 | BOOL _finished; 13 | BOOL _executing; 14 | } 15 | 16 | @end 17 | 18 | 19 | @implementation AsyncOperation 20 | 21 | - (void)start { 22 | [self willChangeValueForKey:@"isExecuting"]; 23 | _executing = YES; 24 | [self didChangeValueForKey:@"isExecuting"]; 25 | } 26 | 27 | - (void)finish { 28 | [self willChangeValueForKey:@"isExecuting"]; 29 | _executing = NO; 30 | [self didChangeValueForKey:@"isExecuting"]; 31 | 32 | [self willChangeValueForKey:@"isFinished"]; 33 | _finished = YES; 34 | [self didChangeValueForKey:@"isFinished"]; 35 | } 36 | 37 | - (BOOL)isFinished { 38 | return _finished; 39 | } 40 | 41 | - (BOOL)isExecuting { 42 | return _executing; 43 | } 44 | 45 | - (BOOL)isAsynchronous { 46 | return YES; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Errors/NSError+Constructor.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+Constructor.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/30/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSError (Constructor) 12 | 13 | + (nonnull NSError *)appErrorWithDescription:(nonnull NSString *)description; 14 | + (nonnull NSError *)appErrorWithDescription:(nonnull NSString *)description errorCode:(NSInteger)code; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Errors/NSError+Constructor.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSError+Constructor.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/30/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "NSError+Constructor.h" 10 | 11 | @implementation NSError (Constructor) 12 | 13 | + (nonnull NSError *)appErrorWithDescription:(nonnull NSString *)description { 14 | return [NSError appErrorWithDescription:description errorCode:0]; 15 | } 16 | 17 | + (NSError *)appErrorWithDescription:(NSString *)description errorCode:(NSInteger)code { 18 | return [[NSError alloc] initWithDomain:[NSBundle mainBundle].bundleIdentifier 19 | code:code 20 | userInfo:@{NSLocalizedDescriptionKey : NSLocalizedString(description, nil)}]; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/MapCameraOverlookingLocations/MKMapCamera+OverlookingLocations.h: -------------------------------------------------------------------------------- 1 | // 2 | // MKMapCamera+OverlookingLocations.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface MKMapCamera (OverlookingLocations) 13 | 14 | + (MKMapCamera *)cameraOverlookingLocation1:(CLLocation *)location1 location2:(CLLocation *)location2 withPadding:(double)padding; 15 | 16 | @end 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/MapCameraOverlookingLocations/MKMapCamera+OverlookingLocations.m: -------------------------------------------------------------------------------- 1 | // 2 | // MKMapCamera+OverlookingLocations.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "MKMapCamera+OverlookingLocations.h" 10 | 11 | @implementation MKMapCamera (OverlookingLocations) 12 | 13 | // https://stackoverflow.com/a/21034410/2671390 14 | static double const mapApertureInRadians = (30 * M_PI) / 180; 15 | 16 | + (MKMapCamera *)cameraOverlookingLocation1:(CLLocation *)location1 location2:(CLLocation *)location2 withPadding:(double)padding { 17 | CLLocationCoordinate2D coordinate1 = location1.coordinate; 18 | CLLocationCoordinate2D coordinate2 = location2.coordinate; 19 | CLLocationCoordinate2D centerCoordinate = CLLocationCoordinate2DMake((coordinate1.latitude + coordinate2.latitude) / 2, (coordinate1.longitude + coordinate2.longitude) / 2); 20 | 21 | double span = [location1 distanceFromLocation:location2] / 2; 22 | double altitude = span / tan(mapApertureInRadians / 2); 23 | double altitudeAdjustedForPadding = altitude + (altitude * padding); 24 | 25 | return [MKMapCamera cameraLookingAtCenterCoordinate:centerCoordinate fromDistance:altitudeAdjustedForPadding pitch:0 heading:0]; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/NSAttributedStringKerning/NSAttributedString+Kerning.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+Kerning.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSAttributedString (Kerning) 12 | 13 | + (NSAttributedString *)kernedStringFromString:(NSString *)address; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/NSAttributedStringKerning/NSAttributedString+Kerning.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSAttributedString+Kerning.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 8/2/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "NSAttributedString+Kerning.h" 10 | @import UIKit; 11 | 12 | @implementation NSAttributedString (Kerning) 13 | 14 | + (NSAttributedString *)kernedStringFromString:(NSString *)string { 15 | NSDictionary *kerning = @{NSKernAttributeName : @(0.82)}; 16 | return [[NSAttributedString alloc] initWithString:string attributes:kerning]; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/NSDateUtilities/NSDate+Utilities.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Utilities.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface NSDate (Utilities) 13 | 14 | @property (nonatomic, readonly, assign) BOOL isYesterday; 15 | @property (nonatomic, readonly, assign) BOOL isToday; 16 | @property (nonatomic, readonly, assign) BOOL isTomorrow; 17 | @property (nonatomic, readonly, assign) BOOL isThisWeek; 18 | @property (nonatomic, readonly, assign) BOOL isThisYear; 19 | @property (nonatomic, readonly, assign) BOOL isInFuture; 20 | @property (nullable, nonatomic, readonly) NSString *relativeDayRepresentation; 21 | 22 | @property (nonatomic, readonly) NSString *abbreviatedTimeintervalFromNow; 23 | 24 | + (NSString *)abbreviatedTimeIntervalForTimeInterval:(NSTimeInterval)timeInterval; 25 | 26 | + (NSString *)timeslotStringFromStartDate:(NSDate *)startDate duration:(NSTimeInterval)duration; 27 | 28 | - (NSString *)stringWithformat:(NSString *)format; 29 | 30 | - (NSString *)weekdayName; 31 | 32 | - (BOOL)isLaterThanDate:(NSDate *)date; 33 | - (BOOL)isEarlierThanDate:(NSDate *)date; 34 | - (BOOL)isBetweenEarlierDate:(NSDate *)earlierDate laterDate:(NSDate *)laterDate; 35 | - (NSString *)dateString; 36 | 37 | @end 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Settings/NSUserDefaults+Settings.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserDefaults+Settings.h 3 | // SF iOS 4 | // 5 | // Created by Zachary Drayer on 4/8/19. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_ENUM(NSInteger, SettingsDirectionProvider) { 11 | SettingsDirectionProviderApple, 12 | SettingsDirectionProviderGoogle 13 | }; 14 | 15 | @interface NSUserDefaults (Settings) 16 | 17 | @property (nonatomic, assign, readwrite) SettingsDirectionProvider directionsProvider; 18 | 19 | @property (nullable, nonatomic, assign, readwrite) NSString *activeStyleIdentifier; 20 | 21 | - (void)setNotificationSetting:(BOOL)isOn forGroupWithID:(nonnull NSString *)groupID; 22 | 23 | /** 24 | * Returns the group specific push notifications setting 25 | * @return BOOL true indicating that the user wants to receive pushes for this group, false otherwise 26 | */ 27 | - (BOOL)notificationSettingForGroupWithID:(nonnull NSString *)groupID; 28 | 29 | - (NSDictionary *_Nullable)notificationSettings; 30 | 31 | - (NSString *_Nullable)lastViewedGroupID; 32 | - (void)setLastViewedGroupID:(NSString *_Nullable)groupID; 33 | @end 34 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Settings/NSUserDefaults+Settings.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSUserDefaults+Settings.m 3 | // SF iOS 4 | // 5 | // Created by Zachary Drayer on 4/8/19. 6 | // 7 | 8 | #import "NSUserDefaults+Settings.h" 9 | #import "Group.h" 10 | 11 | @implementation NSUserDefaults (Settings) 12 | 13 | - (NSString *)SFIOS_directionsProviderKey { 14 | return NSStringFromSelector(@selector(directionsProvider)); 15 | } 16 | 17 | - (SettingsDirectionProvider)directionsProvider { 18 | return [self integerForKey:self.SFIOS_directionsProviderKey]; 19 | } 20 | 21 | - (void)setDirectionsProvider:(SettingsDirectionProvider)directionsProvider { 22 | [self setInteger:directionsProvider forKey:self.SFIOS_directionsProviderKey]; 23 | } 24 | 25 | #pragma mark - 26 | 27 | - (NSString *)SFIOS_notificationsDictionaryKey { 28 | return NSStringFromSelector(@selector(notificationSettingForGroup:)); 29 | } 30 | 31 | - (void)setNotificationSetting:(BOOL)isOn forGroupWithID:(nonnull NSString *)groupID { 32 | NSNumber *newValue = [NSNumber numberWithBool:isOn]; 33 | NSString *key = self.SFIOS_notificationsDictionaryKey; 34 | NSMutableDictionary *settingsDictionary = [[self dictionaryForKey:key] mutableCopy]; 35 | if (settingsDictionary == nil) { 36 | settingsDictionary = [[NSMutableDictionary alloc] initWithObjectsAndKeys:newValue, 37 | groupID, 38 | nil]; 39 | } else { 40 | [settingsDictionary setObject:newValue forKey:groupID]; 41 | } 42 | [self setObject:settingsDictionary forKey:key]; 43 | } 44 | 45 | - (NSDictionary *)notificationSettings { 46 | return [self dictionaryForKey:self.SFIOS_notificationsDictionaryKey]; 47 | } 48 | 49 | - (BOOL)notificationSettingForGroupWithID:(nonnull NSString *)groupID { 50 | NSDictionary *settingsDictionary = [self dictionaryForKey:self.SFIOS_notificationsDictionaryKey]; 51 | return [[settingsDictionary valueForKey:groupID] boolValue]; 52 | } 53 | 54 | #pragma mark - 55 | 56 | - (NSString *)SFIOS_lastViewedGroupKey { 57 | return NSStringFromSelector(@selector(lastViewedGroupID)); 58 | } 59 | 60 | - (NSString *_Nullable)lastViewedGroupID { 61 | return [self objectForKey:self.SFIOS_lastViewedGroupKey]; 62 | } 63 | 64 | - (void)setLastViewedGroupID:(NSString *_Nullable)groupID { 65 | [self setObject:groupID forKey:self.SFIOS_lastViewedGroupKey]; 66 | } 67 | 68 | #pragma mark - 69 | 70 | - (NSString *)SFIOS_activeStyleIdentifier { 71 | return NSStringFromSelector(@selector(SFIOS_activeStyleIdentifier)); 72 | } 73 | 74 | - (NSString *)activeStyleIdentifier { 75 | return [self objectForKey:self.SFIOS_activeStyleIdentifier]; 76 | } 77 | 78 | - (void)setActiveStyleIdentifier:(NSString *)activeStyleIdentifier { 79 | [self setObject:activeStyleIdentifier forKey:self.SFIOS_activeStyleIdentifier]; 80 | } 81 | 82 | @end 83 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/StackViews/UIStackView+ConvenienceInitializer.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIStackView+ConvenienceInitializer.h 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface UIStackView (ConvenienceInitializer) 13 | 14 | - (nonnull instancetype)initWithArrangedSubviews:(nullable NSArray<__kindof UIView *> *)views 15 | axis:(UILayoutConstraintAxis)axis 16 | distribution:(UIStackViewDistribution)distribution 17 | alignment:(UIStackViewAlignment)alignment 18 | spacing:(CGFloat)spacing 19 | margins:(UIEdgeInsets)margins; 20 | 21 | - (void)removeAllArrangedSubviews; 22 | 23 | @end 24 | NS_ASSUME_NONNULL_END 25 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/StackViews/UIStackView+ConvenienceInitializer.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIStackView+ConvenienceInitializer.m 3 | // SF iOS 4 | // 5 | // Created by Amit Jain on 7/31/17. 6 | // Copyright © 2017 Amit Jain. All rights reserved. 7 | // 8 | 9 | #import "UIStackView+ConvenienceInitializer.h" 10 | 11 | @implementation UIStackView (ConvenienceInitializer) 12 | 13 | - (instancetype)initWithArrangedSubviews:(NSArray<__kindof UIView *> *)views 14 | axis:(UILayoutConstraintAxis)axis 15 | distribution:(UIStackViewDistribution)distribution 16 | alignment:(UIStackViewAlignment)alignment 17 | spacing:(CGFloat)spacing 18 | margins:(UIEdgeInsets)margins { 19 | self = [self initWithArrangedSubviews:views ? views : [NSArray new]]; 20 | if (!self) { 21 | return nil; 22 | } 23 | 24 | self.axis = axis; 25 | self.distribution = distribution; 26 | self.alignment = alignment; 27 | self.spacing = spacing; 28 | self.layoutMargins = margins; 29 | self.layoutMarginsRelativeArrangement = true; 30 | 31 | return self; 32 | } 33 | 34 | - (void)removeAllArrangedSubviews { 35 | for (UIView *view in self.arrangedSubviews) { 36 | [self removeArrangedSubview:view]; 37 | [view removeFromSuperview]; 38 | } 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /SF iOS/SF iOS/Helpers/Styles/AffogatoStyle.h: -------------------------------------------------------------------------------- 1 | // 2 | // AffogatoStyle.h 3 | // Coffup 4 | // 5 | // Created by Zachary Drayer on 4/21/19. 6 | // 7 | 8 | #import 9 | 10 | #import "Style.h" 11 | 12 | @interface AffogatoStyle : NSObject