Andres Ramos 3 years ago
parent
commit
ec53e6fd83
43 changed files with 4838 additions and 0 deletions
  1. 477
    0
      Flowerdex/Flowerdex.xcodeproj/project.pbxproj
  2. 7
    0
      Flowerdex/Flowerdex.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  3. 8
    0
      Flowerdex/Flowerdex.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  4. BIN
      Flowerdex/Flowerdex.xcodeproj/project.xcworkspace/xcuserdata/victor.hernandez.xcuserdatad/UserInterfaceState.xcuserstate
  5. 24
    0
      Flowerdex/Flowerdex.xcodeproj/xcuserdata/victor.hernandez.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  6. 14
    0
      Flowerdex/Flowerdex.xcodeproj/xcuserdata/victor.hernandez.xcuserdatad/xcschemes/xcschememanagement.plist
  7. 11
    0
      Flowerdex/Flowerdex/Assets.xcassets/AccentColor.colorset/Contents.json
  8. 98
    0
      Flowerdex/Flowerdex/Assets.xcassets/AppIcon.appiconset/Contents.json
  9. 56
    0
      Flowerdex/Flowerdex/Assets.xcassets/Blue Gray.colorset/Contents.json
  10. 6
    0
      Flowerdex/Flowerdex/Assets.xcassets/Contents.json
  11. 56
    0
      Flowerdex/Flowerdex/Assets.xcassets/Rausch.colorset/Contents.json
  12. 21
    0
      Flowerdex/Flowerdex/Assets.xcassets/amapola.imageset/Contents.json
  13. BIN
      Flowerdex/Flowerdex/Assets.xcassets/amapola.imageset/amapola.jpg
  14. 21
    0
      Flowerdex/Flowerdex/Assets.xcassets/orquidea.imageset/Contents.json
  15. BIN
      Flowerdex/Flowerdex/Assets.xcassets/orquidea.imageset/orquidea.jpg
  16. 28
    0
      Flowerdex/Flowerdex/FlowerdexApp.swift
  17. 61
    0
      Flowerdex/Flowerdex/Info.plist
  18. 46
    0
      Flowerdex/Flowerdex/Model/AuthenticationModel.swift
  19. 30
    0
      Flowerdex/Flowerdex/Model/Filters.swift
  20. 62
    0
      Flowerdex/Flowerdex/Model/FlowerItem.swift
  21. 24
    0
      Flowerdex/Flowerdex/Model/FlowersResponseModel.swift
  22. 60
    0
      Flowerdex/Flowerdex/Model/UserModel.swift
  23. 6
    0
      Flowerdex/Flowerdex/Preview Content/Preview Assets.xcassets/Contents.json
  24. 32
    0
      Flowerdex/Flowerdex/Resources/DummyData.swift
  25. 1055
    0
      Flowerdex/Flowerdex/Resources/flowers.json
  26. 1066
    0
      Flowerdex/Flowerdex/Resources/rawResponse.json
  27. 67
    0
      Flowerdex/Flowerdex/Services/ContentDataSource.swift
  28. 114
    0
      Flowerdex/Flowerdex/Services/FlowerService.swift
  29. 92
    0
      Flowerdex/Flowerdex/Services/ImageManager.swift
  30. 165
    0
      Flowerdex/Flowerdex/Services/UserService.swift
  31. 31
    0
      Flowerdex/Flowerdex/Supporting/Constants.swift
  32. 15
    0
      Flowerdex/Flowerdex/Supporting/Responses.swift
  33. 150
    0
      Flowerdex/Flowerdex/View/Authentication/LoginView.swift
  34. 157
    0
      Flowerdex/Flowerdex/View/Authentication/RegisterView.swift
  35. 77
    0
      Flowerdex/Flowerdex/View/Discover Page/DiscoverView.swift
  36. 139
    0
      Flowerdex/Flowerdex/View/Discover Page/FilterFields.swift
  37. 68
    0
      Flowerdex/Flowerdex/View/Discover Page/FiltersSheet.swift
  38. 86
    0
      Flowerdex/Flowerdex/View/Discover Page/FlowerCard.swift
  39. 100
    0
      Flowerdex/Flowerdex/View/Discover Page/FlowerDetailView.swift
  40. 33
    0
      Flowerdex/Flowerdex/View/Discover Page/FlowersList.swift
  41. 147
    0
      Flowerdex/Flowerdex/View/FilterPane.swift
  42. 84
    0
      Flowerdex/Flowerdex/View/FlowerCardView.swift
  43. 44
    0
      Flowerdex/Flowerdex/View/StatusIcons.swift

+ 477
- 0
Flowerdex/Flowerdex.xcodeproj/project.pbxproj View File

@@ -0,0 +1,477 @@
1
+// !$*UTF8*$!
2
+{
3
+	archiveVersion = 1;
4
+	classes = {
5
+	};
6
+	objectVersion = 50;
7
+	objects = {
8
+
9
+/* Begin PBXBuildFile section */
10
+		A50EEFCD255D175400545CFE /* ContentDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50EEFCC255D175400545CFE /* ContentDataSource.swift */; };
11
+		A51B4F772546674C002AFF05 /* FlowersResponseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A51B4F762546674C002AFF05 /* FlowersResponseModel.swift */; };
12
+		A51B4F7B25466A75002AFF05 /* flowers.json in Resources */ = {isa = PBXBuildFile; fileRef = A51B4F7A25466A75002AFF05 /* flowers.json */; };
13
+		A52FB5C42546C2EA000AD235 /* rawResponse.json in Resources */ = {isa = PBXBuildFile; fileRef = A52FB5C32546C2EA000AD235 /* rawResponse.json */; };
14
+		A5711B8B255CE03000E727BB /* FlowerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5711B8A255CE03000E727BB /* FlowerItem.swift */; };
15
+		A5711B8E255CE53900E727BB /* DummyData.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5711B8D255CE53900E727BB /* DummyData.swift */; };
16
+		A58640BA258F30AD00F626AB /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58640B9258F30AD00F626AB /* LoginView.swift */; };
17
+		A58640BE258F30F200F626AB /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58640BD258F30F200F626AB /* RegisterView.swift */; };
18
+		A59A2741254556880052319F /* FlowerdexApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59A2740254556880052319F /* FlowerdexApp.swift */; };
19
+		A59A2743254556880052319F /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59A2742254556880052319F /* DiscoverView.swift */; };
20
+		A59A27452545568A0052319F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A59A27442545568A0052319F /* Assets.xcassets */; };
21
+		A59A27482545568A0052319F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A59A27472545568A0052319F /* Preview Assets.xcassets */; };
22
+		A5AF3F5C2545FDEB0035AC9A /* FlowerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5AF3F5B2545FDEB0035AC9A /* FlowerCard.swift */; };
23
+		A5EA2F24258FEC2300699182 /* UserService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA2F23258FEC2300699182 /* UserService.swift */; };
24
+		A5EA2F28258FEC3D00699182 /* FlowerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA2F27258FEC3D00699182 /* FlowerService.swift */; };
25
+		A5EA2F2C258FF0BD00699182 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA2F2B258FF0BD00699182 /* Constants.swift */; };
26
+		A5EA2F32258FF58200699182 /* Responses.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EA2F31258FF58200699182 /* Responses.swift */; };
27
+		A5EE388D2576362B0014D0BE /* FilterFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE388C2576362B0014D0BE /* FilterFields.swift */; };
28
+		A5EE389125763D3A0014D0BE /* AuthenticationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE389025763D3A0014D0BE /* AuthenticationModel.swift */; };
29
+		A5EE3897257658400014D0BE /* Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE3896257658400014D0BE /* Filters.swift */; };
30
+		A5EE38A4257700230014D0BE /* ImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE38A3257700230014D0BE /* ImageManager.swift */; };
31
+		A5EE38AC257772200014D0BE /* FiltersSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE38AB257772200014D0BE /* FiltersSheet.swift */; };
32
+		A5EE38B0257818520014D0BE /* FlowersList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE38AF257818520014D0BE /* FlowersList.swift */; };
33
+		A5EE38B3257818BE0014D0BE /* StatusIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5EE38B2257818BE0014D0BE /* StatusIcons.swift */; };
34
+		A5F0D2FC258F3D2200AF735C /* UserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F0D2FB258F3D2200AF735C /* UserModel.swift */; };
35
+		A5F3271A25460C0B003BCE0F /* FlowerDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5F3271925460C0B003BCE0F /* FlowerDetailView.swift */; };
36
+/* End PBXBuildFile section */
37
+
38
+/* Begin PBXFileReference section */
39
+		A50EEFCC255D175400545CFE /* ContentDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentDataSource.swift; sourceTree = "<group>"; };
40
+		A51B4F762546674C002AFF05 /* FlowersResponseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowersResponseModel.swift; sourceTree = "<group>"; };
41
+		A51B4F7A25466A75002AFF05 /* flowers.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = flowers.json; sourceTree = "<group>"; };
42
+		A52FB5C32546C2EA000AD235 /* rawResponse.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = rawResponse.json; sourceTree = "<group>"; };
43
+		A5711B8A255CE03000E727BB /* FlowerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowerItem.swift; sourceTree = "<group>"; };
44
+		A5711B8D255CE53900E727BB /* DummyData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyData.swift; sourceTree = "<group>"; };
45
+		A58640B9258F30AD00F626AB /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
46
+		A58640BD258F30F200F626AB /* RegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterView.swift; sourceTree = "<group>"; };
47
+		A59A273D254556880052319F /* Flowerdex.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Flowerdex.app; sourceTree = BUILT_PRODUCTS_DIR; };
48
+		A59A2740254556880052319F /* FlowerdexApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowerdexApp.swift; sourceTree = "<group>"; };
49
+		A59A2742254556880052319F /* DiscoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscoverView.swift; sourceTree = "<group>"; };
50
+		A59A27442545568A0052319F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
51
+		A59A27472545568A0052319F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
52
+		A59A27492545568A0052319F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
53
+		A5AF3F5B2545FDEB0035AC9A /* FlowerCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowerCard.swift; sourceTree = "<group>"; };
54
+		A5EA2F23258FEC2300699182 /* UserService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserService.swift; sourceTree = "<group>"; };
55
+		A5EA2F27258FEC3D00699182 /* FlowerService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowerService.swift; sourceTree = "<group>"; };
56
+		A5EA2F2B258FF0BD00699182 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
57
+		A5EA2F31258FF58200699182 /* Responses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Responses.swift; sourceTree = "<group>"; };
58
+		A5EE388C2576362B0014D0BE /* FilterFields.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterFields.swift; sourceTree = "<group>"; };
59
+		A5EE389025763D3A0014D0BE /* AuthenticationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationModel.swift; sourceTree = "<group>"; };
60
+		A5EE3896257658400014D0BE /* Filters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filters.swift; sourceTree = "<group>"; };
61
+		A5EE38A3257700230014D0BE /* ImageManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageManager.swift; sourceTree = "<group>"; };
62
+		A5EE38AB257772200014D0BE /* FiltersSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersSheet.swift; sourceTree = "<group>"; };
63
+		A5EE38AF257818520014D0BE /* FlowersList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowersList.swift; sourceTree = "<group>"; };
64
+		A5EE38B2257818BE0014D0BE /* StatusIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusIcons.swift; sourceTree = "<group>"; };
65
+		A5F0D2FB258F3D2200AF735C /* UserModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModel.swift; sourceTree = "<group>"; };
66
+		A5F3271925460C0B003BCE0F /* FlowerDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowerDetailView.swift; sourceTree = "<group>"; };
67
+/* End PBXFileReference section */
68
+
69
+/* Begin PBXFrameworksBuildPhase section */
70
+		A59A273A254556880052319F /* Frameworks */ = {
71
+			isa = PBXFrameworksBuildPhase;
72
+			buildActionMask = 2147483647;
73
+			files = (
74
+			);
75
+			runOnlyForDeploymentPostprocessing = 0;
76
+		};
77
+/* End PBXFrameworksBuildPhase section */
78
+
79
+/* Begin PBXGroup section */
80
+		A51B4F7125466562002AFF05 /* Model */ = {
81
+			isa = PBXGroup;
82
+			children = (
83
+				A51B4F762546674C002AFF05 /* FlowersResponseModel.swift */,
84
+				A5711B8A255CE03000E727BB /* FlowerItem.swift */,
85
+				A5EE3896257658400014D0BE /* Filters.swift */,
86
+				A5EE389025763D3A0014D0BE /* AuthenticationModel.swift */,
87
+				A5F0D2FB258F3D2200AF735C /* UserModel.swift */,
88
+			);
89
+			path = Model;
90
+			sourceTree = "<group>";
91
+		};
92
+		A51B4F79254668C2002AFF05 /* Resources */ = {
93
+			isa = PBXGroup;
94
+			children = (
95
+				A5711B8D255CE53900E727BB /* DummyData.swift */,
96
+				A51B4F7A25466A75002AFF05 /* flowers.json */,
97
+				A52FB5C32546C2EA000AD235 /* rawResponse.json */,
98
+			);
99
+			path = Resources;
100
+			sourceTree = "<group>";
101
+		};
102
+		A5711B85255CCC1700E727BB /* View */ = {
103
+			isa = PBXGroup;
104
+			children = (
105
+				A58640AE258F213600F626AB /* Authentication */,
106
+				A5EE38B6257819AA0014D0BE /* Discover Page */,
107
+				A5EE38B2257818BE0014D0BE /* StatusIcons.swift */,
108
+			);
109
+			path = View;
110
+			sourceTree = "<group>";
111
+		};
112
+		A58640AE258F213600F626AB /* Authentication */ = {
113
+			isa = PBXGroup;
114
+			children = (
115
+				A58640B9258F30AD00F626AB /* LoginView.swift */,
116
+				A58640BD258F30F200F626AB /* RegisterView.swift */,
117
+			);
118
+			path = Authentication;
119
+			sourceTree = "<group>";
120
+		};
121
+		A59A2734254556870052319F = {
122
+			isa = PBXGroup;
123
+			children = (
124
+				A59A273F254556880052319F /* Flowerdex */,
125
+				A59A273E254556880052319F /* Products */,
126
+			);
127
+			sourceTree = "<group>";
128
+		};
129
+		A59A273E254556880052319F /* Products */ = {
130
+			isa = PBXGroup;
131
+			children = (
132
+				A59A273D254556880052319F /* Flowerdex.app */,
133
+			);
134
+			name = Products;
135
+			sourceTree = "<group>";
136
+		};
137
+		A59A273F254556880052319F /* Flowerdex */ = {
138
+			isa = PBXGroup;
139
+			children = (
140
+				A59A2740254556880052319F /* FlowerdexApp.swift */,
141
+				A5EA2F22258FEBE700699182 /* Services */,
142
+				A51B4F7125466562002AFF05 /* Model */,
143
+				A5711B85255CCC1700E727BB /* View */,
144
+				A5EA2F2A258FF07800699182 /* Supporting */,
145
+				A51B4F79254668C2002AFF05 /* Resources */,
146
+				A59A27442545568A0052319F /* Assets.xcassets */,
147
+				A59A27492545568A0052319F /* Info.plist */,
148
+				A59A27462545568A0052319F /* Preview Content */,
149
+			);
150
+			path = Flowerdex;
151
+			sourceTree = "<group>";
152
+		};
153
+		A59A27462545568A0052319F /* Preview Content */ = {
154
+			isa = PBXGroup;
155
+			children = (
156
+				A59A27472545568A0052319F /* Preview Assets.xcassets */,
157
+			);
158
+			path = "Preview Content";
159
+			sourceTree = "<group>";
160
+		};
161
+		A5EA2F22258FEBE700699182 /* Services */ = {
162
+			isa = PBXGroup;
163
+			children = (
164
+				A50EEFCC255D175400545CFE /* ContentDataSource.swift */,
165
+				A5EA2F23258FEC2300699182 /* UserService.swift */,
166
+				A5EA2F27258FEC3D00699182 /* FlowerService.swift */,
167
+				A5EE38A3257700230014D0BE /* ImageManager.swift */,
168
+			);
169
+			path = Services;
170
+			sourceTree = "<group>";
171
+		};
172
+		A5EA2F2A258FF07800699182 /* Supporting */ = {
173
+			isa = PBXGroup;
174
+			children = (
175
+				A5EA2F2B258FF0BD00699182 /* Constants.swift */,
176
+				A5EA2F31258FF58200699182 /* Responses.swift */,
177
+			);
178
+			path = Supporting;
179
+			sourceTree = "<group>";
180
+		};
181
+		A5EE38B6257819AA0014D0BE /* Discover Page */ = {
182
+			isa = PBXGroup;
183
+			children = (
184
+				A59A2742254556880052319F /* DiscoverView.swift */,
185
+				A5EE38AF257818520014D0BE /* FlowersList.swift */,
186
+				A5F3271925460C0B003BCE0F /* FlowerDetailView.swift */,
187
+				A5AF3F5B2545FDEB0035AC9A /* FlowerCard.swift */,
188
+				A5EE38AB257772200014D0BE /* FiltersSheet.swift */,
189
+				A5EE388C2576362B0014D0BE /* FilterFields.swift */,
190
+			);
191
+			path = "Discover Page";
192
+			sourceTree = "<group>";
193
+		};
194
+/* End PBXGroup section */
195
+
196
+/* Begin PBXNativeTarget section */
197
+		A59A273C254556880052319F /* Flowerdex */ = {
198
+			isa = PBXNativeTarget;
199
+			buildConfigurationList = A59A274C2545568A0052319F /* Build configuration list for PBXNativeTarget "Flowerdex" */;
200
+			buildPhases = (
201
+				A59A2739254556880052319F /* Sources */,
202
+				A59A273A254556880052319F /* Frameworks */,
203
+				A59A273B254556880052319F /* Resources */,
204
+			);
205
+			buildRules = (
206
+			);
207
+			dependencies = (
208
+			);
209
+			name = Flowerdex;
210
+			productName = Flowerdex;
211
+			productReference = A59A273D254556880052319F /* Flowerdex.app */;
212
+			productType = "com.apple.product-type.application";
213
+		};
214
+/* End PBXNativeTarget section */
215
+
216
+/* Begin PBXProject section */
217
+		A59A2735254556870052319F /* Project object */ = {
218
+			isa = PBXProject;
219
+			attributes = {
220
+				LastSwiftUpdateCheck = 1210;
221
+				LastUpgradeCheck = 1210;
222
+				TargetAttributes = {
223
+					A59A273C254556880052319F = {
224
+						CreatedOnToolsVersion = 12.1;
225
+					};
226
+				};
227
+			};
228
+			buildConfigurationList = A59A2738254556870052319F /* Build configuration list for PBXProject "Flowerdex" */;
229
+			compatibilityVersion = "Xcode 9.3";
230
+			developmentRegion = en;
231
+			hasScannedForEncodings = 0;
232
+			knownRegions = (
233
+				en,
234
+				Base,
235
+			);
236
+			mainGroup = A59A2734254556870052319F;
237
+			productRefGroup = A59A273E254556880052319F /* Products */;
238
+			projectDirPath = "";
239
+			projectRoot = "";
240
+			targets = (
241
+				A59A273C254556880052319F /* Flowerdex */,
242
+			);
243
+		};
244
+/* End PBXProject section */
245
+
246
+/* Begin PBXResourcesBuildPhase section */
247
+		A59A273B254556880052319F /* Resources */ = {
248
+			isa = PBXResourcesBuildPhase;
249
+			buildActionMask = 2147483647;
250
+			files = (
251
+				A59A27482545568A0052319F /* Preview Assets.xcassets in Resources */,
252
+				A52FB5C42546C2EA000AD235 /* rawResponse.json in Resources */,
253
+				A51B4F7B25466A75002AFF05 /* flowers.json in Resources */,
254
+				A59A27452545568A0052319F /* Assets.xcassets in Resources */,
255
+			);
256
+			runOnlyForDeploymentPostprocessing = 0;
257
+		};
258
+/* End PBXResourcesBuildPhase section */
259
+
260
+/* Begin PBXSourcesBuildPhase section */
261
+		A59A2739254556880052319F /* Sources */ = {
262
+			isa = PBXSourcesBuildPhase;
263
+			buildActionMask = 2147483647;
264
+			files = (
265
+				A50EEFCD255D175400545CFE /* ContentDataSource.swift in Sources */,
266
+				A5EA2F2C258FF0BD00699182 /* Constants.swift in Sources */,
267
+				A59A2743254556880052319F /* DiscoverView.swift in Sources */,
268
+				A5EE389125763D3A0014D0BE /* AuthenticationModel.swift in Sources */,
269
+				A5EE38A4257700230014D0BE /* ImageManager.swift in Sources */,
270
+				A5EE38B0257818520014D0BE /* FlowersList.swift in Sources */,
271
+				A51B4F772546674C002AFF05 /* FlowersResponseModel.swift in Sources */,
272
+				A5EE3897257658400014D0BE /* Filters.swift in Sources */,
273
+				A5711B8B255CE03000E727BB /* FlowerItem.swift in Sources */,
274
+				A59A2741254556880052319F /* FlowerdexApp.swift in Sources */,
275
+				A5EA2F32258FF58200699182 /* Responses.swift in Sources */,
276
+				A58640BE258F30F200F626AB /* RegisterView.swift in Sources */,
277
+				A5EE388D2576362B0014D0BE /* FilterFields.swift in Sources */,
278
+				A5F3271A25460C0B003BCE0F /* FlowerDetailView.swift in Sources */,
279
+				A58640BA258F30AD00F626AB /* LoginView.swift in Sources */,
280
+				A5AF3F5C2545FDEB0035AC9A /* FlowerCard.swift in Sources */,
281
+				A5EE38B3257818BE0014D0BE /* StatusIcons.swift in Sources */,
282
+				A5EA2F24258FEC2300699182 /* UserService.swift in Sources */,
283
+				A5711B8E255CE53900E727BB /* DummyData.swift in Sources */,
284
+				A5F0D2FC258F3D2200AF735C /* UserModel.swift in Sources */,
285
+				A5EE38AC257772200014D0BE /* FiltersSheet.swift in Sources */,
286
+				A5EA2F28258FEC3D00699182 /* FlowerService.swift in Sources */,
287
+			);
288
+			runOnlyForDeploymentPostprocessing = 0;
289
+		};
290
+/* End PBXSourcesBuildPhase section */
291
+
292
+/* Begin XCBuildConfiguration section */
293
+		A59A274A2545568A0052319F /* Debug */ = {
294
+			isa = XCBuildConfiguration;
295
+			buildSettings = {
296
+				ALWAYS_SEARCH_USER_PATHS = NO;
297
+				CLANG_ANALYZER_NONNULL = YES;
298
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
299
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
300
+				CLANG_CXX_LIBRARY = "libc++";
301
+				CLANG_ENABLE_MODULES = YES;
302
+				CLANG_ENABLE_OBJC_ARC = YES;
303
+				CLANG_ENABLE_OBJC_WEAK = YES;
304
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
305
+				CLANG_WARN_BOOL_CONVERSION = YES;
306
+				CLANG_WARN_COMMA = YES;
307
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
308
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
309
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
310
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
311
+				CLANG_WARN_EMPTY_BODY = YES;
312
+				CLANG_WARN_ENUM_CONVERSION = YES;
313
+				CLANG_WARN_INFINITE_RECURSION = YES;
314
+				CLANG_WARN_INT_CONVERSION = YES;
315
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
316
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
317
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
318
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
319
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
320
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
321
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
322
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
323
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
324
+				CLANG_WARN_UNREACHABLE_CODE = YES;
325
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
326
+				COPY_PHASE_STRIP = NO;
327
+				DEBUG_INFORMATION_FORMAT = dwarf;
328
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
329
+				ENABLE_TESTABILITY = YES;
330
+				GCC_C_LANGUAGE_STANDARD = gnu11;
331
+				GCC_DYNAMIC_NO_PIC = NO;
332
+				GCC_NO_COMMON_BLOCKS = YES;
333
+				GCC_OPTIMIZATION_LEVEL = 0;
334
+				GCC_PREPROCESSOR_DEFINITIONS = (
335
+					"DEBUG=1",
336
+					"$(inherited)",
337
+				);
338
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
339
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
340
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
341
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
342
+				GCC_WARN_UNUSED_FUNCTION = YES;
343
+				GCC_WARN_UNUSED_VARIABLE = YES;
344
+				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
345
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
346
+				MTL_FAST_MATH = YES;
347
+				ONLY_ACTIVE_ARCH = YES;
348
+				SDKROOT = iphoneos;
349
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
350
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
351
+			};
352
+			name = Debug;
353
+		};
354
+		A59A274B2545568A0052319F /* Release */ = {
355
+			isa = XCBuildConfiguration;
356
+			buildSettings = {
357
+				ALWAYS_SEARCH_USER_PATHS = NO;
358
+				CLANG_ANALYZER_NONNULL = YES;
359
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
360
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
361
+				CLANG_CXX_LIBRARY = "libc++";
362
+				CLANG_ENABLE_MODULES = YES;
363
+				CLANG_ENABLE_OBJC_ARC = YES;
364
+				CLANG_ENABLE_OBJC_WEAK = YES;
365
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
366
+				CLANG_WARN_BOOL_CONVERSION = YES;
367
+				CLANG_WARN_COMMA = YES;
368
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
369
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
370
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
371
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
372
+				CLANG_WARN_EMPTY_BODY = YES;
373
+				CLANG_WARN_ENUM_CONVERSION = YES;
374
+				CLANG_WARN_INFINITE_RECURSION = YES;
375
+				CLANG_WARN_INT_CONVERSION = YES;
376
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
377
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
378
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
379
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
380
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
381
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
382
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
383
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
384
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
385
+				CLANG_WARN_UNREACHABLE_CODE = YES;
386
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
387
+				COPY_PHASE_STRIP = NO;
388
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
389
+				ENABLE_NS_ASSERTIONS = NO;
390
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
391
+				GCC_C_LANGUAGE_STANDARD = gnu11;
392
+				GCC_NO_COMMON_BLOCKS = YES;
393
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
394
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
395
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
396
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
397
+				GCC_WARN_UNUSED_FUNCTION = YES;
398
+				GCC_WARN_UNUSED_VARIABLE = YES;
399
+				IPHONEOS_DEPLOYMENT_TARGET = 14.1;
400
+				MTL_ENABLE_DEBUG_INFO = NO;
401
+				MTL_FAST_MATH = YES;
402
+				SDKROOT = iphoneos;
403
+				SWIFT_COMPILATION_MODE = wholemodule;
404
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
405
+				VALIDATE_PRODUCT = YES;
406
+			};
407
+			name = Release;
408
+		};
409
+		A59A274D2545568A0052319F /* Debug */ = {
410
+			isa = XCBuildConfiguration;
411
+			buildSettings = {
412
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
413
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
414
+				CODE_SIGN_STYLE = Automatic;
415
+				DEVELOPMENT_ASSET_PATHS = "\"Flowerdex/Preview Content\"";
416
+				DEVELOPMENT_TEAM = 64DLWCZQCP;
417
+				ENABLE_PREVIEWS = YES;
418
+				INFOPLIST_FILE = Flowerdex/Info.plist;
419
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
420
+				LD_RUNPATH_SEARCH_PATHS = (
421
+					"$(inherited)",
422
+					"@executable_path/Frameworks",
423
+				);
424
+				PRODUCT_BUNDLE_IDENTIFIER = com.LosIndecisos.Flowerdex;
425
+				PRODUCT_NAME = "$(TARGET_NAME)";
426
+				SWIFT_VERSION = 5.0;
427
+				TARGETED_DEVICE_FAMILY = "1,2";
428
+			};
429
+			name = Debug;
430
+		};
431
+		A59A274E2545568A0052319F /* Release */ = {
432
+			isa = XCBuildConfiguration;
433
+			buildSettings = {
434
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
435
+				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
436
+				CODE_SIGN_STYLE = Automatic;
437
+				DEVELOPMENT_ASSET_PATHS = "\"Flowerdex/Preview Content\"";
438
+				DEVELOPMENT_TEAM = 64DLWCZQCP;
439
+				ENABLE_PREVIEWS = YES;
440
+				INFOPLIST_FILE = Flowerdex/Info.plist;
441
+				IPHONEOS_DEPLOYMENT_TARGET = 14.0;
442
+				LD_RUNPATH_SEARCH_PATHS = (
443
+					"$(inherited)",
444
+					"@executable_path/Frameworks",
445
+				);
446
+				PRODUCT_BUNDLE_IDENTIFIER = com.LosIndecisos.Flowerdex;
447
+				PRODUCT_NAME = "$(TARGET_NAME)";
448
+				SWIFT_VERSION = 5.0;
449
+				TARGETED_DEVICE_FAMILY = "1,2";
450
+			};
451
+			name = Release;
452
+		};
453
+/* End XCBuildConfiguration section */
454
+
455
+/* Begin XCConfigurationList section */
456
+		A59A2738254556870052319F /* Build configuration list for PBXProject "Flowerdex" */ = {
457
+			isa = XCConfigurationList;
458
+			buildConfigurations = (
459
+				A59A274A2545568A0052319F /* Debug */,
460
+				A59A274B2545568A0052319F /* Release */,
461
+			);
462
+			defaultConfigurationIsVisible = 0;
463
+			defaultConfigurationName = Release;
464
+		};
465
+		A59A274C2545568A0052319F /* Build configuration list for PBXNativeTarget "Flowerdex" */ = {
466
+			isa = XCConfigurationList;
467
+			buildConfigurations = (
468
+				A59A274D2545568A0052319F /* Debug */,
469
+				A59A274E2545568A0052319F /* Release */,
470
+			);
471
+			defaultConfigurationIsVisible = 0;
472
+			defaultConfigurationName = Release;
473
+		};
474
+/* End XCConfigurationList section */
475
+	};
476
+	rootObject = A59A2735254556870052319F /* Project object */;
477
+}

