├── .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 | 
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 | [](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