diff options
author | 2024-12-23 05:25:57 +0000 | |
---|---|---|
committer | 2025-01-20 15:13:45 +0000 | |
commit | 89c4d019dab08a2bc098ea95d9a9daa7952c176c (patch) | |
tree | 9e2901e6c216a6589244a9a2fcf5f778a19a3e8c | |
parent | 28fdd411e9c164297dc62dd198b3a79367652997 (diff) |
[DocsUI M3] Add navigation rail layout for medium size screen
* Add a new layout nav_rail_layout for medium size >=600 && <900
which also uses DrawerLayout but adds a narrower sidebar with
icon/label only.
* Reuse RootsFragment to render Navigation rail so we can keep
all existing functionality for navigation drawer on navigation
rail. We store the layout ID on the RootsFragment argument to
distinguish which layout is rendering, in medium size layout,
2 instances of RootsFragment will be rendered: one for the
navigation drawer, the other for navigation rail.
* A new layout nav_rail_item_root is created to cater the layout
for navigation rail items.
* Extend the existing navigation drawer item classes to create
the corresponding navigation rail item classes so we can use
different layout for navigation rail items.
Check the bug item for demo.
Bug: 381958615
Test: Manual inpsection
Flag: com.android.documentsui.flags.use_material3
Change-Id: I8fac4646031e248020755327caed6b427b5d08cf
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; |