@@ -23,7 +23,25 @@ struct AuthenticationExamples: View {
2323 Button ( " Dynamic Authentication " ) {
2424 self . showsDynamicAuth = true
2525 }
26-
26+
27+ NavigationLink (
28+ destination: PasscodePolicyAuthenticationExample ( showsIllustratedMessage: self . showsIllustratedMessage) )
29+ {
30+ Text ( " Passcode Policy " )
31+ }
32+
33+ NavigationLink (
34+ destination: CreatePasscodeFlowExample ( showsIllustratedMessage: self . showsIllustratedMessage) )
35+ {
36+ Text ( " Create Passcode Flow " )
37+ }
38+
39+ NavigationLink (
40+ destination: CustomInputPasscodePolicyExample ( showsIllustratedMessage: self . showsIllustratedMessage) )
41+ {
42+ Text ( " Passcode Policy (Custom Input) " )
43+ }
44+
2745 Toggle ( isOn: self . $showsIllustratedMessage) {
2846 Text ( " Show Illustration Message " )
2947 }
@@ -219,3 +237,176 @@ struct AuthenticationExample: View {
219237 messageType: self . messageType)
220238 }
221239}
240+
241+ // MARK: - Passcode Policy (single screen)
242+
243+ /// Demonstrates a single-screen passcode entry validated against a `FioriPasscodePolicy`.
244+ struct PasscodePolicyAuthenticationExample : View {
245+ @State private var passcode : String = " "
246+ @State var showsIllustratedMessage : Bool
247+
248+ private let policy : FioriPasscodePolicy = {
249+ var policy = FioriPasscodePolicy ( minLength: 8 , hasDigit: true , hasUpper: true , hasSpecial: true , minUniqueChars: 4 )
250+ policy. addPasscodeRule ( FioriPasscodeRule ( displayName: " At least 2 lowercase letters " , isDisplayed: true ) { passcode in
251+ passcode. unicodeScalars. filter { CharacterSet . lowercaseLetters. contains ( $0) } . count >= 2
252+ } )
253+ return policy
254+ } ( )
255+
256+ var body : some View {
257+ Authentication ( detailImage: {
258+ if self . showsIllustratedMessage {
259+ Image ( . illustration) . resizable ( ) . aspectRatio ( contentMode: . fit)
260+ }
261+ } , title: {
262+ Text ( " Create Passcode " )
263+ } , subtitle: {
264+ Text ( " Your passcode must meet the requirements below. " )
265+ } , isDisabled: !self . policy. validate ( passcode: self . passcode) ) {
266+ print ( " passcode accepted ...... " )
267+ }
268+ . authenticationStyle ( PasscodePolicyAuthenticationStyle ( passcode: self . $passcode, policy: self . policy) )
269+ . navigationBarTitle ( " Passcode Policy " , displayMode: . inline)
270+ }
271+ }
272+
273+ // MARK: - Create Passcode Flow (two steps: create -> confirm)
274+
275+ /// Demonstrates a two-step "create passcode" flow using `PasscodePolicyAuthenticationStyle`:
276+ /// 1. `CreatePasscodeView` — enter a new passcode that must satisfy the policy.
277+ /// 2. `ConfirmPasscodeView` — re-enter the passcode; it must match the one from step 1.
278+ struct CreatePasscodeFlowExample : View {
279+ @State var showsIllustratedMessage : Bool
280+
281+ /// The policy for the "create" step.
282+ private let policy : FioriPasscodePolicy = {
283+ var policy = FioriPasscodePolicy ( minLength: 8 , hasDigit: true , hasUpper: true , hasSpecial: true , minUniqueChars: 4 )
284+ policy. addPasscodeRule ( FioriPasscodeRule ( displayName: " At least 2 lowercase letters " , isDisplayed: true ) { passcode in
285+ passcode. unicodeScalars. filter { CharacterSet . lowercaseLetters. contains ( $0) } . count >= 2
286+ } )
287+ return policy
288+ } ( )
289+
290+ var body : some View {
291+ CreatePasscodeView ( policy: self . policy, showsIllustratedMessage: self . showsIllustratedMessage)
292+ }
293+ }
294+
295+ /// Step 1: enter a new passcode that satisfies the policy, then continue to confirmation.
296+ struct CreatePasscodeView : View {
297+ let policy : FioriPasscodePolicy
298+ var showsIllustratedMessage : Bool
299+
300+ @State private var passcode : String = " "
301+ @State private var showsConfirm : Bool = false
302+
303+ var body : some View {
304+ Authentication ( detailImage: {
305+ if self . showsIllustratedMessage {
306+ Image ( . illustration) . resizable ( ) . aspectRatio ( contentMode: . fit)
307+ }
308+ } , title: {
309+ Text ( " Create Passcode " )
310+ } , subtitle: {
311+ Text ( " Your passcode must meet the requirements below. " )
312+ } , signInAction: {
313+ FioriButton { _ in Text ( " Next " ) }
314+ } , isDisabled: !self . policy. validate ( passcode: self . passcode) ) {
315+ self . showsConfirm = true
316+ }
317+ . authenticationStyle ( PasscodePolicyAuthenticationStyle ( passcode: self . $passcode, policy: self . policy) )
318+ . navigationBarTitle ( " Create Passcode " , displayMode: . inline)
319+ . navigationDestination ( isPresented: self . $showsConfirm) {
320+ ConfirmPasscodeView ( originalPasscode: self . passcode, showsIllustratedMessage: self . showsIllustratedMessage)
321+ }
322+ }
323+ }
324+
325+ /// Step 2: re-enter the passcode; it must match the one created in step 1.
326+ struct ConfirmPasscodeView : View {
327+ let originalPasscode : String
328+ var showsIllustratedMessage : Bool
329+
330+ @Environment ( \. dismiss) private var dismiss
331+ @State private var passcode : String = " "
332+ @State private var isPresentedBanner : Bool = false
333+
334+ /// A policy whose single displayed requirement is "matches the original passcode".
335+ private var confirmPolicy : FioriPasscodePolicy {
336+ var policy = FioriPasscodePolicy ( minLength: 0 )
337+ let original = self . originalPasscode
338+ policy. addPasscodeRule ( FioriPasscodeRule ( displayName: " Matches the passcode above " , isDisplayed: true ) { passcode in
339+ !passcode. isEmpty && passcode == original
340+ } )
341+ return policy
342+ }
343+
344+ var body : some View {
345+ Authentication ( detailImage: {
346+ if self . showsIllustratedMessage {
347+ Image ( . illustration) . resizable ( ) . aspectRatio ( contentMode: . fit)
348+ }
349+ } , title: {
350+ Text ( " Confirm Passcode " )
351+ } , subtitle: {
352+ Text ( " Re-enter your passcode to confirm. " )
353+ } , signInAction: {
354+ FioriButton { _ in Text ( " Done " ) }
355+ } , isDisabled: !self . confirmPolicy. validate ( passcode: self . passcode) ) {
356+ print ( " passcode created ...... " )
357+ self . isPresentedBanner = true
358+ }
359+ . authenticationStyle ( PasscodePolicyAuthenticationStyle ( passcode: self . $passcode, policy: self . confirmPolicy) )
360+ . navigationBarTitle ( " Confirm Passcode " , displayMode: . inline)
361+ . bannerMessageView ( isPresented: self . $isPresentedBanner,
362+ pushContentDown: . constant( false ) ,
363+ icon: { EmptyView ( ) } ,
364+ title: " Passcode created successfully " ,
365+ bannerTapped: nil ,
366+ alignment: nil ,
367+ messageType: . positive)
368+ }
369+ }
370+
371+ // MARK: - Passcode Policy with custom authInput
372+
373+ /// Demonstrates supplying a custom `authInput` (multiple fields / custom styles) while still
374+ /// letting `PasscodePolicyAuthenticationStyle` append the live requirement checklist below it.
375+ /// The custom passcode field and the style share the same `passcode` binding, so the checklist
376+ /// updates as the user types.
377+ struct CustomInputPasscodePolicyExample : View {
378+ @State private var name : String = " "
379+ @State private var passcode : String = " "
380+ @State var showsIllustratedMessage : Bool
381+
382+ private let policy = FioriPasscodePolicy ( minLength: 8 , hasDigit: true , hasUpper: true , hasSpecial: true , minUniqueChars: 4 )
383+
384+ var body : some View {
385+ Authentication ( detailImage: {
386+ if self . showsIllustratedMessage {
387+ Image ( . illustration) . resizable ( ) . aspectRatio ( contentMode: . fit)
388+ }
389+ } , title: {
390+ Text ( " Create Passcode " )
391+ } , subtitle: {
392+ Text ( " Custom fields with an appended requirement checklist. " )
393+ } , authInput: {
394+ VStack ( spacing: 16 ) {
395+ TextFieldFormView ( title: " User Name: " , text: self . $name, placeholder: " Enter your name " )
396+ . textFieldFormViewStyle ( AuthTextFieldStyle ( ) )
397+ . titleStyle { config in
398+ config. title. font ( . fiori( forTextStyle: . headline, weight: . medium) )
399+ }
400+ TextFieldFormView ( title: " Passcode: " , text: self . $passcode, isSecureEnabled: true , placeholder: " Enter your passcode " )
401+ . textFieldFormViewStyle ( AuthTextFieldStyle ( ) )
402+ . titleStyle { config in
403+ config. title. font ( . fiori( forTextStyle: . headline, weight: . medium) )
404+ }
405+ }
406+ } , isDisabled: self . name. isEmpty || !self . policy. validate ( passcode: self . passcode) ) {
407+ print ( " passcode accepted ...... " )
408+ }
409+ . authenticationStyle ( PasscodePolicyAuthenticationStyle ( passcode: self . $passcode, policy: self . policy) )
410+ . navigationBarTitle ( " Custom Input " , displayMode: . inline)
411+ }
412+ }
0 commit comments