Merge "Implement two pane widget picker" into tm-qpr-dev am: 740541e0a0
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20508230 Change-Id: Id1c208c28b7f40eeb1b3446c0c4bf569eb791482 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
Copyright (C) 2023 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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/widget_picker_background_selected"
|
||||
android:gravity="center"
|
||||
>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16.65,13 L11,7.35 16.65,1.7 22.3,7.35ZM3,11V3H11V11ZM13,21V13H21V21ZM3,21V13H11V21ZM5,9H9V5H5ZM16.675,10.2 L19.5,7.375 16.675,4.55 13.85,7.375ZM15,19H19V15H15ZM5,19H9V15H5ZM9,9ZM13.85,7.375ZM9,15ZM15,15Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
Copyright (C) 2023 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.
|
||||
-->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<size
|
||||
android:width="48dp"
|
||||
android:height="48dp" />
|
||||
<solid android:color="@color/surface"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:drawable="@drawable/widget_suggestions"
|
||||
android:gravity="center" />
|
||||
</layer-list>
|
||||
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2022 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.
|
||||
-->
|
||||
<com.android.launcher3.widget.picker.WidgetsFullSheet xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:theme="?attr/widgetsTheme">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_widgets_full_sheet"
|
||||
android:focusable="true"
|
||||
android:importantForAccessibility="no">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/recycler_view_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/right_pane"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintWidth_percent="0.33">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/no_widgets_text"
|
||||
style="@style/PrimaryHeadline"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
tools:text="No widgets available" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fast_scroller_popup"
|
||||
style="@style/FastScrollerPopup"
|
||||
android:layout_marginEnd="@dimen/fastscroll_popup_margin" />
|
||||
|
||||
<!-- Fast scroller popup -->
|
||||
<com.android.launcher3.views.RecyclerViewFastScroller
|
||||
android:id="@+id/fast_scroller"
|
||||
android:layout_width="@dimen/fastscroll_width"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/fastscroll_end_margin" />
|
||||
|
||||
<com.android.launcher3.widget.picker.WidgetsRecyclerView
|
||||
android:id="@+id/search_widgets_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/right_pane"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/recycler_view_container"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="8dp"
|
||||
android:layout_marginTop="26dp"
|
||||
app:layout_constraintWidth_percent="0.67">
|
||||
|
||||
<com.android.launcher3.widget.picker.WidgetsRecommendationTableLayout
|
||||
android:id="@+id/recommended_widget_table"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/widgets_surface_background"
|
||||
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:paddingVertical="@dimen/recommended_widgets_table_vertical_padding"
|
||||
android:visibility="gone" />
|
||||
</ScrollView>
|
||||
|
||||
<View
|
||||
android:id="@+id/collapse_handle"
|
||||
android:layout_width="@dimen/bottom_sheet_handle_width"
|
||||
android:layout_height="@dimen/bottom_sheet_handle_height"
|
||||
android:layout_marginTop="@dimen/bottom_sheet_handle_margin"
|
||||
android:background="@drawable/bg_rounded_corner_bottom_sheet_handle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/collapse_handle"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:text="@string/widget_button_text"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="24sp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.android.launcher3.widget.picker.WidgetsFullSheet>
|
||||
@@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2023 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.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widgets_full_sheet_paged_view_large_screen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/scrollView"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintWidth_percent="0.33">
|
||||
<com.android.launcher3.widget.picker.WidgetPagedView
|
||||
android:id="@+id/widgets_view_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:descendantFocusability="afterDescendants"
|
||||
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
launcher:pageIndicator="@+id/tabs" >
|
||||
|
||||
<com.android.launcher3.widget.picker.WidgetsRecyclerView
|
||||
android:id="@+id/primary_widgets_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<com.android.launcher3.widget.picker.WidgetsRecyclerView
|
||||
android:id="@+id/work_widgets_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
</com.android.launcher3.widget.picker.WidgetPagedView>
|
||||
|
||||
<!-- SearchAndRecommendationsView without the tab layout as well -->
|
||||
<com.android.launcher3.views.StickyHeaderLayout
|
||||
android:id="@+id/search_and_recommendations_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/search_bar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="0.1dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
launcher:layout_sticky="true">
|
||||
|
||||
<include layout="@layout/widgets_search_bar" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/suggestions_header"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:orientation="horizontal">
|
||||
</LinearLayout>
|
||||
|
||||
<com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
|
||||
android:id="@+id/tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="64dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="horizontal"
|
||||
android:paddingVertical="8dp"
|
||||
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:background="?android:attr/colorBackground"
|
||||
style="@style/TextHeadline"
|
||||
launcher:layout_sticky="true">
|
||||
|
||||
<Button
|
||||
android:id="@+id/tab_personal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
|
||||
android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/all_apps_tabs_background"
|
||||
android:text="@string/widgets_full_sheet_personal_tab"
|
||||
android:textColor="@color/all_apps_tab_text"
|
||||
android:textSize="14sp"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/tab_work"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginEnd="@dimen/widget_tabs_button_horizontal_padding"
|
||||
android:layout_marginVertical="@dimen/widget_apps_tabs_vertical_padding"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/all_apps_tabs_background"
|
||||
android:text="@string/widgets_full_sheet_work_tab"
|
||||
android:textColor="@color/all_apps_tab_text"
|
||||
android:textSize="14sp"
|
||||
style="?android:attr/borderlessButtonStyle" />
|
||||
|
||||
</com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip>
|
||||
</com.android.launcher3.views.StickyHeaderLayout>
|
||||
</FrameLayout>
|
||||
</merge>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2021 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.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:launcher="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widgets_full_sheet_recyclerview_large_screen"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toStartOf="@id/scrollView"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:layout_constraintWidth_percent="0.33">
|
||||
|
||||
<com.android.launcher3.widget.picker.WidgetsRecyclerView
|
||||
android:id="@+id/primary_widgets_list_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<!-- SearchAndRecommendationsView without the tab layout as well -->
|
||||
<com.android.launcher3.views.StickyHeaderLayout
|
||||
android:id="@+id/search_and_recommendations_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/search_bar_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:clipToPadding="false"
|
||||
android:elevation="0.1dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
launcher:layout_sticky="true">
|
||||
|
||||
<include layout="@layout/widgets_search_bar" />
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/suggestions_header"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_large_screen"
|
||||
android:orientation="horizontal">
|
||||
</LinearLayout>
|
||||
</com.android.launcher3.views.StickyHeaderLayout>
|
||||
</FrameLayout>
|
||||
</merge>
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
<!-- Widget picker-->
|
||||
<dimen name="widget_list_horizontal_margin">49dp</dimen>
|
||||
<dimen name="widget_list_horizontal_margin_large_screen">24dp</dimen>
|
||||
|
||||
<!-- Bottom sheet-->
|
||||
<dimen name="bottom_sheet_extra_top_padding">0dp</dimen>
|
||||
|
||||
@@ -64,4 +64,6 @@
|
||||
|
||||
<color name="all_apps_button_color_light">@android:color/system_neutral2_700</color>
|
||||
<color name="all_apps_button_color_dark">@android:color/system_neutral2_200</color>
|
||||
|
||||
<color name="widget_picker_background_selected">@android:color/system_accent2_100</color>
|
||||
</resources>
|
||||
|
||||
@@ -192,6 +192,7 @@
|
||||
<dimen name="widget_list_header_view_vertical_padding">20dp</dimen>
|
||||
<dimen name="widget_list_entry_spacing">2dp</dimen>
|
||||
<dimen name="widget_list_horizontal_margin">16dp</dimen>
|
||||
<dimen name="widget_list_horizontal_margin_large_screen">24dp</dimen>
|
||||
|
||||
<dimen name="widget_preview_shadow_blur">0.5dp</dimen>
|
||||
<dimen name="widget_preview_key_shadow_distance">1dp</dimen>
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
<!-- Accessibility spoken message announced when a widget gets added to the home screen using a
|
||||
button in a dialog. [CHAR_LIMIT=none] -->
|
||||
<string name="added_to_home_screen_accessibility_text"><xliff:g id="widget_name" example="Calendar month view">%1$s</xliff:g> widget added to home screen</string>
|
||||
<!-- Widget suggestions header title in the full widgets picker for large screen devices
|
||||
in landscape mode. [CHAR_LIMIT=50] -->
|
||||
<string name="suggested_widgets_header_title">Suggestions</string>
|
||||
<!-- Label for showing the number of widgets an app has in the full widgets picker.
|
||||
[CHAR_LIMIT=25][ICU SYNTAX] -->
|
||||
<string name="widgets_count">
|
||||
|
||||
@@ -63,6 +63,9 @@ public abstract class FastScrollRecyclerView extends RecyclerView {
|
||||
|
||||
public void bindFastScrollbar() {
|
||||
ViewGroup parent = (ViewGroup) getParent().getParent();
|
||||
if (parent.findViewById(R.id.fast_scroller) == null) {
|
||||
parent = (ViewGroup) parent.getParent();
|
||||
}
|
||||
mScrollbar = parent.findViewById(R.id.fast_scroller);
|
||||
mScrollbar.setRecyclerView(this, parent.findViewById(R.id.fast_scroller_popup));
|
||||
onUpdateScrollbar(0);
|
||||
|
||||
@@ -238,6 +238,15 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/** Gets the WidgetsListContentEntry for the currently selected header. */
|
||||
public WidgetsListContentEntry getSelectedAppWidgets(PackageUserKey packageUserKey) {
|
||||
return (WidgetsListContentEntry) mAllWidgets.stream()
|
||||
.filter(row -> row instanceof WidgetsListContentEntry
|
||||
&& row.mPkgItem.packageName.equals(packageUserKey.mPackageName))
|
||||
.findAny()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of notifications that are relevant to given ItemInfo.
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.view.View.MeasureSpec.makeMeasureSpec;
|
||||
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
|
||||
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.allapps.AllAppsTransitionController.SWIPE_ALL_APPS_TO_HOME_MIN_SCALE;
|
||||
import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
|
||||
import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
|
||||
|
||||
@@ -45,14 +46,18 @@ import android.view.WindowInsets;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.FloatRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
@@ -62,7 +67,9 @@ import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.model.UserManagerState;
|
||||
import com.android.launcher3.model.WidgetItem;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.pm.UserCache;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.views.ArrowTipView;
|
||||
import com.android.launcher3.views.RecyclerViewFastScroller;
|
||||
import com.android.launcher3.views.SpringRelativeLayout;
|
||||
@@ -71,6 +78,8 @@ import com.android.launcher3.views.WidgetsEduView;
|
||||
import com.android.launcher3.widget.BaseWidgetSheet;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListContentEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
|
||||
import com.android.launcher3.widget.picker.search.SearchModeListener;
|
||||
import com.android.launcher3.widget.picker.search.WidgetsSearchBar;
|
||||
import com.android.launcher3.widget.util.WidgetsTableUtils;
|
||||
@@ -78,6 +87,7 @@ import com.android.launcher3.workprofile.PersonalWorkPagedView;
|
||||
import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.IntStream;
|
||||
@@ -93,6 +103,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
private static final long EDUCATION_TIP_DELAY_MS = 200;
|
||||
private static final long EDUCATION_DIALOG_DELAY_MS = 500;
|
||||
private static final float VERTICAL_START_POSITION = 0.3f;
|
||||
private static final int PERSONAL_TAB = 0;
|
||||
private static final int WORK_TAB = 1;
|
||||
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
|
||||
// The widget recommendation table can easily take over the entire screen on devices with small
|
||||
// resolution or landscape on phone. This ratio defines the max percentage of content area that
|
||||
// the table can display.
|
||||
@@ -169,14 +182,23 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
private StickyHeaderLayout mSearchScrollView;
|
||||
private WidgetsRecommendationTableLayout mRecommendedWidgetsTable;
|
||||
private LinearLayout mSuggestedWidgetsContainer;
|
||||
private WidgetsListHeader mSuggestedWidgetsHeader;
|
||||
private View mTabBar;
|
||||
private View mSearchBarContainer;
|
||||
private WidgetsSearchBar mSearchBar;
|
||||
private TextView mHeaderTitle;
|
||||
private ScrollView mRightPane;
|
||||
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
|
||||
private final boolean mIsTwoPane;
|
||||
|
||||
private int mOrientation;
|
||||
private @Nullable WidgetsRecyclerView mCurrentTouchEventRecyclerView;
|
||||
|
||||
public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
DeviceProfile dp = Launcher.getLauncher(context).getDeviceProfile();
|
||||
mIsTwoPane = dp.isTablet && dp.isLandscape && LARGE_SCREEN_WIDGET_PICKER.get();
|
||||
mHasWorkProfile = context.getSystemService(LauncherApps.class).getProfiles().size() > 1;
|
||||
mAdapters.put(AdapterHolder.PRIMARY, new AdapterHolder(AdapterHolder.PRIMARY));
|
||||
mAdapters.put(AdapterHolder.WORK, new AdapterHolder(AdapterHolder.WORK));
|
||||
@@ -205,9 +227,16 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
|
||||
int contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view
|
||||
: R.layout.widgets_full_sheet_recyclerview;
|
||||
if (mIsTwoPane) {
|
||||
contentLayoutRes = mHasWorkProfile ? R.layout.widgets_full_sheet_paged_view_large_screen
|
||||
: R.layout.widgets_full_sheet_recyclerview_large_screen;
|
||||
}
|
||||
layoutInflater.inflate(contentLayoutRes, mContent, true);
|
||||
|
||||
RecyclerViewFastScroller fastScroller = findViewById(R.id.fast_scroller);
|
||||
if (mIsTwoPane) {
|
||||
fastScroller.setVisibility(GONE);
|
||||
}
|
||||
mAdapters.get(AdapterHolder.PRIMARY).setup(findViewById(R.id.primary_widgets_list_view));
|
||||
mAdapters.get(AdapterHolder.SEARCH).setup(findViewById(R.id.search_widgets_list_view));
|
||||
if (mHasWorkProfile) {
|
||||
@@ -230,15 +259,61 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mSearchScrollView = findViewById(R.id.search_and_recommendations_container);
|
||||
mSearchScrollView.setCurrentRecyclerView(findViewById(R.id.primary_widgets_list_view));
|
||||
|
||||
mRecommendedWidgetsTable = mSearchScrollView.findViewById(R.id.recommended_widget_table);
|
||||
mRecommendedWidgetsTable = mIsTwoPane
|
||||
? mContent.findViewById(R.id.recommended_widget_table)
|
||||
: mSearchScrollView.findViewById(R.id.recommended_widget_table);
|
||||
|
||||
mRecommendedWidgetsTable.setWidgetCellLongClickListener(this);
|
||||
mRecommendedWidgetsTable.setWidgetCellOnClickListener(this);
|
||||
|
||||
// Add suggested widgets.
|
||||
if (mIsTwoPane) {
|
||||
mSuggestedWidgetsContainer = mSearchScrollView.findViewById(R.id.suggestions_header);
|
||||
|
||||
// Inflate the suggestions header.
|
||||
mSuggestedWidgetsHeader = (WidgetsListHeader) layoutInflater.inflate(
|
||||
R.layout.widgets_list_row_header, mSuggestedWidgetsContainer, false);
|
||||
mSuggestedWidgetsHeader.setExpanded(true);
|
||||
mSuggestedWidgetsHeader.setBackground(
|
||||
new WidgetsListDrawableFactory(getContext()).createHeaderBackgroundDrawable());
|
||||
|
||||
PackageItemInfo packageItemInfo = new PackageItemInfo(
|
||||
/* packageName= */ SUGGESTIONS_PACKAGE_NAME,
|
||||
Process.myUserHandle()) {
|
||||
@Override
|
||||
public boolean usingLowResIcon() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
packageItemInfo.title = getContext().getString(R.string.suggested_widgets_header_title);
|
||||
WidgetsListHeaderEntry widgetsListHeaderEntry = new WidgetsListHeaderEntry(
|
||||
packageItemInfo,
|
||||
getContext().getString(R.string.suggested_widgets_header_title),
|
||||
mActivityContext.getPopupDataProvider().getRecommendedWidgets())
|
||||
.withWidgetListShown();
|
||||
|
||||
mSuggestedWidgetsHeader.applyFromItemInfoWithIcon(widgetsListHeaderEntry);
|
||||
mSuggestedWidgetsHeader.setIcon(
|
||||
getContext().getDrawable(R.drawable.widget_suggestions_icon));
|
||||
mSuggestedWidgetsHeader.setOnExpandChangeListener(isExpanded -> {
|
||||
mSuggestedWidgetsHeader.setExpanded(isExpanded);
|
||||
resetExpandedHeaders();
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(mRecommendedWidgetsTable);
|
||||
});
|
||||
mSuggestedWidgetsContainer.addView(mSuggestedWidgetsHeader);
|
||||
}
|
||||
|
||||
mTabBar = mSearchScrollView.findViewById(R.id.tabs);
|
||||
mSearchBarContainer = mSearchScrollView.findViewById(R.id.search_bar_container);
|
||||
mSearchBar = mSearchScrollView.findViewById(R.id.widgets_search_bar);
|
||||
mHeaderTitle = mSearchScrollView.findViewById(R.id.title);
|
||||
|
||||
mHeaderTitle = mIsTwoPane
|
||||
? mContent.findViewById(R.id.title)
|
||||
: mSearchScrollView.findViewById(R.id.title);
|
||||
mRightPane = mIsTwoPane ? mContent.findViewById(R.id.right_pane) : null;
|
||||
mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(
|
||||
layoutInflater, this, this,
|
||||
new WidgetsListDrawableFactory(getContext()));
|
||||
onRecommendedWidgetsBound();
|
||||
onWidgetsBound();
|
||||
|
||||
@@ -260,6 +335,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
public void onActivePageChanged(int currentActivePage) {
|
||||
|
||||
// if the current active page changes to personal or work we set suggestions
|
||||
// to be the selected widget
|
||||
if (mIsTwoPane && (currentActivePage == PERSONAL_TAB || currentActivePage == WORK_TAB)) {
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
|
||||
AdapterHolder currentAdapterHolder = mAdapters.get(currentActivePage);
|
||||
WidgetsRecyclerView currentRecyclerView =
|
||||
mAdapters.get(currentActivePage).mWidgetsRecyclerView;
|
||||
@@ -428,6 +510,11 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
View content = mHasWorkProfile
|
||||
? mViewPager
|
||||
: mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
|
||||
|
||||
if (mIsTwoPane && mRightPane != null) {
|
||||
content = mRightPane;
|
||||
}
|
||||
|
||||
int maxHorizontalSpans = computeMaxHorizontalSpans(content,
|
||||
mWidgetSheetContentHorizontalPadding);
|
||||
if (mMaxSpansPerRow != maxHorizontalSpans) {
|
||||
@@ -520,6 +607,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
public void onSearchResults(List<WidgetsListBaseEntry> entries) {
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.setWidgetsOnSearch(entries);
|
||||
updateRecyclerViewVisibility(mAdapters.get(AdapterHolder.SEARCH));
|
||||
if (mIsTwoPane) {
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsListAdapter.selectFirstHeaderEntry();
|
||||
}
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop();
|
||||
}
|
||||
|
||||
@@ -527,6 +617,9 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mIsInSearchMode = isInSearchMode;
|
||||
if (isInSearchMode) {
|
||||
mRecommendedWidgetsTable.setVisibility(GONE);
|
||||
if (mIsTwoPane) {
|
||||
mSuggestedWidgetsContainer.setVisibility(GONE);
|
||||
}
|
||||
if (mHasWorkProfile) {
|
||||
mViewPager.setVisibility(GONE);
|
||||
mTabBar.setVisibility(GONE);
|
||||
@@ -538,6 +631,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mNoWidgetsView.setVisibility(GONE);
|
||||
} else {
|
||||
mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
|
||||
if (mIsTwoPane) {
|
||||
mSuggestedWidgetsContainer.setVisibility(VISIBLE);
|
||||
mSuggestedWidgetsHeader.callOnClick();
|
||||
}
|
||||
// Visibility of recommended widgets, recycler views and headers are handled in methods
|
||||
// below.
|
||||
onRecommendedWidgetsBound();
|
||||
@@ -572,7 +669,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
MeasureSpec.EXACTLY),
|
||||
makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
|
||||
MeasureSpec.EXACTLY));
|
||||
float maxTableHeight = (mContent.getMeasuredHeight()
|
||||
float maxTableHeight = mIsTwoPane ? Float.MAX_VALUE : (mContent.getMeasuredHeight()
|
||||
- mTabsHeight - getHeaderViewHeight()
|
||||
- noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
|
||||
|
||||
@@ -626,7 +723,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
// Disable swipe down when recycler view is scrolling
|
||||
// Disable swipe down when recycler view is scrolling or scroll view is scrolling
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
mNoIntercept = false;
|
||||
WidgetsRecyclerView recyclerView = getRecyclerView();
|
||||
@@ -636,6 +733,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mNoIntercept = true;
|
||||
} else if (getPopupContainer().isEventOverView(recyclerView, ev)) {
|
||||
mNoIntercept = !recyclerView.shouldContainerScroll(ev, getPopupContainer());
|
||||
} else if (mIsTwoPane && getPopupContainer().isEventOverView(mRightPane, ev)) {
|
||||
mNoIntercept = mRightPane.getScrollY() > 0;
|
||||
}
|
||||
|
||||
if (mSearchBar.isSearchBarFocused()
|
||||
@@ -649,7 +748,11 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
/** Shows the {@link WidgetsFullSheet} on the launcher. */
|
||||
public static WidgetsFullSheet show(Launcher launcher, boolean animate) {
|
||||
WidgetsFullSheet sheet = (WidgetsFullSheet) launcher.getLayoutInflater()
|
||||
.inflate(R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
|
||||
.inflate(LARGE_SCREEN_WIDGET_PICKER.get()
|
||||
&& launcher.getDeviceProfile().isTablet
|
||||
&& launcher.getDeviceProfile().isLandscape
|
||||
? R.layout.widgets_full_sheet_large_screen
|
||||
: R.layout.widgets_full_sheet, launcher.getDragLayer(), false);
|
||||
sheet.attachToContainer();
|
||||
sheet.mIsOpen = true;
|
||||
sheet.open(animate);
|
||||
@@ -746,6 +849,13 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
if (mIsInSearchMode) {
|
||||
mSearchBar.reset();
|
||||
}
|
||||
|
||||
// Checks the orientation of the screen
|
||||
if (LARGE_SCREEN_WIDGET_PICKER.get() && mOrientation != newConfig.orientation) {
|
||||
mOrientation = newConfig.orientation;
|
||||
handleClose(false);
|
||||
show(Launcher.getLauncher(getContext()), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -835,16 +945,44 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
|
||||
AdapterHolder(int adapterType) {
|
||||
mAdapterType = adapterType;
|
||||
|
||||
Context context = getContext();
|
||||
LauncherAppState apps = LauncherAppState.getInstance(context);
|
||||
|
||||
HeaderChangeListener headerChangeListener = new HeaderChangeListener() {
|
||||
@Override
|
||||
public void onHeaderChanged(@NonNull PackageUserKey selectedHeader) {
|
||||
WidgetsListContentEntry contentEntry = mActivityContext.getPopupDataProvider()
|
||||
.getSelectedAppWidgets(selectedHeader);
|
||||
|
||||
if (contentEntry == null || mRightPane == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSuggestedWidgetsHeader != null) {
|
||||
mSuggestedWidgetsHeader.setExpanded(false);
|
||||
}
|
||||
WidgetsRowViewHolder widgetsRowViewHolder =
|
||||
mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
|
||||
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
|
||||
contentEntry,
|
||||
0, Collections.EMPTY_LIST);
|
||||
widgetsRowViewHolder.mDataCallback = data -> {
|
||||
mWidgetsListTableViewHolderBinder.bindViewHolder(widgetsRowViewHolder,
|
||||
contentEntry,
|
||||
0, Collections.singletonList(data));
|
||||
};
|
||||
mRightPane.removeAllViews();
|
||||
mRightPane.addView(widgetsRowViewHolder.itemView);
|
||||
}
|
||||
};
|
||||
mWidgetsListAdapter = new WidgetsListAdapter(
|
||||
context,
|
||||
LayoutInflater.from(context),
|
||||
apps.getIconCache(),
|
||||
this::getEmptySpaceHeight,
|
||||
/* iconClickListener= */ WidgetsFullSheet.this,
|
||||
/* iconLongClickListener= */ WidgetsFullSheet.this);
|
||||
/* iconLongClickListener= */ WidgetsFullSheet.this,
|
||||
mIsTwoPane ? headerChangeListener : null);
|
||||
mWidgetsListAdapter.setHasStableIds(true);
|
||||
switch (mAdapterType) {
|
||||
case PRIMARY:
|
||||
@@ -871,8 +1009,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mWidgetsRecyclerView.setAdapter(mWidgetsListAdapter);
|
||||
mWidgetsRecyclerView.setItemAnimator(mWidgetsListItemAnimator);
|
||||
mWidgetsRecyclerView.setHeaderViewDimensionsProvider(WidgetsFullSheet.this);
|
||||
mWidgetsRecyclerView.setEdgeEffectFactory(
|
||||
((SpringRelativeLayout) mContent).createEdgeEffectFactory());
|
||||
if (!mIsTwoPane) {
|
||||
mWidgetsRecyclerView.setEdgeEffectFactory(
|
||||
((SpringRelativeLayout) mContent).createEdgeEffectFactory());
|
||||
}
|
||||
// Recycler view binds to fast scroller when it is attached to screen. Make sure
|
||||
// search recycler view is bound to fast scroller if user is in search mode at the time
|
||||
// of attachment.
|
||||
@@ -882,4 +1022,15 @@ public class WidgetsFullSheet extends BaseWidgetSheet
|
||||
mWidgetsListAdapter.setMaxHorizontalSpansPerRow(mMaxSpansPerRow);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a listener for when the selected header gets changed in the left pane.
|
||||
*/
|
||||
public interface HeaderChangeListener {
|
||||
/**
|
||||
* Sets the right pane to have the widgets for the currently selected header from
|
||||
* the left pane.
|
||||
*/
|
||||
void onHeaderChanged(@NonNull PackageUserKey selectedHeader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
private final SparseArray<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
|
||||
private final WidgetListBaseRowEntryComparator mRowComparator =
|
||||
new WidgetListBaseRowEntryComparator();
|
||||
@Nullable private final WidgetsFullSheet.HeaderChangeListener mHeaderChangeListener;
|
||||
|
||||
private final List<WidgetsListBaseEntry> mAllEntries = new ArrayList<>();
|
||||
private ArrayList<WidgetsListBaseEntry> mVisibleEntries = new ArrayList<>();
|
||||
@@ -105,7 +106,9 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
|
||||
public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
|
||||
IconCache iconCache, IntSupplier emptySpaceHeightProvider,
|
||||
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) {
|
||||
OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
|
||||
WidgetsFullSheet.HeaderChangeListener headerChangeListener) {
|
||||
mHeaderChangeListener = headerChangeListener;
|
||||
mContext = context;
|
||||
mDiffReporter = new WidgetsDiffReporter(iconCache, this);
|
||||
WidgetsListDrawableFactory listDrawableFactory = new WidgetsListDrawableFactory(context);
|
||||
@@ -196,9 +199,11 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
getOffsetForPosition(previousPositionForPackageUserKey);
|
||||
|
||||
List<WidgetsListBaseEntry> newVisibleEntries = mAllEntries.stream()
|
||||
.filter(entry -> ((mFilter == null || mFilter.test(entry))
|
||||
.filter(entry -> (((mFilter == null || mFilter.test(entry))
|
||||
&& mHeaderAndSelectedContentFilter.test(entry))
|
||||
|| entry instanceof WidgetListSpaceEntry)
|
||||
&& (mHeaderChangeListener == null
|
||||
|| !(entry instanceof WidgetsListContentEntry)))
|
||||
.map(entry -> {
|
||||
if (entry instanceof WidgetsListBaseEntry.Header<?>
|
||||
&& matchesKey(entry, mWidgetsContentVisiblePackageUserKey)) {
|
||||
@@ -225,7 +230,6 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether {@code entry} matches {@code key}. */
|
||||
private static boolean isHeaderForPackageUserKey(
|
||||
@NonNull WidgetsListBaseEntry entry, @Nullable PackageUserKey key) {
|
||||
@@ -258,7 +262,6 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int pos, List<Object> payloads) {
|
||||
ViewHolderBinder viewHolderBinder = mViewHolderBinders.get(getItemViewType(pos));
|
||||
WidgetsListBaseEntry entry = mVisibleEntries.get(pos);
|
||||
|
||||
// The first entry has an empty space, count from second entries.
|
||||
int listPos = (pos > 1) ? POSITION_DEFAULT : POSITION_FIRST;
|
||||
@@ -268,6 +271,23 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
viewHolderBinder.bindViewHolder(holder, mVisibleEntries.get(pos), listPos, payloads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the first visible header. This is used in search as we want to always select the
|
||||
* first header in the new list that gets generated as we search.
|
||||
*/
|
||||
void selectFirstHeaderEntry() {
|
||||
WidgetsListSearchHeaderEntry firstEntry = null;
|
||||
for (WidgetsListBaseEntry entry: mVisibleEntries) {
|
||||
if (entry instanceof WidgetsListSearchHeaderEntry) {
|
||||
firstEntry = (WidgetsListSearchHeaderEntry) entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstEntry != null) {
|
||||
onHeaderClicked(true, PackageUserKey.fromPackageItemInfo(firstEntry.mPkgItem));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
if (DEBUG) {
|
||||
@@ -318,6 +338,9 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
// Ignore invalid clicks, such as collapsing a package that isn't currently expanded.
|
||||
if (!showWidgets && !packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
|
||||
|
||||
if (mHeaderChangeListener != null
|
||||
&& packageUserKey.equals(mWidgetsContentVisiblePackageUserKey)) return;
|
||||
|
||||
if (showWidgets) {
|
||||
mWidgetsContentVisiblePackageUserKey = packageUserKey;
|
||||
ActivityContext.lookupContext(mContext)
|
||||
@@ -331,6 +354,10 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> implements OnHeaderC
|
||||
mPendingClickHeader = packageUserKey;
|
||||
|
||||
updateVisibleEntries();
|
||||
|
||||
if (mHeaderChangeListener != null && mWidgetsContentVisiblePackageUserKey != null) {
|
||||
mHeaderChangeListener.onHeaderChanged(mWidgetsContentVisiblePackageUserKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,13 +15,17 @@
|
||||
*/
|
||||
package com.android.launcher3.widget.picker;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.LARGE_SCREEN_WIDGET_PICKER;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.CheckBox;
|
||||
@@ -35,12 +39,14 @@ import androidx.annotation.UiThread;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
|
||||
import com.android.launcher3.icons.PlaceHolderIconDrawable;
|
||||
import com.android.launcher3.icons.cache.HandlerRunnable;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.PackageItemInfo;
|
||||
import com.android.launcher3.util.PluralMessageFormat;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
|
||||
import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
|
||||
@@ -55,19 +61,19 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpdateReceiver {
|
||||
|
||||
private boolean mEnableIconUpdateAnimation = false;
|
||||
private final int mIconSize;
|
||||
private final boolean mIsTwoPane;
|
||||
|
||||
@Nullable private HandlerRunnable mIconLoadRequest;
|
||||
@Nullable private Drawable mIconDrawable;
|
||||
private final int mIconSize;
|
||||
|
||||
@Nullable private WidgetsListDrawableState mListDrawableState;
|
||||
private ImageView mAppIcon;
|
||||
private TextView mTitle;
|
||||
private TextView mSubtitle;
|
||||
|
||||
private GradientDrawable mBackground;
|
||||
private CheckBox mExpandToggle;
|
||||
private boolean mEnableIconUpdateAnimation = false;
|
||||
private boolean mIsExpanded = false;
|
||||
@Nullable private WidgetsListDrawableState mListDrawableState;
|
||||
|
||||
public WidgetsListHeader(Context context) {
|
||||
this(context, /* attrs= */ null);
|
||||
@@ -86,6 +92,11 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
R.styleable.WidgetsListRowHeader, defStyleAttr, /* defStyleRes= */ 0);
|
||||
mIconSize = a.getDimensionPixelSize(R.styleable.WidgetsListRowHeader_appIconSize,
|
||||
grid.iconSizePx);
|
||||
|
||||
mIsTwoPane = grid.isLandscape && grid.isTablet && LARGE_SCREEN_WIDGET_PICKER.get();
|
||||
if (mIsTwoPane) {
|
||||
setLargeScreenTheme();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -95,6 +106,9 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
mTitle = findViewById(R.id.app_title);
|
||||
mSubtitle = findViewById(R.id.app_subtitle);
|
||||
mExpandToggle = findViewById(R.id.toggle);
|
||||
if (mIsTwoPane) {
|
||||
mExpandToggle.setVisibility(GONE);
|
||||
}
|
||||
setAccessibilityDelegate(new AccessibilityDelegate() {
|
||||
|
||||
@Override
|
||||
@@ -132,7 +146,7 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
@Nullable OnExpansionChangeListener onExpandChangeListener) {
|
||||
// Use the entire touch area of this view to expand / collapse an app widgets section.
|
||||
setOnClickListener(view -> {
|
||||
setExpanded(!mIsExpanded);
|
||||
setExpanded(mIsTwoPane || !mIsExpanded);
|
||||
if (onExpandChangeListener != null) {
|
||||
onExpandChangeListener.onExpansionChange(mIsExpanded);
|
||||
}
|
||||
@@ -144,8 +158,38 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
public void setExpanded(boolean isExpanded) {
|
||||
this.mIsExpanded = isExpanded;
|
||||
mExpandToggle.setChecked(isExpanded);
|
||||
if (mIsTwoPane) {
|
||||
if (Utilities.isDarkTheme(getContext())) {
|
||||
if (mIsExpanded) {
|
||||
mTitle.setTextColor(Color.BLACK);
|
||||
mSubtitle.setTextColor(Color.BLACK);
|
||||
} else {
|
||||
mTitle.setTextColor(Color.WHITE);
|
||||
mSubtitle.setTextColor(Themes.getAttrColor(getContext(),
|
||||
android.R.attr.textColorSecondary));
|
||||
}
|
||||
}
|
||||
setLargeScreenTheme();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style for the header when we are using large screens in landscape.
|
||||
*/
|
||||
private void setLargeScreenTheme() {
|
||||
if (mBackground == null) {
|
||||
mBackground = new GradientDrawable();
|
||||
mBackground.setCornerRadius((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
|
||||
28,
|
||||
getContext().getResources().getDisplayMetrics()));
|
||||
}
|
||||
mBackground.setColor(mIsExpanded
|
||||
? getResources().getColor(R.color.widget_picker_background_selected)
|
||||
: Color.TRANSPARENT);
|
||||
this.setBackground(mBackground);
|
||||
}
|
||||
|
||||
|
||||
/** Sets the {@link WidgetsListDrawableState} and refreshes the background drawable. */
|
||||
@UiThread
|
||||
public void setListDrawableState(WidgetsListDrawableState state) {
|
||||
@@ -163,7 +207,7 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
@UiThread
|
||||
private void applyIconAndLabel(WidgetsListHeaderEntry entry) {
|
||||
PackageItemInfo info = entry.mPkgItem;
|
||||
setIcon(info);
|
||||
setIcon(info.newIcon(getContext()));
|
||||
setTitles(entry);
|
||||
setExpanded(entry.isWidgetListShown());
|
||||
|
||||
@@ -172,9 +216,7 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
verifyHighRes();
|
||||
}
|
||||
|
||||
private void setIcon(PackageItemInfo info) {
|
||||
Drawable icon;
|
||||
icon = info.newIcon(getContext());
|
||||
void setIcon(Drawable icon) {
|
||||
applyDrawables(icon);
|
||||
mIconDrawable = icon;
|
||||
if (mIconDrawable != null) {
|
||||
@@ -239,7 +281,7 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
@UiThread
|
||||
private void applyIconAndLabel(WidgetsListSearchHeaderEntry entry) {
|
||||
PackageItemInfo info = entry.mPkgItem;
|
||||
setIcon(info);
|
||||
setIcon(info.newIcon(getContext()));
|
||||
setTitles(entry);
|
||||
setExpanded(entry.isWidgetListShown());
|
||||
|
||||
@@ -265,7 +307,7 @@ public final class WidgetsListHeader extends LinearLayout implements ItemInfoUpd
|
||||
// Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
|
||||
info.bitmap.icon.prepareToDraw();
|
||||
|
||||
setIcon((PackageItemInfo) info);
|
||||
setIcon(info.newIcon(getContext()));
|
||||
|
||||
mEnableIconUpdateAnimation = false;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ public final class WidgetsListSearchHeaderViewHolderBinder implements
|
||||
WidgetsListSearchHeaderEntry data, @ListPosition int position, List<Object> payloads) {
|
||||
WidgetsListHeader widgetsListHeader = viewHolder.mWidgetsListHeader;
|
||||
widgetsListHeader.applyFromItemInfoWithIcon(data);
|
||||
widgetsListHeader.setSelected(data.isWidgetListShown());
|
||||
widgetsListHeader.setExpanded(data.isWidgetListShown());
|
||||
widgetsListHeader.setListDrawableState(
|
||||
WidgetsListDrawableState.obtain(
|
||||
|
||||
@@ -110,13 +110,8 @@ public final class WidgetsListTableViewHolderBinder
|
||||
|
||||
// When preview loads, notify adapter to rebind the item and possibly animate
|
||||
widget.applyFromCellItem(widgetItem, 1f,
|
||||
bitmap -> {
|
||||
if (holder.getBindingAdapter() != null) {
|
||||
holder.getBindingAdapter().notifyItemChanged(
|
||||
holder.getBindingAdapterPosition(),
|
||||
Pair.create(widgetItem, bitmap));
|
||||
}
|
||||
}, holder.previewCache.get(widgetItem));
|
||||
bitmap -> holder.onPreviewLoaded(Pair.create(widgetItem, bitmap)),
|
||||
holder.previewCache.get(widgetItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package com.android.launcher3.widget.picker;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||
@@ -25,16 +26,33 @@ import com.android.launcher3.model.WidgetItem;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/** A {@link ViewHolder} for showing widgets of an app in the full widget picker. */
|
||||
public final class WidgetsRowViewHolder extends ViewHolder {
|
||||
|
||||
public final WidgetsListTableView tableContainer;
|
||||
public final Map<WidgetItem, Bitmap> previewCache = new HashMap<>();
|
||||
Consumer<Pair<WidgetItem, Bitmap>> mDataCallback;
|
||||
|
||||
public WidgetsRowViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
tableContainer = v.findViewById(R.id.widgets_table);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the preview is loaded we callback to notify that the preview loaded and we rebind the
|
||||
* view.
|
||||
*
|
||||
* @param data is the payload which is needed when binding the view.
|
||||
*/
|
||||
public void onPreviewLoaded(Pair<WidgetItem, Bitmap> data) {
|
||||
if (mDataCallback != null) {
|
||||
mDataCallback.accept(data);
|
||||
}
|
||||
if (getBindingAdapter() != null) {
|
||||
getBindingAdapter().notifyItemChanged(getBindingAdapterPosition(), data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ public final class WidgetsListAdapterTest {
|
||||
mTestProfile.numColumns = 5;
|
||||
mUserHandle = Process.myUserHandle();
|
||||
mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater,
|
||||
mIconCache, () -> 0, null, null);
|
||||
mIconCache, () -> 0, null, null, null);
|
||||
mAdapter.registerAdapterDataObserver(mListener);
|
||||
|
||||
doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0))
|
||||
|
||||
Reference in New Issue
Block a user