Skip to content

Commit

Permalink
Selecting multiple notebooks now works
Browse files Browse the repository at this point in the history
  • Loading branch information
amberin committed Jun 26, 2024
1 parent acc6e6d commit c7f7ac1
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 59 deletions.
68 changes: 45 additions & 23 deletions app/src/main/java/com/orgzly/android/ui/books/BooksFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import android.text.TextUtils
import android.text.TextWatcher
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
Expand Down Expand Up @@ -155,7 +156,7 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo

} else {
// There are books selected
viewAdapter.getSelection().toggleSingleSelect(item.book.id)
viewAdapter.getSelection().toggle(item.book.id)
viewAdapter.notifyDataSetChanged() // FIXME

viewModel.appBar.toModeFromSelectionCount(viewAdapter.getSelection().count)
Expand All @@ -168,7 +169,7 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
return
}

viewAdapter.getSelection().toggleSingleSelect(item.book.id)
viewAdapter.getSelection().toggle(item.book.id)
viewAdapter.notifyDataSetChanged() // FIXME

viewModel.appBar.toModeFromSelectionCount(viewAdapter.getSelection().count)
Expand Down Expand Up @@ -225,6 +226,7 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
binding.topToolbar.run {
menu.clear()
inflateMenu(R.menu.books_cab)
hideMenuItemsBasedOnSelection(menu)

setNavigationIcon(R.drawable.ic_arrow_back)

Expand All @@ -233,28 +235,29 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
}

setOnMenuItemClickListener { menuItem ->
val bookId = viewAdapter.getSelection().getOnly()
val bookIds = viewAdapter.getSelection().getIds()

if (bookId == null) {
if (bookIds.isEmpty()) {
Log.e(TAG, "Cannot handle action when there are no items selected")
return@setOnMenuItemClickListener true
}

when (menuItem.itemId) {
R.id.books_context_menu_rename -> {
viewModel.renameBookRequest(bookId)
// N.B. Menu item is hidden when multiple books are selected
viewModel.renameBookRequest(bookIds.first())
}

R.id.books_context_menu_set_link -> {
viewModel.setBookLinkRequest(bookId)
viewModel.setBookLinksRequest(bookIds)
}

R.id.books_context_menu_force_save -> {
dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.books_context_menu_item_force_save)
.setMessage(R.string.overwrite_remote_notebook_question)
.setPositiveButton(R.string.overwrite) { _, _ ->
viewModel.forceSaveBookRequest(bookId)
viewModel.forceSaveBookRequest(bookIds)
}
.setNegativeButton(R.string.cancel, null)
.show()
Expand All @@ -265,18 +268,19 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
.setTitle(R.string.books_context_menu_item_force_load)
.setMessage(R.string.overwrite_local_notebook_question)
.setPositiveButton(R.string.overwrite) { _, _ ->
viewModel.forceLoadBookRequest(bookId)
viewModel.forceLoadBookRequest(bookIds)
}
.setNegativeButton(R.string.cancel, null)
.show()
}

R.id.books_context_menu_export -> {
viewModel.exportBookRequest(bookId, BookFormat.ORG)
// N.B. Menu item is hidden when multiple books are selected
viewModel.exportBookRequest(bookIds.first(), BookFormat.ORG)
}

R.id.books_context_menu_delete -> {
viewModel.deleteBookRequest(bookId)
viewModel.deleteBooksRequest(bookIds)
}
}

Expand All @@ -291,6 +295,13 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
}
}

private fun hideMenuItemsBasedOnSelection(menu: Menu) {
// Hide choices which are not applicable when multiple books are selected
for (id in listOf(R.id.books_context_menu_rename, R.id.books_context_menu_export)) {
menu.findItem(id)?.isVisible = viewAdapter.getSelection().count == 1
}
}

private val pickFileForBookExport =
registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
if (uri != null) {
Expand All @@ -305,8 +316,17 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
pickFileForBookExport.launch(defaultFileName)
}

private fun deleteBookDialog(book: BookView) {
private fun deleteBooksDialog(books: Set<BookView>) {
val dialogBinding = DialogBookDeleteBinding.inflate(LayoutInflater.from(context))
val dialogTitle: String
val book: BookView?
if (books.size == 1) {
book = books.first()
dialogTitle = getString(R.string.delete_with_quoted_argument, book.book.name)
} else {
book = null
dialogTitle = getString(R.string.delete_amount_of_books, books.size)
}

dialogBinding.deleteLinkedCheckbox.setOnCheckedChangeListener { _, isChecked ->
dialogBinding.deleteLinkedUrl.isEnabled = isChecked
Expand All @@ -316,20 +336,23 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
val deleteLinked = dialogBinding.deleteLinkedCheckbox.isChecked
viewModel.deleteBook(book.book.id, deleteLinked)
for (book in books) {
viewModel.deleteBook(book.book.id, deleteLinked)
}
}
}
}

val builder = MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_with_quoted_argument, book.book.name))
.setTitle(dialogTitle)
.setPositiveButton(R.string.delete, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)

if (book.syncedTo != null) {
if (book?.syncedTo != null) {
dialogBinding.deleteLinkedUrl.text = book.syncedTo.uri.toString()
builder.setView(dialogBinding.root)
dialogBinding.deleteLinkedCheckbox.text = getString(R.string.also_delete_linked_book)
}
builder.setView(dialogBinding.root)

dialog = builder.show()
}
Expand Down Expand Up @@ -421,9 +444,9 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
})


viewModel.bookToDeleteEvent.observeSingle(viewLifecycleOwner, Observer { bookView ->
if (bookView != null) {
deleteBookDialog(bookView)
viewModel.booksToDeleteEvent.observeSingle(viewLifecycleOwner, Observer { bookViews ->
if (bookViews.isNotEmpty()) {
deleteBooksDialog(bookViews)
}
})

Expand All @@ -445,7 +468,7 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
activity?.showSnackbar(R.string.message_book_deleted)
})

viewModel.setBookLinkRequestEvent.observeSingle(this) { (book, links, urls, checked) ->
viewModel.setBookLinkRequestEvent.observeSingle(this) { (bookIds, links, urls, checked) ->
if (links.isEmpty()) {
activity?.showSnackbar(getString(R.string.no_repos), R.string.repositories) {
activity?.let {
Expand All @@ -454,20 +477,19 @@ class BooksFragment : CommonFragment(), DrawerItem, OnViewHolderClickListener<Bo
ContextCompat.startActivity(it, intent, null)
}
}

} else {
dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.book_link)
.setSingleChoiceItems(
urls.toTypedArray(),
checked
) { _: DialogInterface, which: Int ->
viewModel.setBookLink(book.id, links[which])
viewModel.setBookLinks(bookIds, links[which])
dialog?.dismiss()
dialog = null
}
.setNeutralButton(R.string.remove_notebook_link) { dialog, which ->
viewModel.setBookLink(book.id)
.setNeutralButton(R.string.remove_notebook_link) { _, _ ->
viewModel.setBookLinks(bookIds)
}
.setNegativeButton(R.string.cancel, null)
.show()
Expand Down
67 changes: 36 additions & 31 deletions app/src/main/java/com/orgzly/android/ui/books/BooksViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import com.orgzly.android.ui.CommonViewModel
import com.orgzly.android.ui.SingleLiveEvent
import com.orgzly.android.usecase.*
import com.orgzly.android.util.LogUtils
import java.io.File


class BooksViewModel(private val dataRepository: DataRepository) : CommonViewModel() {
Expand All @@ -24,7 +23,7 @@ class BooksViewModel(private val dataRepository: DataRepository) : CommonViewMod
// Book being operated on (deleted, renamed, etc.)
private var lastBook = MutableLiveData<Pair<Book, BookFormat>>()

val bookToDeleteEvent: SingleLiveEvent<BookView> = SingleLiveEvent()
val booksToDeleteEvent: SingleLiveEvent<Set<BookView>> = SingleLiveEvent()
val bookDeletedEvent: SingleLiveEvent<UseCaseResult> = SingleLiveEvent()
val bookToRenameEvent: SingleLiveEvent<BookView> = SingleLiveEvent()
val bookToExportEvent: SingleLiveEvent<Pair<Book, BookFormat>> = SingleLiveEvent()
Expand Down Expand Up @@ -64,9 +63,10 @@ class BooksViewModel(private val dataRepository: DataRepository) : CommonViewMod
}
}

fun deleteBookRequest(bookId: Long) {
fun deleteBooksRequest(bookIds: Set<Long>) {
val bookViews = bookIds.map { requireNotNull(dataRepository.getBookView(it)) }.toSet()
App.EXECUTORS.diskIO().execute {
bookToDeleteEvent.postValue(dataRepository.getBookView(bookId))
booksToDeleteEvent.postValue(bookViews)
}
}

Expand Down Expand Up @@ -94,56 +94,61 @@ class BooksViewModel(private val dataRepository: DataRepository) : CommonViewMod
}

data class BookLinkOptions(
val book: Book, val links: List<Repo>, val urls: List<String>, val selected: Int)
val bookIds: Set<Long>, val links: List<Repo>, val urls: List<String>, val selected: Int)

fun setBookLinkRequest(bookId: Long) {
fun setBookLinksRequest(bookIds: Set<Long>) {
App.EXECUTORS.diskIO().execute {
val bookView = dataRepository.getBookView(bookId)

if (bookView == null) {
errorEvent.postValue(Throwable("Book not found"))

if (bookIds.isEmpty()) {
errorEvent.postValue(Throwable("No books found"))
} else {
val repos = dataRepository.getRepos()

val options = if (repos.isEmpty()) {
BookLinkOptions(bookView.book, emptyList(), emptyList(), -1)

BookLinkOptions(bookIds, emptyList(), emptyList(), -1)
} else {
val currentLink = bookView.linkRepo

val selectedLink = repos.indexOfFirst {
it.url == currentLink?.url
if (bookIds.size == 1) {
val bookView = dataRepository.getBookView(bookIds.first())
val currentLink = bookView?.linkRepo
val selectedLink = repos.indexOfFirst {
it.url == currentLink?.url
}
BookLinkOptions(bookIds, repos, repos.map { it.url }, selectedLink)
} else {
BookLinkOptions(bookIds, repos, repos.map { it.url }, -1)
}

BookLinkOptions(bookView.book, repos, repos.map { it.url }, selectedLink)
}

setBookLinkRequestEvent.postValue(options)
}
}
}

fun setBookLink(bookId: Long, repo: Repo? = null) {
App.EXECUTORS.diskIO().execute {
catchAndPostError {
UseCaseRunner.run(BookLinkUpdate(bookId, repo))
fun setBookLinks(bookIds: Set<Long>, repo: Repo? = null) {
for (bookId in bookIds) {
App.EXECUTORS.diskIO().execute {
catchAndPostError {
UseCaseRunner.run(BookLinkUpdate(bookId, repo))
}
}
}
}

fun forceSaveBookRequest(bookId: Long) {
App.EXECUTORS.diskIO().execute {
catchAndPostError {
UseCaseRunner.run(BookForceSave(bookId))
fun forceSaveBookRequest(bookIds: Set<Long>) {
for (bookId in bookIds) {
App.EXECUTORS.diskIO().execute {
catchAndPostError {
UseCaseRunner.run(BookForceSave(bookId))
}
}
}
}

fun forceLoadBookRequest(bookId: Long) {
App.EXECUTORS.diskIO().execute {
catchAndPostError {
UseCaseRunner.run(BookForceLoad(bookId))
fun forceLoadBookRequest(bookIds: Set<Long>) {
for (bookId in bookIds) {
App.EXECUTORS.diskIO().execute {
catchAndPostError {
UseCaseRunner.run(BookForceLoad(bookId))
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/layout/dialog_book_delete.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:gravity="start|center_vertical"
android:text="@string/also_delete_linked_book"
android:text="@string/also_delete_linked_books"
android:paddingStart="8dp"
android:paddingEnd="8dp" />

Expand Down
10 changes: 6 additions & 4 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<string name="edit_note">Edit note</string>
<string name="delete">Delete</string>
<string name="delete_with_quoted_argument">Delete “%s”</string>
<string name="delete_amount_of_books">Delete %d notebooks</string>
<string name="name">Name</string>
<string name="query">Query</string>
<string name="done">Done</string>
Expand Down Expand Up @@ -290,8 +291,9 @@
<string name="renamed_book_from">Renamed from “%s”</string>

<string name="also_delete_linked_book">Delete linked remote notebook</string>
<string name="message_deleting_book_failed">Deleting notebook failed: %s</string>
<string name="message_book_deleted">Notebook deleted</string>
<string name="also_delete_linked_books">Delete all linked remote notebooks</string>
<string name="message_deleting_book_failed">Deleting notebook(s) failed: %s</string>
<string name="message_book_deleted">Notebook(s) deleted</string>

<string name="priority_with_argument">Priority %s</string>
<string name="default_priority">Default priority</string>
Expand Down Expand Up @@ -614,8 +616,8 @@
<string name="notification_channel_settings">Notifications</string>
<string name="notification_channel_settings_summary">Sound, vibrate, notification dot</string>
<string name="hide_note_metadata">Hide metadata</string>
<string name="overwrite_local_notebook_question">Overwrite local notebook?</string>
<string name="overwrite_remote_notebook_question">Overwrite remote notebook?</string>
<string name="overwrite_local_notebook_question">Overwrite local notebook(s)?</string>
<string name="overwrite_remote_notebook_question">Overwrite remote notebook(s)?</string>
<string name="drawers_folded_by_default">Drawers folded</string>
<string name="drawers_folded_by_default_summary">Fold drawers by default</string>
<string name="log_on_time_shift">Log to drawer on time shift</string>
Expand Down

0 comments on commit c7f7ac1

Please sign in to comment.