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}