From 95c97a62cbadbec76cecd748695392678789d2e1 Mon Sep 17 00:00:00 2001 From: Dima Date: Fri, 21 Feb 2025 00:33:30 +0900 Subject: [PATCH] Bugfix for ShareScreen crashing when saving recognized item into My Library, Updating UI of ShareScreen to change icon and title according to recognized item's data. Upping versionCode to 136 --- .../android/pdfworker/PdfWorkerController.kt | 66 ++++++++++++++++- .../RetrieveMetadataViewModel.kt | 9 ++- .../data/RetrieveMetadataState.kt | 2 +- .../android/screens/share/ShareScreen.kt | 73 +++++++++++++++++-- .../android/screens/share/ShareViewModel.kt | 8 +- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 6 files changed, 144 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/zotero/android/pdfworker/PdfWorkerController.kt b/app/src/main/java/org/zotero/android/pdfworker/PdfWorkerController.kt index dc1bde3..5f30fa9 100644 --- a/app/src/main/java/org/zotero/android/pdfworker/PdfWorkerController.kt +++ b/app/src/main/java/org/zotero/android/pdfworker/PdfWorkerController.kt @@ -11,6 +11,7 @@ import org.zotero.android.api.mappers.CreatorResponseMapper import org.zotero.android.api.mappers.ItemResponseMapper import org.zotero.android.api.mappers.TagResponseMapper import org.zotero.android.api.pojo.sync.ItemResponse +import org.zotero.android.api.pojo.sync.KeyBaseKeyPair import org.zotero.android.api.pojo.sync.LibraryResponse import org.zotero.android.api.pojo.sync.TagResponse import org.zotero.android.architecture.Defaults @@ -19,6 +20,8 @@ import org.zotero.android.architecture.core.EventStream import org.zotero.android.architecture.coroutines.Dispatchers import org.zotero.android.database.DbWrapperMain import org.zotero.android.database.objects.Attachment +import org.zotero.android.database.objects.FieldKeys +import org.zotero.android.database.objects.ItemTypes import org.zotero.android.database.objects.RItem import org.zotero.android.database.requests.CreateTranslatedItemsDbRequest import org.zotero.android.database.requests.LinkAttachmentToParentItemDbRequest @@ -118,11 +121,62 @@ class PdfWorkerController @Inject constructor( ) } if (state is LookupData.State.translatedOnly) { - this.itemResponse = state.itemResponse - observable.emitAsync(Update.recognizedAndKeptInMemory) + val itemResponse = state.itemResponse + this.itemResponse = itemResponse + val (title, typeIconName) = getTitleAndContentTypeFromResponse(itemResponse) + observable.emitAsync( + Update.recognizedAndKeptInMemory( + recognizedTitle = title, + recognizedTypeIcon = typeIconName + ) + ) + } + } + } + } + + private fun getTitleAndContentTypeFromResponse(itemResponse: ItemResponse): Pair { + val title = getTitleFromResponse(itemResponse) + val contentType = getContentTypeFromResponse(itemResponse) ?: "" + val typeIconName = ItemTypes.iconName( + rawType = itemResponse.rawType, + contentType = contentType + ) + return Pair(title, typeIconName) + } + + private fun getTitleFromResponse(response: ItemResponse): String { + val title: String + val _title = response.fields[KeyBaseKeyPair( + key = FieldKeys.Item.title, + baseKey = null + )] + if (_title != null) { + title = _title + } else { + val _title = response.fields.entries.firstOrNull { + this.schemaController.baseKey( + type = response.rawType, + field = it.key.key + ) == FieldKeys.Item.title + }?.value + title = _title ?: "" + } + return title + } + + private fun getContentTypeFromResponse(data: ItemResponse): String? { + var contentType: String? = null + val allFieldKeys = data.fields.keys.toTypedArray() + for (keyPair in allFieldKeys) { + val value = data.fields[keyPair] ?: "" + when { + keyPair.key == FieldKeys.Item.Attachment.contentType || keyPair.baseKey == FieldKeys.Item.Attachment.contentType -> { + contentType = value } } } + return contentType } private fun updateItemAndPostProgress( @@ -191,7 +245,11 @@ class PdfWorkerController @Inject constructor( } PdfWorkerMode.recognizeAndWait -> { this.itemResponse = item - observable.emitAsync(Update.recognizedAndKeptInMemory) + val (title, typeIconName) = getTitleAndContentTypeFromResponse(item) + observable.emitAsync(Update.recognizedAndKeptInMemory( + recognizedTitle = title, + recognizedTypeIcon = typeIconName + )) } } } @@ -348,6 +406,6 @@ class PdfWorkerController @Inject constructor( object recognizedDataIsEmpty: Update data class recognizeError(val errorMessage: String) : Update data class recognizedAndSaved(val recognizedTitle: String) : Update - object recognizedAndKeptInMemory : Update + data class recognizedAndKeptInMemory(val recognizedTitle: String, val recognizedTypeIcon: String) : Update } } \ No newline at end of file diff --git a/app/src/main/java/org/zotero/android/screens/retrievemetadata/RetrieveMetadataViewModel.kt b/app/src/main/java/org/zotero/android/screens/retrievemetadata/RetrieveMetadataViewModel.kt index 4571bae..977a48e 100644 --- a/app/src/main/java/org/zotero/android/screens/retrievemetadata/RetrieveMetadataViewModel.kt +++ b/app/src/main/java/org/zotero/android/screens/retrievemetadata/RetrieveMetadataViewModel.kt @@ -56,11 +56,18 @@ internal class RetrieveMetadataViewModel @Inject constructor( is PdfWorkerController.Update.recognizeError -> { updateState { copy(retrieveMetadataState = RetrieveMetadataState.failed(update.errorMessage)) } } + is PdfWorkerController.Update.recognizedAndSaved -> { updateState { - copy(retrieveMetadataState = RetrieveMetadataState.success(update.recognizedTitle)) + copy( + retrieveMetadataState = RetrieveMetadataState.success( + recognizedTitle = update.recognizedTitle, + recognizedTypeIcon = "" //no-op + ) + ) } } + is PdfWorkerController.Update.recognizedAndKeptInMemory -> { //no-op } diff --git a/app/src/main/java/org/zotero/android/screens/retrievemetadata/data/RetrieveMetadataState.kt b/app/src/main/java/org/zotero/android/screens/retrievemetadata/data/RetrieveMetadataState.kt index 274bcc2..93ec11f 100644 --- a/app/src/main/java/org/zotero/android/screens/retrievemetadata/data/RetrieveMetadataState.kt +++ b/app/src/main/java/org/zotero/android/screens/retrievemetadata/data/RetrieveMetadataState.kt @@ -4,6 +4,6 @@ sealed interface RetrieveMetadataState { object loading : RetrieveMetadataState object recognizedDataIsEmpty : RetrieveMetadataState data class failed(val message: String) : RetrieveMetadataState - data class success(val recognizedTitle: String) : RetrieveMetadataState + data class success(val recognizedTitle: String, val recognizedTypeIcon: String) : RetrieveMetadataState object fileIsNotPdf : RetrieveMetadataState } \ No newline at end of file diff --git a/app/src/main/java/org/zotero/android/screens/share/ShareScreen.kt b/app/src/main/java/org/zotero/android/screens/share/ShareScreen.kt index a064807..850a11f 100644 --- a/app/src/main/java/org/zotero/android/screens/share/ShareScreen.kt +++ b/app/src/main/java/org/zotero/android/screens/share/ShareScreen.kt @@ -1,19 +1,31 @@ package org.zotero.android.screens.share +import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.core.text.HtmlCompat import androidx.hilt.navigation.compose.hiltViewModel +import org.zotero.android.androidx.content.getDrawableByItemType import org.zotero.android.screens.retrievemetadata.data.RetrieveMetadataState import org.zotero.android.screens.share.ShareViewEffect.NavigateBack import org.zotero.android.screens.share.ShareViewEffect.NavigateToCollectionPickerScreen @@ -78,13 +90,19 @@ internal fun ShareScreen( .padding(horizontal = 16.dp) ) { item { - ParsedShareItemSection( - item = viewState.expectedItem, - attachment = viewState.expectedAttachment, - attachmentState = viewState.attachmentState, - title = viewState.title, - itemTitle = viewModel::itemTitle - ) + val retrieveMetadataState = viewState.retrieveMetadataState + if (retrieveMetadataState is RetrieveMetadataState.success) { + RecognizeItemSection(retrieveMetadataState) + } else { + ParsedShareItemSection( + item = viewState.expectedItem, + attachment = viewState.expectedAttachment, + attachmentState = viewState.attachmentState, + title = viewState.title, + itemTitle = viewModel::itemTitle + ) + } + } item { @@ -120,3 +138,44 @@ internal fun ShareScreen( } } } + +@Composable +private fun RecognizeItemSection(retrieveMetadataState: RetrieveMetadataState.success) { + Spacer(modifier = Modifier.height(20.dp)) + ShareSection { + RecognizedItemRow( + retrieveMetadataState.recognizedTitle, + retrieveMetadataState.recognizedTypeIcon + ) + } +} + +@Composable +fun RecognizedItemRow(title: String, typeIconName: String) { + Row( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 44.dp) + .background(CustomTheme.colors.surface), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.width(16.dp)) + Image( + modifier = Modifier.size(22.dp), + painter = painterResource(id = LocalContext.current.getDrawableByItemType(typeIconName)), + contentDescription = null, + ) + Spacer(modifier = Modifier.width(16.dp)) + Text( + modifier = Modifier.padding(end = 8.dp), + text = HtmlCompat.fromHtml( + title, + HtmlCompat.FROM_HTML_MODE_LEGACY + ).toString(), + style = CustomTheme.typography.newBody, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + +} diff --git a/app/src/main/java/org/zotero/android/screens/share/ShareViewModel.kt b/app/src/main/java/org/zotero/android/screens/share/ShareViewModel.kt index 737d87a..c2c6fbd 100644 --- a/app/src/main/java/org/zotero/android/screens/share/ShareViewModel.kt +++ b/app/src/main/java/org/zotero/android/screens/share/ShareViewModel.kt @@ -1001,10 +1001,11 @@ internal class ShareViewModel @Inject constructor( private fun maybeSaveCachedDataInPdfWorker() { if (viewState.retrieveMetadataState is RetrieveMetadataState.success) { val tags = viewState.tags.map { TagResponse(tag = it.name, type = it.type) } + val collectionKeys = this.selectedCollectionId.keyGet?.let { setOf(it) } ?: emptySet() pdfWorkerController.saveCachedData( attachmentItemKey = this.attachmentKey, libraryId = this.selectedLibraryId, - collectionKeys = setOf(this.selectedCollectionId.keyGet!!), + collectionKeys = collectionKeys, tags = tags ) } else { @@ -1077,7 +1078,10 @@ internal class ShareViewModel @Inject constructor( } is PdfWorkerController.Update.recognizedAndKeptInMemory -> { updateState { - copy(retrieveMetadataState = RetrieveMetadataState.success("")) + copy(retrieveMetadataState = RetrieveMetadataState.success( + recognizedTitle = update.recognizedTitle, + recognizedTypeIcon = update.recognizedTypeIcon + )) } } } diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 1b9eb0c..829e2ec 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -4,7 +4,7 @@ object BuildConfig { const val compileSdkVersion = 34 const val targetSdk = 34 - val versionCode = 135 // Must be updated on every build + val versionCode = 136 // Must be updated on every build val version = Version( major = 1, minor = 0,