krz/libre-secrets

A free and private password manager for iOS.

clone: git clone https://gitbay.org/krz/libre-secrets.git

main: LibreSecrets/ContentView.swift · raw

  1//
  2//  ContentView.swift
  3//  LibreSecrets
  4//
  5//  Created by Christian Cleberg on 2024-01-10.
  6//
  7
  8import SwiftUI
  9import UniformTypeIdentifiers
 10
 11struct ContentView: View {
 12    // Create initial variables
 13    @State private var speed = 50.0
 14    @State private var isEditing = false
 15    @State private var enableNumbers = false
 16    @State private var enableSpecial = false
 17    @State private var enableCapitalization = false
 18    @State private var isCopied: Bool = false
 19    
 20    // Create Picker options to choose password type
 21    enum PasswordType: String, CaseIterable, Identifiable {
 22        case random, xkcd
 23        var id: Self { self }
 24    }
 25    @State private var passwordType: PasswordType = .random
 26    
 27    // Generates a random string of alphanumeric, numeric (optional), and special (optional) characters per user-determined length
 28    func randomString(length: Int) -> String {
 29        var characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 30        if enableNumbers {
 31            characters.append("0123456789")
 32        }
 33        if enableSpecial {
 34            characters.append("!%&()*+,-./:;<=>?@[^_`{|}~")
 35        }
 36        return String((0..<length).map{ _ in characters.randomElement()! })
 37    }
 38    
 39    // Generates a series of words separated by "-", including a digit (optional), and capitalization (optional) per user-determined length
 40    func randomWord(length: Int) -> String {
 41        var randomLine = ""
 42        for i in 1...length {
 43            if let wordsFilePath = Bundle.main.path(forResource: "words", ofType: nil) {
 44                do {
 45                    let wordsString = try String(contentsOfFile: wordsFilePath)
 46                    let wordLines = wordsString.components(separatedBy: .newlines)
 47                    if enableCapitalization {
 48                        randomLine += wordLines[numericCast(arc4random_uniform(numericCast(wordLines.count)))].capitalized
 49                    } else {
 50                        randomLine += wordLines[numericCast(arc4random_uniform(numericCast(wordLines.count)))]
 51                    }
 52                    if i != length {
 53                        randomLine += "-"
 54                    }
 55                    if i == length && enableNumbers {
 56                        randomLine += String(Int.random(in: 0..<9))
 57                    }
 58                } catch { // contentsOfFile throws an error
 59                    print("Error: \(error)")
 60                }
 61            }
 62        }
 63        return randomLine
 64    }
 65
 66    // Generate the view
 67    var body: some View {
 68        VStack {
 69            
 70            VStack {
 71                Text("Password Generator")
 72                    .font(.largeTitle)
 73                Text("Save your password somewhere safe!")
 74                    .font(.caption)
 75            }
 76            
 77            Form {
 78                Section(header: Text("Password Type")) {
 79                    Picker("Type", selection: $passwordType) {
 80                        Text("Random").tag(PasswordType.random)
 81                        Text("XKCD").tag(PasswordType.xkcd)
 82                    }
 83                }
 84                
 85                if passwordType == .random {
 86                    Section(header: Text("Random Password")) {
 87                        Slider(
 88                            value: $speed,
 89                            in: 8...36,
 90                            step: 1
 91                        ) {
 92                            Text("Characters")
 93                        } minimumValueLabel: {
 94                            Text("8")
 95                        } maximumValueLabel: {
 96                            Text("36")
 97                        } onEditingChanged: { editing in
 98                            isEditing = editing
 99                        }
100                        .onAppear {
101                            self.speed = 12
102                        }
103                        Toggle("Numbers", isOn: $enableNumbers)
104                        Toggle("Special Characters", isOn: $enableSpecial)
105                    }
106                    
107                    let password = randomString(length: Int(speed))
108                    
109                    Text("\(password)")
110                        .onTapGesture {
111                            let clipboard = UIPasteboard.general
112                            clipboard.setValue(password, forPasteboardType: UTType.plainText.identifier)
113                            withAnimation {
114                                isCopied = true
115                            }
116                            DispatchQueue.main.asyncAfter(wallDeadline: .now() + 3) {
117                                withAnimation {
118                                    isCopied = false
119                                }
120                            }
121                        }
122                } else {
123                    Section(header: Text("XKCD Password")) {
124                        Slider(
125                            value: $speed,
126                            in: 1...10,
127                            step: 1
128                        ) {
129                            Text("Words")
130                        } minimumValueLabel: {
131                            Text("1")
132                        } maximumValueLabel: {
133                            Text("10")
134                        } onEditingChanged: { editing in
135                            isEditing = editing
136                        }
137                        .onAppear {
138                            self.speed = 4
139                        }
140                        Toggle("Numbers", isOn: $enableNumbers)
141                        Toggle("Capitalize Words", isOn: $enableCapitalization)
142                    }
143                    
144                    let password = randomWord(length: Int(speed))
145                    
146                    Text("\(password)")
147                        .onTapGesture {
148                            let clipboard = UIPasteboard.general
149                            clipboard.setValue(password, forPasteboardType: UTType.plainText.identifier)
150                            withAnimation {
151                                isCopied = true
152                            }
153                            DispatchQueue.main.asyncAfter(wallDeadline: .now() + 3) {
154                                withAnimation {
155                                    isCopied = false
156                                }
157                            }
158                        }
159                }
160            }
161            
162            if isCopied {
163                Text("Copied successfully!")
164                    .foregroundColor(.white)
165                    .bold()
166                    .font(.footnote)
167                    .frame(width: 140, height: 30)
168                    .background(Color.indigo.cornerRadius(7))
169            }
170            
171        }
172        .padding()
173    }
174}
175
176#Preview {
177    ContentView()
178}