diff --git a/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt b/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt index ec18c4b0b3d1..7dc86df7dcf5 100644 --- a/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt +++ b/app/src/androidTest/java/app/opendocument/droid/test/MainActivityTests.kt @@ -22,9 +22,7 @@ import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.matcher.ViewMatchers.isDisplayed -import androidx.test.espresso.matcher.ViewMatchers.isEnabled 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 @@ -121,7 +119,11 @@ class MainActivityTests { // next onView will be blocked until the idling resource is idle, which now covers // the load itself and not just the picker round trip. - clickEditWithOverflowFallback() + waitForDocumentActions() + + unfoldDocumentActions() + + onView(withText(R.string.menu_edit)).check(matches(isDisplayed())) } @Test @@ -132,7 +134,7 @@ class MainActivityTests { // next onView will be blocked until the idling resource is idle, which now covers // the load itself and not just the picker round trip. - clickEditWithOverflowFallback() + waitForDocumentActions() // there used to be a 10s sleep here that asserted nothing, and it is why "testPDF // crashed" was an api 34 bucket of its own: the app is killed whenever play services @@ -185,8 +187,8 @@ class MainActivityTests { onView(withId(android.R.id.button1)).perform(click()) - // Check if edit button becomes available (indicating successful load) - clickEditWithOverflowFallback() + // Check if the document buttons become available (indicating successful load) + waitForDocumentActions() } @Test @@ -269,22 +271,15 @@ class MainActivityTests { .perform(click()) } - private fun clickEditWithOverflowFallback() { - onView(allOf(withId(R.id.menu_edit), withContentDescription("Edit document"), isEnabled())) - .withFailureHandler { _, _ -> - // fails on small screens, try again with overflow menu - onView(allOf(withContentDescription("More options"), isDisplayed())) - .perform(click()) - - onView( - allOf( - withId(R.id.menu_edit), - withContentDescription("Edit document"), - isDisplayed(), - ) - ) - .perform(click()) - } + // the buttons of the open document are up once it has loaded, so this blocks on the idling + // resource and then says whether anything came of the load. only the button that unfolds the + // rest is checked: what is inside it differs per format, a pdf cannot be edited. + private fun waitForDocumentActions() { + onView(withId(R.id.document_actions_more)).check(matches(isDisplayed())) + } + + private fun unfoldDocumentActions() { + onView(withId(R.id.document_actions_more)).perform(click()) } private fun recreate(activity: MainActivity): MainActivity? { diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt index 5fe7aa31c5bc..ed774bbe0b88 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/DocumentFragment.kt @@ -8,16 +8,13 @@ import android.os.Handler import android.os.Looper import android.text.InputType import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.EditText import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog -import androidx.core.view.MenuProvider import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -32,6 +29,7 @@ import app.opendocument.droid.nonfree.AnalyticsManager import app.opendocument.droid.nonfree.CrashManager import app.opendocument.droid.ui.OpenFileIdling import app.opendocument.droid.ui.SnackbarHelper +import app.opendocument.droid.ui.widget.DocumentActions import app.opendocument.droid.ui.widget.PageView import app.opendocument.droid.ui.widget.ProgressDialogFragment import com.google.android.material.tabs.TabLayout @@ -40,7 +38,7 @@ import java.io.File import java.io.FileNotFoundException import java.io.IOException -class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider { +class DocumentFragment : Fragment(), LoaderService.LoaderListener { private lateinit var mainHandler: Handler @@ -56,7 +54,15 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider var pageView: PageView? = null private set - private var menu: Menu? = null + private lateinit var actions: DocumentActions + + /** Folding the actions back up is what back does first, while they are unfolded. */ + private val actionsBackCallback = + object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + actions.collapse() + } + } private var resultOnStart: FileLoader.Result? = null private var errorOnStart: Throwable? = null @@ -135,11 +141,7 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { - requireActivity().addMenuProvider(this, requireActivity()) - - return inflater.inflate(R.layout.fragment_document, container, false) - } + ): View? = inflater.inflate(R.layout.fragment_document, container, false) private fun initializePageView() { pageView?.let { @@ -186,6 +188,16 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider analyticsManager = mainActivity.analyticsManager crashManager = mainActivity.crashManager + actions = view.findViewById(R.id.document_actions) + actions.listener = DocumentActions.Listener { action -> + mainActivity.onDocumentAction(action) + } + actions.expandedListener = { expanded -> actionsBackCallback.isEnabled = expanded } + + // on viewLifecycleOwner, so it stacks above the activity's own callback - the dispatcher + // runs the most recently added enabled callback first + mainActivity.onBackPressedDispatcher.addCallback(viewLifecycleOwner, actionsBackCallback) + serviceQueue = mainActivity.loaderServiceQueue serviceQueue.addToQueue { service -> service.setListener(this) } @@ -213,6 +225,7 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider prepareLoad(lastResult.loaderType, false) restoreTabs(lastResult) + prepareActions(lastResult) } pageView?.restoreState(savedInstanceState) @@ -244,24 +257,6 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider tabLayout.addOnTabSelectedListener(tabSelectedListener) } - override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { - this.menu = menu - - menu.findItem(R.id.menu_fullscreen).isVisible = true - menu.findItem(R.id.menu_open_with).isVisible = true - menu.findItem(R.id.menu_share).isVisible = true - menu.findItem(R.id.menu_save).isVisible = true - menu.findItem(R.id.menu_print).isVisible = true - - // the other menu items are dynamically enabled based on the loaded document - state.lastResult?.let { prepareMenu(it.loaderType, it.options.fileType) } - } - - override fun onMenuItemSelected(menuItem: MenuItem): Boolean { - // TODO: handle menu item clicks here. currently done in Activity for historical reasons - return false - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) @@ -360,7 +355,10 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider } private fun unload() { - toggleDocumentMenu(false) + // guarded like resetTabs below: a load can fail before there is a view to put right + if (::actions.isInitialized) { + actions.setActions(null, emptyList()) + } resetTabs() } @@ -375,32 +373,17 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider state.lastSelectedTab = -1 } - private fun toggleDocumentMenu(enabled: Boolean) { - toggleDocumentMenu(enabled, enabled) - } - - private fun toggleDocumentMenu(enabled: Boolean, editEnabled: Boolean) { - val menu = this.menu - if (menu == null) { - val activity = activity - val pageView = this.pageView - if (activity == null || activity.isFinishing || pageView == null) { - return - } - - // menu is not set when loadUri is called via onStart, retry later - pageView.post { toggleDocumentMenu(enabled, editEnabled) } - - return - } - - menu.findItem(R.id.menu_edit).isVisible = editEnabled - - menu.findItem(R.id.menu_search).isVisible = enabled - menu.findItem(R.id.menu_tts).isVisible = enabled - } + /** + * Puts the buttons of the loaded document up, in the order they are worth reaching for. + * + * Called for every result rather than from a menu callback, which is what the toolbar version + * relied on: the menu was only rebuilt when something happened to invalidate it, and a document + * that finished loading is not one of those things. + */ + private fun prepareActions(result: FileLoader.Result) { + val loaderType = result.loaderType + val fileType = result.options.fileType - private fun prepareMenu(loaderType: FileLoader.LoaderType, fileType: String?) { var isEditEnabled = false var isDarkModeSupported = true @@ -423,10 +406,81 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider } } - toggleDocumentMenu(true, isEditEnabled) + val unfolding = ArrayList() + if (isEditEnabled) { + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_EDIT, + R.string.menu_edit, + R.drawable.ic_edit, + ) + ) + } + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_TTS, + R.string.menu_tts, + R.drawable.ic_volume_up, + ) + ) + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_SHARE, + R.string.menu_share, + R.drawable.ic_share, + ) + ) + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_PRINT, + R.string.menu_cloud_print, + R.drawable.ic_print, + ) + ) + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_OPEN_WITH, + R.string.menu_open_with, + R.drawable.ic_open_in_new, + ) + ) + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_SAVE, + R.string.action_edit_save, + R.drawable.ic_save, + ) + ) + unfolding.add( + DocumentActions.Action( + DocumentActions.ACTION_FULLSCREEN, + R.string.menu_fullscreen, + R.drawable.ic_fullscreen, + ) + ) + + actions.setActions( + DocumentActions.Action( + DocumentActions.ACTION_SEARCH, + R.string.menu_search, + R.drawable.ic_search, + ), + unfolding, + ) + pageView?.toggleDarkMode(isDarkModeSupported) } + /** Takes the buttons away while the document has the screen to itself. */ + fun setActionsVisible(visible: Boolean) { + if (!::actions.isInitialized) { + return + } + + actions.collapse() + actions.visibility = if (visible) View.VISIBLE else View.GONE + } + private fun requestInAppRating(activity: Activity) { analyticsManager.report("in_app_review_eligible") @@ -494,6 +548,8 @@ class DocumentFragment : Fragment(), LoaderService.LoaderListener, MenuProvider loadData(result.partUris[0].toString()) } + prepareActions(result) + if ( result.loaderType == FileLoader.LoaderType.RAW || result.loaderType == FileLoader.LoaderType.ONLINE diff --git a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt index 30779b51bdfb..0545b4029675 100644 --- a/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt +++ b/app/src/main/java/app/opendocument/droid/ui/activity/MainActivity.kt @@ -12,15 +12,11 @@ import android.os.Handler import android.os.IBinder import android.os.Looper import android.view.ActionMode -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem import android.view.View import android.widget.LinearLayout import androidx.activity.OnBackPressedCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.MenuProvider import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -41,10 +37,11 @@ import app.opendocument.droid.ui.FindActionModeCallback import app.opendocument.droid.ui.OpenFileIdling import app.opendocument.droid.ui.SnackbarHelper import app.opendocument.droid.ui.TtsActionModeCallback +import app.opendocument.droid.ui.widget.DocumentActions import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability -class MainActivity : AppCompatActivity(), MenuProvider { +class MainActivity : AppCompatActivity() { private lateinit var handler: Handler @@ -255,8 +252,6 @@ class MainActivity : AppCompatActivity(), MenuProvider { analyticsManager.setCurrentScreen(this, "screen_main") } - - addMenuProvider(this, this) } override fun onStart() { @@ -363,10 +358,6 @@ class MainActivity : AppCompatActivity(), MenuProvider { ) } - override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { - menuInflater.inflate(R.menu.menu_main, menu) - } - // The play services availability dialog calls startActivityForResult() itself with a // request code we hand it, so its result cannot be routed through an // ActivityResultLauncher. Everything the app launches on its own goes through the @@ -435,11 +426,16 @@ class MainActivity : AppCompatActivity(), MenuProvider { documentFragment.loadUri(uri, isPersistentUri) } - override fun onMenuItemSelected(item: MenuItem): Boolean { + /** + * A button of the open document was tapped. These used to be the toolbar menu, and the ids are + * now [DocumentActions]' own - the handling stays here, where the action modes and the printing + * manager already live. + */ + fun onDocumentAction(action: Int) { val documentFragment = this.documentFragment - when (item.itemId) { - R.id.menu_search -> { + when (action) { + DocumentActions.ACTION_SEARCH -> { val findActionModeCallback = FindActionModeCallback(this) documentFragment?.pageView?.let { findActionModeCallback.setWebView(it) } startSupportActionMode(findActionModeCallback) @@ -448,25 +444,25 @@ class MainActivity : AppCompatActivity(), MenuProvider { analyticsManager.report(AnalyticsConstants.EVENT_SEARCH) } - R.id.menu_open_with -> { + DocumentActions.ACTION_OPEN_WITH -> { documentFragment?.openWith(this) analyticsManager.report("menu_open_with") } - R.id.menu_save -> { + DocumentActions.ACTION_SAVE -> { documentFragment?.prepareSave({ requestSave() }, true) analyticsManager.report("menu_save") } - R.id.menu_share -> { + DocumentActions.ACTION_SHARE -> { documentFragment?.share(this) analyticsManager.report("menu_share") } - R.id.menu_fullscreen -> { + DocumentActions.ACTION_FULLSCREEN -> { if (fullscreen) { analyticsManager.report("menu_fullscreen_leave") @@ -496,9 +492,11 @@ class MainActivity : AppCompatActivity(), MenuProvider { } fullscreen = !fullscreen + + updateDocumentActionsVisible() } - R.id.menu_print -> { + DocumentActions.ACTION_PRINT -> { analyticsManager.report("menu_print") documentFragment?.pageView?.let { pageView -> @@ -508,7 +506,7 @@ class MainActivity : AppCompatActivity(), MenuProvider { } } - R.id.menu_tts -> { + DocumentActions.ACTION_TTS -> { analyticsManager.report("menu_tts") documentFragment?.pageView?.let { pageView -> @@ -519,7 +517,7 @@ class MainActivity : AppCompatActivity(), MenuProvider { } } - R.id.menu_edit -> { + DocumentActions.ACTION_EDIT -> { analyticsManager.report("menu_edit") documentFragment?.let { fragment -> @@ -529,11 +527,7 @@ class MainActivity : AppCompatActivity(), MenuProvider { startSupportActionMode(editActionMode) } } - - else -> return super.onOptionsItemSelected(item) } - - return true } private fun offerPurchase() { @@ -555,13 +549,56 @@ class MainActivity : AppCompatActivity(), MenuProvider { ) } - override fun onActionModeFinished(mode: ActionMode?) { - super.onActionModeFinished(mode) + /** + * The buttons of the document are only up while nothing else is using the screen: an action + * mode has taken the toolbar over and brought its own controls, and fullscreen is for reading. + * + * Counted rather than a flag, because the two kinds of action mode overlap - selecting text in + * the page starts a framework one on top of the appcompat one that edit mode is. + */ + private var actionModes = 0 + + private fun updateDocumentActionsVisible() { + documentFragment?.setActionsVisible(actionModes == 0 && !fullscreen) + } + + // the appcompat ones, which is what startSupportActionMode() raises: find, tts and edit + override fun onSupportActionModeStarted(mode: androidx.appcompat.view.ActionMode) { + super.onSupportActionModeStarted(mode) + + actionModes++ + + updateDocumentActionsVisible() + } + + override fun onSupportActionModeFinished(mode: androidx.appcompat.view.ActionMode) { + super.onSupportActionModeFinished(mode) + + actionModes-- + + updateDocumentActionsVisible() editActionMode = null ttsActionMode = null } + // and the framework ones, which is what selecting text in the page raises + override fun onActionModeStarted(mode: ActionMode?) { + super.onActionModeStarted(mode) + + actionModes++ + + updateDocumentActionsVisible() + } + + override fun onActionModeFinished(mode: ActionMode?) { + super.onActionModeFinished(mode) + + actionModes-- + + updateDocumentActionsVisible() + } + /** * Whether the ad removal is still worth offering: never in pro, where the purchase is implied, * and not once it has been bought. @@ -598,13 +635,13 @@ class MainActivity : AppCompatActivity(), MenuProvider { fullscreen = false + updateDocumentActionsVisible() + analyticsManager.report("fullscreen_end") } private fun closeDocument() { documentFragment?.let { fragment -> - removeMenuProvider(fragment) - supportFragmentManager.beginTransaction().remove(fragment).commitNow() documentFragment = null diff --git a/app/src/main/java/app/opendocument/droid/ui/widget/DocumentActions.kt b/app/src/main/java/app/opendocument/droid/ui/widget/DocumentActions.kt new file mode 100644 index 000000000000..7d3572183a31 --- /dev/null +++ b/app/src/main/java/app/opendocument/droid/ui/widget/DocumentActions.kt @@ -0,0 +1,196 @@ +package app.opendocument.droid.ui.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +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. + * + * 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]. + */ +class DocumentActions(context: Context, attributeSet: AttributeSet?) : + FrameLayout(context, attributeSet) { + + /** One button, identified by one of the ACTION_ ids the caller gets back when it is tapped. */ + class Action(val id: Int, @param:StringRes val label: Int, @param:DrawableRes val icon: Int) + + fun interface Listener { + fun onDocumentActionClicked(id: Int) + } + + var listener: Listener? = null + + /** Told whenever the actions unfold or fold back up, so back can close them. */ + var expandedListener: ((Boolean) -> Unit)? = null + + private val scrim: View + private val rowsScroll: ScrollView + private val rows: LinearLayout + private val primaryButton: FloatingActionButton + private val moreButton: FloatingActionButton + + var isExpanded: Boolean = false + private set + + init { + LayoutInflater.from(context).inflate(R.layout.view_document_actions, this, true) + + scrim = findViewById(R.id.document_actions_scrim) + rowsScroll = findViewById(R.id.document_actions_rows_scroll) + rows = findViewById(R.id.document_actions_rows) + primaryButton = findViewById(R.id.document_actions_primary) + moreButton = findViewById(R.id.document_actions_more) + + scrim.setOnClickListener { collapse() } + moreButton.setOnClickListener { if (isExpanded) collapse() else expand() } + + setActions(null, emptyList()) + } + + /** + * 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. + * + * Nothing and an empty list take the buttons away entirely, which is what a document that + * failed to load leaves behind. + */ + fun setActions(primary: Action?, unfolding: List) { + 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) } + } + + rows.removeAllViews() + + // added back to front, because the row added last is the one that ends up at the bottom + // of the column, right above the button they unfold from + for (action in unfolding.asReversed()) { + rows.addView(newRow(action)) + } + + moreButton.visibility = if (unfolding.isEmpty()) View.GONE else View.VISIBLE + } + + private fun newRow(action: Action): View { + val row = LayoutInflater.from(context).inflate(R.layout.item_document_action, rows, false) + + val label: TextView = row.findViewById(R.id.document_action_label) + label.setText(action.label) + + val button: FloatingActionButton = row.findViewById(R.id.document_action_button) + button.setImageResource(action.icon) + button.contentDescription = context.getString(action.label) + + // the button is only the visible half of the row: the label is as much of a target + val click = OnClickListener { + collapse() + + listener?.onDocumentActionClicked(action.id) + } + row.setOnClickListener(click) + button.setOnClickListener(click) + + return row + } + + private fun expand() { + if (isExpanded || rows.childCount == 0) { + return + } + + isExpanded = true + + // a fold that was still fading out would otherwise hide all of this again when it ends + scrim.animate().cancel() + rowsScroll.animate().cancel() + + scrim.alpha = 0f + scrim.visibility = View.VISIBLE + scrim.animate().alpha(1f).setDuration(ANIMATION_MILLIS) + + rowsScroll.alpha = 1f + rowsScroll.visibility = View.VISIBLE + + // the rows hang off the bottom of the scroller, and that is the end the buttons are at + rowsScroll.post { rowsScroll.fullScroll(View.FOCUS_DOWN) } + + val rise = RISE_DP * resources.displayMetrics.density + for (index in 0 until rows.childCount) { + val row = rows.getChildAt(index) + + row.alpha = 0f + row.translationY = rise + row.animate().alpha(1f).translationY(0f).setDuration(ANIMATION_MILLIS) + } + + moreButton.setImageResource(R.drawable.ic_close) + moreButton.contentDescription = context.getString(R.string.document_actions_close) + + expandedListener?.invoke(true) + } + + fun collapse() { + if (!isExpanded) { + // also the state setActions() starts from, so the views have to be put right anyway + scrim.visibility = View.GONE + rowsScroll.visibility = View.GONE + + return + } + + isExpanded = false + + scrim.animate().alpha(0f).setDuration(ANIMATION_MILLIS).withEndAction { + scrim.visibility = View.GONE + } + + rowsScroll.animate().alpha(0f).setDuration(ANIMATION_MILLIS).withEndAction { + rowsScroll.visibility = View.GONE + rowsScroll.alpha = 1f + } + + moreButton.setImageResource(R.drawable.ic_more_vert) + moreButton.contentDescription = context.getString(R.string.document_actions_more) + + expandedListener?.invoke(false) + } + + companion object { + const val ACTION_SEARCH: Int = 1 + const val ACTION_EDIT: Int = 2 + const val ACTION_TTS: Int = 3 + const val ACTION_SHARE: Int = 4 + const val ACTION_PRINT: Int = 5 + const val ACTION_OPEN_WITH: Int = 6 + const val ACTION_SAVE: Int = 7 + const val ACTION_FULLSCREEN: Int = 8 + + private const val ANIMATION_MILLIS = 150L + + /** How far below its place a row starts out, so the column rises as it appears. */ + private const val RISE_DP = 16f + } +} diff --git a/app/src/main/res/drawable/bg_document_action_label.xml b/app/src/main/res/drawable/bg_document_action_label.xml new file mode 100644 index 000000000000..4eaa6a035da3 --- /dev/null +++ b/app/src/main/res/drawable/bg_document_action_label.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 000000000000..7a52b3b744f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_vert.xml b/app/src/main/res/drawable/ic_more_vert.xml new file mode 100644 index 000000000000..2d31aa6ebe86 --- /dev/null +++ b/app/src/main/res/drawable/ic_more_vert.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_open_in_new.xml b/app/src/main/res/drawable/ic_open_in_new.xml new file mode 100644 index 000000000000..1bd20d427424 --- /dev/null +++ b/app/src/main/res/drawable/ic_open_in_new.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_print.xml b/app/src/main/res/drawable/ic_print.xml new file mode 100644 index 000000000000..5175220eb535 --- /dev/null +++ b/app/src/main/res/drawable/ic_print.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 000000000000..b9996940831c --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_volume_up.xml b/app/src/main/res/drawable/ic_volume_up.xml new file mode 100644 index 000000000000..62f0a8e4cc05 --- /dev/null +++ b/app/src/main/res/drawable/ic_volume_up.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_document.xml b/app/src/main/res/layout/fragment_document.xml index ff854beddbc6..774974211d26 100644 --- a/app/src/main/res/layout/fragment_document.xml +++ b/app/src/main/res/layout/fragment_document.xml @@ -1,20 +1,34 @@ - - - + android:layout_height="match_parent" + android:orientation="vertical"> - + + + - + diff --git a/app/src/main/res/layout/item_document_action.xml b/app/src/main/res/layout/item_document_action.xml new file mode 100644 index 000000000000..2cdcae9f12a5 --- /dev/null +++ b/app/src/main/res/layout/item_document_action.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/layout/view_document_actions.xml b/app/src/main/res/layout/view_document_actions.xml new file mode 100644 index 000000000000..0823f936e4f7 --- /dev/null +++ b/app/src/main/res/layout/view_document_actions.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml deleted file mode 100644 index 5e0e7c730bfe..000000000000 --- a/app/src/main/res/menu/menu_main.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 082052a015f3..012ea56e1d19 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -48,4 +48,10 @@ --> #FFFFFF + + #52000000 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c36f2b8818a1..d7e2b05ec1d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -53,6 +53,8 @@ Up one level Nothing here yet Open a document to get started. It will show up here next time. + More actions + Close actions Reading… Initializing TTS… Ready!