diff --git a/android/Omnivore/app/src/main/graphql/UpdatePage.graphql b/android/Omnivore/app/src/main/graphql/UpdatePage.graphql new file mode 100644 index 000000000..acb8f248f --- /dev/null +++ b/android/Omnivore/app/src/main/graphql/UpdatePage.graphql @@ -0,0 +1,15 @@ +mutation UpdatePage($input: UpdatePageInput!) { + updatePage(input: $input) { + ... on UpdatePageSuccess { + updatedPage { + title + author + description + } + } + + ... on UpdatePageError { + errorCodes + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt index f5d774913..5ddda0328 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt @@ -15,6 +15,7 @@ import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import app.omnivore.omnivore.ui.auth.LoginViewModel import app.omnivore.omnivore.ui.components.LabelsViewModel +import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel import app.omnivore.omnivore.ui.library.LibraryViewModel import app.omnivore.omnivore.ui.library.SearchViewModel import app.omnivore.omnivore.ui.root.RootView @@ -39,6 +40,7 @@ class MainActivity : ComponentActivity() { val searchViewModel: SearchViewModel by viewModels() val labelsViewModel: LabelsViewModel by viewModels() val saveViewModel: SaveViewModel by viewModels() + val editInfoViewModel: EditInfoViewModel by viewModels() val context = this @@ -65,7 +67,8 @@ class MainActivity : ComponentActivity() { libraryViewModel, settingsViewModel, labelsViewModel, - saveViewModel) + saveViewModel, + editInfoViewModel) } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt index 7dbd1dfd0..4d5f4cae6 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/persistence/entities/SavedItem.kt @@ -226,7 +226,7 @@ interface SavedItemDao { object SavedItemQueryConstants { - const val columns = "savedItemId, slug, publisherURLString, title, author, imageURLString, isArchived, pageURLString, contentReader, savedAt, readingProgress, wordsCount" + const val columns = "savedItemId, slug, publisherURLString, title, author, descriptionText, imageURLString, isArchived, pageURLString, contentReader, savedAt, readingProgress, wordsCount" const val libraryColumns = "SavedItem.savedItemId, " + "SavedItem.slug, " + "SavedItem.createdAt, " + @@ -234,6 +234,7 @@ object SavedItemQueryConstants { "SavedItem.publisherURLString, " + "SavedItem.title, " + "SavedItem.author, " + + "SavedItem.descriptionText, " + "SavedItem.imageURLString, " + "SavedItem.isArchived, " + "SavedItem.pageURLString, " + diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt new file mode 100644 index 000000000..fa7abe53b --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoSheet.kt @@ -0,0 +1,146 @@ +package app.omnivore.omnivore.ui.editinfo + +import android.widget.Toast +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.* +import androidx.compose.runtime.* +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.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.lifecycle.MutableLiveData +import app.omnivore.omnivore.R + +@Composable +fun EditInfoSheetContent( + savedItemId: String?, + title: String?, + author: String?, + description: String?, + viewModel: EditInfoViewModel, + onCancel: () -> Unit, + onUpdated: () -> Unit +) { + + val context = LocalContext.current + + var titleTextFieldValue by remember { mutableStateOf(TextFieldValue(title ?: "")) } + var authorTextFieldValue by remember { mutableStateOf(TextFieldValue(author ?: "")) } + var descriptionTextFieldValue by remember { mutableStateOf(TextFieldValue(description ?: "")) } + + fun showToast(msg: String) { + Toast.makeText( + context, + msg, + Toast.LENGTH_SHORT + ).show() + } + + val state: EditInfoState by viewModel.state.observeAsState(EditInfoState.DEFAULT) + val isUpdating = MutableLiveData(false) + + when (state) { + EditInfoState.DEFAULT -> { + isUpdating.value = false + } + EditInfoState.UPDATING -> { + isUpdating.value = true + } + EditInfoState.ERROR -> { + isUpdating.value = false + showToast(viewModel.message ?: context.getString(R.string.edit_info_sheet_error)) + + viewModel.resetState() + } + EditInfoState.UPDATED -> { + isUpdating.value = false + showToast(viewModel.message ?: context.getString(R.string.edit_info_sheet_success)) + + onUpdated() + viewModel.resetState() + } + } + + Surface( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background), + ) { + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + + Row( + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + ) { + TextButton(onClick = onCancel) { + Text(text = stringResource(R.string.edit_info_sheet_action_cancel)) + } + + Text(stringResource(R.string.edit_info_sheet_title), fontWeight = FontWeight.ExtraBold) + + TextButton(onClick = { + val newTitle = titleTextFieldValue.text + val newAuthor = authorTextFieldValue.text.ifEmpty { null } + val newDescription = descriptionTextFieldValue.text.ifEmpty { null } + + savedItemId?.let { + viewModel.editInfo(it, newTitle, newAuthor, newDescription) + } + }) { + Text(stringResource(R.string.edit_info_sheet_action_save)) + } + } + + if (isUpdating.value == true) { + Spacer(modifier = Modifier.width(16.dp)) + CircularProgressIndicator( + modifier = Modifier + .height(16.dp) + .width(16.dp), + strokeWidth = 2.dp, + color = MaterialTheme.colorScheme.primary + ) + } + + OutlinedTextField( + value = titleTextFieldValue, + label = { Text(stringResource(R.string.edit_info_sheet_text_field_label_title)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + onValueChange = { titleTextFieldValue = it }, + modifier = Modifier.padding(top = 24.dp).fillMaxWidth() + ) + + OutlinedTextField( + value = authorTextFieldValue, + label = { Text(stringResource(R.string.edit_info_sheet_text_field_label_author)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + onValueChange = { authorTextFieldValue = it }, + modifier = Modifier.padding(top = 24.dp).fillMaxWidth() + ) + + OutlinedTextField( + value = descriptionTextFieldValue, + label = { Text(stringResource(R.string.edit_info_sheet_text_field_label_description)) }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), + singleLine = false, minLines = 1, maxLines = 5, + onValueChange = { descriptionTextFieldValue = it }, + modifier = Modifier.padding(top = 24.dp).fillMaxWidth() + ) + } + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoViewModel.kt new file mode 100644 index 000000000..b9b67133f --- /dev/null +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/editinfo/EditInfoViewModel.kt @@ -0,0 +1,108 @@ +package app.omnivore.omnivore.ui.editinfo + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import app.omnivore.omnivore.Constants +import app.omnivore.omnivore.DatastoreKeys +import app.omnivore.omnivore.DatastoreRepository +import app.omnivore.omnivore.R +import app.omnivore.omnivore.dataService.DataService +import app.omnivore.omnivore.graphql.generated.UpdatePageMutation +import app.omnivore.omnivore.graphql.generated.type.UpdatePageInput +import app.omnivore.omnivore.ui.ResourceProvider +import com.apollographql.apollo3.ApolloClient +import com.apollographql.apollo3.api.Optional +import com.pspdfkit.internal.sa +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import javax.inject.Inject + +enum class EditInfoState { + DEFAULT(), + UPDATING(), + ERROR(), + UPDATED() +} + +@HiltViewModel +class EditInfoViewModel @Inject constructor( + private val dataService: DataService, + private val datastoreRepo: DatastoreRepository, + private val resourceProvider: ResourceProvider +) : ViewModel() { + val state = MutableLiveData(EditInfoState.DEFAULT) + + var isLoading by mutableStateOf(false) + private set + + var message by mutableStateOf(null) + private set + + private fun getAuthToken(): String? = runBlocking { + datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken) + } + + fun editInfo(itemId: String, title: String, author: String?, description: String?) { + viewModelScope.launch { + isLoading = true + state.postValue(EditInfoState.UPDATING) + + val authToken = getAuthToken() + + if (authToken == null) { + message = resourceProvider.getString(R.string.edit_info_view_model_error_not_logged_in) + isLoading = false + return@launch + } + + val apolloClient = ApolloClient.Builder() + .serverUrl("${Constants.apiURL}/api/graphql") + .addHttpHeader("Authorization", value = authToken) + .build() + + try { + val response = apolloClient.mutation( + UpdatePageMutation( + UpdatePageInput( + pageId = itemId, + title = Optional.present(title), + byline = Optional.presentIfNotNull(author), + description = Optional.presentIfNotNull(description) + ) + ) + ).execute() + + withContext(Dispatchers.IO) { + val savedItem = dataService.db.savedItemDao().findById(itemId) ?: return@withContext + val updatedSavedItem = savedItem.copy(title = title, author = author, descriptionText = description) + dataService.db.savedItemDao().update(updatedSavedItem) + } + + isLoading = false + + val success = (response.data?.updatePage?.onUpdatePageSuccess?.updatedPage != null) + if (success) { + message = resourceProvider.getString(R.string.edit_info_sheet_success) + state.postValue(EditInfoState.UPDATED) + } else { + message = resourceProvider.getString(R.string.edit_info_sheet_error) + state.postValue(EditInfoState.ERROR) + } + } catch (e: java.lang.Exception) { + message = resourceProvider.getString(R.string.edit_info_sheet_error) + state.postValue(EditInfoState.ERROR) + } + } + } + + fun resetState() { + state.postValue(EditInfoState.DEFAULT) + } +} diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt index 30af18ee7..bfcfccd8f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryNavigationBar.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Info import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -49,7 +50,7 @@ fun LibraryNavigationBar( savedItemViewModel.actionsMenuItemLiveData.postValue(null) }) { Icon( - imageVector = androidx.compose.material.icons.Icons.Filled.ArrowBack, + imageVector = Icons.Filled.ArrowBack, modifier = Modifier, contentDescription = "Back" ) @@ -75,6 +76,12 @@ fun LibraryNavigationBar( ) } } + IconButton(onClick = { savedItemViewModel.handleSavedItemAction(it.savedItem.savedItemId, SavedItemAction.EditInfo) }) { + Icon( + Icons.Outlined.Info, + contentDescription = null + ) + } IconButton(onClick = { savedItemViewModel.handleSavedItemAction(it.savedItem.savedItemId, SavedItemAction.EditLabels) }) { Icon( painter = painterResource(id = R.drawable.tag), diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index 35045b459..294808dae 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -28,8 +28,10 @@ import app.omnivore.omnivore.Routes import app.omnivore.omnivore.persistence.entities.SavedItemLabel import app.omnivore.omnivore.persistence.entities.SavedItemWithLabelsAndHighlights import app.omnivore.omnivore.ui.components.AddLinkSheetContent +import app.omnivore.omnivore.ui.editinfo.EditInfoSheetContent import app.omnivore.omnivore.ui.components.LabelsSelectionSheetContent import app.omnivore.omnivore.ui.components.LabelsViewModel +import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard import app.omnivore.omnivore.ui.reader.PDFReaderActivity import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity @@ -45,11 +47,13 @@ fun LibraryView( libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel, saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel, navController: NavHostController ) { val scaffoldState: ScaffoldState = rememberScaffoldState() val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false) val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false) + val showEditInfoSheet: Boolean by libraryViewModel.showEditInfoSheetLiveData.observeAsState(false) val coroutineScope = rememberCoroutineScope() val modalBottomSheetState = rememberModalBottomSheetState( @@ -57,7 +61,7 @@ fun LibraryView( confirmStateChange = { it != ModalBottomSheetValue.Hidden } ) - if (showLabelsSelectionSheet || showAddLinkSheet) { + if (showLabelsSelectionSheet || showAddLinkSheet || showEditInfoSheet) { coroutineScope.launch { modalBottomSheetState.show() } @@ -78,7 +82,7 @@ fun LibraryView( sheetBackgroundColor = Color.Transparent, sheetState = modalBottomSheetState, sheetContent = { - BottomSheetContent(libraryViewModel, labelsViewModel, saveViewModel) + BottomSheetContent(libraryViewModel, labelsViewModel, saveViewModel,editInfoViewModel) Spacer(modifier = Modifier.weight(1.0F)) } ) { @@ -103,9 +107,13 @@ fun LibraryView( } @Composable -fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: LabelsViewModel, saveViewModel: SaveViewModel) { +fun BottomSheetContent(libraryViewModel: LibraryViewModel, + labelsViewModel: LabelsViewModel, + saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel) { val showLabelsSelectionSheet: Boolean by libraryViewModel.showLabelsSelectionSheetLiveData.observeAsState(false) val showAddLinkSheet: Boolean by libraryViewModel.showAddLinkSheetLiveData.observeAsState(false) + val showEditInfoSheet: Boolean by libraryViewModel.showEditInfoSheetLiveData.observeAsState(false) val currentSavedItemData = libraryViewModel.currentSavedItemUnderEdit() val labels: List by libraryViewModel.savedItemLabelsLiveData.observeAsState(listOf()) @@ -118,7 +126,7 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe initialSelectedLabels = currentSavedItemData.labels, onCancel = { libraryViewModel.showLabelsSelectionSheetLiveData.value = false - libraryViewModel.labelsSelectionCurrentItemLiveData.value = null + libraryViewModel.currentItemLiveData.value = null }, isLibraryMode = false, onSave = { @@ -128,7 +136,7 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe labels = it ) } - libraryViewModel.labelsSelectionCurrentItemLiveData.value = null + libraryViewModel.currentItemLiveData.value = null libraryViewModel.showLabelsSelectionSheetLiveData.value = false }, onCreateLabel = { newLabelName, labelHexValue -> @@ -144,7 +152,7 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe isLibraryMode = true, onSave = { libraryViewModel.updateAppliedLabels(it) - libraryViewModel.labelsSelectionCurrentItemLiveData.value = null + libraryViewModel.currentItemLiveData.value = null libraryViewModel.showLabelsSelectionSheetLiveData.value = false }, onCreateLabel = { newLabelName, labelHexValue -> @@ -167,6 +175,25 @@ fun BottomSheetContent(libraryViewModel: LibraryViewModel, labelsViewModel: Labe } ) } + } else if (showEditInfoSheet) { + BottomSheetUI { + EditInfoSheetContent( + savedItemId = currentSavedItemData?.savedItem?.savedItemId, + title = currentSavedItemData?.savedItem?.title, + author = currentSavedItemData?.savedItem?.author, + description = currentSavedItemData?.savedItem?.descriptionText, + viewModel = editInfoViewModel, + onCancel = { + libraryViewModel.showEditInfoSheetLiveData.value = false + libraryViewModel.currentItemLiveData.value = null + }, + onUpdated = { + libraryViewModel.showEditInfoSheetLiveData.value = false + libraryViewModel.currentItemLiveData.value = null + libraryViewModel.refresh() + } + ) + } } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt index ae3525d3f..79539a826 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryViewModel.kt @@ -62,8 +62,9 @@ class LibraryViewModel @Inject constructor( val appliedFilterLiveData = MutableLiveData(SavedItemFilter.INBOX) val appliedSortFilterLiveData = MutableLiveData(SavedItemSortFilter.NEWEST) val showLabelsSelectionSheetLiveData = MutableLiveData(false) + val showEditInfoSheetLiveData = MutableLiveData(false) val showAddLinkSheetLiveData = MutableLiveData(false) - val labelsSelectionCurrentItemLiveData = MutableLiveData(null) + val currentItemLiveData = MutableLiveData(null) val savedItemLabelsLiveData = dataService.db.savedItemLabelDao().getSavedItemLabelsLiveData() val activeLabelsLiveData = MutableLiveData>(listOf()) @@ -296,9 +297,13 @@ class LibraryViewModel @Inject constructor( } } SavedItemAction.EditLabels -> { - labelsSelectionCurrentItemLiveData.value = itemID + currentItemLiveData.value = itemID showLabelsSelectionSheetLiveData.value = true } + SavedItemAction.EditInfo -> { + currentItemLiveData.value = itemID + showEditInfoSheetLiveData.value = true + } else -> { } @@ -350,7 +355,7 @@ class LibraryViewModel @Inject constructor( } fun currentSavedItemUnderEdit(): SavedItemWithLabelsAndHighlights? { - labelsSelectionCurrentItemLiveData.value?.let { itemID -> + currentItemLiveData.value?.let { itemID -> return itemsLiveData.value?.first { it.savedItem.savedItemId == itemID } } @@ -376,4 +381,5 @@ enum class SavedItemAction { Archive, Unarchive, EditLabels, + EditInfo, } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchViewModel.kt index 8db931e1a..bbd37030d 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/SearchViewModel.kt @@ -167,6 +167,9 @@ class SearchViewModel @Inject constructor( SavedItemAction.EditLabels -> { // TODO } + SavedItemAction.EditInfo -> { + // TODO + } } actionsMenuItemLiveData.postValue(null) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt index 87a0d63ed..9f9e7c6ee 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderLoadingContainer.kt @@ -44,7 +44,9 @@ import kotlinx.coroutines.launch import kotlin.math.roundToInt import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import app.omnivore.omnivore.ui.editinfo.EditInfoSheetContent import app.omnivore.omnivore.ui.components.LabelsViewModel +import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel import app.omnivore.omnivore.ui.notebook.EditNoteModal @AndroidEntryPoint @@ -52,6 +54,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { val viewModel: WebReaderViewModel by viewModels() private val notebookViewModel: NotebookViewModel by viewModels() private val labelsViewModel: LabelsViewModel by viewModels() + private val editInfoViewModel: EditInfoViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -89,6 +92,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() { webReaderViewModel = viewModel, notebookViewModel = notebookViewModel, labelsViewModel = labelsViewModel, + editInfoViewModel = editInfoViewModel, ) } } @@ -119,7 +123,8 @@ enum class BottomSheetState( EDITNOTE(), HIGHLIGHTNOTE(), LABELS(), - LINK() + LINK(), + EDIT_INFO(), } @@ -129,7 +134,8 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, onLibraryIconTap: (() -> Unit)? = null, webReaderViewModel: WebReaderViewModel, notebookViewModel: NotebookViewModel, - labelsViewModel: LabelsViewModel) { + labelsViewModel: LabelsViewModel, + editInfoViewModel: EditInfoViewModel) { val currentThemeKey = webReaderViewModel.currentThemeKey.observeAsState() val currentTheme = Themes.values().find { it.themeKey == currentThemeKey.value } val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher @@ -181,7 +187,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, } } BottomSheetState.NOTEBOOK, BottomSheetState.EDITNOTE, - BottomSheetState.HIGHLIGHTNOTE, BottomSheetState.LABELS, + BottomSheetState.HIGHLIGHTNOTE, BottomSheetState.LABELS, BottomSheetState.EDIT_INFO, BottomSheetState.LINK, -> { showMenu() } @@ -282,6 +288,28 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null, ) } } + BottomSheetState.EDIT_INFO -> { + BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_edit_info)) { + EditInfoSheetContent( + savedItemId = webReaderParams?.item?.savedItemId, + title = webReaderParams?.item?.title, + author = webReaderParams?.item?.author, + description = webReaderParams?.item?.descriptionText, + viewModel = editInfoViewModel, + onCancel = { + coroutineScope.launch { + webReaderViewModel.resetBottomSheet() + } + }, + onUpdated = { + coroutineScope.launch { + webReaderViewModel.updateItemTitle() + webReaderViewModel.resetBottomSheet() + } + } + ) + } + } BottomSheetState.LINK -> { BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_open_link)) { OpenLinkView(webReaderViewModel) diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt index 555b85ab1..6b8216e88 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt @@ -296,6 +296,9 @@ class WebReaderViewModel @Inject constructor( SavedItemAction.EditLabels -> { bottomSheetStateLiveData.postValue(BottomSheetState.LABELS) } + SavedItemAction.EditInfo -> { + bottomSheetStateLiveData.postValue(BottomSheetState.EDIT_INFO) + } } } @@ -525,6 +528,25 @@ class WebReaderViewModel @Inject constructor( } } + fun updateItemTitle() { + viewModelScope.launch { + slug?.let { + loadItemFromDB(it) + } + + webReaderParamsLiveData.value?.item?.title?.let { + updateItemTitleInWebView(it) + } + } + } + + private fun updateItemTitleInWebView(title: String) { + val script = "var event = new Event('updateTitle');event.title = '${title}';document.dispatchEvent(event);" + CoroutineScope(Dispatchers.Main).launch { + enqueueScript(script) + } + } + fun createNewSavedItemLabel(labelName: String, hexColorValue: String) { viewModelScope.launch { withContext(Dispatchers.IO) { diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt index a53280a3c..80f96118f 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt @@ -17,6 +17,7 @@ import app.omnivore.omnivore.Routes import app.omnivore.omnivore.ui.auth.LoginViewModel import app.omnivore.omnivore.ui.auth.WelcomeScreen import app.omnivore.omnivore.ui.components.LabelsViewModel +import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel import app.omnivore.omnivore.ui.library.LibraryView import app.omnivore.omnivore.ui.library.SearchView import app.omnivore.omnivore.ui.library.LibraryViewModel @@ -34,6 +35,7 @@ fun RootView( settingsViewModel: SettingsViewModel, labelsViewModel: LabelsViewModel, saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel, ) { val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false) val systemUiController = rememberSystemUiController() @@ -59,7 +61,8 @@ fun RootView( libraryViewModel = libraryViewModel, settingsViewModel = settingsViewModel, labelsViewModel = labelsViewModel, - saveViewModel = saveViewModel + saveViewModel = saveViewModel, + editInfoViewModel = editInfoViewModel, ) } else { WelcomeScreen(viewModel = loginViewModel) @@ -82,6 +85,7 @@ fun PrimaryNavigator( settingsViewModel: SettingsViewModel, labelsViewModel: LabelsViewModel, saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel, ) { val navController = rememberNavController() @@ -92,6 +96,7 @@ fun PrimaryNavigator( navController = navController, labelsViewModel = labelsViewModel, saveViewModel = saveViewModel, + editInfoViewModel = editInfoViewModel, ) } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt index 25913bb8d..a7057680c 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/savedItemViews/SavedItemContextMenu.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CheckCircle import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.List import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.DropdownMenu @@ -31,6 +32,19 @@ fun SavedItemContextMenu( expanded = isExpanded, onDismissRequest = onDismiss ) { + DropdownMenuItem( + text = { Text(stringResource(R.string.saved_item_context_menu_action_edit_info)) }, + onClick = { + actionHandler(SavedItemAction.EditInfo) + onDismiss() + }, + leadingIcon = { + Icon( + Icons.Outlined.Info, + contentDescription = null + ) + } + ) DropdownMenuItem( text = { Text(stringResource(R.string.saved_item_context_menu_action_edit_labels)) }, onClick = { diff --git a/android/Omnivore/app/src/main/res/values-zh-rCN/strings.xml b/android/Omnivore/app/src/main/res/values-zh-rCN/strings.xml index 4187efa9c..780ae2bcd 100644 --- a/android/Omnivore/app/src/main/res/values-zh-rCN/strings.xml +++ b/android/Omnivore/app/src/main/res/values-zh-rCN/strings.xml @@ -152,6 +152,7 @@ 我们无法取得您的内容。 阅读器偏好设定 笔记 + 编辑信息 开启链接 @@ -178,6 +179,7 @@ 保存您的页面时出错 + 编辑信息 编辑标签 封存 取消封存 @@ -222,4 +224,17 @@ 从旧到新 最近阅读 最近发布 + + + 您尚未登入。请在保存前登入。 + + + 编辑信息 + 标题 + 作者 + 说明 + 节省 + 取消 + 编辑文章时出错 + 文章信息更新成功 diff --git a/android/Omnivore/app/src/main/res/values-zh-rTW/strings.xml b/android/Omnivore/app/src/main/res/values-zh-rTW/strings.xml index ff6db1f70..c634719cf 100644 --- a/android/Omnivore/app/src/main/res/values-zh-rTW/strings.xml +++ b/android/Omnivore/app/src/main/res/values-zh-rTW/strings.xml @@ -151,6 +151,7 @@ 我們無法取得您的內容。 閱讀器偏好設定 筆記本 + 编辑信息 開啟連結 @@ -177,6 +178,7 @@ 儲存您的頁面時出錯 + 编辑信息 編輯標籤 封存 取消封存 diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml index e46ae3194..878d99cce 100644 --- a/android/Omnivore/app/src/main/res/values/strings.xml +++ b/android/Omnivore/app/src/main/res/values/strings.xml @@ -151,6 +151,8 @@ We were unable to fetch your content. Reader Preferences Notebook + Edit Info + Notebook Open Link @@ -177,6 +179,7 @@ There was an error saving your page + Edit Info Edit Labels Archive Unarchive @@ -214,4 +217,17 @@ Invalid URL Error while saving link! Link successfully saved! + + + You are not logged in. Please login before saving. + + + Edit Info + Title + Author + Description + Save + Cancel + Error while editing article! + Article info successfully updated!