Proyectos de Subversion Iphone Microlearning - Nuevo Interface

Rev

Rev 11 | Rev 23 | Ir a la última revisión | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  LoginView.swift
3
//  twogetskills
4
//
5
//  Created by Efrain Yanez Recanatini on 1/27/22.
6
//
7
 
8
import SwiftUI
9
import AudioToolbox
10
import Network
11
import Alamofire
12
import SwiftyJSON
13
import SafariServices
14
import RNCryptor
15
 
16
 
17
 
18
 
19
 
20
struct SigninView: View {
21
 
22
    @Environment(\.openURL) var openURL
8 efrain 23
    @EnvironmentObject var networkMonitor : NetworkMonitor
1 efrain 24
    @EnvironmentObject var appNavigation : AppNavigation
25
    @State var keyboardHeight : CGFloat = 0
26
 
17 efrain 27
    private var appData = AppData.sharedInstance
1 efrain 28
 
29
 
30
    @State private var email: String = "efrain.yanez@leaderslinked.com" {
31
        didSet {
32
            if email.count > 250 {
33
                email = String(email.prefix(250))
34
                AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) { return }
35
            }
36
        }
37
    }
38
    @State private var isValidEmail : Bool = true
39
    @State private var isEditingEmail : Bool = false
40
 
41
    @State private var password: String = "Cesa2020$" {
42
        didSet {
43
            if password.count > 25 {
44
                password = String(password.prefix(25))
45
                AudioServicesPlayAlertSoundWithCompletion(SystemSoundID(kSystemSoundID_Vibrate)) { return }
46
            }
47
        }
48
    }
49
 
50
    @State private var isValidPassword : Bool = true
51
    @State private var isEditingPassword : Bool = false
52
 
53
    @State private var isPasswordShow: Bool = false
54
    @State private var showProgressView : Bool = false
55
 
56
    @State private var presentAlert : Bool = false
57
    @State private var titleAlert : String = ""
58
    @State private var messageAlert : String = ""
59
 
60
    var body: some View {
61
        ZStack {
62
 
63
            Color("color_window_background")
64
                    .edgesIgnoringSafeArea(.all)
8 efrain 65
 
66
            if self.showProgressView {
67
                ProgressView()
68
                    .progressViewStyle(CircularProgressViewStyle(tint: Color("color_progress_view_foreground")))
69
                    .scaleEffect(3, anchor: .center).zIndex(100000)
70
            }
1 efrain 71
 
72
            VStack(spacing: 0) {
8 efrain 73
 
74
                if networkMonitor.status == .disconnected {
75
                    HStack {
76
 
77
 
78
                        Text(Config.LANG_ERROR_NETWORK_MESSAGE_SHORT)
79
                        .font(Font.custom(Config.FONT_NAME_REGULAR, size: Config.FONT_SIZE_APP_BAR_HEAD1 ))
80
                            .foregroundColor(Color("color_network_disconnected_foreground"))
81
                            .padding(.leading, 16)
82
 
83
 
84
                        Spacer()
85
                    }
86
                    .edgesIgnoringSafeArea(.top)
87
                    .frame(height: 50)
88
                    .background(Color("color_network_disconnected_background"))
89
 
90
 
91
                    Divider().background(Color("color_network_disconnected_background"))
92
                }
1 efrain 93
 
94
                HeaderGroupView()
95
 
96
                EmailTextFieldGroup(email: self.$email, isEditingEmail: self.$isEditingEmail, isValidEmail: self.$isValidEmail, password: self.$password, isEditingPassword: self.$isEditingPassword, isValidPassword: self.$isValidPassword)
97
 
98
                PasswordTextFieldGroup(
99
                    password: self.$password,
100
                    isEditingPassword: self.$isEditingPassword,
101
                    isValidPassword: self.$isValidPassword,
102
                    isPasswordShow: self.$isPasswordShow
103
                ).padding(.top, isValidEmail ? 10 : 2)
104
 
105
 
106
 
107
                Button(action: {
108
                    if(validate()) {
109
                        signin()
110
                    }
111
                }, label: {
112
                    Text(Config.LANG_SIGNIN_BUTTON_SIGNIN)
113
                    .font(Font.custom(Config.FONT_NAME_REGULAR, size: 13))
114
                    .frame(width: UIScreen.main.bounds.width - 32, height: 35)
115
                    .foregroundColor(Color("color_button_foreground"))
116
                    .background(Color("color_button_background"))
117
                    .border(Color("color_button_border"), width: Config.BUTTON_BORDER_SIZE)
118
                                           .cornerRadius(Config.BUTTON_BORDER_RADIUS)
119
 
120
                                   })
121
 
122
                                   .padding(.top, 16)
123
                                   .padding(.leading, 16)
124
                                   .padding(.trailing, 16)
125
 
126
 
127
                ButtonSignUpGroup()
128
 
129
 
130
 
131
                Spacer()
132
 
133
                ButtonForgotPasswordGroup()
134
 
135
 
136
            }
137
 
138
 
139
        }
140
 
141
        //}.offset(y : CGFloat(-(self.keyboardHeight / 2)))
142
 
143
        .onAppear {
144
    /*
145
            NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { (notification) in
146
                let keyboardHeight: CGFloat = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.height ?? 0
147
 
148
 
149
                self.keyboardHeight = keyboardHeight
11 efrain 150
                //print("keyboardHeightShow = \(keyboardHeight)")
1 efrain 151
 
152
            }*/
153
 
154
            NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { (notification) in
155
 
156
 
11 efrain 157
               // print("keyboardHeightHide ")
1 efrain 158
 
159
            }
160
        }
161
 
162
         .alert(isPresented: $presentAlert) {
163
            Alert(
164
                title: Text(self.titleAlert),
165
                message: Text(self.messageAlert),
8 efrain 166
                dismissButton: .default(Text(Config.LANG_COMMON_OK))
1 efrain 167
            )
168
        }
169
 
170
    }
