ソースを参照

please add folder

Luis 5 年 前
コミット
d42886a301
共有53 個のファイルを変更した3584 個の追加0 個の削除を含む
  1. 397
    0
      app/app.xcodeproj/project.pbxproj
  2. 7
    0
      app/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  3. 8
    0
      app/app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  4. 8
    0
      app/app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  5. バイナリ
      app/app.xcodeproj/project.xcworkspace/xcuserdata/Luis.xcuserdatad/UserInterfaceState.xcuserstate
  6. 16
    0
      app/app.xcodeproj/project.xcworkspace/xcuserdata/Luis.xcuserdatad/WorkspaceSettings.xcsettings
  7. 6
    0
      app/app.xcodeproj/xcuserdata/Luis.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  8. 14
    0
      app/app.xcodeproj/xcuserdata/Luis.xcuserdatad/xcschemes/xcschememanagement.plist
  9. 44
    0
      app/app/AppDelegate.swift
  10. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/1024.png
  11. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/114.png
  12. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/120.png
  13. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/180.png
  14. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/29.png
  15. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/40.png
  16. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/57.png
  17. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/58.png
  18. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/60.png
  19. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/80.png
  20. バイナリ
      app/app/Assets.xcassets/AppIcon.appiconset/87.png
  21. 80
    0
      app/app/Assets.xcassets/AppIcon.appiconset/Contents.json
  22. 6
    0
      app/app/Assets.xcassets/Contents.json
  23. 6
    0
      app/app/Assets.xcassets/Icons/Contents.json
  24. 21
    0
      app/app/Assets.xcassets/Icons/camera.imageset/Contents.json
  25. バイナリ
      app/app/Assets.xcassets/Icons/camera.imageset/camera.pdf
  26. 21
    0
      app/app/Assets.xcassets/Icons/home.imageset/Contents.json
  27. バイナリ
      app/app/Assets.xcassets/Icons/home.imageset/home.pdf
  28. 21
    0
      app/app/Assets.xcassets/Icons/menu.imageset/Contents.json
  29. バイナリ
      app/app/Assets.xcassets/Icons/menu.imageset/menu.pdf
  30. 21
    0
      app/app/Assets.xcassets/Icons/profile.imageset/Contents.json
  31. バイナリ
      app/app/Assets.xcassets/Icons/profile.imageset/profile.pdf
  32. 21
    0
      app/app/Assets.xcassets/icons8-checked-checkbox-50.imageset/Contents.json
  33. バイナリ
      app/app/Assets.xcassets/icons8-checked-checkbox-50.imageset/icons8-checked-checkbox-50.png
  34. 21
    0
      app/app/Assets.xcassets/icons8-unchecked-checkbox-50.imageset/Contents.json
  35. バイナリ
      app/app/Assets.xcassets/icons8-unchecked-checkbox-50.imageset/icons8-unchecked-checkbox-50.png
  36. 25
    0
      app/app/Base.lproj/LaunchScreen.storyboard
  37. 1262
    0
      app/app/Base.lproj/Main.storyboard
  38. 26
    0
      app/app/ContactsTableViewCell.swift
  39. 44
    0
      app/app/ContactsTableViewCell.xib
  40. 157
    0
      app/app/ContactsViewController.swift
  41. 198
    0
      app/app/CustomMsgFormViewController.swift
  42. 40
    0
      app/app/FirstTimeViewController.swift
  43. 20
    0
      app/app/HomeNavigationController.swift
  44. 425
    0
      app/app/HomeViewController.swift
  45. 59
    0
      app/app/Info.plist
  46. 94
    0
      app/app/MainViewController.swift
  47. 52
    0
      app/app/MenuViewController.swift
  48. 60
    0
      app/app/MessagesViewController.swift
  49. 238
    0
      app/app/MsgsFormViewController.swift
  50. 66
    0
      app/app/SlideInTransition.swift
  51. 44
    0
      app/app/TableWithMenuViewController.swift
  52. 56
    0
      app/app/ToolbarPickerView.swift
  53. バイナリ
      app/app/icons8-unchecked-checkbox-50.png

+ 397
- 0
app/app.xcodeproj/project.pbxproj ファイルの表示

