Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8ce189c5 authored by Cassy Chun-Crogan's avatar Cassy Chun-Crogan
Browse files

[DocsUI M3] Add bottom Select and Cancel buttons to file pickers

The GET_CONTENT and OPEN_DOCUMENT pickers allow the users to pick
files, rather than folders. Previously if the user wanted to pick
the file or files they had enabled selection on, there would be a
SELECT button in the top right hand corner (in the toolbar).

We want to update this design to instead have two buttons:
Cancel and Select, in the bottom right hand corner. This is similar
to the design of the CREATE and OPEN_TREE/PICK_COPY_DESTINATION pickers.
These pickers use an additional SaveFragment/PickDirectoryFragment on
top of the PickActivity to add these buttons. We can't re-use these
fragments as their logic is quite different. The SaveFragment is
creating the picked file and the PickDirectoryFragment is picking a
folder.

Create PickFilesFragment that adds a Cancel and Select button.
The Select button is only enabled when there are files that are
selected. When the user clicks Select, those files are passed to the
PickActivity (via onDocumentPicked()/onDocumentsPicked()) just like
they were in the previous SELECT button.

PickFilesFragment can track selection through a SelectionObserver which
will notify the fragment of selection changes. We do need to be careful
here as to add a SelectionObserver we need to call
DocsSelectionHelper.addObserver(). Unfortunately the DocsSelectionHelper
gets initialised after the PickFilesFragment. Add a ResetObsever to the
DocsSelectionHelper that the PickFilesFragment can use to be notified
when the DocsSelectionHelper has been initialised (reset).

Do not show the previous SELECT button when the flag is off.

See bug for demo.

Bug: 409931248
Test: atest PickActivityTest
Test: atest ActionHandlerTest
Flag: com.android.documentsui.flags.use_material3
Change-Id: I085e527d6da0391d85f373df8bbd64140bf247a6
parent db742eef
Loading
Loading
Loading
Loading
+52 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2025 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingStart="@dimen/picker_saver_padding_start"
    android:paddingEnd="@dimen/picker_saver_padding_end"
    android:paddingBottom="@dimen/picker_saver_padding_bottom"
    android:paddingTop="@dimen/picker_saver_padding_top"
    android:background="?attr/colorSurfaceContainer"
    android:baselineAligned="false"
    android:gravity="center_vertical|end"
    android:orientation="horizontal">

        <com.google.android.material.button.MaterialButton
            android:id="@+id/button_cancel"
            style="@style/MaterialTonalButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="@dimen/picker_saver_button_gap"
            android:layout_marginTop="@dimen/picker_saver_button_gap"
            android:layout_marginBottom="@dimen/picker_saver_button_gap"
            android:text="@android:string/cancel"
            app:cornerRadius="@dimen/button_corner_radius_m3" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/button_pick"
            style="@style/MaterialButtonM3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="@dimen/picker_saver_button_gap"
            android:layout_marginTop="@dimen/picker_saver_button_gap"
            android:layout_marginBottom="@dimen/picker_saver_button_gap"
            android:text="@string/menu_select"
            app:cornerRadius="@dimen/button_corner_radius_m3" />
</LinearLayout>
+37 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import androidx.recyclerview.selection.Selection;
import androidx.recyclerview.selection.SelectionTracker;
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;

import java.util.HashSet;
import java.util.Set;

/**
@@ -42,6 +43,8 @@ public final class DocsSelectionHelper extends SelectionTracker<String> {
    // See: b/69306667.
    private SelectionTracker<String> mDelegate = new StubSelectionTracker<>();

    private Set<ResetObserver> mResetObservers = new HashSet<>();

    @VisibleForTesting
    DocsSelectionHelper(DelegateFactory factory) {
        mFactory = factory;
@@ -52,6 +55,40 @@ public final class DocsSelectionHelper extends SelectionTracker<String> {
            mDelegate.clearSelection();
        }
        mDelegate = mFactory.create(selectionTracker);
        for (ResetObserver observer : mResetObservers) {
            observer.onReset();
        }
    }

    /**
     * Observes when the DocsSelectionHelper gets reset (this happens on every initialization and
     * re-initialization).
     */
    public abstract static class ResetObserver {
        /**
         * Called when the DocsSelectionHelper resets.
         */
        public void onReset() {
        }
    }

    /**
     * Adds a ResetObserver.
     * @param observer
     */
    public void addResetObserver(ResetObserver observer) {
        if (mResetObservers.contains(observer)) {
            return;
        }
        mResetObservers.add(observer);
    }

    /**
     * Removes a ResetObserver.
     * @param observer
     */
    public void removeResetObserver(ResetObserver observer) {
        mResetObservers.remove(observer);
    }

    @Override
