-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve item detail fields #1051
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,16 +10,18 @@ import Foundation | |
|
||
import RealmSwift | ||
|
||
struct EditItemFieldsDbRequest: DbRequest { | ||
let key: String | ||
let libraryId: LibraryIdentifier | ||
let fieldValues: [KeyBaseKeyPair: String] | ||
let dateParser: DateParser | ||
protocol EditItemFieldsBaseRequest { | ||
var key: String { get } | ||
var libraryId: LibraryIdentifier { get } | ||
var fieldValues: [KeyBaseKeyPair: String] { get } | ||
var dateParser: DateParser { get } | ||
|
||
var needsWrite: Bool { return true } | ||
func processAndReturnResponse(in database: Realm) throws -> Date? | ||
} | ||
|
||
func process(in database: Realm) throws { | ||
guard !fieldValues.isEmpty, let item = database.objects(RItem.self).uniqueObject(key: key, libraryId: libraryId) else { return } | ||
extension EditItemFieldsBaseRequest { | ||
func processAndReturnResponse(in database: Realm) throws -> Date? { | ||
guard !fieldValues.isEmpty, let item = database.objects(RItem.self).uniqueObject(key: key, libraryId: libraryId) else { return nil } | ||
|
||
var didChange = false | ||
|
||
|
@@ -60,6 +62,37 @@ struct EditItemFieldsDbRequest: DbRequest { | |
item.changes.append(RObjectChange.create(changes: RItemChanges.fields)) | ||
item.changeType = .user | ||
item.dateModified = Date() | ||
return item.dateModified | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
struct EditItemFieldsDbRequest: EditItemFieldsBaseRequest, DbRequest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this used anywhere? Why separate into 2 same requests where the distinction is that one returns and the other one doesn't? Can't we just ignore the response when it's not needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's used in
|
||
let key: String | ||
let libraryId: LibraryIdentifier | ||
let fieldValues: [KeyBaseKeyPair: String] | ||
let dateParser: DateParser | ||
|
||
var needsWrite: Bool { return true } | ||
|
||
func process(in database: Realm) throws { | ||
_ = try processAndReturnResponse(in: database) | ||
} | ||
} | ||
|
||
struct EditItemFieldsDbResponseRequest: EditItemFieldsBaseRequest, DbResponseRequest { | ||
typealias Response = Date? | ||
|
||
let key: String | ||
let libraryId: LibraryIdentifier | ||
let fieldValues: [KeyBaseKeyPair: String] | ||
let dateParser: DateParser | ||
|
||
var needsWrite: Bool { return true } | ||
|
||
func process(in database: Realm) throws -> Date? { | ||
return try processAndReturnResponse(in: database) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -7,6 +7,7 @@ | |||||||||||||||||
// | ||||||||||||||||||
|
||||||||||||||||||
import Foundation | ||||||||||||||||||
import OrderedCollections | ||||||||||||||||||
|
||||||||||||||||||
import CocoaLumberjackSwift | ||||||||||||||||||
|
||||||||||||||||||
|
@@ -67,7 +68,7 @@ struct ItemDetailDataCreator { | |||||||||||||||||
throw ItemDetailError.cantCreateData | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
let (fieldIds, fields, hasAbstract) = try fieldData(for: itemType, schemaController: schemaController, dateParser: dateParser, urlDetector: urlDetector, doiDetector: doiDetector) | ||||||||||||||||||
let (fields, hasAbstract) = try fieldData(for: itemType, schemaController: schemaController, dateParser: dateParser, urlDetector: urlDetector, doiDetector: doiDetector) | ||||||||||||||||||
let date = Date() | ||||||||||||||||||
let attachments: [Attachment] = child.flatMap({ [$0] }) ?? [] | ||||||||||||||||||
let data = ItemDetailState.Data( | ||||||||||||||||||
|
@@ -79,7 +80,6 @@ struct ItemDetailDataCreator { | |||||||||||||||||
creators: [:], | ||||||||||||||||||
creatorIds: [], | ||||||||||||||||||
fields: fields, | ||||||||||||||||||
fieldIds: fieldIds, | ||||||||||||||||||
abstract: (hasAbstract ? "" : nil), | ||||||||||||||||||
dateModified: date, | ||||||||||||||||||
dateAdded: date | ||||||||||||||||||
|
@@ -125,10 +125,9 @@ struct ItemDetailDataCreator { | |||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
let (fieldIds, fields, _) = try fieldData(for: item.rawType, schemaController: schemaController, dateParser: dateParser, | ||||||||||||||||||
urlDetector: urlDetector, doiDetector: doiDetector, getExistingData: { key, _ in | ||||||||||||||||||
let (fields, _) = try fieldData(for: item.rawType, schemaController: schemaController, dateParser: dateParser, urlDetector: urlDetector, doiDetector: doiDetector) { key, _ in | ||||||||||||||||||
return (nil, values[key]) | ||||||||||||||||||
}) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
var creatorIds: [String] = [] | ||||||||||||||||||
var creators: [String: ItemDetailState.Creator] = [:] | ||||||||||||||||||
|
@@ -185,7 +184,6 @@ struct ItemDetailDataCreator { | |||||||||||||||||
creators: creators, | ||||||||||||||||||
creatorIds: creatorIds, | ||||||||||||||||||
fields: fields, | ||||||||||||||||||
fieldIds: fieldIds, | ||||||||||||||||||
abstract: abstract, | ||||||||||||||||||
dateModified: item.dateModified, | ||||||||||||||||||
dateAdded: item.dateAdded | ||||||||||||||||||
|
@@ -209,27 +207,27 @@ struct ItemDetailDataCreator { | |||||||||||||||||
urlDetector: UrlDetector, | ||||||||||||||||||
doiDetector: (String) -> Bool, | ||||||||||||||||||
getExistingData: ((String, String?) -> (String?, String?))? = nil | ||||||||||||||||||
) throws -> ([String], [String: ItemDetailState.Field], Bool) { | ||||||||||||||||||
) throws -> (OrderedDictionary<String, ItemDetailState.Field>, Bool) { | ||||||||||||||||||
guard var fieldSchemas = schemaController.fields(for: itemType) else { | ||||||||||||||||||
throw ItemDetailError.typeNotSupported(itemType) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
var fieldKeys = fieldSchemas.map({ $0.field }) | ||||||||||||||||||
let abstractIndex = fieldKeys.firstIndex(of: FieldKeys.Item.abstract) | ||||||||||||||||||
var hasAbstract: Bool = false | ||||||||||||||||||
let titleKey = schemaController.titleKey(for: itemType) | ||||||||||||||||||
var fields: OrderedDictionary<String, ItemDetailState.Field> = [:] | ||||||||||||||||||
for schema in fieldSchemas { | ||||||||||||||||||
let key = schema.field | ||||||||||||||||||
// Remove title and abstract keys, those 2 are used separately in Data struct. | ||||||||||||||||||
if key == FieldKeys.Item.abstract { | ||||||||||||||||||
hasAbstract = true | ||||||||||||||||||
continue | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// Remove title and abstract keys, those 2 are used separately in Data struct | ||||||||||||||||||
if let index = abstractIndex { | ||||||||||||||||||
fieldKeys.remove(at: index) | ||||||||||||||||||
fieldSchemas.remove(at: index) | ||||||||||||||||||
} | ||||||||||||||||||
if let key = schemaController.titleKey(for: itemType), let index = fieldKeys.firstIndex(of: key) { | ||||||||||||||||||
fieldKeys.remove(at: index) | ||||||||||||||||||
fieldSchemas.remove(at: index) | ||||||||||||||||||
} | ||||||||||||||||||
if key == titleKey { | ||||||||||||||||||
continue | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
var fields: [String: ItemDetailState.Field] = [:] | ||||||||||||||||||
for (offset, key) in fieldKeys.enumerated() { | ||||||||||||||||||
let baseField = fieldSchemas[offset].baseField | ||||||||||||||||||
let baseField = schema.baseField | ||||||||||||||||||
let (existingName, existingValue) = (getExistingData?(key, baseField) ?? (nil, nil)) | ||||||||||||||||||
|
||||||||||||||||||
let name = existingName ?? schemaController.localized(field: key) ?? "" | ||||||||||||||||||
|
@@ -241,45 +239,30 @@ struct ItemDetailDataCreator { | |||||||||||||||||
additionalInfo = [.dateOrder: order] | ||||||||||||||||||
} | ||||||||||||||||||
if key == FieldKeys.Item.accessDate, let date = Formatter.iso8601.date(from: value) { | ||||||||||||||||||
additionalInfo = [.formattedDate: Formatter.dateAndTime.string(from: date), | ||||||||||||||||||
.formattedEditDate: Formatter.sqlFormat.string(from: date)] | ||||||||||||||||||
additionalInfo = [.formattedDate: Formatter.dateAndTime.string(from: date), .formattedEditDate: Formatter.sqlFormat.string(from: date)] | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't add comment for unchanged lines, but I'd convert these
|
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
fields[key] = ItemDetailState.Field(key: key, | ||||||||||||||||||
baseField: baseField, | ||||||||||||||||||
name: name, | ||||||||||||||||||
value: value, | ||||||||||||||||||
isTitle: false, | ||||||||||||||||||
isTappable: isTappable, | ||||||||||||||||||
additionalInfo: additionalInfo) | ||||||||||||||||||
fields[key] = ItemDetailState.Field(key: key, baseField: baseField, name: name, value: value, isTitle: false, isTappable: isTappable, additionalInfo: additionalInfo) | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could add For example I believe this could be changed: zotero-ios/Zotero/Scenes/Detail/ItemDetail/Views/ItemDetailCollectionViewHandler.swift Lines 222 to 228 in 757aa35
and this zotero-ios/Zotero/Scenes/Detail/ItemDetail/Views/ItemDetailCollectionViewHandler.swift Line 914 in 757aa35
to guard let field = viewModel.state.data.fields[fieldId], field.isTappable && (!viewModel.state.isEditing || !field.isEditable) else { return }
So that they are not only tied to |
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
return (fieldKeys, fields, (abstractIndex != nil)) | ||||||||||||||||||
return (fields, hasAbstract) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Returns all field keys for given item type, except those that should not appear as fields in item detail. | ||||||||||||||||||
static func allFieldKeys(for itemType: String, schemaController: SchemaController) -> [String] { | ||||||||||||||||||
static func allFieldKeys(for itemType: String, schemaController: SchemaController) -> OrderedSet<String> { | ||||||||||||||||||
guard let fieldSchemas = schemaController.fields(for: itemType) else { return [] } | ||||||||||||||||||
var fieldKeys = fieldSchemas.map({ $0.field }) | ||||||||||||||||||
// Remove title and abstract keys, those 2 are used separately in Data struct | ||||||||||||||||||
if let index = fieldKeys.firstIndex(of: FieldKeys.Item.abstract) { | ||||||||||||||||||
fieldKeys.remove(at: index) | ||||||||||||||||||
} | ||||||||||||||||||
if let key = schemaController.titleKey(for: itemType), let index = fieldKeys.firstIndex(of: key) { | ||||||||||||||||||
fieldKeys.remove(at: index) | ||||||||||||||||||
var fieldKeys: OrderedSet<String> = OrderedSet(fieldSchemas.map({ $0.field })) | ||||||||||||||||||
// Remove title and abstract keys, those 2 are used separately in Data struct. | ||||||||||||||||||
fieldKeys.remove(FieldKeys.Item.abstract) | ||||||||||||||||||
if let titleKey = schemaController.titleKey(for: itemType) { | ||||||||||||||||||
fieldKeys.remove(titleKey) | ||||||||||||||||||
} | ||||||||||||||||||
return fieldKeys | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Returns filtered, sorted array of keys for fields that have non-empty values. | ||||||||||||||||||
static func filteredFieldKeys(from fieldKeys: [String], fields: [String: ItemDetailState.Field]) -> [String] { | ||||||||||||||||||
var newFieldKeys: [String] = [] | ||||||||||||||||||
fieldKeys.forEach { key in | ||||||||||||||||||
if !(fields[key]?.value ?? "").isEmpty { | ||||||||||||||||||
newFieldKeys.append(key) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
return newFieldKeys | ||||||||||||||||||
/// Returns filtered, ordered set of keys for fields that have non-empty values. | ||||||||||||||||||
static func filteredFieldKeys(from fields: OrderedDictionary<String, ItemDetailState.Field>) -> OrderedSet<String> { | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably worth renaming to something better like |
||||||||||||||||||
return fields.filter({ !$0.value.value.isEmpty }).keys | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Checks whether field is tappable based on its key and value. | ||||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
// | ||
|
||
import UIKit | ||
import OrderedCollections | ||
|
||
import CocoaLumberjackSwift | ||
import RealmSwift | ||
|
@@ -177,8 +178,7 @@ struct ItemDetailState: ViewModelState { | |
var localizedType: String | ||
var creators: [String: Creator] | ||
var creatorIds: [String] | ||
var fields: [String: Field] | ||
var fieldIds: [String] | ||
var fields: OrderedDictionary<String, Field> | ||
var abstract: String? | ||
|
||
var dateModified: Date | ||
|
@@ -222,7 +222,6 @@ struct ItemDetailState: ViewModelState { | |
creators: [:], | ||
creatorIds: [], | ||
fields: [:], | ||
fieldIds: [], | ||
abstract: nil, | ||
dateModified: date, | ||
dateAdded: date, | ||
|
@@ -248,6 +247,7 @@ struct ItemDetailState: ViewModelState { | |
var data: Data | ||
var snapshot: Data? | ||
var promptSnapshot: Data? | ||
var presentedFieldIds: OrderedSet<String> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd probably change to |
||
var notes: [Note] | ||
var attachments: [Attachment] | ||
var tags: [Tag] | ||
|
@@ -291,6 +291,7 @@ struct ItemDetailState: ViewModelState { | |
self.userId = userId | ||
self.changes = [] | ||
self.data = .empty | ||
self.presentedFieldIds = [] | ||
self.attachments = [] | ||
self.notes = [] | ||
self.tags = [] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess if nothing changed we could throw an error so that you don't have to check whether
dateModified
is not nil when processing this request. It could possibly help with debugging some time, we log the error, we don't log this being nil.