@@ -0,0 +1,397 @@
1
+// !$*UTF8*$!
2
+{
3
+	archiveVersion = 1;
4
+	classes = {
5
+	};
6
+	objectVersion = 50;
7
+	objects = {
8
+
9
+/* Begin PBXBuildFile section */
10
+		A0069FB423A6C19E000998B6 /* TableWithMenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0069FB323A6C19E000998B6 /* TableWithMenuViewController.swift */; };
11
+		A0069FB823A9D5E0000998B6 /* FirstTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0069FB723A9D5E0000998B6 /* FirstTimeViewController.swift */; };
12
+		A0105E3823636493009B0E8F /* SlideInTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0105E3723636493009B0E8F /* SlideInTransition.swift */; };
13
+		A0695AD923633D1C00CD562B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0695AD823633D1C00CD562B /* AppDelegate.swift */; };
14
+		A0695ADD23633D1C00CD562B /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0695ADC23633D1C00CD562B /* HomeViewController.swift */; };
15
+		A0695AE023633D1C00CD562B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A0695ADE23633D1C00CD562B /* Main.storyboard */; };
16
+		A0695AE223633D2200CD562B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A0695AE123633D2200CD562B /* Assets.xcassets */; };
17
+		A0695AE523633D2200CD562B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A0695AE323633D2200CD562B /* LaunchScreen.storyboard */; };
18
+		A0695AEF236340BF00CD562B /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0695AEE236340BF00CD562B /* MenuViewController.swift */; };
19
+		A08A32BA23A5B78D0036F4C9 /* ToolbarPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A08A32B923A5B78D0036F4C9 /* ToolbarPickerView.swift */; };
20
+		A0C1E0F92392DB8A00996F53 /* MsgsFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0C1E0F82392DB8A00996F53 /* MsgsFormViewController.swift */; };
21
+		A0C1E0FF2392F57900996F53 /* CustomMsgFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0C1E0FE2392F57900996F53 /* CustomMsgFormViewController.swift */; };
22
+		A0E6269723922DA100A576B2 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E6269623922DA100A576B2 /* MessagesViewController.swift */; };
23
+		A0EDB313237CD924006100E6 /* ContactsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0EDB311237CD924006100E6 /* ContactsTableViewCell.swift */; };
24
+		A0EDB314237CD924006100E6 /* ContactsTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = A0EDB312237CD924006100E6 /* ContactsTableViewCell.xib */; };
25
+		A0EE435F2369FA8700ABA953 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0EE435E2369FA8700ABA953 /* MainViewController.swift */; };
26
+		A0EE4361236A073D00ABA953 /* HomeNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0EE4360236A073D00ABA953 /* HomeNavigationController.swift */; };
27
+		A0EE43652374938600ABA953 /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0EE43642374938600ABA953 /* ContactsViewController.swift */; };
28
+/* End PBXBuildFile section */
29
+
30
+/* Begin PBXFileReference section */
31
+		A0069FB323A6C19E000998B6 /* TableWithMenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableWithMenuViewController.swift; sourceTree = "<group>"; };
32
+		A0069FB723A9D5E0000998B6 /* FirstTimeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeViewController.swift; sourceTree = "<group>"; };
33
+		A0105E3723636493009B0E8F /* SlideInTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlideInTransition.swift; sourceTree = "<group>"; };
34
+		A0695AD523633D1C00CD562B /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
35
+		A0695AD823633D1C00CD562B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
36
+		A0695ADC23633D1C00CD562B /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
37
+		A0695ADF23633D1C00CD562B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
38
+		A0695AE123633D2200CD562B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
39
+		A0695AE423633D2200CD562B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
40
+		A0695AE623633D2200CD562B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
41
+		A0695AEE236340BF00CD562B /* MenuViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuViewController.swift; sourceTree = "<group>"; };
42
+		A08A32B923A5B78D0036F4C9 /* ToolbarPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarPickerView.swift; sourceTree = "<group>"; };
43
+		A0C1E0F82392DB8A00996F53 /* MsgsFormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgsFormViewController.swift; sourceTree = "<group>"; };
44
+		A0C1E0FE2392F57900996F53 /* CustomMsgFormViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMsgFormViewController.swift; sourceTree = "<group>"; };
45
+		A0E6269623922DA100A576B2 /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = "<group>"; };
46
+		A0EDB311237CD924006100E6 /* ContactsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsTableViewCell.swift; sourceTree = "<group>"; };
47
+		A0EDB312237CD924006100E6 /* ContactsTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ContactsTableViewCell.xib; sourceTree = "<group>"; };
48
+		A0EE435E2369FA8700ABA953 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
49
+		A0EE4360236A073D00ABA953 /* HomeNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeNavigationController.swift; sourceTree = "<group>"; };
50
+		A0EE43642374938600ABA953 /* ContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = "<group>"; };
51
+/* End PBXFileReference section */
52
+
53
+/* Begin PBXFrameworksBuildPhase section */
54
+		A0695AD223633D1C00CD562B /* Frameworks */ = {
55
+			isa = PBXFrameworksBuildPhase;
56
+			buildActionMask = 2147483647;
57
+			files = (
58
+			);
59
+			runOnlyForDeploymentPostprocessing = 0;
60
+		};
61
+/* End PBXFrameworksBuildPhase section */
62
+
63
+/* Begin PBXGroup section */
64
+		A0695ACC23633D1C00CD562B = {
65
+			isa = PBXGroup;
66
+			children = (
67
+				A0695AD723633D1C00CD562B /* app */,
68
+				A0695AD623633D1C00CD562B /* Products */,
69
+			);
70
+			sourceTree = "<group>";
71
+		};
72
+		A0695AD623633D1C00CD562B /* Products */ = {
73
+			isa = PBXGroup;
74
+			children = (
75
+				A0695AD523633D1C00CD562B /* app.app */,
76
+			);
77
+			name = Products;
78
+			sourceTree = "<group>";
79
+		};
80
+		A0695AD723633D1C00CD562B /* app */ = {
81
+			isa = PBXGroup;
82
+			children = (
83
+				A0695AD823633D1C00CD562B /* AppDelegate.swift */,
84
+				A0105E3723636493009B0E8F /* SlideInTransition.swift */,
85
+				A0EE4360236A073D00ABA953 /* HomeNavigationController.swift */,
86
+				A0EE435E2369FA8700ABA953 /* MainViewController.swift */,
87
+				A0695ADC23633D1C00CD562B /* HomeViewController.swift */,
88
+				A0695AEE236340BF00CD562B /* MenuViewController.swift */,
89
+				A0EE43642374938600ABA953 /* ContactsViewController.swift */,
90
+				A0E6269623922DA100A576B2 /* MessagesViewController.swift */,
91
+				A0C1E0F82392DB8A00996F53 /* MsgsFormViewController.swift */,
92
+				A0C1E0FE2392F57900996F53 /* CustomMsgFormViewController.swift */,
93
+				A0EDB312237CD924006100E6 /* ContactsTableViewCell.xib */,
94
+				A0EDB311237CD924006100E6 /* ContactsTableViewCell.swift */,
95
+				A0695AE123633D2200CD562B /* Assets.xcassets */,
96
+				A0695AE323633D2200CD562B /* LaunchScreen.storyboard */,
97
+				A0695AE623633D2200CD562B /* Info.plist */,
98
+				A0695ADE23633D1C00CD562B /* Main.storyboard */,
99
+				A08A32B923A5B78D0036F4C9 /* ToolbarPickerView.swift */,
100
+				A0069FB323A6C19E000998B6 /* TableWithMenuViewController.swift */,
101
+				A0069FB723A9D5E0000998B6 /* FirstTimeViewController.swift */,
102
+			);
103
+			path = app;
104
+			sourceTree = "<group>";
105
+		};
106
+/* End PBXGroup section */
107
+
108
+/* Begin PBXNativeTarget section */
109
+		A0695AD423633D1C00CD562B /* app */ = {
110
+			isa = PBXNativeTarget;
111
+			buildConfigurationList = A0695AE923633D2200CD562B /* Build configuration list for PBXNativeTarget "app" */;
112
+			buildPhases = (
113
+				A0695AD123633D1C00CD562B /* Sources */,
114
+				A0695AD223633D1C00CD562B /* Frameworks */,
115
+				A0695AD323633D1C00CD562B /* Resources */,
116
+			);
117
+			buildRules = (
118
+			);
119
+			dependencies = (
120
+			);
121
+			name = app;
122
+			productName = app;
123
+			productReference = A0695AD523633D1C00CD562B /* app.app */;
124
+			productType = "com.apple.product-type.application";
125
+		};
126
+/* End PBXNativeTarget section */
127
+
128
+/* Begin PBXProject section */
129
+		A0695ACD23633D1C00CD562B /* Project object */ = {
130
+			isa = PBXProject;
131
+			attributes = {
132
+				LastSwiftUpdateCheck = 1110;
133
+				LastUpgradeCheck = 1110;
134
+				ORGANIZATIONNAME = "Luis Quiñones ";
135
+				TargetAttributes = {
136
+					A0695AD423633D1C00CD562B = {
137
+						CreatedOnToolsVersion = 11.1;
138
+					};
139
+				};
140
+			};
141
+			buildConfigurationList = A0695AD023633D1C00CD562B /* Build configuration list for PBXProject "app" */;
142
+			compatibilityVersion = "Xcode 9.3";
143
+			developmentRegion = en;
144
+			hasScannedForEncodings = 0;
145
+			knownRegions = (
146
+				en,
147
+				Base,
148
+			);
149
+			mainGroup = A0695ACC23633D1C00CD562B;
150
+			productRefGroup = A0695AD623633D1C00CD562B /* Products */;
151
+			projectDirPath = "";
152
+			projectRoot = "";
153
+			targets = (
154
+				A0695AD423633D1C00CD562B /* app */,
155
+			);
156
+		};
157
+/* End PBXProject section */
158
+
159
+/* Begin PBXResourcesBuildPhase section */
160
+		A0695AD323633D1C00CD562B /* Resources */ = {
161
+			isa = PBXResourcesBuildPhase;
162
+			buildActionMask = 2147483647;
163
+			files = (
164
+				A0EDB314237CD924006100E6 /* ContactsTableViewCell.xib in Resources */,
165
+				A0695AE523633D2200CD562B /* LaunchScreen.storyboard in Resources */,
166
+				A0695AE223633D2200CD562B /* Assets.xcassets in Resources */,
167
+				A0695AE023633D1C00CD562B /* Main.storyboard in Resources */,
168
+			);
169
+			runOnlyForDeploymentPostprocessing = 0;
170
+		};
171
+/* End PBXResourcesBuildPhase section */
172
+
173
+/* Begin PBXSourcesBuildPhase section */
174
+		A0695AD123633D1C00CD562B /* Sources */ = {
175
+			isa = PBXSourcesBuildPhase;
176
+			buildActionMask = 2147483647;
177
+			files = (
178
+				A0069FB823A9D5E0000998B6 /* FirstTimeViewController.swift in Sources */,
179
+				A0069FB423A6C19E000998B6 /* TableWithMenuViewController.swift in Sources */,
180
+				A0695ADD23633D1C00CD562B /* HomeViewController.swift in Sources */,
181
+				A0C1E0FF2392F57900996F53 /* CustomMsgFormViewController.swift in Sources */,
182
+				A0E6269723922DA100A576B2 /* MessagesViewController.swift in Sources */,
183
+				A0C1E0F92392DB8A00996F53 /* MsgsFormViewController.swift in Sources */,
184
+				A0105E3823636493009B0E8F /* SlideInTransition.swift in Sources */,
185
+				A0EDB313237CD924006100E6 /* ContactsTableViewCell.swift in Sources */,
186
+				A08A32BA23A5B78D0036F4C9 /* ToolbarPickerView.swift in Sources */,
187
+				A0695AD923633D1C00CD562B /* AppDelegate.swift in Sources */,
188
+				A0EE435F2369FA8700ABA953 /* MainViewController.swift in Sources */,
189
+				A0EE43652374938600ABA953 /* ContactsViewController.swift in Sources */,
190
+				A0EE4361236A073D00ABA953 /* HomeNavigationController.swift in Sources */,
191
+				A0695AEF236340BF00CD562B /* MenuViewController.swift in Sources */,
192
+			);
193
+			runOnlyForDeploymentPostprocessing = 0;
194
+		};
195
+/* End PBXSourcesBuildPhase section */
196
+
197
+/* Begin PBXVariantGroup section */
198
+		A0695ADE23633D1C00CD562B /* Main.storyboard */ = {
199
+			isa = PBXVariantGroup;
200
+			children = (
201
+				A0695ADF23633D1C00CD562B /* Base */,
202
+			);
203
+			name = Main.storyboard;
204
+			sourceTree = "<group>";
205
+		};
206
+		A0695AE323633D2200CD562B /* LaunchScreen.storyboard */ = {
207
+			isa = PBXVariantGroup;
208
+			children = (
209
+				A0695AE423633D2200CD562B /* Base */,
210
+			);
211
+			name = LaunchScreen.storyboard;
212
+			sourceTree = "<group>";
213
+		};
214
+/* End PBXVariantGroup section */
215
+
216
+/* Begin XCBuildConfiguration section */
217
+		A0695AE723633D2200CD562B /* Debug */ = {
218
+			isa = XCBuildConfiguration;
219
+			buildSettings = {
220
+				ALWAYS_SEARCH_USER_PATHS = NO;
221
+				CLANG_ANALYZER_NONNULL = YES;
222
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
223
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
224
+				CLANG_CXX_LIBRARY = "libc++";
225
+				CLANG_ENABLE_MODULES = YES;
226
+				CLANG_ENABLE_OBJC_ARC = YES;
227
+				CLANG_ENABLE_OBJC_WEAK = YES;
228
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
229
+				CLANG_WARN_BOOL_CONVERSION = YES;
230
+				CLANG_WARN_COMMA = YES;
231
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
232
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
233
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
234
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
235
+				CLANG_WARN_EMPTY_BODY = YES;
236
+				CLANG_WARN_ENUM_CONVERSION = YES;
237
+				CLANG_WARN_INFINITE_RECURSION = YES;
238
+				CLANG_WARN_INT_CONVERSION = YES;
239
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
240
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
241
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
242
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
243
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
244
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
245
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
246
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
247
+				CLANG_WARN_UNREACHABLE_CODE = YES;
248
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
249
+				COPY_PHASE_STRIP = NO;
250
+				DEBUG_INFORMATION_FORMAT = dwarf;
251
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
252
+				ENABLE_TESTABILITY = YES;
253
+				GCC_C_LANGUAGE_STANDARD = gnu11;
254
+				GCC_DYNAMIC_NO_PIC = NO;
255
+				GCC_NO_COMMON_BLOCKS = YES;
256
+				GCC_OPTIMIZATION_LEVEL = 0;
257
+				GCC_PREPROCESSOR_DEFINITIONS = (
258
+					"DEBUG=1",
259
+					"$(inherited)",
260
+				);
261
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
262
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
263
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
264
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
265
+				GCC_WARN_UNUSED_FUNCTION = YES;
266
+				GCC_WARN_UNUSED_VARIABLE = YES;
267
+				IPHONEOS_DEPLOYMENT_TARGET = 13.1;
268
+				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
269
+				MTL_FAST_MATH = YES;
270
+				ONLY_ACTIVE_ARCH = YES;
271
+				SDKROOT = iphoneos;
272
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
273
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
274
+			};
275
+			name = Debug;
276
+		};
277
+		A0695AE823633D2200CD562B /* Release */ = {
278
+			isa = XCBuildConfiguration;
279
+			buildSettings = {
280
+				ALWAYS_SEARCH_USER_PATHS = NO;
281
+				CLANG_ANALYZER_NONNULL = YES;
282
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
283
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
284
+				CLANG_CXX_LIBRARY = "libc++";
285
+				CLANG_ENABLE_MODULES = YES;
286
+				CLANG_ENABLE_OBJC_ARC = YES;
287
+				CLANG_ENABLE_OBJC_WEAK = YES;
288
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
289
+				CLANG_WARN_BOOL_CONVERSION = YES;
290
+				CLANG_WARN_COMMA = YES;
291
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
292
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
293
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
294
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
295
+				CLANG_WARN_EMPTY_BODY = YES;
296
+				CLANG_WARN_ENUM_CONVERSION = YES;
297
+				CLANG_WARN_INFINITE_RECURSION = YES;
298
+				CLANG_WARN_INT_CONVERSION = YES;
299
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
300
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
301
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
302
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
303
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
304
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
305
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
306
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
307
+				CLANG_WARN_UNREACHABLE_CODE = YES;
308
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
309
+				COPY_PHASE_STRIP = NO;
310
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
311
+				ENABLE_NS_ASSERTIONS = NO;
312
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
313
+				GCC_C_LANGUAGE_STANDARD = gnu11;
314
+				GCC_NO_COMMON_BLOCKS = YES;
315
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
316
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
317
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
318
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
319
+				GCC_WARN_UNUSED_FUNCTION = YES;
320
+				GCC_WARN_UNUSED_VARIABLE = YES;
321
+				IPHONEOS_DEPLOYMENT_TARGET = 13.1;
322
+				MTL_ENABLE_DEBUG_INFO = NO;
323
+				MTL_FAST_MATH = YES;
324
+				SDKROOT = iphoneos;
325
+				SWIFT_COMPILATION_MODE = wholemodule;
326
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
327
+				VALIDATE_PRODUCT = YES;
328
+			};
329
+			name = Release;
330
+		};
331
+		A0695AEA23633D2200CD562B /* Debug */ = {
332
+			isa = XCBuildConfiguration;
333
+			buildSettings = {
334
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
335
+				CODE_SIGN_IDENTITY = "Apple Development";
336
+				CODE_SIGN_STYLE = Automatic;
337
+				DEVELOPMENT_TEAM = 4287PJE2JJ;
338
+				INFOPLIST_FILE = app/Info.plist;
339
+				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
340
+				LD_RUNPATH_SEARCH_PATHS = (
341
+					"$(inherited)",
342
+					"@executable_path/Frameworks",
343
+				);
344
+				PRODUCT_BUNDLE_IDENTIFIER = com.km0app;
345
+				PRODUCT_NAME = "$(TARGET_NAME)";
346
+				PROVISIONING_PROFILE_SPECIFIER = "";
347
+				SWIFT_VERSION = 4.2;
348
+				TARGETED_DEVICE_FAMILY = 1;
349
+			};
350
+			name = Debug;
351
+		};
352
+		A0695AEB23633D2200CD562B /* Release */ = {
353
+			isa = XCBuildConfiguration;
354
+			buildSettings = {
355
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
356
+				CODE_SIGN_IDENTITY = "Apple Development";
357
+				CODE_SIGN_STYLE = Automatic;
358
+				DEVELOPMENT_TEAM = 4287PJE2JJ;
359
+				INFOPLIST_FILE = app/Info.plist;
360
+				IPHONEOS_DEPLOYMENT_TARGET = 12.4;
361
+				LD_RUNPATH_SEARCH_PATHS = (
362
+					"$(inherited)",
363
+					"@executable_path/Frameworks",
364
+				);
365
+				PRODUCT_BUNDLE_IDENTIFIER = com.km0app;
366
+				PRODUCT_NAME = "$(TARGET_NAME)";
367
+				PROVISIONING_PROFILE_SPECIFIER = "";
368
+				SWIFT_VERSION = 4.2;
369
+				TARGETED_DEVICE_FAMILY = 1;
370
+			};
371
+			name = Release;
372
+		};
373
+/* End XCBuildConfiguration section */
374
+
375
+/* Begin XCConfigurationList section */
376
+		A0695AD023633D1C00CD562B /* Build configuration list for PBXProject "app" */ = {
377
+			isa = XCConfigurationList;
378
+			buildConfigurations = (
379
+				A0695AE723633D2200CD562B /* Debug */,
380
+				A0695AE823633D2200CD562B /* Release */,
381
+			);
382
+			defaultConfigurationIsVisible = 0;
383
+			defaultConfigurationName = Release;
384
+		};
385
+		A0695AE923633D2200CD562B /* Build configuration list for PBXNativeTarget "app" */ = {
386
+			isa = XCConfigurationList;
387
+			buildConfigurations = (
388
+				A0695AEA23633D2200CD562B /* Debug */,
389
+				A0695AEB23633D2200CD562B /* Release */,
390
+			);
391
+			defaultConfigurationIsVisible = 0;
392
+			defaultConfigurationName = Release;
393
+		};
394
+/* End XCConfigurationList section */
395
+	};
396
+	rootObject = A0695ACD23633D1C00CD562B /* Project object */;
397
+}