+ 7
- 0
Flowerdex/Flowerdex.xcodeproj/project.xcworkspace/contents.xcworkspacedata View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Workspace
3
+   version = "1.0">
4
+   <FileRef
5
+      location = "self:">
6
+   </FileRef>
7
+</Workspace>

+ 8
- 0
Flowerdex/Flowerdex.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist View File

@@ -0,0 +1,8 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>IDEDidComputeMac32BitWarning</key>
6
+	<true/>
7
+</dict>
8
+</plist>

BIN
Flowerdex/Flowerdex.xcodeproj/project.xcworkspace/xcuserdata/victor.hernandez.xcuserdatad/UserInterfaceState.xcuserstate View File


+ 24
- 0
Flowerdex/Flowerdex.xcodeproj/xcuserdata/victor.hernandez.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist View File

@@ -0,0 +1,24 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Bucket
3
+   uuid = "D6B586DA-A748-4B1B-97C4-0045A88FED79"
4
+   type = "1"
5
+   version = "2.0">
6
+   <Breakpoints>
7
+      <BreakpointProxy
8
+         BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
9
+         <BreakpointContent
10
+            uuid = "98EA52C7-D195-41FE-8759-7AAA517703FF"
11
+            shouldBeEnabled = "No"
12
+            ignoreCount = "0"
13
+            continueAfterRunningActions = "No"
14
+            filePath = "Flowerdex/URLImage.swift"
15
+            startingColumnNumber = "9223372036854775807"
16
+            endingColumnNumber = "9223372036854775807"
17
+            startingLineNumber = "12"
18
+            endingLineNumber = "12"
19
+            landmarkName = "URLImage"
20
+            landmarkType = "14">
21
+         </BreakpointContent>
22
+      </BreakpointProxy>
23
+   </Breakpoints>
24
+</Bucket>

+ 14
- 0
Flowerdex/Flowerdex.xcodeproj/xcuserdata/victor.hernandez.xcuserdatad/xcschemes/xcschememanagement.plist View File

@@ -0,0 +1,14 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>SchemeUserState</key>
6
+	<dict>
7
+		<key>Flowerdex.xcscheme_^#shared#^_</key>
8
+		<dict>
9
+			<key>orderHint</key>
10
+			<integer>0</integer>
11
+		</dict>
12
+	</dict>
13
+</dict>
14
+</plist>

+ 11
- 0
Flowerdex/Flowerdex/Assets.xcassets/AccentColor.colorset/Contents.json View File

@@ -0,0 +1,11 @@
1
+{
2
+  "colors" : [
3
+    {
4
+      "idiom" : "universal"
5
+    }
6
+  ],
7
+  "info" : {
8
+    "author" : "xcode",
9
+    "version" : 1
10
+  }
11
+}

+ 98
- 0
Flowerdex/Flowerdex/Assets.xcassets/AppIcon.appiconset/Contents.json View File

@@ -0,0 +1,98 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "idiom" : "iphone",
5
+      "scale" : "2x",
6
+      "size" : "20x20"
7
+    },
8
+    {
9
+      "idiom" : "iphone",
10
+      "scale" : "3x",
11
+      "size" : "20x20"
12
+    },
13
+    {
14
+      "idiom" : "iphone",
15
+      "scale" : "2x",
16
+      "size" : "29x29"
17
+    },
18
+    {
19
+      "idiom" : "iphone",
20
+      "scale" : "3x",
21
+      "size" : "29x29"
22
+    },
23
+    {
24
+      "idiom" : "iphone",
25
+      "scale" : "2x",
26
+      "size" : "40x40"
27
+    },
28
+    {
29
+      "idiom" : "iphone",
30
+      "scale" : "3x",
31
+      "size" : "40x40"
32
+    },
33
+    {
34
+      "idiom" : "iphone",
35
+      "scale" : "2x",
36
+      "size" : "60x60"
37
+    },
38
+    {
39
+      "idiom" : "iphone",
40
+      "scale" : "3x",
41
+      "size" : "60x60"
42
+    },
43
+    {
44
+      "idiom" : "ipad",
45
+      "scale" : "1x",
46
+      "size" : "20x20"
47
+    },
48
+    {
49
+      "idiom" : "ipad",
50
+      "scale" : "2x",
51
+      "size" : "20x20"
52
+    },
53
+    {
54
+      "idiom" : "ipad",
55
+      "scale" : "1x",
56
+      "size" : "29x29"
57
+    },
58
+    {
59
+      "idiom" : "ipad",
60
+      "scale" : "2x",
61
+      "size" : "29x29"
62
+    },
63
+    {
64
+      "idiom" : "ipad",
65
+      "scale" : "1x",
66
+      "size" : "40x40"
67
+    },
68
+    {
69
+      "idiom" : "ipad",
70
+      "scale" : "2x",
71
+      "size" : "40x40"
72
+    },
73
+    {
74
+      "idiom" : "ipad",
75
+      "scale" : "1x",
76
+      "size" : "76x76"
77
+    },
78
+    {
79
+      "idiom" : "ipad",
80
+      "scale" : "2x",
81
+      "size" : "76x76"
82
+    },
83
+    {
84
+      "idiom" : "ipad",
85
+      "scale" : "2x",
86
+      "size" : "83.5x83.5"
87
+    },
88
+    {
89
+      "idiom" : "ios-marketing",
90
+      "scale" : "1x",
91
+      "size" : "1024x1024"
92
+    }
93
+  ],
94
+  "info" : {
95
+    "author" : "xcode",
96
+    "version" : 1
97
+  }
98
+}

+ 56
- 0
Flowerdex/Flowerdex/Assets.xcassets/Blue Gray.colorset/Contents.json View File

@@ -0,0 +1,56 @@
1
+{
2
+  "colors" : [
3
+    {
4
+      "color" : {
5
+        "color-space" : "srgb",
6
+        "components" : {
7
+          "alpha" : "1.000",
8
+          "blue" : "0.282",
9
+          "green" : "0.282",
10
+          "red" : "0.282"
11
+        }
12
+      },
13
+      "idiom" : "universal"
14
+    },
15
+    {
16
+      "appearances" : [
17
+        {
18
+          "appearance" : "luminosity",
19
+          "value" : "light"
20
+        }
21
+      ],
22
+      "color" : {
23
+        "color-space" : "srgb",
24
+        "components" : {
25
+          "alpha" : "1.000",
26
+          "blue" : "0.282",
27
+          "green" : "0.282",
28
+          "red" : "0.282"
29
+        }
30
+      },
31
+      "idiom" : "universal"
32
+    },
33
+    {
34
+      "appearances" : [
35
+        {
36
+          "appearance" : "luminosity",
37
+          "value" : "dark"
38
+        }
39
+      ],
40
+      "color" : {
41
+        "color-space" : "srgb",
42
+        "components" : {
43
+          "alpha" : "1.000",
44
+          "blue" : "1.000",
45
+          "green" : "1.000",
46
+          "red" : "1.000"
47
+        }
48
+      },
49
+      "idiom" : "universal"
50
+    }
51
+  ],
52
+  "info" : {
53
+    "author" : "xcode",
54
+    "version" : 1
55
+  }
56
+}

+ 6
- 0
Flowerdex/Flowerdex/Assets.xcassets/Contents.json View File

@@ -0,0 +1,6 @@
1
+{
2
+  "info" : {
3
+    "author" : "xcode",
4
+    "version" : 1
5
+  }
6
+}

+ 56
- 0
Flowerdex/Flowerdex/Assets.xcassets/Rausch.colorset/Contents.json View File

@@ -0,0 +1,56 @@
1
+{
2
+  "colors" : [
3
+    {
4
+      "color" : {
5
+        "color-space" : "srgb",
6
+        "components" : {
7
+          "alpha" : "1.000",
8
+          "blue" : "0.376",
9
+          "green" : "0.353",
10
+          "red" : "1.000"
11
+        }
12
+      },
13
+      "idiom" : "universal"
14
+    },
15
+    {
16
+      "appearances" : [
17
+        {
18
+          "appearance" : "luminosity",
19
+          "value" : "light"
20
+        }
21
+      ],
22
+      "color" : {
23
+        "color-space" : "srgb",
24
+        "components" : {
25
+          "alpha" : "1.000",
26
+          "blue" : "0.376",
27
+          "green" : "0.353",
28
+          "red" : "1.000"
29
+        }
30
+      },
31
+      "idiom" : "universal"
32
+    },
33
+    {
34
+      "appearances" : [
35
+        {
36
+          "appearance" : "luminosity",
37
+          "value" : "dark"
38
+        }
39
+      ],
40
+      "color" : {
41
+        "color-space" : "srgb",
42
+        "components" : {
43
+          "alpha" : "1.000",
44
+          "blue" : "1.000",
45
+          "green" : "1.000",
46
+          "red" : "1.000"
47
+        }
48
+      },
49
+      "idiom" : "universal"
50
+    }
51
+  ],
52
+  "info" : {
53
+    "author" : "xcode",
54
+    "version" : 1
55
+  }
56
+}

+ 21
- 0
Flowerdex/Flowerdex/Assets.xcassets/amapola.imageset/Contents.json View File

