Proyectos de Subversion Iphone Microlearning - Inconcert

Rev

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