+ 7
- 0
app/app.xcodeproj/project.xcworkspace/contents.xcworkspacedata ファイルの表示

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

+ 8
- 0
app/app.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ファイルの表示

@@ -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>

+ 8
- 0
app/app.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ファイルの表示

@@ -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>PreviewsEnabled</key>
6
+	<false/>
7
+</dict>
8
+</plist>

バイナリ
app/app.xcodeproj/project.xcworkspace/xcuserdata/Luis.xcuserdatad/UserInterfaceState.xcuserstate ファイルの表示


+ 16
- 0
app/app.xcodeproj/project.xcworkspace/xcuserdata/Luis.xcuserdatad/WorkspaceSettings.xcsettings ファイルの表示

@@ -0,0 +1,16 @@
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>BuildLocationStyle</key>
6
+	<string>UseAppPreferences</string>
7
+	<key>CustomBuildLocationType</key>
8
+	<string>RelativeToDerivedData</string>
9
+	<key>DerivedDataLocationStyle</key>
10
+	<string>Default</string>
11
+	<key>IssueFilterStyle</key>
12
+	<string>ShowActiveSchemeOnly</string>
13
+	<key>LiveSourceIssuesEnabled</key>
14
+	<true/>
15
+</dict>
16
+</plist>

+ 6
- 0
app/app.xcodeproj/xcuserdata/Luis.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist ファイルの表示

@@ -0,0 +1,6 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Bucket
3
+   uuid = "27E77465-973C-48AE-83A5-A951C5C43EB0"
4
+   type = "1"
5
+   version = "2.0">
6
+</Bucket>

+ 14
- 0
app/app.xcodeproj/xcuserdata/Luis.xcuserdatad/xcschemes/xcschememanagement.plist ファイルの表示

@@ -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>app.xcscheme_^#shared#^_</key>
8
+		<dict>
9
+			<key>orderHint</key>
10
+			<integer>0</integer>
11
+		</dict>
12
+	</dict>
13
+</dict>
14
+</plist>

+ 44
- 0
app/app/AppDelegate.swift ファイルの表示

@@ -0,0 +1,44 @@
1
+//
2
+//  AppDelegate.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 10/25/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+@UIApplicationMain
12
+class AppDelegate: UIResponder, UIApplicationDelegate {
13
+                            
14
+    var window: UIWindow?
15
+
16
+
17
+    private func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
18
+        // Override point for customization after application launch.
19
+        return true
20
+    }
21
+
22
+    func applicationWillResignActive(_ application: UIApplication) {
23
+        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24
+        // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25
+    }
26
+
27
+    func applicationDidEnterBackground(_ application: UIApplication) {
28
+        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29
+        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30
+    }
31
+
32
+    func applicationWillEnterForeground(_ application: UIApplication) {
33
+        // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
34
+    }
35
+
36
+    func applicationDidBecomeActive(_ application: UIApplication) {
37
+        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38
+    }
39
+
40
+    func applicationWillTerminate(_ application: UIApplication) {
41
+        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42
+    }
43
+
44
+}

バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/1024.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/114.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/120.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/180.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/29.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/40.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/57.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/58.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/60.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/80.png ファイルの表示


バイナリ
app/app/Assets.xcassets/AppIcon.appiconset/87.png ファイルの表示


+ 80
- 0
app/app/Assets.xcassets/AppIcon.appiconset/Contents.json ファイルの表示

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

+ 6
- 0
app/app/Assets.xcassets/Contents.json ファイルの表示

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

+ 6
- 0
app/app/Assets.xcassets/Icons/Contents.json ファイルの表示

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

+ 21
- 0
app/app/Assets.xcassets/Icons/camera.imageset/Contents.json ファイルの表示

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

バイナリ
app/app/Assets.xcassets/Icons/camera.imageset/camera.pdf ファイルの表示


+ 21
- 0
app/app/Assets.xcassets/Icons/home.imageset/Contents.json ファイルの表示

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

バイナリ
app/app/Assets.xcassets/Icons/home.imageset/home.pdf ファイルの表示


+ 21
- 0
app/app/Assets.xcassets/Icons/menu.imageset/Contents.json ファイルの表示

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

バイナリ
app/app/Assets.xcassets/Icons/menu.imageset/menu.pdf ファイルの表示


+ 21
- 0
app/app/Assets.xcassets/Icons/profile.imageset/Contents.json ファイルの表示

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

バイナリ
app/app/Assets.xcassets/Icons/profile.imageset/profile.pdf ファイルの表示


+ 21
- 0
app/app/Assets.xcassets/icons8-checked-checkbox-50.imageset/Contents.json ファイルの表示

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

バイナリ
app/app/Assets.xcassets/icons8-checked-checkbox-50.imageset/icons8-checked-checkbox-50.png ファイルの表示


+ 21
- 0
app/app/Assets.xcassets/icons8-unchecked-checkbox-50.imageset/Contents.json ファイルの表示

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

バイナリ
app/app/Assets.xcassets/icons8-unchecked-checkbox-50.imageset/icons8-unchecked-checkbox-50.png ファイルの表示


+ 25
- 0
app/app/Base.lproj/LaunchScreen.storyboard ファイルの表示

@@ -0,0 +1,25 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
3
+    <dependencies>
4
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
5
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
6
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
7
+    </dependencies>
8
+    <scenes>
9
+        <!--View Controller-->
10
+        <scene sceneID="EHf-IW-A2E">
11
+            <objects>
12
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
13
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
14
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16
+                        <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
17
+                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
18
+                    </view>
19
+                </viewController>
20
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
21
+            </objects>
22
+            <point key="canvasLocation" x="53" y="375"/>
23
+        </scene>
24
+    </scenes>
25
+</document>

+ 1262
- 0
app/app/Base.lproj/Main.storyboard
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 26
- 0
app/app/ContactsTableViewCell.swift ファイルの表示

@@ -0,0 +1,26 @@
1
+//
2
+//  ContactsTableViewCell.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 11/13/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class ContactsTableViewCell: UITableViewCell {
12
+
13
+    override func awakeFromNib() {
14
+        super.awakeFromNib()
15
+        // Initialization code
16
+    }
17
+
18
+    override func setSelected(_ selected: Bool, animated: Bool) {
19
+        super.setSelected(selected, animated: animated)
20
+        // Configure the view for the selected state
21
+    }
22
+    
23
+    @IBOutlet weak var contactName: UILabel!
24
+    @IBOutlet weak var contactPhone: UILabel!
25
+    
26
+}

+ 44
- 0
app/app/ContactsTableViewCell.xib ファイルの表示

@@ -0,0 +1,44 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
4
+    <dependencies>
5
+        <deployment identifier="iOS"/>
6
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
7
+        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9
+    </dependencies>
10
+    <objects>
11
+        <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12
+        <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13
+        <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="ContactsTableViewCell" rowHeight="52" id="KGk-i7-Jjw" customClass="ContactsTableViewCell" customModule="app" customModuleProvider="target">
14
+            <rect key="frame" x="0.0" y="0.0" width="320" height="52"/>
15
+            <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
16
+            <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM" customClass="ContactsTableViewCell" customModule="app" customModuleProvider="target">
17
+                <rect key="frame" x="0.0" y="0.0" width="320" height="52"/>
18
+                <autoresizingMask key="autoresizingMask"/>
19
+                <subviews>
20
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="contactName" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gFY-E2-ZMb">
21
+                        <rect key="frame" x="27" y="6" width="277" height="21"/>
22
+                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
23
+                        <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
24
+                        <nil key="textColor"/>
25
+                        <nil key="highlightedColor"/>
26
+                    </label>
27
+                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="contactPhone" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vlV-UN-KID">
28
+                        <rect key="frame" x="27" y="25" width="277" height="18"/>
29
+                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
30
+                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
31
+                        <nil key="textColor"/>
32
+                        <nil key="highlightedColor"/>
33
+                    </label>
34
+                </subviews>
35
+            </tableViewCellContentView>
36
+            <viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
37
+            <connections>
38
+                <outlet property="contactName" destination="gFY-E2-ZMb" id="IMT-rF-tbf"/>
39
+                <outlet property="contactPhone" destination="vlV-UN-KID" id="iVe-ir-vzQ"/>
40
+            </connections>
41
+            <point key="canvasLocation" x="137.68115942028987" y="87.053571428571431"/>
42
+        </tableViewCell>
43
+    </objects>
44
+</document>

+ 157
- 0
app/app/ContactsViewController.swift ファイルの表示

