Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.test.espresso.intent.VerificationModes.times
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand Down Expand Up @@ -124,9 +125,8 @@ class MainActivityTests {
// the load itself and not just the picker round trip.
waitForDocumentActions()

unfoldDocumentActions()

onView(withText(R.string.menu_edit)).check(matches(isDisplayed()))
// nothing unfolded: Edit stands on its own where the core can write the document back
onView(withContentDescription(R.string.menu_edit)).check(matches(isDisplayed()))
}

@Test
Expand All @@ -142,7 +142,7 @@ class MainActivityTests {

unfoldDocumentActions()

onView(withText(R.string.menu_edit)).check(doesNotExist())
onView(withContentDescription(R.string.menu_edit)).check(doesNotExist())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener {

// guarded like resetTabs below: a load can fail before there is a view to put right
if (::actions.isInitialized) {
actions.setActions(null, emptyList())
actions.setActions(emptyList(), emptyList())
}

resetTabs()
Expand Down Expand Up @@ -558,7 +558,6 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener {
R.string.menu_fullscreen,
R.drawable.ic_fullscreen,
),
edit,
DocumentActions.Action(
DocumentActions.ACTION_TTS,
R.string.menu_tts,
Expand Down Expand Up @@ -586,11 +585,16 @@ class DocumentFragment : Fragment(), DocumentLoader.Listener {
),
)

// Edit above Search, not below it: Search is offered for every document and Edit is not,
// so this is the order that keeps the button nearest the thumb the same one throughout
actions.setActions(
DocumentActions.Action(
DocumentActions.ACTION_SEARCH,
R.string.menu_search,
R.drawable.ic_search,
listOfNotNull(
edit,
DocumentActions.Action(
DocumentActions.ACTION_SEARCH,
R.string.menu_search,
R.drawable.ic_search,
),
),
unfolding,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ import android.widget.ScrollView
import android.widget.TextView
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.widget.TooltipCompat
import app.opendocument.droid.R
import com.google.android.material.floatingactionbutton.FloatingActionButton

/**
* What can be done with the open document, as buttons over the bottom right corner of it: one for
* the action worth its own button, and one that unfolds the rest.
* each action worth its own button, and one that unfolds the rest.
*
* This is what the toolbar menu used to be. A document is read with the phone in one hand, and the
* top right corner of a modern screen is the one place a thumb cannot reach - so the actions sit
* where the thumb already is, and the ones that were hidden behind "More options" now say what they
* are.
*
* Material ships no speed dial component (the one it had was never brought over to Material 3), so
* the rows are built here from [R.layout.item_document_action].
* the buttons are built here, from [R.layout.item_document_action_standing] and
* [R.layout.item_document_action].
*/
class DocumentActions(context: Context, attributeSet: AttributeSet?) :
FrameLayout(context, attributeSet) {
Expand All @@ -44,7 +46,7 @@ class DocumentActions(context: Context, attributeSet: AttributeSet?) :
private val buttons: LinearLayout
private val rowsScroll: ScrollView
private val rows: LinearLayout
private val primaryButton: FloatingActionButton
private val standingButtons: LinearLayout
private val moreButton: FloatingActionButton

private val basePaddingBottom: Int
Expand All @@ -64,15 +66,15 @@ class DocumentActions(context: Context, attributeSet: AttributeSet?) :
buttons = findViewById(R.id.document_actions_buttons)
rowsScroll = findViewById(R.id.document_actions_rows_scroll)
rows = findViewById(R.id.document_actions_rows)
primaryButton = findViewById(R.id.document_actions_primary)
standingButtons = findViewById(R.id.document_actions_standing)
moreButton = findViewById(R.id.document_actions_more)

basePaddingBottom = buttons.paddingBottom

scrim.setOnClickListener { collapse() }
moreButton.setOnClickListener { if (isExpanded) collapse() else expand() }

setActions(null, emptyList())
setActions(emptyList(), emptyList())
}

/**
Expand All @@ -92,22 +94,20 @@ class DocumentActions(context: Context, attributeSet: AttributeSet?) :
}

/**
* What the document can do right now. [primary] gets a button of its own, the rest unfold out
* of the second one, the first of them closest to it - so the order is most wanted first.
* What the document can do right now. Each of [standing] keeps a button of its own whether the
* rest are folded up or not, the last of them closest to the thumb; [unfolding] comes out of
* the button below them, its first entry closest to it - so both orders are most wanted first.
*
* Nothing and an empty list take the buttons away entirely, which is what a document that
* failed to load leaves behind.
* Two empty lists take the buttons away entirely, which is what a document that failed to load
* leaves behind.
*/
fun setActions(primary: Action?, unfolding: List<Action>) {
fun setActions(standing: List<Action>, unfolding: List<Action>) {
collapse()

if (primary == null) {
primaryButton.visibility = View.GONE
} else {
primaryButton.visibility = View.VISIBLE
primaryButton.setImageResource(primary.icon)
primaryButton.contentDescription = context.getString(primary.label)
primaryButton.setOnClickListener { listener?.onDocumentActionClicked(primary.id) }
standingButtons.removeAllViews()

for (action in standing) {
standingButtons.addView(newStandingButton(action))
}

rows.removeAllViews()
Expand All @@ -121,6 +121,23 @@ class DocumentActions(context: Context, attributeSet: AttributeSet?) :
moreButton.visibility = if (unfolding.isEmpty()) View.GONE else View.VISIBLE
}

private fun newStandingButton(action: Action): View {
val button =
LayoutInflater.from(context)
.inflate(R.layout.item_document_action_standing, standingButtons, false)
as FloatingActionButton

button.setImageResource(action.icon)
button.contentDescription = context.getString(action.label)

// no label plate beside it, so the name is what a long press turns up
TooltipCompat.setTooltipText(button, context.getString(action.label))

button.setOnClickListener { listener?.onDocumentActionClicked(action.id) }

return button
}

private fun newRow(action: Action): View {
val row = LayoutInflater.from(context).inflate(R.layout.item_document_action, rows, false)

Expand Down
8 changes: 8 additions & 0 deletions app/src/main/res/layout/item_document_action_standing.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- one action that stands on its own, folded up or not - see DocumentActions -->
<com.google.android.material.floatingactionbutton.FloatingActionButton xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/Widget.Material3.FloatingActionButton.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginBottom="16dp" />
11 changes: 7 additions & 4 deletions app/src/main/res/layout/view_document_actions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,16 @@
android:orientation="vertical" />
</ScrollView>

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/document_actions_primary"
style="@style/Widget.Material3.FloatingActionButton.Secondary"
<!-- the actions that do not fold away, above the button the rest unfold from -->
<LinearLayout
android:id="@+id/document_actions_standing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginBottom="16dp" />
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="end"
android:orientation="vertical" />

<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/document_actions_more"
Expand Down
Loading