@@ -0,0 +1,21 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "filename" : "amapola.jpg",
5
+      "idiom" : "universal",
6
+      "scale" : "1x"
7
+    },
8
+    {
9
+      "idiom" : "universal",
10
+      "scale" : "2x"
11
+    },
12
+    {
13
+      "idiom" : "universal",
14
+      "scale" : "3x"
15
+    }
16
+  ],
17
+  "info" : {
18
+    "author" : "xcode",
19
+    "version" : 1
20
+  }
21
+}

BIN
Flowerdex/Flowerdex/Assets.xcassets/amapola.imageset/amapola.jpg View File


+ 21
- 0
Flowerdex/Flowerdex/Assets.xcassets/orquidea.imageset/Contents.json View File

@@ -0,0 +1,21 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "filename" : "orquidea.jpg",
5
+      "idiom" : "universal",
6
+      "scale" : "1x"
7
+    },
8
+    {
9
+      "idiom" : "universal",
10
+      "scale" : "2x"
11
+    },
12
+    {
13
+      "idiom" : "universal",
14
+      "scale" : "3x"
15
+    }
16
+  ],
17
+  "info" : {
18
+    "author" : "xcode",
19
+    "version" : 1
20
+  }
21
+}

BIN
Flowerdex/Flowerdex/Assets.xcassets/orquidea.imageset/orquidea.jpg View File


+ 28
- 0
Flowerdex/Flowerdex/FlowerdexApp.swift View File

@@ -0,0 +1,28 @@
1
+//
2
+//  FlowerdexApp.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 10/25/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+@main
11
+struct FlowerdexApp: App {
12
+    @StateObject var lm = LoginModel()
13
+    @StateObject var rm = RegistrationModel()
14
+    @StateObject var fm = FlowerService()
15
+    var body: some Scene {
16
+        WindowGroup {
17
+            if User.exists() {
18
+                DiscoverView()
19
+                    .environmentObject(fm)
20
+            } else {
21
+                LoginView()
22
+                    .environmentObject(lm)
23
+                    .environmentObject(rm)
24
+            }
25
+        }
26
+    }
27
+}
28
+

+ 61
- 0
Flowerdex/Flowerdex/Info.plist View File

@@ -0,0 +1,61 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>NSAppTransportSecurity</key>
6
+	<dict>
7
+		<key>NSAllowsArbitraryLoads</key>
8
+		<true/>
9
+		<key>NSAllowsArbitraryLoadsInWebContent</key>
10
+		<true/>
11
+		<key>NSAllowsArbitraryLoadsForMedia</key>
12
+		<true/>
13
+	</dict>
14
+	<key>UIFileSharingEnabled</key>
15
+	<false/>
16
+	<key>CFBundleDevelopmentRegion</key>
17
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
18
+	<key>CFBundleExecutable</key>
19
+	<string>$(EXECUTABLE_NAME)</string>
20
+	<key>CFBundleIdentifier</key>
21
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
22
+	<key>CFBundleInfoDictionaryVersion</key>
23
+	<string>6.0</string>
24
+	<key>CFBundleName</key>
25
+	<string>$(PRODUCT_NAME)</string>
26
+	<key>CFBundlePackageType</key>
27
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
28
+	<key>CFBundleShortVersionString</key>
29
+	<string>1.0</string>
30
+	<key>CFBundleVersion</key>
31
+	<string>1</string>
32
+	<key>LSRequiresIPhoneOS</key>
33
+	<true/>
34
+	<key>UIApplicationSceneManifest</key>
35
+	<dict>
36
+		<key>UIApplicationSupportsMultipleScenes</key>
37
+		<true/>
38
+	</dict>
39
+	<key>UIApplicationSupportsIndirectInputEvents</key>
40
+	<true/>
41
+	<key>UILaunchScreen</key>
42
+	<dict/>
43
+	<key>UIRequiredDeviceCapabilities</key>
44
+	<array>
45
+		<string>armv7</string>
46
+	</array>
47
+	<key>UISupportedInterfaceOrientations</key>
48
+	<array>
49
+		<string>UIInterfaceOrientationPortrait</string>
50
+		<string>UIInterfaceOrientationLandscapeLeft</string>
51
+		<string>UIInterfaceOrientationLandscapeRight</string>
52
+	</array>
53
+	<key>UISupportedInterfaceOrientations~ipad</key>
54
+	<array>
55
+		<string>UIInterfaceOrientationPortrait</string>
56
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
57
+		<string>UIInterfaceOrientationLandscapeLeft</string>
58
+		<string>UIInterfaceOrientationLandscapeRight</string>
59
+	</array>
60
+</dict>
61
+</plist>

+ 46
- 0
Flowerdex/Flowerdex/Model/AuthenticationModel.swift View File

@@ -0,0 +1,46 @@
1
+//
2
+//  AuthenticationModel.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/1/20.
6
+//
7
+
8
+import Foundation
9
+
10
+class LoginData: Codable {
11
+    var email: String
12
+    var password: String
13
+    
14
+    init(email: String, password: String) {
15
+        self.email = email
16
+        self.password = password
17
+    }
18
+    
19
+    func isComplete() -> Bool {
20
+        if email == "" || password == "" {
21
+            return false
22
+        } else {
23
+            return true
24
+        }
25
+    }
26
+}
27
+
28
+class RegistrationData: Codable {
29
+    var username: String
30
+    var password: String
31
+    var email: String
32
+    
33
+    init(username: String, email: String, password: String) {
34
+        self.username = username
35
+        self.email = email
36
+        self.password = password
37
+    }
38
+    
39
+    func isComplete() -> Bool {
40
+        if username == "" || password == "" || email == "" {
41
+            return false
42
+        } else {
43
+            return true
44
+        }
45
+    }
46
+}

+ 30
- 0
Flowerdex/Flowerdex/Model/Filters.swift View File

@@ -0,0 +1,30 @@
1
+//
2
+//  Filters.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/1/20.
6
+//
7
+
8
+import Foundation
9
+
10
+class Filters {
11
+    
12
+    var edible: Bool
13
+    var vegetable: Bool
14
+    var petalCount: Int
15
+    var growthMonths: Int
16
+    var bloomMonths: Int
17
+    var scientificName: String
18
+    var commonName: String
19
+    
20
+    init(edible: Bool, vegetable: Bool, petalCount: Int, growthMonths: Int, bloomMonths: Int, scientificName: String, commonName: String) {
21
+        self.edible = edible
22
+        self.vegetable = vegetable
23
+        self.petalCount = petalCount
24
+        self.growthMonths = growthMonths
25
+        self.bloomMonths = bloomMonths
26
+        self.scientificName = scientificName
27
+        self.commonName = commonName
28
+    }
29
+    
30
+}

+ 62
- 0
Flowerdex/Flowerdex/Model/FlowerItem.swift View File

@@ -0,0 +1,62 @@
1
+//
2
+//  FlowerItem.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 11/11/20.
6
+//
7
+
8
+import Foundation
9
+
10
+struct FlowerItem: Hashable, Codable, Identifiable {
11
+    let id: Int
12
+    let common_name: String?
13
+    var commonName: String {
14
+        if let cm = common_name {
15
+            return cm
16
+        } else {
17
+            return scientificName
18
+        }
19
+    }
20
+    let slug: String // *
21
+    let scientific_name: String
22
+    var scientificName: String { scientific_name }
23
+    let year: Int?
24
+    let bibliography: String?
25
+    let author: String? // *
26
+    let status: String // *
27
+    let rank: String // *
28
+    let family_common_name: String?
29
+    var familyCommonName: String {
30
+        if let fcn = family_common_name {
31
+            return fcn
32
+        } else {
33
+            return "[Family Common Name]"
34
+        }
35
+    }
36
+    let genus_id: Int // *
37
+    var genusID: Int { genus_id }
38
+    let image_url: String?
39
+    var imageURL: String {
40
+        if let iu = image_url {
41
+            return iu
42
+        } else {
43
+            return "[Image URL]"
44
+        }
45
+    }
46
+    let synonyms: [String]
47
+    let genus: String
48
+    let family: String
49
+    let links: Dictionary<String, String> // FlowerLinks
50
+    
51
+    // Properties added by us
52
+    let isFavorite: Bool
53
+    let hasBeenFound: Bool
54
+}
55
+
56
+/*
57
+struct FlowerLinks: Codable {
58
+    // let self: String
59
+    let plant: String
60
+    let genus: String
61
+}
62
+*/

+ 24
- 0
Flowerdex/Flowerdex/Model/FlowersResponseModel.swift View File

@@ -0,0 +1,24 @@
1
+//
2
+//  FlowersResponseModel.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 10/25/20.
6
+//
7
+
8
+import Foundation
9
+
10
+struct FlowersResponseModel: Hashable, Codable {
11
+    let data: [FlowerItem]?
12
+    let links: Dictionary<String, String>?
13
+    let meta: Dictionary<String, Int>?
14
+    let error: String?
15
+}
16
+
17
+// TODO: probably adopt this model instead of decoding user directly
18
+//struct UserResponseModel: Hashable, Codable {
19
+//    let id: Int?
20
+//    let username: String?
21
+//    let email: String?
22
+//    let password: String?
23
+//    let error: String?
24
+//}

+ 60
- 0
Flowerdex/Flowerdex/Model/UserModel.swift View File

@@ -0,0 +1,60 @@
1
+//
2
+//  UserModel.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import Foundation
9
+
10
+class User: Codable, Identifiable { // not a hashable struct anymore :(
11
+    
12
+    // REFERENCE: https://www.makeschool.com/academy/track/build-ios-apps/build-a-photo-sharing-app/keeping-users-logged-in
13
+    
14
+    let id: Int?
15
+    let username: String?
16
+    let email: String
17
+    let password: String?
18
+    
19
+    private static var _current: User?
20
+    
21
+    static var current: User {
22
+        guard let currentUser = User._current else {
23
+            fatalError("Error: Current user doesn't exist")
24
+        }
25
+        
26
+        return currentUser
27
+    }
28
+    
29
+    // TODO: implement save to keychain on wtud == false
30
+    static func setCurrent(_ user: User, writeToUserDefaults: Bool = false) {
31
+        if writeToUserDefaults {
32
+            if let data = try? JSONEncoder().encode(user) {
33
+                UserDefaults.standard.set(data, forKey: Constants.UserDefaults.currentUser)
34
+            }
35
+        }
36
+        _current = user
37
+    }
38
+    
39
+    static func fetchCurrent() -> User? {
40
+        if let userData = UserDefaults.standard.object(forKey: Constants.UserDefaults.currentUser) as? Data,
41
+           let user = try? JSONDecoder().decode(User.self, from: userData) {
42
+            print("from fetchCurrent(): \(String(describing: user))")
43
+            User.setCurrent(user)
44
+            return user
45
+        } else {
46
+            return nil
47
+        }
48
+    }
49
+    
50
+    static func exists() -> Bool {
51
+        let user = fetchCurrent()
52
+        print("from exists(): \(String(describing: user))")
53
+        if user != nil {
54
+            return true
55
+        } else {
56
+            return false
57
+        }
58
+    }
59
+    
60
+}

+ 6
- 0
Flowerdex/Flowerdex/Preview Content/Preview Assets.xcassets/Contents.json View File

@@ -0,0 +1,6 @@
1
+{
2
+  "info" : {
3
+    "author" : "xcode",
4
+    "version" : 1
5
+  }
6
+}

+ 32
- 0
Flowerdex/Flowerdex/Resources/DummyData.swift View File

@@ -0,0 +1,32 @@
1
+//
2
+//  DummyData.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 11/11/20.
6
+//
7
+
8
+import Foundation
9
+
10
+let dummyFlowers: [FlowerItem] = load("flowers.json")
11
+
12
+func load<T: Decodable>(_ filename: String) -> T {
13
+    let data: Data
14
+    
15
+    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
16
+        else {
17
+            fatalError("Couldn't find \(filename) in main bundle.")
18
+        }
19
+    
20
+    do {
21
+        data = try Data(contentsOf: file)
22
+    } catch {
23
+        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
24
+    }
25
+    
26
+    do {
27
+        let decoder = JSONDecoder()
28
+        return try decoder.decode(T.self, from: data)
29
+    } catch {
30
+        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
31
+    }
32
+}

+ 1055
- 0
Flowerdex/Flowerdex/Resources/flowers.json
File diff suppressed because it is too large
View File


+ 1066
- 0
Flowerdex/Flowerdex/Resources/rawResponse.json
File diff suppressed because it is too large
View File


+ 67
- 0
Flowerdex/Flowerdex/Services/ContentDataSource.swift View File