@@ -0,0 +1,157 @@
1
+//
2
+//  ContactsViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 11/7/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+import ContactsUI
11
+
12
+class ContactsViewController: UIViewController, CNContactPickerDelegate, UITableViewDataSource, UITableViewDelegate {
13
+
14
+    let transition = SlideInTransition()
15
+    var topView: UIView?
16
+    let defaults = UserDefaults.standard
17
+    var emergency_contacts: [String:String]?
18
+    var keys: Array<String>?
19
+    
20
+    @IBOutlet weak var tableView: UITableView!
21
+
22
+    override func viewDidLoad() {
23
+        super.viewDidLoad()
24
+        // Do any additional setup after loading the view.
25
+        emergency_contacts = defaults.object(forKey: "contacts") as? [String: String] ?? [String: String]()
26
+        keys = Array(emergency_contacts!.keys)
27
+        self.tableView.register(UINib(nibName: "ContactsTableViewCell", bundle: nil), forCellReuseIdentifier: "ContactsTableViewCell")
28
+    }
29
+    
30
+    override func viewWillAppear(_ animated: Bool) {
31
+        super.viewWillAppear(animated)
32
+        self.navigationItem.hidesBackButton = true
33
+        tableView.reloadData()
34
+    }
35
+
36
+    @IBAction func didTapMenu(_ sender: UIBarButtonItem) {
37
+        guard let menuViewController = storyboard?.instantiateViewController(withIdentifier: "MenuViewController") as? MenuViewController else { return }
38
+        
39
+        menuViewController.modalPresentationStyle = .overCurrentContext
40
+        menuViewController.transitioningDelegate = self
41
+        present(menuViewController, animated: true)
42
+    }
43
+    
44
+    // MARK: - Contacts
45
+    
46
+    @IBAction func addContactsTapped(_ sender: Any) {
47
+        
48
+        let picker = CNContactPickerViewController()
49
+        picker.delegate = self
50
+        present(picker,animated: true, completion: nil)
51
+
52
+    }
53
+    
54
+    func contactPicker(_ picker: CNContactPickerViewController, didSelect contacts: [CNContact]) {
55
+        
56
+        if !defaults.contains(key: "contacts") {
57
+            emergency_contacts = [:]
58
+            defaults.set(emergency_contacts, forKey: "contacts")
59
+            print("Set empty contact list")
60
+        }
61
+        contacts.forEach { (contact) in
62
+            for phone_number in contact.phoneNumbers {
63
+                let contact_num = phone_number.value.stringValue
64
+//                var contacts_dict = defaults.object(forKey: "contacts") as? [String: String] ?? [String: String]()
65
+                
66
+                if emergency_contacts!.count >= 10 {
67
+                    print("Contact list limited to 10.")
68
+                }
69
+                    
70
+                else if emergency_contacts?[contact_num] == nil {
71
+                    // Add contact
72
+                    emergency_contacts?[contact_num] = contact.givenName
73
+                    keys = Array(emergency_contacts!.keys)
74
+                    defaults.set(emergency_contacts, forKey: "contacts")
75
+                    print("contact added")
76
+                    // Refresh table
77
+                    tableView.reloadData()
78
+                }
79
+                else {
80
+                    print("Phone number already added.")
81
+                }
82
+            }
83
+        }
84
+    }
85
+    
86
+    func contactPickerDidCancel(_ picker: CNContactPickerViewController) {
87
+        self.dismiss(animated: true, completion: nil)
88
+    }
89
+    
90
+    // MARK: - TableView
91
+    
92
+    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
93
+        return keys!.count
94
+    }
95
+    
96
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
97
+        let cell = tableView.dequeueReusableCell(withIdentifier: "ContactsTableViewCell", for: indexPath) as! ContactsTableViewCell
98
+        let phone = keys?[indexPath.row]
99
+        let name = emergency_contacts?[phone!]
100
+//        print("name: \(name)")
101
+//        print("phone: \(phone)")
102
+        cell.contactName?.text = name
103
+        cell.contactPhone?.text = phone
104
+        return cell
105
+    }
106
+    
107
+    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
108
+        return true
109
+    }
110
+    
111
+    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
112
+            if editingStyle == .delete {
113
+                let cell = tableView.cellForRow(at: indexPath) as! ContactsTableViewCell
114
+                
115
+                let phone = cell.contactPhone?.text
116
+                keys?.remove(at: indexPath.row)
117
+//                print(phone)
118
+                emergency_contacts?[phone!] = nil
119
+                defaults.set(emergency_contacts, forKey: "contacts")
120
+                tableView.beginUpdates()
121
+                tableView.deleteRows(at: [indexPath], with: .fade)
122
+                tableView.endUpdates()
123
+//                print(keys)
124
+//                print(emergency_contacts)
125
+        }
126
+    }
127
+    
128
+} // End VC
129
+
130
+// MARK: - Extensions
131
+
132
+extension ContactsViewController: UIViewControllerTransitioningDelegate {
133
+    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
134
+        transition.isPresenting = true
135
+        return transition
136
+    }
137
+    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
138
+        transition.isPresenting = false
139
+        return transition
140
+    }
141
+}
142
+
143
+extension UserDefaults {
144
+    func object<T: Codable>(_ type: T.Type, with key: String, usingDecoder decoder: JSONDecoder = JSONDecoder()) -> T? {
145
+        guard let data = self.value(forKey: key) as? Data else { return nil }
146
+        return try? decoder.decode(type.self, from: data)
147
+    }
148
+
149
+    func set<T: Codable>(object: T, forKey key: String, usingEncoder encoder: JSONEncoder = JSONEncoder()) {
150
+        let data = try? encoder.encode(object)
151
+        self.set(data, forKey: key)
152
+    }
153
+   
154
+    func contains(key: String) -> Bool {
155
+        return UserDefaults.standard.object(forKey: key) != nil
156
+    }
157
+}

+ 198
- 0
app/app/CustomMsgFormViewController.swift ファイルの表示

@@ -0,0 +1,198 @@
1
+//
2
+//  CustomMsgFormViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 11/30/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class CustomMsgFormViewController: UIViewController {
12
+
13
+        let defaults = UserDefaults.standard
14
+        var event: String?
15
+        var eventDict: [String:Any] = [:]
16
+        
17
+        fileprivate let riskLevelPicker = UIPickerView()
18
+        fileprivate let freqPicker = UIPickerView()
19
+        fileprivate let durationPicker = UIPickerView()
20
+        
21
+        let riskLevelPickerData = [String](arrayLiteral: "No veo riesgo al momento.", "Percibo un riesgo moderado.", "La situación está escalando.", "Me preocupa mi bienestar, el peligro es inminente.")
22
+        
23
+        let freqPickerData = [String](arrayLiteral: "5","10", "15","30")
24
+        let durationPickerData = [String](arrayLiteral: "1/2","1","3","5")
25
+        
26
+        @IBOutlet weak var eventTitle: UILabel!
27
+        
28
+        @IBOutlet weak var msgTextView: UITextView!
29
+        
30
+        @IBOutlet weak var riskLevelTextField: UITextField!
31
+        
32
+        @IBOutlet weak var eventDurationField: UITextField!
33
+        @IBOutlet weak var msgFreqField: UITextField!
34
+        @IBAction func locationPrefBox(_ sender: UIButton) {
35
+            sender.isSelected = !sender.isSelected
36
+        }
37
+        
38
+        @IBOutlet weak var locationPrefBtn: UIButton!
39
+        
40
+        // MARK: - Save Button
41
+        @IBAction func saveMsgForm(_ sender: Any) {
42
+            guard let msg = msgTextView.text else {
43
+                print("Missing msg in form.")
44
+                return
45
+            }
46
+            guard let riskLevel = riskLevelTextField.text else {
47
+                print("Missing riskLevel in form.")
48
+                return
49
+            }
50
+            guard let eventDuration = eventDurationField.text else {
51
+                print("Missing eventDuration in form.")
52
+                return
53
+            }
54
+            guard let msgFreq = msgFreqField.text else {
55
+                print("Missing msgFreq in form.")
56
+                return
57
+            }
58
+    //        if msgTextView.text.isEmpty || riskLevelTextField.text!.isEmpty || msgFreqField.text!.isEmpty || eventDurationField.text!.isEmpty {
59
+    //
60
+    //            // alert missing field
61
+    //
62
+    //        }
63
+            
64
+            
65
+            // Form is valid
66
+            eventDict["msg"] = msg
67
+            eventDict["riskLevel"] = riskLevel
68
+            eventDict["eventDuration"] = eventDuration
69
+            eventDict["msgFreq"] = msgFreq
70
+            eventDict["locationPref"] = !locationPrefBtn.isSelected
71
+            defaults.set(eventDict, forKey: event!)
72
+        }
73
+        
74
+        @IBOutlet weak var saveMsgFormBtn: UIButton!
75
+        
76
+        // MARK: - viewDidLoad
77
+        override func viewDidLoad() {
78
+            super.viewDidLoad()
79
+            // Do any additional setup after loading the view.
80
+
81
+            switch event {
82
+                case "deCamino":
83
+                    self.eventTitle.text = "De camino"
84
+                case "soleao":
85
+                    self.eventTitle.text = "Ando soleá/o"
86
+                case "aPie":
87
+                    self.eventTitle.text = "Me quedé a pie"
88
+                default:
89
+                    self.eventTitle.text = "Personalizado"
90
+            }
91
+            
92
+            let toolBar = UIToolbar()
93
+            toolBar.sizeToFit()
94
+            let button = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(self.action))
95
+            toolBar.setItems([button], animated: true)
96
+            toolBar.isUserInteractionEnabled = true
97
+            
98
+            
99
+            riskLevelPicker.delegate = self
100
+            riskLevelPicker.dataSource = self
101
+            durationPicker.delegate = self
102
+            durationPicker.dataSource = self
103
+            freqPicker.delegate = self
104
+            freqPicker.dataSource = self
105
+
106
+            
107
+            riskLevelTextField.inputView = riskLevelPicker
108
+            riskLevelTextField.inputAccessoryView = toolBar
109
+            eventDurationField.inputView = durationPicker
110
+            eventDurationField.inputAccessoryView = toolBar
111
+            msgFreqField.inputView = freqPicker
112
+            msgFreqField.inputAccessoryView = toolBar
113
+            
114
+            eventDict = defaults.object(forKey: event!) as? [String: Any] ?? [String: Any]()
115
+            
116
+            // by default reload and enable form
117
+            reloadAndEnableFormData()
118
+        }
119
+        
120
+        @objc func action() {
121
+              view.endEditing(true)
122
+        }
123
+        
124
+        // MARK: - Reload Form Data
125
+        func reloadAndEnableFormData() {
126
+            
127
+            // Load saved input
128
+            
129
+            eventDict = defaults.object(forKey: event!) as? [String: Any] ?? [String: Any]()
130
+            
131
+            msgTextView.text = eventDict["msg"] as? String
132
+            msgTextView.isEditable = true
133
+            
134
+            riskLevelTextField.text = eventDict["riskLevel"] as? String
135
+            riskLevelTextField.isEnabled = true
136
+            
137
+            eventDurationField.text = eventDict["eventDuration"] as? String
138
+            eventDurationField.isEnabled = true
139
+            
140
+            msgFreqField.text = eventDict["msgFreq"] as? String
141
+            msgFreqField.isEnabled = true
142
+            
143
+            locationPrefBtn.isSelected = !(eventDict["locationPref"] as! Bool)
144
+            locationPrefBtn.isEnabled = true
145
+            
146
+        }
147
+}
148
+
149
+extension CustomMsgFormViewController: UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource {
150
+    func numberOfComponents(in pickerView: UIPickerView) -> Int {
151
+        return 1
152
+    }
153
+    
154
+    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
155
+        
156
+        if pickerView == riskLevelPicker {
157
+            return riskLevelPickerData.count
158
+        }
159
+            
160
+        else if pickerView == freqPicker {
161
+            return freqPickerData.count
162
+        }
163
+        else {
164
+            return durationPickerData.count
165
+        }
166
+    }
167
+    
168
+    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
169
+        if pickerView == riskLevelPicker {
170
+            riskLevelTextField.text = riskLevelPickerData[row]
171
+        }
172
+        else if pickerView == durationPicker {
173
+            eventDurationField.text = durationPickerData[row]
174
+        }
175
+        else {
176
+            msgFreqField.text = freqPickerData[row]
177
+        }
178
+    }
179
+    
180
+    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
181
+        if pickerView == riskLevelPicker {
182
+            return riskLevelPickerData[row]
183
+        }
184
+            
185
+        else if pickerView == freqPicker {
186
+            return freqPickerData[row]
187
+        }
188
+        else {
189
+            return durationPickerData[row]
190
+        }
191
+    }
192
+    
193
+    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
194
+           let newText = (textView.text as NSString).replacingCharacters(in: range, with: text)
195
+           let numberOfChars = newText.count
196
+           return numberOfChars < 100    // 100 Limit Value
197
+       }
198
+}

