Skip to content

Commit 97749c6

Browse files
committed
Added search, updated to have monospace
1 parent 2ae8151 commit 97749c6

File tree

3 files changed

+143
-75
lines changed

3 files changed

+143
-75
lines changed

boringNotch/Localizable.xcstrings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15669,6 +15669,9 @@
1566915669
}
1567015670
}
1567115671
}
15672+
},
15673+
"Search" : {
15674+
1567215675
},
1567315676
"Select the music source you want to use. You can change this later in the app settings." : {
1567415677
"localizations" : {

boringNotch/components/Notch/NotchNotesView.swift

Lines changed: 79 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@ import SwiftUI
99

1010
struct NotchNotesView: View {
1111
@StateObject var notesManager = NotesManager()
12+
@State private var searchText: String = ""
1213

1314
var body: some View {
1415
GeometryReader { geo in
1516
HStack(spacing: 0) {
16-
// editor panel
1717
VStack {
1818
if notesManager.notes.indices.contains(notesManager.selectedNoteIndex) {
1919
ZStack(alignment: .topLeading) {
20-
if notesManager.notes[notesManager.selectedNoteIndex].content.isEmpty {
20+
let currentNote = notesManager.notes[notesManager.selectedNoteIndex]
21+
22+
if currentNote.content.isEmpty {
2123
Text("Enter your note...")
2224
.foregroundColor(.gray)
2325
.padding(.leading, 8)
@@ -27,24 +29,57 @@ struct NotchNotesView: View {
2729
.allowsHitTesting(false)
2830
}
2931

30-
TextEditor(
31-
text: Binding(
32-
get: {
33-
guard notesManager.selectedNoteIndex < notesManager.notes.count else { return "" }
34-
return notesManager.notes[notesManager.selectedNoteIndex].content
35-
},
36-
set: { newValue in
37-
guard notesManager.selectedNoteIndex < notesManager.notes.count else { return }
38-
notesManager.save(note: newValue, at: notesManager.selectedNoteIndex)
39-
}
32+
ZStack(alignment: .bottomTrailing) {
33+
TextEditor(
34+
text: Binding(
35+
get: {
36+
guard notesManager.selectedNoteIndex < notesManager.notes.count else { return "" }
37+
return notesManager.notes[notesManager.selectedNoteIndex].content
38+
},
39+
set: { newValue in
40+
guard notesManager.selectedNoteIndex < notesManager.notes.count else { return }
41+
notesManager.save(note: newValue, at: notesManager.selectedNoteIndex)
42+
}
43+
)
44+
)
45+
.fontWeight(.thin)
46+
.font(
47+
notesManager.notes.indices.contains(notesManager.selectedNoteIndex) &&
48+
notesManager.notes[notesManager.selectedNoteIndex].isMonospaced
49+
? .system(.caption, design: .monospaced)
50+
: .caption
4051
)
41-
)
42-
.fontWeight(.ultraLight)
43-
.fontWidth(.expanded)
44-
.textEditorStyle(.plain)
45-
.transition(.opacity.combined(with: .blurReplace))
46-
.id(notesManager.selectedNoteIndex)
47-
.padding(4)
52+
.animation(
53+
.easeInOut(duration: 0.2),
54+
value: notesManager.notes[notesManager.selectedNoteIndex].isMonospaced
55+
)
56+
.textEditorStyle(.plain)
57+
.transition(.opacity.combined(with: .blurReplace))
58+
.id(notesManager.selectedNoteIndex)
59+
.padding(4)
60+
61+
Button {
62+
notesManager.toggleMonospaced(at: notesManager.selectedNoteIndex)
63+
} label: {
64+
Image(systemName: "textformat")
65+
.padding(5)
66+
.background(
67+
ZStack {
68+
Rectangle()
69+
.fill(.ultraThinMaterial)
70+
if notesManager.notes.indices.contains(notesManager.selectedNoteIndex) &&
71+
notesManager.notes[notesManager.selectedNoteIndex].isMonospaced
72+
{
73+
Rectangle()
74+
.fill(Color.white.opacity(0.15))
75+
}
76+
}
77+
)
78+
.clipShape(RoundedRectangle(cornerRadius: 8))
79+
.padding(3)
80+
}
81+
.buttonStyle(.plain)
82+
}
4883
}
4984
.background(
5085
RoundedRectangle(cornerRadius: 10)
@@ -55,30 +90,40 @@ struct NotchNotesView: View {
5590
.animation(.easeInOut, value: notesManager.selectedNoteIndex)
5691
.frame(width: (geo.size.width / 3) * 2)
5792

58-
// sidebar
5993
VStack {
6094
ScrollView(.vertical) {
61-
Button {
62-
notesManager.addNote()
63-
notesManager.selectedNoteIndex = notesManager.notes.count - 1
64-
} label: {
65-
Image(systemName: "plus")
95+
HStack {
96+
TextField("Search", text: $searchText)
97+
.textFieldStyle(.plain)
6698
.frame(maxWidth: .infinity)
99+
.padding(3)
100+
.padding(.leading, 3)
67101
.frame(height: 25)
68-
.padding(.vertical, 1)
69102
.background(Color.white.opacity(0.1))
70103
.clipShape(RoundedRectangle(cornerRadius: 8))
71104
.contentShape(RoundedRectangle(cornerRadius: 8))
105+
106+
Button {
107+
notesManager.addNote()
108+
} label: {
109+
Image(systemName: "plus")
110+
.frame(width: 25, height: 25)
111+
.padding(.bottom, 1)
112+
.background(Color.white.opacity(0.1))
113+
.clipShape(RoundedRectangle(cornerRadius: 8))
114+
.contentShape(RoundedRectangle(cornerRadius: 8))
115+
}
116+
.buttonStyle(.plain)
72117
}
73-
.buttonStyle(.plain)
74118

75119
LazyVStack(spacing: 4) {
76-
ForEach(Array(notesManager.notes.enumerated()), id: \.element.id) {
77-
index,
78-
note in
120+
let displayed = Array(notesManager.notes.enumerated())
121+
.filter { searchText.isEmpty || $0.element.content.localizedCaseInsensitiveContains(searchText) }
122+
123+
ForEach(displayed, id: \.element.id) { originalIndex, note in
79124
HStack {
80125
Button {
81-
notesManager.selectedNoteIndex = index
126+
notesManager.selectedNoteIndex = originalIndex
82127
} label: {
83128
HStack {
84129
if !note.content.isEmpty {
@@ -96,7 +141,7 @@ struct NotchNotesView: View {
96141
.animation(.easeInOut, value: note.content)
97142
.frame(maxWidth: .infinity, alignment: .center)
98143
.background(
99-
notesManager.selectedNoteIndex == index
144+
notesManager.selectedNoteIndex == originalIndex
100145
? Color.accentColor.opacity(0.4)
101146
: Color.white.opacity(0.1)
102147
)
@@ -106,7 +151,7 @@ struct NotchNotesView: View {
106151
.buttonStyle(.plain)
107152

108153
Button {
109-
notesManager.removeNote(at: index)
154+
notesManager.removeNote(at: originalIndex)
110155
} label: {
111156
Label("Delete", systemImage: "trash")
112157
.foregroundStyle(.white.opacity(0.7))
@@ -131,12 +176,9 @@ struct NotchNotesView: View {
131176
.frame(width: geo.size.width / 3)
132177
.clipShape(RoundedRectangle(cornerRadius: 8))
133178
}
179+
.preferredColorScheme(.dark)
134180
.frame(height: geo.size.height)
135181
}
136182
.transition(.opacity.combined(with: .blurReplace))
137183
}
138184
}
139-
140-
#Preview {
141-
NotchNotesView()
142-
}
Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
1-
//  NotesManager.swift
2-
//  boringNotch
3-
//
4-
//  Created by Adon Omeri on 31/8/2025.
5-
//
6-
7-
1+
//  NotesManager.swift
2+
//  boringNotch
3+
//
4+
//  Created by Adon Omeri on 31/8/2025.
5+
//
86

97
import Foundation
108

11-
struct Note: Identifiable {
9+
// MARK: - Model + Manager
10+
11+
struct Note: Identifiable, Codable {
1212
let id: Int
1313
var content: String
14+
var lastEdited: Date
15+
var isMonospaced: Bool
1416
}
1517

16-
class NotesManager: ObservableObject {
18+
final class NotesManager: ObservableObject {
19+
@Published var notes: [Note] = []
20+
@Published var selectedNoteIndex: Int = 0
21+
1722
private let notesFolderURL: URL
1823
private let fileManager = FileManager.default
1924

20-
@Published var selectedNoteIndex = 0
21-
@Published var notes: [Note] = []
22-
2325
init() {
2426
let appSupport = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
2527
notesFolderURL = appSupport.appendingPathComponent("Notes")
@@ -34,56 +36,77 @@ class NotesManager: ObservableObject {
3436
func loadAllNotes() {
3537
do {
3638
let fileURLs = try fileManager.contentsOfDirectory(at: notesFolderURL, includingPropertiesForKeys: nil)
37-
let txtFiles = fileURLs
38-
.filter { $0.pathExtension == "txt" }
39-
.compactMap { url -> (Int, URL)? in
40-
let nameWithoutExtension = url.deletingPathExtension().lastPathComponent
41-
guard let index = Int(nameWithoutExtension) else { return nil }
42-
return (index, url)
43-
}
44-
.sorted { $0.0 < $1.0 }
45-
46-
notes = txtFiles.map { index, url in
47-
let content = (try? String(contentsOf: url, encoding: .utf8)) ?? ""
48-
return Note(id: index, content: content)
39+
let jsonFiles = fileURLs.filter { $0.pathExtension.lowercased() == "json" }
40+
41+
notes = try jsonFiles.compactMap { url in
42+
let data = try Data(contentsOf: url)
43+
return try JSONDecoder().decode(Note.self, from: data)
4944
}
5045
} catch {
5146
notes = []
5247
}
5348

5449
if notes.isEmpty {
5550
addNote()
51+
} else {
52+
sortNotesByEdited()
5653
}
5754
}
5855

59-
func save(note: String, at index: Int) {
60-
guard index >= 0 && index < notes.count else { return }
61-
let noteID = notes[index].id
62-
let url = notesFolderURL.appendingPathComponent("\(noteID).txt")
63-
try? note.write(to: url, atomically: true, encoding: .utf8)
64-
notes[index].content = note
56+
func save(note: Note) {
57+
guard let index = notes.firstIndex(where: { $0.id == note.id }) else { return }
58+
notes[index] = note
59+
let url = notesFolderURL.appendingPathComponent("\(note.id).json")
60+
if let data = try? JSONEncoder().encode(note) {
61+
try? data.write(to: url, options: .atomic)
62+
}
63+
}
64+
65+
func save(note content: String, at index: Int) {
66+
guard index >= 0, index < notes.count else { return }
67+
var note = notes[index]
68+
note.content = content
69+
note.lastEdited = Date()
70+
save(note: note)
71+
sortNotesByEdited(preserveSelectedID: note.id)
6572
}
6673

6774
func addNote() {
6875
let newID = (notes.map(\.id).max() ?? -1) + 1
69-
let newNote = Note(id: newID, content: "")
76+
let newNote = Note(id: newID, content: "", lastEdited: Date(), isMonospaced: false)
7077
notes.append(newNote)
71-
72-
let url = notesFolderURL.appendingPathComponent("\(newID).txt")
73-
try? "".write(to: url, atomically: true, encoding: .utf8)
78+
save(note: newNote)
79+
sortNotesByEdited(preserveSelectedID: newNote.id)
7480
}
7581

7682
func removeNote(at index: Int) {
77-
guard index >= 0 && index < notes.count && notes.count > 1 else { return }
78-
83+
guard index >= 0, index < notes.count, notes.count > 1 else { return }
7984
let noteID = notes[index].id
80-
let url = notesFolderURL.appendingPathComponent("\(noteID).txt")
85+
let url = notesFolderURL.appendingPathComponent("\(noteID).json")
8186
try? fileManager.removeItem(at: url)
82-
8387
notes.remove(at: index)
8488

8589
if selectedNoteIndex >= notes.count {
8690
selectedNoteIndex = notes.count - 1
8791
}
8892
}
93+
94+
func toggleMonospaced(at index: Int) {
95+
guard index >= 0, index < notes.count else { return }
96+
var note = notes[index]
97+
note.isMonospaced.toggle()
98+
note.lastEdited = Date()
99+
save(note: note)
100+
sortNotesByEdited(preserveSelectedID: note.id)
101+
}
102+
103+
private func sortNotesByEdited(preserveSelectedID preservedID: Int? = nil) {
104+
let selectedIDBefore: Int? = preservedID ?? (notes.indices.contains(selectedNoteIndex) ? notes[selectedNoteIndex].id : nil)
105+
notes.sort { $0.lastEdited > $1.lastEdited }
106+
if let id = selectedIDBefore, let newIndex = notes.firstIndex(where: { $0.id == id }) {
107+
selectedNoteIndex = newIndex
108+
} else {
109+
selectedNoteIndex = min(selectedNoteIndex, notes.count - 1)
110+
}
111+
}
89112
}

0 commit comments

Comments
 (0)