@@ -0,0 +1,67 @@
1
+//
2
+//  ContentDataSource.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 11/12/20.
6
+//
7
+
8
+import Foundation
9
+import Combine
10
+// TAKEN FROM https://www.donnywals.com/implementing-an-infinite-scrolling-list-with-swiftui-and-combine/
11
+
12
+/*
13
+class ContentDataSource: ObservableObject {
14
+  @Published var items = [FlowerItem]() // ListItem
15
+  @Published var isLoadingPage = false
16
+  private var currentPage = 1
17
+  private var canLoadMorePages = true
18
+
19
+  init() {
20
+    print("More 0")
21
+    loadMoreContent()
22
+  }
23
+
24
+  func loadMoreContentIfNeeded(currentItem item: FlowerItem?) {
25
+    guard let item = item else {
26
+        print("More 1")
27
+      loadMoreContent()
28
+      return
29
+    }
30
+
31
+    let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
32
+    if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
33
+        print("More 2")
34
+      loadMoreContent()
35
+    }
36
+  }
37
+
38
+  private func loadMoreContent() {
39
+    guard !isLoadingPage && canLoadMorePages else {
40
+        print("Terminate \(isLoadingPage) \(canLoadMorePages)")
41
+        return
42
+    }
43
+
44
+    isLoadingPage = true
45
+
46
+    let url = URL(string: "https://trefle.io/api/v1/plants/?token=hg6M-l4XhrgVgn2A-qZC6KKMrVPMUuCffVfDgDPtc0I&page=\(currentPage)")!
47
+    // https://s3.eu-west-2.amazonaws.com/com.donnywals.misc/feed-\(currentPage).json
48
+    URLSession.shared.dataTaskPublisher(for: url)
49
+      .map(\.data)
50
+      .decode(type: TrefleResponseModel.self, decoder: JSONDecoder()) // ListResponse.self
51
+      .receive(on: DispatchQueue.main)
52
+      .handleEvents(receiveOutput: { response in
53
+        print("Called, \(response.links!["self"] != response.links!["last"])")
54
+        self.canLoadMorePages = (response.links!["self"] != response.links!["last"]) // response.hasMorePages
55
+        self.isLoadingPage = false
56
+        self.currentPage += 1
57
+      })
58
+      .map({ response in
59
+        return self.items + response.data! // response.items
60
+      })
61
+      .catch({ _ in
62
+        return Just(self.items)
63
+      })
64
+      .assign(to: &$items)
65
+  }
66
+}
67
+*/

+ 114
- 0
Flowerdex/Flowerdex/Services/FlowerService.swift View File

@@ -0,0 +1,114 @@
1
+//
2
+//  FlowerService.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import Foundation
9
+
10
+class FlowerService: ObservableObject {
11
+    
12
+    @Published var response: Response = .unfetched
13
+    @Published var items = [FlowerItem]()
14
+    
15
+    
16
+    private func getQueryParameters(_ filters: Filters) -> [URLQueryItem] {
17
+        
18
+        var list = [URLQueryItem]()
19
+        list.append(URLQueryItem(name: "edible", value: "\(filters.edible)"))
20
+        list.append(URLQueryItem(name: "vegetable", value: "\(filters.vegetable)"))
21
+        
22
+        if filters.commonName != "" {
23
+            list.append(URLQueryItem(name: "q", value: filters.commonName))
24
+        }
25
+//        list.append(URLQueryItem(name: "common_name", value: filters.commonName)) // if common_name was empty, which didn't make sense
26
+        
27
+
28
+        if filters.scientificName != "" {
29
+            list.append(URLQueryItem(name: "scientific_name", value: filters.scientificName))
30
+        }
31
+        
32
+        if filters.growthMonths > 0 {
33
+            list.append(URLQueryItem(name: "growth_months", value: "\(filters.growthMonths)"))
34
+        }
35
+        
36
+        if filters.bloomMonths > 0 {
37
+            list.append(URLQueryItem(name: "bloom_months", value: "\(filters.bloomMonths)"))
38
+        }
39
+        
40
+        return list
41
+        
42
+    }
43
+    
44
+    
45
+    private func getBaseURLComponent(_ apiPath: String) -> URLComponents {
46
+        var components = URLComponents()
47
+        components.scheme = Constants.Services.apiScheme
48
+        components.host = Constants.Services.apiHost
49
+        components.path = apiPath
50
+        components.queryItems = [
51
+            URLQueryItem(name: "user_id", value: "\(User.current.id!)"),
52
+        ]
53
+        return components
54
+    }
55
+    
56
+    
57
+    private func fetchFlowers(_ request: URLRequest) {
58
+        URLSession.shared.dataTask(with: request) { data, response, error in
59
+            guard let data = data else {
60
+                print("No data in response: \(error?.localizedDescription ?? "Unknown error")")
61
+                DispatchQueue.main.async {
62
+                    self.response = .failure
63
+                }
64
+                return
65
+            }
66
+            print(response ?? "No response")
67
+//            print("Recieved data:", String(decoding: data, as: UTF8.self))
68
+            
69
+            guard let apiResponse = try? JSONDecoder().decode(FlowersResponseModel.self, from: data) else {
70
+                print("Failed to decode Trefle response")
71
+                DispatchQueue.main.async {
72
+                    self.response = .failure
73
+                }
74
+                return
75
+            }
76
+            
77
+            if let httpResponse = response as? HTTPURLResponse {
78
+                DispatchQueue.main.async {
79
+                    if httpResponse.statusCode == 200 && apiResponse.error == nil {
80
+                        print("No error")
81
+                        self.response = .success
82
+                        self.items = apiResponse.data!
83
+                    } else {
84
+                        print("Error: \(apiResponse.error ?? "Unknown error")")
85
+                        self.response = .failure
86
+//                        self.items = [FlowerItem]()
87
+                    }
88
+                }
89
+            }
90
+        }.resume()
91
+    }
92
+    
93
+    
94
+    func getFlowers(p page: Int = 1, f filters: Filters?) {
95
+        
96
+        // Start loading state
97
+        self.response = .loading
98
+        
99
+        // Construct URL
100
+        var components = self.getBaseURLComponent(Constants.Services.apiGetFlowersPath)
101
+        if filters != nil {
102
+            components.queryItems!.append(contentsOf: self.getQueryParameters(filters!))
103
+        }
104
+        components.queryItems!.append(URLQueryItem(name: "page", value: "\(page)"))
105
+        
106
+        // Prepare and carry out request
107
+//        print("😂" + components.url!.absoluteString)
108
+        var request = URLRequest(url: components.url!)
109
+        request.httpMethod = "GET"
110
+        self.fetchFlowers(request)
111
+        
112
+    }
113
+    
114
+}

+ 92
- 0
Flowerdex/Flowerdex/Services/ImageManager.swift View File

@@ -0,0 +1,92 @@
1
+//
2
+//  ImageManager.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 11/12/20.
6
+//
7
+
8
+import Foundation
9
+import SwiftUI
10
+
11
+class ImageManager: ObservableObject {
12
+    
13
+    @Published var response: Response = .unfetched
14
+    @Published var image = UIImage(systemName: "photo")
15
+    
16
+    func fetchImage(_ urlString: String) {
17
+        
18
+        // Start loading state
19
+        self.response = .loading
20
+        
21
+        if let url = URL(string: urlString) {
22
+            URLSession(configuration: .default).dataTask(with: url) { data, response, error in
23
+                
24
+                if error != nil {
25
+                    print("Error occurred: \(error?.localizedDescription ?? "Unknown error")")
26
+                    return
27
+                }
28
+                
29
+                guard let data = data else {
30
+                    print("No data in response: \(error?.localizedDescription ?? "Unknown error")")
31
+                    return
32
+                }
33
+                
34
+                DispatchQueue.main.async {
35
+                    if let img = UIImage(data: data) {
36
+                        self.image = img
37
+                        self.response = .success
38
+                    } else {
39
+                        self.response = .failure
40
+                    }
41
+                }
42
+                
43
+            }.resume()
44
+        }
45
+    }
46
+    
47
+}
48
+
49
+//
50
+//class HistoryManager: ObservableObject {
51
+//    
52
+//    @Published var response: Response = .unfetched
53
+//    @Published var hasBeenFound = false
54
+//    let baseURL = "https://ada.uprrp.edu/~victor.hernandez17/"
55
+//    let foundEndpoint = "flowerdex/isFoundFlower.php"
56
+//    let putFoundEndpoint = "flowerdex/putFoundFlower.php"
57
+//    let removeFoundEndpoint = "flowerdex/removeFoundFlower.php"
58
+//    
59
+//    func fetch() {
60
+//        
61
+//        // Start loading state
62
+//        self.response = .loading
63
+//        
64
+//        let urlString = baseURL + foundEndpoint
65
+//        
66
+//        if let url = URL(string: urlString) {
67
+//            URLSession(configuration: .default).dataTask(with: url) { data, response, error in
68
+//                
69
+//                if error != nil {
70
+//                    print("Error occurred: \(error?.localizedDescription ?? "Unknown error")")
71
+//                    return
72
+//                }
73
+//                
74
+//                guard let data = data else {
75
+//                    print("No data in response: \(error?.localizedDescription ?? "Unknown error")")
76
+//                    return
77
+//                }
78
+//                
79
+//                DispatchQueue.main.async {
80
+//                    if let img = UIImage(data: data) {
81
+//                        self.image = img
82
+//                        self.response = .success
83
+//                    } else {
84
+//                        self.response = .failure
85
+//                    }
86
+//                }
87
+//                
88
+//            }.resume()
89
+//        }
90
+//    }URLSession.shared.dataTask(with: request) { data, response, error in
91
+//    
92
+//}

+ 165
- 0
Flowerdex/Flowerdex/Services/UserService.swift View File

@@ -0,0 +1,165 @@
1
+//
2
+//  UserService.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import Foundation
9
+
10
+class LoginModel: ObservableObject {
11
+    
12
+    @Published var response: Response = .unfetched
13
+    
14
+    
15
+    private func getBaseURL(_ apiPath: String) -> URL {
16
+        var components = URLComponents()
17
+        components.scheme = Constants.Services.apiScheme
18
+        components.host = Constants.Services.apiHost
19
+        components.path = apiPath
20
+        return components.url!
21
+    }
22
+    
23
+    
24
+    private func authenticate(_ request: URLRequest) {
25
+        URLSession.shared.dataTask(with: request) { data, response, error in
26
+            
27
+            guard let data = data
28
+            else {
29
+                print("No data in response: \(error?.localizedDescription ?? "Unknown error")")
30
+                DispatchQueue.main.async {
31
+                    self.response = .failure
32
+                }
33
+                return
34
+            }
35
+            
36
+            print(response ?? "No response")
37
+            print("Received data:", data)
38
+            print(error ?? "No error")
39
+            
40
+            guard let apiResponse = try? JSONDecoder().decode(User.self, from: data) else {
41
+                print("Failed to decode ADA response")
42
+                DispatchQueue.main.async {
43
+                    self.response = .failure
44
+                }
45
+                return
46
+            }
47
+            
48
+            if let httpResponse = response as? HTTPURLResponse {
49
+                DispatchQueue.main.async {
50
+                    if httpResponse.statusCode == 200 {
51
+                        self.response = .success
52
+                        User.setCurrent(apiResponse, writeToUserDefaults: true) // TODO: change to false when other auth method is supplied
53
+                    } else {
54
+                        self.response = .failure
55
+                    }
56
+                }
57
+            }
58
+            
59
+        }.resume()
60
+    }
61
+    
62
+    
63
+    func login(data: LoginData) {
64
+        
65
+        self.response = .loading
66
+        
67
+        guard let encodedData = try? JSONEncoder().encode(data)
68
+        else {
69
+            print("Failed to encode login request")
70
+            DispatchQueue.main.async {
71
+                self.response = .failure
72
+            }
73
+            return
74
+        }
75
+        
76
+        let url = getBaseURL(Constants.Services.apiLoginPath)
77
+        var request = URLRequest(url: url)
78
+        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
79
+        request.httpMethod = "POST"
80
+        request.httpBody = encodedData
81
+        self.authenticate(request)
82
+        
83
+    }
84
+
85
+}
86
+
87
+
88
+class RegistrationModel: ObservableObject {
89
+    
90
+    @Published var response: Response = .unfetched
91
+    
92
+    
93
+    private func getBaseURL(_ apiPath: String) -> URL {
94
+        var components = URLComponents()
95
+        components.scheme = Constants.Services.apiScheme
96
+        components.host = Constants.Services.apiHost
97
+        components.path = apiPath
98
+        return components.url!
99
+    }
100
+    
101
+    
102
+    private func authenticate(_ request: URLRequest) {
103
+        URLSession.shared.dataTask(with: request) { data, response, error in
104
+            
105
+            guard let data = data
106
+            else {
107
+                print("No data in response: \(error?.localizedDescription ?? "Unknown error")")
108
+                DispatchQueue.main.async {
109
+                    self.response = .failure
110
+                }
111
+                return
112
+            }
113
+            
114
+            print(response ?? "No response")
115
+            print("Received data:", data)
116
+            print(error ?? "No error")
117
+            
118
+            guard let apiResponse = try? JSONDecoder().decode(User.self, from: data) else {
119
+                print("Failed to decode ADA response")
120
+                DispatchQueue.main.async {
121
+                    self.response = .failure
122
+                }
123
+                return
124
+            }
125
+            
126
+            if let httpResponse = response as? HTTPURLResponse {
127
+                DispatchQueue.main.async {
128
+                    if httpResponse.statusCode == 200 {
129
+                        self.response = .success
130
+                        User.setCurrent(apiResponse, writeToUserDefaults: true) // TODO: change to false when other auth method is supplied
131
+                    } else {
132
+                        self.response = .failure
133
+                    }
134
+                }
135
+            }
136
+            
137
+        }.resume()
138
+    }
139
+    
140
+    
141
+    func register(data: RegistrationData) {
142
+        
143
+        self.response = .loading
144
+        
145
+        guard let encodedData = try? JSONEncoder().encode(data)
146
+        else {
147
+            print("Failed to encode registration request")
148
+            DispatchQueue.main.async {
149
+                self.response = .failure
150
+            }
151
+            return
152
+        }
153
+        
154
+        print("Registration JSON:", String(data: encodedData, encoding: .utf8)!)
155
+        
156
+        let url = getBaseURL(Constants.Services.apiRegistrationPath)
157
+        var request = URLRequest(url: url)
158
+        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
159
+        request.httpMethod = "POST"
160
+        request.httpBody = encodedData
161
+        self.authenticate(request)
162
+        
163
+    }
164
+    
165
+}