171
 
172
    private func signin() -> Void
173
    {
174
        self.isEditingEmail = false
175
        self.isValidEmail = Validator.checkEmail(email: self.email)
176
        self.isEditingPassword = false
177
        self.isValidPassword = Validator.checkPassword(password: self.password)
178
 
179
        if !self.isValidEmail || !self.isValidPassword {
180
            return
181
        }
182
 
183
 
184
        let device_uuid = appData.deviceUuid
185
 
11 efrain 186
        //print("signin")
187
        //print(" aes  = \(appData.deviceAes) " )
188
        //print(" email  = \(self.email) " )
189
        //print(" password  = \(self.password) " )
1 efrain 190
 
191
 
192
        let syncDao = SyncDao.sharedInstance
11 efrain 193
        if appData.deviceAes.isEmpty  {
1 efrain 194
 
195
            let syncRecord = syncDao.selectOneByType(type: Constants.SYNC_ADAPTER_TYPE_DEVICE)
196
 
197
            if syncRecord.id > 0 {
198
                let syncAdapter = SyncAdapter()
199
                syncAdapter.sync {
200
                    success in
201
                }
202
            }
203
 
204
            self.titleAlert = Config.LANG_ERROR_DEVICE_NOT_REGISTER_TITLE
205
            self.messageAlert = Config.LANG_ERROR_DEVICE_NOT_REGISTER_MESSAGE
206
            self.presentAlert = true
207
 
208
            return
209
        }
210
 
8 efrain 211
 
212
 
213
        if networkMonitor.status == .disconnected {
214
            self.titleAlert = Config.LANG_ERROR_NETWORK_TITLE
215
            self.messageAlert = Config.LANG_ERROR_NETWORK_MESSAGE_SHORT
216
            self.presentAlert = true
217
 
218
            return
219
        }
220
 
1 efrain 221
        self.showProgressView = true;
222
 
223
        let emailData = email.data(using: .utf8)!
11 efrain 224
        let emailCipherData = RNCryptor.encrypt(data: emailData, withPassword: appData.deviceAes)
1 efrain 225
        let emailEncrypted = emailCipherData.base64EncodedString()
226
 
227
        let passwordData = password.data(using: .utf8)!
11 efrain 228
        let passwordCipherData = RNCryptor.encrypt(data: passwordData, withPassword: appData.deviceAes)
1 efrain 229
        let passwordEncrypted = passwordCipherData.base64EncodedString()
230
 
231
 
11 efrain 232
        //print(" email encrypted = \(emailEncrypted) " )
233
        //print(" password encrypted = \(passwordEncrypted) " )
1 efrain 234
 
235
        let parameters = [
236
            Constants.POST_SIGNIN_FIELD_APPLICATION_ID: "\(Constants.GLOBAL_APPLICATION_ID)",
237
            Constants.POST_SYNC_FIELD_DEVICE_UUID: device_uuid,
238
            Constants.POST_SIGNIN_FIELD_EMAIL: emailEncrypted,
239
            Constants.POST_SIGNIN_FIELD_PASSWORD: passwordEncrypted,
240
            Constants.POST_SIGNIN_FIELD_ENCRYPTER: Constants.GLOBAL_ENCRYPTER
241
        ]
242
 
243
        let headers: HTTPHeaders = [
244
            .accept(Constants.HTTP_HEADER_ACCEPT)
245
        ]
246
 
11 efrain 247
        //print("URL signin : \(Config.URL_SIGNIN)")
1 efrain 248
 
249
        self.showProgressView = true
250
        AF.request(Config.URL_SIGNIN, method: .post, parameters: parameters, encoding: URLEncoding.default, headers: headers).responseJSON{(response) in
251
            self.showProgressView = false
252
 
253
            switch response.result {
254
                case .success:
255
                    let json = try? JSON(data: response.data!)
256
 
11 efrain 257
                    //print("json : \(json)")
1 efrain 258
 
259
                    if json?["success"] ?? "" != false {
260
                        let dataService = DataService()
261
 
262
                        if dataService.syncFromServer(json : json) {
263
 
264
                            let now = Date()
265
                            let dateFormatter = DateFormatter()
266
                            dateFormatter.dateFormat = Constants.FORMAT_DATETIME_SERVICE
267
                            let dateOn = dateFormatter.string(from: now)
268
 
269
                            var userLog = UserLogModel()
270
                            userLog.userUuid = appData.userUuid
271
                            userLog.activity = Constants.USER_LOG_ACTIVITY_SIGNIN
272
                            userLog.addedOn = dateOn
273
 
274
                            let userLogDao = UserLogDao.sharedInstance
275
                            userLogDao.insert(record: userLog)
276
 
277
                            var sync = SyncModel()
278
 
279
                            var json = userLog.toJson()
280
                            json[Constants.SYNC_ADAPTER_DATA_TYPE_FIELD_NAME] = Constants.SYNC_ADAPTER_DATA_TYPE_USER_LOG
281
 
282
                            sync = SyncModel();
283
                            sync.type = Constants.SYNC_ADAPTER_TYPE_SYNC
284
                            if let theJSONData = try?  JSONSerialization.data(withJSONObject: json, options: .prettyPrinted),
285
                                let data = String(data: theJSONData, encoding: String.Encoding.ascii) {
286
                                    sync.data = data
287
                                }
288
 
289
                            syncDao.insert(record : sync);
290
 
8 efrain 291
 
292
                            appNavigation.subpageActive = AppMainSubPage.mycapsules
1 efrain 293
                            appNavigation.pageActive = AppMainPage.home
294
 
295
 
296
 
297
                            //self.goToMain = true
298
                        }
299
                    } else {
300
                        let message = json?["data"].string ?? ""
301
                        if !message.isEmpty {
302
                            self.titleAlert = Config.LANG_ERROR_GENERIC_TITLE
303
                            self.messageAlert = message
304
                            self.presentAlert = true
305
                        }
306
                    }
307
 
308
                   return
309
 
310
                case .failure :
311
                    self.titleAlert = Config.LANG_ERROR_COMMUNICATION_TITLE
312
                    self.messageAlert = Config.LANG_ERROR_COMMUNICATION_MESSAGE
313
                    self.presentAlert = true
314
 
315
                    return
316
            }
317
 
318
 
319
 
320
 
8 efrain 321
 
1 efrain 322
        }
323
    }
