Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-feature android:name="android.hardware.touchscreen" android:required="false" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package ru.nsu.bobrofon.easysshfs.mountpointlist

import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
Expand All @@ -10,6 +11,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.AdapterView
import android.widget.Button
import android.widget.ListAdapter
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
Expand All @@ -28,6 +30,96 @@ class MountpointFragment : EasySSHFSFragment(), AdapterView.OnItemClickListener,
private lateinit var listAdapter: ListAdapter
private lateinit var mountPointsList: MountPointsList

/**
* buttonOnKeyListener is used when a mount button is focused, to implement custom D-pad
* navigation between the button and an enclosing ListView's items.
*
* The listener expects the ListView item to look like this:
* |----------------------|
* | +--------+|
* | Item view | Button ||
* | +--------+|
* |----------------------|
*/
private val buttonOnKeyListener = View.OnKeyListener { _, keycode, event ->
if (event.action != KeyEvent.ACTION_DOWN) {
return@OnKeyListener false
}

val dpadNavigationKeys = arrayOf(
KeyEvent.KEYCODE_DPAD_RIGHT,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN
)
if (!dpadNavigationKeys.contains(keycode)) {
return@OnKeyListener false
}

// We are leaving the button, so we need to pass the focus back.
listView.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
listView.requestFocus()

// The button is on the right side of the ListView item.
// Moving left means "go back to the item containing this button".
if (keycode == KeyEvent.KEYCODE_DPAD_LEFT) {
// Focus is already passed to the original ListView item.
true
} else {
// Allow ListView to select the next item. Otherwise, the currently selected item will
// remain selected after moving out of the button.
listView.onKeyDown(keycode, event)
}
}

/**
* buttonOnFocusChangeListener implements custom focus logic for buttons inside a ListView.
*/
private val buttonOnFocusChangeListener = View.OnFocusChangeListener { buttonView, hasFocus ->
if (hasFocus) {
return@OnFocusChangeListener
}

// We should keep ButtonView unfocusable to allow ListView/GridView to handle onItemClick
// properly.
buttonView.isFocusable = false

// Restore the original state in case the button lost the focus not via buttonOnKeyListener.
listView.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
}

/**
* listViewOnKeyListener is used when a mount button is not focused, to implement custom D-pad
* navigation between the selected ListView item and the button inside it.
*
* The listener expects the ListView item to look like this:
* |----------------------|
* | +--------+|
* | Item view | Button ||
* | +--------+|
* |----------------------|
*/
private val listViewOnKeyListener = View.OnKeyListener { _, keycode, event ->
val selectedItemView = listView.selectedView ?: return@OnKeyListener false

if (event.action != KeyEvent.ACTION_DOWN) {
return@OnKeyListener false
}
if (keycode != KeyEvent.KEYCODE_DPAD_RIGHT) {
return@OnKeyListener false
}
// The button is on the right side of the item view.
// Moving right means "moving to the button".

listView.descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS

val button = selectedItemView.findViewById<Button>(R.id.mountButton)
button.isFocusable = true
button.requestFocus()

true
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
Expand All @@ -37,11 +129,18 @@ class MountpointFragment : EasySSHFSFragment(), AdapterView.OnItemClickListener,
val view = inflater.inflate(R.layout.fragment_mountpoint, container, false)

mountPointsList = MountPointsList.instance(context)
listAdapter = MountPointsArrayAdapter(context, mountPointsList.mountPoints, shell)
listAdapter = MountPointsArrayAdapter(
context,
mountPointsList.mountPoints,
shell,
buttonOnFocusChangeListener,
buttonOnKeyListener
)

listView = view.findViewById(android.R.id.list)
listView.adapter = listAdapter
listView.onItemClickListener = this
listView.setOnKeyListener(listViewOnKeyListener)

mountPointsList.registerMountObserver(this)
// mountPointsList.autoMount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import ru.nsu.bobrofon.easysshfs.databinding.RowLayoutBinding
class MountPointsArrayAdapter(
context: Context,
private val values: List<MountPoint>,
private val shell: Shell
private val shell: Shell,
private val buttonOnFocusChangeListener: View.OnFocusChangeListener,
private val buttonOnKeyListener: View.OnKeyListener
) : ArrayAdapter<MountPoint>(context, R.layout.row_layout, values) {

override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
Expand All @@ -39,6 +41,8 @@ class MountPointsArrayAdapter(
mountButton.text = context.getString(R.string.mount)
mountButton.setOnClickListener { self.mount(shell, context) }
}
mountButton.onFocusChangeListener = buttonOnFocusChangeListener
mountButton.setOnKeyListener(buttonOnKeyListener)

return rowView
}
Expand Down
Loading