+ 40
- 0
app/app/FirstTimeViewController.swift ファイルの表示

@@ -0,0 +1,40 @@
1
+//
2
+//  FirstTimeViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 12/17/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class FirstTimeViewController: UIViewController {
12
+
13
+    let emptyAlert = UIAlertController(title: "Nombre vacío", message: "Ingrese un nombre para continuar.", preferredStyle: .alert)
14
+    
15
+    
16
+    override func viewDidLoad() {
17
+        super.viewDidLoad()
18
+
19
+        // Do any additional setup after loading the view.
20
+        self.navigationController?.isNavigationBarHidden = true
21
+        emptyAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
22
+    }
23
+    
24
+    
25
+    
26
+    @IBOutlet weak var userName: UITextField!
27
+
28
+    @IBAction func saveUser(_ sender: UIButton) {
29
+        if userName.text?.isEmpty ?? true {
30
+            self.present(emptyAlert, animated: true)
31
+        } else {
32
+            let defaults = UserDefaults.standard
33
+            let user = userName.text
34
+            defaults.set(user, forKey: "user")
35
+            performSegue(withIdentifier: "firstTimeToHome", sender: self)
36
+        }
37
+    }
38
+    
39
+
40
+}

+ 20
- 0
app/app/HomeNavigationController.swift ファイルの表示

@@ -0,0 +1,20 @@
1
+//
2
+//  ViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 10/30/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class HomeNavigationController: UINavigationController {
12
+
13
+    override func viewDidLoad() {
14
+        super.viewDidLoad()
15
+
16
+//        self.navigationController?.pushViewController(HomeViewController(), animated: false)
17
+    }
18
+    
19
+
20
+}

+ 425
- 0
app/app/HomeViewController.swift ファイルの表示