324
 
325
 
326
    private func validate() -> Bool
327
    {
328
 
11 efrain 329
        //print("validate - email : \(email)")
330
        //print("validate - password : \(password)")
1 efrain 331
 
332
 
333
        if email.isEmpty {
334
            self.isValidEmail = false
335
        } else  if !checkEmail(email: email) {
336
            self.isValidEmail = false
337
        } else {
338
            self.isValidEmail = true
339
        }
340
 
341
 
342
 
343
        if password.isEmpty {
344
            self.isValidPassword = false
345
        } else if(!checkPassword(password: password)) {
346
            self.isValidPassword = false
347
 
348
        } else {
349
            self.isValidPassword = true
350
        }
351
 
352
        return  self.isValidEmail &&  self.isValidPassword
353
    }
354
 
355
 
356
    private func checkEmail(email : String) -> Bool {
357
        let regex = #"^[a-zA-Z0-9_\-\.~]{2,}@[a-zA-Z0-9_\-\.~]{2,}\.[a-zA-Z]{2,}$"#
358
 
359
        let predicate = NSPredicate(format: "SELF MATCHES %@", regex)
360
        return predicate.evaluate(with: email) ? true : false;
361
    }
362
 
363
    private func checkPassword(password : String) -> Bool {
364
        let regexOld = #"^(?=.*\d+)(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z]{6,16}$"#
365
        let regexNew = #"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[\#\?\!\@\$\^\%\*\-]).{6,16}$"#
366
 
367
        let predicateNew = NSPredicate(format: "SELF MATCHES %@", regexNew)
368
        let predicateOld = NSPredicate(format: "SELF MATCHES %@", regexOld)
369
        if(predicateOld.evaluate(with: password)) {
370
            return true
371
        } else  if(predicateNew.evaluate(with: password)) {
372
            return true
373
        } else {
374
            return false
375
        }
376
    }
8 efrain 377
 
378
 
1 efrain 379
 
380
 
381
}
382
 
383
 
384
 
385
struct SigninView_Previews: PreviewProvider {
386
    static var previews: some View {
387
        SigninView()
388
    }
389
}
390
 
8 efrain 391
 
392
 
1 efrain 393
struct HeaderGroupView: View {
394
 
395
 
396
    var body: some View {
397
        //Inicio Logo
398
        HStack {
399
            Image("logo")
400
            .resizable()
401
            .frame(width: 50, height: 50)
402
 
403
            Text(Config.LANG_SIGNIN_APP_NAME)
404
            .font(Font.custom(Config.FONT_NAME_BOLD, size: 24))
8 efrain 405
            .foregroundColor(Color("color_textview_foreground"))
1 efrain 406
 
407
            Spacer()
408
        }
409
        .padding(.leading, 16)
410
        .padding(.top, 66)
411
        //Fin logo
412
 
413
        //Inicio Saludo
414
        HStack {
415
            Text(Config.LANG_SIGNIN_GREATING)
416
            .font(Font.custom(Config.FONT_NAME_BOLD, size: 32))
8 efrain 417
            .foregroundColor(Color("color_textview_foreground"))
1 efrain 418
            Spacer()
419
        }
420
        .padding(.leading, 16)
421
        .padding(.top, 10)
422
        //Fin Saludo
423
 
424
        //Inicio Encabezado
425
        HStack {
426
            Text(Config.LANG_SIGNIN_HEAD_LINE1)
427
            .font(Font.custom(Config.FONT_NAME_REGULAR, size: 16))
8 efrain 428
            .foregroundColor(Color("color_textview_foreground"))
1 efrain 429
            Spacer()
430
        }
431
        .padding(.leading, 16)
432
        .padding(.top, 10)
433
        //Fin Encabezado
434
    }
435
}
436
 
