diff options
26 files changed, 851 insertions, 24 deletions
diff --git a/res/flag(com.android.documentsui.flags.use_material3)/color/nav_rail_item_text_color.xml b/res/flag(com.android.documentsui.flags.use_material3)/color/nav_rail_item_text_color.xml new file mode 100644 index 000000000..ec5aecb33 --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/color/nav_rail_item_text_color.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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 + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_activated="true" android:color="?attr/colorSecondary" /> + <item android:color="?attr/colorOnSurfaceVariant" /> +</selector> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_background.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_background.xml new file mode 100644 index 000000000..7809766cd --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_background.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- By default the nav rail item has a grey background when it's focused, but we need the + background to be put on the icon inside, so we override the focus background color to be + transparent here. + --> + <item android:state_focused="true" android:drawable="@android:color/transparent" /> +</selector>
\ No newline at end of file diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_icon_background.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_icon_background.xml new file mode 100644 index 000000000..7864321af --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_icon_background.xml @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:color="@color/item_root_ripple_color"> + + <!-- The mask below only works for the ripple itself, doesn't work for other <item>s, we + need to explicitly apply the drawable if the other items also need this mask. --> + <item + android:id="@android:id/mask" + android:drawable="@drawable/nav_rail_item_icon_mask"/> + + <item> + <selector> + <!-- Selected (activated). --> + <!-- Highlight: when dragging files over the item. --> + <item + android:state_activated="true" + app:state_highlighted="true"> + <layer-list> + <item> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="?attr/colorSecondaryContainer" /> + </shape> + </item> + <item> + <shape android:tint="?attr/colorOnSecondaryContainer"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="@color/overlay_color_percentage_10" /> + </shape> + </item> + </layer-list> + </item> + <item + android:state_activated="true" + android:state_pressed="true"> + <layer-list> + <item> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="?attr/colorSecondaryContainer" /> + </shape> + </item> + <item> + <shape android:tint="?attr/colorOnSecondaryContainer"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="@color/overlay_color_percentage_10" /> + </shape> + </item> + </layer-list> + </item> + <item + android:state_activated="true" + android:state_focused="true"> + <layer-list> + <item> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="?attr/colorSecondaryContainer" /> + </shape> + </item> + <item> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <stroke + android:width="@dimen/focus_ring_width" + android:color="?attr/colorSecondary" /> + </shape> + </item> + </layer-list> + </item> + <item + android:state_activated="true" + android:state_hovered="true"> + <layer-list> + <item> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="?attr/colorSecondaryContainer" /> + </shape> + </item> + <item> + <shape android:tint="?attr/colorOnSecondaryContainer"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="@color/overlay_color_percentage_10" /> + </shape> + </item> + </layer-list> + </item> + <item android:state_activated="true"> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="?attr/colorSecondaryContainer" /> + </shape> + </item> + + <!-- Unselected. --> + <item app:state_highlighted="true"> + <shape android:tint="?attr/colorOnSurface"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="@color/overlay_color_percentage_10" /> + </shape> + </item> + <item android:state_pressed="true"> + <shape android:tint="?attr/colorOnSurface"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="@color/overlay_color_percentage_10" /> + </shape> + </item> + <item android:state_focused="true"> + <shape> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <stroke + android:width="@dimen/focus_ring_width" + android:color="?attr/colorSecondary" /> + </shape> + </item> + <item android:state_hovered="true"> + <shape android:tint="?attr/colorOnSurface"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius" /> + <solid android:color="@color/overlay_color_percentage_10" /> + </shape> + </item> + + <!-- Default: use the container background. --> + <item android:drawable="@android:color/transparent" /> + </selector> + </item> +</ripple>
\ No newline at end of file diff --git a/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_icon_mask.xml b/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_icon_mask.xml new file mode 100644 index 000000000..fd2a14b8d --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/drawable/nav_rail_item_icon_mask.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="@dimen/nav_rail_item_icon_bg_radius"/> + <!-- The color here doesn't matter, it's just being used as a mask. --> + <solid android:color="@android:color/white" /> +</shape>
\ No newline at end of file diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/fragment_nav_rail_roots.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/fragment_nav_rail_roots.xml new file mode 100644 index 000000000..7848dea0b --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/fragment_nav_rail_roots.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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.documentsui.sidebar.RootsList xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/roots_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:keyboardNavigationCluster="true" + android:divider="@null" + android:focusable="false" + android:descendantFocusability="afterDescendants" + style="@style/NavRailStyle"/> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_item_root.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_item_root.xml new file mode 100644 index 000000000..461027c73 --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_item_root.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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.documentsui.sidebar.RootItemView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="@dimen/nav_rail_item_height" + android:gravity="center_horizontal" + android:orientation="vertical" + android:baselineAligned="false" + android:clickable="true" + android:focusable="true" + style="@style/NavRailItemStyle"> + + <LinearLayout + android:layout_width="@dimen/nav_rail_item_icon_bg_width" + android:layout_height="@dimen/nav_rail_item_icon_bg_height" + android:gravity="center" + android:duplicateParentState="true" + android:background="@drawable/nav_rail_item_icon_background"> + + <ImageView + android:id="@android:id/icon" + android:layout_width="@dimen/root_icon_size" + android:layout_height="@dimen/root_icon_size" + android:scaleType="centerInside" + android:contentDescription="@null" /> + + </LinearLayout> + + <TextView + android:id="@android:id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="end" + android:textAlignment="center" + android:duplicateParentState="true" + style="@style/NavRailItemTextStyle" /> + +</com.android.documentsui.sidebar.RootItemView> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_layout.xml b/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_layout.xml new file mode 100644 index 000000000..5d753f336 --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/layout/nav_rail_layout.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<!-- CoordinatorLayout is necessary for various components (e.g. Snackbars, and + floating action buttons) to operate correctly. --> +<androidx.coordinatorlayout.widget.CoordinatorLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/coordinator_layout"> + + <androidx.drawerlayout.widget.DrawerLayout + android:id="@+id/drawer_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- Main section --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:baselineAligned="false" + android:paddingTop="@dimen/layout_padding_top" + android:paddingBottom="@dimen/layout_padding_bottom" + android:paddingEnd="@dimen/layout_padding_end" + android:background="?attr/colorSurfaceContainer"> + + <!-- Navigation rail: left hand side. --> + <FrameLayout + android:id="@+id/nav_rail_container_roots" + android:layout_width="144dp" + android:layout_height="match_parent" + /> + + <!-- Main container for the right hand side. --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@drawable/main_container_background" + android:paddingTop="@dimen/main_container_padding_top"> + + <com.google.android.material.appbar.MaterialToolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:layout_marginTop="@dimen/action_bar_margin" + android:touchscreenBlocksFocus="false"> + + <TextView + android:id="@+id/searchbar_title" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:gravity="center_vertical" + android:text="@string/search_bar_hint" + android:textAppearance="@style/SearchBarTitle" /> + + </com.google.android.material.appbar.MaterialToolbar> + + <include layout="@layout/directory_header" /> + + <!-- Main list area (file list/grid or search results). --> + <FrameLayout + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"> + + <FrameLayout + android:id="@+id/container_directory" + android:clipToPadding="false" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <FrameLayout + android:id="@+id/container_search_fragment" + android:clipToPadding="false" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </FrameLayout> + + <!-- Footer of right hand side: Breadcrumbs and Picker footer. --> + <com.android.documentsui.HorizontalBreadcrumb + android:id="@+id/horizontal_breadcrumb" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <androidx.coordinatorlayout.widget.CoordinatorLayout + android:id="@+id/container_save" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?android:attr/colorBackgroundFloating" + android:elevation="8dp" /> + + </LinearLayout> + + </LinearLayout> + + <!-- Drawer section --> + <LinearLayout + android:id="@+id/drawer_roots" + android:layout_width="256dp" + android:layout_height="match_parent" + android:layout_gravity="start" + android:orientation="vertical"> + + <FrameLayout + android:id="@+id/container_roots" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" /> + + </LinearLayout> + + </androidx.drawerlayout.widget.DrawerLayout> +</androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/dimens.xml b/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/dimens.xml index 4707991f6..2781026e9 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/dimens.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/dimens.xml @@ -23,7 +23,7 @@ we zero here, to avoid pushing the title further. --> <dimen name="search_bar_text_margin_start">0dp</dimen> - <dimen name="toolbar_padding_start">@dimen/main_container_padding_start</dimen> + <dimen name="toolbar_padding_start">@dimen/space_small_3</dimen> <dimen name="list_container_padding">@dimen/space_extra_small_6</dimen> </resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/layouts.xml b/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/layouts.xml new file mode 100644 index 000000000..4b0634d54 --- /dev/null +++ b/res/flag(com.android.documentsui.flags.use_material3)/values-w600dp/layouts.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<resources> + <item name="documents_activity" type="layout">@layout/nav_rail_layout</item> + <item name="files_activity" type="layout">@layout/nav_rail_layout</item> +</resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values-w900dp/dimens.xml b/res/flag(com.android.documentsui.flags.use_material3)/values-w900dp/dimens.xml index 51b1f7bdc..d37f3af68 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values-w900dp/dimens.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values-w900dp/dimens.xml @@ -32,6 +32,7 @@ <dimen name="main_container_padding_top">@dimen/space_extra_small_6</dimen> + <dimen name="toolbar_padding_start">@dimen/main_container_padding_start</dimen> <dimen name="toolbar_padding_end">@dimen/space_small_3</dimen> <dimen name="drawer_padding_top">@dimen/space_small_1</dimen> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml index 47d0049a4..2c2d54f34 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/dimens.xml @@ -81,6 +81,11 @@ <dimen name="drawer_item_text_margin_start">12dp</dimen> <dimen name="drawer_item_action_icon_margin_start">4dp</dimen> + <dimen name="nav_rail_item_height">64dp</dimen> + <dimen name="nav_rail_item_icon_bg_radius">16dp</dimen> + <dimen name="nav_rail_item_icon_bg_width">56dp</dimen> + <dimen name="nav_rail_item_icon_bg_height">32dp</dimen> + <dimen name="drag_shadow_width">176dp</dimen> <dimen name="drag_shadow_height">64dp</dimen> <dimen name="drag_shadow_radius">4dp</dimen> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml index 4c61e5152..5fde94ab8 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles.xml @@ -166,4 +166,24 @@ <item name="android:textAlignment">viewStart</item> <item name="android:textAppearance">@style/FileItemLabelText</item> </style> + + <style name="NavRailStyle" parent=""> + <item name="android:background">?attr/colorSurfaceContainer</item> + <item name="android:paddingHorizontal">@dimen/space_small_3</item> + <item name="android:paddingTop">@dimen/space_extra_small_6</item> + <item name="android:paddingBottom">@dimen/space_small_1</item> + <item name="android:scrollbarStyle">outsideOverlay</item> + <item name="android:clipToPadding">false</item> + </style> + + <style name="NavRailItemStyle" parent=""> + <item name="android:background">@drawable/nav_rail_item_background</item> + <item name="android:paddingVertical">6dp</item> + </style> + + <style name="NavRailItemTextStyle" parent=""> + <item name="android:layout_marginTop">4dp</item> + <item name="android:textColor">@color/nav_rail_item_text_color</item> + <item name="android:textAppearance">@style/NavRailItemTextAppearance</item> + </style> </resources> diff --git a/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml b/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml index fb6d0e472..717295315 100644 --- a/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml +++ b/res/flag(com.android.documentsui.flags.use_material3)/values/styles_text.xml @@ -141,4 +141,9 @@ <style name="Subhead" parent="@style/TextAppearance.Material3.BodyLarge"> <item name="fontFamily">@string/config_fontFamily</item> </style> + + <style name="NavRailItemTextAppearance" parent="@style/TextAppearance.Material3.LabelMedium"> + <item name="fontFamily">@string/config_fontFamily</item> + </style> + </resources> diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index fe81fbf16..4c25b3608 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -358,6 +358,14 @@ public abstract class BaseActivity if (roots != null) { roots.onSelectedUserChanged(); } + if (useMaterial3()) { + final RootsFragment navRailRoots = + RootsFragment.getNavRail(getSupportFragmentManager()); + if (navRailRoots != null) { + navRailRoots.onSelectedUserChanged(); + } + } + if (mState.stack.size() <= 1) { // We do not load cross-profile root if the stack contains two documents. The @@ -690,6 +698,13 @@ public abstract class BaseActivity if (roots != null) { roots.onCurrentRootChanged(); } + if (useMaterial3()) { + final RootsFragment navRailRoots = + RootsFragment.getNavRail(getSupportFragmentManager()); + if (navRailRoots != null) { + navRailRoots.onCurrentRootChanged(); + } + } String appName = getString(R.string.files_label); String currentTitle = getTitle() != null ? getTitle().toString() : ""; diff --git a/src/com/android/documentsui/DrawerController.java b/src/com/android/documentsui/DrawerController.java index a988635fd..88c41b3f2 100644 --- a/src/com/android/documentsui/DrawerController.java +++ b/src/com/android/documentsui/DrawerController.java @@ -127,7 +127,10 @@ public abstract class DrawerController implements DrawerListener { if (activityConfig.dragAndDropEnabled()) { View edge = layout.findViewById(R.id.drawer_edge); - edge.setOnDragListener(new ItemDragListener<>(this, SPRING_TIMEOUT)); + // nav_rail_layout also uses DrawerLayout, but it doesn't have drawer edge. + if (edge != null) { + edge.setOnDragListener(new ItemDragListener<>(this, SPRING_TIMEOUT)); + } } } diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index 1ebe2374f..1da0d8f2b 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -17,6 +17,7 @@ package com.android.documentsui.files; import static com.android.documentsui.OperationDialogFragment.DIALOG_TYPE_UNKNOWN; +import static com.android.documentsui.flags.Flags.useMaterial3; import android.app.ActivityManager.TaskDescription; import android.content.Intent; @@ -181,6 +182,14 @@ public class FilesActivity extends BaseActivity implements AbstractActionHandler RootsFragment.show(getSupportFragmentManager(), /* includeApps= */ false, /* intent= */ null); + if (useMaterial3()) { + View navRailRoots = findViewById(R.id.nav_rail_container_roots); + if (navRailRoots != null) { + // Medium layout, populate navigation rail layout. + RootsFragment.showNavRail(getSupportFragmentManager(), /* includeApps= */ false, + /* intent= */ null); + } + } final Intent intent = getIntent(); diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java index e9b91b1a0..481b67e77 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -21,6 +21,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.flags.Flags.useMaterial3; import android.content.Intent; import android.content.res.Resources; @@ -249,6 +250,15 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { RootsFragment.show(getSupportFragmentManager(), /* includeApps= */ mState.action == ACTION_GET_CONTENT, /* intent= */ moreApps); + if (useMaterial3()) { + View navRailRoots = findViewById(R.id.nav_rail_container_roots); + if (navRailRoots != null) { + // Medium layout, populate navigation rail layout. + RootsFragment.showNavRail(getSupportFragmentManager(), + /* includeApps= */ mState.action == ACTION_GET_CONTENT, + /* intent= */ moreApps); + } + } } } diff --git a/src/com/android/documentsui/sidebar/AppItem.java b/src/com/android/documentsui/sidebar/AppItem.java index b8abf8be9..c719241d2 100644 --- a/src/com/android/documentsui/sidebar/AppItem.java +++ b/src/com/android/documentsui/sidebar/AppItem.java @@ -26,6 +26,8 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.LayoutRes; + import com.android.documentsui.ActionHandler; import com.android.documentsui.IconUtils; import com.android.documentsui.R; @@ -43,7 +45,16 @@ public class AppItem extends Item { private final ActionHandler mActionHandler; public AppItem(ResolveInfo info, String title, UserId userId, ActionHandler actionHandler) { - super(R.layout.item_root, title, getStringId(info), userId); + this(R.layout.item_root, info, title, userId, actionHandler); + } + + public AppItem( + @LayoutRes int layoutId, + ResolveInfo info, + String title, + UserId userId, + ActionHandler actionHandler) { + super(layoutId, title, getStringId(info), userId); this.info = info; mActionHandler = actionHandler; } diff --git a/src/com/android/documentsui/sidebar/NavRailAppItem.java b/src/com/android/documentsui/sidebar/NavRailAppItem.java new file mode 100644 index 000000000..befddf0aa --- /dev/null +++ b/src/com/android/documentsui/sidebar/NavRailAppItem.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.android.documentsui.sidebar; + +import android.content.pm.ResolveInfo; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.documentsui.ActionHandler; +import com.android.documentsui.R; +import com.android.documentsui.base.UserId; + +/** + * Similar to {@link AppItem} but only used in the navigation rail. + */ +public class NavRailAppItem extends AppItem { + + public NavRailAppItem( + ResolveInfo info, String title, UserId userId, ActionHandler actionHandler) { + super(R.layout.nav_rail_item_root, info, title, userId, actionHandler); + } + + @Override + public void bindView(View convertView) { + final ImageView icon = convertView.findViewById(android.R.id.icon); + final TextView titleView = convertView.findViewById(android.R.id.title); + + titleView.setText(title); + titleView.setContentDescription(userId.getUserBadgedLabel(convertView.getContext(), title)); + + bindIcon(icon); + } +} diff --git a/src/com/android/documentsui/sidebar/NavRailProfileItem.java b/src/com/android/documentsui/sidebar/NavRailProfileItem.java new file mode 100644 index 000000000..fe69c286f --- /dev/null +++ b/src/com/android/documentsui/sidebar/NavRailProfileItem.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.android.documentsui.sidebar; + +import android.content.pm.ResolveInfo; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.documentsui.ActionHandler; +import com.android.documentsui.R; + + +/** + * Similar to {@link ProfileItem} but only used in the navigation rail. + */ +public class NavRailProfileItem extends ProfileItem { + + public NavRailProfileItem(ResolveInfo info, String title, ActionHandler actionHandler) { + super(R.layout.nav_rail_item_root, info, title, actionHandler); + } + + @Override + public void bindView(View convertView) { + final ImageView icon = convertView.findViewById(android.R.id.icon); + final TextView titleView = convertView.findViewById(android.R.id.title); + + titleView.setText(title); + titleView.setContentDescription(userId.getUserBadgedLabel(convertView.getContext(), title)); + + bindIcon(icon); + } +} diff --git a/src/com/android/documentsui/sidebar/NavRailRootAndAppItem.java b/src/com/android/documentsui/sidebar/NavRailRootAndAppItem.java new file mode 100644 index 000000000..1bcc42f5c --- /dev/null +++ b/src/com/android/documentsui/sidebar/NavRailRootAndAppItem.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.android.documentsui.sidebar; + +import android.content.pm.ResolveInfo; +import android.view.View; + +import com.android.documentsui.ActionHandler; +import com.android.documentsui.R; +import com.android.documentsui.base.RootInfo; + +/** + * Similar to {@link RootAndAppItem} but only used in the navigation rail. + */ +public class NavRailRootAndAppItem extends RootAndAppItem { + + public NavRailRootAndAppItem( + RootInfo root, ResolveInfo info, ActionHandler actionHandler, boolean maybeShowBadge) { + super(R.layout.nav_rail_item_root, root, info, actionHandler, maybeShowBadge); + } + + @Override + public void bindView(View convertView) { + bindIconAndTitle(convertView); + } +} diff --git a/src/com/android/documentsui/sidebar/NavRailRootItem.java b/src/com/android/documentsui/sidebar/NavRailRootItem.java new file mode 100644 index 000000000..3d4042f22 --- /dev/null +++ b/src/com/android/documentsui/sidebar/NavRailRootItem.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 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. + */ + +package com.android.documentsui.sidebar; + + +import android.view.View; + +import com.android.documentsui.ActionHandler; +import com.android.documentsui.R; +import com.android.documentsui.base.RootInfo; + +/** + * Similar to {@link RootItem} but only used in the navigation rail. + */ +public class NavRailRootItem extends RootItem { + + public NavRailRootItem(RootInfo root, ActionHandler actionHandler, boolean maybeShowBadge) { + super( + R.layout.nav_rail_item_root, + root, + actionHandler, + "" /* packageName */, + maybeShowBadge); + } + + public NavRailRootItem( + RootInfo root, + ActionHandler actionHandler, + String packageName, + boolean maybeShowBadge) { + super(R.layout.nav_rail_item_root, root, actionHandler, packageName, maybeShowBadge); + } + + @Override + public void bindView(View convertView) { + bindIconAndTitle(convertView); + } +} diff --git a/src/com/android/documentsui/sidebar/ProfileItem.java b/src/com/android/documentsui/sidebar/ProfileItem.java index 15068ad4b..779f54445 100644 --- a/src/com/android/documentsui/sidebar/ProfileItem.java +++ b/src/com/android/documentsui/sidebar/ProfileItem.java @@ -20,6 +20,8 @@ import android.content.pm.ResolveInfo; import android.view.View; import android.widget.ImageView; +import androidx.annotation.LayoutRes; + import com.android.documentsui.ActionHandler; import com.android.documentsui.base.UserId; @@ -32,6 +34,11 @@ class ProfileItem extends AppItem { super(info, title, UserId.CURRENT_USER, actionHandler); } + ProfileItem( + @LayoutRes int layoutId, ResolveInfo info, String title, ActionHandler actionHandler) { + super(layoutId, info, title, UserId.CURRENT_USER, actionHandler); + } + @Override protected void bindIcon(ImageView icon) { icon.setImageResource(com.android.documentsui.R.drawable.ic_user_profile); diff --git a/src/com/android/documentsui/sidebar/RootAndAppItem.java b/src/com/android/documentsui/sidebar/RootAndAppItem.java index b893878f3..8861f6058 100644 --- a/src/com/android/documentsui/sidebar/RootAndAppItem.java +++ b/src/com/android/documentsui/sidebar/RootAndAppItem.java @@ -18,11 +18,11 @@ package com.android.documentsui.sidebar; import android.content.Context; import android.content.pm.ResolveInfo; -import android.os.UserManager; import android.provider.DocumentsProvider; -import android.text.TextUtils; import android.view.View; +import androidx.annotation.LayoutRes; + import com.android.documentsui.ActionHandler; import com.android.documentsui.R; import com.android.documentsui.base.RootInfo; @@ -36,9 +36,18 @@ class RootAndAppItem extends RootItem { public final ResolveInfo resolveInfo; - public RootAndAppItem(RootInfo root, ResolveInfo info, ActionHandler actionHandler, + RootAndAppItem( + RootInfo root, ResolveInfo info, ActionHandler actionHandler, boolean maybeShowBadge) { + this(R.layout.item_root, root, info, actionHandler, maybeShowBadge); + } + + RootAndAppItem( + @LayoutRes int layoutId, + RootInfo root, + ResolveInfo info, + ActionHandler actionHandler, boolean maybeShowBadge) { - super(root, actionHandler, info.activityInfo.packageName, maybeShowBadge); + super(layoutId, root, actionHandler, info.activityInfo.packageName, maybeShowBadge); this.resolveInfo = info; } diff --git a/src/com/android/documentsui/sidebar/RootItem.java b/src/com/android/documentsui/sidebar/RootItem.java index 4b40d91b3..326f086e1 100644 --- a/src/com/android/documentsui/sidebar/RootItem.java +++ b/src/com/android/documentsui/sidebar/RootItem.java @@ -30,6 +30,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.LayoutRes; import androidx.annotation.Nullable; import com.android.documentsui.ActionHandler; @@ -64,7 +65,16 @@ public class RootItem extends Item { public RootItem(RootInfo root, ActionHandler actionHandler, String packageName, boolean maybeShowBadge) { - super(R.layout.item_root, root.title, getStringId(root), root.userId); + this(R.layout.item_root, root, actionHandler, packageName, maybeShowBadge); + } + + public RootItem( + @LayoutRes int layoutId, + RootInfo root, + ActionHandler actionHandler, + String packageName, + boolean maybeShowBadge) { + super(layoutId, root.title, getStringId(root), root.userId); this.root = root; mActionHandler = actionHandler; mPackageName = packageName; diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java index 76df696ab..2ec4c1728 100644 --- a/src/com/android/documentsui/sidebar/RootsFragment.java +++ b/src/com/android/documentsui/sidebar/RootsFragment.java @@ -19,6 +19,7 @@ package com.android.documentsui.sidebar; import static com.android.documentsui.base.Shared.compareToIgnoreCaseNullable; import static com.android.documentsui.base.SharedMinimal.DEBUG; import static com.android.documentsui.base.SharedMinimal.VERBOSE; +import static com.android.documentsui.flags.Flags.useMaterial3; import android.app.admin.DevicePolicyManager; import android.content.Context; @@ -49,6 +50,7 @@ import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ListView; +import androidx.annotation.IdRes; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; @@ -96,12 +98,23 @@ import java.util.stream.Collectors; /** * Display list of known storage backend roots. + * This fragment will be used in: + * * fixed_layout: as navigation tree (sidebar) + * * drawer_layout: as navigation drawer + * * nav_rail_layout: as navigation drawer and navigation rail. */ public class RootsFragment extends Fragment { private static final String TAG = "RootsFragment"; private static final String EXTRA_INCLUDE_APPS = "includeApps"; private static final String EXTRA_INCLUDE_APPS_INTENT = "includeAppsIntent"; + /** + * A key used to store the container id in the RootFragment. + * RootFragment is used in both navigation drawer and navigation rail, there are 2 instances + * of the fragment rendered on the page, we need to know which one is which to render different + * nav items inside. + */ + private static final String EXTRA_CONTAINER_ID = "containerId"; private static final int CONTEXT_MENU_ITEM_TIMEOUT = 500; private final OnItemClickListener mItemListener = new OnItemClickListener() { @@ -135,41 +148,88 @@ public class RootsFragment extends Fragment { private List<Item> mApplicationItemList; + // Weather the fragment is using nav_rail_container_roots as its container (in nav_rail_layout). + // This will always be false if useMaterial3() flag is off. + private boolean mUseRailAsContainer = false; + + /** + * Show the RootsFragment inside the navigation drawer container. + */ + public static RootsFragment show(FragmentManager fm, boolean includeApps, Intent intent) { + return showWithLayout(R.id.container_roots, fm, includeApps, intent); + } + + /** + * Show the RootsFragment inside the navigation rail container. + */ + public static RootsFragment showNavRail(FragmentManager fm, boolean includeApps, + Intent intent) { + return showWithLayout(R.id.nav_rail_container_roots, fm, includeApps, intent); + } + /** * Shows the {@link RootsFragment}. * + * @param containerId the container id where the {@link RootsFragment} will be rendered into * @param fm the FragmentManager for interacting with fragments associated with this * fragment's activity * @param includeApps if {@code true}, query the intent from the system and include apps in * the {@RootsFragment}. * @param intent the intent to query for package manager */ - public static RootsFragment show(FragmentManager fm, boolean includeApps, Intent intent) { + private static RootsFragment showWithLayout( + @IdRes int containerId, FragmentManager fm, boolean includeApps, Intent intent) { final Bundle args = new Bundle(); args.putBoolean(EXTRA_INCLUDE_APPS, includeApps); args.putParcelable(EXTRA_INCLUDE_APPS_INTENT, intent); + if (useMaterial3()) { + args.putInt(EXTRA_CONTAINER_ID, containerId); + } final RootsFragment fragment = new RootsFragment(); fragment.setArguments(args); final FragmentTransaction ft = fm.beginTransaction(); - ft.replace(R.id.container_roots, fragment); + ft.replace(containerId, fragment); ft.commitAllowingStateLoss(); return fragment; } + /** + * Get the RootsFragment instance for the navigation drawer. + */ public static RootsFragment get(FragmentManager fm) { return (RootsFragment) fm.findFragmentById(R.id.container_roots); } + /** + * Get the RootsFragment instance for the navigation drawer. + */ + public static RootsFragment getNavRail(FragmentManager fm) { + return (RootsFragment) fm.findFragmentById(R.id.nav_rail_container_roots); + } + @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + if (useMaterial3()) { + mUseRailAsContainer = + getArguments() != null + && getArguments().getInt(EXTRA_CONTAINER_ID) + == R.id.nav_rail_container_roots; + } + mInjector = getBaseActivity().getInjector(); - final View view = inflater.inflate(R.layout.fragment_roots, container, false); + final View view = + inflater.inflate( + mUseRailAsContainer + ? R.layout.fragment_nav_rail_roots + : R.layout.fragment_roots, + container, + false); mList = (ListView) view.findViewById(R.id.roots_list); mList.setOnItemClickListener(mItemListener); // ListView does not have right-click specific listeners, so we will have a @@ -312,10 +372,17 @@ public class RootsFragment extends Fragment { if (crossProfileResolveInfo != null && !Features.CROSS_PROFILE_TABS) { // Add profile item if we don't support cross-profile tab. sortedItems.add(new SpacerItem()); - sortedItems.add(new ProfileItem(crossProfileResolveInfo, - crossProfileResolveInfo.loadLabel( - getContext().getPackageManager()).toString(), - mActionHandler)); + if (mUseRailAsContainer) { + sortedItems.add(new NavRailProfileItem(crossProfileResolveInfo, + crossProfileResolveInfo.loadLabel( + getContext().getPackageManager()).toString(), + mActionHandler)); + } else { + sortedItems.add(new ProfileItem(crossProfileResolveInfo, + crossProfileResolveInfo.loadLabel( + getContext().getPackageManager()).toString(), + mActionHandler)); + } } // Disable drawer if only one root @@ -414,15 +481,30 @@ public class RootsFragment extends Fragment { if (root.isExternalStorageHome()) { continue; } else if (root.isLibrary() || root.isDownloads()) { - item = new RootItem(root, mActionHandler, maybeShowBadge); + item = + mUseRailAsContainer + ? new NavRailRootItem(root, mActionHandler, maybeShowBadge) + : new RootItem(root, mActionHandler, maybeShowBadge); librariesBuilder.add(item); } else if (root.isStorage()) { - item = new RootItem(root, mActionHandler, maybeShowBadge); + item = + mUseRailAsContainer + ? new NavRailRootItem(root, mActionHandler, maybeShowBadge) + : new RootItem(root, mActionHandler, maybeShowBadge); storageProvidersBuilder.add(item); } else { - item = new RootItem(root, mActionHandler, - providersAccess.getPackageName(root.userId, root.authority), - maybeShowBadge); + item = + mUseRailAsContainer + ? new NavRailRootItem( + root, + mActionHandler, + providersAccess.getPackageName(root.userId, root.authority), + maybeShowBadge) + : new RootItem( + root, + mActionHandler, + providersAccess.getPackageName(root.userId, root.authority), + maybeShowBadge); otherProviders.add(item); } } @@ -566,8 +648,18 @@ public class RootsFragment extends Fragment { appsMapping.put(userPackage, info); if (!CrossProfileUtils.isCrossProfileIntentForwarderActivity(info)) { - final Item item = new AppItem(info, info.loadLabel(pm).toString(), userId, - mActionHandler); + final Item item = + mUseRailAsContainer + ? new NavRailAppItem( + info, + info.loadLabel(pm).toString(), + userId, + mActionHandler) + : new AppItem( + info, + info.loadLabel(pm).toString(), + userId, + mActionHandler); appItems.put(userPackage, item); if (VERBOSE) Log.v(TAG, "Adding handler app: " + item); } @@ -583,8 +675,12 @@ public class RootsFragment extends Fragment { final Item item; if (resolveInfo != null) { - item = new RootAndAppItem(rootItem.root, resolveInfo, mActionHandler, - maybeShowBadge); + item = + mUseRailAsContainer + ? new NavRailRootAndAppItem( + rootItem.root, resolveInfo, mActionHandler, maybeShowBadge) + : new RootAndAppItem( + rootItem.root, resolveInfo, mActionHandler, maybeShowBadge); appItems.remove(userPackage); } else { item = rootItem; |