Przeglądaj źródła

Added profile view, localizations, connection with member.php

Hector Carrion 4 lat temu
rodzic
commit
8c25391de4

+ 37
- 4
Comedores Sociales.xcodeproj/project.pbxproj Wyświetl plik

@@ -23,7 +23,11 @@
23 23
 		356533A525449C3900F2F0DE /* Comedores_SocialesUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356533A425449C3900F2F0DE /* Comedores_SocialesUITests.swift */; };
24 24
 		356533B92544A1AF00F2F0DE /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356533B82544A1AF00F2F0DE /* LoginView.swift */; };
25 25
 		356533C42544B1FF00F2F0DE /* RegisterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 356533C32544B1FF00F2F0DE /* RegisterView.swift */; };
26
+		3580269E258A5A2E00CDE7AE /* ProfileSummary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3580269D258A5A2E00CDE7AE /* ProfileSummary.swift */; };
27
+		358026A6258A5BB700CDE7AE /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358026A5258A5BB700CDE7AE /* ProfileModel.swift */; };
28
+		358026AB258A5DA600CDE7AE /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358026AA258A5DA600CDE7AE /* ProfileView.swift */; };
26 29
 		358E94702558AC130074E928 /* NetworkingModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 358E946F2558AC130074E928 /* NetworkingModel.swift */; };
30
+		35CAE51C258B9B7800FC27A6 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 35CAE51E258B9B7800FC27A6 /* Localizable.strings */; };
27 31
 /* End PBXBuildFile section */
28 32
 
29 33
 /* Begin PBXContainerItemProxy section */
@@ -66,7 +70,13 @@
66 70
 		356533A625449C3900F2F0DE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
67 71
 		356533B82544A1AF00F2F0DE /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
68 72
 		356533C32544B1FF00F2F0DE /* RegisterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterView.swift; sourceTree = "<group>"; };
73
+		3580269D258A5A2E00CDE7AE /* ProfileSummary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileSummary.swift; sourceTree = "<group>"; };
74
+		358026A5258A5BB700CDE7AE /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = "<group>"; };
75
+		358026AA258A5DA600CDE7AE /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
69 76
 		358E946F2558AC130074E928 /* NetworkingModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkingModel.swift; sourceTree = "<group>"; };
77
+		35CAE51D258B9B7800FC27A6 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
78
+		35CAE51F258B9BC600FC27A6 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
79
+		35CAE520258B9C2A00FC27A6 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
70 80
 /* End PBXFileReference section */
71 81
 
72 82
 /* Begin PBXFrameworksBuildPhase section */
@@ -97,10 +107,12 @@
97 107
 		350E75972575672F006B17CC /* View */ = {
98 108
 			isa = PBXGroup;
99 109
 			children = (
110
+				350E75BB257576BD006B17CC /* CustomNavigationView.swift */,
100 111
 				350E75B325757572006B17CC /* FoodCardView.swift */,
112
+				3580269D258A5A2E00CDE7AE /* ProfileSummary.swift */,
113
+				358026AA258A5DA600CDE7AE /* ProfileView.swift */,
101 114
 				350E759D257567F2006B17CC /* Search */,
102
-				350E759C257567E4006B17CC /* Login */,
103
-				350E75BB257576BD006B17CC /* CustomNavigationView.swift */,
115
+				350E759C257567E4006B17CC /* Auth */,
104 116
 			);
105 117
 			path = View;
106 118
 			sourceTree = "<group>";
@@ -112,17 +124,18 @@
112 124
 				3565338B25449C3800F2F0DE /* Persistence.swift */,
113 125
 				350E75AE2575687C006B17CC /* FoodItem.swift */,
114 126
 				350E75CC25758D92006B17CC /* UserSettings.swift */,
127
+				358026A5258A5BB700CDE7AE /* ProfileModel.swift */,
115 128
 			);
116 129
 			path = Model;
117 130
 			sourceTree = "<group>";
118 131
 		};
