From bd72a9eb07a9bea288bdda51f8d63628ffc6c136 Mon Sep 17 00:00:00 2001 From: mykh-hailo Date: Wed, 15 Apr 2026 12:36:29 -0300 Subject: [PATCH 1/3] feat: imporve tablet and large-screen navigationlayout Signed-off-by: mykh-hailo --- .../android/ui/activity/DrawerActivity.java | 248 +++++++++++++++--- .../ui/activity/FileDisplayActivity.kt | 23 ++ app/src/main/res/layout/activity_compose.xml | 68 +++-- .../main/res/layout/activity_navigator.xml | 47 +++- .../main/res/layout/contacts_preference.xml | 52 +++- app/src/main/res/layout/files.xml | 108 +++++--- app/src/main/res/layout/sidebar.xml | 67 +++++ .../main/res/layout/synced_folders_layout.xml | 122 +++++---- app/src/main/res/layout/toolbar_standard.xml | 2 +- app/src/main/res/layout/trashbin_activity.xml | 93 ++++--- .../main/res/layout/upload_list_layout.xml | 74 ++++-- app/src/main/res/values-sw600dp/bool.xml | 11 + app/src/main/res/values-sw600dp/dims.xml | 3 +- app/src/main/res/values-w600dp/bool.xml | 11 + app/src/main/res/values-w600dp/dims.xml | 11 + app/src/main/res/values/bools.xml | 1 + 16 files changed, 711 insertions(+), 230 deletions(-) create mode 100644 app/src/main/res/layout/sidebar.xml create mode 100644 app/src/main/res/values-sw600dp/bool.xml create mode 100644 app/src/main/res/values-w600dp/bool.xml create mode 100644 app/src/main/res/values-w600dp/dims.xml diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index b63241173040..2b8abaeaf5ef 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -184,6 +184,11 @@ public abstract class DrawerActivity extends ToolbarActivity private TextView mQuotaTextPercentage; private TextView mQuotaTextLink; + private LinearLayout sidebarQuotaView; + private LinearProgressIndicator sidebarQuotaProgressBar; + private TextView sidebarQuotaTextPercentage; + private TextView sidebarQuotaTextLink; + /** * runnable that will be executed after the drawer has been closed. */ @@ -195,6 +200,9 @@ public abstract class DrawerActivity extends ToolbarActivity private BottomNavigationView bottomNavigationView; private NavigationView drawerNavigationView; + private View sidebarMenu; + private NavigationView sidebarNavigationView; + /** * Returns the navigation drawer menu item ID that represents * the current activity. @@ -243,10 +251,18 @@ protected void setupDrawer(int id) { mDrawerLayout = findViewById(R.id.drawer_layout); } + if (sidebarMenu == null) { + sidebarMenu = findViewById(R.id.sidebar_menu); + } + if (drawerNavigationView == null) { drawerNavigationView = findViewById(R.id.nav_view); } + if (sidebarMenu != null && sidebarNavigationView == null) { + sidebarNavigationView = sidebarMenu.findViewById(R.id.sidebar_view); + } + if (drawerNavigationView != null) { viewThemeUtils.files.colorNavigationView(drawerNavigationView); @@ -255,16 +271,23 @@ protected void setupDrawer(int id) { updateHeader(); setupDrawerMenu(drawerNavigationView); - getAndDisplayUserQuota(); setupQuotaElement(); - highlightNavigationViewItem(id); } - setupDrawerToggle(); + if (sidebarNavigationView != null) { + viewThemeUtils.files.colorNavigationView(sidebarNavigationView); + + // Setting up drawer header + mNavigationViewHeader = sidebarNavigationView.getHeaderView(0); + updateHeader(); - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setupSidebarMenu(sidebarNavigationView); + setupSidebarQuotaElement(); } + getAndDisplayUserQuota(); + highlightNavigationViewItem(id); + + setupDrawerToggle(); if (bottomNavigationView == null) { bottomNavigationView = findViewById(R.id.bottom_navigation); @@ -276,6 +299,12 @@ protected void setupDrawer(int id) { handleBottomNavigationViewClicks(); highlightNavigationViewItem(id); } + + if (!isDrawerLayout() && mMenuButton != null) { + mMenuButton.setVisibility(View.GONE); + } + + hideOrShowSidebar(); } /** @@ -298,9 +327,16 @@ protected void setupDrawer(int id) { * @param menuItemId the ID of the menu item to mark as selected/highlighted */ public void highlightNavigationViewItem(int menuItemId) { - NavigationViewExtensionsKt.highlightNavigationView(drawerNavigationView, - bottomNavigationView, - menuItemId); + if (drawerNavigationView != null) { + NavigationViewExtensionsKt.highlightNavigationView(drawerNavigationView, + bottomNavigationView, + menuItemId); + } + if (sidebarNavigationView != null) { + NavigationViewExtensionsKt.highlightNavigationView(sidebarNavigationView, + bottomNavigationView, + menuItemId); + } Log_OC.d(TAG, "New menu item is: " + menuItemId); } @@ -393,6 +429,16 @@ private void setupQuotaElement() { viewThemeUtils.platform.colorViewBackground(mQuotaView); } + private void setupSidebarQuotaElement() { + sidebarQuotaView = (LinearLayout) findSidebarQuotaViewById(R.id.sidebar_quota); + sidebarQuotaProgressBar = (LinearProgressIndicator) findSidebarQuotaViewById(R.id.sidebar_quota_ProgressBar); + sidebarQuotaTextPercentage = (TextView) findSidebarQuotaViewById(R.id.sidebar_quota_percentage); + sidebarQuotaTextLink = (TextView) findSidebarQuotaViewById(R.id.sidebar_quota_link); + viewThemeUtils.material.colorProgressBar(sidebarQuotaProgressBar, ColorRole.PRIMARY); + sidebarQuotaProgressBar.setTrackStopIndicatorSize(0); + viewThemeUtils.platform.colorViewBackground(sidebarQuotaView); + } + public void updateHeader() { final var account = getAccount(); boolean isClientBranded = getResources().getBoolean(R.bool.is_branded_client); @@ -567,6 +613,17 @@ private void setupDrawerMenu(NavigationView navigationView) { filterDrawerMenu(navigationView.getMenu(), account); } + private void setupSidebarMenu(NavigationView navigationView) { + navigationView.setNavigationItemSelectedListener( + menuItem -> { + onNavigationItemClicked(menuItem); + return true; + }); + + User account = accountManager.getUser(); + filterDrawerMenu(navigationView.getMenu(), account); + } + private void filterDrawerMenu(final Menu menu, @NonNull final User user) { final var optionalCapability = getCapabilities(); if (optionalCapability.isPresent()) { @@ -588,6 +645,10 @@ private void filterDrawerMenu(final Menu menu, @NonNull final User user) { private void onNavigationItemClicked(final MenuItem menuItem) { int itemId = menuItem.getItemId(); + if (itemId == getMenuItemId()) { + return; + } + if (itemId == R.id.nav_all_files || itemId == R.id.nav_personal_files) { closeDrawer(); DrawerActivityExtensionsKt.navigateToAllFiles(this,itemId == R.id.nav_personal_files); @@ -884,6 +945,10 @@ protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) { if (mDrawerToggle != null) { mDrawerToggle.setDrawerIndicatorEnabled(chosenFile != null && isRoot(chosenFile)); } + + if (!isDrawerLayout() && getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(!isRoot(chosenFile)); + } } /** @@ -893,9 +958,19 @@ protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) { */ private void showQuota(boolean showQuota) { if (showQuota) { - mQuotaView.setVisibility(View.VISIBLE); + if (mQuotaView != null) { + mQuotaView.setVisibility(View.VISIBLE); + } + if (sidebarQuotaView != null) { + sidebarQuotaView.setVisibility(View.VISIBLE); + } } else { - mQuotaView.setVisibility(View.GONE); + if (mQuotaView != null) { + mQuotaView.setVisibility(View.GONE); + } + if (sidebarQuotaView != null) { + sidebarQuotaView.setVisibility(View.GONE); + } } } @@ -909,25 +984,62 @@ private void showQuota(boolean showQuota) { */ private void setQuotaInformation(long usedSpace, long totalSpace, int relative, long quotaValue) { if (GetUserInfoRemoteOperation.SPACE_UNLIMITED == quotaValue) { - mQuotaTextPercentage.setText(String.format( - getString(R.string.drawer_quota_unlimited), - DisplayUtils.bytesToHumanReadable(usedSpace))); + if (mQuotaTextPercentage != null) { + mQuotaTextPercentage.setText(String.format( + getString(R.string.drawer_quota_unlimited), + DisplayUtils.bytesToHumanReadable(usedSpace))); + } + + if (sidebarQuotaTextPercentage != null) { + sidebarQuotaTextPercentage.setText(String.format( + getString(R.string.drawer_quota_unlimited), + DisplayUtils.bytesToHumanReadable(usedSpace))); + } } else { - mQuotaTextPercentage.setText(String.format( - getString(R.string.drawer_quota), - DisplayUtils.bytesToHumanReadable(usedSpace), - DisplayUtils.bytesToHumanReadable(totalSpace))); + if (mQuotaTextPercentage != null) { + mQuotaTextPercentage.setText(String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(usedSpace), + DisplayUtils.bytesToHumanReadable(totalSpace))); + } + + if (sidebarQuotaTextPercentage != null) { + sidebarQuotaTextPercentage.setText(String.format( + getString(R.string.drawer_quota), + DisplayUtils.bytesToHumanReadable(usedSpace), + DisplayUtils.bytesToHumanReadable(totalSpace))); + } } - mQuotaProgressBar.setProgress(relative); + if (mQuotaProgressBar != null) { + mQuotaProgressBar.setProgress(relative); + } + + if (sidebarQuotaProgressBar != null) { + sidebarQuotaProgressBar.setProgress(relative); + } if (relative < RELATIVE_THRESHOLD_WARNING) { - viewThemeUtils.material.colorProgressBar(mQuotaProgressBar, ColorRole.PRIMARY); + if (mQuotaProgressBar != null) { + viewThemeUtils.material.colorProgressBar(mQuotaProgressBar, ColorRole.PRIMARY); + } + if (sidebarQuotaProgressBar != null) { + viewThemeUtils.material.colorProgressBar(sidebarQuotaProgressBar, ColorRole.PRIMARY); + } } else { - viewThemeUtils.material.colorProgressBar( - mQuotaProgressBar, - getResources().getColor(R.color.infolevel_warning, null) - ); + if (mQuotaProgressBar != null) { + viewThemeUtils.material.colorProgressBar( + mQuotaProgressBar, + getResources().getColor(R.color.infolevel_warning, null) + ); + } + + if (sidebarQuotaProgressBar != null) { + viewThemeUtils.material.colorProgressBar( + sidebarQuotaProgressBar, + getResources().getColor(R.color.infolevel_warning, null) + ); + } } updateQuotaLink(); @@ -953,23 +1065,44 @@ private void updateQuotaLink() { } final ExternalLink firstQuota = quotas.get(0); - mQuotaTextLink.setText(firstQuota.getName()); - mQuotaTextLink.setClickable(true); - mQuotaTextLink.setVisibility(View.VISIBLE); - mQuotaTextLink.setOnClickListener(v -> { - Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class); - externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, firstQuota.getName()); - externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, firstQuota.getUrl()); - externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true); - startActivity(externalWebViewIntent); - }); + if (mQuotaTextLink != null) { + mQuotaTextLink.setText(firstQuota.getName()); + mQuotaTextLink.setClickable(true); + mQuotaTextLink.setVisibility(View.VISIBLE); + mQuotaTextLink.setOnClickListener(v -> { + Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, firstQuota.getName()); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, firstQuota.getUrl()); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true); + startActivity(externalWebViewIntent); + }); - Target quotaTarget = createQuotaDrawableTarget(size, mQuotaTextLink); - GlideHelper.INSTANCE.loadIntoTarget(DrawerActivity.this, - accountManager.getCurrentOwnCloudAccount(), - firstQuota.getIconUrl(), - quotaTarget, - R.drawable.ic_link); + Target quotaTarget = createQuotaDrawableTarget(size, mQuotaTextLink); + GlideHelper.INSTANCE.loadIntoTarget(DrawerActivity.this, + accountManager.getCurrentOwnCloudAccount(), + firstQuota.getIconUrl(), + quotaTarget, + R.drawable.ic_link); + } + if (sidebarQuotaTextLink != null) { + sidebarQuotaTextLink.setText(firstQuota.getName()); + sidebarQuotaTextLink.setClickable(true); + sidebarQuotaTextLink.setVisibility(View.VISIBLE); + sidebarQuotaTextLink.setOnClickListener(v -> { + Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, firstQuota.getName()); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, firstQuota.getUrl()); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true); + startActivity(externalWebViewIntent); + }); + + Target quotaTarget = createQuotaDrawableTarget(size, sidebarQuotaTextLink); + GlideHelper.INSTANCE.loadIntoTarget(DrawerActivity.this, + accountManager.getCurrentOwnCloudAccount(), + firstQuota.getIconUrl(), + quotaTarget, + R.drawable.ic_link); + } return Unit.INSTANCE; }); } @@ -1185,6 +1318,29 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) { if (mDrawerToggle != null) { mDrawerToggle.onConfigurationChanged(newConfig); } + + hideOrShowSidebar(); + } + + private void hideOrShowSidebar() { + if (isDrawerLayout()) { + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + if (sidebarMenu != null) { + sidebarMenu.setVisibility(View.GONE); + } + } else { + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + } + + if (sidebarMenu != null) { + sidebarMenu.setVisibility(View.VISIBLE); + } + closeDrawer(); + } } public void addOnBackPressedCallback() { @@ -1252,6 +1408,16 @@ private View findQuotaViewById(int id) { } } + private View findSidebarQuotaViewById(int id) { + View v = ((NavigationView) findViewById(R.id.sidebar_view)).getHeaderView(0).findViewById(id); + + if (v != null) { + return v; + } else { + return findViewById(id); + } + } + /** * restart helper method which is called after a changing the current account. */ @@ -1493,4 +1659,8 @@ public boolean isToolbarStyleSearch() { menuItemId == R.id.nav_all_files || menuItemId == R.id.nav_personal_files; } + + protected boolean isDrawerLayout() { + return getResources().getBoolean(R.bool.is_support_drawer); + } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 864df474b964..1c4620b3cb93 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -400,6 +400,16 @@ class FileDisplayActivity : } } } + + if (isDrawerLayout) { + mMenuButton.visibility = View.VISIBLE + } else { + if (isRoot(currentDir)) { + mMenuButton.visibility = View.GONE + } else { + mMenuButton.visibility = View.VISIBLE + } + } } override fun onPostCreate(savedInstanceState: Bundle?) { @@ -1052,6 +1062,9 @@ class FileDisplayActivity : private fun exitSelectionMode() { val ocFileListFragment = this.listOfFilesFragment ocFileListFragment?.exitSelectionMode() + + // Update toolbar with current directory + updateActionBarTitleAndHomeButton(currentDir) } private fun requestUploadOfFilesFromFileSystem(data: Intent, resultCode: Int) { @@ -1980,6 +1993,16 @@ class FileDisplayActivity : chosenFile = file // if no file is passed, current file decides } super.updateActionBarTitleAndHomeButton(chosenFile) + + if (isDrawerLayout) { + mMenuButton.visibility = View.VISIBLE + } else { + if (isRoot(currentDir)) { + mMenuButton.visibility = View.GONE + } else { + mMenuButton.visibility = View.VISIBLE + } + } } override fun isDrawerIndicatorAvailable(): Boolean = isRoot(getCurrentDir()) diff --git a/app/src/main/res/layout/activity_compose.xml b/app/src/main/res/layout/activity_compose.xml index f5cda35ea67e..c06524adb1c9 100644 --- a/app/src/main/res/layout/activity_compose.xml +++ b/app/src/main/res/layout/activity_compose.xml @@ -8,35 +8,61 @@ --> + android:focusable="true"> - + android:layout_height="match_parent"> + + + + - + - - - - - + android:orientation="vertical" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/sidebar_menu" + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + + - - + - + - + + + + + + + + + + + - - - - + android:layout_height="match_parent"> + android:id="@+id/sidebar_menu" + android:layout_width="@dimen/drawer_width" + android:layout_height="0dp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + - + + + + + + + + + + + + - - + + + + + - - + - + + + + android:background="@color/bg_default" + android:baselineAligned="false" + android:contentDescription="@string/list_layout" + android:orientation="horizontal" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + - + - + + + - + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/synced_folders_layout.xml b/app/src/main/res/layout/synced_folders_layout.xml index 7101594d388e..92889a1eea7c 100644 --- a/app/src/main/res/layout/synced_folders_layout.xml +++ b/app/src/main/res/layout/synced_folders_layout.xml @@ -15,71 +15,97 @@ android:layout_height="match_parent" tools:openDrawer="start"> - - - - - - + + android:layout_height="match_parent" + android:layout_gravity="start" /> + + + + + - + + + + android:layout_height="match_parent" + android:orientation="vertical" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - + + + android:layout_height="0dp" + android:layout_weight="1"> - - + - + - + + + + + + - + - + - + + - - - + - + - + + + + + + + + android:layout_below="@id/toolbar_standard_include"> - - + android:layout_height="match_parent" + android:footerDividersEnabled="false" + android:visibility="visible" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + + - - - + - + + + - + + + - + - + + - - - - + android:layout_height="match_parent"> + android:id="@+id/sidebar_menu" + android:layout_width="@dimen/drawer_width" + android:layout_height="0dp" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + + + + + + - + + + android:layout_below="@id/appbar"> - + android:layout_height="match_parent" + android:footerDividersEnabled="false" + android:visibility="visible"> - + - + - + + + + + - + + + + false + diff --git a/app/src/main/res/values-sw600dp/dims.xml b/app/src/main/res/values-sw600dp/dims.xml index 7436d593a3c5..1abd0e1019f7 100644 --- a/app/src/main/res/values-sw600dp/dims.xml +++ b/app/src/main/res/values-sw600dp/dims.xml @@ -8,5 +8,6 @@ --> 6 - 512dp + 512dp + 300dp diff --git a/app/src/main/res/values-w600dp/bool.xml b/app/src/main/res/values-w600dp/bool.xml new file mode 100644 index 000000000000..2e4fa466b2a2 --- /dev/null +++ b/app/src/main/res/values-w600dp/bool.xml @@ -0,0 +1,11 @@ + + + + false + diff --git a/app/src/main/res/values-w600dp/dims.xml b/app/src/main/res/values-w600dp/dims.xml new file mode 100644 index 000000000000..8883840772a3 --- /dev/null +++ b/app/src/main/res/values-w600dp/dims.xml @@ -0,0 +1,11 @@ + + + + 300dp + diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml index ef48d5a0e566..31cb2b2c1978 100644 --- a/app/src/main/res/values/bools.xml +++ b/app/src/main/res/values/bools.xml @@ -8,4 +8,5 @@ --> true + true From bfaeccff138652f16ce17280f4363486789951fc Mon Sep 17 00:00:00 2001 From: mykh-hailo Date: Wed, 15 Apr 2026 13:42:54 -0300 Subject: [PATCH 2/3] fix: extract sidebar logic in a separate class Signed-off-by: mykh-hailo --- .../android/ui/activity/DrawerActivity.java | 190 +++++++----------- .../android/ui/activity/SidebarManager.kt | 165 +++++++++++++++ 2 files changed, 239 insertions(+), 116 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/activity/SidebarManager.kt diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 2b8abaeaf5ef..fce133642e18 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -184,11 +184,6 @@ public abstract class DrawerActivity extends ToolbarActivity private TextView mQuotaTextPercentage; private TextView mQuotaTextLink; - private LinearLayout sidebarQuotaView; - private LinearProgressIndicator sidebarQuotaProgressBar; - private TextView sidebarQuotaTextPercentage; - private TextView sidebarQuotaTextLink; - /** * runnable that will be executed after the drawer has been closed. */ @@ -200,8 +195,7 @@ public abstract class DrawerActivity extends ToolbarActivity private BottomNavigationView bottomNavigationView; private NavigationView drawerNavigationView; - private View sidebarMenu; - private NavigationView sidebarNavigationView; + private SidebarManager sidebarManager; /** * Returns the navigation drawer menu item ID that represents @@ -251,18 +245,14 @@ protected void setupDrawer(int id) { mDrawerLayout = findViewById(R.id.drawer_layout); } - if (sidebarMenu == null) { - sidebarMenu = findViewById(R.id.sidebar_menu); + if (sidebarManager == null) { + sidebarManager = new SidebarManager(findViewById(android.R.id.content)); } if (drawerNavigationView == null) { drawerNavigationView = findViewById(R.id.nav_view); } - if (sidebarMenu != null && sidebarNavigationView == null) { - sidebarNavigationView = sidebarMenu.findViewById(R.id.sidebar_view); - } - if (drawerNavigationView != null) { viewThemeUtils.files.colorNavigationView(drawerNavigationView); @@ -274,15 +264,19 @@ protected void setupDrawer(int id) { setupQuotaElement(); } - if (sidebarNavigationView != null) { - viewThemeUtils.files.colorNavigationView(sidebarNavigationView); - - // Setting up drawer header - mNavigationViewHeader = sidebarNavigationView.getHeaderView(0); - updateHeader(); - - setupSidebarMenu(sidebarNavigationView); - setupSidebarQuotaElement(); + if (sidebarManager != null) { + sidebarManager.setup( + viewThemeUtils, + headerView -> { + mNavigationViewHeader = headerView; + updateHeader(); + }, + menu -> { + User account = accountManager.getUser(); + filterDrawerMenu(menu, account); + }, + this::onNavigationItemClicked + ); } getAndDisplayUserQuota(); highlightNavigationViewItem(id); @@ -304,7 +298,7 @@ protected void setupDrawer(int id) { mMenuButton.setVisibility(View.GONE); } - hideOrShowSidebar(); + updateSidebarLayoutMode(); } /** @@ -332,10 +326,8 @@ public void highlightNavigationViewItem(int menuItemId) { bottomNavigationView, menuItemId); } - if (sidebarNavigationView != null) { - NavigationViewExtensionsKt.highlightNavigationView(sidebarNavigationView, - bottomNavigationView, - menuItemId); + if (sidebarManager != null) { + sidebarManager.highlight(bottomNavigationView, menuItemId); } Log_OC.d(TAG, "New menu item is: " + menuItemId); } @@ -429,16 +421,6 @@ private void setupQuotaElement() { viewThemeUtils.platform.colorViewBackground(mQuotaView); } - private void setupSidebarQuotaElement() { - sidebarQuotaView = (LinearLayout) findSidebarQuotaViewById(R.id.sidebar_quota); - sidebarQuotaProgressBar = (LinearProgressIndicator) findSidebarQuotaViewById(R.id.sidebar_quota_ProgressBar); - sidebarQuotaTextPercentage = (TextView) findSidebarQuotaViewById(R.id.sidebar_quota_percentage); - sidebarQuotaTextLink = (TextView) findSidebarQuotaViewById(R.id.sidebar_quota_link); - viewThemeUtils.material.colorProgressBar(sidebarQuotaProgressBar, ColorRole.PRIMARY); - sidebarQuotaProgressBar.setTrackStopIndicatorSize(0); - viewThemeUtils.platform.colorViewBackground(sidebarQuotaView); - } - public void updateHeader() { final var account = getAccount(); boolean isClientBranded = getResources().getBoolean(R.bool.is_branded_client); @@ -613,17 +595,6 @@ private void setupDrawerMenu(NavigationView navigationView) { filterDrawerMenu(navigationView.getMenu(), account); } - private void setupSidebarMenu(NavigationView navigationView) { - navigationView.setNavigationItemSelectedListener( - menuItem -> { - onNavigationItemClicked(menuItem); - return true; - }); - - User account = accountManager.getUser(); - filterDrawerMenu(navigationView.getMenu(), account); - } - private void filterDrawerMenu(final Menu menu, @NonNull final User user) { final var optionalCapability = getCapabilities(); if (optionalCapability.isPresent()) { @@ -946,8 +917,8 @@ protected void updateActionBarTitleAndHomeButton(OCFile chosenFile) { mDrawerToggle.setDrawerIndicatorEnabled(chosenFile != null && isRoot(chosenFile)); } - if (!isDrawerLayout() && getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(!isRoot(chosenFile)); + if (sidebarManager != null) { + sidebarManager.updateActionBarForFile(isDrawerLayout(), getSupportActionBar(), isRoot(chosenFile)); } } @@ -961,15 +932,15 @@ private void showQuota(boolean showQuota) { if (mQuotaView != null) { mQuotaView.setVisibility(View.VISIBLE); } - if (sidebarQuotaView != null) { - sidebarQuotaView.setVisibility(View.VISIBLE); + if (sidebarManager != null) { + sidebarManager.showQuota(true); } } else { if (mQuotaView != null) { mQuotaView.setVisibility(View.GONE); } - if (sidebarQuotaView != null) { - sidebarQuotaView.setVisibility(View.GONE); + if (sidebarManager != null) { + sidebarManager.showQuota(false); } } } @@ -990,8 +961,8 @@ private void setQuotaInformation(long usedSpace, long totalSpace, int relative, DisplayUtils.bytesToHumanReadable(usedSpace))); } - if (sidebarQuotaTextPercentage != null) { - sidebarQuotaTextPercentage.setText(String.format( + if (sidebarManager != null) { + sidebarManager.setQuotaText(String.format( getString(R.string.drawer_quota_unlimited), DisplayUtils.bytesToHumanReadable(usedSpace))); } @@ -1003,8 +974,8 @@ private void setQuotaInformation(long usedSpace, long totalSpace, int relative, DisplayUtils.bytesToHumanReadable(totalSpace))); } - if (sidebarQuotaTextPercentage != null) { - sidebarQuotaTextPercentage.setText(String.format( + if (sidebarManager != null) { + sidebarManager.setQuotaText(String.format( getString(R.string.drawer_quota), DisplayUtils.bytesToHumanReadable(usedSpace), DisplayUtils.bytesToHumanReadable(totalSpace))); @@ -1015,16 +986,16 @@ private void setQuotaInformation(long usedSpace, long totalSpace, int relative, mQuotaProgressBar.setProgress(relative); } - if (sidebarQuotaProgressBar != null) { - sidebarQuotaProgressBar.setProgress(relative); + if (sidebarManager != null) { + sidebarManager.setQuotaProgress(relative); } if (relative < RELATIVE_THRESHOLD_WARNING) { if (mQuotaProgressBar != null) { viewThemeUtils.material.colorProgressBar(mQuotaProgressBar, ColorRole.PRIMARY); } - if (sidebarQuotaProgressBar != null) { - viewThemeUtils.material.colorProgressBar(sidebarQuotaProgressBar, ColorRole.PRIMARY); + if (sidebarManager != null) { + sidebarManager.colorQuotaProgressPrimary(viewThemeUtils); } } else { if (mQuotaProgressBar != null) { @@ -1034,11 +1005,11 @@ private void setQuotaInformation(long usedSpace, long totalSpace, int relative, ); } - if (sidebarQuotaProgressBar != null) { - viewThemeUtils.material.colorProgressBar( - sidebarQuotaProgressBar, + if (sidebarManager != null) { + sidebarManager.colorQuotaProgressWarning( + viewThemeUtils, getResources().getColor(R.color.infolevel_warning, null) - ); + ); } } @@ -1047,12 +1018,17 @@ private void setQuotaInformation(long usedSpace, long totalSpace, int relative, } private void updateQuotaLink() { - if (mQuotaTextLink == null) { + if (mQuotaTextLink == null && sidebarManager == null) { return; } if (!MDMConfig.INSTANCE.externalSiteSupport(this)) { - mQuotaTextLink.setVisibility(View.GONE); + if (mQuotaTextLink != null) { + mQuotaTextLink.setVisibility(View.GONE); + } + if (sidebarManager != null) { + sidebarManager.hideQuotaLink(); + } return; } @@ -1060,7 +1036,12 @@ private void updateQuotaLink() { float density = getResources().getDisplayMetrics().density; final int size = Math.round(24 * density); if (quotas.isEmpty()) { - mQuotaTextLink.setVisibility(View.GONE); + if (mQuotaTextLink != null) { + mQuotaTextLink.setVisibility(View.GONE); + } + if (sidebarManager != null) { + sidebarManager.hideQuotaLink(); + } return Unit.INSTANCE; } @@ -1084,24 +1065,26 @@ private void updateQuotaLink() { quotaTarget, R.drawable.ic_link); } - if (sidebarQuotaTextLink != null) { - sidebarQuotaTextLink.setText(firstQuota.getName()); - sidebarQuotaTextLink.setClickable(true); - sidebarQuotaTextLink.setVisibility(View.VISIBLE); - sidebarQuotaTextLink.setOnClickListener(v -> { - Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class); - externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, firstQuota.getName()); - externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, firstQuota.getUrl()); - externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true); - startActivity(externalWebViewIntent); - }); - - Target quotaTarget = createQuotaDrawableTarget(size, sidebarQuotaTextLink); - GlideHelper.INSTANCE.loadIntoTarget(DrawerActivity.this, - accountManager.getCurrentOwnCloudAccount(), - firstQuota.getIconUrl(), - quotaTarget, - R.drawable.ic_link); + if (sidebarManager != null) { + sidebarManager.updateQuotaLink( + firstQuota, + size, + quotaLink -> { + Intent externalWebViewIntent = new Intent(getApplicationContext(), ExternalSiteWebView.class); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, quotaLink.getName()); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_URL, quotaLink.getUrl()); + externalWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, true); + startActivity(externalWebViewIntent); + }, + this::createQuotaDrawableTarget, + (quotaTarget, iconUrl) -> GlideHelper.INSTANCE.loadIntoTarget( + DrawerActivity.this, + accountManager.getCurrentOwnCloudAccount(), + iconUrl, + quotaTarget, + R.drawable.ic_link + ) + ); } return Unit.INSTANCE; }); @@ -1319,27 +1302,12 @@ public void onConfigurationChanged(@NonNull Configuration newConfig) { mDrawerToggle.onConfigurationChanged(newConfig); } - hideOrShowSidebar(); + updateSidebarLayoutMode(); } - private void hideOrShowSidebar() { - if (isDrawerLayout()) { - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - if (sidebarMenu != null) { - sidebarMenu.setVisibility(View.GONE); - } - } else { - if (getSupportActionBar() != null) { - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - } - - if (sidebarMenu != null) { - sidebarMenu.setVisibility(View.VISIBLE); - } - closeDrawer(); + private void updateSidebarLayoutMode() { + if (sidebarManager != null) { + sidebarManager.applyLayoutMode(isDrawerLayout(), getSupportActionBar(), this::closeDrawer); } } @@ -1408,16 +1376,6 @@ private View findQuotaViewById(int id) { } } - private View findSidebarQuotaViewById(int id) { - View v = ((NavigationView) findViewById(R.id.sidebar_view)).getHeaderView(0).findViewById(id); - - if (v != null) { - return v; - } else { - return findViewById(id); - } - } - /** * restart helper method which is called after a changing the current account. */ diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SidebarManager.kt b/app/src/main/java/com/owncloud/android/ui/activity/SidebarManager.kt new file mode 100644 index 000000000000..606725d618c8 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/activity/SidebarManager.kt @@ -0,0 +1,165 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.ui.activity + +import android.graphics.drawable.Drawable +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.annotation.IdRes +import androidx.appcompat.app.ActionBar +import com.bumptech.glide.request.target.Target +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.google.android.material.navigation.NavigationView +import com.google.android.material.progressindicator.LinearProgressIndicator +import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.utils.extensions.highlightNavigationView +import com.owncloud.android.R +import com.owncloud.android.lib.common.ExternalLink +import com.owncloud.android.utils.theme.ViewThemeUtils +import java.util.function.BiConsumer +import java.util.function.BiFunction +import java.util.function.Consumer + +class SidebarManager(private val hostView: View) { + private var sidebarMenu: View? = null + private var sidebarNavigationView: NavigationView? = null + private var sidebarQuotaView: LinearLayout? = null + private var sidebarQuotaProgressBar: LinearProgressIndicator? = null + private var sidebarQuotaTextPercentage: TextView? = null + private var sidebarQuotaTextLink: TextView? = null + + fun setup( + viewThemeUtils: ViewThemeUtils, + onHeaderReady: Consumer, + filterMenu: Consumer, + onNavigationItemClicked: Consumer + ) { + if (bindNavigationView() == null) { + return + } + + colorNavigationView(viewThemeUtils) + getHeaderView()?.let { onHeaderReady.accept(it) } + setupMenu(filterMenu, onNavigationItemClicked) + setupQuotaElements(viewThemeUtils) + } + + fun highlight(bottomNavigationView: BottomNavigationView?, menuItemId: Int) { + sidebarNavigationView?.let { + highlightNavigationView(it, bottomNavigationView, menuItemId) + } + } + + fun showQuota(show: Boolean) { + sidebarQuotaView?.visibility = if (show) View.VISIBLE else View.GONE + } + + fun setQuotaText(text: String) { + sidebarQuotaTextPercentage?.text = text + } + + fun setQuotaProgress(relative: Int) { + sidebarQuotaProgressBar?.progress = relative + } + + fun colorQuotaProgressPrimary(viewThemeUtils: ViewThemeUtils) { + sidebarQuotaProgressBar?.let { viewThemeUtils.material.colorProgressBar(it, ColorRole.PRIMARY) } + } + + fun colorQuotaProgressWarning(viewThemeUtils: ViewThemeUtils, @ColorInt color: Int) { + sidebarQuotaProgressBar?.let { viewThemeUtils.material.colorProgressBar(it, color) } + } + + fun hideQuotaLink() { + sidebarQuotaTextLink?.visibility = View.GONE + } + + fun updateQuotaLink( + firstQuota: ExternalLink, + iconSize: Int, + onQuotaLinkClick: Consumer, + createQuotaDrawableTarget: BiFunction>, + loadQuotaIcon: BiConsumer, String> + ) { + val quotaTextLink = sidebarQuotaTextLink ?: return + quotaTextLink.text = firstQuota.name + quotaTextLink.isClickable = true + quotaTextLink.visibility = View.VISIBLE + quotaTextLink.setOnClickListener { onQuotaLinkClick.accept(firstQuota) } + + val quotaTarget = createQuotaDrawableTarget.apply(iconSize, quotaTextLink) + loadQuotaIcon.accept(quotaTarget, firstQuota.iconUrl) + } + + fun applyLayoutMode(isDrawerLayout: Boolean, actionBar: ActionBar?, onSidebarVisible: Runnable) { + if (isDrawerLayout) { + actionBar?.setDisplayHomeAsUpEnabled(true) + sidebarMenu?.visibility = View.GONE + } else { + actionBar?.setDisplayHomeAsUpEnabled(false) + sidebarMenu?.visibility = View.VISIBLE + onSidebarVisible.run() + } + } + + fun updateActionBarForFile(isDrawerLayout: Boolean, actionBar: ActionBar?, isRoot: Boolean) { + if (!isDrawerLayout) { + actionBar?.setDisplayHomeAsUpEnabled(!isRoot) + } + } + + private fun bindNavigationView(): NavigationView? { + if (sidebarMenu == null) { + sidebarMenu = hostView.findViewById(R.id.sidebar_menu) + } + + if (sidebarMenu != null && sidebarNavigationView == null) { + sidebarNavigationView = sidebarMenu?.findViewById(R.id.sidebar_view) + } + + return sidebarNavigationView + } + + private fun getHeaderView(): View? = sidebarNavigationView?.getHeaderView(0) + + private fun setupMenu(filterMenu: Consumer, onNavigationItemClicked: Consumer) { + sidebarNavigationView?.setNavigationItemSelectedListener { menuItem -> + onNavigationItemClicked.accept(menuItem) + true + } + + sidebarNavigationView?.menu?.let { filterMenu.accept(it) } + } + + private fun colorNavigationView(viewThemeUtils: ViewThemeUtils) { + sidebarNavigationView?.let { viewThemeUtils.files.colorNavigationView(it) } + } + + private fun setupQuotaElements(viewThemeUtils: ViewThemeUtils) { + sidebarQuotaView = findQuotaViewById(R.id.sidebar_quota) as? LinearLayout + sidebarQuotaProgressBar = findQuotaViewById(R.id.sidebar_quota_ProgressBar) as? LinearProgressIndicator + sidebarQuotaTextPercentage = findQuotaViewById(R.id.sidebar_quota_percentage) as? TextView + sidebarQuotaTextLink = findQuotaViewById(R.id.sidebar_quota_link) as? TextView + + sidebarQuotaProgressBar?.let { + viewThemeUtils.material.colorProgressBar(it, ColorRole.PRIMARY) + it.trackStopIndicatorSize = 0 + } + sidebarQuotaView?.let { viewThemeUtils.platform.colorViewBackground(it) } + } + + private fun findQuotaViewById(@IdRes id: Int): View? { + val navigationView = hostView.findViewById(R.id.sidebar_view) + val headerView = navigationView?.getHeaderView(0) + return headerView?.findViewById(id) ?: hostView.findViewById(id) + } +} + From b45b00daa25249b1a10e159366a1af431338ac43 Mon Sep 17 00:00:00 2001 From: mykh-hailo Date: Thu, 16 Apr 2026 04:20:10 -0300 Subject: [PATCH 3/3] chore: add screenshot test file Signed-off-by: mykh-hailo --- .../client/DrawerNavigationScreenshotIT.kt | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 app/src/androidTest/java/com/nextcloud/client/DrawerNavigationScreenshotIT.kt diff --git a/app/src/androidTest/java/com/nextcloud/client/DrawerNavigationScreenshotIT.kt b/app/src/androidTest/java/com/nextcloud/client/DrawerNavigationScreenshotIT.kt new file mode 100644 index 000000000000..68d7f20d650f --- /dev/null +++ b/app/src/androidTest/java/com/nextcloud/client/DrawerNavigationScreenshotIT.kt @@ -0,0 +1,117 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.client + +import android.Manifest +import androidx.test.core.app.launchActivity +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.contrib.DrawerActions +import androidx.test.espresso.contrib.NavigationViewActions +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import com.owncloud.android.AbstractIT +import com.owncloud.android.R +import com.owncloud.android.ui.activity.FileDisplayActivity +import com.owncloud.android.ui.fragment.EmptyListState +import com.owncloud.android.utils.ScreenshotTest +import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue +import org.junit.Rule +import org.junit.Test +import androidx.test.rule.GrantPermissionRule + +class DrawerNavigationScreenshotIT : AbstractIT() { + private val testClassName = "com.nextcloud.client.DrawerNavigationScreenshotIT" + + @get:Rule + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.POST_NOTIFICATIONS + ) + + private fun isDrawerLayout(): Boolean = targetContext.resources.getBoolean(R.bool.is_support_drawer) + + private fun prepareStableFileListUi(activity: FileDisplayActivity) { + activity.resetScrolling(true) + activity.hideInfoBox() + activity.listOfFilesFragment?.let { + it.setFabEnabled(false) + it.setEmptyListMessage(EmptyListState.LOADING) + it.isLoading = false + } + } + + @Test + @ScreenshotTest + fun phoneAllFiles() { + assumeFalse(isDrawerLayout()) + launchActivity().use { scenario -> + scenario.onActivity { prepareStableFileListUi(it) } + val screenShotName = createName("${testClassName}_phoneAllFiles", "") + onView(isRoot()).check(matches(isDisplayed())) + scenario.onActivity { screenshotViaName(it, screenShotName) } + } + } + + @Test + @ScreenshotTest + fun phoneDrawerOpen() { + assumeFalse(isDrawerLayout()) + launchActivity().use { scenario -> + scenario.onActivity { prepareStableFileListUi(it) } + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) + val screenShotName = createName("${testClassName}_phoneDrawerOpen", "") + onView(isRoot()).check(matches(isDisplayed())) + scenario.onActivity { screenshotViaName(it, screenShotName) } + } + } + + @Test + @ScreenshotTest + fun phoneNavigateToFavorites() { + assumeFalse(isDrawerLayout()) + launchActivity().use { scenario -> + scenario.onActivity { prepareStableFileListUi(it) } + onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()) + onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_favorites)) + val screenShotName = createName("${testClassName}_phoneNavigateToFavorites", "") + onView(isRoot()).check(matches(isDisplayed())) + scenario.onActivity { screenshotViaName(it, screenShotName) } + // Return from the screen opened by drawer navigation before scenario teardown. + pressBack() + } + } + + @Test + @ScreenshotTest + fun tabletAllFilesSidebar() { + assumeTrue(isDrawerLayout()) + launchActivity().use { scenario -> + scenario.onActivity { prepareStableFileListUi(it) } + val screenShotName = createName("${testClassName}_tabletAllFilesSidebar", "") + onView(isRoot()).check(matches(isDisplayed())) + scenario.onActivity { screenshotViaName(it, screenShotName) } + } + } + + @Test + @ScreenshotTest + fun tabletNavigateToGallery() { + assumeTrue(isDrawerLayout()) + launchActivity().use { scenario -> + scenario.onActivity { prepareStableFileListUi(it) } + onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.nav_gallery)) + val screenShotName = createName("${testClassName}_tabletNavigateToGallery", "") + onView(isRoot()).check(matches(isDisplayed())) + scenario.onActivity { screenshotViaName(it, screenShotName) } + } + } +} +