@@ -0,0 +1,425 @@
1
+//
2
+//  ViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 10/25/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+import CoreLocation
11
+
12
+class HomeViewController: UIViewController {
13
+
14
+    let transition = SlideInTransition()
15
+    let locationManager = CLLocationManager()
16
+    var topView: UIView?
17
+    let defaults = UserDefaults.standard
18
+    var eventDict: [String:Any] = [:]
19
+    var msgCount: Int?
20
+    var lastLocation: CLLocationCoordinate2D?
21
+    var lastUpdateTime: Date?
22
+    var isAnyEventActivated = false
23
+    var activatedEvent: String?
24
+    var numberOfMsgsToSend: Int?
25
+    var deCaminoAlert: UIAlertController?
26
+    let emptyContactsAlert = UIAlertController(title: "No existen contactos", message: "Debe importar los contactos a los que desea que se envíen los mensajes. Esto lo puede hacer en la sección 'Contactos'", preferredStyle: .alert)
27
+    let alertaInmediata = UIAlertController(title:"Enviando alerta inmediata", message: "Se acaba de notificar a sus contactos que usted se encuentra en grave peligro.", preferredStyle: .alert)
28
+    
29
+    // MARK: - Alerts
30
+    
31
+    
32
+    @IBAction func alertaInmediata(_ sender: UIButton) {
33
+        if CLLocationManager.locationServicesEnabled() {
34
+            print("Can send alert.")
35
+        }
36
+//        se encuentra en una situación de peligro.
37
+    }
38
+    
39
+    // MARK: - De Camino
40
+    @IBOutlet weak var deCaminoBtn: UIButton!
41
+    @IBAction func deCaminoActivate(_ sender: UIButton) {
42
+        
43
+        if !defaults.contains(key: "contacts") {
44
+            self.present(emptyContactsAlert, animated: true)
45
+        }
46
+        else if !isAnyEventActivated {
47
+            // Pop alert
48
+            eventDict = defaults.object(forKey: "deCamino") as! [String : Any]
49
+            locationManager.delegate = self
50
+            locationManager.startUpdatingLocation()
51
+            isAnyEventActivated = true
52
+            activatedEvent = "De camino"
53
+            msgCount = 0
54
+            if (eventDict["eventDuration"] as! String) != "1/2" {
55
+                numberOfMsgsToSend = Int(eventDict["eventDuration"] as! String)!*60/Int(eventDict["msgFreq"] as! String)!
56
+            }
57
+            else {
58
+                numberOfMsgsToSend = 30 / Int(eventDict["msgFreq"] as! String)!
59
+            }
60
+            
61
+            deCaminoAlert = UIAlertController(title: "Alerta activada", message: "Su mensaje 'De camino' se está enviando cada \(eventDict["msgFreq"] ?? "") minutos. Usted puede extender la duración del evento o desactivarlo.", preferredStyle: .alert)
62
+
63
+            deCaminoAlert?.addAction(UIAlertAction(title: "Extender por 30 minutos", style: .default, handler: { action in
64
+                let msgFreq = self.eventDict["msgFreq"] as! Int
65
+                self.numberOfMsgsToSend! += 30/msgFreq
66
+            }))
67
+            deCaminoAlert?.addAction(UIAlertAction(title: "Desactivar", style: .cancel, handler: { action in
68
+                self.locationManager.stopUpdatingLocation()
69
+                self.locationManager.delegate = nil
70
+                self.msgCount = 0
71
+                self.isAnyEventActivated = false
72
+                print("De camino event deactivated.")
73
+            }))
74
+            
75
+            self.present(deCaminoAlert!, animated: true)
76
+
77
+        }
78
+        else if activatedEvent == "De camino" {
79
+            locationManager.stopUpdatingLocation()
80
+            locationManager.delegate = nil
81
+            msgCount = 0
82
+            isAnyEventActivated = false
83
+            deCaminoAlert?.dismiss(animated: true, completion: nil)
84
+            print("De camino event deactivated.")
85
+        }
86
+        else {
87
+            print("Event: \(String(describing: activatedEvent)) is activated.")
88
+        }
89
+        
90
+    }
91
+    
92
+    // MARK: - Me quedé a pie
93
+
94
+    @IBAction func aPie(_ sender: UIButton) {
95
+        if !defaults.contains(key: "contacts") {
96
+            self.present(emptyContactsAlert, animated: true)
97
+        }
98
+        else if !isAnyEventActivated {
99
+            // Pop alert
100
+            eventDict = defaults.object(forKey: "aPie") as! [String : Any]
101
+            locationManager.delegate = self
102
+            locationManager.startUpdatingLocation()
103
+            isAnyEventActivated = true
104
+            activatedEvent = "Me quedé a pie"
105
+            msgCount = 0
106
+            if (eventDict["eventDuration"] as! String) != "1/2" {
107
+                numberOfMsgsToSend = Int(eventDict["eventDuration"] as! String)!*60/Int(eventDict["msgFreq"] as! String)!
108
+            }
109
+            else {
110
+                numberOfMsgsToSend = 30 / Int(eventDict["msgFreq"] as! String)!
111
+            }
112
+            
113
+            deCaminoAlert = UIAlertController(title: "Alerta activada", message: "Su mensaje '\(activatedEvent ?? "")' se está enviando cada \(eventDict["msgFreq"] ?? "") minutos. Usted puede extender la duración del evento o desactivarlo.", preferredStyle: .alert)
114
+
115
+            deCaminoAlert?.addAction(UIAlertAction(title: "Extender por 30 minutos", style: .default, handler: { action in
116
+                let msgFreq = self.eventDict["msgFreq"] as! Int
117
+                self.numberOfMsgsToSend! += 30/msgFreq
118
+            }))
119
+            deCaminoAlert?.addAction(UIAlertAction(title: "Desactivar", style: .cancel, handler: { action in
120
+                self.locationManager.stopUpdatingLocation()
121
+                self.locationManager.delegate = nil
122
+                self.msgCount = 0
123
+                self.isAnyEventActivated = false
124
+                print("Me quedé a pie event deactivated.")
125
+            }))
126
+            
127
+            self.present(deCaminoAlert!, animated: true)
128
+
129
+        }
130
+        else if activatedEvent == "Me quedé a pie" {
131
+            locationManager.stopUpdatingLocation()
132
+            locationManager.delegate = nil
133
+            msgCount = 0
134
+            isAnyEventActivated = false
135
+            deCaminoAlert?.dismiss(animated: true, completion: nil)
136
+            print("Me quedé a pie event deactivated.")
137
+        }
138
+        else {
139
+            print("Event: \(String(describing: activatedEvent)) is activated.")
140
+        }
141
+    }
142
+    
143
+    // MARK: - Ando soleá/o
144
+    @IBAction func soleao(_ sender: Any) {
145
+        if !defaults.contains(key: "contacts") {
146
+            self.present(emptyContactsAlert, animated: true)
147
+        }
148
+        else if !isAnyEventActivated {
149
+            // Pop alert
150
+            eventDict = defaults.object(forKey: "soleao") as! [String : Any]
151
+            locationManager.delegate = self
152
+            locationManager.startUpdatingLocation()
153
+            isAnyEventActivated = true
154
+            activatedEvent = "Ando soleá/o"
155
+            msgCount = 0
156
+            if (eventDict["eventDuration"] as! String) != "1/2" {
157
+                numberOfMsgsToSend = Int(eventDict["eventDuration"] as! String)!*60/Int(eventDict["msgFreq"] as! String)!
158
+            }
159
+            else {
160
+                numberOfMsgsToSend = 30 / Int(eventDict["msgFreq"] as! String)!
161
+            }
162
+            
163
+            deCaminoAlert = UIAlertController(title: "Alerta activada", message: "Su mensaje '\(activatedEvent ?? "")' se está enviando cada \(eventDict["msgFreq"] ?? "") minutos. Usted puede extender la duración del evento o desactivarlo.", preferredStyle: .alert)
164
+
165
+            deCaminoAlert?.addAction(UIAlertAction(title: "Extender por 30 minutos", style: .default, handler: { action in
166
+                let msgFreq = self.eventDict["msgFreq"] as! Int
167
+                self.numberOfMsgsToSend! += 30/msgFreq
168
+            }))
169
+            deCaminoAlert?.addAction(UIAlertAction(title: "Desactivar", style: .cancel, handler: { action in
170
+                self.locationManager.stopUpdatingLocation()
171
+                self.locationManager.delegate = nil
172
+                self.msgCount = 0
173
+                self.isAnyEventActivated = false
174
+                print("Ando soleá/o event deactivated.")
175
+            }))
176
+            
177
+            self.present(deCaminoAlert!, animated: true)
178
+
179
+        }
180
+        else if activatedEvent == "Ando soleá/o" {
181
+            locationManager.stopUpdatingLocation()
182
+            locationManager.delegate = nil
183
+            msgCount = 0
184
+            isAnyEventActivated = false
185
+            deCaminoAlert?.dismiss(animated: true, completion: nil)
186
+            print("Me quedé a pie event deactivated.")
187
+        }
188
+        else {
189
+            print("Event: \(String(describing: activatedEvent)) is activated.")
190
+        }
191
+    }
192
+    
193
+    
194
+    // MARK: - Personalizado
195
+    @IBAction func customEvent(_ sender: UIButton) {
196
+        
197
+        if !defaults.contains(key: "contacts") {
198
+            return
199
+        }
200
+        else if !isAnyEventActivated {
201
+            // Pop alert
202
+            eventDict = defaults.object(forKey: "personalizado") as! [String : Any]
203
+            locationManager.delegate = self
204
+            locationManager.startUpdatingLocation()
205
+            isAnyEventActivated = true
206
+            activatedEvent = "Personalizado"
207
+            msgCount = 0
208
+            if (eventDict["eventDuration"] as! String) != "1/2" {
209
+                numberOfMsgsToSend = Int(eventDict["eventDuration"] as! String)!*60/Int(eventDict["msgFreq"] as! String)!
210
+            }
211
+            else {
212
+                numberOfMsgsToSend = 30 / Int(eventDict["msgFreq"] as! String)!
213
+            }
214
+            
215
+            deCaminoAlert = UIAlertController(title: "Alerta activada", message: "Su mensaje '\(activatedEvent ?? "")' se está enviando cada \(eventDict["msgFreq"] ?? "") minutos. Usted puede extender la duración del evento o desactivarlo.", preferredStyle: .alert)
216
+
217
+            deCaminoAlert?.addAction(UIAlertAction(title: "Extender por 30 minutos", style: .default, handler: { action in
218
+                let msgFreq = self.eventDict["msgFreq"] as! Int
219
+                self.numberOfMsgsToSend! += 30/msgFreq
220
+            }))
221
+            deCaminoAlert?.addAction(UIAlertAction(title: "Desactivar", style: .cancel, handler: { action in
222
+                self.locationManager.stopUpdatingLocation()
223
+                self.locationManager.delegate = nil
224
+                self.msgCount = 0
225
+                self.isAnyEventActivated = false
226
+                print("Personalizado event deactivated.")
227
+            }))
228
+            
229
+            self.present(deCaminoAlert!, animated: true)
230
+
231
+        }
232
+        else if activatedEvent == "Personalizado" {
233
+            locationManager.stopUpdatingLocation()
234
+            locationManager.delegate = nil
235
+            msgCount = 0
236
+            isAnyEventActivated = false
237
+            deCaminoAlert?.dismiss(animated: true, completion: nil)
238
+            print("Personalizado event deactivated.")
239
+        }
240
+        else {
241
+            print("Event: \(String(describing: activatedEvent)) is activated.")
242
+        }
243
+    }
244
+    
245
+    
246
+    // MARK: - Send Message
247
+    func sendMsg() {
248
+        let text = eventDict["msg"] as! String
249
+        let riskLevel = eventDict["riskLevel"] as! String
250
+        // let duration = eventDict["eventDuration"]
251
+        let freq = eventDict["msgFreq"] as! String
252
+        let user = defaults.string(forKey: "user")!
253
+        var msg = "Esto es un mensaje automatizado de Pa'SOS. \(user) le ha enviado una alerta:\n\(String(describing: text))\n\(String(describing: riskLevel))\nRecibirá un mensaje cada \(String(describing: freq)) minutos."
254
+        
255
+        let pref = eventDict["locationPref"] as! Bool
256
+        if pref == true {
257
+            var gmapsPin = "https://maps.google.com/?q="
258
+            gmapsPin += "\(Double(lastLocation?.latitude ?? 0)),\(Double(lastLocation?.longitude ?? 0))"
259
+            msg += "\nÚltima ubicación: " + gmapsPin
260
+        }
261
+        
262
+        print(msg)
263
+        
264
+        // to server
265
+        
266
+        
267
+        let contactsDict = defaults.object(forKey: "contacts") as? [String: String] ?? [String: String]()
268
+        let contacts = Array(contactsDict.keys)
269
+        
270
+        let parameters: [String: Any] = ["msg": msg, "contacts": contacts.description]
271
+    
272
+        //create the url with URL
273
+        let url = URL(string: "http://136.145.231.45/pasos_server.php")!
274
+
275
+        //create the session object
276
+        let session = URLSession.shared
277
+
278
+        //now create the URLRequest object using the url object
279
+        var request = URLRequest(url: url)
280
+        request.httpMethod = "POST" //set http method as POST
281
+
282
+        request.httpBody = parameters.percentEncoded()
283
+
284
+        request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
285
+
286
+//        request.log()
287
+        
288
+        //create dataTask using the session object to send data to the server
289
+        let task = session.dataTask(with: request, completionHandler: { data, response, error in
290
+
291
+            guard error == nil else {
292
+                return
293
+            }
294
+            
295
+            if let response = response {
296
+                let nsHTTPResponse = response as! HTTPURLResponse
297
+                let statusCode = nsHTTPResponse.statusCode
298
+                print ("status code = \(statusCode)")
299
+            }
300
+            
301
+        })
302
+        task.resume()
303
+    }
304
+    
305
+    override func viewDidLoad() {
306
+        super.viewDidLoad()
307
+        // Do any additional setup after loading the view.
308
+        
309
+        locationManager.delegate = self
310
+        locationManager.allowsBackgroundLocationUpdates = true
311
+        
312
+        if CLLocationManager.authorizationStatus() == .notDetermined {
313
+            locationManager.requestAlwaysAuthorization()
314
+        }
315
+        
316
+        if CLLocationManager.locationServicesEnabled() {
317
+            locationManager.desiredAccuracy = kCLLocationAccuracyBest
318
+        }
319
+        else {
320
+            print("Location services are disabled.")
321
+        }
322
+        emptyContactsAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
323
+    }
324
+    
325
+    override func viewWillAppear(_ animated: Bool) {
326
+        super.viewWillAppear(animated)
327
+        self.navigationItem.hidesBackButton = true
328
+    }
329
+
330
+    @IBAction func didTapMenu(_ sender: UIBarButtonItem) {
331
+        guard let menuViewController = storyboard?.instantiateViewController(withIdentifier: "MenuViewController") as? MenuViewController else { return }
332
+        
333
+        menuViewController.modalPresentationStyle = .overCurrentContext
334
+        menuViewController.transitioningDelegate = self
335
+        present(menuViewController, animated: true)
336
+    }
337
+    
338
+}
339
+
340
+extension HomeViewController: UIViewControllerTransitioningDelegate, CLLocationManagerDelegate {
341
+    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
342
+        transition.isPresenting = true
343
+        return transition
344
+    }
345
+    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
346
+        transition.isPresenting = false
347
+        return transition
348
+    }
349
+    // MARK: Handle Location
350
+    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
351
+        let now = Date()
352
+        // get location for first msg
353
+        if msgCount == 0 {
354
+            lastLocation = manager.location?.coordinate
355
+            lastUpdateTime = now
356
+            sendMsg()
357
+            msgCount! += 1
358
+        }
359
+        // check if freq is met
360
+        else if now.timeIntervalSince(lastUpdateTime!) >= Double(eventDict["msgFreq"] as! String)!*60 {
361
+            
362
+            locationManager.desiredAccuracy = kCLLocationAccuracyBest
363
+            do {
364
+                sleep(5)
365
+            }
366
+            // get location after some time to get better accuracy
367
+            lastLocation = manager.location?.coordinate
368
+            lastUpdateTime = now
369
+            locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
370
+            sendMsg()
371
+            msgCount! += 1
372
+            print(msgCount!)
373
+        }
374
+        
375
+        if msgCount! >= numberOfMsgsToSend! {
376
+            switch activatedEvent {
377
+            case "De camino":
378
+                deCaminoBtn.sendActions(for: .touchUpInside)
379
+            default:
380
+                break
381
+            }
382
+        }
383
+        
384
+        print(Date())
385
+    }
386
+}
387
+
388
+// DEBUG
389
+
390
+extension Data {
391
+    func toString() -> String? {
392
+        return String(data: self, encoding: .utf8)
393
+    }
394
+}
395
+
396
+extension URLRequest {
397
+    func log() {
398
+        print("\(httpMethod ?? "") \(self)")
399
+        print("BODY \n\(httpBody?.toString() ?? "")")
400
+        print("HEADERS \n\(allHTTPHeaderFields ?? [:])")
401
+    }
402
+}
403
+
404
+extension Dictionary {
405
+    func percentEncoded() -> Data? {
406
+        return map { key, value in
407
+            let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
408
+            let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
409
+            return escapedKey + "=" + escapedValue
410
+        }
411
+        .joined(separator: "&")
412
+        .data(using: .utf8)
413
+    }
414
+}
415
+
416
+extension CharacterSet {
417
+    static let urlQueryValueAllowed: CharacterSet = {
418
+        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
419
+        let subDelimitersToEncode = "!$&'()*+,;="
420
+
421
+        var allowed = CharacterSet.urlQueryAllowed
422
+        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
423
+        return allowed
424
+    }()
425
+}

+ 59
- 0
app/app/Info.plist ファイルの表示

@@ -0,0 +1,59 @@
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
+	</dict>
10
+	<key>NSLocationWhenInUseUsageDescription</key>
11
+	<string>Para enviar mensajes automatizados con su localización mientras utiliza la aplicación.</string>
12
+	<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
13
+	<string>Para enviar mensajes automatizados con su localización, necesitamos poder acceder su localización mientras no utiliza la aplicación.</string>
14
+	<key>CFBundleDevelopmentRegion</key>
15
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
16
+	<key>CFBundleExecutable</key>
17
+	<string>$(EXECUTABLE_NAME)</string>
18
+	<key>CFBundleIdentifier</key>
19
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
20
+	<key>CFBundleInfoDictionaryVersion</key>
21
+	<string>6.0</string>
22
+	<key>CFBundleName</key>
23
+	<string>$(PRODUCT_NAME)</string>
24
+	<key>CFBundlePackageType</key>
25
+	<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
26
+	<key>CFBundleShortVersionString</key>
27
+	<string>1.0</string>
28
+	<key>CFBundleVersion</key>
29
+	<string>1</string>
30
+	<key>LSRequiresIPhoneOS</key>
31
+	<true/>
32
+	<key>UIBackgroundModes</key>
33
+	<array>
34
+		<string>location</string>
35
+		<string>processing</string>
36
+	</array>
37
+	<key>UILaunchStoryboardName</key>
38
+	<string>LaunchScreen</string>
39
+	<key>UIMainStoryboardFile</key>
40
+	<string>Main</string>
41
+	<key>UIRequiredDeviceCapabilities</key>
42
+	<array>
43
+		<string>armv7</string>
44
+	</array>
45
+	<key>UISupportedInterfaceOrientations</key>
46
+	<array>
47
+		<string>UIInterfaceOrientationPortrait</string>
48
+	</array>
49
+	<key>UISupportedInterfaceOrientations~ipad</key>
50
+	<array>
51
+		<string>UIInterfaceOrientationPortrait</string>
52
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
53
+		<string>UIInterfaceOrientationLandscapeLeft</string>
54
+		<string>UIInterfaceOrientationLandscapeRight</string>
55
+	</array>
56
+	<key>UIUserInterfaceStyle</key>
57
+	<string>Light</string>
58
+</dict>
59
+</plist>

