Skip to content

Commit 211bd61

Browse files
committed
feat: make document created date optional and add clearable picker
Make the `created` field nullable in `DocumentModel`. Update initializers and state handling to default to `nil` when the user chooses not to include a date. Add a new `ClearableDatePickerView` that shows an optional date picker with a clear button and optional title. Replace the old date picker in `CreateDocumentView` with this component, keeping logic to remember whether a date was set. Adjust networking code to skip the created field when it is nil, and update API serialization accordingly. Add preview for the new view. This removes mandatory date entry, improves UX with a clearable picker, and keeps data consistency across the app.
1 parent ea7d263 commit 211bd61

File tree

5 files changed

+109
-23
lines changed

5 files changed

+109
-23
lines changed

DataModel/Sources/DataModel/Model/DocumentModel.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ public struct ProtoDocument: DocumentProtocol, Equatable, Sendable {
167167
public var documentType: UInt?
168168
public var correspondent: UInt?
169169
public var tags: [UInt]
170-
public var created: Date
170+
public var created: Date?
171171
public var storagePath: UInt?
172172

173173
public var customFields = CustomFieldRawEntryList()
174174

175175
public init(
176176
title: String = "", asn: UInt? = nil, documentType: UInt? = nil, correspondent: UInt? = nil,
177-
tags: [UInt] = [], created: Date = .now, storagePath: UInt? = nil
177+
tags: [UInt] = [], created: Date? = .now, storagePath: UInt? = nil
178178
) {
179179
self.title = title
180180
self.asn = asn

Networking/Sources/Networking/Api/ApiRepository.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,12 @@ extension ApiRepository: Repository {
448448
mp.add(name: "correspondent", string: String(corr))
449449
}
450450

451-
let formatter = DateFormatter()
452-
formatter.dateFormat = "yyyy-MM-dd"
453-
let createdStr = formatter.string(from: document.created)
454-
mp.add(name: "created", string: createdStr)
451+
if let created = document.created {
452+
let formatter = DateFormatter()
453+
formatter.dateFormat = "yyyy-MM-dd"
454+
let createdStr = formatter.string(from: created)
455+
mp.add(name: "created", string: createdStr)
456+
}
455457

456458
if let dt = document.documentType {
457459
mp.add(name: "document_type", string: String(dt))

Networking/Sources/Networking/Api/TransientRepository.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ extension TransientRepository: Repository {
166166
asn: document.asn,
167167
documentType: document.documentType,
168168
correspondent: document.correspondent,
169-
created: document.created,
169+
created: document.created ?? .now,
170170
tags: document.tags,
171171
added: .now,
172172
modified: .now,

swift-paperless/Views/Filter/ClearableDatePickerView.swift

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,26 @@ import SwiftUI
99

1010
struct ClearableDatePickerView: View {
1111
@Binding var value: Date?
12+
let title: LocalizedStringResource?
13+
14+
init(value: Binding<Date?>, title: LocalizedStringResource? = nil) {
15+
self._value = value
16+
self.title = title
17+
}
1218

1319
var body: some View {
1420
HStack {
21+
Text(title ?? "")
22+
.frame(maxWidth: .infinity, alignment: .leading)
1523
if let unwrapped = Binding(unwrapping: $value) {
16-
DatePicker(selection: unwrapped, displayedComponents: .date) {
17-
Image(systemName: "xmark.circle.fill")
18-
.foregroundColor(.secondary)
19-
.accessibilityLabel(String(localized: .localizable(.dateClear)))
20-
.contentShape(Rectangle())
21-
.frame(maxWidth: .infinity, alignment: .trailing)
22-
.onTapGesture {
23-
value = nil
24-
}
25-
}
24+
Image(systemName: "xmark.circle.fill")
25+
.foregroundColor(.secondary)
26+
.accessibilityLabel(String(localized: .localizable(.dateClear)))
27+
.contentShape(Rectangle())
28+
.onTapGesture {
29+
value = nil
30+
}
31+
DatePicker(selection: unwrapped, displayedComponents: .date) {}
2632
} else {
2733
HStack {
2834
Image(systemName: "plus.circle.fill")
@@ -37,3 +43,37 @@ struct ClearableDatePickerView: View {
3743
.animation(.spring, value: value)
3844
}
3945
}
46+
47+
private struct ClearableDatePickerPreview: View {
48+
@State private var emptyValue: Date?
49+
@State private var setValue: Date?
50+
@State private var noTitleValue: Date?
51+
52+
init() {
53+
_emptyValue = State(initialValue: nil)
54+
_setValue = State(initialValue: Date(timeIntervalSince1970: 0))
55+
_noTitleValue = State(initialValue: nil)
56+
}
57+
58+
var body: some View {
59+
Form {
60+
Section("With Title") {
61+
ClearableDatePickerView(
62+
value: $emptyValue,
63+
title: "No Date Set"
64+
)
65+
ClearableDatePickerView(
66+
value: $setValue,
67+
title: "Date Set"
68+
)
69+
}
70+
Section("No Title") {
71+
ClearableDatePickerView(value: $noTitleValue)
72+
}
73+
}
74+
}
75+
}
76+
77+
#Preview("ClearableDatePickerView") {
78+
ClearableDatePickerPreview()
79+
}

swift-paperless/Views/Import/CreateDocumentView.swift

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ private struct DetailView: View {
103103
}
104104
}
105105

106+
extension ProtoDocument {
107+
fileprivate var createdDate: Date {
108+
get {
109+
created ?? .now
110+
}
111+
set {
112+
created = newValue
113+
}
114+
}
115+
}
116+
106117
struct CreateDocumentView: View {
107118
private enum Status {
108119
case none
@@ -124,7 +135,10 @@ struct CreateDocumentView: View {
124135
@State private var status = Status.none
125136
@State private var isAsnValid = true
126137

127-
@AppStorage("CreateDocumentUseOriginalTitle", store: .group, )
138+
@AppStorage("IncludeDocumentCreatedDate", store: .group)
139+
private var includeCreatedDate = true
140+
141+
@AppStorage("CreateDocumentUseOriginalTitle", store: .group)
128142
private var useOriginalTitle = false
129143

130144
private var isDocumentValid: Bool {
@@ -142,12 +156,14 @@ struct CreateDocumentView: View {
142156
sourceUrl = url
143157
let initialTitle = url.deletingPathExtension().lastPathComponent
144158
.precomposedStringWithCanonicalMapping
145-
_document = State(
146-
initialValue: ProtoDocument(title: initialTitle))
147159
self.callback = callback
148160
self.share = share
149161
self.title = title ?? String(localized: .localizable(.documentAdd))
150162
thumbnailView = ThumbnailView(sourceUrl: sourceUrl)
163+
164+
let initialDocument = ProtoDocument(
165+
title: initialTitle, created: includeCreatedDate ? .now : nil)
166+
_document = State(initialValue: initialDocument)
151167
}
152168

153169
func upload() async {
@@ -183,6 +199,7 @@ struct CreateDocumentView: View {
183199
document.correspondent = nil
184200
document.tags = []
185201
document.storagePath = nil
202+
document.created = includeCreatedDate ? .now : nil
186203
}
187204

188205
private var filename: String {
@@ -246,9 +263,31 @@ struct CreateDocumentView: View {
246263
DocumentAsnEditingView(document: $document, isValid: $isAsnValid)
247264
.alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
248265

249-
DatePicker(
250-
String(localized: .localizable(.documentEditCreatedDateLabel)),
251-
selection: $document.created, displayedComponents: .date)
266+
// Toggle(
267+
// .localizable(.documentEditCreatedDateLabel),
268+
// isOn: $includeCreatedDate
269+
// )
270+
271+
// .onChange(of: includeCreatedDate) { _, include in
272+
// if include {
273+
// if document.created == nil {
274+
// document.created = .now
275+
// }
276+
// } else {
277+
// document.created = nil
278+
// }
279+
// }
280+
281+
ClearableDatePickerView(
282+
value: $document.created, title: .localizable(.documentEditCreatedDateLabel))
283+
284+
// if includeCreatedDate {
285+
// DatePicker(
286+
// String(localized: .localizable(.documentEditCreatedDateLabel)),
287+
// selection: $document.createdDate,
288+
// displayedComponents: .date
289+
// )
290+
// }
252291

253292
Toggle(.localizable(.documentUploadUseOriginalFilename), isOn: $useOriginalTitle)
254293
}
@@ -413,6 +452,11 @@ struct CreateDocumentView: View {
413452
default: break
414453
}
415454
}
455+
456+
// When the user adds or removes the date, we remember that for the future
457+
.onChange(of: document.created) {
458+
includeCreatedDate = document.created != nil
459+
}
416460
}
417461

418462
.errorOverlay(errorController: errorController, offset: 20)

0 commit comments

Comments
 (0)