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

Commit e8de94a2 authored by Arc Wang's avatar Arc Wang
Browse files

Fix 'No Apps' UI issues of ManageApplications

Fixes below UI issues

- "No Apps" may not show in fragments of profile tab.

  Fix it by using ConstraintLayout to specify alignments
  of each view and removing extra padding.

-- "No Apps" may flicker by moving position.

  The flicker is from unnecessary visibility changes.
  This change integrates empty view visibility
  control in LoadingViewController to simplify code
  and avoid unnecessary visibility changes.

Bug: 189390795
Bug: 183398721
Test: atest com.android.settings.deviceinfo
      make RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.deviceinfo
      Manual visual, observe UI
        Settings -> Storage -> Games
        Settings -> Notifications -> App Settings
	Settings > Apps > Special app access > Media management apps
Change-Id: I634209c6f8466e2adae703226902190bbdf470b9
parent 1060b2f9
Loading
Loading
Loading
Loading
+33 −35
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@
     limitations under the License.
-->

<FrameLayout
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
@@ -24,40 +24,38 @@
        android:id="@+id/pinned_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:elevation="2dp"/>

    <FrameLayout
        android:id="@+id/list_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">
        android:elevation="2dp"
        settings:layout_constraintTop_toTopOf="parent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/apps_list"
        android:layout_width="match_parent"
            android:layout_height="match_parent"
        android:layout_height="wrap_content"
        android:clipToPadding="false"
        android:scrollbars="none"
        android:visibility="invisible"
        settings:fastScrollEnabled="true"
        settings:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
        settings:fastScrollHorizontalTrackDrawable="@drawable/line_drawable"
        settings:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
            settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"/>
        settings:fastScrollVerticalTrackDrawable="@drawable/line_drawable"
        settings:layout_constraintTop_toBottomOf="@id/pinned_header"/>

    <TextView
        android:id="@android:id/empty"
        android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="bottom|center_horizontal"
            android:layout_gravity="center"
        android:layout_height="0dp"
        android:gravity="center"
        android:textAlignment="gravity"
        android:text="@string/no_applications"
        android:textAppearance="?android:attr/textAppearanceLarge"
            android:visibility="invisible"/>

    </FrameLayout>

    <include layout="@layout/loading_container"/>
        android:visibility="invisible"
        settings:layout_constraintTop_toBottomOf="@id/pinned_header"
        settings:layout_constraintBottom_toBottomOf="parent"/>

</FrameLayout>
    <include layout="@layout/loading_container"
        settings:layout_constraintTop_toBottomOf="@id/pinned_header"
        settings:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
+0 −2
Original line number Diff line number Diff line
@@ -149,8 +149,6 @@
    <dimen name="wifi_assistant_height">182dp</dimen>
    <dimen name="wifi_assistant_image_top">32dp</dimen>
    <dimen name="wifi_assistant_image_start">24dp</dimen>
    <!-- appbar height is equal search bar height (48dp) plus search bar top and bottom margin  -->
    <dimen name="app_bar_height">80dp</dimen>

    <!-- CryptKeeper top margin for password/pin screen -->
    <dimen name="crypt_keeper_password_top_margin">88dip</dimen>
+5 −1
Original line number Diff line number Diff line
@@ -72,7 +72,11 @@ public class RunningServices extends SettingsPreferenceFragment {
    public void onResume() {
        super.onResume();
        boolean haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail);
        mLoadingViewController.handleLoadingContainer(haveData /* done */, false /* animate */);
        if (haveData) {
            mLoadingViewController.showContent(false /* animate */);
        } else {
            mLoadingViewController.showLoadingView();
        }
    }

    @Override
+17 −35
Original line number Diff line number Diff line
@@ -208,7 +208,6 @@ public class ManageApplications extends InstrumentedFragment
    private ApplicationsAdapter mApplications;

    private View mLoadingContainer;
    private View mListContainer;
    private SearchView mSearchView;

    // Size resource used for packages whose size computation failed for some reason
@@ -402,10 +401,8 @@ public class ManageApplications extends InstrumentedFragment

        mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
        mLoadingContainer = mRootView.findViewById(R.id.loading_container);
        mListContainer = mRootView.findViewById(R.id.list_container);
        if (mListContainer != null) {
            // Create adapter and list view here
            mEmptyView = mListContainer.findViewById(android.R.id.empty);
        mEmptyView = mRootView.findViewById(android.R.id.empty);
        mRecyclerView = mRootView.findViewById(R.id.apps_list);

        mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
                savedInstanceState);
@@ -415,12 +412,10 @@ public class ManageApplications extends InstrumentedFragment
            mApplications.mHasReceivedBridgeCallback =
                    savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
        }
            mRecyclerView = mListContainer.findViewById(R.id.apps_list);
        mRecyclerView.setItemAnimator(null);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(
                getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
        mRecyclerView.setAdapter(mApplications);
        }

        // We have to do this now because PreferenceFrameLayout looks at it
        // only when the view is added.
@@ -985,16 +980,8 @@ public class ManageApplications extends InstrumentedFragment
            // overlapped by floating filter.
            if (hasFilter) {
                mManageApplications.mSpinnerHeader.setVisibility(View.VISIBLE);
                mManageApplications.mRecyclerView.setPadding(0 /* left */,
                        mContext.getResources().getDimensionPixelSize(
                                R.dimen.app_bar_height) /* top */,
                        0 /* right */,
                        0 /* bottom */);
            } else {
                mManageApplications.mSpinnerHeader.setVisibility(View.GONE);
                mManageApplications.mRecyclerView.setPadding(0 /* left */, 0 /* top */,
                        0 /* right */,
                        0 /* bottom */);
            }
        }
    }
@@ -1044,7 +1031,8 @@ public class ManageApplications extends InstrumentedFragment
            mManageApplications = manageApplications;
            mLoadingViewController = new LoadingViewController(
                    mManageApplications.mLoadingContainer,
                    mManageApplications.mListContainer
                    mManageApplications.mRecyclerView,
                    mManageApplications.mEmptyView
            );
            mContext = manageApplications.getActivity();
            mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
@@ -1303,11 +1291,9 @@ public class ManageApplications extends InstrumentedFragment
            mOriginalEntries = entries;
            notifyDataSetChanged();
            if (getItemCount() == 0) {
                mManageApplications.mRecyclerView.setVisibility(View.GONE);
                mManageApplications.mEmptyView.setVisibility(View.VISIBLE);
                mLoadingViewController.showEmpty(false /* animate */);
            } else {
                mManageApplications.mEmptyView.setVisibility(View.GONE);
                mManageApplications.mRecyclerView.setVisibility(View.VISIBLE);
                mLoadingViewController.showContent(false /* animate */);

                if (mManageApplications.mSearchView != null
                        && mManageApplications.mSearchView.isVisibleToUser()) {
@@ -1324,10 +1310,6 @@ public class ManageApplications extends InstrumentedFragment
                mLastIndex = -1;
            }

            if (mSession.getAllApps().size() != 0
                    && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
                mLoadingViewController.showContent(true /* animate */);
            }
            if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
                // No enabled or disabled filters for usage access.
                return;
+60 −8
Original line number Diff line number Diff line
@@ -22,34 +22,66 @@ import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;

import androidx.annotation.Nullable;

/**
 * A helper class that manages show/hide loading spinner.
 * A helper class that manages show/hide loading spinner, content view and empty view (optional).
 */
public class LoadingViewController {

    private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L;

    public final Handler mFgHandler;
    public final View mLoadingView;
    public final View mContentView;
    private final Handler mFgHandler;
    private final View mLoadingView;
    private final View mContentView;
    private final View mEmptyView;

    public LoadingViewController(View loadingView, View contentView) {
        this(loadingView, contentView, null /* emptyView*/);
    }

    public LoadingViewController(View loadingView, View contentView, @Nullable View emptyView) {
        mLoadingView = loadingView;
        mContentView = contentView;
        mEmptyView = emptyView;
        mFgHandler = new Handler(Looper.getMainLooper());
    }

    private Runnable mShowLoadingContainerRunnable = new Runnable() {
        public void run() {
            handleLoadingContainer(false /* done */, false /* animate */);
            showLoadingView();
        }
    };

    /**
     *  Shows content view and hides loading view & empty view.
     */
    public void showContent(boolean animate) {
        // Cancel any pending task to show the loading animation and show the list of
        // apps directly.
        mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
        handleLoadingContainer(true /* show */, animate);
        handleLoadingContainer(true /* showContent */, false /* showEmpty*/, animate);
    }

    /**
     *  Shows empty view and hides loading view & content view.
     */
    public void showEmpty(boolean animate) {
        if (mEmptyView == null) {
            return;
        }

        // Cancel any pending task to show the loading animation and show the list of
        // apps directly.
        mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
        handleLoadingContainer(false /* showContent */, true /* showEmpty */, animate);
    }

    /**
     *  Shows loading view and hides content view & empty view.
     */
    public void showLoadingView() {
        handleLoadingContainer(false /* showContent */, false /* showEmpty */, false /* animate */);
    }

    public void showLoadingViewDelayed() {
@@ -57,8 +89,9 @@ public class LoadingViewController {
                mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS);
    }

    public void handleLoadingContainer(boolean done, boolean animate) {
        handleLoadingContainer(mLoadingView, mContentView, done, animate);
    private void handleLoadingContainer(boolean showContent, boolean showEmpty, boolean animate) {
        handleLoadingContainer(mLoadingView, mContentView, mEmptyView,
                showContent, showEmpty, animate);
    }

    /**
@@ -75,6 +108,25 @@ public class LoadingViewController {
        setViewShown(content, done, animate);
    }

    /**
     * Show/hide loading view and content view and empty view.
     *
     * @param loading The loading spinner view
     * @param content The content view
     * @param empty The empty view shows no item summary to users.
     * @param showContent    If true, content is set visible and loading is set invisible.
     * @param showEmpty    If true, empty is set visible and loading is set invisible.
     * @param animate Whether or not content/loading views should animate in/out.
     */
    public static void handleLoadingContainer(View loading, View content, View empty,
            boolean showContent, boolean showEmpty, boolean animate) {
        if (empty != null) {
            setViewShown(empty, showEmpty, animate);
        }
        setViewShown(content, showContent, animate);
        setViewShown(loading, !showContent && !showEmpty, animate);
    }

    private static void setViewShown(final View view, boolean shown, boolean animate) {
        if (animate) {
            Animation animation = AnimationUtils.loadAnimation(view.getContext(),
Loading