+ 94
- 0
app/app/MainViewController.swift ファイルの表示

@@ -0,0 +1,94 @@
1
+//
2
+//  MainViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 10/30/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class MainViewController: UIViewController {
12
+
13
+    
14
+    let defaults = UserDefaults.standard
15
+    var toSaveDict: [String:Any] = [:]
16
+    
17
+    
18
+    override func viewDidLoad() {
19
+        super.viewDidLoad()
20
+        setDefaults()
21
+        // Do any additional setup after loading the view.
22
+    }
23
+    
24
+    override func viewDidAppear(_ animated: Bool) {
25
+        if isAppAlreadyLaunchedOnce() {
26
+            self.performSegue(withIdentifier: "mainToHome", sender: self)
27
+        }
28
+        else {
29
+            self.performSegue(withIdentifier: "mainToFirstTime", sender: self)
30
+        }
31
+    }
32
+    
33
+    func isAppAlreadyLaunchedOnce()->Bool{
34
+        if let _ = defaults.string(forKey: "isAppAlreadyLaunchedOnce"){
35
+            return true
36
+        }else{
37
+            defaults.set(true, forKey: "isAppAlreadyLaunchedOnce")
38
+            return false
39
+        }
40
+    }
41
+    func setDefaults() {
42
+        
43
+        // Set default and first custom forms
44
+        
45
+        if !defaults.contains(key: "deCaminoDefault") {
46
+            toSaveDict["title"] = "De camino"
47
+            toSaveDict["msg"] = "Voy de regreso a casa."
48
+            toSaveDict["riskLevel"] = "No veo riesgo al momento."
49
+            toSaveDict["eventDuration"] = "1/2"
50
+            toSaveDict["msgFreq"] = "5"
51
+            toSaveDict["locationPref"] = true
52
+            defaults.set(toSaveDict, forKey: "deCaminoDefault")
53
+            defaults.set(toSaveDict, forKey: "deCamino")
54
+//            print("Set deCaminoDefault dictionary of inputs")
55
+        }
56
+        
57
+        if !defaults.contains(key: "soleaoDefault") {
58
+            toSaveDict["title"] = "Ando soleá/o"
59
+            toSaveDict["msg"] = "Estoy caminando sola/o por la calle."
60
+            toSaveDict["riskLevel"] = "No veo riesgo al momento."
61
+            toSaveDict["eventDuration"] = "1/2"
62
+            toSaveDict["msgFreq"] = "5"
63
+            toSaveDict["locationPref"] = true
64
+            defaults.set(toSaveDict, forKey: "soleaoDefault")
65
+            defaults.set(toSaveDict, forKey: "soleao")
66
+//            print("Set soleaoDefault dictionary of inputs")
67
+        }
68
+
69
+        if !defaults.contains(key: "aPieDefault") {
70
+            toSaveDict["title"] = "Estoy quedá/o"
71
+            toSaveDict["msg"] = "Me quedé a pie."
72
+            toSaveDict["riskLevel"] = "No veo riesgo al momento."
73
+            toSaveDict["eventDuration"] = "1/2"
74
+            toSaveDict["msgFreq"] = "5"
75
+            toSaveDict["locationPref"] = true
76
+            defaults.set(toSaveDict, forKey: "aPieDefault")
77
+            defaults.set(toSaveDict, forKey: "aPie")
78
+//            print("Set aPieDefault dictionary of inputs")
79
+        }
80
+        
81
+        if !defaults.contains(key: "Personalizado") {
82
+            toSaveDict["title"] = "Personalizado"
83
+            toSaveDict["msg"] = ""
84
+            toSaveDict["riskLevel"] = ""
85
+            toSaveDict["eventDuration"] = ""
86
+            toSaveDict["msgFreq"] = ""
87
+            toSaveDict["locationPref"] = true
88
+            defaults.set(toSaveDict, forKey: "personalizado")
89
+//            print("Set aPieDefault dictionary of inputs")
90
+        }
91
+    }
92
+
93
+    
94
+}

+ 52
- 0
app/app/MenuViewController.swift ファイルの表示

@@ -0,0 +1,52 @@
1
+//
2
+//  MenuViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 10/25/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+enum MenuType: Int {
12
+    case home
13
+    case contactos
14
+    case mensajes
15
+    case ajustes
16
+}
17
+
18
+class MenuViewController: UITableViewController {
19
+
20
+    var didTapMenuType: ((MenuType) -> Void)?
21
+    
22
+    override func viewDidLoad() {
23
+        super.viewDidLoad()
24
+
25
+        // Do any additional setup after loading the view.
26
+    }
27
+    
28
+    
29
+    // MARK: - Navigation
30
+    
31
+    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
32
+
33
+        let myLabel = UILabel()
34
+        myLabel.frame = CGRect(x: 20, y: 8, width: 320, height: 20)
35
+        myLabel.font = UIFont.boldSystemFont(ofSize: 18)
36
+        myLabel.text = self.tableView(tableView, titleForHeaderInSection: section)
37
+
38
+        let headerView = UIView()
39
+        headerView.addSubview(myLabel)
40
+
41
+        return headerView
42
+    }
43
+    
44
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
45
+        guard let menuType = MenuType(rawValue: indexPath.row) else { return }
46
+        
47
+        print("Dismissing: \(menuType)")
48
+        self.didTapMenuType?(menuType)
49
+        
50
+    }
51
+
52
+}

+ 60
- 0
app/app/MessagesViewController.swift ファイルの表示

@@ -0,0 +1,60 @@
1
+//
2
+//  MensajesViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 11/30/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class MessagesViewController: UIViewController {
12
+
13
+    let transition = SlideInTransition()
14
+    var topView: UIView?
15
+    
16
+    override func viewDidLoad() {
17
+        super.viewDidLoad()
18
+        // Do any additional setup after loading the view.
19
+        
20
+    }
21
+    
22
+    override func viewWillAppear(_ animated: Bool) {
23
+        super.viewWillAppear(animated)
24
+        self.navigationItem.hidesBackButton = true
25
+    }
26
+
27
+    @IBAction func didTapMenu(_ sender: UIBarButtonItem) {
28
+        guard let menuViewController = storyboard?.instantiateViewController(withIdentifier: "MenuViewController") as? MenuViewController else { return }
29
+        
30
+        menuViewController.modalPresentationStyle = .overCurrentContext
31
+        menuViewController.transitioningDelegate = self
32
+        present(menuViewController, animated: true)
33
+    }
34
+    
35
+
36
+    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
37
+        
38
+        if let formVC: MsgsFormViewController = segue.destination as? MsgsFormViewController {
39
+            formVC.event = segue.identifier
40
+            formVC.defaultEvent = (segue.identifier ?? "") + "Default"
41
+        }
42
+        else if let formVC: CustomMsgFormViewController = segue.destination as? CustomMsgFormViewController {
43
+            formVC.event = segue.identifier
44
+        }
45
+    }
46
+    
47
+}
48
+
49
+extension MessagesViewController: UIViewControllerTransitioningDelegate {
50
+    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
51
+        transition.isPresenting = true
52
+        return transition
53
+    }
54
+    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
55
+        transition.isPresenting = false
56
+        return transition
57
+    }
58
+}
59
+
60
+

+ 238
- 0
app/app/MsgsFormViewController.swift ファイルの表示

