Proyectos de Subversion Iphone Microlearning - Nuevo Interface

Rev

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