Skip to content
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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

mvasilak
Copy link
Contributor

@mvasilak mvasilak commented Jan 9, 2025

  • Doesn't show empty fields when editing an item of attachment type.
  • Properly ends item details editing if no changes have occured when Done is pressed.
  • Updates date modified for each saved item details change.

@mvasilak mvasilak requested a review from michalrentka January 9, 2025 13:57
Copy link
Contributor

@michalrentka michalrentka left a 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 in Data should probably a constant? "Normal" item can't become an attachment, so that flag won't ever change.
  • creators and creatorIds can be converted to OrderedDictionary 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), the NSAttributedString 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 {
Copy link
Contributor

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?

Copy link
Contributor Author

@mvasilak mvasilak Jan 10, 2025

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)
Copy link
Contributor

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?

Copy link
Contributor Author

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> {
Copy link
Contributor

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 {
Copy link
Contributor

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)
Copy link
Contributor

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 {
Copy link
Contributor

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)
Copy link
Contributor

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
Copy link
Contributor

@michalrentka michalrentka Jan 10, 2025

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)
Copy link
Contributor

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 {
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants