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

[GSoC'24] Add Right-Click Support to Long Click Listeners #16712

Merged
merged 1 commit into from
Jul 28, 2024

Conversation

SanjaySargam
Copy link
Contributor

Purpose / Description

This PR introduces functionality to support right-click actions wherever long click listeners are implemented in the application. This enhancement aims to improve user experience by providing additional interaction capabilities on devices that support right-click actions.

Approach

yourView.setOnContextClickListener {
    showContextMenu()
    true
}

How Has This Been Tested?

HP Chromebook

Checklist

Please, go through these checks before submitting the PR.

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@SanjaySargam SanjaySargam added Needs Review GSoC Pull requests authored by a Google Summer of Code participant [Candidate/Selected], for GSoC mentors labels Jul 9, 2024
@SanjaySargam SanjaySargam changed the title Add Right-Click Support to Long Click Listeners [GSoC'24] Add Right-Click Support to Long Click Listeners Jul 9, 2024
@@ -399,6 +398,19 @@ open class DeckPicker :
)
)
}
}

private val deckLongClickListener = OnLongClickListener { v ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have a slight preference for having it directly on top of showDeckPickerContextMenu. Just to reduce the size of the diff.
And then deckContextClickListener even on top of it

@@ -179,7 +179,11 @@ open class DeckSelectionDialog : AnalyticsDialogFragment() {
}
}

private fun showSubDeckDialog(parentDeckPath: String) {
private fun showSubDeckDialog(parentDeckPath: String, deckID: DeckId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add some javadoc.
It used to be very clear what this method was doing. Now that it may not open the subdeck dialog, it's important to note this special case

showSubDeckDialog(deckName, deckID)
true
}
deckHolder.setOnContextClickListener { // creating sub deck with parent deck path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that each time you introduce setOnContextClickListener in the codebase, it does the same thing as setOnLongClickListener

I'd suggest we do something such as

    /**
     * A listener that has the same action for both "context click"  (i.e. mostly right click) and "long click" (i.e. holding the finger on the view).
     */
    interface OnContextAndLongClickListener: OnContextClickListener, OnLongClickListener {
        fun onAction(v: View): Boolean
        override fun onContextClick(v: View): Boolean {
            Timber.i("${this.javaClass}: user context clicked")
            return onAction(v)
        }
        override fun onLongClick(v: View): Boolean {
            Timber.i("${this.javaClass}: user long clicked")
            return onAction(v)
        }

        companion object {
            /**
             * Ensures [this] gets both a long click and a context click listener.
             * @see View.setOnLongClickListener
             * @see View.setOnContextClickListener
             */
            fun View.setContextAndLongClickListener(listener: OnContextAndLongClickListener) {
                setOnLongClickListener(listener)
                setOnContextClickListener(listener)
            }
        }
    }

Maybe not exactly as-is, if we can improve the usage.
Still, it would ensure we don't need to duplicate code everywhere, given that we'll probably never want to have one listener and not the other

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, when I state "something such as", or similar requirement, you don't have to copy-paste it as-is. I don't guarantee that it's working properly.
In particular, if you tested the Timber, you'd see it gives

class com.ichi2.anki.DeckPicker$$ExternalSyntheticLambda6: user long clicked

Which to be honest I only realized because I could not guess what would be the class of this with your implementation.

I admit that I have no great idea about how to solve that. I would have loved to have an extra parameter which is a string value, but that would make the code far less readable, since we could not use only the declaration as a single function.
Worse, the TagsArrayAdapter is not easy to correct

As I've no better idea, I guess we can leave it as is. Unless someone wiser find a good solution that don't require too much work.
I doubt it'll really impede debugging, given the small number of place it's used in each file.

@@ -114,6 +116,10 @@ class DeckAdapter(private val layoutInflater: LayoutInflater, context: Context)
deckLongClickListener = listener
}

fun setDeckContextClickListener(listener: OnContextClickListener?) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the parameter nullable ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reason:

    var deckContextClickListener: OnContextClickListener? = null

@@ -114,6 +116,10 @@ class DeckAdapter(private val layoutInflater: LayoutInflater, context: Context)
deckLongClickListener = listener
}

fun setDeckContextClickListener(listener: OnContextClickListener?) {
deckContextClickListener = listener
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the setter is public, I think the whole var can be public.

david-allison

This comment was marked as resolved.

Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed a comment - feels like a GitHub bug

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Jul 21, 2024
@david-allison
Copy link
Member

Really clean, thanks!

@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge and removed Needs Author Reply Waiting for a reply from the original author Needs Review labels Jul 21, 2024
* * Ensure that using the same action for both is appropriate for your use case.
*/
fun interface OnContextAndLongClickListener : View.OnContextClickListener, View.OnLongClickListener {
fun onAction(v: View): Boolean
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As usual, I'd like documentation. It can be very short, such as

The action to do for both contextClick and long click
@returns whether the operation was succesful

showSubDeckDialog(deckName, deckID)
true
}
deckHolder.setOnContextClickListener { // creating sub deck with parent deck path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, when I state "something such as", or similar requirement, you don't have to copy-paste it as-is. I don't guarantee that it's working properly.
In particular, if you tested the Timber, you'd see it gives

class com.ichi2.anki.DeckPicker$$ExternalSyntheticLambda6: user long clicked

Which to be honest I only realized because I could not guess what would be the class of this with your implementation.

I admit that I have no great idea about how to solve that. I would have loved to have an extra parameter which is a string value, but that would make the code far less readable, since we could not use only the declaration as a single function.
Worse, the TagsArrayAdapter is not easy to correct

As I've no better idea, I guess we can leave it as is. Unless someone wiser find a good solution that don't require too much work.
I doubt it'll really impede debugging, given the small number of place it's used in each file.

fun interface OnContextAndLongClickListener : View.OnContextClickListener, View.OnLongClickListener {
fun onAction(v: View): Boolean
override fun onContextClick(v: View): Boolean {
Timber.i("${this.javaClass}: user context clicked")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, when I state "something such as", or similar requirement, you don't have to copy-paste it as-is. I don't guarantee that it's working properly.
In particular, if you tested the Timber, you'd see it gives

class com.ichi2.anki.DeckPicker$$ExternalSyntheticLambda6: user long clicked

Which to be honest I only realized because I could not guess what would be the class of this with your implementation.

I admit that I have no great idea about how to solve that. I would have loved to have an extra parameter which is a string value, but that would make the code far less readable, since we could not use only the declaration as a single function.
Worse, the TagsArrayAdapter is not easy to correct

As I've no better idea, I guess we can leave it as is. Unless someone wiser find a good solution that don't require too much work.
I doubt it'll really impede debugging, given the small number of place it's used in each file.

true
}

private fun showDeckPickerContextMenu(deckId: DeckId) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with having a function that is just a function. But in this case, I think you should not have the value deckContextAndLongClickListener and directly move its content at the only place where it's used

@Arthur-Milchior
Copy link
Member

I'll want a little bit of changes, but essentially, I approve the commit as it is. If you find a way to improve timber let us know, otherwise I just want you to add the documentation/comment please

-Added right-click support for all existing long click listeners.
-Created an OnContextAndLongClickListener interface to handle both context click and long click events consistently.
 -This interface includes an onAction method to define the common action (context and long click).
 -The interface ensures that both listeners are set without duplicating code.
-Applied the OnContextAndLongClickListener interface to handle right-click and long-click events uniformly.
@Arthur-Milchior Arthur-Milchior added this pull request to the merge queue Jul 28, 2024
Merged via the queue into ankidroid:main with commit 76eea74 Jul 28, 2024
8 checks passed
@github-actions github-actions bot added this to the 2.19 release milestone Jul 28, 2024
@github-actions github-actions bot removed the Needs Second Approval Has one approval, one more approval to merge label Jul 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
GSoC Pull requests authored by a Google Summer of Code participant [Candidate/Selected], for GSoC mentors
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants