krz/libre-edit

A free and private text editor for iOS. ios privacy swift text-editor

Commit b5a5dc8963

b5a5dc8963dbd73d30c188eb9f3018add76a2c06

parent: 9e2c7e0954

Verified · cmc

cmc <hello@cleberg.net> · 2024-01-14 17:09 UTC

feat: update app to use native file manager as the base

Layout: unified · split

LibreEdit/ContentView.swift +54 −82
@@ -6,104 +6,81 @@
6// 6//
7 7
8import SwiftUI 8import SwiftUI
9import UniformTypeIdentifiers
9 10
10func getDatetime() -> String { 11struct TextFile: FileDocument {
11 let dateFormatter = DateFormatter() 12 // tell the system we support only plain text
12 let enUSPosixLocale = Locale(identifier: "en_US_POSIX") 13 static var readableContentTypes = [UTType.plainText]
13 dateFormatter.locale = enUSPosixLocale
14 dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
15 dateFormatter.calendar = Calendar(identifier: .gregorian)
16 14
17 let iso8601String = dateFormatter.string(from: Date()) 15 // by default our document is empty
16 var text = ""
18 17
19 return iso8601String 18 // a simple initializer that creates new, empty documents
19 init(initialText: String = "") {
20 text = initialText
21 }
22
23 // this initializer loads data that has been saved previously
24 init(configuration: ReadConfiguration) throws {
25 if let data = configuration.file.regularFileContents {
26 text = String(decoding: data, as: UTF8.self)
27 } else {
28 throw CocoaError(.fileReadCorruptFile)
29 }
30 }
31
32 // this will be called when the system wants to write our data to disk
33 func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
34 let data = Data(text.utf8)
35 return FileWrapper(regularFileWithContents: data)
36 }
20} 37}
21 38
22// Ensure that `Supports Document Browser` = YES in the `ios-edit.xcodeproj` file, 39func countWords(text: String) -> Int {
23// so that users can browse saved files in the Files app 40 let words = text.split { $0 == " " || $0.isNewline }
24func getDocumentsDirectory() -> URL { 41 return words.count
25 let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
26 let documentsDirectory = paths[0]
27 return documentsDirectory
28} 42}
29 43
30struct ContentView: View { 44struct ContentView: View {
31 enum FocusedField { 45 @Binding var document: TextFile
32 case content
33 }
34
35 @State private var content = ""
36 @State private var wordCount: Int = 0
37 @State private var showingPopover = false 46 @State private var showingPopover = false
38 @FocusState private var focusedField: FocusedField? 47 @State private var wordCount: Int = 0
39 48
40 var body: some View { 49 var body: some View {
41 NavigationView { 50 Text("\(wordCount) words")
42 VStack(alignment: .leading) { 51 .foregroundColor(Color.white)
43 Text("\(wordCount) words") 52 .font(.headline)
44 .font(.headline) 53 .padding(.trailing)
45 .foregroundColor(.secondary) 54 .frame(maxWidth: .infinity, maxHeight: 50)
46 .padding(.trailing) 55 .background(Color.gray.opacity(0.3))
47 TextEditor(text: $content) 56 .padding(.top, 20)
48 .onChange(of: content) { 57 TextEditor(text: $document.text)
49 let words = content.split { $0 == " " || $0.isNewline }
50 self.wordCount = words.count
51 }
52 .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
53 .focused($focusedField, equals: .content)
54 }
55 .onAppear { 58 .onAppear {
56 focusedField = .content 59 self.wordCount = countWords(text: document.text)
60 }
61 .onChange(of: document.text) {
62 self.wordCount = countWords(text: document.text)
57 } 63 }
58 .padding()
59 .navigationTitle("Libre Edit")
60 .toolbar { 64 .toolbar {
61 ToolbarItemGroup(placement: .primaryAction) {
62 Button(action: {
63 do {
64 let data = Data(self.content.utf8)
65 let url = URL.documentsDirectory.appending(path: (getDatetime() + ".txt"))
66 try data.write(to: url, options: [.atomic, .completeFileProtection])
67
68 // TODO: Alerts currently aren't working/aren't visible
69 Text("File saved successfully!")
70 .foregroundColor(.white)
71 .bold()
72 .font(.footnote)
73 .frame(width: 140, height: 30)
74 .background(Color.indigo.cornerRadius(7))
75 print("Saved content to file: \(url)")
76 } catch {
77 // TODO: Alerts currently aren't working/aren't visible
78 Text("Failed to save file.")
79 .foregroundColor(.white)
80 .bold()
81 .font(.footnote)
82 .frame(width: 140, height: 30)
83 .background(Color.red.cornerRadius(7))
84 print("Error saving content to file: \(error)")
85 }
86 }) {
87 Text("Save")
88 }
89 // TODO: Confirm (possibly save) & then clear TextEditor
90 Button(action: {
91 print("TODO: This will create a new note.")
92 }) {
93 Text("New")
94 }
95 }
96 ToolbarItemGroup(placement: .secondaryAction) { 65 ToolbarItemGroup(placement: .secondaryAction) {
97 Button { 66 Button {
98 showingPopover = true 67 showingPopover = true
99 } label: { 68 } label: {
100 Text("App Info") 69 Text("More Info")
101 }.popover(isPresented: $showingPopover) { 70 }.popover(isPresented: $showingPopover) {
102 VStack(alignment: .leading) { 71 VStack(alignment: .leading) {
103 Text("App Info") 72 Text("More Info")
104 .font(.largeTitle) 73 .font(.largeTitle)
105 Text("") 74 Text("")
106 Text("Libre Edit is a free and open source text editor for iOS built by [Christian Cleberg](https://cleberg.net).") 75 Text("Instructions")
76 .font(.title)
77 Text("")
78 Text("LibreEdit provides a direct interface with the Apple Files app on your iPhone. Simply navigate to your preferred directory, edit existing files, or create new files!")
79 Text("")
80 Text("Developer")
81 .font(.title)
82 Text("")
83 Text("LibreEdit is a free and open source text editor for iOS built by [Christian Cleberg](https://cleberg.net).")
107 Text("") 84 Text("")
108 Text("Visit the [GitHub Repository](https://github.com/ccleberg/ios-edit) to view the source code.") 85 Text("Visit the [GitHub Repository](https://github.com/ccleberg/ios-edit) to view the source code.")
109 Text("") 86 Text("")
@@ -114,10 +91,5 @@ struct ContentView: View {
114 } 91 }
115 } 92 }
116 } 93 }
117 }
118 } 94 }
119} 95}
120
121#Preview {
122 ContentView()
123}
LibreEdit/LibreEditApp.swift +2 −2
@@ -10,8 +10,8 @@ import SwiftUI
10@main 10@main
11struct LibreEditApp: App { 11struct LibreEditApp: App {
12 var body: some Scene { 12 var body: some Scene {
13 WindowGroup { 13 DocumentGroup(newDocument: TextFile()) { file in
14 ContentView() 14 ContentView(document: file.$document)
15 } 15 }
16 } 16 }
17} 17}