-
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?
Conversation
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.
Some comments for ItemDetailState.Data
struct:
isAttachment
inData
should probably a constant? "Normal" item can't become an attachment, so that flag won't ever change.creators
andcreatorIds
can be converted toOrderedDictionary
too.maxFieldTitleWidth
,maxNonemptyFieldTitleWidth
don't see to be used anymore, can they be deleted?- should
attributedTitle
maybe be exluded from equatability of this struct? If for example font size changes (from iOS accessibility settings), theNSAttributedString
would differ even if title never changed? It's quite an edge case, but no need to compare attributed string when we already compare the original.
What happens, if the user starts editing, the RItem.changesSyncPaused
flag is set to true
and then the user crashes at some point while editing? When they open the app, that RItem
's sync is still disabled, right? Maybe on app launch/activation we should reset all of these flags to false?
I also noticed a UI bug. When you edit some field and quickly tap "Done", the field change is debounced and since we tap "Done" before the debounce triggers, our UI then shows old state. It's stored in database and synced properly.
} | ||
} | ||
|
||
struct EditItemFieldsDbRequest: EditItemFieldsBaseRequest, DbRequest { |
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.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
It's used in PDFReaderActionHandler
where it is used in perform(writeRequests: requests)
, that's why I differentiated them like that. For more streamlining, we can consider changingDbRequest
to something like:
protocol DbRequest: DbResponseRequest where Response == Void
} | ||
|
||
self.saveReloaded(data: data, attachments: attachments, notes: notes, tags: tags, isEditing: canEdit, library: library, token: token, in: viewModel) | ||
saveReloaded(data: data, attachments: attachments, notes: notes, tags: tags, isEditing: canEdit, library: library, token: token, in: viewModel) |
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.
Do we handle the canEdit
exception which was there before anyhow?
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.
Yes, since canEdit
is passed to saveReloaded
as isEditing
, it is done directly there, when state.presentedFields
are computed.
} | ||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Probably worth renaming to something better like nonEmptyFieldKeys
or similar.
) { | ||
update(viewModel: viewModel) { state in | ||
state.data = data | ||
if state.snapshot != nil || isEditing { |
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.
Do we need to store snapshot if we're not editing? As far as I can see it's only used in start/end editing, right?
if isEditing && !data.isAttachment { | ||
state.presentedFieldIds = data.fields.keys | ||
} else { | ||
state.presentedFieldIds = ItemDetailDataCreator.filteredFieldKeys(from: data.fields) |
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.
Is this right? This filters all empty fields and doesn't care about editability of given field. Currently attachments don't have editable fields, but if some editable field was added later, it would not appear for attachment, right? Just to be safe, I'd add a check for editability here (which would be simpler with the isEditable
mentioned above).
@@ -648,16 +648,28 @@ struct ItemDetailActionHandler: ViewModelActionHandler, BackgroundDbProcessingAc | |||
private func startEditing(in viewModel: ViewModel<ItemDetailActionHandler>) { | |||
self.update(viewModel: viewModel) { state in | |||
state.snapshot = state.data | |||
state.data.fieldIds = ItemDetailDataCreator.allFieldKeys(for: state.data.type, schemaController: self.schemaController) | |||
if !state.data.isAttachment { |
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.
Why not just filter out all empty, non-editable fields? For all item types.
If attachment has a URL, it is still shown and it's tappable, even though it's not editable (at least currently).
var requests: [DbRequest] = [EndItemDetailEditingDbRequest(libraryId: state.library.identifier, itemKey: state.key)] | ||
if !updatedFields.isEmpty { | ||
requests.insert(EditItemFieldsDbRequest(key: state.key, libraryId: state.library.identifier, fieldValues: updatedFields, dateParser: dateParser), at: 0) | ||
let endEditingRequest = EndItemDetailEditingDbRequest(libraryId: state.library.identifier, itemKey: state.key) |
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.
So since we're live updating, this is a bit confusing. It made sense before, to have it here, when we had Save + Cancel, but now, not so much.
If you didn't know what this does, when we tap "Done" then fields like "Date" and "Accessed" are converted in some cases, for example if you write "Today" it'll convert to yyyy-mm-dd date format. So this conversion should happen when we're about to store these values to the database, not here.
So we don't even need to update dateModified
here and this could be simplified a lot.
} | ||
|
||
return nil |
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.
self.perform(request: request) { error in | ||
guard let error else { return } | ||
DDLogError("ItemDetailActionHandler: can't store title - \(error)") | ||
let request = EditItemFieldsDbResponseRequest(key: viewModel.state.key, libraryId: viewModel.state.library.identifier, fieldValues: [keyPair: viewModel.state.data.title], dateParser: dateParser) |
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'd probably compare base strings instead of NSAttributedStrings
on line 967
above (same reason as in comment below).
DDLogError("ItemDetailActionHandler: can't store title - \(error)") | ||
let request = EditItemFieldsDbResponseRequest(key: viewModel.state.key, libraryId: viewModel.state.library.identifier, fieldValues: [keyPair: viewModel.state.data.title], dateParser: dateParser) | ||
perform(request: request, invalidateRealm: false) { [weak viewModel] result in | ||
switch result { |
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.
This response is repeated in every function which updates specific field, I guess you could extract and re-use it.
Done
is pressed.