437
struct EmailTextFieldGroup : View {
438
 
439
 
440
 
441
    @Binding var email: String
442
    @Binding var isEditingEmail : Bool
443
    @Binding var isValidEmail : Bool
444
 
445
    @Binding var password: String
446
    @Binding var isEditingPassword: Bool
447
    @Binding var isValidPassword: Bool
448
 
449
 
450
    var body : some View {
451
        //Inicio Label Email
452
        HStack {
453
            Text(Config.LANG_SIGNIN_TITLE_EMAIL_FIELD)
454
            .font(Font.custom(Config  .FONT_NAME_REGULAR, size: 11))
8 efrain 455
            .foregroundColor(Color("color_textview_foreground"))
1 efrain 456
            Spacer()
457
        }
458
        .padding(.leading, 16)
459
        .padding(.top, 10)
460
        //Fin Label Email
461
 
462
        //Inicio TextField  Email
463
 
464
        Group {
465
            HStack {
466
                /*
467
                Image("ui_mail")
468
                .resizable()
469
                .frame(width: 24, height: 24)
470
                         .padding(.horizontal, 4)
471
                */
472
 
473
 
474
                TextField("",
475
                    text: self.$email,
476
                    onEditingChanged: { (changed) in
477
 
478
                        if changed {
479
 
480
                            if self.isEditingPassword {
481
                                self.isValidPassword = Validator.checkPassword(password: self.password)
482
                                self.isEditingPassword = false
483
                            }
484
 
485
 
486
                            self.isEditingEmail = true
487
                        } else {
488
                            self.isEditingEmail = false
489
                            self.isValidEmail = Validator.checkEmail(email: self.email)
490
                        }
491
 
492
                    }, onCommit: {
493
                        self.isEditingEmail = false
494
                        self.isValidEmail = Validator.checkEmail(email: self.email)
495
                    }
496
                )
497
 
498
                .font(Font.custom(Config.FONT_NAME_REGULAR, size: 12))
499
                .textFieldStyle(PlainTextFieldStyle())
500
                .frame(height: 32)
501
                .keyboardType(.emailAddress)
502
                .autocapitalization(.none)
503
                //.foregroundColor(Color("color_textfield_foreground"))
504
                //.background(Color("color_textfield_background"))
505
                .padding(.leading, 4)
506
                Spacer()
507
 
508
            }
509
        }
510
 
511
        .foregroundColor(Color("color_textfield_foreground"))
512
        .background(Color("color_textfield_background"))
513
        .overlay(RoundedRectangle(cornerRadius: 5).stroke(
514
            Color(self.isEditingEmail ? "color_textfield_border_active" : self.isValidEmail ? "color_textfield_border" : "color_textfield_border_error" )
515
        ))
516
        .padding(.leading, 16)
517
        .padding(.trailing, 16)
518
        .padding(.top, self.isValidEmail ? 10 : 2)
519
 
520
 
521
        if !self.isValidEmail {
522
            HStack {
523
                Spacer()
524
 
525
                Text(Config.LANG_SIGNIN_ERROR_EMAIL_FIELD)
526
                .foregroundColor(.red)
527
                    .font(Font.custom(Config.FONT_NAME_REGULAR, size: 11))
528
 
529
            }
530
            .padding(.top, 5)
531
            .padding(.trailing, 16)
532
        }
533
 
534
        //Fin TextField Email
535
    }
536
 
537
}
538
 
539
 
540
 