+27 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
import static com.android.documentsui.base.State.ACTION_OPEN;
import static com.android.documentsui.base.State.ACTION_OPEN_TREE;
import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;

import static java.util.regex.Pattern.CASE_INSENSITIVE;

@@ -71,6 +72,7 @@ import com.android.documentsui.util.FileUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;

@@ -427,6 +429,9 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
        return !doc.isContainer();
    }

    /**
     * Picks a folder for the ACTION_OPEN_TREE or ACTION_PICK_COPY_DESTINATION picker.
     */
    void pickDocument(FragmentManager fm, DocumentInfo pickTarget) {
        assert (pickTarget != null);
        mInjector.pickResult.increaseActionCount();
@@ -445,6 +450,28 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH
        }
    }

    /**
     * Picks selected documents for the ACTION_OPEN or ACTION_GET_CONTENT picker.
     */
    void pickSelected() {
        if (!isUseMaterial3FlagEnabled()) {
            return;
        }
        assert (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT);
        List<DocumentInfo> selection = mInjector.getModel().getDocuments(
                mInjector.selectionMgr.getSelection());
        if (selection.isEmpty()) {
            Log.w(TAG, "There are no selected files");
            return;
        }

        if (selection.size() > 1) {
            mActivity.onDocumentsPicked(selection);
        } else {
            mActivity.onDocumentPicked(selection.getFirst());
        }
    }

    void saveDocument(
            String mimeType, String displayName, BooleanConsumer inProgressStateListener) {
        assert (mState.action == ACTION_CREATE);
+3 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static com.android.documentsui.base.State.ACTION_GET_CONTENT;
import static com.android.documentsui.base.State.ACTION_OPEN;
import static com.android.documentsui.base.State.ACTION_OPEN_TREE;
import static com.android.documentsui.base.State.ACTION_PICK_COPY_DESTINATION;
import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
import static com.android.documentsui.util.Material3Config.getRes;

import android.database.Cursor;
@@ -137,7 +138,8 @@ public final class MenuManager extends com.android.documentsui.MenuManager {

    @Override
    protected void updateSelect(MenuItem select, SelectionDetails selectionDetails) {
        Menus.setEnabledAndVisible(select, (mState.action == ACTION_GET_CONTENT
        Menus.setEnabledAndVisible(select,
                !isUseMaterial3FlagEnabled() && (mState.action == ACTION_GET_CONTENT
                || mState.action == ACTION_OPEN)
                && selectionDetails.size() > 0);
        select.setTitle(getRes(R.string.menu_select));
+4 −1
Original line number Diff line number Diff line
@@ -245,8 +245,11 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons {
        } else if (mState.action == ACTION_OPEN_TREE ||
                mState.action == ACTION_PICK_COPY_DESTINATION) {
            PickDirectoryFragment.show(getSupportFragmentManager());
        } else if (isUseMaterial3FlagEnabled() && (mState.action == ACTION_OPEN
                || mState.action == ACTION_GET_CONTENT)) {
            PickFilesFragment.show(getSupportFragmentManager(), mState.action);
        } else if (!isUseMaterial3FlagEnabled()) {
            // If PickFragment or SaveFragment does not show,
            // If PickDirectoryFragment, PickFilesFragment or SaveFragment does not show,
            // Set save container background to transparent for edge to edge nav bar.
            // However when the use_material3 flag is on, the file path bar is at the bottom of the
            // layout and hence the edge to edge nav bar is no longer required.
Loading