+ 31
- 0
Flowerdex/Flowerdex/Supporting/Constants.swift View File

@@ -0,0 +1,31 @@
1
+//
2
+//  Constants.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import Foundation
9
+import SwiftUI
10
+
11
+struct Constants {
12
+    struct UserDefaults {
13
+        static let currentUser = "currentUser"
14
+        static let isFirstTimeUser = "isFirstTimeUser"
15
+    }
16
+    
17
+    struct Services {
18
+        static let apiScheme = "https"
19
+        static let apiHost = "ada.uprrp.edu"
20
+        static let apiGetFlowersPath = "/~victor.hernandez17/flowerdex/listFlowers.php"
21
+        static let apiLoginPath = "/~victor.hernandez17/flowerdex/login.php"
22
+        static let apiRegistrationPath = "/~victor.hernandez17/flowerdex/signup.php"
23
+    }
24
+    
25
+    struct Colors {
26
+        static let lightGrayColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)
27
+        static let darkGrayColor = Color(red: 41.0/255.0, green: 42.0/255.0, blue: 47.0/255.0, opacity: 1.0)
28
+        static let blueGray = Color("Blue Gray")
29
+        static let rausch = Color("Rausch")
30
+    }
31
+}

+ 15
- 0
Flowerdex/Flowerdex/Supporting/Responses.swift View File

@@ -0,0 +1,15 @@
1
+//
2
+//  Responses.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import Foundation
9
+
10
+enum Response {
11
+    case success
12
+    case failure
13
+    case loading
14
+    case unfetched
15
+}

+ 150
- 0
Flowerdex/Flowerdex/View/Authentication/LoginView.swift View File

@@ -0,0 +1,150 @@
1
+//
2
+//  LoginView.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct LoginView: View {
11
+    @EnvironmentObject var lModel: LoginModel
12
+    
13
+    @State var email: String = ""
14
+    @State var password: String = ""
15
+    @State var showingRegistrationPane: Bool = false
16
+    @State private var showingAlert = false
17
+    
18
+    var body: some View {
19
+        VStack {
20
+            
21
+            WelcomeImage()
22
+            WelcomeText()
23
+            EmailField(email: $email)
24
+            PasswordField(password: $password)
25
+            LoginStatusText()
26
+            
27
+            // Login Button
28
+            Button(action: {
29
+                let authData = LoginData(email: email, password: password)
30
+                if authData.isComplete() {
31
+                    lModel.login(data: authData)
32
+                } else {
33
+                    self.showingAlert = true
34
+                }
35
+                
36
+            }) {
37
+                LoginButtonText()
38
+            }
39
+            
40
+            // Register Button
41
+            Button(action: {self.showingRegistrationPane.toggle()}) {
42
+                PromptRegisterText()
43
+            }
44
+            .sheet(isPresented: $showingRegistrationPane, content: { RegisterView(showingRegistrationPane: $showingRegistrationPane) })
45
+            .alert(isPresented: $showingAlert) {
46
+                Alert(title: Text("Fields incomplete"), message: Text("Please enter a valid email and password"), dismissButton: .default(Text("OK")))
47
+                
48
+            }
49
+        }
50
+        .padding()
51
+    }
52
+}
53
+
54
+
55
+struct LoginStatusText: View {
56
+    @EnvironmentObject var lModel: LoginModel
57
+    var body: some View {
58
+        if lModel.response == .failure {
59
+            Text("No user with given credentials")
60
+                .offset(y: -10)
61
+                .foregroundColor(.red)
62
+        } else if lModel.response == .success {
63
+            Text("Login successful 🎉")
64
+                .offset(y: -10)
65
+                .foregroundColor(.green)
66
+        } else if lModel.response == .loading {
67
+            ProgressView("Loading…")
68
+                .padding()
69
+        }
70
+    }
71
+}
72
+
73
+
74
+struct WelcomeText: View {
75
+    var body: some View {
76
+        Text("Flowerdex")
77
+            .font(.largeTitle)
78
+            .fontWeight(.semibold)
79
+            .foregroundColor(Color("Blue Gray"))
80
+            .padding(.bottom, 20)
81
+    }
82
+}
83
+
84
+struct WelcomeImage: View {
85
+    let size: CGFloat = 220
86
+    var body: some View {
87
+        // TODO: change this to app icon
88
+        Image("amapola")
89
+            
90
+            .resizable()
91
+            .aspectRatio(contentMode: .fill)
92
+            .frame(width: size, height: size)
93
+            .clipped()
94
+            .cornerRadius(size)
95
+//            .padding(.bottom, size / 4)
96
+    }
97
+}
98
+
99
+struct LoginButtonText: View {
100
+    @Environment(\.colorScheme) var colorScheme
101
+    var body: some View {
102
+        Text("Login")
103
+            .font(.headline)
104
+            .foregroundColor(colorScheme == .dark ? Constants.Colors.darkGrayColor : .white)
105
+            .padding()
106
+            .frame(width: 220, height: 50)
107
+            .background(Color("Rausch")) // Color.blue
108
+            .cornerRadius(5)
109
+            .padding(.bottom, 5)
110
+    }
111
+}
112
+
113
+struct PromptRegisterText: View {
114
+    var body: some View {
115
+        Text("Don't have an account? Register")
116
+            .font(.headline)
117
+            .foregroundColor(.gray)
118
+            .padding()
119
+            .frame(width: 350, height: 50)
120
+            .background(Color.clear)
121
+            .cornerRadius(5)
122
+    }
123
+}
124
+
125
+struct EmailField: View {
126
+    @Binding var email: String
127
+    @Environment(\.colorScheme) var colorScheme
128
+    var body: some View {
129
+        TextField("Email", text: $email)
130
+            .disableAutocorrection(true)
131
+            .autocapitalization(.none)
132
+            .padding()
133
+            .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
134
+            .cornerRadius(5.0)
135
+            .padding(.bottom, 5)
136
+    }
137
+}
138
+
139
+struct PasswordField: View {
140
+    @Binding var password: String
141
+    @Environment(\.colorScheme) var colorScheme
142
+    var body: some View {
143
+        SecureField("Password", text: $password)
144
+            .autocapitalization(.none)
145
+            .padding()
146
+            .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
147
+            .cornerRadius(5.0)
148
+            .padding(.bottom, 20)
149
+    }
150
+}

+ 157
- 0
Flowerdex/Flowerdex/View/Authentication/RegisterView.swift View File

@@ -0,0 +1,157 @@
1
+//
2
+//  RegisterView.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/20/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct RegisterView: View {
11
+    @EnvironmentObject var rModel: RegistrationModel
12
+    
13
+    @State var username: String = ""
14
+    @State var email: String = ""
15
+    @State var password: String = ""
16
+    @State var confirmPassword: String = ""
17
+    
18
+    @Binding var showingRegistrationPane: Bool
19
+    @State var showingMissingFieldsAlert = false
20
+    @State var showingPasswordsMismatchAlert = false
21
+    
22
+    var body: some View {
23
+        VStack {
24
+            
25
+            // NOTE: Chaining alerts in SwiftUI is prohibited,
26
+            // hence, I attach them to two separate views as a hack
27
+            // SEE: https://www.hackingwithswift.com/quick-start/swiftui/how-to-show-multiple-alerts-in-a-single-view
28
+            
29
+            RegistrationText()
30
+            RegistrationFields(username: $username, email: $email, password: $password, confirmPassword: $confirmPassword)
31
+            RegistrationStatusText()
32
+            
33
+            
34
+            // Register Button
35
+            Button(action: {
36
+                
37
+                // Reset booleans before reassignment below
38
+                showingPasswordsMismatchAlert = false
39
+                showingMissingFieldsAlert = false
40
+                
41
+                let data = RegistrationData(username: username, email: email, password: password)
42
+                
43
+                if data.isComplete() {
44
+                    if password == confirmPassword {
45
+                        rModel.register(data: data)
46
+                    } else {
47
+                        self.showingPasswordsMismatchAlert = true
48
+                        
49
+                    }
50
+                } else {
51
+                    self.showingMissingFieldsAlert = true
52
+                }
53
+                
54
+            }) {
55
+                RegistrationButtonText()
56
+            }
57
+            .alert(isPresented: $showingMissingFieldsAlert) {
58
+                Alert(title: Text("Fields incomplete"), message: Text("Please enter a all required information"), dismissButton: .default(Text("OK")))
59
+            }
60
+            
61
+            
62
+            // Back Button
63
+            Button(action: {self.showingRegistrationPane = false}, label: {
64
+                BackButtonText()
65
+            })
66
+            .alert(isPresented: $showingPasswordsMismatchAlert) {
67
+                Alert(title: Text("Passwords do not match"), message: Text("Please retype your password and try again"), dismissButton: .default(Text("OK")))
68
+            }
69
+            
70
+            
71
+        }
72
+        .padding()
73
+    }
74
+}
75
+
76
+struct RegistrationFields: View {
77
+    @Binding var username: String
78
+    @Binding var email: String
79
+    @Binding var password: String
80
+    @Binding var confirmPassword: String
81
+    @Environment(\.colorScheme) var colorScheme
82
+    
83
+    var body: some View {
84
+        ScrollView {
85
+            Group {
86
+                TextField("Username", text: $username)
87
+                    .autocapitalization(.words)
88
+                    .padding()
89
+                    .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
90
+                    .cornerRadius(5.0)
91
+                    .padding(.bottom, 5)
92
+                TextField("Email", text: $email)
93
+                    .disableAutocorrection(true)
94
+                    .autocapitalization(.none)
95
+                    .padding()
96
+                    .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
97
+                    .cornerRadius(5.0)
98
+                    .padding(.bottom, 5)
99
+                SecureField("Password", text: $password)
100
+                    .autocapitalization(.none)
101
+                    .padding()
102
+                    .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
103
+                    .cornerRadius(5.0)
104
+                    .padding(.bottom, 5)
105
+                SecureField("Confirm Password", text: $confirmPassword)
106
+                    .autocapitalization(.none)
107
+                    .padding()
108
+                    .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
109
+                    .cornerRadius(5.0)
110
+                    .padding(.bottom, 5)
111
+            }.padding(5)
112
+        }
113
+    }
114
+}
115
+
116
+struct RegistrationText: View {
117
+    var body: some View {
118
+        Text("Details")
119
+            .font(.largeTitle)
120
+            .fontWeight(.semibold)
121
+            .foregroundColor(Constants.Colors.blueGray)
122
+            .frame(width: 350, height: 25, alignment: .topLeading)
123
+            .padding(.bottom, 20)
124
+    }
125
+}
126
+
127
+struct RegistrationButtonText: View {
128
+    @Environment(\.colorScheme) var colorScheme
129
+    var body: some View {
130
+        Text("Register")
131
+            .font(.headline)
132
+            .foregroundColor(colorScheme == .dark ? Constants.Colors.darkGrayColor : .white)
133
+            .padding()
134
+            .frame(width: 220, height: 50)
135
+            .background(Constants.Colors.rausch) // Color.blue
136
+            .cornerRadius(5)
137
+            .padding(.bottom, 5)
138
+    }
139
+}
140
+
141
+struct RegistrationStatusText: View {
142
+    @EnvironmentObject var rModel: RegistrationModel
143
+    var body: some View {
144
+        if rModel.response == .failure {
145
+            Text("Registration failed, please try again")
146
+                //.offset(y: -5)
147
+                .foregroundColor(.red)
148
+        } else if rModel.response == .loading {
149
+            ProgressView("Loading…")
150
+                .padding()
151
+        } else if rModel.response == .success {
152
+            Text("Registration successful 🎉")
153
+                //.offset(y: -5)
154
+                .foregroundColor(.green)
155
+        }
156
+    }
157
+}

+ 77
- 0
Flowerdex/Flowerdex/View/Discover Page/DiscoverView.swift View File

@@ -0,0 +1,77 @@
1
+//
2
+//  DiscoverView.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 10/25/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct DiscoverView: View {
11
+    
12
+    @EnvironmentObject var fModel: FlowerService
13
+    @State var showingFilterPane: Bool = false
14
+    @State var page: Int = 1
15
+    
16
+    // Filter Variables
17
+    @State var edible: Bool = false
18
+    @State var vegetable: Bool = false
19
+    @State var petalCount: Int = 0
20
+    @State var growthMonths: Int = 0
21
+    @State var bloomMonths: Int = 0
22
+    @State var scientificName: String = ""
23
+    @State var commonName: String = ""
24
+    
25
+    init() {
26
+        // FROM: https://sarunw.com/posts/uinavigationbar-changes-in-ios13/
27
+        let appearance = UINavigationBarAppearance()
28
+        appearance.titleTextAttributes = [.foregroundColor: UIColor(Constants.Colors.blueGray)] // small, collapsed title
29
+        appearance.largeTitleTextAttributes = [.foregroundColor: UIColor(Constants.Colors.rausch)] // large title
30
+//        UINavigationBar.appearance().scrollEdgeAppearance = appearance
31
+        UINavigationBar.appearance().standardAppearance = appearance
32
+    }
33
+    
34
+    var body: some View {
35
+        NavigationView {
36
+            ScrollView {
37
+                ScrollableSearchedContent()
38
+            }
39
+            .navigationBarTitle("Discover")
40
+            .navigationBarItems(trailing:
41
+                Button(action: {self.showingFilterPane = true}) {
42
+                    FilterButtonIcon()
43
+                }
44
+                .sheet(isPresented: $showingFilterPane, content: {
45
+                    FiltersSheet(showingFilterPane: $showingFilterPane, page: $page, edible: $edible, vegetable: $vegetable, petalCount: $petalCount, growthMonths: $growthMonths, bloomMonths: $bloomMonths, scientificName: $scientificName, commonName: $commonName)
46
+                })
47
+            )
48
+        }
49
+        .onAppear {
50
+            self.fModel.getFlowers(p: page, f: nil)
51
+        }
52
+    }
53
+    
54
+}
55
+
56
+struct ScrollableSearchedContent: View {
57
+    @EnvironmentObject var fModel: FlowerService
58
+    var body: some View {
59
+        if fModel.response == .success {
60
+            FlowersList(flowerList: fModel.items)
61
+        } else if fModel.response == .loading || fModel.response == .unfetched {
62
+            // TODO: make this centered vertically within parent ScrollView
63
+            ProgressView("Loading...")
64
+        } else if fModel.response == .failure {
65
+            // TODO: make this centered vertically within parent ScrollView
66
+            FailureIcon()
67
+        }
68
+    }
69
+}
70
+
71
+struct FilterButtonIcon: View {
72
+    var body: some View {
73
+        Image(systemName: "slider.horizontal.3")
74
+            .font(.title)
75
+            .foregroundColor(Constants.Colors.rausch)
76
+    }
77
+}

+ 139
- 0
Flowerdex/Flowerdex/View/Discover Page/FilterFields.swift View File

@@ -0,0 +1,139 @@
1
+//
2
+//  FilterFields.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/1/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct FilterFields: View {
11
+
12
+    @Binding var edible: Bool
13
+    @Binding var vegetable: Bool
14
+    @Binding var petalCount: Int
15
+    @Binding var growthMonths: Int
16
+    @Binding var bloomMonths: Int
17
+    @Binding var scientificName: String
18
+    @Binding var commonName: String
19
+    
20
+    var body: some View {
21
+        VStack {
22
+            MainText()
23
+            EdibleField(edible: $edible)
24
+            VegetableField(vegetable: $vegetable)
25
+            PetalsField(petalCount: $petalCount)
26
+            GrowthMonthsField(growthMonths: $growthMonths)
27
+            BloomMonthsField(bloomMonths: $bloomMonths)
28
+            CommonNameField(commonName: $commonName)
29
+            ScientificNameField(scientificName: $scientificName)
30
+        }
31
+        .padding()
32
+    }
33
+}
34
+
35
+struct MainText: View {
36
+    var body: some View {
37
+        Text("Filters")
38
+            .font(.title2)
39
+            .bold()
40
+            .foregroundColor(Color("Rausch"))
41
+    }
42
+}
43
+
44
+struct CommonNameField: View {
45
+    @Binding var commonName: String
46
+    @Environment(\.colorScheme) var colorScheme
47
+    var body: some View {
48
+        TextField("Common Name", text: $commonName)
49
+            .disableAutocorrection(true)
50
+            .autocapitalization(.none)
51
+            .padding()
52
+            .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
53
+            .cornerRadius(5.0)
54
+            .padding(.bottom, 5)
55
+    }
56
+}
57
+
58
+struct ScientificNameField: View {
59
+    @Binding var scientificName: String
60
+    @Environment(\.colorScheme) var colorScheme
61
+    var body: some View {
62
+        TextField("Scienfitic Name", text: $scientificName)
63
+            .disableAutocorrection(true)
64
+            .autocapitalization(.none)
65
+            .padding()
66
+            .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Constants.Colors.lightGrayColor)
67
+            .cornerRadius(5.0)
68
+            .padding(.bottom, 5)
69
+    }
70
+}
71
+
72
+struct EdibleField: View {
73
+    @Binding var edible: Bool
74
+    var body: some View {
75
+        Toggle(isOn: $edible, label: {
76
+            Text("Edible")
77
+        })
78
+    }
79
+}
80
+
81
+struct VegetableField: View {
82
+    @Binding var vegetable: Bool
83
+    var body: some View {
84
+        Toggle(isOn: $vegetable, label: {
85
+            Text("Vegetable")
86
+        })
87
+    }
88
+}
89
+
90
+struct PetalsField: View {
91
+    @Binding var petalCount: Int
92
+    var body: some View {
93
+        Stepper(onIncrement: {
94
+            if self.petalCount < 10 { // 10 petals is a lot, but oh well
95
+                self.petalCount += 1
96
+            }
97
+        }, onDecrement: {
98
+            if self.petalCount > 0 {
99
+                self.petalCount -= 1
100
+            }
101
+        }, label: {
102
+            Text("Petals (\(self.petalCount))")
103
+        })
104
+    }
105
+}
106
+
107
+struct GrowthMonthsField: View {
108
+    @Binding var growthMonths: Int
109
+    var body: some View {
110
+        Stepper(onIncrement: {
111
+            if self.growthMonths < 10 { // 10 petals is a lot, but oh well
112
+                self.growthMonths += 1
113
+            }
114
+        }, onDecrement: {
115
+            if self.growthMonths > 0 {
116
+                self.growthMonths -= 1
117
+            }
118
+        }, label: {
119
+            Text("Growth Months (\(self.growthMonths))")
120
+        })
121
+    }
122
+}
123
+
124
+struct BloomMonthsField: View {
125
+    @Binding var bloomMonths: Int
126
+    var body: some View {
127
+        Stepper(onIncrement: {
128
+            if self.bloomMonths < 10 { // 10 petals is a lot, but oh well
129
+                self.bloomMonths += 1
130
+            }
131
+        }, onDecrement: {
132
+            if self.bloomMonths > 0 {
133
+                self.bloomMonths -= 1
134
+            }
135
+        }, label: {
136
+            Text("Bloom Months (\(self.bloomMonths))")
137
+        })
138
+    }
139
+}

+ 68
- 0
Flowerdex/Flowerdex/View/Discover Page/FiltersSheet.swift View File

@@ -0,0 +1,68 @@
1
+//
2
+//  FiltersSheet.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/2/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct FiltersSheet: View {
11
+
12
+    @EnvironmentObject var fModel: FlowerService
13
+    @Binding var showingFilterPane: Bool
14
+    @Binding var page: Int
15
+    
16
+    // Filter Variables
17
+    @Binding var edible: Bool
18
+    @Binding var vegetable: Bool
19
+    @Binding var petalCount: Int
20
+    @Binding var growthMonths: Int
21
+    @Binding var bloomMonths: Int
22
+    @Binding var scientificName: String
23
+    @Binding var commonName: String
24
+
25
+    var body: some View {
26
+        FilterFields(edible: $edible, vegetable: $vegetable, petalCount: $petalCount, growthMonths: $growthMonths, bloomMonths: $bloomMonths, scientificName: $scientificName, commonName: $commonName)
27
+        
28
+        Spacer()
29
+        
30
+        Button(action: {
31
+            let filters = Filters(edible: edible, vegetable: vegetable, petalCount: petalCount, growthMonths: growthMonths, bloomMonths: bloomMonths, scientificName: scientificName, commonName: commonName)
32
+            
33
+            self.fModel.getFlowers(p: page, f: filters)
34
+            
35
+            self.showingFilterPane = false
36
+        }, label: {
37
+            ApplyButtonText()
38
+        })
39
+        
40
+        Button(action: {self.showingFilterPane = false}, label: {
41
+            BackButtonText()
42
+        })
43
+    }
44
+}
45
+
46
+struct ApplyButtonText: View {
47
+    @Environment(\.colorScheme) var colorScheme
48
+    var body: some View {
49
+        Text("Apply")
50
+            .font(.headline) // wasn't here originally
51
+            .foregroundColor(colorScheme == .dark ? Constants.Colors.darkGrayColor : .white)
52
+            .padding() // wasn't here originally
53
+//            .padding(.horizontal)
54
+//            .padding(.vertical, 10)
55
+            .frame(width: 220, height: 50) // wasn't here originally
56
+            .background(Color("Rausch"))
57
+            .cornerRadius(5)
58
+//            .padding(.bottom, 5) // dunno if worth adding
59
+    }
60
+}
61
+
62
+struct BackButtonText: View {
63
+    var body: some View {
64
+        Text("Back")
65
+            .foregroundColor(Constants.Colors.blueGray)
66
+            .padding()
67
+    }
68
+}

+ 86
- 0
Flowerdex/Flowerdex/View/Discover Page/FlowerCard.swift View File

@@ -0,0 +1,86 @@
1
+//
2
+//  FowerCard.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 10/25/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct FlowerCard: View {
11
+    
12
+    var flower: FlowerItem
13
+    @ObservedObject var imageManager = ImageManager()
14
+    var size: CGFloat = 150
15
+    @Environment(\.colorScheme) var colorScheme
16
+    
17
+    var body: some View {
18
+        HStack {
19
+            HStack(alignment: .top, spacing: 0) {
20
+                // TODO: make placeholder and actual image look equally good
21
+                Image(uiImage: self.imageManager.image!)
22
+                    .resizable()
23
+//                    .scaledToFit()
24
+                    .aspectRatio(contentMode: .fill)
25
+                    .frame(width: self.size, height: self.size)
26
+                    .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Color.white)
27
+                CardDescription(commonName: self.flower.commonName, genus: self.flower.genus, family: self.flower.family, size: self.size, isFavorite: self.flower.isFavorite)
28
+            }
29
+            
30
+        }
31
+        .cornerRadius(15)
32
+        .shadow(radius: 5)
33
+        .padding(.horizontal, 20)
34
+        .padding(.vertical, 5)
35
+        .onAppear {
36
+            self.imageManager.fetchImage(self.flower.imageURL)
37
+        }
38
+    }
39
+}
40
+
41
+struct CardDescription: View {
42
+    
43
+    var commonName: String
44
+    var genus: String
45
+    var family: String
46
+    var size: CGFloat
47
+    var isFavorite: Bool
48
+    @Environment(\.colorScheme) var colorScheme
49
+    
50
+    var body: some View {
51
+        VStack(alignment: .leading) {
52
+            Text(self.commonName.capitalized)
53
+                .font(.headline)
54
+            Spacer()
55
+            HStack {
56
+                Text(self.genus + " > " + self.family)
57
+                    .font(.caption)
58
+                Spacer()
59
+                Button(action: {print("hello")}) { // 
60
+                    if self.isFavorite {
61
+                        Image(systemName: "star.fill")
62
+                            .foregroundColor(.yellow)
63
+                    } else {
64
+                        Image(systemName: "star")
65
+                            .foregroundColor(.black)
66
+                    }
67
+                    
68
+                }
69
+            }
70
+        }
71
+        .padding(.vertical, 20)
72
+        .padding(.horizontal, 15)
73
+        .frame(maxWidth: .infinity, maxHeight: self.size, alignment: .topLeading)
74
+        .background(colorScheme == .dark ? Constants.Colors.darkGrayColor : Color.white)
75
+    }
76
+}
77
+
78
+struct FowerCardView_Previews: PreviewProvider {
79
+    static var previews: some View {
80
+        Group {
81
+            FlowerCard(flower: dummyFlowers[0])
82
+            FlowerCard(flower: dummyFlowers[1])
83
+        }
84
+        .previewLayout(.sizeThatFits)
85
+    }
86
+}

+ 100
- 0
Flowerdex/Flowerdex/View/Discover Page/FlowerDetailView.swift View File

@@ -0,0 +1,100 @@
1
+//
2
+//  FlowerDetailView.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 10/25/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct FlowerDetailView: View {
11
+    
12
+    var flower: FlowerItem
13
+    @ObservedObject var imageManager = ImageManager()
14
+//    @ObservedObject var foundManager = FoundManager()
15
+    var size: CGFloat = 300
16
+    
17
+    var body: some View {
18
+        VStack(alignment: .leading, spacing: 0) {
19
+            HStack {
20
+                Text(self.flower.commonName.capitalized)
21
+                    .font(.title)
22
+                    .fontWeight(/*@START_MENU_TOKEN@*/.semibold/*@END_MENU_TOKEN@*/)
23
+                    .foregroundColor(Constants.Colors.blueGray)
24
+                    
25
+                //                .frame(width: .infinity, alignment: .center)
26
+                Spacer()
27
+                Button(action: {print("hello")}) { //self.hasBeenFound.toggle()
28
+                    if self.flower.hasBeenFound {
29
+                        Image(systemName: "eye")
30
+                            .font(.title)
31
+                            .foregroundColor(Constants.Colors.rausch)
32
+                    } else {
33
+                        Image(systemName: "eye.slash")
34
+                            .font(.title)
35
+                            .foregroundColor(Constants.Colors.blueGray)
36
+                    }
37
+                    
38
+                }
39
+            }
40
+            
41
+            .padding()
42
+            
43
+
44
+            
45
+//            URLImage(url: self.flower.imageURL)
46
+//            Image("amapola")
47
+            // TODO: make placeholder and actual image look equally good
48
+            Image(uiImage: self.imageManager.image!)
49
+                .resizable()
50
+                .frame(width: self.size, height: self.size)
51
+            FlowerInformationPane(flower: flower)
52
+            Spacer()
53
+        }
54
+        .onAppear {
55
+            self.imageManager.fetchImage(self.flower.imageURL)
56
+        }
57
+    }
58
+}
59
+
60
+// TODO: account for text overflow
61
+struct FlowerInformationPane: View {
62
+    var flower: FlowerItem
63
+    var body: some View {
64
+        HStack {
65
+            VStack(alignment: .leading, spacing: nil) {
66
+                Text("Scientific Name:")
67
+                    .bold()
68
+                Text("\(self.flower.scientificName)")
69
+                    .foregroundColor(Constants.Colors.blueGray)
70
+                    .padding(.bottom)
71
+                Text("Year Discovered:")
72
+                    .bold()
73
+                Text("\(self.flower.year ?? -999)")
74
+                    .foregroundColor(Constants.Colors.blueGray)
75
+            }
76
+            .foregroundColor(Constants.Colors.rausch)
77
+            .padding()
78
+            VStack(alignment: .leading, spacing: nil) {
79
+                Text("Bibliography:")
80
+                    .bold()
81
+                Text("\(self.flower.bibliography ?? "n/a")")
82
+                    .foregroundColor(Constants.Colors.blueGray)
83
+                    .padding(.bottom)
84
+                Text("Author:")
85
+                    .bold()
86
+                Text("\(self.flower.author ?? "n/a")")
87
+                    .foregroundColor(Constants.Colors.blueGray)
88
+            }
89
+            .foregroundColor(Constants.Colors.rausch)
90
+            .padding()
91
+        }
92
+        .padding(.vertical)
93
+    }
94
+}
95
+
96
+struct FlowerDetailView_Previews: PreviewProvider {
97
+    static var previews: some View {
98
+        FlowerDetailView(flower: dummyFlowers[0])
99
+    }
100
+}

+ 33
- 0
Flowerdex/Flowerdex/View/Discover Page/FlowersList.swift View File

@@ -0,0 +1,33 @@
1
+//
2
+//  FlowersList.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/2/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct FlowersList: View {
11
+    var flowerList: [FlowerItem]
12
+    var body: some View {
13
+        if flowerList.count == 0 {
14
+            // TODO: make this centered vertically within parent ScrollView
15
+            NoMatchIcon()
16
+        } else {
17
+            LazyVStack {
18
+                ForEach(flowerList) { flower in
19
+                    NavigationLink(destination: FlowerDetailView(flower: flower)) {
20
+                        FlowerCard(flower: flower)
21
+                    }
22
+                    .foregroundColor(Constants.Colors.blueGray)
23
+                }
24
+            }
25
+        }
26
+    }
27
+}
28
+
29
+struct FlowersList_Previews: PreviewProvider {
30
+    static var previews: some View {
31
+        FlowersList(flowerList: dummyFlowers) // use: [] or dummyFlowers
32
+    }
33
+}

+ 147
- 0
Flowerdex/Flowerdex/View/FilterPane.swift View File

@@ -0,0 +1,147 @@
1
+//
2
+//  FilterPane.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/1/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+// bloom_months: Int
11
+// common_name: String
12
+// days_to_harvest: Int
13
+// edible: Bool
14
+// duration????????
15
+// flower_color???????
16
+// scientific_name: String
17
+// vegetable: Bool
18
+// growth_months: Int
19
+// leaf_retention???????
20
+
21
+//    Slider(value: $progress, in: 1...6, step: 1, minimumValueLabel: Text("Minimum"), maximumValueLabel: Text("Maximum"), label: {
22
+//        Text("Hello")
23
+//    })
24
+
25
+struct FilterFields: View {
26
+
27
+    @Binding var edible: Bool
28
+    @Binding var vegetable: Bool
29
+    @Binding var petalCount: Int
30
+    @Binding var growthMonths: Int
31
+    @Binding var bloomMonths: Int
32
+    @Binding var scientificName: String
33
+    @Binding var commonName: String
34
+    
35
+    var body: some View {
36
+        VStack {
37
+            MainText()
38
+            CommonNameField(commonName: $commonName)
39
+            EdibleField(edible: $edible)
40
+            VegetableField(vegetable: $vegetable)
41
+            PetalsField(petalCount: $petalCount)
42
+            GrowthMonthsField(growthMonths: $growthMonths)
43
+            BloomMonthsField(bloomMonths: $bloomMonths)
44
+            ScientificNameField(scientificName: $scientificName)
45
+        }
46
+        .padding()
47
+    }
48
+}
49
+
50
+struct MainText: View {
51
+    var body: some View {
52
+        Text("Filters")
53
+            .font(.title2)
54
+            .bold()
55
+            .foregroundColor(Color("Rausch"))
56
+    }
57
+}
58
+
59
+struct CommonNameField: View {
60
+    @Binding var commonName: String
61
+    var body: some View {
62
+        TextField("Common Name", text: $commonName)
63
+            .disableAutocorrection(true)
64
+            .autocapitalization(.none)
65
+            .padding()
66
+            .background(Color.black)
67
+            .cornerRadius(5.0)
68
+            .padding(.bottom, 5)
69
+    }
70
+}
71
+
72
+
73
+struct EdibleField: View {
74
+    @Binding var edible: Bool
75
+    var body: some View {
76
+        Toggle(isOn: $edible, label: {
77
+            Text("Edible")
78
+        })
79
+    }
80
+}
81
+
82
+struct VegetableField: View {
83
+    @Binding var vegetable: Bool
84
+    var body: some View {
85
+        Toggle(isOn: $vegetable, label: {
86
+            Text("Vegetable")
87
+        })
88
+    }
89
+}
90
+
91
+struct PetalsField: View {
92
+    @Binding var petalCount: Int
93
+    var body: some View {
94
+        Stepper(onIncrement: {
95
+            if self.petalCount < 10 { // 10 petals is a lot, but oh well
96
+                self.petalCount += 1
97
+            }
98
+        }, onDecrement: {
99
+            if self.petalCount > 0 {
100
+                self.petalCount -= 1
101
+            }
102
+        }, label: {
103
+            Text("Petals (\(self.petalCount))")
104
+        })
105
+    }
106
+}
107
+
108
+struct GrowthMonthsField: View {
109
+    @Binding var growthMonths: Int
110
+    var body: some View {
111
+        Stepper(onIncrement: {
112
+            if self.growthMonths < 10 { // 10 petals is a lot, but oh well
113
+                self.growthMonths += 1
114
+            }
115
+        }, onDecrement: {
116
+            if self.growthMonths > 0 {
117
+                self.growthMonths -= 1
118
+            }
119
+        }, label: {
120
+            Text("Growth Months (\(self.growthMonths))")
121
+        })
122
+    }
123
+}
124
+
125
+struct BloomMonthsField: View {
126
+    @Binding var bloomMonths: Int
127
+    var body: some View {
128
+        Stepper(onIncrement: {
129
+            if self.bloomMonths < 10 { // 10 petals is a lot, but oh well
130
+                self.bloomMonths += 1
131
+            }
132
+        }, onDecrement: {
133
+            if self.bloomMonths > 0 {
134
+                self.bloomMonths -= 1
135
+            }
136
+        }, label: {
137
+            Text("Bloom Months (\(self.bloomMonths))")
138
+        })
139
+    }
140
+}
141
+
142
+struct ScientificNameField: View {
143
+    @Binding var scientificName: String
144
+    var body: some View {
145
+        TextField("Scienfitic Name", text: $scientificName)
146
+    }
147
+}

+ 84
- 0
Flowerdex/Flowerdex/View/FlowerCardView.swift View File

@@ -0,0 +1,84 @@
1
+//
2
+//  FowerCardView.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 10/25/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct FlowerCard: View {
11
+    
12
+    var flower: FlowerModel
13
+    @ObservedObject var imageManager = ImageManager()
14
+    @State var isFavorite: Bool = true
15
+    var size: CGFloat = 150
16
+    
17
+    var body: some View {
18
+        HStack {
19
+            HStack(alignment: .top, spacing: 0) {
20
+                Image(uiImage: self.imageManager.image!)
21
+                    .resizable()
22
+//                    .scaledToFit()
23
+                    .aspectRatio(contentMode: .fill)
24
+                    .frame(width: self.size, height: self.size)
25
+                    .background(Color.white)
26
+                CardDescription(commonName: self.flower.commonName, genus: self.flower.genus, family: self.flower.family, size: self.size, isFavorite: self.$isFavorite)
27
+            }
28
+            
29
+        }
30
+        .cornerRadius(15)
31
+        .shadow(radius: 5)
32
+        .padding(.horizontal, 20)
33
+        .padding(.vertical, 5)
34
+        .onAppear {
35
+            self.imageManager.fetchImage(self.flower.imageURL)
36
+        }
37
+    }
38
+}
39
+
40
+struct CardDescription: View {
41
+    
42
+    var commonName: String
43
+    var genus: String
44
+    var family: String
45
+    var size: CGFloat
46
+    @Binding var isFavorite: Bool
47
+    
48
+    var body: some View {
49
+        VStack(alignment: .leading) {
50
+            Text(self.commonName.capitalized)
51
+                .font(.headline)
52
+            Spacer()
53
+            HStack {
54
+                Text(self.genus + " > " + self.family)
55
+                    .font(.caption)
56
+                Spacer()
57
+                Button(action: {self.isFavorite.toggle()}) {
58
+                    if self.isFavorite {
59
+                        Image(systemName: "star.fill")
60
+                            .foregroundColor(.yellow)
61
+                    } else {
62
+                        Image(systemName: "star")
63
+                            .foregroundColor(.black)
64
+                    }
65
+                    
66
+                }
67
+            }
68
+        }
69
+        .padding(.vertical, 20)
70
+        .padding(.horizontal, 15)
71
+        .frame(maxWidth: .infinity, maxHeight: self.size, alignment: .topLeading)
72
+        .background(Color.white)
73
+    }
74
+}
75
+
76
+struct FowerCardView_Previews: PreviewProvider {
77
+    static var previews: some View {
78
+        Group {
79
+            FlowerCard(flower: dummyFlowers[0])
80
+            FlowerCard(flower: dummyFlowers[1])
81
+        }
82
+        .previewLayout(.sizeThatFits)
83
+    }
84
+}

+ 44
- 0
Flowerdex/Flowerdex/View/StatusIcons.swift View File

@@ -0,0 +1,44 @@
1
+//
2
+//  StatusIcons.swift
3
+//  Flowerdex
4
+//
5
+//  Created by Víctor A. Hernández on 12/2/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct StatusIcon: View {
11
+    var sysName: String
12
+    var statusText: String
13
+    var body: some View {
14
+        VStack {
15
+            Image(systemName: sysName)
16
+                .font(.system(size: 40))
17
+            Text(statusText)
18
+                .font(.headline)
19
+                .padding(.vertical)
20
+        }
21
+        .foregroundColor(Constants.Colors.blueGray)
22
+    }
23
+}
24
+
25
+struct NoMatchIcon: View {
26
+    var body: some View {
27
+        StatusIcon(sysName: "zzz", statusText: "No matching results")
28
+    }
29
+}
30
+
31
+struct FailureIcon: View {
32
+    var body: some View {
33
+        StatusIcon(sysName: "xmark", statusText: "Something went wrong... 😔")
34
+    }
35
+}
36
+
37
+struct StatusIcons_Previews: PreviewProvider {
38
+    static var previews: some View {
39
+        Group {
40
+            NoMatchIcon()
41
+            FailureIcon()
42
+        }
43
+    }
44
+}