541
struct PasswordTextFieldGroup : View {
542
    @Binding var password: String
543
    @Binding var isEditingPassword: Bool
544
    @Binding var isValidPassword: Bool
545
    @Binding var isPasswordShow: Bool
546
 
547
    var body : some View {
548
        //Inicio Label Password
549
        HStack {
550
            Text(Config.LANG_SIGNIN_TITLE_PASSWORD_FIELD)
551
            .font(Font.custom(Config  .FONT_NAME_REGULAR, size: 11))
8 efrain 552
            .foregroundColor(Color("color_textview_foreground"))
1 efrain 553
            Spacer()
554
        }
555
        .padding(.leading, 16)
556
        //Fin Label Password
557
 
558
 
559
        //Inicio TextField  Password
560
 
561
        Group {
562
            HStack {
563
                /*
564
                Image("ui_key")
565
                .resizable()
566
                .frame(width: 24, height: 24)
567
                         .padding(.horizontal, 4)
568
                 */
569
                if isPasswordShow {
570
 
571
                    TextField("",
572
                        text: self.$password,
573
                        onEditingChanged: { (changed) in
574
 
575
                            if changed {
576
                                self.isEditingPassword = true
577
                            } else {
578
                                self.isEditingPassword = false
579
                                self.isValidPassword = Validator.checkPassword(password: self.password)
580
                            }
581
 
582
                        }, onCommit: {
583
                            self.isEditingPassword = false
584
                            self.isValidPassword = Validator.checkPassword(password:  self.password)
585
                        }
586
                    )
587
                    .font(Font.custom(Config.FONT_NAME_REGULAR, size: 12))
588
                    .textFieldStyle(PlainTextFieldStyle())
589
                    .frame(height: 32)
590
                        .keyboardType(.default)
591
                    .autocapitalization(.none)
592
                    /*
593
                    .foregroundColor(Color("color_textfield_foreground"))
594
                    .background(Color("color_textfield_background"))
595
 */
596
                    .padding(.leading, 4)
597
                    Spacer()
598
 
599
                    Button(action: {
600
                        self.isPasswordShow.toggle()
601
                    }, label: {
602
                        Image("ui_visibility_off")
603
                        .resizable()
604
                        .frame(width: 24, height: 24)
605
                                 .padding(.horizontal, 4)
606
                    })
607
                } else {
608
 
609
                    SecureField("", text: self.$password, onCommit: {
610
                        self.isEditingPassword = false
611
                        self.isValidPassword = Validator.checkPassword(password: self.password)
612
                    })
613
                    .onTapGesture {
614
                        self.isEditingPassword = true
615
                    }
616
                    .font(Font.custom(Config.FONT_NAME_REGULAR, size: 11))
617
                    .textFieldStyle(PlainTextFieldStyle())
618
                    .frame(height: 32)
619
                        .keyboardType(.default)
620
                    .autocapitalization(.none)
621
                    /*
622
                    .foregroundColor(Color("color_textfield_foreground"))
623
                    .background(Color("color_textfield_background"))*/
624
                        .padding(.leading, 4)
625
                    Spacer()
626
 
627
                    Button(action: {
628
                        self.isPasswordShow.toggle()
629
                    }, label: {
630
                        Image("ui_visibility")
631
                        .resizable()
632
                        .frame(width: 24, height: 24)
633
                                 .padding(.horizontal, 4)
634
 
635
                    })
636
 
637
                }
638
 
639
                //
640
                //ui_visibility
641
 
642
            }
643
        }
644
        .foregroundColor(Color("color_textfield_foreground"))
645
        .background(Color("color_textfield_background"))
646
        .overlay(RoundedRectangle(cornerRadius: 5).stroke(
647
            Color(self.isEditingPassword ? "color_textfield_border_active" : self.isValidPassword ? "color_textfield_border" : "color_textfield_border_error" )
648
        ))
649
        .padding(.leading, 16)
650
        .padding(.trailing, 16)
651
        .padding(.top, 2)
652
 
653
        if !self.isValidPassword {
654
            HStack {
655
                Spacer()
656
 
657
                Text(Config.LANG_SIGNIN_ERROR_PASSWORD_FIELD)
658
                .foregroundColor(.red)
659
                    .font(Font.custom(Config.FONT_NAME_REGULAR, size: 11))
660
 
661
            }
662
            .padding(.top, 5)
663
            .padding(.trailing, 16)
664
        }
665
 
666
        //Fin TextField Password
667
 
668
 
669
    }
670
 
671
 
672
 
673
}
674
 
675
struct ButtonSignUpGroup : View {
676
    @Environment(\.openURL) var openURL
677
 
678
 
679
    var body : some View {
680
        Button(action: {
681
            openURL(URL(string: Config.URL_SIGNUP_ENDPOINT)!)
682
 
683
        }, label: {
684
            Text(Config.LANG_SIGNIN_BUTTON_SIGNUP)
685
             .font(Font.custom(Config.FONT_NAME_REGULAR, size: 13))
686
             .frame(width: UIScreen.main.bounds.width - 32, height: 35)
687
                .foregroundColor(Color("color_button_foreground"))
688
                .background(Color("color_button_background"))
689
                .border(Color("color_button_border"), width: Config.BUTTON_BORDER_SIZE)
690
                .cornerRadius(Config.BUTTON_BORDER_RADIUS)
691
 
692
        })
693
        .padding(.top, 16)
694
        .padding(.leading, 16)
695
        .padding(.trailing, 16)
696
    }
697
}
698
 
699
struct ButtonForgotPasswordGroup : View {
700
    @Environment(\.openURL) var openURL
701
 
702
 
703
    var body : some View {
704
        Button(action: {
705
            openURL(URL(string: Config.URL_FORGOT_PASSWORD_ENDPOINT)!)
706
 
707
        }, label: {
708
            Text(Config.LANG_SIGNIN_BUTTON_FORGOT_PASSWORD)
709
             .font(Font.custom(Config.FONT_NAME_REGULAR, size: 13))
710
 
711
                .foregroundColor(Color("color_button_foreground"))
712
 
713
 
714
 
715
        })
716
        .padding(.vertical, 16)
717
    }
718
}