@@ -0,0 +1,238 @@
1
+//
2
+//  MessagesFormViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 11/30/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class MsgsFormViewController: UIViewController {
12
+    
13
+    
14
+    let defaults = UserDefaults.standard
15
+    var event: String?
16
+    var defaultEvent: String?
17
+    var eventDict: [String:Any] = [:]
18
+    var defaultEventDict: [String:Any] = [:]
19
+    
20
+    fileprivate let riskLevelPicker = UIPickerView()
21
+    fileprivate let freqPicker = UIPickerView()
22
+    fileprivate let durationPicker = UIPickerView()
23
+    
24
+    let riskLevelPickerData = [String](arrayLiteral: "No veo riesgo al momento.", "Percibo un riesgo moderado.", "La situación está escalando.", "Me preocupa mi bienestar, el peligro es inminente.")
25
+    
26
+    let freqPickerData = [String](arrayLiteral: "5","10", "15","30")
27
+    let durationPickerData = [String](arrayLiteral: "1/2","1","3","5")
28
+    
29
+    @IBOutlet weak var eventTitle: UILabel!
30
+    
31
+    // MARK: - Default Box
32
+    @IBAction func setDefaultBox(_ sender: UIButton) {
33
+        
34
+        sender.isSelected = !sender.isSelected
35
+
36
+//      Show default input data and disable fields
37
+        
38
+        if !sender.isSelected {
39
+            
40
+            msgTextView.text = defaultEventDict["msg"] as? String
41
+            msgTextView.isEditable = false
42
+            
43
+            riskLevelTextField.text = defaultEventDict["riskLevel"] as? String
44
+            riskLevelTextField.isEnabled = false
45
+            
46
+            eventDurationField.text = defaultEventDict["eventDuration"] as? String
47
+            eventDurationField.isEnabled = false
48
+            
49
+            msgFreqField.text = defaultEventDict["msgFreq"] as? String
50
+            msgFreqField.isEnabled = false
51
+            
52
+            locationPrefBtn.isSelected = false
53
+            locationPrefBtn.isEnabled = false
54
+            
55
+        }
56
+//      Else reload form data
57
+        else {
58
+            reloadAndEnableFormData()
59
+        }
60
+    }
61
+    @IBOutlet weak var setDefaultBtn: UIButton!
62
+    
63
+    @IBOutlet weak var msgTextView: UITextView!
64
+    
65
+    @IBOutlet weak var riskLevelTextField: UITextField!
66
+    
67
+    @IBOutlet weak var eventDurationField: UITextField!
68
+    @IBOutlet weak var msgFreqField: UITextField!
69
+    @IBAction func locationPrefBox(_ sender: UIButton) {
70
+        sender.isSelected = !sender.isSelected
71
+    }
72
+    
73
+    @IBOutlet weak var locationPrefBtn: UIButton!
74
+    
75
+    // MARK: - Save Button
76
+    @IBAction func saveMsgForm(_ sender: Any) {
77
+        guard let msg = msgTextView.text else {
78
+            print("Missing msg in form.")
79
+            return
80
+        }
81
+        guard let riskLevel = riskLevelTextField.text else {
82
+            print("Missing riskLevel in form.")
83
+            return
84
+        }
85
+        guard let eventDuration = eventDurationField.text else {
86
+            print("Missing eventDuration in form.")
87
+            return
88
+        }
89
+        guard let msgFreq = msgFreqField.text else {
90
+            print("Missing msgFreq in form.")
91
+            return
92
+        }
93
+//        if msgTextView.text.isEmpty || riskLevelTextField.text!.isEmpty || msgFreqField.text!.isEmpty || eventDurationField.text!.isEmpty {
94
+//
95
+//            // alert missing field
96
+//
97
+//        }
98
+        
99
+        
100
+        // Form is valid
101
+        eventDict["msg"] = msg
102
+        eventDict["riskLevel"] = riskLevel
103
+        eventDict["eventDuration"] = eventDuration
104
+        eventDict["msgFreq"] = msgFreq
105
+        eventDict["locationPref"] = !locationPrefBtn.isSelected
106
+        defaults.set(eventDict, forKey: event!)
107
+    }
108
+    
109
+    @IBOutlet weak var saveMsgFormBtn: UIButton!
110
+    
111
+    // MARK: - viewDidLoad
112
+    override func viewDidLoad() {
113
+        super.viewDidLoad()
114
+        // Do any additional setup after loading the view.
115
+
116
+        switch event {
117
+            case "deCamino":
118
+                self.eventTitle.text = "De camino"
119
+            case "soleao":
120
+                self.eventTitle.text = "Ando soleá/o"
121
+            case "aPie":
122
+                self.eventTitle.text = "Me quedé a pie"
123
+            default:
124
+                self.eventTitle.text = "Personalizado"
125
+        }
126
+        
127
+        let toolBar = UIToolbar()
128
+        toolBar.sizeToFit()
129
+        let button = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(self.action))
130
+        toolBar.setItems([button], animated: true)
131
+        toolBar.isUserInteractionEnabled = true
132
+        
133
+        
134
+        riskLevelPicker.delegate = self
135
+        riskLevelPicker.dataSource = self
136
+        durationPicker.delegate = self
137
+        durationPicker.dataSource = self
138
+        freqPicker.delegate = self
139
+        freqPicker.dataSource = self
140
+        
141
+        riskLevelTextField.inputView = riskLevelPicker
142
+        riskLevelTextField.inputAccessoryView = toolBar
143
+        eventDurationField.inputView = durationPicker
144
+        eventDurationField.inputAccessoryView = toolBar
145
+        msgFreqField.inputView = freqPicker
146
+        msgFreqField.inputAccessoryView = toolBar
147
+        
148
+        defaultEventDict = defaults.object(forKey: defaultEvent!) as? [String: Any] ?? [String: Any]()
149
+        
150
+        eventDict = defaults.object(forKey: event!) as? [String: Any] ?? [String: Any]()
151
+        
152
+        setDefaultBtn.isSelected = true
153
+        
154
+        // by default reload and enable form
155
+        reloadAndEnableFormData()
156
+    }
157
+    
158
+    @objc func action() {
159
+          view.endEditing(true)
160
+    }
161
+    
162
+    // MARK: - Reload Form Data
163
+    func reloadAndEnableFormData() {
164
+        
165
+        // Load saved input
166
+        
167
+        eventDict = defaults.object(forKey: event!) as? [String: Any] ?? [String: Any]()
168
+        
169
+        msgTextView.text = eventDict["msg"] as? String
170
+        msgTextView.isEditable = true
171
+        
172
+        riskLevelTextField.text = eventDict["riskLevel"] as? String
173
+        riskLevelTextField.isEnabled = true
174
+        
175
+        eventDurationField.text = eventDict["eventDuration"] as? String
176
+        eventDurationField.isEnabled = true
177
+        
178
+        msgFreqField.text = eventDict["msgFreq"] as? String
179
+        msgFreqField.isEnabled = true
180
+        
181
+        locationPrefBtn.isSelected = !(eventDict["locationPref"] as! Bool)
182
+        locationPrefBtn.isEnabled = true
183
+        
184
+    }
185
+    
186
+}
187
+
188
+extension MsgsFormViewController: UITextViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource {
189
+    func numberOfComponents(in pickerView: UIPickerView) -> Int {
190
+        return 1
191
+    }
192
+    
193
+    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
194
+        
195
+        if pickerView == riskLevelPicker {
196
+            return riskLevelPickerData.count
197
+        }
198
+            
199
+        else if pickerView == freqPicker {
200
+            return freqPickerData.count
201
+        }
202
+        else {
203
+            return durationPickerData.count
204
+        }
205
+    }
206
+    
207
+    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
208
+        if pickerView == riskLevelPicker {
209
+            riskLevelTextField.text = riskLevelPickerData[row]
210
+        }
211
+        else if pickerView == durationPicker {
212
+            eventDurationField.text = durationPickerData[row]
213
+        }
214
+        else {
215
+            msgFreqField.text = freqPickerData[row]
216
+        }
217
+    }
218
+    
219
+    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
220
+        if pickerView == riskLevelPicker {
221
+            return riskLevelPickerData[row]
222
+        }
223
+            
224
+        else if pickerView == freqPicker {
225
+            return freqPickerData[row]
226
+        }
227
+        else {
228
+            return durationPickerData[row]
229
+        }
230
+    }
231
+    
232
+    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
233
+           let newText = (textView.text as NSString).replacingCharacters(in: range, with: text)
234
+           let numberOfChars = newText.count
235
+           return numberOfChars < 100    // 100 Limit Value
236
+       }
237
+}
238
+

+ 66
- 0
app/app/SlideInTransition.swift ファイルの表示

@@ -0,0 +1,66 @@
1
+//
2
+//  SlideInTransition.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 10/25/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class SlideInTransition: NSObject, UIViewControllerAnimatedTransitioning {
12
+    
13
+    var isPresenting = false
14
+    let dimmingView = UIView()
15
+    
16
+    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
17
+        return 0.3
18
+    }
19
+    
20
+    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
21
+        
22
+        guard let toViewController = transitionContext.viewController(forKey: .to),
23
+            let fromViewController = transitionContext.viewController(forKey: .from) else { return }
24
+        
25
+        let containerView = transitionContext.containerView
26
+        
27
+        let finalWidth = toViewController.view.bounds.width * 0.8
28
+        let finalHeight = toViewController.view.bounds.height
29
+        
30
+        if isPresenting {
31
+            
32
+            // Add dimming view
33
+            dimmingView.backgroundColor = .black
34
+            dimmingView.alpha = 0.0
35
+            containerView.addSubview(dimmingView)
36
+            dimmingView.frame = containerView.bounds
37
+            
38
+            // Add menu view controller to container
39
+            containerView.addSubview(toViewController.view)
40
+            
41
+            // Init fram off the screen
42
+            toViewController.view.frame = CGRect(x: -finalWidth, y: 0, width: finalWidth, height: finalHeight)
43
+        }
44
+        
45
+        // Animate on screen
46
+        let transform = {
47
+            self.dimmingView.alpha = 0.5
48
+            toViewController.view.transform = CGAffineTransform(translationX: finalWidth, y: 0)
49
+        }
50
+        
51
+        // Animate off screen
52
+        let identity = {
53
+            self.dimmingView.alpha = 0.0
54
+            fromViewController.view.transform = .identity
55
+        }
56
+        
57
+        let duration = transitionDuration(using: transitionContext)
58
+        let isCancelled = transitionContext.transitionWasCancelled
59
+        UIView.animate(withDuration: duration, animations: {
60
+            self.isPresenting ? transform() : identity()
61
+        }) { (_) in
62
+            transitionContext.completeTransition(!isCancelled)
63
+        }
64
+        
65
+    }
66
+}

+ 44
- 0
app/app/TableWithMenuViewController.swift ファイルの表示

@@ -0,0 +1,44 @@
1
+//
2
+//  TableWithMenuViewController.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 12/15/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import UIKit
10
+
11
+class TableWithMenuViewController: UITableViewController {
12
+
13
+    let transition = SlideInTransition()
14
+    
15
+    override func viewDidLoad() {
16
+        super.viewDidLoad()
17
+        self.navigationItem.hidesBackButton = true
18
+        // Uncomment the following line to preserve selection between presentations
19
+        // self.clearsSelectionOnViewWillAppear = false
20
+
21
+        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
22
+        // self.navigationItem.rightBarButtonItem = self.editButtonItem
23
+    }
24
+
25
+   @IBAction func didTapMenu(_ sender: UIBarButtonItem) {
26
+       guard let menuViewController = storyboard?.instantiateViewController(withIdentifier: "MenuViewController") as? MenuViewController else { return }
27
+       
28
+       menuViewController.modalPresentationStyle = .overCurrentContext
29
+       menuViewController.transitioningDelegate = self
30
+       present(menuViewController, animated: true)
31
+   }
32
+
33
+}
34
+
35
+extension TableWithMenuViewController: UIViewControllerTransitioningDelegate {
36
+    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
37
+        transition.isPresenting = true
38
+        return transition
39
+    }
40
+    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
41
+        transition.isPresenting = false
42
+        return transition
43
+    }
44
+}

+ 56
- 0
app/app/ToolbarPickerView.swift ファイルの表示

@@ -0,0 +1,56 @@
1
+//
2
+//  ToolbarPickerView.swift
3
+//  app
4
+//
5
+//  Created by Luis Quiñones  on 12/14/19.
6
+//  Copyright © 2019 Luis Quiñones . All rights reserved.
7
+//
8
+
9
+import Foundation
10
+import UIKit
11
+
12
+protocol ToolbarPickerViewDelegate: class {
13
+    func didTapDone()
14
+    func didTapCancel()
15
+}
16
+
17
+class ToolbarPickerView: UIPickerView {
18
+
19
+    public private(set) var toolbar: UIToolbar?
20
+    public weak var toolbarDelegate: ToolbarPickerViewDelegate?
21
+
22
+    override init(frame: CGRect) {
23
+        super.init(frame: frame)
24
+        self.commonInit()
25
+    }
26
+
27
+    required init?(coder aDecoder: NSCoder) {
28
+        super.init(coder: aDecoder)
29
+        self.commonInit()
30
+    }
31
+
32
+    private func commonInit() {
33
+        let toolBar = UIToolbar()
34
+        toolBar.barStyle = UIBarStyle.default
35
+        toolBar.isTranslucent = true
36
+        toolBar.tintColor = .black
37
+        toolBar.sizeToFit()
38
+
39
+        let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(self.doneTapped))
40
+        let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
41
+        let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(self.cancelTapped))
42
+
43
+        toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
44
+        toolBar.isUserInteractionEnabled = true
45
+
46
+        self.toolbar = toolBar
47
+    }
48
+
49
+    @objc func doneTapped() {
50
+        self.toolbarDelegate?.didTapDone()
51
+    }
52
+
53
+    @objc func cancelTapped() {
54
+        self.toolbarDelegate?.didTapCancel()
55
+    }
56
+}

バイナリ
app/app/icons8-unchecked-checkbox-50.png ファイルの表示