├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── Example
├── .swift-version
├── Podfile
├── StravaSwift.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── xcschemes
│ │ └── StravaSwift-Example.xcscheme
├── StravaSwift.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── StravaSwift
│ ├── ActivitiesViewController.swift
│ ├── ActivityDetailVC.swift
│ ├── AppDelegate.swift
│ ├── AthleteViewController.swift
│ ├── ConnectViewController.swift
│ ├── Images.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── Strava.imageset
│ │ │ ├── ConnectWithStrava.png
│ │ │ ├── ConnectWithStrava@2x.png
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── LaunchScreen.storyboard
│ ├── Main.storyboard
│ └── test2.gpx
└── Tests
│ ├── Info.plist
│ └── Tests.swift
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
└── StravaSwift
│ ├── Achievement.swift
│ ├── Activity.swift
│ ├── AlamofireRequest.swift
│ ├── Athlete.swift
│ ├── AthleteStats.swift
│ ├── Club.swift
│ ├── Comment.swift
│ ├── DefaultTokenDelegate.swift
│ ├── Effort.swift
│ ├── Event.swift
│ ├── GearBikeShoe.swift
│ ├── GeneralExtensions.swift
│ ├── Location.swift
│ ├── Map.swift
│ ├── ModelEnums.swift
│ ├── NSURLExtensions.swift
│ ├── OAuthToken.swift
│ ├── Photo.swift
│ ├── Route.swift
│ ├── Router.swift
│ ├── Segment.swift
│ ├── Split.swift
│ ├── StravaClient.swift
│ ├── StravaClientError.swift
│ ├── StravaConfig.swift
│ ├── StravaProtocols.swift
│ ├── Stream.swift
│ ├── StringExtensions.swift
│ ├── SwiftyJSON.swift
│ ├── TokenDelegate.swift
│ └── Upload.swift
├── StravaSwift.podspec
└── Tests
├── LinuxMain.swift
└── StravaSwiftTests
├── RouterTests.swift
└── XCTestManifests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | docs
2 |
3 | # OS X
4 | .DS_Store
5 |
6 | # Xcode
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata/
17 | *.xccheckout
18 | profile
19 | *.moved-aside
20 | DerivedData
21 | *.hmap
22 | *.ipa
23 |
24 | # Bundler
25 | .bundle
26 |
27 | Carthage
28 | # We recommend against adding the Pods directory to your .gitignore. However
29 | # you should judge for yourself, the pros and cons are mentioned at:
30 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
31 | #
32 | # Note: if you ignore the Pods directory, make sure to uncomment
33 | # `pod install` in .travis.yml
34 | #
35 | Pods/
36 | Podfile.lock
37 |
38 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # references:
2 | # * http://www.objc.io/issue-6/travis-ci.html
3 | # * https://github.com/supermarin/xcpretty#usage
4 |
5 | language: swift
6 | podfile: Example/Podfile
7 | osx_image: xcode13.1
8 |
9 | before_install:
10 | # - gem install xcpretty cocoapods --pre # Since Travis is not always on latest version
11 | - pod repo update
12 | - pod install --project-directory=Example
13 | script:
14 | - set -o pipefail && xcodebuild -workspace Example/StravaSwift.xcworkspace -scheme StravaSwift-Example -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO | xcpretty
15 | - pod lib lint --allow-warnings
16 |
--------------------------------------------------------------------------------
/Example/.swift-version:
--------------------------------------------------------------------------------
1 | 4.0
2 |
--------------------------------------------------------------------------------
/Example/Podfile:
--------------------------------------------------------------------------------
1 | use_frameworks!
2 | platform :ios, '13.0'
3 |
4 | target 'StravaSwift_Example' do
5 | pod 'StravaSwift', :path => '../'
6 |
7 | target 'StravaSwift_Tests' do
8 | inherit! :search_paths
9 | end
10 | end
11 |
12 | post_install do |installer|
13 | installer.pods_project.build_configurations.each do |config|
14 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
15 | end
16 | installer.pods_project.targets.each do |target|
17 | target.build_configurations.each do |config|
18 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
19 | end
20 | end
21 | end
22 |
23 |
--------------------------------------------------------------------------------
/Example/StravaSwift.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 042240876C1C98B43960AA19 /* Pods_StravaSwift_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ACA61AA5A9D07B6AA6D80544 /* Pods_StravaSwift_Tests.framework */; };
11 | 5A2F0F7B275E9545006CBD76 /* ActivityDetailVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A2F0F7A275E9545006CBD76 /* ActivityDetailVC.swift */; };
12 | 5A2F0F7D275EBEFD006CBD76 /* test2.gpx in Resources */ = {isa = PBXBuildFile; fileRef = 5A2F0F7C275EBEFD006CBD76 /* test2.gpx */; };
13 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
14 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
15 | A70657D966A103F32858A0B8 /* Pods_StravaSwift_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 31A74C66DF27481B43059A3C /* Pods_StravaSwift_Example.framework */; };
16 | C9E97C411CF3D522001C8BDD /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9E97C3D1CF3D522001C8BDD /* LaunchScreen.storyboard */; };
17 | C9F475431CF3E19400B730EF /* ActivitiesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F4753F1CF3E19400B730EF /* ActivitiesViewController.swift */; };
18 | C9F475441CF3E19400B730EF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F475401CF3E19400B730EF /* AppDelegate.swift */; };
19 | C9F475451CF3E19400B730EF /* AthleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F475411CF3E19400B730EF /* AthleteViewController.swift */; };
20 | C9F475461CF3E19400B730EF /* ConnectViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F475421CF3E19400B730EF /* ConnectViewController.swift */; };
21 | C9F475481CF3E1B800B730EF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9F475471CF3E1B800B730EF /* Main.storyboard */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = 607FACCF1AFB9204008FA782;
30 | remoteInfo = StravaSwift;
31 | };
32 | /* End PBXContainerItemProxy section */
33 |
34 | /* Begin PBXFileReference section */
35 | 22D8DE0EB6934B7E51CB342F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
36 | 28B11C57DFEF6CF47F7E1A14 /* Pods-StravaSwift_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StravaSwift_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-StravaSwift_Tests/Pods-StravaSwift_Tests.release.xcconfig"; sourceTree = ""; };
37 | 2E43E18C32F4437137707402 /* Pods-StravaSwift_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StravaSwift_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-StravaSwift_Tests/Pods-StravaSwift_Tests.debug.xcconfig"; sourceTree = ""; };
38 | 31A74C66DF27481B43059A3C /* Pods_StravaSwift_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StravaSwift_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
39 | 357100604A445BFCF095B848 /* StravaSwift.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = StravaSwift.podspec; path = ../StravaSwift.podspec; sourceTree = ""; };
40 | 50961F78A3E9092861111DD5 /* Pods-StravaSwift_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StravaSwift_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-StravaSwift_Example/Pods-StravaSwift_Example.release.xcconfig"; sourceTree = ""; };
41 | 54225B8273D655E5B950E17C /* Pods-StravaSwift_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-StravaSwift_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-StravaSwift_Example/Pods-StravaSwift_Example.debug.xcconfig"; sourceTree = ""; };
42 | 5A2F0F7A275E9545006CBD76 /* ActivityDetailVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityDetailVC.swift; sourceTree = ""; };
43 | 5A2F0F7C275EBEFD006CBD76 /* test2.gpx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = test2.gpx; sourceTree = ""; };
44 | 607FACD01AFB9204008FA782 /* StravaSwift_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StravaSwift_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; };
46 | 607FACE51AFB9204008FA782 /* StravaSwift_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StravaSwift_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
48 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; };
49 | ACA61AA5A9D07B6AA6D80544 /* Pods_StravaSwift_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_StravaSwift_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
50 | C9E97C3A1CF3D505001C8BDD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | C9E97C3D1CF3D522001C8BDD /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; };
52 | C9F4753F1CF3E19400B730EF /* ActivitiesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivitiesViewController.swift; sourceTree = ""; };
53 | C9F475401CF3E19400B730EF /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
54 | C9F475411CF3E19400B730EF /* AthleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AthleteViewController.swift; sourceTree = ""; };
55 | C9F475421CF3E19400B730EF /* ConnectViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectViewController.swift; sourceTree = ""; };
56 | C9F475471CF3E1B800B730EF /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; };
57 | E205A84E7E90DD2DF36F8489 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; };
58 | /* End PBXFileReference section */
59 |
60 | /* Begin PBXFrameworksBuildPhase section */
61 | 607FACCD1AFB9204008FA782 /* Frameworks */ = {
62 | isa = PBXFrameworksBuildPhase;
63 | buildActionMask = 2147483647;
64 | files = (
65 | A70657D966A103F32858A0B8 /* Pods_StravaSwift_Example.framework in Frameworks */,
66 | );
67 | runOnlyForDeploymentPostprocessing = 0;
68 | };
69 | 607FACE21AFB9204008FA782 /* Frameworks */ = {
70 | isa = PBXFrameworksBuildPhase;
71 | buildActionMask = 2147483647;
72 | files = (
73 | 042240876C1C98B43960AA19 /* Pods_StravaSwift_Tests.framework in Frameworks */,
74 | );
75 | runOnlyForDeploymentPostprocessing = 0;
76 | };
77 | /* End PBXFrameworksBuildPhase section */
78 |
79 | /* Begin PBXGroup section */
80 | 607FACC71AFB9204008FA782 = {
81 | isa = PBXGroup;
82 | children = (
83 | 607FACF51AFB993E008FA782 /* Podspec Metadata */,
84 | 607FACD21AFB9204008FA782 /* Example for StravaSwift */,
85 | 607FACE81AFB9204008FA782 /* Tests */,
86 | 607FACD11AFB9204008FA782 /* Products */,
87 | FCFF2F7A18414445CF6AC169 /* Pods */,
88 | FBD232F6176D5FB32046FF1C /* Frameworks */,
89 | );
90 | sourceTree = "";
91 | };
92 | 607FACD11AFB9204008FA782 /* Products */ = {
93 | isa = PBXGroup;
94 | children = (
95 | 607FACD01AFB9204008FA782 /* StravaSwift_Example.app */,
96 | 607FACE51AFB9204008FA782 /* StravaSwift_Tests.xctest */,
97 | );
98 | name = Products;
99 | sourceTree = "";
100 | };
101 | 607FACD21AFB9204008FA782 /* Example for StravaSwift */ = {
102 | isa = PBXGroup;
103 | children = (
104 | C9F475401CF3E19400B730EF /* AppDelegate.swift */,
105 | C9F4753F1CF3E19400B730EF /* ActivitiesViewController.swift */,
106 | 5A2F0F7A275E9545006CBD76 /* ActivityDetailVC.swift */,
107 | C9F475411CF3E19400B730EF /* AthleteViewController.swift */,
108 | C9F475421CF3E19400B730EF /* ConnectViewController.swift */,
109 | 607FACD31AFB9204008FA782 /* Supporting Files */,
110 | );
111 | name = "Example for StravaSwift";
112 | path = StravaSwift;
113 | sourceTree = "";
114 | };
115 | 607FACD31AFB9204008FA782 /* Supporting Files */ = {
116 | isa = PBXGroup;
117 | children = (
118 | 5A2F0F7C275EBEFD006CBD76 /* test2.gpx */,
119 | C9F475471CF3E1B800B730EF /* Main.storyboard */,
120 | C9E97C3D1CF3D522001C8BDD /* LaunchScreen.storyboard */,
121 | 607FACDC1AFB9204008FA782 /* Images.xcassets */,
122 | C9E97C3A1CF3D505001C8BDD /* Info.plist */,
123 | );
124 | name = "Supporting Files";
125 | sourceTree = "";
126 | };
127 | 607FACE81AFB9204008FA782 /* Tests */ = {
128 | isa = PBXGroup;
129 | children = (
130 | 607FACEB1AFB9204008FA782 /* Tests.swift */,
131 | 607FACE91AFB9204008FA782 /* Supporting Files */,
132 | );
133 | path = Tests;
134 | sourceTree = "";
135 | };
136 | 607FACE91AFB9204008FA782 /* Supporting Files */ = {
137 | isa = PBXGroup;
138 | children = (
139 | 607FACEA1AFB9204008FA782 /* Info.plist */,
140 | );
141 | name = "Supporting Files";
142 | sourceTree = "";
143 | };
144 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
145 | isa = PBXGroup;
146 | children = (
147 | 357100604A445BFCF095B848 /* StravaSwift.podspec */,
148 | E205A84E7E90DD2DF36F8489 /* README.md */,
149 | 22D8DE0EB6934B7E51CB342F /* LICENSE */,
150 | );
151 | name = "Podspec Metadata";
152 | sourceTree = "";
153 | };
154 | FBD232F6176D5FB32046FF1C /* Frameworks */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 31A74C66DF27481B43059A3C /* Pods_StravaSwift_Example.framework */,
158 | ACA61AA5A9D07B6AA6D80544 /* Pods_StravaSwift_Tests.framework */,
159 | );
160 | name = Frameworks;
161 | sourceTree = "";
162 | };
163 | FCFF2F7A18414445CF6AC169 /* Pods */ = {
164 | isa = PBXGroup;
165 | children = (
166 | 54225B8273D655E5B950E17C /* Pods-StravaSwift_Example.debug.xcconfig */,
167 | 50961F78A3E9092861111DD5 /* Pods-StravaSwift_Example.release.xcconfig */,
168 | 2E43E18C32F4437137707402 /* Pods-StravaSwift_Tests.debug.xcconfig */,
169 | 28B11C57DFEF6CF47F7E1A14 /* Pods-StravaSwift_Tests.release.xcconfig */,
170 | );
171 | name = Pods;
172 | sourceTree = "";
173 | };
174 | /* End PBXGroup section */
175 |
176 | /* Begin PBXNativeTarget section */
177 | 607FACCF1AFB9204008FA782 /* StravaSwift_Example */ = {
178 | isa = PBXNativeTarget;
179 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StravaSwift_Example" */;
180 | buildPhases = (
181 | 806B908665F416C34816EADA /* [CP] Check Pods Manifest.lock */,
182 | 607FACCC1AFB9204008FA782 /* Sources */,
183 | 607FACCD1AFB9204008FA782 /* Frameworks */,
184 | 607FACCE1AFB9204008FA782 /* Resources */,
185 | 469703632F4658D264C59B94 /* [CP] Embed Pods Frameworks */,
186 | );
187 | buildRules = (
188 | );
189 | dependencies = (
190 | );
191 | name = StravaSwift_Example;
192 | productName = StravaSwift;
193 | productReference = 607FACD01AFB9204008FA782 /* StravaSwift_Example.app */;
194 | productType = "com.apple.product-type.application";
195 | };
196 | 607FACE41AFB9204008FA782 /* StravaSwift_Tests */ = {
197 | isa = PBXNativeTarget;
198 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StravaSwift_Tests" */;
199 | buildPhases = (
200 | 92EDE072465CFE848C31F400 /* [CP] Check Pods Manifest.lock */,
201 | 607FACE11AFB9204008FA782 /* Sources */,
202 | 607FACE21AFB9204008FA782 /* Frameworks */,
203 | 607FACE31AFB9204008FA782 /* Resources */,
204 | );
205 | buildRules = (
206 | );
207 | dependencies = (
208 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */,
209 | );
210 | name = StravaSwift_Tests;
211 | productName = Tests;
212 | productReference = 607FACE51AFB9204008FA782 /* StravaSwift_Tests.xctest */;
213 | productType = "com.apple.product-type.bundle.unit-test";
214 | };
215 | /* End PBXNativeTarget section */
216 |
217 | /* Begin PBXProject section */
218 | 607FACC81AFB9204008FA782 /* Project object */ = {
219 | isa = PBXProject;
220 | attributes = {
221 | LastSwiftUpdateCheck = 0730;
222 | LastUpgradeCheck = 1310;
223 | ORGANIZATIONNAME = CocoaPods;
224 | TargetAttributes = {
225 | 607FACCF1AFB9204008FA782 = {
226 | CreatedOnToolsVersion = 6.3.1;
227 | LastSwiftMigration = 1110;
228 | };
229 | 607FACE41AFB9204008FA782 = {
230 | CreatedOnToolsVersion = 6.3.1;
231 | LastSwiftMigration = 1110;
232 | TestTargetID = 607FACCF1AFB9204008FA782;
233 | };
234 | };
235 | };
236 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "StravaSwift" */;
237 | compatibilityVersion = "Xcode 3.2";
238 | developmentRegion = en;
239 | hasScannedForEncodings = 0;
240 | knownRegions = (
241 | en,
242 | Base,
243 | );
244 | mainGroup = 607FACC71AFB9204008FA782;
245 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
246 | projectDirPath = "";
247 | projectRoot = "";
248 | targets = (
249 | 607FACCF1AFB9204008FA782 /* StravaSwift_Example */,
250 | 607FACE41AFB9204008FA782 /* StravaSwift_Tests */,
251 | );
252 | };
253 | /* End PBXProject section */
254 |
255 | /* Begin PBXResourcesBuildPhase section */
256 | 607FACCE1AFB9204008FA782 /* Resources */ = {
257 | isa = PBXResourcesBuildPhase;
258 | buildActionMask = 2147483647;
259 | files = (
260 | C9F475481CF3E1B800B730EF /* Main.storyboard in Resources */,
261 | 5A2F0F7D275EBEFD006CBD76 /* test2.gpx in Resources */,
262 | C9E97C411CF3D522001C8BDD /* LaunchScreen.storyboard in Resources */,
263 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
264 | );
265 | runOnlyForDeploymentPostprocessing = 0;
266 | };
267 | 607FACE31AFB9204008FA782 /* Resources */ = {
268 | isa = PBXResourcesBuildPhase;
269 | buildActionMask = 2147483647;
270 | files = (
271 | );
272 | runOnlyForDeploymentPostprocessing = 0;
273 | };
274 | /* End PBXResourcesBuildPhase section */
275 |
276 | /* Begin PBXShellScriptBuildPhase section */
277 | 469703632F4658D264C59B94 /* [CP] Embed Pods Frameworks */ = {
278 | isa = PBXShellScriptBuildPhase;
279 | buildActionMask = 2147483647;
280 | files = (
281 | );
282 | inputPaths = (
283 | "${PODS_ROOT}/Target Support Files/Pods-StravaSwift_Example/Pods-StravaSwift_Example-frameworks.sh",
284 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework",
285 | "${BUILT_PRODUCTS_DIR}/StravaSwift/StravaSwift.framework",
286 | "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework",
287 | );
288 | name = "[CP] Embed Pods Frameworks";
289 | outputPaths = (
290 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework",
291 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/StravaSwift.framework",
292 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework",
293 | );
294 | runOnlyForDeploymentPostprocessing = 0;
295 | shellPath = /bin/sh;
296 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-StravaSwift_Example/Pods-StravaSwift_Example-frameworks.sh\"\n";
297 | showEnvVarsInLog = 0;
298 | };
299 | 806B908665F416C34816EADA /* [CP] Check Pods Manifest.lock */ = {
300 | isa = PBXShellScriptBuildPhase;
301 | buildActionMask = 2147483647;
302 | files = (
303 | );
304 | inputPaths = (
305 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
306 | "${PODS_ROOT}/Manifest.lock",
307 | );
308 | name = "[CP] Check Pods Manifest.lock";
309 | outputPaths = (
310 | "$(DERIVED_FILE_DIR)/Pods-StravaSwift_Example-checkManifestLockResult.txt",
311 | );
312 | runOnlyForDeploymentPostprocessing = 0;
313 | shellPath = /bin/sh;
314 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
315 | showEnvVarsInLog = 0;
316 | };
317 | 92EDE072465CFE848C31F400 /* [CP] Check Pods Manifest.lock */ = {
318 | isa = PBXShellScriptBuildPhase;
319 | buildActionMask = 2147483647;
320 | files = (
321 | );
322 | inputPaths = (
323 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
324 | "${PODS_ROOT}/Manifest.lock",
325 | );
326 | name = "[CP] Check Pods Manifest.lock";
327 | outputPaths = (
328 | "$(DERIVED_FILE_DIR)/Pods-StravaSwift_Tests-checkManifestLockResult.txt",
329 | );
330 | runOnlyForDeploymentPostprocessing = 0;
331 | shellPath = /bin/sh;
332 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
333 | showEnvVarsInLog = 0;
334 | };
335 | /* End PBXShellScriptBuildPhase section */
336 |
337 | /* Begin PBXSourcesBuildPhase section */
338 | 607FACCC1AFB9204008FA782 /* Sources */ = {
339 | isa = PBXSourcesBuildPhase;
340 | buildActionMask = 2147483647;
341 | files = (
342 | C9F475451CF3E19400B730EF /* AthleteViewController.swift in Sources */,
343 | 5A2F0F7B275E9545006CBD76 /* ActivityDetailVC.swift in Sources */,
344 | C9F475441CF3E19400B730EF /* AppDelegate.swift in Sources */,
345 | C9F475431CF3E19400B730EF /* ActivitiesViewController.swift in Sources */,
346 | C9F475461CF3E19400B730EF /* ConnectViewController.swift in Sources */,
347 | );
348 | runOnlyForDeploymentPostprocessing = 0;
349 | };
350 | 607FACE11AFB9204008FA782 /* Sources */ = {
351 | isa = PBXSourcesBuildPhase;
352 | buildActionMask = 2147483647;
353 | files = (
354 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */,
355 | );
356 | runOnlyForDeploymentPostprocessing = 0;
357 | };
358 | /* End PBXSourcesBuildPhase section */
359 |
360 | /* Begin PBXTargetDependency section */
361 | 607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
362 | isa = PBXTargetDependency;
363 | target = 607FACCF1AFB9204008FA782 /* StravaSwift_Example */;
364 | targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
365 | };
366 | /* End PBXTargetDependency section */
367 |
368 | /* Begin XCBuildConfiguration section */
369 | 607FACED1AFB9204008FA782 /* Debug */ = {
370 | isa = XCBuildConfiguration;
371 | buildSettings = {
372 | ALWAYS_SEARCH_USER_PATHS = NO;
373 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
374 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
375 | CLANG_CXX_LIBRARY = "libc++";
376 | CLANG_ENABLE_MODULES = YES;
377 | CLANG_ENABLE_OBJC_ARC = YES;
378 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
379 | CLANG_WARN_BOOL_CONVERSION = YES;
380 | CLANG_WARN_COMMA = YES;
381 | CLANG_WARN_CONSTANT_CONVERSION = YES;
382 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
383 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
384 | CLANG_WARN_EMPTY_BODY = YES;
385 | CLANG_WARN_ENUM_CONVERSION = YES;
386 | CLANG_WARN_INFINITE_RECURSION = YES;
387 | CLANG_WARN_INT_CONVERSION = YES;
388 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
389 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
390 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
391 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
392 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
393 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
394 | CLANG_WARN_STRICT_PROTOTYPES = YES;
395 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
396 | CLANG_WARN_UNREACHABLE_CODE = YES;
397 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
398 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
399 | COPY_PHASE_STRIP = NO;
400 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
401 | ENABLE_STRICT_OBJC_MSGSEND = YES;
402 | ENABLE_TESTABILITY = YES;
403 | GCC_C_LANGUAGE_STANDARD = gnu99;
404 | GCC_DYNAMIC_NO_PIC = NO;
405 | GCC_NO_COMMON_BLOCKS = YES;
406 | GCC_OPTIMIZATION_LEVEL = 0;
407 | GCC_PREPROCESSOR_DEFINITIONS = (
408 | "DEBUG=1",
409 | "$(inherited)",
410 | );
411 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
412 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
413 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
414 | GCC_WARN_UNDECLARED_SELECTOR = YES;
415 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
416 | GCC_WARN_UNUSED_FUNCTION = YES;
417 | GCC_WARN_UNUSED_VARIABLE = YES;
418 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
419 | MTL_ENABLE_DEBUG_INFO = YES;
420 | ONLY_ACTIVE_ARCH = YES;
421 | SDKROOT = iphoneos;
422 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
423 | };
424 | name = Debug;
425 | };
426 | 607FACEE1AFB9204008FA782 /* Release */ = {
427 | isa = XCBuildConfiguration;
428 | buildSettings = {
429 | ALWAYS_SEARCH_USER_PATHS = NO;
430 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
431 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
432 | CLANG_CXX_LIBRARY = "libc++";
433 | CLANG_ENABLE_MODULES = YES;
434 | CLANG_ENABLE_OBJC_ARC = YES;
435 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
436 | CLANG_WARN_BOOL_CONVERSION = YES;
437 | CLANG_WARN_COMMA = YES;
438 | CLANG_WARN_CONSTANT_CONVERSION = YES;
439 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
440 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
441 | CLANG_WARN_EMPTY_BODY = YES;
442 | CLANG_WARN_ENUM_CONVERSION = YES;
443 | CLANG_WARN_INFINITE_RECURSION = YES;
444 | CLANG_WARN_INT_CONVERSION = YES;
445 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
446 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
447 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
448 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
449 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
451 | CLANG_WARN_STRICT_PROTOTYPES = YES;
452 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
453 | CLANG_WARN_UNREACHABLE_CODE = YES;
454 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
455 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
456 | COPY_PHASE_STRIP = NO;
457 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
458 | ENABLE_NS_ASSERTIONS = NO;
459 | ENABLE_STRICT_OBJC_MSGSEND = YES;
460 | GCC_C_LANGUAGE_STANDARD = gnu99;
461 | GCC_NO_COMMON_BLOCKS = YES;
462 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
463 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
464 | GCC_WARN_UNDECLARED_SELECTOR = YES;
465 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
466 | GCC_WARN_UNUSED_FUNCTION = YES;
467 | GCC_WARN_UNUSED_VARIABLE = YES;
468 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
469 | MTL_ENABLE_DEBUG_INFO = NO;
470 | SDKROOT = iphoneos;
471 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
472 | VALIDATE_PRODUCT = YES;
473 | };
474 | name = Release;
475 | };
476 | 607FACF01AFB9204008FA782 /* Debug */ = {
477 | isa = XCBuildConfiguration;
478 | baseConfigurationReference = 54225B8273D655E5B950E17C /* Pods-StravaSwift_Example.debug.xcconfig */;
479 | buildSettings = {
480 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
481 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
482 | CLANG_ENABLE_MODULES = YES;
483 | DEVELOPMENT_TEAM = "";
484 | INFOPLIST_FILE = StravaSwift/Info.plist;
485 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
486 | MODULE_NAME = ExampleApp;
487 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
488 | PRODUCT_NAME = "$(TARGET_NAME)";
489 | SWIFT_OBJC_BRIDGING_HEADER = "";
490 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
491 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
492 | SWIFT_VERSION = 5.0;
493 | };
494 | name = Debug;
495 | };
496 | 607FACF11AFB9204008FA782 /* Release */ = {
497 | isa = XCBuildConfiguration;
498 | baseConfigurationReference = 50961F78A3E9092861111DD5 /* Pods-StravaSwift_Example.release.xcconfig */;
499 | buildSettings = {
500 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "$(inherited)";
501 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
502 | CLANG_ENABLE_MODULES = YES;
503 | DEVELOPMENT_TEAM = "";
504 | INFOPLIST_FILE = StravaSwift/Info.plist;
505 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
506 | MODULE_NAME = ExampleApp;
507 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
508 | PRODUCT_NAME = "$(TARGET_NAME)";
509 | SWIFT_OBJC_BRIDGING_HEADER = "";
510 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
511 | SWIFT_VERSION = 5.0;
512 | };
513 | name = Release;
514 | };
515 | 607FACF31AFB9204008FA782 /* Debug */ = {
516 | isa = XCBuildConfiguration;
517 | baseConfigurationReference = 2E43E18C32F4437137707402 /* Pods-StravaSwift_Tests.debug.xcconfig */;
518 | buildSettings = {
519 | DEVELOPMENT_TEAM = "";
520 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
521 | GCC_PREPROCESSOR_DEFINITIONS = (
522 | "DEBUG=1",
523 | "$(inherited)",
524 | );
525 | INFOPLIST_FILE = Tests/Info.plist;
526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
527 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
528 | PRODUCT_NAME = "$(TARGET_NAME)";
529 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
530 | SWIFT_VERSION = 5.0;
531 | };
532 | name = Debug;
533 | };
534 | 607FACF41AFB9204008FA782 /* Release */ = {
535 | isa = XCBuildConfiguration;
536 | baseConfigurationReference = 28B11C57DFEF6CF47F7E1A14 /* Pods-StravaSwift_Tests.release.xcconfig */;
537 | buildSettings = {
538 | DEVELOPMENT_TEAM = "";
539 | FRAMEWORK_SEARCH_PATHS = "$(inherited)";
540 | INFOPLIST_FILE = Tests/Info.plist;
541 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
542 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
543 | PRODUCT_NAME = "$(TARGET_NAME)";
544 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
545 | SWIFT_VERSION = 5.0;
546 | };
547 | name = Release;
548 | };
549 | /* End XCBuildConfiguration section */
550 |
551 | /* Begin XCConfigurationList section */
552 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "StravaSwift" */ = {
553 | isa = XCConfigurationList;
554 | buildConfigurations = (
555 | 607FACED1AFB9204008FA782 /* Debug */,
556 | 607FACEE1AFB9204008FA782 /* Release */,
557 | );
558 | defaultConfigurationIsVisible = 0;
559 | defaultConfigurationName = Release;
560 | };
561 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StravaSwift_Example" */ = {
562 | isa = XCConfigurationList;
563 | buildConfigurations = (
564 | 607FACF01AFB9204008FA782 /* Debug */,
565 | 607FACF11AFB9204008FA782 /* Release */,
566 | );
567 | defaultConfigurationIsVisible = 0;
568 | defaultConfigurationName = Release;
569 | };
570 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "StravaSwift_Tests" */ = {
571 | isa = XCConfigurationList;
572 | buildConfigurations = (
573 | 607FACF31AFB9204008FA782 /* Debug */,
574 | 607FACF41AFB9204008FA782 /* Release */,
575 | );
576 | defaultConfigurationIsVisible = 0;
577 | defaultConfigurationName = Release;
578 | };
579 | /* End XCConfigurationList section */
580 | };
581 | rootObject = 607FACC81AFB9204008FA782 /* Project object */;
582 | }
583 |
--------------------------------------------------------------------------------
/Example/StravaSwift.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/StravaSwift.xcodeproj/xcshareddata/xcschemes/StravaSwift-Example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
44 |
45 |
51 |
52 |
53 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
76 |
78 |
84 |
85 |
86 |
87 |
93 |
95 |
101 |
102 |
103 |
104 |
106 |
107 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/Example/StravaSwift.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Example/StravaSwift.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/StravaSwift/ActivitiesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivitiesViewController.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 23/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import StravaSwift
11 |
12 | class ActivitiesViewController: UITableViewController {
13 |
14 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView?
15 |
16 | fileprivate var activities: [Activity] = []
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 | self.tableView.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
21 | update()
22 | }
23 |
24 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
25 | switch segue.identifier {
26 | case "SegueActivityDetail":
27 | // User tapped on a row and wants to view the ActivityDetailVC
28 | // We must set the activityId property during the segue
29 | if let vc = segue.destination as? ActivityDetailVC?, let indexPath = tableView.indexPathForSelectedRow {
30 | vc?.activityID = activities[indexPath.row].id
31 | }
32 | default: break
33 | }
34 | }
35 |
36 | }
37 |
38 | extension ActivitiesViewController {
39 |
40 | fileprivate func update(params: Router.Params = nil) {
41 | activityIndicator?.startAnimating()
42 | StravaClient.sharedInstance.request(Router.athleteActivities(params: params), result: { [weak self] (activities: [Activity]?) in
43 | guard let self = self else { return }
44 | self.activityIndicator?.stopAnimating()
45 |
46 | guard let activities = activities else { return }
47 | self.activities = activities
48 |
49 | self.tableView?.reloadData()
50 | }, failure: { (error: NSError) in
51 | self.activityIndicator?.stopAnimating()
52 | debugPrint(error)
53 | })
54 | }
55 | }
56 |
57 | extension ActivitiesViewController {
58 |
59 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
60 | return activities.count
61 | }
62 |
63 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
64 |
65 | let cell = tableView.dequeueReusableCell(withIdentifier: "activity", for: indexPath)
66 | let activity = activities[indexPath.row]
67 |
68 | cell.textLabel?.text = activity.name
69 | if let date = activity.startDate {
70 | cell.detailTextLabel?.text = "\(date)"
71 | }
72 |
73 | return cell
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Example/StravaSwift/ActivityDetailVC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityDetailVC.swift
3 | // StravaSwift_Example
4 | //
5 | // Created by Michael Chartier on 12/6/21.
6 | // Copyright © 2021 CocoaPods. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import StravaSwift
11 |
12 |
13 | // This VC is presented by ActivitiesViewController
14 | // The purpose here is to demonstrate how to download the details for just one specific activity.
15 | // Yes the presenting VC already has those details, but for this demo we just ignore those and download again.
16 | class ActivityDetailVC: UIViewController
17 | {
18 | @IBOutlet weak var O_id: UILabel!
19 | @IBOutlet weak var O_name: UILabel!
20 | @IBOutlet weak var O_description: UILabel!
21 |
22 |
23 |
24 | var activityID: Int! // set by the presenting VC
25 |
26 | override func viewDidLoad() {
27 | super.viewDidLoad()
28 |
29 | // Download the details for the specific activity by ID
30 | StravaClient.sharedInstance.request(Router.activities(id: activityID, params: nil), result: { [weak self] (activity: Activity?) in
31 | guard let self = self else { return }
32 | guard let activity = activity else { return }
33 | self.updateUI(activity: activity)
34 | }, failure: { (error: NSError) in
35 | debugPrint(error)
36 | })
37 | }
38 |
39 | private func updateUI( activity: Activity ) {
40 | if let id = activity.id {
41 | O_id.text = "ID: \(id)"
42 | } else {
43 | O_id.text = "ID: ??"
44 | }
45 | O_name.text = activity.name
46 | O_description.text = activity.description
47 | }
48 |
49 | }
50 |
51 |
52 | /*
53 |
54 | public let id: Int?
55 | public let resourceState: ResourceState?
56 | public let externalId: String?
57 | public let uploadId: Int?
58 | public let athlete: Athlete?
59 | public let name: String?
60 | public let description: String?
61 | public let distance: Double?
62 | public let movingTime: TimeInterval?
63 | public let elapsedTime: TimeInterval?
64 | public let highElevation : Double?
65 | public let lowElevation : Double?
66 | public let totalElevationGain: Double?
67 | public let type: ActivityType?
68 | public let startDate: Date?
69 | public let startDateLocal: Date?
70 | public let timeZone: String?
71 | public let startLatLng: Location?
72 | public let endLatLng: Location?
73 | public let achievementCount: Count?
74 | public let kudosCount: Count?
75 | public let commentCount: Count?
76 | public let athleteCount: Count?
77 | public let photoCount: Count?
78 | public let totalPhotoCount: Count?
79 | public let photos: [Photo]?
80 | public let map: Map?
81 | public let trainer: Bool?
82 | public let commute: Bool?
83 | public let manual: Bool?
84 | public let `private`: Bool?
85 | public let flagged: Bool?
86 | public let workoutType: WorkoutType?
87 | public let gear: Gear?
88 | public let averageSpeed: Speed?
89 | public let maxSpeed: Speed?
90 | public let calories: Double?
91 | public let hasKudoed: Bool?
92 | public let segmentEfforts: [Effort]?
93 | public let splitsMetric: [Split]?
94 | public let splitsStandard: [Split]?
95 | public let bestEfforts: [Split]?
96 | public let kiloJoules: Double?
97 | public let averagePower : Double?
98 | public let maxPower : Double?
99 | public let deviceWatts : Bool?
100 | public let hasHeartRate : Bool?
101 | public let averageHeartRate : Double?
102 | public let maxHeartRate : Double?
103 |
104 | */
105 |
--------------------------------------------------------------------------------
/Example/StravaSwift/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import StravaSwift
11 |
12 | @UIApplicationMain
13 | class AppDelegate: UIResponder, UIApplicationDelegate {
14 |
15 | var window: UIWindow?
16 |
17 | let strava: StravaClient
18 |
19 | override init() {
20 | let config = StravaConfig(
21 | clientId: 8873,
22 | clientSecret: "97b97b29ede769eec2dc26c52dd281b5a1efe594",
23 | redirectUri: "stravaswift://mpclarkson.github.io",
24 | scopes: [.activityReadAll, .activityWrite]
25 | )
26 | strava = StravaClient.sharedInstance.initWithConfig(config)
27 |
28 | super.init()
29 | }
30 |
31 | lazy var storyboard = { return UIStoryboard(name: "Main", bundle: nil) }()
32 |
33 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
34 | loadInitialViewController()
35 | return true
36 | }
37 |
38 | private func loadInitialViewController() {
39 | if let _ = StravaClient.sharedInstance.token {
40 | window?.rootViewController = storyboard.instantiateViewController(withIdentifier: "tab") as! UITabBarController
41 | self.window?.makeKeyAndVisible()
42 | }
43 | }
44 |
45 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
46 | return strava.handleAuthorizationRedirect(url)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Example/StravaSwift/AthleteViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileViewController.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 20/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import StravaSwift
11 |
12 | extension UIImageView {
13 | func from(url: URL?) {
14 | guard let url = url else { return }
15 | do {
16 | let data = try Data(contentsOf: url)
17 | self.image = UIImage(data: data)
18 | }
19 | catch {
20 | return
21 | }
22 | }
23 | }
24 |
25 |
26 | class AthleteViewController: UIViewController {
27 |
28 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView?
29 |
30 | @IBOutlet weak var name: UILabel?
31 | @IBOutlet weak var avatar: UIImageView?
32 | @IBOutlet weak var rides: UILabel?
33 | @IBOutlet weak var runs: UILabel?
34 | @IBOutlet weak var swims: UILabel?
35 | @IBOutlet weak var O_upload: UIButton!
36 | @IBOutlet weak var O_uploadSpinner: UIActivityIndicatorView!
37 | @IBOutlet weak var O_monitor: UIButton!
38 |
39 | var athlete: Athlete? {
40 | didSet {
41 | name?.text = "\(athlete?.firstname ?? "") \(athlete?.lastname ?? "")"
42 | avatar?.from(url: athlete?.profile)
43 | }
44 | }
45 |
46 | var stats: AthleteStats? {
47 | didSet {
48 | if let ridesInt = stats?.allRideTotals?.count {
49 | rides?.text = String(ridesInt)
50 | }
51 | if let runsInt = stats?.allRunTotals?.count {
52 | runs?.text = String(runsInt)
53 | }
54 | if let swimsInt = stats?.allSwimTotals?.count {
55 | swims?.text = String(swimsInt)
56 | }
57 | }
58 | }
59 |
60 |
61 | // Couple counters used for uploading new activities
62 | var uploadID: Int?
63 | var activityID: Int?
64 | var matchCounter = 0
65 | var previousEffortCount = 0
66 | var monitorCounter = 0
67 | let maximumReties = 20
68 |
69 | override func viewDidLoad() {
70 | super.viewDidLoad()
71 | update()
72 | }
73 |
74 | @IBAction func A_upload(_ sender: Any) {
75 | O_upload.setTitle("Uploading...", for: .normal)
76 | O_upload.isEnabled = false
77 | O_monitor.isHidden = true
78 | O_uploadSpinner.startAnimating()
79 |
80 | // Grab a GPX test file from the app bundle. Load it into a Data object.
81 | guard let filePath = Bundle.main.url(forResource: "test2", withExtension: "gpx") else { return }
82 | guard let fileData = try? Data(contentsOf: filePath) else { return }
83 |
84 | let params = StravaSwift.UploadData(activityType: .ride, name: "test1", description: "upload test", private: false, trainer: nil, externalId: nil, dataType: .gpx, file: fileData)
85 |
86 | StravaClient.sharedInstance.upload(Router.uploadFile(upload: params), upload: params, result: { [weak self] (status: UploadStatus?) in
87 | guard let self = self else { return }
88 | if let status = status, let uploadID = status.id {
89 | // At this point only status.id will be valid. All other properties will be nil.
90 | self.uploadID = uploadID
91 | self.O_upload.setTitle("Processing new activity", for: .normal)
92 | self.monitorCounter = self.maximumReties
93 | self.monitorUploadProgress()
94 | }}, failure: { (error: NSError) in
95 | debugPrint(error)
96 | self.doAlert(title: "Strava Error", message: error.localizedDescription)
97 | })
98 | }
99 |
100 | @IBAction func A_monitor(_ sender: Any) {
101 | O_monitor.setTitle("Monitoring...", for: .normal)
102 | O_monitor.isEnabled = false
103 | O_uploadSpinner.startAnimating()
104 | monitorSegments()
105 | }
106 |
107 | private func monitorUploadProgress() {
108 | // Monitor the upload record until we get a valid activityID
109 | guard let uploadID = uploadID else { return }
110 | Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
111 | StravaClient.sharedInstance.request(Router.uploads(id: uploadID), result: { [weak self] (status: UploadStatus?) in
112 | guard let self = self else { return }
113 | guard let status = status else { return }
114 | if let error = status.error {
115 | self.doAlert(title: "Upload Failed", message: error)
116 | return
117 | } else if let activityID = status.activityId {
118 | // We have a valid activityID so the upload is considered complete.
119 | // However, note that segment processing can continue for quite a while.
120 | self.activityID = activityID
121 | self.O_upload.setTitle("Upload Complete", for: .normal)
122 | self.O_uploadSpinner.stopAnimating()
123 | self.O_monitor.setTitle("Monitor Segments", for: .normal)
124 | self.O_monitor.isHidden = false
125 | self.O_monitor.isEnabled = true
126 | } else {
127 | // Start another timer
128 | self.monitorUploadProgress()
129 | }
130 | }, failure: { (error: NSError) in
131 | debugPrint(error)
132 | })
133 | }
134 | }
135 |
136 | private func monitorSegments() {
137 | // We want to know when the Strava server has finished processing the new upload.
138 | // Processing can take anywhere from a few seconds to several minutes depending on many factors.
139 | // When we first upload a new activity the number of segments will be zero.
140 | // This number will increase as new segments are detected by the Strava server.
141 | // This number will stay at zero if no segments are found.
142 | // For example, for a 3 hour recording with 20 segments this number will typically increment
143 | // by 1 or 2 every second until all 20 segments are found.
144 | // As far as I can tell, there is no "done" flag we can monitor.
145 | // The approach here is to check segment_count once every 2 seconds until we detect no further changes.
146 |
147 | monitorCounter = maximumReties
148 | matchCounter = 0
149 | previousEffortCount = 0
150 |
151 | getEffortCount()
152 | }
153 |
154 | private func getEffortCount() {
155 | guard let activityID = activityID else { return }
156 | Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
157 | // Download the details for the specific activity by ID
158 | StravaClient.sharedInstance.request(Router.activities(id: activityID, params: nil), result: { [weak self] (activity: Activity?) in
159 | guard let self = self else { return }
160 | guard let activity = activity else { return }
161 | if let efforts = activity.segmentEfforts, efforts.count > 0 {
162 | if efforts.count == self.previousEffortCount {
163 | self.matchCounter += 1
164 | if (self.matchCounter >= 3) {
165 | // There is a good chance that processing has completed.
166 | self.O_monitor.setTitle("\(efforts.count) segments found", for: .normal)
167 | self.O_uploadSpinner.stopAnimating()
168 | self.O_upload.isEnabled = true
169 | return // stop monitoring
170 | }
171 | }
172 | self.previousEffortCount = efforts.count
173 | }
174 | // Schedule another timer OR abort
175 | self.monitorCounter -= 1
176 | if (self.monitorCounter > 0) {
177 | self.getEffortCount()
178 | } else {
179 | self.O_monitor.setTitle("Timeout Error", for: .normal)
180 | self.O_uploadSpinner.stopAnimating()
181 | self.O_upload.isEnabled = true
182 | }
183 | }, failure: { (error: NSError) in
184 | debugPrint(error)
185 | self.doAlert(title: "Strava Error", message: error.localizedDescription)
186 | })
187 | }
188 | }
189 |
190 | func doAlert(title: String, message: String)
191 | {
192 | O_uploadSpinner.stopAnimating()
193 | O_upload.setTitle("Upload to Strava", for: .normal)
194 | O_upload.isEnabled = true
195 | O_monitor.isHidden = true
196 |
197 | let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
198 | let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
199 | alert.addAction(okAction)
200 | self.present(alert, animated: true, completion: nil)
201 | }
202 |
203 | }
204 |
205 | extension AthleteViewController {
206 |
207 | func update() {
208 | activityIndicator?.startAnimating()
209 | StravaClient.sharedInstance.request(Router.athlete, result: { [weak self] (athlete: Athlete?) in
210 | guard let self = self else { return }
211 | self.activityIndicator?.stopAnimating()
212 |
213 | guard let athlete = athlete else { return }
214 | self.athlete = athlete
215 |
216 | StravaClient.sharedInstance.request(Router.athletesStats(id: athlete.id!, params: nil), result: { [weak self] (stats: AthleteStats?) in
217 | guard let self = self else { return }
218 | self.activityIndicator?.stopAnimating()
219 | self.stats = stats
220 |
221 | }, failure: { (error: NSError) in
222 | self.activityIndicator?.stopAnimating()
223 | debugPrint(error)
224 | })
225 |
226 | }, failure: { (error: NSError) in
227 | self.activityIndicator?.stopAnimating()
228 | debugPrint(error)
229 | })
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/Example/StravaSwift/ConnectViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import StravaSwift
10 | import UIKit
11 |
12 | class ConnectViewController: UIViewController {
13 |
14 | @IBOutlet weak var loginButton: UIButton!
15 | @IBOutlet weak var activityIndicator: UIActivityIndicatorView?
16 |
17 | var code: String?
18 | private var token: OAuthToken?
19 |
20 | @IBAction func login(_ sender: AnyObject) {
21 | activityIndicator?.startAnimating()
22 | loginButton.isHidden = true
23 | StravaClient.sharedInstance.authorize() { [weak self] (result: Result) in
24 | guard let self = self else { return }
25 | self.activityIndicator?.stopAnimating()
26 | self.loginButton.isHidden = false
27 | self.didAuthenticate(result: result)
28 | }
29 | }
30 |
31 | private func didAuthenticate(result: Result) {
32 | switch result {
33 | case .success(let token):
34 | self.token = token
35 | self.performSegue(withIdentifier: "navigation", sender: self)
36 | case .failure(let error):
37 | debugPrint(error)
38 | }
39 | }
40 |
41 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
42 | if segue.identifier == "navigation" {
43 | let barViewControllers = segue.destination as! UITabBarController
44 | let vc = barViewControllers.viewControllers![0] as! AthleteViewController
45 | vc.athlete = self.token?.athlete
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Example/StravaSwift/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Example/StravaSwift/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Example/StravaSwift/Images.xcassets/Strava.imageset/ConnectWithStrava.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpclarkson/StravaSwift/2a6db87aa19f14dc67f97a4006f14d0c0eacc4f9/Example/StravaSwift/Images.xcassets/Strava.imageset/ConnectWithStrava.png
--------------------------------------------------------------------------------
/Example/StravaSwift/Images.xcassets/Strava.imageset/ConnectWithStrava@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mpclarkson/StravaSwift/2a6db87aa19f14dc67f97a4006f14d0c0eacc4f9/Example/StravaSwift/Images.xcassets/Strava.imageset/ConnectWithStrava@2x.png
--------------------------------------------------------------------------------
/Example/StravaSwift/Images.xcassets/Strava.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ConnectWithStrava.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "ConnectWithStrava@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/Example/StravaSwift/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleURLTypes
22 |
23 |
24 | CFBundleTypeRole
25 | Editor
26 | CFBundleURLName
27 | com.stravaswift.ios
28 | CFBundleURLSchemes
29 |
30 | stravaswift
31 |
32 |
33 |
34 | CFBundleVersion
35 | 1
36 | LSApplicationQueriesSchemes
37 |
38 | strava
39 |
40 | LSRequiresIPhoneOS
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/Example/StravaSwift/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 |
--------------------------------------------------------------------------------
/Example/StravaSwift/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
64 |
70 |
76 |
82 |
88 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
118 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
200 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
239 |
245 |
251 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
--------------------------------------------------------------------------------
/Example/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Example/Tests/Tests.swift:
--------------------------------------------------------------------------------
1 | // https://github.com/Quick/Quick
2 |
3 | //import Quick
4 | //import Nimble
5 | //import StravaSwift
6 | //
7 | //class TableOfContentsSpec: QuickSpec {
8 | // override func spec() {
9 | // describe("these will fail") {
10 | //
11 | // it("can do maths") {
12 | // expect(1) == 2
13 | // }
14 | //
15 | // it("can read") {
16 | // expect("number") == "string"
17 | // }
18 | //
19 | // it("will eventually fail") {
20 | // expect("time").toEventually( equal("done") )
21 | // }
22 | //
23 | // context("these will pass") {
24 | //
25 | // it("can do maths") {
26 | // expect(23) == 23
27 | // }
28 | //
29 | // it("can read") {
30 | // expect("🐮") == "🐮"
31 | // }
32 | //
33 | // it("will eventually pass") {
34 | // var time = "passing"
35 | //
36 | // dispatch_async(dispatch_get_main_queue()) {
37 | // time = "done"
38 | // }
39 | //
40 | // waitUntil { done in
41 | // NSThread.sleepForTimeInterval(0.5)
42 | // expect(time) == "done"
43 | //
44 | // done()
45 | // }
46 | // }
47 | // }
48 | // }
49 | // }
50 | //}
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Matthew Clarkson
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Alamofire",
6 | "repositoryURL": "https://github.com/Alamofire/Alamofire.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "ce5be6fbc6f51414c49f56fc8e2b7c99253d9f8e",
10 | "version": "4.9.0"
11 | }
12 | },
13 | {
14 | "package": "SwiftyJSON",
15 | "repositoryURL": "https://github.com/SwiftyJSON/SwiftyJSON.git",
16 | "state": {
17 | "branch": null,
18 | "revision": "2b6054efa051565954e1d2b9da831680026cd768",
19 | "version": "5.0.0"
20 | }
21 | }
22 | ]
23 | },
24 | "version": 1
25 | }
26 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "StravaSwift",
8 | products: [
9 | .library(name: "StravaSwift", targets: ["StravaSwift"]),
10 | ],
11 | dependencies: [
12 | .package(url: "https://github.com/Alamofire/Alamofire.git", from: "4.9.0"),
13 | .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"),
14 | ],
15 | targets: [
16 | .target(name: "StravaSwift", dependencies: ["Alamofire", "SwiftyJSON"]),
17 | .testTarget(name: "StravaSwiftTests", dependencies: ["StravaSwift"]),
18 | ]
19 | )
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StravaSwift
2 |
3 | [](https://travis-ci.org/mpclarkson/StravaSwift)
4 | [](https://codebeat.co/projects/github-com-mpclarkson-stravaswift)
5 | [](http://cocoapods.org/pods/StravaSwift)
6 | [](http://cocoapods.org/pods/StravaSwift)
7 | [](http://cocoapods.org/pods/StravaSwift)
8 |
9 | This is a Swift wrapper for the [Strava v3 API](https://strava.github.io/api/).
10 |
11 | As this is a passion project, I only work on it when I have time. So, if you are interested in contributing, feel free to submit PRs.
12 |
13 | ## Example
14 |
15 | To run the example project, clone the repo, and run `pod install` from the Example directory first.
16 |
17 | ## Installation
18 |
19 | [StravaSwift](https://github.com/mpclarkson/StravaSwift) is available through the [Swift Package Manager](https://swift.org/package-manager/) and [CocoaPods](http://cocoapods.org).
20 |
21 | ### Swift Package Manager
22 |
23 | ```swift
24 | dependencies: [
25 | .package(url: "https://github.com/mpclarkson/StravaSwift.git", from: "1.0.1")
26 | ]
27 | ```
28 |
29 | ### Cocoapods
30 |
31 | To install it, simply add the following line to your Podfile:
32 |
33 | ```ruby
34 | pod "StravaSwift", '~> 1.0.1'
35 | ```
36 |
37 | ## Quick Start
38 |
39 | The full library documentation is available [here](http://cocoadocs.org/docsets/StravaSwift).
40 |
41 | * First, you must [register your app](http://labs.strava.com/developers/) with Strava and get an OAuth `client id` and `client secret`.
42 |
43 | * Initialize the Strava Client as follows, preferably in your `AppDelegate.swift` to ensure it is configured before you call it:
44 |
45 | ```swift
46 | let config = StravaConfig(
47 | clientId: YourStravaClientId,
48 | clientSecret: YourStravaClientSecret,
49 | redirectUri: YourRedirectUrl
50 | )
51 |
52 | StravaClient.sharedInstance.initWithConfig(config)
53 | ```
54 |
55 | * Note, by default the OAuth token is only available while the app is running, which means you need to request a new token. You can implement custom token storage and retrieval behaviour by overriding the default token `delegate` in the `StravaConfig` initializer which must implement the `TokenDelegate` protocol.
56 |
57 | * Register your redirect URL scheme in your `info.plist` file.
58 |
59 | * The authentication will use the Strava app be default if it is installed on the device. If the user does not have Strava installed, it will fallback on `SFAuthenticationSession` or `ASWebAuthenticationSession` depending on the iOS version. If your app is linked on or after iOS 9.0, you must add `strava` in you app’s `info.plist` file. It should be added as an entry fo the array under the `LSApplicationQueriesSchemes` key. Failure to do this will result in a crash when calling `canOpenUrl:`.
60 |
61 | ```xml
62 | LSApplicationQueriesSchemes
63 |
64 | strava
65 |
66 | ```
67 |
68 | * Implement the following method in your `AppDelegate.swift` to handle the OAuth redirection from Strava:
69 |
70 | ```swift
71 | let strava = StravaClient.sharedInstance
72 |
73 | func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
74 | return strava.handleAuthorizationRedirect(url)
75 | }
76 | ```
77 |
78 | * After authorizing, you can start requesting resources:
79 |
80 | ```swift
81 | strava.authorize() { result in
82 | switch result {
83 | case .success(let token):
84 | //do something for success
85 | case .failure(let error):
86 | //do something for error
87 | }
88 | }
89 | ```
90 |
91 | * Requesting resources:
92 |
93 | > The Router implementation is based on this
94 | Alamofire [example](https://github.com/Alamofire/Alamofire#api-parameter-abstraction):
95 |
96 | ```swift
97 | strava.request(Router.athletes(id: 9999999999)) { (athlete: Athlete?) in
98 | //do something with the athlete
99 | }
100 |
101 | let params = [
102 | 'page' = 2,
103 | 'per_page' = 25
104 | ]
105 |
106 | strava.request(Router.athleteActivities(params: params) { (activities: [Activity]?) in
107 | //do something with the activities
108 | }
109 | ```
110 |
111 | ## Todos
112 |
113 | - [ ] 100% API coverage (about 90% now)
114 | - [ ] Documentation
115 | - [ ] Tests
116 | - [ ] Better example app
117 |
118 | ## Author
119 |
120 | Matthew Clarkson, mpclarkson@gmail.com
121 |
122 | ## License
123 |
124 | StravaSwift is available under the MIT license. See the LICENSE file for more info.
125 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Achievement.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Achievement.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 24/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Achievement struct - details the type of achievement and the rank
14 | **/
15 | public struct Achievement: Strava {
16 | /** Achievement type enum **/
17 | public let type: AchievementType?
18 |
19 | /** Rank for the achievement **/
20 | public let rank: Int?
21 |
22 | /**
23 | Initializer
24 |
25 | - Parameter json: SwiftyJSON object
26 | - Internal
27 | **/
28 | public init(_ json: JSON) {
29 | type = json["type"].strava(AchievementType.self)
30 | rank = json["rank"].int
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Activity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Activity.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Achievement class - a detailed representation is returned if the activity is owned by the requesting athlete, otherwise
14 | a summary representation is returned for all other requests.
15 |
16 | Activity details, including segment efforts, splits and best efforts, are only available to the owner of the activity.
17 |
18 | By default, only “important” efforts are included. “Importance” is based on a number of factors and its value may change over time.
19 |
20 | **/
21 | public final class Activity: Strava {
22 |
23 | public typealias Speed = Double
24 | public typealias Count = Int
25 |
26 | public let id: Int?
27 | public let resourceState: ResourceState?
28 | public let externalId: String?
29 | public let uploadId: Int?
30 | public let athlete: Athlete?
31 | public let name: String?
32 | public let description: String?
33 | public let distance: Double?
34 | public let movingTime: TimeInterval?
35 | public let elapsedTime: TimeInterval?
36 | public let highElevation : Double?
37 | public let lowElevation : Double?
38 | public let totalElevationGain: Double?
39 | public let type: ActivityType?
40 | public let startDate: Date?
41 | public let startDateLocal: Date?
42 | public let timeZone: String?
43 | public let startLatLng: Location?
44 | public let endLatLng: Location?
45 | public let achievementCount: Count?
46 | public let kudosCount: Count?
47 | public let commentCount: Count?
48 | public let athleteCount: Count?
49 | public let photoCount: Count?
50 | public let totalPhotoCount: Count?
51 | public let photos: [Photo]?
52 | public let map: Map?
53 | public let trainer: Bool?
54 | public let commute: Bool?
55 | public let manual: Bool?
56 | public let `private`: Bool?
57 | public let flagged: Bool?
58 | public let workoutType: WorkoutType?
59 | public let gear: Gear?
60 | public let averageSpeed: Speed?
61 | public let maxSpeed: Speed?
62 | public let calories: Double?
63 | public let hasKudoed: Bool?
64 | public let segmentEfforts: [Effort]?
65 | public let splitsMetric: [Split]?
66 | public let splitsStandard: [Split]?
67 | public let bestEfforts: [Split]?
68 | public let kiloJoules: Double?
69 | public let averagePower : Double?
70 | public let maxPower : Double?
71 | public let deviceWatts : Bool?
72 | public let hasHeartRate : Bool?
73 | public let averageHeartRate : Double?
74 | public let maxHeartRate : Double?
75 |
76 |
77 | /**
78 | Initializer
79 |
80 | - Parameter json: SwiftyJSON object
81 | - Internal
82 | **/
83 | required public init(_ json: JSON) {
84 | id = json["id"].int
85 | resourceState = json["resource_state"].strava(ResourceState.self)
86 | externalId = json["external_id"].string
87 | uploadId = json["upload_id"].int
88 | athlete = json["athlete"].strava(Athlete.self)
89 | name = json["name"].string
90 | description = json["description"].string
91 | distance = json["distance"].double
92 | movingTime = json["moving_time"].double
93 | elapsedTime = json["elapsed_time"].double
94 | lowElevation = json["elev_low"].double
95 | highElevation = json["elev_high"].double
96 | totalElevationGain = json["total_elevation_gain"].double
97 | type = json["type"].strava(ActivityType.self)
98 | startDate = json["start_date"].string?.toDate()
99 | startDateLocal = json["start_date_local"].string?.toDate()
100 | startLatLng = json["start_latlng"].strava(Location.self)
101 | endLatLng = json["end_latlng"].strava(Location.self)
102 | achievementCount = json["achievement_count"].int
103 | kudosCount = json["kudos_count"].int
104 | commentCount = json["comment_count"].int
105 | athleteCount = json["athlete_count"].int
106 | photoCount = json["php_count"].int
107 | totalPhotoCount = json["total_photo_count"].int
108 | photos = json["photos"].strava(Photo.self)
109 | trainer = json["trainer"].bool
110 | commute = json["commute"].bool
111 | manual = json["manual"].bool
112 | `private` = json["private"].bool
113 | flagged = json["flagged"].bool
114 | workoutType = json["workout_type"].strava(WorkoutType.self)
115 | gear = json["gear"].strava(Gear.self)
116 | averageSpeed = json["average_speed"].double
117 | maxSpeed = json["max_speed"].double
118 | calories = json["calories"].double
119 | hasKudoed = json["has_kudoed"].bool
120 | segmentEfforts = json["segment_efforts"].strava(Effort.self)
121 | splitsMetric = json["splits_metric"].strava(Split.self)
122 | splitsStandard = json["splits_standard"].strava(Split.self)
123 | bestEfforts = json["best_efforts"].strava(Split.self)
124 | map = json["map"].strava(Map.self)
125 | timeZone = json["timezone"].string
126 | kiloJoules = json["kilojoules"].double
127 | averagePower = json["average_watts"].double
128 | maxPower = json["max_watts"].double
129 | deviceWatts = json["device_watts"].bool
130 | hasHeartRate = json["has_heartrate"].bool
131 | averageHeartRate = json["average_heartrate"].double
132 | maxHeartRate = json["max_heartrate"].double
133 | }
134 | }
135 |
136 | // MetaActivity is used by Effort to hold unique Activity ID
137 | public final class MetaActivity: Strava {
138 | public let id: Int?
139 |
140 | required public init(_ json: JSON) {
141 | id = json["id"].int
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/AlamofireRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyJSONRequest.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 15/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 | import SwiftyJSON
12 |
13 | //MARK: - Methods
14 |
15 | extension DataRequest {
16 |
17 | @discardableResult
18 | func responseStrava(_ completionHandler: @escaping (DataResponse) -> Void) -> Self {
19 | return responseStrava(nil, keyPath: nil, completionHandler: completionHandler)
20 | }
21 |
22 | @discardableResult
23 | func responseStrava(_ keyPath: String, completionHandler: @escaping (DataResponse) -> Void) -> Self {
24 | return responseStrava(nil, keyPath: keyPath, completionHandler: completionHandler)
25 | }
26 |
27 | @discardableResult
28 | func responseStrava(_ queue: DispatchQueue?, keyPath: String?, completionHandler: @escaping (DataResponse) -> Void) -> Self {
29 | return response(queue: queue, responseSerializer: DataRequest.StravaSerializer(keyPath), completionHandler: completionHandler)
30 | }
31 |
32 | @discardableResult
33 | func responseStravaArray(_ completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self {
34 | return responseStravaArray(nil, keyPath: nil, completionHandler: completionHandler)
35 | }
36 |
37 | @discardableResult
38 | func responseStravaArray(_ keyPath: String, completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self {
39 | return responseStravaArray(nil, keyPath: keyPath, completionHandler: completionHandler)
40 | }
41 |
42 | @discardableResult
43 | func responseStravaArray(_ queue: DispatchQueue?, completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self {
44 | return responseStravaArray(queue, keyPath: nil, completionHandler: completionHandler)
45 | }
46 |
47 | @discardableResult
48 | func responseStravaArray(_ queue: DispatchQueue?, keyPath: String?, completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self {
49 | return response(queue: queue, responseSerializer: DataRequest.StravaArraySerializer(keyPath), completionHandler: completionHandler)
50 | }
51 | }
52 |
53 | //MARK: Serializers
54 |
55 | //TODO: Clean these up so there is no duplication
56 |
57 | extension DataRequest {
58 |
59 | typealias SerializeResponse = (URLRequest?, HTTPURLResponse?, Data?, Error?)
60 |
61 | fileprivate static func parseResponse(_ info: SerializeResponse) -> (Result?, Error?) {
62 | let (request, response, data, error) = info
63 |
64 | guard let _ = data else {
65 | let error = generateError(failureReason: "Data could not be serialized. Input data was nil.", response: response)
66 | return (nil, error)
67 | }
68 |
69 | let JSONResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
70 | let result = JSONResponseSerializer.serializeResponse(request, response, data, error)
71 |
72 | return (result, nil)
73 | }
74 |
75 | fileprivate static func generateError(failureReason: String, response: HTTPURLResponse?) -> NSError {
76 | let errorDomain = "com.stravaswift.error"
77 | let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
78 | let code = response?.statusCode ?? 0
79 | let returnError = NSError(domain: errorDomain, code: code, userInfo: userInfo)
80 |
81 | return returnError
82 | }
83 |
84 | static func StravaSerializer(_ keyPath: String?) -> DataResponseSerializer {
85 | return DataResponseSerializer { request, response, data, error in
86 | let (result, e) = parseResponse((request, response, data, error))
87 |
88 | if let e = e {
89 | return .failure(e)
90 | }
91 |
92 | if let json = result?.value {
93 | let object = T.init(JSON(json))
94 | return .success(object)
95 | }
96 |
97 | return .failure(generateError(failureReason: "StravaSerializer failed to serialize response.", response: response))
98 | }
99 | }
100 |
101 | static func StravaArraySerializer(_ keyPath: String?) -> DataResponseSerializer<[T]> {
102 | return DataResponseSerializer { request, response, data, error in
103 |
104 | let (result, e) = parseResponse((request, response, data, error))
105 |
106 | if let e = e {
107 | return .failure(e)
108 | }
109 |
110 | if let json = result?.value {
111 | var results: [T] = []
112 | JSON(json).array?.forEach {
113 | results.append(T.init($0))
114 | }
115 |
116 | return .success(results)
117 | }
118 |
119 | return .failure(generateError(failureReason: "StravaSerializer failed to serialize response.", response: response))
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Athlete.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Athelete.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Athletes are Strava users, Strava users are athletes. The object is returned in detailed, summary or meta representations.
14 | **/
15 | public final class Athlete: Strava {
16 | public let id: Int?
17 | public let resourceState: ResourceState?
18 | public let firstname: String?
19 | public let lastname: String?
20 | public let profileMedium: URL?
21 | public let profile: URL?
22 | public let city: String?
23 | public let state: String?
24 | public let country: String?
25 | public let sex: Sex?
26 | public let friend: FollowingStatus?
27 | public let follower: FollowingStatus?
28 | public let premium:Bool?
29 | public let createdAt: Date?
30 | public let updatedAt: Date?
31 | public let friendCount: Int?
32 | public let followerCount: Int?
33 | public let mutualFriendCount: Int?
34 | public let datePreference: String?
35 | public let measurementPreference: Units?
36 | public let email: String?
37 | public let FTP: Int?
38 | public let weight: Double?
39 | public let clubs: [Club]?
40 | public let bikes: [Bike]?
41 | public let shoes: [Shoe]?
42 |
43 | /**
44 | Initializer
45 |
46 | - Parameter json: SwiftyJSON object
47 | - Internal
48 | **/
49 | required public init(_ json: JSON) {
50 | id = json["id"].int
51 | resourceState = json["resource_state"].strava(ResourceState.self)
52 | city = json["city"].string
53 | state = json["state"].string
54 | country = json["country"].string
55 | profileMedium = URL(optionalString: json["profile_medium"].string)
56 | profile = URL(optionalString: json["profile"].string)
57 | firstname = json["firstname"].string
58 | lastname = json["lastname"].string
59 | sex = json["sex"].strava(Sex.self)
60 | friend = json["friend"].strava(FollowingStatus.self)
61 | follower = json["follower"].strava(FollowingStatus.self)
62 | premium = json["premium"].bool
63 | createdAt = json["created_at"].string?.toDate()
64 | updatedAt = json["updated_at"].string?.toDate()
65 | followerCount = json["follower_count"].int
66 | friendCount = json["friend_count"].int
67 | mutualFriendCount = json["mutual_friend_count"].int
68 | datePreference = json["date_preference"].string
69 | measurementPreference = json["measurement_preference"].strava(Units.self)
70 | email = json["email"].string
71 | FTP = json["ftp"].int
72 | weight = json["weight"].double
73 | clubs = json["clubs"].strava(Club.self)
74 | bikes = json["bikes"].strava(Bike.self)
75 | shoes = json["shoes"].strava(Shoe.self)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/AthleteStats.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AthleteStats.swift
3 | // StravaSwift
4 | //
5 | // Created by Andy on 01/22/2017
6 | // Copyright © 2017 Andy Gonzalez. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Stats are aggregated data for an athlete
14 | **/
15 | public final class AthleteStats: Strava {
16 |
17 | public final class Totals {
18 |
19 | public let count: Int?
20 | public let distance: Double?
21 | public let movingTime: TimeInterval?
22 | public let elapsedTime: TimeInterval?
23 | public let elevationGain: Double?
24 | public let achievementCount: Int?
25 |
26 | required public init(_ json: JSON) {
27 | count = json["count"].int
28 | distance = json["distance"].double
29 | movingTime = json["moving_time"].double
30 | elapsedTime = json["elapsed_time"].double
31 | elevationGain = json["elevation_gain"].double
32 | achievementCount = json["achievement_count"].int
33 | }
34 | }
35 |
36 | public let biggestRideDistance: Double?
37 | public let biggestClimbElevationGain: Double?
38 | public let recentRideTotals: Totals?
39 | public let recentRunTotals: Totals?
40 | public let recentSwimTotals: Totals?
41 | public let ytdRideTotals: Totals?
42 | public let ytdRunTotals: Totals?
43 | public let ytdSwimTotals: Totals?
44 | public let allRideTotals: Totals?
45 | public let allRunTotals: Totals?
46 | public let allSwimTotals: Totals?
47 |
48 | /**
49 | Initializer
50 |
51 | - Parameter json: SwiftyJSON object
52 | - Internal
53 | **/
54 | required public init(_ json: JSON) {
55 |
56 | biggestRideDistance = json["biggest_ride_distance"].double
57 | biggestClimbElevationGain = json["biggest_climb_elevation_gain"].double
58 | recentRideTotals = Totals(json["recent_ride_totals"])
59 | recentRunTotals = Totals(json["recent_run_totals"])
60 | recentSwimTotals = Totals(json["recent_swim_totals"])
61 | ytdRideTotals = Totals(json["ytd_ride_totals"])
62 | ytdRunTotals = Totals(json["ytd_run_totals"])
63 | ytdSwimTotals = Totals(json["ytd_swim_totals"])
64 | allRideTotals = Totals(json["all_ride_totals"])
65 | allRunTotals = Totals(json["all_run_totals"])
66 | allSwimTotals = Totals(json["all_swim_totals"])
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Club.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Club.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Clubs represent groups of athletes on Strava. They can be public or private. The object is returned in summary or detailed representations.
14 | **/
15 | public final class Club: Strava {
16 | public let id: Int?
17 | public let profileMedium: URL?
18 | public let profile: URL?
19 | public let name: String?
20 | public let description: String?
21 | public let city: String?
22 | public let state: String?
23 | public let country: String?
24 | public let clubType: ClubType?
25 | public let sportType: SportType?
26 | public let isPrivate: Bool?
27 | public let memberCount: Int?
28 | public let resourceState: ResourceState?
29 |
30 | /**
31 | Initializer
32 |
33 | - Parameter json: SwiftyJSON object
34 | - Internal
35 | **/
36 | required public init(_ json: JSON) {
37 | id = json["id"].int
38 | name = json["name"].string
39 | description = json["description"].string
40 | resourceState = json["resource_state"].strava(ResourceState.self)
41 | city = json["city"].string
42 | state = json["state"].string
43 | country = json["country"].string
44 | clubType = json["club_type"].strava(ClubType.self)
45 | sportType = json["sport_type"].strava(SportType.self)
46 | profileMedium = URL(optionalString: json["profile_medium"].string)
47 | profile = URL(optionalString: json["profile"].string)
48 | isPrivate = json["private"].bool
49 | memberCount = json["member_count"].int
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Comment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Comment.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 24/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Comments on an activity can be viewed by any user. However, only internal applications are allowed to create or delete them.
14 | Comment posting can be enabled on a per application basis, email developers@strava.com for more information.
15 | **/
16 | public final class Comment: Strava {
17 | public let id: Int?
18 | public let resourceState: ResourceState?
19 | public let activityId: Int?
20 | public let text: String?
21 | public let athlete: Athlete?
22 | public let createdAt: Date?
23 |
24 | /**
25 | Initializer
26 |
27 | - Parameter json: SwiftyJSON object
28 | - Internal
29 | **/
30 | required public init(_ json: JSON) {
31 | id = json["id"].int
32 | resourceState = json["resource_state"].strava(ResourceState.self)
33 | activityId = json["activity_id"].int
34 | text = json["text"].string
35 | athlete = json["athlete"].strava(Athlete.self)
36 | createdAt = json["created_at"].string?.toDate()
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/DefaultTokenDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultTokenDelegate.swift
3 | // Pods
4 | //
5 | // Created by MATTHEW CLARKSON on 24/05/2016.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | The default token delegate. You should replace this with something that persists the token (e.g. to NSUserDefaults)
13 | **/
14 | open class DefaultTokenDelegate: TokenDelegate {
15 | fileprivate var token: OAuthToken?
16 |
17 | /**
18 | Retrieves the token
19 |
20 | - Returns: a optional OAuthToken
21 | **/
22 | open func get() -> OAuthToken? {
23 | return token
24 | }
25 |
26 | /**
27 | Stores the token internally (note that it is not persisted between app start ups)
28 |
29 | - Parameter token: an optional OAuthToken
30 | **/
31 | open func set(_ token: OAuthToken?) {
32 | self.token = token
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Effort.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Effort.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | A segment effort represents an athlete’s attempt at a segment. It can also be thought of as a portion of a ride that covers a segment. The object is returned in summary or detailed representations. They are currently the same.
14 | **/
15 | public final class Effort: Strava {
16 | public let id: Int?
17 | public let resourceState: ResourceState?
18 | public let name: String?
19 | public let activity: MetaActivity?
20 | public let athlete: Athlete?
21 | public let elapsedTime: Int?
22 | public let movingTime: Int?
23 | public let startDate: Date?
24 | public let startDateLocal: Date?
25 | public let distance: Double?
26 | public let startIndex: Int?
27 | public let endIndex: Int?
28 | public let averageCadence: Double?
29 | public let averageWatts: Double?
30 | public let deviceWatts: Bool?
31 | public let averageHeartRate: Double?
32 | public let maxHeartRate: Int?
33 | public let segment: Segment?
34 | public let komRank: Int?
35 | public let prRank: Int?
36 | public let hidden: Bool?
37 |
38 | /**
39 | Initializer
40 |
41 | - Parameter json: SwiftyJSON object
42 | - Internal
43 | **/
44 | required public init(_ json: JSON) {
45 | id = json["id"].int
46 | resourceState = json["resource_state"].strava(ResourceState.self)
47 | name = json["name"].string
48 | activity = json["activity"].strava(MetaActivity.self)
49 | athlete = json["athlete"].strava(Athlete.self)
50 | elapsedTime = json["elapsed_time"].int
51 | movingTime = json["moving_time"].int
52 | startDate = json["start_date"].string?.toDate()
53 | startDateLocal = json["start_date_local"].string?.toDate()
54 | distance = json["distance"].double
55 | startIndex = json["start_index"].int
56 | endIndex = json["end_index"].int
57 | averageCadence = json["average_cadence"].double
58 | averageWatts = json["average_watts"].double
59 | deviceWatts = json["device_watts"].bool
60 | averageHeartRate = json["average_heartrate"].double
61 | maxHeartRate = json["max_heartrate"].int
62 | segment = json["segment"].strava(Segment.self)
63 | komRank = json["kom_rank"].int
64 | prRank = json["pr_rank"].int
65 | hidden = json["hidden"].bool
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Event.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Event.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Group Events are optionally recurring events for club members. Only club members can access private club events. The objects are returned in summary representation.
14 | **/
15 | public final class Event: Strava {
16 | public let id: Int?
17 | public let resourceState: ResourceState?
18 | public let title: String?
19 | public let descr: String?
20 | public let clubId: Int?
21 | public let organizingAthlete: Athlete?
22 | public let activityType: ActivityType?
23 | public let createdAt: Date?
24 | public let routeId: Int?
25 | public let womenOnly: Bool?
26 | public let `private`: Bool?
27 | public let skillLevels: SkillLevel?
28 | public let terrain: Terrain?
29 | public let upcomingOccurrences: [Date]?
30 |
31 | /**
32 | Initializer
33 |
34 | - Parameter json: SwiftyJSON object
35 | - Internal
36 | **/
37 | required public init(_ json: JSON) {
38 | resourceState = json["resource_state"].strava(ResourceState.self)
39 | id = json["id"].int
40 | title = json["title"].string
41 | descr = json["description"].string
42 | clubId = json["club_id"].int
43 | organizingAthlete = json["organizing_athlete"].strava(Athlete.self)
44 | activityType = json["activity_type"].strava(ActivityType.self)
45 | createdAt = json["created_at"].string?.toDate()
46 | routeId = json["route_id"].int
47 | womenOnly = json["women_only"].bool
48 | `private` = json["private"].bool
49 | skillLevels = json["skill_level"].strava(SkillLevel.self)
50 | terrain = json["terrain"].strava(Terrain.self)
51 | upcomingOccurrences = json["terrain"].arrayValue.compactMap { $0.string?.toDate() }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/GearBikeShoe.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gear.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 15/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Gear represents equipment used during an activity. The object is returned in summary or detailed representations.
14 | **/
15 | open class Gear: Strava {
16 | public let id: String?
17 | public let primary: Bool?
18 | public let name: String?
19 | public let description: String?
20 | public let resourceState: ResourceState?
21 | public let distance: Double?
22 | public let brandName: String?
23 | public let modelName: String?
24 |
25 | /**
26 | Initializer
27 |
28 | - Parameter json: SwiftyJSON object
29 | - Internal
30 | **/
31 | required public init(_ json: JSON) {
32 | id = json["id"].string
33 | primary = json["primary"].bool
34 | name = json["name"].string
35 | description = json["description"].string
36 | resourceState = json["resource_state"].strava(ResourceState.self)
37 | distance = json["distance"].double
38 | brandName = json["brand_name"].string
39 | modelName = json["model_name"].string
40 | }
41 | }
42 |
43 | /**
44 | Shoe represents shoes worn on a run. The object is returned in summary or detailed representations.
45 | **/
46 | public final class Shoe: Gear {}
47 |
48 | /**
49 | Bike represents a... bike! The object is returned in summary or detailed representations.
50 | **/
51 | public final class Bike: Gear {
52 | public let frameType: FrameType?
53 |
54 | /**
55 | Initializer
56 |
57 | - Parameter json: SwiftyJSON object
58 | - Internal
59 | **/
60 | required public init(_ json: JSON) {
61 | frameType = json["frame_type"].strava(FrameType.self)
62 | super.init(json)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/GeneralExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GeneralExtensions.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 19/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension RawRepresentable {
12 | init?(optionalRawValue rawValue: RawValue?) {
13 | guard let rawValue = rawValue, let value = Self(rawValue: rawValue) else { return nil }
14 | self = value
15 | }
16 | }
17 |
18 | extension DateFormatter {
19 | func dateFromString(optional string: String?) -> Date? {
20 | guard let string = string else { return nil }
21 | return date(from: string)
22 | }
23 | }
24 |
25 | extension URL {
26 | init?(optionalString string: String?) {
27 | guard let string = string else { return nil }
28 | self.init(string: string)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Location.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Location.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 23/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Represents the latitude and longitude of a point
14 | **/
15 | public struct Location: Strava {
16 | public let lat: Double?
17 | public let lng: Double?
18 |
19 | /**
20 | Initializer (failable)
21 |
22 | - Parameter json: SwiftyJSON object
23 | - Internal
24 | **/
25 | public init(_ json: JSON) {
26 | let points = json.arrayValue
27 | self.lat = points.first?.double
28 | self.lng = points.last?.double
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Map.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Map.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 24/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Represents a map of a ride or route
14 | **/
15 | public final class Map: Strava {
16 | public let id: String?
17 | public let resourceState: ResourceState?
18 | public let polyline: String?
19 | public let summaryPolyline: String?
20 |
21 | /**
22 | Initializer
23 |
24 | - Parameter json: SwiftyJSON object
25 | - Internal
26 | **/
27 | required public init(_ json: JSON) {
28 | id = json["id"].string
29 | resourceState = json["resource_state"].strava(ResourceState.self)
30 | polyline = json["polyline"].string
31 | summaryPolyline = json["summary_polyline"].string
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/ModelEnums.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ModelEnums.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 23/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 |
10 | /**
11 | Athlete's gender
12 |
13 | - Male
14 | - Female
15 | **/
16 | public enum Sex: String {
17 | case male = "M"
18 | case female = "F"
19 | }
20 |
21 | /**
22 | Following status of the athlete
23 |
24 | - Pending
25 | - Accepted
26 | - Blocked
27 | **/
28 | public enum FollowingStatus: String {
29 | case accepted
30 | case blocked
31 | case pending
32 | }
33 |
34 | /**
35 | Membership status for a club
36 |
37 | - Pending
38 | - Accepted
39 | **/
40 | public enum MembershipStatus: String {
41 | case member
42 | case pending
43 | }
44 |
45 | /**
46 | Measurement units
47 |
48 | - Feet
49 | - Meters
50 | **/
51 | public enum Units: String {
52 | case feet
53 | case meters
54 | }
55 |
56 | /**
57 | Resource state (describes the detail of the resource)
58 |
59 | - Meta
60 | - Summary
61 | - Detailed
62 | **/
63 | public enum ResourceState: Int {
64 | case meta = 1
65 | case summary = 2
66 | case detailed = 3
67 | }
68 |
69 | /**
70 | Workout type
71 |
72 | - Run
73 | - RaceRun
74 | - LongRun
75 | - WorkOutRun
76 | - Ride
77 | - RaceRide
78 | - WorkoutRide
79 | **/
80 | public enum WorkoutType: Int {
81 | case run = 0
82 | case raceRun = 1
83 | case longRun = 3
84 | case workoutRun = 4
85 | case ride = 10
86 | case raceRide = 11
87 | case workoutRide = 12
88 | }
89 |
90 | /**
91 | Activity type
92 |
93 | - AlpineSki
94 | - BackcountrySki
95 | - Canoeing
96 | - Crossfit
97 | - EBikeRide
98 | - Elliptical
99 | - Golf
100 | - Handcycle
101 | - Hike
102 | - IceSkate
103 | - InlineSkate
104 | - Kayaking
105 | - Kitesurf
106 | - NordicSki
107 | - Ride
108 | - RockClimbing
109 | - RollerSki
110 | - Rowing
111 | - Run
112 | - Sail
113 | - Skateboard
114 | - Snowboard
115 | - Snowshoe
116 | - Soccer
117 | - StairStepper
118 | - StandUpPaddling
119 | - Surfing
120 | - Swim
121 | - Velomobile
122 | - VirtualRide
123 | - VirtualRun
124 | - Walk
125 | - WeightTraining
126 | - Wheelchair
127 | - Windsurf
128 | - Workout
129 | - Yoga
130 | **/
131 | public enum ActivityType: String {
132 | case alpineSki = "AlpineSki"
133 | case backcountrySki = "BackcountrySki"
134 | case canoeing = "Canoeing"
135 | case crossfit = "Crossfit"
136 | case eBikeRide = "EBikeRide"
137 | case elliptical = "Elliptical"
138 | case golf = "Golf"
139 | case handcycle = "Handcycle"
140 | case hike = "Hike"
141 | case iceSkate = "IceSkate"
142 | case inlineSkate = "InlineSkate"
143 | case kayaking = "Kayaking"
144 | case kitesurf = "Kitesurf"
145 | case nordicSki = "NordicSki"
146 | case ride = "Ride"
147 | case rockClimbing = "RockClimbing"
148 | case rollerSki = "RollerSki"
149 | case rowing = "Rowing"
150 | case run = "Run"
151 | case sail = "Sail"
152 | case skateboard = "Skateboard"
153 | case snowboard = "Snowboard"
154 | case snowshoe = "Snowshoe"
155 | case soccer = "Soccer"
156 | case stairStepper = "StairStepper"
157 | case standUpPaddling = "StandUpPaddling"
158 | case surfing = "Surfing"
159 | case swim = "Swim"
160 | case velomobile = "Velomobile"
161 | case virtualRide = "VirtualRide"
162 | case virtualRun = "VirtualRun"
163 | case walk = "Walk"
164 | case weightTraining = "WeightTraining"
165 | case wheelchair = "Wheelchair"
166 | case windsurf = "Windsurf"
167 | case workout = "Workout"
168 | case yoga = "Yoga"
169 | }
170 |
171 | /**
172 | Sport type
173 |
174 | - Cycling
175 | - Running
176 | - Triathlon
177 | - Other
178 | **/
179 | public enum SportType: String {
180 | case cycling
181 | case running
182 | case triathlon
183 | case other
184 | }
185 |
186 | /**
187 | Club type
188 |
189 | - CasualClub
190 | - RacingTeam
191 | - Shop
192 | - Company
193 | - Other
194 | **/
195 | public enum ClubType: String {
196 | case casualClub = "casual_club"
197 | case racingTeam = "racing_team"
198 | case shop
199 | case company
200 | case other
201 | }
202 |
203 | /**
204 | Frame type (cycling only)
205 |
206 | - MTB
207 | - Cross
208 | - Road
209 | - TimeTrial
210 | **/
211 | public enum FrameType: Int {
212 | case mtb = 1
213 | case cross = 2
214 | case road = 3
215 | case timeTrial = 4
216 | }
217 |
218 | /**
219 | Resolution type
220 |
221 | - Low
222 | - Medium
223 | - High
224 | **/
225 | public enum ResolutionType: String {
226 | case low
227 | case medium
228 | case high
229 | }
230 |
231 | /**
232 | Stream type (ie the data type)
233 |
234 | - Time
235 | - LatLng
236 | - Distance
237 | - Altitude
238 | - VelocitySmooth
239 | - HeartRate
240 | - Cadence
241 | - Watts
242 | - Temp
243 | - Moving
244 | - GradeSmooth
245 | **/
246 | public enum StreamType: String {
247 | case time
248 | case latLng = "latlng"
249 | case distance
250 | case altitude
251 | case velocitySmooth = "velocity_smooth"
252 | case heartRate = "heartrate"
253 | case cadence
254 | case watts
255 | case temp
256 | case moving
257 | case gradeSmooth = "grade_smooth"
258 |
259 | //Description of the units associated with the stream
260 | var unit: String {
261 | switch self {
262 | case .time:
263 | return "integer seconds"
264 | case .latLng:
265 | return "floats [latitude, longitude]"
266 | case .distance:
267 | return "float meters"
268 | case .altitude:
269 | return "float meters"
270 | case .velocitySmooth:
271 | return "float meters per second"
272 | case .heartRate:
273 | return "integer BPM"
274 | case .cadence:
275 | return "integer RPM"
276 | case .watts:
277 | return "integer watts"
278 | case .temp:
279 | return "integer degrees Celsius"
280 | case .moving:
281 | return "boolean"
282 | case .gradeSmooth:
283 | return "float percent"
284 | }
285 | }
286 | }
287 |
288 | /**
289 | Skill level
290 |
291 | - Casual
292 | - Tempo
293 | - Hammerfest
294 | **/
295 | public enum SkillLevel: Int {
296 | case casual = 1
297 | case tempo = 2
298 | case hammerfest = 4
299 | }
300 |
301 | /**
302 | Terrain description
303 |
304 | - MostlyFlat
305 | - RollingHills
306 | - KillerClimbs
307 | **/
308 | public enum Terrain: Int {
309 | case mostlyFlat
310 | case rollingHills
311 | case killerClimbs
312 | }
313 |
314 | /**
315 | Photo source
316 |
317 | - Strava
318 | - Instagram
319 | **/
320 | public enum PhotoSource: Int {
321 | case strava = 1
322 | case instagram = 2
323 | }
324 |
325 | /**
326 | Achievement type
327 |
328 | - Overall
329 | - PR
330 | - YearOverall
331 | **/
332 | public enum AchievementType: String {
333 | case overall
334 | case pr
335 | case yearOverall = "year_overall"
336 | }
337 |
338 | /** Route type enum **/
339 | public enum RouteType: Int {
340 | case ride = 1
341 | case run = 2
342 | }
343 |
344 | /** Route sub type enum **/
345 | public enum RouteSubType: Int {
346 | case road = 1
347 | case mtb = 2
348 | case cx = 3
349 | case trail = 4
350 | case mixed = 5
351 | }
352 |
353 | /**
354 | Data type enum for uploaded activities
355 | **/
356 | public enum DataType: String {
357 | case fit
358 | case fitGz = "fit.gz"
359 | case tcx
360 | case tcxGz = "tcx.gz"
361 | case gpx
362 | case gpxGz = "gpx.gz"
363 | }
364 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/NSURLExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSURLExtensions.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 13/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension URL {
12 | func getQueryParameters() -> Dictionary? {
13 | var results = [String:String]()
14 | let keyValues = self.query?.components(separatedBy: "&")
15 | keyValues?.forEach {
16 | let kv = $0.components(separatedBy: "=")
17 | if kv.count > 1 {
18 | results[kv[0]] = kv[1]
19 | }
20 | }
21 | return results
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/OAuthToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OAuthToken.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | OAuthToken which is required for requesting Strava resources
14 | **/
15 | public struct OAuthToken: Strava {
16 |
17 | /** The access token **/
18 | public let accessToken: String?
19 |
20 | /** The refresh token **/
21 | public let refreshToken: String?
22 |
23 | /** Expiry for the token in seconds since the epoch **/
24 | public let expiresAt : Int?
25 |
26 | /** The athlete **/
27 | public let athlete: Athlete?
28 |
29 | /**
30 | Initializers
31 |
32 | - Parameter json: A SwiftyJSON object
33 | **/
34 | public init(_ json: JSON) {
35 | accessToken = json["access_token"].string
36 | refreshToken = json["refresh_token"].string
37 | expiresAt = json["expires_at"].int
38 | athlete = Athlete(json["athlete"])
39 | }
40 |
41 | public init(access: String?, refresh: String?, expiry: Int?) {
42 | self.accessToken = access
43 | self.refreshToken = refresh
44 | self.expiresAt = expiry
45 | self.athlete = nil
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Photo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Photo.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 23/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Photos are objects associated with an activity. Currently, the only external photo source is Instagram. Photos can also be stored on Strava - these photos are referred to as “native”.
14 | **/
15 | public final class Photo: Strava {
16 | public let id: Int?
17 | public let uniqueId: String?
18 | public let activityId: Int?
19 | public let resourceState: ResourceState?
20 | public let urls: [URL]?
21 | public let caption: String?
22 | public let source: PhotoSource?
23 | public let uploadedAt: Date?
24 | public let createdAt: Date?
25 | public let location: Location?
26 | public let refs: String?
27 | public let uuid: String?
28 | public let type: String?
29 |
30 | /**
31 | Initializer
32 |
33 | - Parameter json: SwiftyJSON object
34 | - Internal
35 | **/
36 | required public init(_ json: JSON) {
37 | id = json["id"].int
38 | resourceState = json["resource_state"].strava(ResourceState.self)
39 | uniqueId = json["unique_id"].string
40 | activityId = json["activity_id"].int
41 | urls = json["urls"].dictionary?.compactMap { URL(optionalString: $0.1.string) }
42 | caption = json["caption"].string
43 | source = json["source"].strava(PhotoSource.self)
44 | uploadedAt = json["uploaded_at"].string?.toDate()
45 | createdAt = json["created_at"].string?.toDate()
46 | location = json["location"].strava(Location.self)
47 | refs = json["refs"].string
48 | uuid = json["uuid"].string
49 | type = json["type"].string
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Route.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Route.swift
3 | // Pods
4 | //
5 | // Created by MATTHEW CLARKSON on 25/05/2016.
6 | //
7 | //
8 |
9 | import SwiftyJSON
10 |
11 | /**
12 | Routes are manually-created paths made up of sections called legs.
13 | **/
14 | public final class Route: Strava {
15 | public let id: Int?
16 | public let resourceState: ResourceState?
17 | public let name: String?
18 | public let description: String?
19 | public let athlete: Athlete?
20 | public let distance: Double?
21 | public let elevationGain: Double?
22 | public let map: Map?
23 | public let type: RouteType?
24 | public let subType: RouteSubType?
25 | public let `private`: Bool?
26 | public let starred: Bool?
27 | public let timeStamp: Int?
28 | public let segments: [Segment]?
29 |
30 | /**
31 | Initializer
32 |
33 | - Parameter json: SwiftyJSON object
34 | - Internal
35 | **/
36 | required public init(_ json: JSON) {
37 | id = json["id"].int
38 | resourceState = json["resource_state"].strava(ResourceState.self)
39 | name = json["name"].string
40 | description = json["description"].string
41 | athlete = json["athlete"].strava(Athlete.self)
42 | distance = json["distance"].double
43 | elevationGain = json["elevation_gain"].double
44 | map = json["map"].strava(Map.self)
45 | type = json["type"].strava(RouteType.self)
46 | subType = json["sub_type"].strava(RouteSubType.self)
47 | `private` = json["private"].bool
48 | starred = json["starred"].bool
49 | timeStamp = json["time_stamp"].int
50 | segments = json["segments"].strava(Segment.self)
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Router.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Router.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Alamofire
11 | import SwiftyJSON
12 |
13 |
14 | /**
15 | Router enum for type safe routing. For information on how this is used please [see here](https://github.com/Alamofire/Alamofire#api-parameter-abstraction).
16 | **/
17 | public enum Router {
18 |
19 | public typealias Id = Int
20 | public typealias Params = [String: Any]?
21 |
22 | /**
23 | Requests a Strava OAuth token
24 |
25 | - parameter code: the code returned from Strava after granting access to the application
26 | **/
27 | case token(code: String)
28 |
29 | /**
30 | Requests a Strava OAuth token refresh
31 |
32 | - parameter refresh: the refresh token returned from Strava after granting access to the application
33 | **/
34 | case refresh(refreshToken: String)
35 |
36 | /**
37 | Allows an application to revoke its access to an athlete’s data. This will invalidate all access tokens associated with the ‘athlete,application’ pair used to create the token. The application will be removed from the Athlete Settings page on Strava. All requests made using invalidated tokens will receive a 401 Unauthorized response.
38 |
39 | - parameter token: the access token to deauthorize
40 | **/
41 | case deauthorize(accessToken: String)
42 |
43 | /**
44 | Gets the current user/athlete
45 | **/
46 | case athlete
47 |
48 | /**
49 | Updates the current user/athlete
50 | **/
51 | case updateAthlete
52 |
53 | /**
54 | Lists the current user/athlete's activities
55 |
56 | - parameter params: a [String: String] dictionary of acceptable parameters
57 | **/
58 | case athleteActivities(params: Params)
59 |
60 | /**
61 | Lists the current user/athlete's friends
62 |
63 | - parameter params: a [String: String] dictionary of acceptable parameters
64 | **/
65 | case athleteFriends(params: Params)
66 |
67 | /**
68 | Lists the current user/athlete's followers
69 |
70 | - parameter params: a [String: String] dictionary of acceptable parameters
71 | **/
72 | case athleteFollowers(params: Params)
73 |
74 | /**
75 | Lists the current user/athlete's clubs
76 |
77 | - parameter params: a [String: String] dictionary of acceptable parameters
78 | **/
79 | case athleteClubs(params: Params)
80 |
81 | /**
82 | Gets a specific athlete
83 |
84 | - parameter id: the athlete id
85 | - parameter params: a [String: String] dictionary of acceptable parameters
86 | **/
87 | case athletes(id: Id, params: Params)
88 |
89 | /**
90 | Lists a specific athlete's friends
91 |
92 | - parameter id: the athlete id
93 | - parameter params: a [String: String] dictionary of acceptable parameters
94 | **/
95 | case athletesFriends(id: Id, params: Params)
96 |
97 | /**
98 | Lists a specific athlete's followers
99 |
100 | - parameter id: the athlete id
101 | - parameter params: a [String: String] dictionary of acceptable parameters
102 | **/
103 | case athletesFollowers(id: Id, params: Params)
104 |
105 | /**
106 | Lists athletes the current user and the requested athlete are both following
107 |
108 | - parameter id: the athlete id
109 | - parameter params: a [String: String] dictionary of acceptable parameters
110 | **/
111 | case athletesBothFollowing(id: Id, params: Params)
112 |
113 | /**
114 | Gets the statistics for a specific athlete
115 |
116 | - parameter id: the athlete id
117 | - parameter params: a [String: String] dictionary of acceptable parameters
118 | **/
119 | case athletesStats(id: Id, params: Params)
120 |
121 | /**
122 | Lists the specific athlete's KOMS
123 |
124 | - parameter id: the athlete id
125 | - parameter params: a [String: String] dictionary of acceptable parameters
126 | **/
127 | case athletesKoms(id: Id, params: Params)
128 |
129 | /**
130 | Creates a new manual activity in Strava for the athlete (not for uploading files)
131 |
132 | - parameter params: a [String: String] dictionary representing the activity
133 | **/
134 | case createActivity(params: Params)
135 |
136 | /**
137 | Updates an activity, requires write permissions
138 | - parameter activity: an Activity object
139 | **/
140 | case updateActivity(activity: Activity)
141 |
142 | /**
143 | Deletes an activity, requires write permissions
144 |
145 | - parameter activity: an Activity object
146 | **/
147 | case deleteActivity(activity: Activity)
148 |
149 | /**
150 | Gets an activity
151 |
152 | - parameter id: the activity id
153 | - parameter params: a [String: String] dictionary of acceptable parameters
154 | **/
155 | case activities(id: Id, params: Params)
156 |
157 | /**
158 | Lists kudos for an activity
159 |
160 | - parameter id: the activity id
161 | - parameter params: a [String: String] dictionary of acceptable parameters
162 | **/
163 | case activitiesKudos(id: Id, params: Params)
164 |
165 | /**
166 | Lists comments for an activity
167 |
168 | - parameter id: the activity id
169 | - parameter params: a [String: String] dictionary of acceptable parameters
170 | **/
171 | case activitiesComments(id: Id, params: Params)
172 |
173 | /**
174 | Lists photos for an activity
175 |
176 | - parameter id: the activity id
177 | - parameter params: a [String: String] dictionary of acceptable parameters
178 | **/
179 | case activitiesPhotos(id: Id, params: Params)
180 |
181 | /**
182 | Lists related activities
183 |
184 | - parameter id: the activity id
185 | - parameter params: a [String: String] dictionary of acceptable parameters
186 | **/
187 | case activitiesRelated(id: Id, params: Params)
188 |
189 | /**
190 | List the recent activities performed by the current athlete and those they are following. Pagination is supported. However, results are limited to the last 200 total activities.
191 |
192 | - parameter id: the activity id
193 | - parameter params: a [String: String] dictionary of acceptable parameters
194 | **/
195 | case activitiesFriends(id: Id, params: Params)
196 |
197 | /**
198 | Heartrate and power zones are set by the athlete. This endpoint returns the time (seconds) in each zone. The distribution is not customizable. Requires an access token associated with the owner of the activity and the owner must be a premium user.
199 |
200 | - parameter id: the activity id
201 | - parameter params: a [String: String] dictionary of acceptable parameters
202 | **/
203 | case activitiesZones(id: Id, params: Params)
204 |
205 | /**
206 | Lists all laps associated with an activity
207 |
208 | - parameter id: the activity id
209 | - parameter params: a [String: String] dictionary of acceptable parameters
210 | **/
211 | case activitiesLaps(id: Id, params: Params)
212 |
213 | /**
214 | Retrieves a club
215 |
216 | - parameter id: the club id
217 | - parameter params: a [String: String] dictionary of acceptable parameters
218 | **/
219 | case clubs(id: Id, params: Params)
220 |
221 | /**
222 | Lists announcments for a club
223 |
224 | - parameter id: the club id
225 | - parameter params: a [String: String] dictionary of acceptable parameters
226 | **/
227 | case clubsAnnouncements(id: Id, params: Params)
228 |
229 | /**
230 | Lists events for a club
231 |
232 | - parameter id: the club id
233 | - parameter params: a [String: String] dictionary of acceptable parameters
234 | **/
235 | case clubsEvents(id: Id, params: Params)
236 |
237 | /**
238 | Lists members of a club
239 |
240 | - parameter id: the club id
241 | - parameter params: a [String: String] dictionary of acceptable parameters
242 | **/
243 | case clubsMembers(id: Id, params: Params)
244 |
245 | /**
246 | Lists activities by club members
247 |
248 | - parameter id: the club id
249 | - parameter params: a [String: String] dictionary of acceptable parameters
250 | **/
251 | case clubsActivities(id: Id, params: Params)
252 |
253 | /**
254 | Join a club (ie the current athlete)
255 |
256 | - parameter id: the club id
257 | **/
258 | case clubsJoin(id: Id)
259 |
260 | /**
261 | Leave a club (ie the current athlete)
262 |
263 | - parameter id: the club id
264 | **/
265 | case clubsLeave(id: Id)
266 |
267 | /**
268 | Retrieves a gear object
269 |
270 | - parameter id: the gear id
271 | **/
272 | case gear(id: Id, params: Params)
273 |
274 | /**
275 | Retrieves a segment
276 |
277 | - parameter id: the segment id
278 | - parameter params: a [String: String] dictionary of acceptable parameters
279 | **/
280 | case segments(id: Id, params: Params)
281 |
282 | /**
283 | Lists segments the current athlete has starred
284 |
285 | - parameter params: a [String: String] dictionary of acceptable parameters
286 | **/
287 | case segmentsStarred(params: Params)
288 |
289 | /**
290 | Lists efforts for a segment
291 |
292 | - parameter id: the segment id
293 | - parameter params: a [String: String] dictionary of acceptable parameters
294 | **/
295 | case segmentsEfforts(id: Id, params: Params)
296 |
297 | /**
298 | Lists the leaderboards for a segment
299 |
300 | - parameter id: the segment id
301 | - parameter params: a [String: String] dictionary of acceptable parameters
302 | **/
303 | case segmentsLeaderboards(id: Id, params: Params)
304 |
305 | /**
306 | Find popular segments within a given area.
307 |
308 | - parameter id: the segment id
309 | - parameter params: a [String: String] dictionary of acceptable parameters
310 | **/
311 | case segmentsExplore(id: Id, params: Params)
312 |
313 | /**
314 | Retrieve details about a specific segment effort. The effort must be public or it must correspond to the current athlete.
315 |
316 | - parameter id: the segment efforr id
317 | - parameter params: a [String: String] dictionary of acceptable parameters
318 | **/
319 | case segmentEfforts(id: Id, params: Params)
320 |
321 | /**
322 | Retrieves details about a route. Private routes can only be accessed if owned by the authenticating user and the token has view_private permissions. For raw data associated with a route see route streams.
323 |
324 | - parameter id: the route id
325 | **/
326 | case routes(id: Id)
327 |
328 | /**
329 | Lists a specific athlete’s routes. Private routes will only be included if the authenticating user is viewing their own routes and the token has view_private permissions.
330 |
331 | - parameter id: the route id
332 | - parameter params: a [String: String] dictionary of acceptable parameters
333 | **/
334 | case athleteRoutes(id: Id, params: Params)
335 |
336 | /**
337 | Streams represent the raw data of the uploaded file. External applications may only access this information for activities owned by the authenticated athlete.
338 |
339 | - parameter id: the activity id
340 | - parameter types: single stream type or comma-separated list of types, if the activity does not have that stream it will not be included in the response
341 | **/
342 | case activityStreams(id: Id, types: String)
343 |
344 | /**
345 | A segment effort represents an attempt on a segment. This resource returns a subset of the activity streams that correspond to that effort.
346 |
347 | - parameter id: the effort id
348 | - parameter params: a [String: String] dictionary of acceptable parameters
349 | **/
350 | case effortStreams(id: Id, types: String)
351 |
352 | /**
353 | Retrieve segment streams
354 |
355 | Only distance, altitude and latlng stream types are available.
356 |
357 | - parameter id: the effort id
358 | - parameter params: a [String: String] dictionary of acceptable parameters
359 | **/
360 | case segmentStreams(id: Id, types: String)
361 |
362 | /**
363 | Retrieve route streams
364 |
365 | Distance, altitude and latlng stream types are always returned.
366 |
367 | - parameter id: the activity id
368 | **/
369 | case routeStreams(id: Id)
370 |
371 | /**
372 | Upload an activity
373 |
374 | Requires write permissions, as requested during the authorization process.
375 |
376 | Posting a file for upload will enqueue it for processing. Initial checks will be done for malformed data and duplicates.
377 |
378 | - parameter upload: an upload object
379 | **/
380 | case uploadFile(upload: StravaSwift.UploadData)
381 |
382 | /**
383 | Check upload status
384 |
385 | Upon upload, Strava will respond with an upload ID. You may use this ID to poll the status of your upload. Strava recommends polling no more than once a second. Polling more frequently is unnecessary. The mean processing time is around 8 seconds.
386 |
387 | - parameter id: the upload id
388 | **/
389 | case uploads(id: Id)
390 |
391 |
392 | }
393 |
394 | extension Router: URLRequestConvertible {
395 |
396 | /**
397 | The Strava app authorization deeplink url including the oauth query parameters
398 | **/
399 | static var appAuthorizationUrl: URL {
400 | let baseUrl = "strava://oauth/mobile/authorize"
401 | let authParams = StravaClient.sharedInstance.authParams
402 | .map { "\($0.key)=\($0.value)" }
403 | .joined(separator:"&")
404 | return URL(string: "\(baseUrl)?\(authParams)")!
405 | }
406 |
407 | /**
408 | The Strava web authorization url including the oauth query parameters
409 | **/
410 | static var webAuthorizationUrl: URL {
411 | let baseUrl = "https://www.strava.com/oauth/authorize"
412 | let authParams = StravaClient.sharedInstance.authParams
413 | .map { "\($0.key)=\($0.value)" }
414 | .joined(separator:"&")
415 | return URL(string: "\(baseUrl)?\(authParams)")!
416 | }
417 |
418 | /**
419 | The Url Request
420 | **/
421 | public func asURLRequest () throws -> URLRequest {
422 | let config = self.requestConfig
423 |
424 | var baseURL: URL {
425 | switch self {
426 | case .token, .deauthorize, .refresh:
427 | return URL(string: "https://www.strava.com/oauth")!
428 | default:
429 | return URL(string: "https://www.strava.com/api/v3")!
430 | }
431 | }
432 |
433 | var urlRequest = URLRequest(url: baseURL.appendingPathComponent(config.path))
434 | urlRequest.httpMethod = config.method.rawValue
435 |
436 | if let token = StravaClient.sharedInstance.token?.accessToken {
437 | urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
438 | }
439 |
440 | if let params = config.params, params.count > 0 {
441 | switch config.method {
442 | case .get:
443 | var urlComponents = URLComponents(url: urlRequest.url!, resolvingAgainstBaseURL: false)!
444 | urlComponents.queryItems = params.map { URLQueryItem(name: $0, value: "\($1)")}
445 | urlRequest.url = urlComponents.url!
446 | return urlRequest
447 | default:
448 | return try JSONEncoding.default.encode(urlRequest, with: params)
449 | }
450 | } else {
451 | return try JSONEncoding.default.encode(urlRequest)
452 | }
453 | }
454 | }
455 |
456 | extension Router {
457 |
458 | fileprivate var requestConfig: (path: String, params: Params, method: Alamofire.HTTPMethod) {
459 | switch self {
460 |
461 | case .token(let code):
462 | return ("/token", StravaClient.sharedInstance.tokenParams(code), .post)
463 | case .refresh(let refreshToken):
464 | return ("/token", StravaClient.sharedInstance.refreshParams(refreshToken), .post)
465 | case .deauthorize(let token):
466 | let params = ["access_token" : token]
467 | return ("/deauthorize", params, .post)
468 |
469 | case .athlete:
470 | return ("/athlete", nil, .get)
471 | case .updateAthlete:
472 | return ("/athlete", nil, .put)
473 | case .athleteFriends(let params):
474 | return ("/athlete/friends", params, .get)
475 | case .athleteFollowers(let params):
476 | return ("/athlete/followers", params, .get)
477 | case .athleteClubs(let params):
478 | return ("/athlete/clubs", params, .get)
479 | case .athleteActivities(let params):
480 | return ("/athlete/activities", params, .get)
481 |
482 | case .athletes(let id, let params):
483 | return ("/athletes/\(id)", params, .get)
484 | case .athletesFriends(let id, let params):
485 | return ("/athletes/\(id)/friends", params, .get)
486 | case .athletesFollowers(let id, let params):
487 | return ("/athletes/\(id)/friends", params, .get)
488 | case .athletesBothFollowing(let id, let params):
489 | return ("/athletes/\(id)/both-following", params, .get)
490 | case .athletesStats(let id, let params):
491 | return ("/athletes/\(id)/stats", params, .get)
492 | case .athletesKoms(let id, let params):
493 | return ("/athletes/\(id)/koms", params, .get)
494 |
495 | case .createActivity(let params):
496 | return ("/activities", params, .post)
497 | case .updateActivity(let activity):
498 | return ("/activities/\(activity.id!)", nil, .put)
499 | case .deleteActivity(let activity):
500 | return ("/activities/\(activity.id!)", nil, .delete)
501 |
502 | case .activities(let id, let params):
503 | return ("/activities/\(id)", params, .get)
504 | case .activitiesKudos(let id, let params):
505 | return ("/activities/\(id)/kudos", params, .get)
506 | case .activitiesComments(let id, let params):
507 | return ("/activities/\(id)/comments", params, .get)
508 | case .activitiesPhotos(let id, let params):
509 | return ("/activities/\(id)/photos/photo_sources=true", params, .get)
510 | case .activitiesRelated(let id, let params):
511 | return ("/activities/\(id)/related", params, .get)
512 | case .activitiesFriends(let id, let params):
513 | return ("/activities/\(id)/following", params, .get)
514 | case .activitiesZones(let id, let params):
515 | return ("/activities/\(id)/zones", params, .get)
516 | case .activitiesLaps(let id, let params):
517 | return ("/activities/\(id)/laps", params, .get)
518 |
519 | case .clubs(let id, let params):
520 | return ("/clubs/\(id)", params, .get)
521 | case .clubsAnnouncements(let id, let params):
522 | return ("/clubs/\(id)/announcements", params, .get)
523 | case .clubsEvents(let id, let params):
524 | return ("/clubs/\(id)/events", params, .get)
525 | case .clubsMembers(let id, let params):
526 | return ("/clubs/\(id)/members", params, .get)
527 | case .clubsActivities(let id, let params):
528 | return ("/clubs/\(id)/activities", params, .get)
529 | case .clubsJoin(let id):
530 | return ("/clubs/\(id)/join", nil, .post)
531 | case .clubsLeave(let id):
532 | return ("/clubs/\(id)/leave", nil, .post)
533 |
534 | case .segments(let id, let params):
535 | return ("/segments/\(id)", params, .get)
536 | case .segmentsEfforts(let id, let params):
537 | return ("/segments/\(id)/all_efforts", params, .get)
538 | case .segmentsLeaderboards(let id, let params):
539 | return ("/segments/\(id)/leaderboard", params, .get)
540 | case .segmentsExplore(let id, let params):
541 | return ("/segments/\(id)/explore", params, .get)
542 | case .segmentsStarred:
543 | return ("/segments/starred", nil, .get)
544 | case .segmentEfforts(let id, let params):
545 | return ("/segment_efforts/\(id)", params, .get)
546 |
547 | case .gear(let id, let params):
548 | return ("/gear/\(id)", params, .get)
549 |
550 | case .routes(let id):
551 | return ("/routes/\(id)", nil, .get)
552 | case .athleteRoutes(let id, let params):
553 | return ("/athletes/\(id)/routes", params, .get)
554 |
555 | case .activityStreams(let id, let type):
556 | return ("/activities/\(id)/streams/\(type)", nil, .get)
557 | case .effortStreams(let id, let type):
558 | return ("/segment_efforts/\(id)/streams/\(type)", nil, .get)
559 | case .segmentStreams(let id, let type):
560 | return ("/segments/\(id)/streams/\(type)", nil, .get)
561 | case .routeStreams(let id):
562 | return ("/routes/\(id)/streams", nil, .get)
563 |
564 | case .uploadFile(let upload):
565 | return ("/uploads", upload.params, .post)
566 | case .uploads(let id):
567 | return ("/uploads/\(id)", nil, .get)
568 | }
569 | }
570 | }
571 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Segment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Segment.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Segments are specific sections of road. Athletes’ times are compared on these segments and leaderboards are created.
14 | **/
15 | public final class Segment: Strava {
16 | public let id: Int?
17 | public let name: String?
18 | public let descr: String?
19 | public let resourceState: ResourceState?
20 | public let activityType: ActivityType?
21 | public let distance: Double?
22 | public let averageGrade: Double?
23 | public let maximumGrade: Double?
24 | public let elevationHigh: Double?
25 | public let elevationLow: Double?
26 | public let startLatLng: Location?
27 | public let endLatLng: Location?
28 | public let climbCategory: Int?
29 | public let city: String?
30 | public let state: String?
31 | public let country: String?
32 | public let `private`: Bool?
33 | public let starred: Bool?
34 | public let createdAt: Date?
35 | public let updateAt: Date?
36 | public let totalElevationGain: Double?
37 | public let map: Map?
38 | public let effortCount: Int?
39 | public let athleteCount: Int?
40 | public let hazardous: Bool?
41 | public let starCount: Int?
42 |
43 | /**
44 | Initializer
45 |
46 | - Parameter json: SwiftyJSON object
47 | - Internal
48 | **/
49 | required public init(_ json: JSON) {
50 | id = json["id"].int
51 | resourceState = json["resource_state"].strava(ResourceState.self)
52 | name = json["name"].string
53 | descr = json["description"].string
54 | activityType = json["activity_type"].strava(ActivityType.self)
55 | distance = json["distance"].double
56 | averageGrade = json["average_grade"].double
57 | maximumGrade = json["maximum_grade"].double
58 | elevationHigh = json["elevation_high"].double
59 | elevationLow = json["elevation_low"].double
60 | startLatLng = json["start_latlng"].strava(Location.self)
61 | endLatLng = json["end_latlng"].strava(Location.self)
62 | climbCategory = json["climb_category"].int
63 | city = json["city"].string
64 | state = json["state"].string
65 | country = json["country"].string
66 | `private` = json["private"].bool
67 | starred = json["starred"].bool
68 | createdAt = json["created_at"].string?.toDate()
69 | updateAt = json["updated_at"].string?.toDate()
70 | totalElevationGain = json["total_elevation_gained"].double
71 | map = json["map"].strava(Map.self)
72 | effortCount = json["effort_count"].int
73 | athleteCount = json["athlete_count"].int
74 | hazardous = json["hazardous"].bool
75 | starCount = json["star_count"].int
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Split.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Split.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 24/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Represents a summary of a split
14 | **/
15 | public struct Split: Strava {
16 | public let distance: Double?
17 | public let elapsedTime: Int?
18 | public let movingTime: Int?
19 | public let elevationDifference: Int?
20 | public let split: Int?
21 |
22 | /**
23 | Initializer
24 |
25 | - Parameter json: SwiftyJSON object
26 | - Internal
27 | **/
28 | public init(_ json: JSON) {
29 | distance = json["distance"].double
30 | elapsedTime = json["elapsed_time"].int
31 | movingTime = json["moving_time"].int
32 | elevationDifference = json["elevation_difference"].int
33 | split = json["split"].int
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/StravaClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StravaClient.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import AuthenticationServices
10 | import Foundation
11 | import Alamofire
12 | import SwiftyJSON
13 | import SafariServices
14 |
15 | /**
16 | StravaClient responsible for making all api requests
17 | */
18 | open class StravaClient: NSObject {
19 |
20 | /**
21 | Access the shared instance
22 | */
23 | public static let sharedInstance = StravaClient()
24 |
25 | fileprivate override init() {}
26 | fileprivate var config: StravaConfig?
27 |
28 | public typealias AuthorizationHandler = (Swift.Result) -> ()
29 | fileprivate var currentAuthorizationHandler: AuthorizationHandler?
30 | fileprivate var authSession: NSObject? // Holds a reference to ASWebAuthenticationSession / SFAuthenticationSession depending on iOS version
31 |
32 | /**
33 | The OAuthToken returned by the delegate
34 | **/
35 | open var token: OAuthToken? { return config?.delegate.get() }
36 |
37 | internal var authParams: [String: Any] {
38 | return [
39 | "client_id" : config?.clientId ?? 0,
40 | "redirect_uri" : config?.redirectUri ?? "",
41 | "scope" : (config?.scopes ?? []).map { $0.rawValue }.joined(separator: ","),
42 | "state" : "ios" as AnyObject,
43 | "approval_prompt" : config?.forcePrompt ?? true ? "force" : "auto",
44 | "response_type" : "code"
45 | ]
46 | }
47 |
48 | internal func tokenParams(_ code: String) -> [String: Any] {
49 | return [
50 | "client_id" : config?.clientId ?? 0,
51 | "client_secret" : config?.clientSecret ?? "",
52 | "code" : code
53 | ]
54 | }
55 |
56 | internal func refreshParams(_ refreshToken: String) -> [String: Any] {
57 | return [
58 | "client_id" : config?.clientId ?? 0,
59 | "client_secret" : config?.clientSecret ?? "",
60 | "grant_type" : "refresh_token",
61 | "refresh_token" : refreshToken
62 | ]
63 | }
64 | }
65 |
66 | //MARK:varConfig
67 |
68 | extension StravaClient {
69 |
70 | /**
71 | Initialize the shared instance with your credentials. You must use this otherwise fatal errors will be
72 | returned when making api requests.
73 |
74 | - Parameter config: a StravaConfig struct
75 | - Returns: An instance of self (i.e. StravaClient)
76 | */
77 | public func initWithConfig(_ config: StravaConfig) -> StravaClient {
78 | self.config = config
79 |
80 | return self
81 | }
82 | }
83 |
84 | //MARK : - Auth
85 |
86 | extension StravaClient: ASWebAuthenticationPresentationContextProviding {
87 |
88 | var currentWindow: UIWindow? { return UIApplication.shared.keyWindow }
89 | var currentViewController: UIViewController? { return currentWindow?.rootViewController }
90 |
91 | /**
92 | Starts the Strava OAuth authorization. The authentication will use the Strava app be default if it is installed on the device. If the user does not have Strava installed, it will fallback on `SFAuthenticationSession` or `ASWebAuthenticationSession` depending on the iOS version used at runtime.
93 | */
94 | public func authorize(result: @escaping AuthorizationHandler) {
95 | let appAuthorizationUrl = Router.appAuthorizationUrl
96 | if UIApplication.shared.canOpenURL(appAuthorizationUrl) {
97 | currentAuthorizationHandler = result // Stores the handler to be executed once `handleAuthorizationRedirect(url:)` is called
98 | if #available(iOS 10.0, *) {
99 | UIApplication.shared.open(appAuthorizationUrl, options: [:])
100 | } else {
101 | UIApplication.shared.openURL(appAuthorizationUrl)
102 | }
103 | } else {
104 | if #available(iOS 12.0, *) {
105 | let webAuthenticationSession = ASWebAuthenticationSession(url: Router.webAuthorizationUrl,
106 | callbackURLScheme: config?.redirectUri.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed),
107 | completionHandler: { (url, error) in
108 | if let url = url, error == nil {
109 | self.handleAuthorizationRedirect(url, result: result)
110 | } else {
111 | result(.failure(error!))
112 | }
113 | })
114 | authSession = webAuthenticationSession
115 | if #available(iOS 13.0, *) {
116 | webAuthenticationSession.presentationContextProvider = self
117 | }
118 | webAuthenticationSession.start()
119 | } else {
120 | currentAuthorizationHandler = result // Stores the handler to be executed once `handleAuthorizationRedirect(url:)` is called
121 | UIApplication.shared.open(Router.webAuthorizationUrl, options: [:])
122 | }
123 | }
124 | }
125 |
126 | /**
127 | Helper method to get the code from the redirection from Strava after the user has authorized the application (useful in AppDelegate)
128 |
129 | - Parameter url the url returned by Strava through the (ASWeb/SF)AuthenricationSession or application open options.
130 | - Returns: a boolean that indicates if this url is for Strava, has a code and can be handled properly
131 | **/
132 | public func handleAuthorizationRedirect(_ url: URL) -> Bool {
133 | if let redirectUri = config?.redirectUri, url.absoluteString.starts(with: redirectUri),
134 | let params = url.getQueryParameters(), params["code"] != nil, params["scope"] != nil, params["state"] == "ios" {
135 |
136 | self.handleAuthorizationRedirect(url) { result in
137 | if let currentAuthorizationHandler = self.currentAuthorizationHandler {
138 | currentAuthorizationHandler(result)
139 | self.currentAuthorizationHandler = nil
140 | }
141 | }
142 | return true
143 | } else {
144 | return false
145 | }
146 | }
147 |
148 | /**
149 | Helper method to get the code from the redirection from Strava after the user has authorized the application (useful in AppDelegate)
150 |
151 | - Parameter url the url returned by Strava through the (ASWeb/SF)AuthenricationSession or application open options.
152 | - Parameter result a closure to handle the OAuthToken
153 | **/
154 | private func handleAuthorizationRedirect(_ url: URL, result: @escaping AuthorizationHandler) {
155 | if let code = url.getQueryParameters()?["code"] {
156 | self.getAccessToken(code, result: result)
157 | } else {
158 | result(.failure(generateError(failureReason: "Invalid authorization code", response: nil)))
159 | }
160 | }
161 |
162 | /**
163 | Get an OAuth token from Strava
164 |
165 | - Parameter code: the code (string) returned from strava
166 | - Parameter result: a closure to handle the OAuthToken
167 | **/
168 | private func getAccessToken(_ code: String, result: @escaping AuthorizationHandler) {
169 | do {
170 | try oauthRequest(Router.token(code: code))?.responseStrava { [weak self] (response: DataResponse) in
171 | guard let self = self else { return }
172 | if let token = response.result.value {
173 | self.config?.delegate.set(token)
174 | result(.success(token))
175 | } else if let error = response.error {
176 | result(.failure(error))
177 | } else {
178 | result(.failure(self.generateError(failureReason: "No valid token", response: nil)))
179 | }
180 | }
181 | } catch let error as NSError {
182 | result(.failure(error))
183 | }
184 | }
185 |
186 | /**
187 | Refresh an OAuth token from Strava
188 |
189 | - Parameter refresh: the refresh token from Strava
190 | - Parameter result: a closure to handle the OAuthToken
191 | **/
192 | public func refreshAccessToken(_ refreshToken: String, result: @escaping AuthorizationHandler) {
193 | do {
194 | try oauthRequest(Router.refresh(refreshToken: refreshToken))?.responseStrava { [weak self] (response: DataResponse) in
195 | guard let self = self else { return }
196 | if let token = response.result.value {
197 | self.config?.delegate.set(token)
198 | result(.success(token))
199 | } else {
200 | result(.failure(self.generateError(failureReason: "No valid token", response: nil)))
201 | }
202 | }
203 | } catch let error as NSError {
204 | result(.failure(error))
205 | }
206 | }
207 |
208 | // ASWebAuthenticationPresentationContextProviding
209 |
210 | @available(iOS 12.0, *)
211 | public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
212 | return currentWindow ?? ASPresentationAnchor()
213 | }
214 | }
215 |
216 |
217 | //MARK: - Athlete
218 |
219 | extension StravaClient {
220 |
221 | public func upload(_ route: Router, upload: UploadData, result: @escaping (((T)?) -> Void), failure: @escaping (NSError) -> Void) {
222 | do {
223 | try oauthUpload(URLRequest: route.asURLRequest(), upload: upload) { (response: DataResponse) in
224 | if let statusCode = response.response?.statusCode, (400..<500).contains(statusCode) {
225 | failure(self.generateError(failureReason: "Strava API Error", response: response.response))
226 | } else {
227 | result(response.result.value)
228 | }
229 | }
230 | } catch let error as NSError {
231 | failure(error)
232 | }
233 | }
234 |
235 | /**
236 | Request a single object from the Strava Api
237 |
238 | - Parameter route: a Router enum case which may require parameters
239 | - Parameter result: a closure to handle the returned object
240 | **/
241 | public func request(_ route: Router, result: @escaping (((T)?) -> Void), failure: @escaping (NSError) -> Void) {
242 | do {
243 | try oauthRequest(route)?.responseStrava { (response: DataResponse) in
244 | // HTTP Status codes above 400 are errors
245 | if let statusCode = response.response?.statusCode, (400..<500).contains(statusCode) {
246 | failure(self.generateError(failureReason: "Strava API Error", response: response.response))
247 | } else {
248 | result(response.result.value)
249 | }
250 | }
251 | } catch let error as NSError {
252 | failure(error)
253 | }
254 | }
255 |
256 | /**
257 | Request an array of objects from the Strava Api
258 |
259 | - Parameter route: a Router enum case which may require parameters
260 | - Parameter result: a closure to handle the returned objects
261 | **/
262 | public func request(_ route: Router, result: @escaping ((([T])?) -> Void), failure: @escaping (NSError) -> Void) {
263 | do {
264 | try oauthRequest(route)?.responseStravaArray { (response: DataResponse<[T]>) in
265 | // HTTP Status codes above 400 are errors
266 | if let statusCode = response.response?.statusCode, (400..<500).contains(statusCode) {
267 | failure(self.generateError(failureReason: "Strava API Error", response: response.response))
268 | } else {
269 | result(response.result.value)
270 | }
271 | }
272 | } catch let error as NSError {
273 | failure(error)
274 | }
275 | }
276 |
277 | fileprivate func generateError(failureReason: String, response: HTTPURLResponse?) -> NSError {
278 | let errorDomain = "com.stravaswift.error"
279 | let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
280 | let code = response?.statusCode ?? 0
281 | let returnError = NSError(domain: errorDomain, code: code, userInfo: userInfo)
282 |
283 | return returnError
284 | }
285 |
286 | }
287 |
288 | extension StravaClient {
289 |
290 | fileprivate func isConfigured() -> (Bool) {
291 | return config != nil
292 | }
293 |
294 | fileprivate func checkConfiguration() {
295 | if !isConfigured() {
296 | fatalError("Strava client is not configured")
297 | }
298 | }
299 |
300 | fileprivate func oauthRequest(_ urlRequest: URLRequestConvertible) throws -> DataRequest? {
301 | checkConfiguration()
302 |
303 | return Alamofire.request(urlRequest)
304 | }
305 |
306 | fileprivate func oauthUpload(URLRequest: URLRequestConvertible, upload: UploadData, completion: @escaping (DataResponse) -> ()) {
307 | checkConfiguration()
308 |
309 | guard let url = try? URLRequest.asURLRequest() else { return }
310 |
311 | Alamofire.upload(multipartFormData: { multipartFormData in
312 | multipartFormData.append(upload.file, withName: "file", fileName: "\(upload.name ?? "default").\(upload.dataType)", mimeType: "octet/stream")
313 | for (key, value) in upload.params {
314 | if let value = value as? String {
315 | multipartFormData.append(value.data(using: .utf8)!, withName: key)
316 | }
317 | }
318 | }, usingThreshold: SessionManager.multipartFormDataEncodingMemoryThreshold, with: url) { encodingResult in
319 | switch encodingResult {
320 | case .success(let upload, _, _):
321 | upload.responseStrava { (response: DataResponse) in
322 | completion(response)
323 | }
324 | case .failure(let encodingError):
325 | print(encodingError)
326 | }
327 | }
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/StravaClientError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StravaClientError.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | StravaClientError Enum
13 | */
14 | public enum StravaClientError: Error {
15 |
16 | /**
17 | The OAuthCredentials are invalid
18 | **/
19 | case invalidCredentials
20 |
21 | /**
22 | Uknown error
23 | **/
24 | case unknown
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/StravaConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Client.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | /**
10 | OAuth scope
11 | */
12 | public enum Scope: String {
13 | /** Default: Read public segments, public routes, public profile data, public posts, public events, club feeds, and leaderboards **/
14 | case read = "read"
15 | /** Read private routes, private segments, and private events for the user **/
16 | case readAll = "read_all"
17 | /** Read all profile information even if the user has set their profile visibility to Followers or Only You **/
18 | case profileReadAll = "profile:read_all"
19 | /** Update the user's weight and Functional Threshold Power (FTP), and access to star or unstar segments on their behalf **/
20 | case profileWrite = "profile:write"
21 | /** Read the user's activity data for activities that are visible to Everyone and Followers, excluding privacy zone data **/
22 | case activityRead = "activity:read"
23 | /** The same access as activity:read, plus privacy zone data and access to read the user's activities with visibility set to Only You **/
24 | case activityReadAll = "activity:read_all"
25 | /** Access to create manual activities and uploads, and access to edit any activities that are visible to the app, based on activity read access level **/
26 | case activityWrite = "activity:write"
27 | }
28 |
29 | /**
30 | Strava configuration struct which should be passed to the StravaClient.sharedInstance.initWithConfig(_:) method
31 | **/
32 | public struct StravaConfig {
33 |
34 | /** The application's Id **/
35 | public let clientId: Int
36 | /** The application's Secrent **/
37 | public let clientSecret: String
38 | /** The application's RedirectURL - this should be registered in the info.plist **/
39 | public let redirectUri: String
40 | /** The requested permission scope **/
41 | public let scopes: [Scope]
42 | /** The delegate responsible for storing and retrieving the OAuth token in your app **/
43 | public let delegate: TokenDelegate
44 |
45 | public let forcePrompt: Bool
46 |
47 | /**
48 | Initializer
49 |
50 | - Parameters:
51 | - clientId: Int
52 | - clientSecret: Int
53 | - redirectUri: String
54 | - scope: Scope enum - default is .read)
55 | - delegate: TokenDelegateProtocol - default is the DefaultTokenDelegate
56 | **/
57 | public init(clientId: Int,
58 | clientSecret: String,
59 | redirectUri: String,
60 | scopes: [Scope] = [.read],
61 | delegate: TokenDelegate? = nil,
62 | forcePrompt: Bool = true) {
63 | self.clientId = clientId
64 | self.clientSecret = clientSecret
65 | self.redirectUri = redirectUri
66 | self.scopes = scopes
67 | self.delegate = delegate ?? DefaultTokenDelegate()
68 | self.forcePrompt = forcePrompt
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/StravaProtocols.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StravaProtocols.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Base protocol for Strava resources
14 |
15 | - Internal
16 | **/
17 | public protocol Strava: CustomStringConvertible {
18 | init(_ json: JSON)
19 | }
20 |
21 | extension Strava {
22 | public var description: String {
23 | let mirror = Mirror(reflecting: self)
24 | var desc = ""
25 | for child in mirror.children {
26 | desc += "\(child.label!): \(child.value) \n"
27 | }
28 | return desc
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Stream.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stream.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Streams is the Strava term for the raw data associated with an activity. All streams for a given activity or segment effort will be the same length and the values at a given index correspond to the same time.
14 | **/
15 | public struct Stream: Strava {
16 | public let type: StreamType?
17 | public let data: [Any]?
18 | public let seriesType: String?
19 | public let originalSize: Int?
20 | public let resolution: ResolutionType?
21 |
22 | /**
23 | Initializer
24 |
25 | - Parameter json: SwiftyJSON object
26 | - Internal
27 | **/
28 | public init(_ json: JSON) {
29 | type = json["type"].strava(StreamType.self)
30 | data = json["data"].arrayObject
31 | seriesType = json["series_type"].string
32 | originalSize = json["original_size"].int
33 | resolution = json["resolution"].strava(ResolutionType.self)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/StringExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringExtensions.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 23/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | func toDate(_ format: String = "yyyy-MM-dd'T'HH:mm:ssZZZZZ") -> Date? {
13 | let formatter = DateFormatter()
14 | formatter.dateFormat = format
15 | return formatter.date(from: self)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/SwiftyJSON.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftJSON.swift
3 | // Pods
4 | //
5 | // Created by MATTHEW CLARKSON on 28/05/2016.
6 | //
7 | //
8 |
9 | import SwiftyJSON
10 |
11 | extension RawRepresentable {
12 | init?(o rawValue: RawValue?) {
13 | guard let rawValue = rawValue, let value = Self(rawValue: rawValue) else { return nil }
14 | self = value
15 | }
16 | }
17 |
18 | extension JSON {
19 | public func strava(_ type: T.Type?) -> T? {
20 | return type?.init(self)
21 | }
22 |
23 | public func strava(_ type: T.Type?) -> T? where T.RawValue == Int {
24 | return type?.init(optionalRawValue: self.int)
25 | }
26 |
27 | public func strava(_ type: T.Type?) -> T? where T.RawValue == String {
28 | return type?.init(optionalRawValue: self.string)
29 | }
30 |
31 | public func strava(_ type: T.Type?) -> [T]? {
32 | return self.arrayValue.compactMap { T($0) }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/TokenDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenDelegate.swift
3 | // StravaSwift
4 | //
5 | // Created by MATTHEW CLARKSON on 23/05/2016.
6 | // Copyright © 2016 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | /**
10 | Token Delegate protocol - responsible for storing and retrieving the OAuth token
11 | **/
12 | public protocol TokenDelegate {
13 |
14 | /**
15 | Retrieves the token
16 |
17 | - Returns: an optional OAuthToken
18 | **/
19 | func get() -> OAuthToken?
20 |
21 | /**
22 | Store the token
23 |
24 | - Parameter token: an optional OAuthToken
25 | **/
26 | func set(_ token: OAuthToken?)
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/StravaSwift/Upload.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Upload.swift
3 | // StravaSwift
4 | //
5 | // Created by Matthew on 11/11/2015.
6 | // Copyright © 2015 Matthew Clarkson. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftyJSON
11 |
12 | /**
13 | Upload struct
14 |
15 | Uploading to Strava is an asynchronous process. A file is uploaded using a multipart/form-data POST request which performs initial checks on the data and enqueues the file for processing. The activity will not appear in other API requests until it has finished processing successfully.
16 |
17 | Processing status may be checked by polling Strava. A one-second or longer polling interval is recommended. The mean processing time is currently around 8 seconds. Once processing is complete, Strava will respond to polling requests with the activity’s ID.
18 |
19 | Errors can occur during the submission or processing steps and may be due to malformed activity data or duplicate data submission.
20 |
21 | - warning: Not yet tested
22 |
23 | **/
24 | public struct UploadData {
25 | public var activityType: ActivityType?
26 | public var name: String?
27 | public var description: String?
28 | public var `private`: Bool?
29 | public var trainer: Bool?
30 | public var externalId: String?
31 |
32 | public var dataType: DataType
33 | public var file: Data
34 |
35 | public init(name: String, dataType: DataType, file: Data) {
36 | self.name = name
37 | self.dataType = dataType
38 | self.file = file
39 | }
40 |
41 | public init(activityType: ActivityType?, name: String?, description: String?,
42 | `private`: Bool?, trainer: Bool?, externalId: String?, dataType: DataType, file: Data) {
43 | self.activityType = activityType
44 | self.description = description
45 | self.`private` = `private`
46 | self.trainer = trainer
47 | self.externalId = externalId
48 | self.name = name
49 | self.dataType = dataType
50 | self.file = file
51 | }
52 |
53 | internal var params: [String: Any] {
54 |
55 | var params: [String: Any] = [:]
56 | params["data_type"] = dataType.rawValue
57 | params["name"] = name
58 | params["description"] = description
59 | if let `private` = `private` {
60 | params["private"] = (`private` as NSNumber).stringValue
61 | }
62 | if let trainer = trainer {
63 | params["trainer"] = (trainer as NSNumber).stringValue
64 | }
65 | params["external_id"] = externalId
66 | return params
67 | }
68 | }
69 |
70 | /**
71 | Upload status
72 | **/
73 | public final class UploadStatus: Strava {
74 | public let id: Int?
75 | public let externalId: String?
76 | public let error: String?
77 | public let status: String?
78 | public let activityId: Int?
79 |
80 | public required init(_ json: JSON) {
81 | id = json["id"].int
82 | externalId = json["external_id"].string
83 | error = json["error"].string
84 | status = json["status"].string
85 | activityId = json["activity_id"].int
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/StravaSwift.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'StravaSwift'
3 | s.version = '1.0.2'
4 | s.summary = 'A Swift library for the Strava API v3'
5 | s.description = <<-DESC
6 | A Swift library for the Strava API v3. For complete details visit the Strava developer site.
7 | DESC
8 | s.homepage = 'https://github.com/mpclarkson/StravaSwift'
9 | s.license = { :type => 'MIT', :file => 'LICENSE' }
10 | s.author = { 'Matthew Clarkson' => 'mpclarkson@gmail.com' }
11 | s.source = { :git => 'https://github.com/mpclarkson/StravaSwift.git', :tag => s.version.to_s }
12 | s.social_media_url = 'https://twitter.com/matt_pc'
13 | s.swift_version = '5.0'
14 | s.ios.deployment_target = '12.0'
15 | s.source_files = 'Sources/StravaSwift/**/*'
16 | s.dependency 'Alamofire', '~> 4'
17 | s.dependency 'SwiftyJSON', '~> 5'
18 | end
19 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import RouterTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += RouterTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/Tests/StravaSwiftTests/RouterTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import StravaSwift
3 |
4 | final class RouterTests: XCTestCase {
5 |
6 | let baseApiUrl = "https://www.strava.com/api/v3"
7 |
8 | func testAthleteRequest() {
9 | let urlRequest = try! Router.athlete.asURLRequest()
10 | XCTAssertEqual(urlRequest.url?.absoluteString, "\(baseApiUrl)/athlete")
11 | XCTAssertEqual(urlRequest.httpMethod, "GET")
12 | XCTAssertNil(urlRequest.httpBody)
13 | }
14 |
15 | static var allTests = [
16 | ("testAthleteRequest", testAthleteRequest),
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/StravaSwiftTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(RouterTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------