119
-		350E759C257567E4006B17CC /* Login */ = {
132
+		350E759C257567E4006B17CC /* Auth */ = {
120 133
 			isa = PBXGroup;
121 134
 			children = (
122 135
 				356533B82544A1AF00F2F0DE /* LoginView.swift */,
123 136
 				356533C32544B1FF00F2F0DE /* RegisterView.swift */,
124 137
 			);
125
-			path = Login;
138
+			path = Auth;
126 139
 			sourceTree = "<group>";
127 140
 		};
128 141
 		350E759D257567F2006B17CC /* Search */ = {
@@ -137,6 +150,7 @@
137 150
 		3565337625449C3600F2F0DE = {
138 151
 			isa = PBXGroup;
139 152
 			children = (
153
+				35CAE51E258B9B7800FC27A6 /* Localizable.strings */,
140 154
 				3565338125449C3600F2F0DE /* Comedores Sociales */,
141 155
 				3565339825449C3800F2F0DE /* Comedores SocialesTests */,
142 156
 				356533A325449C3900F2F0DE /* Comedores SocialesUITests */,
@@ -280,6 +294,8 @@
280 294
 			knownRegions = (
281 295
 				en,
282 296
 				Base,
297
+				es,
298
+				"es-419",
283 299
 			);
284 300
 			mainGroup = 3565337625449C3600F2F0DE;
285 301
 			productRefGroup = 3565338025449C3600F2F0DE /* Products */;
@@ -299,6 +315,7 @@
299 315
 			buildActionMask = 2147483647;
300 316
 			files = (
301 317
 				3565338A25449C3800F2F0DE /* Preview Assets.xcassets in Resources */,
318
+				35CAE51C258B9B7800FC27A6 /* Localizable.strings in Resources */,
302 319
 				3565338725449C3800F2F0DE /* Assets.xcassets in Resources */,
303 320
 			);
304 321
 			runOnlyForDeploymentPostprocessing = 0;
@@ -324,6 +341,7 @@
324 341
 			isa = PBXSourcesBuildPhase;
325 342
 			buildActionMask = 2147483647;
326 343
 			files = (
344
+				358026A6258A5BB700CDE7AE /* ProfileModel.swift in Sources */,
327 345
 				358E94702558AC130074E928 /* NetworkingModel.swift in Sources */,
328 346
 				356533B92544A1AF00F2F0DE /* LoginView.swift in Sources */,
329 347
 				3565338C25449C3800F2F0DE /* Persistence.swift in Sources */,
@@ -331,11 +349,13 @@
331 349
 				350E75CD25758D92006B17CC /* UserSettings.swift in Sources */,
332 350
 				350E75AF2575687C006B17CC /* FoodItem.swift in Sources */,
333 351
 				350E75C125757B24006B17CC /* SearchContainer.swift in Sources */,
352
+				358026AB258A5DA600CDE7AE /* ProfileView.swift in Sources */,
334 353
 				356533C42544B1FF00F2F0DE /* RegisterView.swift in Sources */,
335 354
 				350E75AA25756829006B17CC /* Search.swift in Sources */,
336 355
 				3565338F25449C3800F2F0DE /* Comedores_Sociales.xcdatamodeld in Sources */,
337 356
 				350E75BC257576BD006B17CC /* CustomNavigationView.swift in Sources */,
338 357
 				350E75B425757572006B17CC /* FoodCardView.swift in Sources */,
358
+				3580269E258A5A2E00CDE7AE /* ProfileSummary.swift in Sources */,
339 359
 				3565338325449C3600F2F0DE /* Comedores_SocialesApp.swift in Sources */,
340 360
 			);
341 361
 			runOnlyForDeploymentPostprocessing = 0;
@@ -371,6 +391,19 @@
371 391
 		};
372 392
 /* End PBXTargetDependency section */
373 393
 
394
+/* Begin PBXVariantGroup section */
395
+		35CAE51E258B9B7800FC27A6 /* Localizable.strings */ = {
396
+			isa = PBXVariantGroup;
397
+			children = (
398
+				35CAE51D258B9B7800FC27A6 /* en */,
399
+				35CAE51F258B9BC600FC27A6 /* es */,
400
+				35CAE520258B9C2A00FC27A6 /* es-419 */,
401
+			);
402
+			name = Localizable.strings;
403
+			sourceTree = "<group>";
404
+		};
405
+/* End PBXVariantGroup section */
406
+
374 407
 /* Begin XCBuildConfiguration section */
375 408
 		356533A725449C3900F2F0DE /* Debug */ = {
376 409
 			isa = XCBuildConfiguration;

+ 3
- 1
Comedores Sociales/ContentView.swift Wyświetl plik

@@ -10,10 +10,12 @@ import SwiftUI
10 10
 struct ContentView: View {
11 11
     @StateObject var lvm = LoginViewModel()
12 12
     @StateObject var rvm = RegisterViewModel()
13
+    @StateObject var pvm = ProfileViewModel()
13 14
 
14 15
     var body: some View {
15
-        if lvm.response ?? false || rvm.response ?? false {
16
+        if lvm.response == .success || rvm.response == .success || UserDefaults.standard.bool(forKey: "logged-in") {
16 17
             SearchContainer()
18
+                .environmentObject(pvm)
17 19
         } else {
18 20
             LoginView()
19 21
                 .environmentObject(lvm)

+ 159
- 9
Comedores Sociales/Model/NetworkingModel.swift Wyświetl plik

@@ -10,16 +10,16 @@ import Foundation
10 10
 let serverAddress: String = "http://161.35.60.201/"
11 11
 
12 12
 class Authentication: Codable {
13
-    var username: String
13
+    var correo: String
14 14
     var password: String
15 15
     
16 16
     init(email: String, password: String) {
17
-        self.username = email
17
+        self.correo = email
18 18
         self.password = password
19 19
     }
20 20
     
21 21
     func isComplete() -> Bool {
22
-        if username != "" && password != "" {
22
+        if correo != "" && password != "" {
23 23
             return true
24 24
         } else {
25 25
             return false
@@ -78,17 +78,58 @@ class Registration: Codable {
78 78
     }
79 79
 }
80 80
 
81
+enum Response {
82
+    case success
83
+    case failure
84
+    case loading
85
+    case unfetched
86
+}
87
+
88
+struct UserToken: Codable {
89
+    let token: String
90
+}
91
+
92
+func getToken(input: String) -> UserToken {
93
+    let JSONData = input.data(using: .utf8)!
94
+    let token: UserToken = try! JSONDecoder().decode(UserToken.self, from: JSONData)
95
+    return token
96
+}
97
+
98
+struct UserDetails: Codable {
99
+    let correo: String
100
+    let nombre: String
101
+    let organizacion: String?
102
+    let puesto: String?
103
+    let calle: String
104
+    let pueblo: String
105
+    let cpostal: String
106
+    let telefono: String
107
+    let membresia: String?
108
+    let vigencia: String?
109
+    let horas_trabajadas: String?
110
+}
111
+
112
+func getUserDetails(input: String) -> UserDetails {
113
+    let JSONData = input.data(using: .utf8)!
114
+    let info: UserDetails = try! JSONDecoder().decode(UserDetails.self, from: JSONData)
115
+    return info
116
+}
81 117
 
82 118
 class LoginViewModel: ObservableObject {
83
-    @Published var response: Bool?
119
+    @Published var response: Response = .unfetched
84 120
     
85 121
     func changeResponse(response: Bool) {
86
-        self.response = response
122
+        if response == true {
123
+            self.response = .success
124
+        } else {
125
+            self.response = .failure
126
+        }
87 127
     }
88 128
     
89 129
     func login(data: Authentication) {
130
+        self.response = .loading
90 131
         
91
-        let service = "login.php"
132
+        let service = "api/login.php"
92 133
         guard let encodedData = try? JSONEncoder().encode(data)
93 134
         else {
94 135
             print("Failed to encode login request")
@@ -119,6 +160,13 @@ class LoginViewModel: ObservableObject {
119 160
                 DispatchQueue.main.async {
120 161
                     if httpResponse.statusCode == 200 {
121 162
                         self.changeResponse(response: true)
163
+                        // MARK: JSON Decoding
164
+                        let dataString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
165
+                        let json = getToken(input: dataString)
166
+                        
167
+                        let defaults = UserDefaults.standard
168
+                        defaults.set(json.token, forKey: "token")
169
+                        defaults.set(true, forKey: "logged-in")
122 170
                     } else {
123 171
                         self.changeResponse(response: false)
124 172
                     }
@@ -131,13 +179,18 @@ class LoginViewModel: ObservableObject {
131 179
 }
132 180
 
133 181
 class RegisterViewModel: ObservableObject {
134
-    @Published var response: Bool?
182
+    @Published var response: Response = .unfetched
135 183
     
136 184
     func changeResponse(response: Bool) {
137
-        self.response = response
185
+        if response == true {
186
+            self.response = .success
187
+        } else {
188
+            self.response = .failure
189
+        }
138 190
     }
139 191
     
140 192
     func register(data: Registration) {
193
+        self.response = .loading
141 194
         
142 195
         let service = "signup.php"
143 196
         guard let encodedData = try? JSONEncoder().encode(data)
@@ -146,7 +199,7 @@ class RegisterViewModel: ObservableObject {
146 199
             return
147 200
         }
148 201
         
149
-        print("Reg JSON to send:", String(data: encodedData, encoding: .utf8)!)
202
+        //print("Reg JSON to send:", String(data: encodedData, encoding: .utf8)!)
150 203
         
151 204
         let url = URL(string: serverAddress + service)!
152 205
         var request = URLRequest(url: url)
@@ -178,3 +231,100 @@ class RegisterViewModel: ObservableObject {
178 231
         }.resume()
179 232
     }
180 233
 }
234
+
235
+
236
+class ProfileViewModel: ObservableObject {
237
+    @Published var profile: Profile = .default
238
+    @Published var response: Response = .unfetched
239
+    
240
+    func changeResponse(response: Bool) {
241
+        if response == true {
242
+            self.response = .success
243
+        } else {
244
+            self.response = .failure
245
+        }
246
+    }
247
+    
248
+    func changeProfile(data: UserDetails) {
249
+        self.profile.name = data.nombre
250
+        self.profile.email = data.correo
251
+        self.profile.phone = addDashes(phone: data.telefono)
252
+        
253
+        if data.membresia != "" {
254
+            self.profile.membership = data.membresia ?? "No memebership recieved from server"
255
+        }
256
+        if (data.vigencia != nil) {
257
+            self.profile.membershipStatus = .valid
258
+        } else {
259
+            self.profile.membershipStatus = .expired
260
+        }
261
+        
262
+        if let hours = data.horas_trabajadas {
263
+            self.profile.hoursWorked = Int(hours) ?? 0
264
+        }
265
+        
266
+    }
267
+    
268
+    func fetchMember(token: UserToken) {
269
+        self.response = .loading
270
+        
271
+        let service = "api/member.php"
272
+        guard let encodedData = try? JSONEncoder().encode(token)
273
+        else {
274
+            print("Failed to encode login request")
275
+            return
276
+        }
277
+        
278
+        //print("JSON to send:", String(data: encodedData, encoding: .utf8)!)
279
+        
280
+        let url = URL(string: serverAddress + service)!
281
+        var request = URLRequest(url: url)
282
+        request.setValue("appmember/json", forHTTPHeaderField: "Content-Type")
283
+        request.httpMethod = "POST"
284
+        request.httpBody = encodedData
285
+        
286
+        URLSession.shared.dataTask(with: request) {data, response, error in
287
+            
288
+            //handle result here
289
+            guard let data = data
290
+            else {
291
+                print("No data in response: \(error?.localizedDescription ?? "Unknown error").")
292
+                return
293
+            }
294
+            print(response ?? "No response")
295
+            print("Recieved data:", data)
296
+            print(error ?? "No error")
297
+            
298
+            
299
+            if let httpResponse = response as? HTTPURLResponse {
300
+                DispatchQueue.main.async {
301
+                    if httpResponse.statusCode == 200 {
302
+                        // MARK: JSON Decoding
303
+                        let dataString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
304
+                        let json = getUserDetails(input: dataString)
305
+                        //print(json)
306
+                        self.changeResponse(response: true)
307
+                        self.changeProfile(data: json)
308
+                    } else {
309
+                        self.changeResponse(response: false)
310
+                    }
311
+                    
312
+                }
313
+            }
314
+            
315
+        }.resume()
316
+    }
317
+}
318
+
319
+func addDashes(phone: String) -> String {
320
+    var prettyPhone: String = ""
321
+    var i = 0
322
+    for number in phone {
323
+        prettyPhone.append(number)
324
+        if i == 2 || i == 5 {
325
+            prettyPhone.append("-")
326
+        }
327
+        i += 1
328
+    }
329
+    return prettyPhone
330
+}

+ 25
- 0
Comedores Sociales/Model/ProfileModel.swift Wyświetl plik

@@ -0,0 +1,25 @@
1
+//
2
+//  ProfileModel.swift
3
+//  Comedores Sociales
4
+//
5
+//  Created by Hector Carrion on 12/16/20.
6
+//
7
+
8
+import Foundation
9
+
10
+struct Profile {
11
+    var name: String
12
+    var email: String
13
+    var phone: String
14
+    var membership: String
15
+    var membershipStatus = membership.undetermined
16
+    var hoursWorked: Int = 0
17
+
18
+    static let `default` = Profile(name: "Comedores Sociales", email: "user@comedores.com", phone: "787-555-5555", membership: "Loading...", membershipStatus: membership.undetermined, hoursWorked: 0)
19
+    
20
+    enum membership: String {
21
+        case valid = "Valid"
22
+        case expired = "Expired"
23
+        case undetermined = "Unknown"
24
+    }
25
+}

Comedores Sociales/View/Login/LoginView.swift → Comedores Sociales/View/Auth/LoginView.swift Wyświetl plik

@@ -22,14 +22,17 @@ struct LoginView: View {
22 22
             EmailField(email: $email)
23 23
             PasswordField(password: $password)
24 24
             
25
-            if loginVM.response == false {
26
-                Text("Information incorrect, please try again")
25
+            if loginVM.response == .failure {
26
+                Text(NSLocalizedString("incorrect", comment:""))
27 27
                     .offset(y: -10)
28 28
                     .foregroundColor(.red)
29
-            } else if loginVM.response == true {
29
+            } else if loginVM.response == .success {
30 30
                 Text("Login successful 🎉")
31 31
                     .offset(y: -10)
32 32
                     .foregroundColor(.green)
33
+            } else if loginVM.response == .loading {
34
+                ProgressView("Loading…")
35
+                    .padding()
33 36
             }
34 37
             
35 38
             Button(action: {
@@ -50,7 +53,7 @@ struct LoginView: View {
50 53
             }
51 54
             .sheet(isPresented: $showingRegister, content: {RegisterView()})
52 55
             .alert(isPresented: $showingAlert) {
53
-                Alert(title: Text("Fields incomplete"), message: Text("Please enter a valid email and password"), dismissButton: .default(Text("OK")))
56
+                Alert(title: Text(NSLocalizedString("incomplete", comment:"")), message: Text(NSLocalizedString("enter_valid", comment:"")), dismissButton: .default(Text("OK")))
54 57
                 
55 58
             }
56 59
         }
@@ -82,7 +85,7 @@ struct WelcomeImage: View {
82 85
 
83 86
 struct LoginText: View {
84 87
     var body: some View {
85
-        Text("Login")
88
+        Text(NSLocalizedString("login", comment:""))
86 89
             .font(.headline)
87 90
             .foregroundColor(.white)
88 91
             .padding()
@@ -95,7 +98,7 @@ struct LoginText: View {
95 98
 
96 99
 struct RegisterText: View {
97 100
     var body: some View {
98
-        Text("Don't have an account? Register")
101
+        Text(NSLocalizedString("register", comment:""))
99 102
             .font(.headline)
100 103
             .foregroundColor(.gray)
101 104
             .padding()
@@ -110,6 +113,7 @@ struct EmailField: View {
110 113
     @Environment(\.colorScheme) var colorScheme
111 114
     var body: some View {
112 115
         TextField("Email", text: $email)
116
+            .disableAutocorrection(true)
113 117
             .autocapitalization(.none)
114 118
             .padding()
115 119
             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)

Comedores Sociales/View/Login/RegisterView.swift → Comedores Sociales/View/Auth/RegisterView.swift Wyświetl plik

@@ -28,7 +28,7 @@ struct RegisterView: View {
28 28
     
29 29
     var body: some View {
30 30
         VStack {
31
-            Text("Details")
31
+            Text(NSLocalizedString("details", comment:""))
32 32
                 .font(.largeTitle)
33 33
                 .fontWeight(.semibold)
34 34
                 .frame(width: 350, height: 25, alignment: .topLeading)
@@ -37,7 +37,7 @@ struct RegisterView: View {
37 37
             ScrollView {
38 38
                 Group {
39 39
                     Group {
40
-                        TextField("Full Name", text: $name)
40
+                        TextField(NSLocalizedString("full_name", comment:""), text: $name)
41 41
                             .autocapitalization(.words)
42 42
                             .padding()
43 43
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
@@ -56,50 +56,50 @@ struct RegisterView: View {
56 56
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
57 57
                             .cornerRadius(5.0)
58 58
                             .padding(.bottom, 5)
59
-                        SecureField("Password (confirmation)", text: $passwordConf)
59
+                        SecureField(NSLocalizedString("pass_conf", comment:""), text: $passwordConf)
60 60
                             .autocapitalization(.none)
61 61
                             .padding()
62 62
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
63 63
                             .cornerRadius(5.0)
64 64
                             .padding(.bottom, 5)
65 65
                     }
66
-                        TextField("Phone Number", text: $phoneNumber)
66
+                        TextField(NSLocalizedString("phone", comment:""), text: $phoneNumber)
67 67
                             .keyboardType(.numberPad)
68 68
                             .padding()
69 69
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
70 70
                             .cornerRadius(5.0)
71 71
                             .padding(.bottom, 5)
72
-                        TextField("Urbanization", text: $urb)
72
+                        TextField(NSLocalizedString("urb", comment:""), text: $urb)
73 73
                             .padding()
74 74
                             .autocapitalization(.words)
75 75
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
76 76
                             .cornerRadius(5.0)
77 77
                             .padding(.bottom, 5)
78
-                        TextField("Street", text: $street)
78
+                        TextField(NSLocalizedString("street", comment:""), text: $street)
79 79
                             .autocapitalization(.words)
80 80
                             .padding()
81 81
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
82 82
                             .cornerRadius(5.0)
83 83
                             .padding(.bottom, 5)
84
-                        TextField("City", text: $city)
84
+                        TextField(NSLocalizedString("city", comment:""), text: $city)
85 85
                             .autocapitalization(.words)
86 86
                             .padding()
87 87
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
88 88
                             .cornerRadius(5.0)
89 89
                             .padding(.bottom, 5)
90
-                        TextField("Zip Code", text: $zip)
90
+                        TextField(NSLocalizedString("zip", comment:""), text: $zip)
91 91
                             .keyboardType(.numberPad)
92 92
                             .padding()
93 93
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
94 94
                             .cornerRadius(5.0)
95 95
                             .padding(.bottom, 5)
96
-                        TextField("Organization (optional)", text: $org)
96
+                        TextField(NSLocalizedString("org", comment:""), text: $org)
97 97
                             .autocapitalization(.words)
98 98
                             .padding()
99 99
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
100 100
                             .cornerRadius(5.0)
101 101
                             .padding(.bottom, 5)
102
-                        TextField("Position (optional)", text: $position)
102
+                        TextField(NSLocalizedString("pos", comment:""), text: $position)
103 103
                             .padding()
104 104
                             .background(colorScheme == .dark ? darkGreyColor : lightGreyColor)
105 105
                             .cornerRadius(5.0)
@@ -108,11 +108,14 @@ struct RegisterView: View {
108 108
                 }.padding(5)
109 109
             }
110 110
             
111
-            if registerVM.response == false {
112
-                Text("Registration failed, please try again")
111
+            if registerVM.response == .failure {
112
+                Text(NSLocalizedString("reg_fail", comment:""))
113 113
                     //.offset(y: -5)
114 114
                     .foregroundColor(.red)
115
-            } else if registerVM.response == true {
115
+            } else if registerVM.response == .loading {
116
+                ProgressView("Loading…")
117
+                    .padding()
118
+            } else if registerVM.response == .success {
116 119
                 Text("Registration successful 🎉")
117 120
                     //.offset(y: -5)
118 121
                     .foregroundColor(.green)
@@ -138,7 +141,7 @@ struct RegisterView: View {
138 141
                 
139 142
                 
140 143
             }) {
141
-                Text("Register")
144
+                Text(NSLocalizedString("reg", comment:""))
142 145
                     .font(.headline)
143 146
                     .foregroundColor(.white)
144 147
                     .padding()
@@ -150,9 +153,9 @@ struct RegisterView: View {
150 153
             
151 154
             
152 155
         }.alert(isPresented: $showingAlert) {
153
-            Alert(title: Text("Fields incomplete"), message: Text("Please enter a all required information"), dismissButton: .default(Text("OK")))}
156
+            Alert(title: Text(NSLocalizedString("incomplete", comment:"")), message: Text(NSLocalizedString("enter_all", comment:"")), dismissButton: .default(Text("OK")))}
154 157
         .alert(isPresented: $passwordsAlert) {
155
-            Alert(title: Text("Passwords do not match"), message: Text("Please retype your password and try again"), dismissButton: .default(Text("OK")))}
158
+            Alert(title: Text(NSLocalizedString("pass_missmatch", comment:"")), message: Text(NSLocalizedString("pass_retype", comment:"")), dismissButton: .default(Text("OK")))}
156 159
         .padding()
157 160
     }
158 161
 }

+ 8
- 4
Comedores Sociales/View/CustomNavigationView.swift Wyświetl plik

@@ -14,11 +14,9 @@ struct CustomNavigationView: UIViewControllerRepresentable {
14 14
         return CustomNavigationView.Coordinator(parent: self)
15 15
     }
16 16
     
17
-    // Just Change Your View That Requires Search Bar...
18 17
     var view: AnyView
19 18
     
20 19
     // Ease Of Use.....
21
-    
22 20
     var largeTitle: Bool
23 21
     var title: String
24 22
     var placeHolder: String
@@ -26,17 +24,19 @@ struct CustomNavigationView: UIViewControllerRepresentable {
26 24
     // onSearch And OnCancel Closures....
27 25
     var onSearch: (String)->()
28 26
     var onCancel: ()->()
27
+    var onClick: ()->()
29 28
     
30 29
     // requre closure on Call...
31 30
     
32
-    init(view: AnyView,placeHolder: String? = "Search",largeTitle: Bool? = true,title: String,onSearch: @escaping (String)->(),onCancel: @escaping ()->()) {
31
+    init(view: AnyView, placeHolder: String? = NSLocalizedString("search", comment:""), largeTitle: Bool? = true, title: String, onSearch: @escaping (String)->(), onCancel: @escaping ()->(), onClick: @escaping ()->()) {
33 32
       
34
-        self.title = title
33
+        self.title = title // Inventory
35 34
         self.largeTitle = largeTitle!
36 35
         self.placeHolder = placeHolder!
37 36
         self.view = view
38 37
         self.onSearch = onSearch
39 38
         self.onCancel = onCancel
39
+        self.onClick = onClick
40 40
     }
41 41
     
42 42
     // Integrating UIKit Navigation Controller With SwiftUI View...
@@ -89,6 +89,10 @@ struct CustomNavigationView: UIViewControllerRepresentable {
89 89
         init(parent: CustomNavigationView) {
90 90
             self.parent = parent
91 91
         }
92
+        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
93
+            self.parent.onClick()
94
+            
95
+        }
92 96
         
93 97
         func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
94 98
             // when text changes....

+ 3
- 3
Comedores Sociales/View/FoodCardView.swift Wyświetl plik

@@ -26,7 +26,7 @@ struct FoodCardView: View {
26 26
                     
27 27
                     VStack(alignment: .leading, spacing: 5) {
28 28
                         
29
-                        Text(item.name)
29
+                        Text(NSLocalizedString(item.name, comment:""))
30 30
                             .font(.title2)
31 31
                         
32 32
                         Text(item.cuantity)
@@ -40,7 +40,7 @@ struct FoodCardView: View {
40 40
                         
41 41
                         Button(action: {show.toggle()}) {
42 42
                             
43
-                            Text(show ? "Added" : "$" + String(item.priceLocal))
43
+                            Text(show ? NSLocalizedString("added", comment:"") : "$" + String(item.priceLocal))
44 44
                                 .fontWeight(.heavy)
45 45
                                 .padding(.vertical,8)
46 46
                                 .padding(.horizontal,20)
@@ -48,7 +48,7 @@ struct FoodCardView: View {
48 48
                                 .clipShape(Capsule())
49 49
                         }
50 50
                         
51
-                        Text("Savings of $" + String(item.savings))
51
+                        Text(NSLocalizedString("savings", comment:"") + String(item.savings))
52 52
                             .font(.caption2)
53 53
                             .foregroundColor(.green)
54 54
                     }

+ 91
- 0
Comedores Sociales/View/ProfileSummary.swift Wyświetl plik

@@ -0,0 +1,91 @@
1
+//
2
+//  ProfileView.swift
3
+//  Comedores Sociales
4
+//
5
+//  Created by Hector Carrion on 12/16/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct ProfileSummary: View {
11
+    @State var profile: Profile
12
+
13
+    var body: some View {
14
+        VStack(alignment: .leading, spacing: 10) {
15
+            HStack {
16
+                Image(systemName: "person.crop.circle")
17
+                    .font(.system(size: 100))
18
+                VStack(alignment: .leading, spacing: 5) {
19
+                    Text(profile.name)
20
+                        .bold()
21
+                        .font(.title)
22
+                    Text(profile.membership)
23
+                        .font(.headline)
24
+                    
25
+                }
26
+                
27
+            }.padding()
28
+            .frame(width: 350, height: 125, alignment: .topLeading)
29
+            .offset(x: -10, y: 10.0)
30
+            
31
+            HStack {
32
+                Image(systemName: "envelope")
33
+                    .font(.system(size: 20))
34
+                    .foregroundColor(.gray)
35
+                Text(profile.email)
36
+                    .font(.headline)
37
+                    .foregroundColor(.gray)
38
+            }.padding()
39
+            .offset(x: 0, y: 10.0)
40
+            HStack {
41
+                Image(systemName: "phone")
42
+                    .font(.system(size: 20))
43
+                    .foregroundColor(.gray)
44
+                Text(profile.phone)
45
+                    .font(.headline)
46
+                    .foregroundColor(.gray)
47
+            }.padding()
48
+            
49
+            HStack() {
50
+                VStack {
51
+                    Text(String(profile.hoursWorked))
52
+                        .font(.title)
53
+                        .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
54
+                    Text(NSLocalizedString("hours_donated", comment:""))
55
+                        .foregroundColor(.gray)
56
+                }.padding()
57
+                
58
+                Spacer()
59
+                VStack {
60
+                    if profile.membershipStatus == .expired {
61
+                        Text(NSLocalizedString("expired", comment:""))
62
+                            .font(.title)
63
+                            .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
64
+                            .foregroundColor(.red)
65
+                    } else if profile.membershipStatus == .valid {
66
+                        Text(NSLocalizedString("valid", comment:""))
67
+                            .font(.title)
68
+                            .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
69
+                            .foregroundColor(.green)
70
+                    } else {
71
+                        Text(NSLocalizedString("unknown", comment:""))
72
+                            .font(.title)
73
+                            .fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
74
+                            .foregroundColor(.orange)
75
+                    }
76
+                    
77
+                    Text(NSLocalizedString("membership", comment:""))
78
+                        .foregroundColor(.gray)
79
+                }.padding()
80
+            }.padding()
81
+            .offset(x: 0, y: -10)
82
+            Spacer()
83
+        }
84
+    }
85
+}
86
+
87
+struct ProfileSummary_Previews: PreviewProvider {
88
+    static var previews: some View {
89
+        ProfileSummary(profile: Profile.default)
90
+    }
91
+}

+ 30
- 0
Comedores Sociales/View/ProfileView.swift Wyświetl plik

@@ -0,0 +1,30 @@
1
+//
2
+//  ProfileView.swift
3
+//  Comedores Sociales
4
+//
5
+//  Created by Hector Carrion on 12/16/20.
6
+//
7
+
8
+import SwiftUI
9
+
10
+struct ProfileView: View {
11
+    
12
+    @EnvironmentObject var profileVM: ProfileViewModel
13
+    
14
+    var body: some View {
15
+            VStack(alignment: .leading, spacing: 20) {
16
+                if profileVM.response == .unfetched {
17
+                    ProfileSummary(profile: Profile.default)
18
+                } else if profileVM.response == .success {
19
+                    ProfileSummary(profile: profileVM.profile)
20
+                }
21
+            }
22
+            .padding()
23
+        }
24
+}
25
+
26
+struct ProfileView_Previews: PreviewProvider {
27
+    static var previews: some View {
28
+        ProfileView()
29
+    }
30
+}

+ 39
- 17
Comedores Sociales/View/Search/SearchContainer.swift Wyświetl plik

@@ -9,27 +9,49 @@ import SwiftUI
9 9
 
10 10
 struct SearchContainer: View {
11 11
     @State var filteredItems = foods
12
+    @State var showingProfile: Bool = false
13
+    @EnvironmentObject var profileVM: ProfileViewModel
14
+    @State var hideButton = false
15
+    @State var disableButton = false
12 16
     
13 17
     var body: some View {
14
-
15
-        CustomNavigationView(view: AnyView(Search(filteredItems: $filteredItems)), placeHolder: "Search", largeTitle: true, title: "Inventory",
16
-                             
17
-            onSearch: { (txt) in
18
-
19
-            // filterting Data...
20
-            if txt != ""{
21
-                self.filteredItems = foods.filter{$0.name.lowercased().contains(txt.lowercased())}
22
-            }
23
-            else{
18
+        ZStack(alignment: .topTrailing) {
19
+            CustomNavigationView(view: AnyView(Search(filteredItems: $filteredItems)), placeHolder: NSLocalizedString("search", comment:""), largeTitle: true, title: NSLocalizedString("inventory", comment:""),
20
+                
21
+                onSearch: { (txt) in
22
+                // filterting Data...
23
+                if txt != ""{
24
+                    self.filteredItems = foods.filter{$0.name.lowercased().contains(txt.lowercased())}
25
+                }
26
+                else{
27
+                    self.filteredItems = foods
28
+                }
29
+                
30
+            }, onCancel: {
31
+                // Do Your Own Code When Search And Canceled....
24 32
                 self.filteredItems = foods
25
-            }
26
-            
27
-        }, onCancel: {
28
-            // Do Your Own Code When Search And Canceled....
29
-            self.filteredItems = foods
33
+                self.hideButton = false
34
+                self.disableButton = false
35
+                
36
+            }, onClick: {
37
+                self.hideButton = true
38
+                self.disableButton = true
39
+            })
40
+            .ignoresSafeArea()
30 41
             
31
-        })
32
-        .ignoresSafeArea()
42
+            Button(action: {
43
+                self.showingProfile.toggle()
44
+                let token = UserToken(token: UserDefaults.standard.string(forKey: "token")!)
45
+                profileVM.fetchMember(token: token)
46
+            }) {
47
+                Image(systemName: "person.crop.circle")
48
+                    .font(.system(size: 30))
49
+                    .opacity(hideButton ? 0 : 1)
50
+            }.disabled(disableButton)
51
+            .offset(x: 0, y: -10)
52
+            .padding()
53
+            .sheet(isPresented: $showingProfile, content: {ProfileView()})
54
+        }
33 55
     }
34 56
 }
35 57
 

+ 53
- 0
en.lproj/Localizable.strings Wyświetl plik

@@ -0,0 +1,53 @@
1
+/* 
2
+  Localizable.strings
3
+  Comedores Sociales
4
+
5
+  Created by Hector Carrion on 12/17/20.
6
+  
7
+*/
8
+
9
+"hello_world" = "Hello, world!";
10
+"search" = "Search";
11
+"incorrect" = "Information incorrect, please try again";
12
+"incomplete" = "Fields incomplete";
13
+"enter_valid" = "Please enter a valid email and password";
14
+"login" = "Login";
15
+"register" = "Don't have an account? Register";
16
+"details" = "Details";
17
+"full_name" = "Full Name";
18
+"pass_conf" = "Password (confirmation)";
19
+"phone" = "Phone Number";
20
+"urb" = "Address";
21
+"street" = "Address (apt, suite, etc)";
22
+"city" = "City";
23
+"zip" = "Zip Code";
24
+"org" = "Company or Organization (optional)";
25
+"pos" = "Position (optional)";
26
+"reg_fail" = "Registration failed, please double-check all fields and try again";
27
+"reg" = "Register";
28
+"enter_all" = "Please enter a all required information";
29
+"pass_missmatch" = "Passwords do not match";
30
+"pass_retype" = "Please retype your password and try again";
31
+"inventory" = "Inventory";
32
+"hours_donated" = "Hours Donated";
33
+"membership" = "Membership";
34
+"added" = "Added";
35
+"savings" = "Savings of $";
36
+"valid" = "Valid";
37
+"expired" = "Expired";
38
+"unknown" = "Unknown";
39
+"Beans" = "Beans";
40
+"Carrots" = "Carrots";
41
+"Eggs" = "Eggs";
42
+"Malanga" = "Malanga";
43
+"Oatmeal" = "Oatmeal";
44
+"Pasta" = "Pasta";
45
+"Green Peppers" = "Green Peppers";
46
+"Pineapple" = "Pineapple";
47
+"Potatoes" = "Potatoes";
48
+"Rice" = "Rice";
49
+"Sausages" = "Sausages";
50
+"Vegetable Oil" = "Vegetable Oil";
51
+"Yellow Onions" = "Yellow Onions";
52
+"Yuca" = "Yuca";
53
+"Crackers" = "Crackers";

+ 53
- 0
es-419.lproj/Localizable.strings Wyświetl plik

@@ -0,0 +1,53 @@
1
+/* 
2
+  Localizable.strings
3
+  Comedores Sociales
4
+
5
+  Created by Hector Carrion on 12/17/20.
6
+  
7
+*/
8
+
9
+"hello_world" = "Hola mundo!";
10
+"search" = "Buscar";
11
+"incorrect" = "Información incorrecta, trate nuevamente";
12
+"incomplete" = "Campos incompletos";
13
+"enter_valid" = "Porfavor entre email y password válidos";
14
+"login" = "Entrar";
15
+"register" = "¿No tiene cuenta? Regístrese";
16
+"details" = "Detalles";
17
+"full_name" = "Nombre Completo";
18
+"pass_conf" = "Password (confirmación)";
19
+"phone" = "Número de Teléfono";
20
+"urb" = "Urbanización";
21
+"street" = "Calle";
22
+"city" = "Ciudad";
23
+"zip" = "Código Zip";
24
+"org" = "Compañía o Organización (opcional)";
25
+"pos" = "Posición (optional)";
26
+"reg_fail" = "Error al registrar, verifique todos los campos y trate nuevamente";
27
+"reg" = "Registrarme";
28
+"enter_all" = "Porfavor complete todos los campos requeridos";
29
+"pass_missmatch" = "Passwords no coinciden";
30
+"pass_retype" = "Porfavor reescriba su password y trate nuevamente";
31
+"inventory" = "Inventario";
32
+"hours_donated" = "Horas Donadas";
33
+"membership" = "Membresía";
34
+"added" = "Añadido";
35
+"savings" = "Ahorros de $";
36
+"valid" = "Válida";
37
+"expired" = "Expirada";
38
+"unknown" = "Desconocida";
39
+"Beans" = "Habichuelas";
40
+"Carrots" = "Zanahorias";
41
+"Eggs" = "Huevos";
42
+"Malanga" = "Malanga";
43
+"Oatmeal" = "Avena";
44
+"Pasta" = "Pasta";
45
+"Green Peppers" = "Pimiento Verde";
46
+"Pineapple" = "Piña";
47
+"Potatoes" = "Papas";
48
+"Rice" = "Arroz";
49
+"Sausages" = "Salchichas";
50
+"Vegetable Oil" = "Aceite Vegetal";
51
+"Yellow Onions" = "Cebolla Amarilla";
52
+"Yuca" = "Yuca";
53
+"Crackers" = "Galletas";

+ 53
- 0
es.lproj/Localizable.strings Wyświetl plik

@@ -0,0 +1,53 @@
1
+/* 
2
+  Localizable.strings
3
+  Comedores Sociales
4
+
5
+  Created by Hector Carrion on 12/17/20.
6
+  
7
+*/
8
+
9
+"hello_world" = "Hola mundo!";
10
+"search" = "Buscar";
11
+"incorrect" = "Información incorrecta, trate nuevamente";
12
+"incomplete" = "Campos incompletos";
13
+"enter_valid" = "Porfavor entre email y password válidos";
14
+"login" = "Entrar";
15
+"register" = "¿No tiene cuenta? Regístrese";
16
+"details" = "Detalles";
17
+"full_name" = "Nombre Completo";
18
+"pass_conf" = "Password (confirmación)";
19
+"phone" = "Número de Teléfono";
20
+"urb" = "Urbanización";
21
+"street" = "Calle";
22
+"city" = "Ciudad";
23
+"zip" = "Código Zip";
24
+"org" = "Compañía o Organización (opcional)";
25
+"pos" = "Posición (optional)";
26
+"reg_fail" = "Error al registrar, verifique todos los campos y trate nuevamente";
27
+"reg" = "Registrarme";
28
+"enter_all" = "Porfavor complete todos los campos requeridos";
29
+"pass_missmatch" = "Passwords no coinciden";
30
+"pass_retype" = "Porfavor reescriba su password y trate nuevamente";
31
+"inventory" = "Inventario";
32
+"hours_donated" = "Horas Donadas";
33
+"membership" = "Membresía";
34
+"added" = "Añadido";
35
+"savings" = "Ahorros de $";
36
+"valid" = "Válida";
37
+"expired" = "Expirada";
38
+"unknown" = "Desconocida";
39
+"Beans" = "Habichuelas";
40
+"Carrots" = "Zanahorias";
41
+"Eggs" = "Huevos";
42
+"Malanga" = "Malanga";
43
+"Oatmeal" = "Avena";
44
+"Pasta" = "Pasta";
45
+"Green Peppers" = "Pimiento Verde";
46
+"Pineapple" = "Piña";
47
+"Potatoes" = "Papas";
48
+"Rice" = "Arroz";
49
+"Sausages" = "Salchichas";
50
+"Vegetable Oil" = "Aceite Vegetal";
51
+"Yellow Onions" = "Cebolla Amarilla";
52
+"Yuca" = "Yuca";
53
+"Crackers" = "Galletas";