Merge pull request #14146 from nextcloud/feature-bottom-navigation

Feature - Bottom Navigation View
This commit is contained in:
Tobias Kaminsky 2024-12-17 08:04:57 +01:00 committed by GitHub
commit 586a9b8010
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 149 additions and 43 deletions

View file

@ -163,6 +163,7 @@ class FileUploadWorker(
return Result.success() return Result.success()
} }
@Suppress("NestedBlockDepth")
private fun uploadFiles(totalUploadSize: Int, uploadsPerPage: List<OCUpload>, accountName: String) { private fun uploadFiles(totalUploadSize: Int, uploadsPerPage: List<OCUpload>, accountName: String) {
val user = userAccountManager.getUser(accountName) val user = userAccountManager.getUser(accountName)
setWorkerState(user.get(), uploadsPerPage) setWorkerState(user.get(), uploadsPerPage)

View file

@ -46,6 +46,7 @@ import com.bumptech.glide.load.model.StreamEncoder;
import com.bumptech.glide.load.resource.file.FileToStreamDecoder; import com.bumptech.glide.load.resource.file.FileToStreamDecoder;
import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.SimpleTarget;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.navigation.NavigationView; import com.google.android.material.navigation.NavigationView;
import com.google.android.material.progressindicator.LinearProgressIndicator; import com.google.android.material.progressindicator.LinearProgressIndicator;
@ -123,6 +124,7 @@ import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hct.Hct; import hct.Hct;
import static com.nextcloud.utils.extensions.DrawerActivityExtensionsKt.getMenuItemIdFromTitle; import static com.nextcloud.utils.extensions.DrawerActivityExtensionsKt.getMenuItemIdFromTitle;
@ -155,7 +157,7 @@ public abstract class DrawerActivity extends ToolbarActivity
/** /**
* Reference to the navigation view. * Reference to the navigation view.
*/ */
private NavigationView mNavigationView; private NavigationView drawerNavigationView;
/** /**
* Reference to the navigation view header. * Reference to the navigation view header.
@ -196,6 +198,8 @@ public abstract class DrawerActivity extends ToolbarActivity
private ExternalLinksProvider externalLinksProvider; private ExternalLinksProvider externalLinksProvider;
private ArbitraryDataProvider arbitraryDataProvider; private ArbitraryDataProvider arbitraryDataProvider;
private BottomNavigationView bottomNavigationView;
@Inject @Inject
AppPreferences preferences; AppPreferences preferences;
@ -208,14 +212,14 @@ public abstract class DrawerActivity extends ToolbarActivity
protected void setupDrawer() { protected void setupDrawer() {
mDrawerLayout = findViewById(R.id.drawer_layout); mDrawerLayout = findViewById(R.id.drawer_layout);
mNavigationView = findViewById(R.id.nav_view); drawerNavigationView = findViewById(R.id.nav_view);
if (mNavigationView != null) { if (drawerNavigationView != null) {
// Setting up drawer header // Setting up drawer header
mNavigationViewHeader = mNavigationView.getHeaderView(0); mNavigationViewHeader = drawerNavigationView.getHeaderView(0);
updateHeader(); updateHeader();
setupDrawerMenu(mNavigationView); setupDrawerMenu(drawerNavigationView);
getAndDisplayUserQuota(); getAndDisplayUserQuota();
setupQuotaElement(); setupQuotaElement();
} }
@ -225,6 +229,56 @@ public abstract class DrawerActivity extends ToolbarActivity
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
bottomNavigationView = findViewById(R.id.bottom_navigation);
if (bottomNavigationView != null) {
themeBottomNavigationMenu();
checkAssistantBottomNavigationMenu();
handleBottomNavigationViewClicks();
}
}
private void themeBottomNavigationMenu() {
viewThemeUtils.platform.colorBottomNavigationView(bottomNavigationView);
}
@SuppressFBWarnings("RV")
private void checkAssistantBottomNavigationMenu() {
boolean isAssistantAvailable = getCapabilities().getAssistant().isTrue();
bottomNavigationView
.getMenu()
.findItem(R.id.nav_assistant)
.setVisible(isAssistantAvailable);
}
@SuppressFBWarnings("RV")
private void handleBottomNavigationViewClicks() {
bottomNavigationView.setOnItemSelectedListener(menuItem -> {
menuItemId = menuItem.getItemId();
if (menuItemId == R.id.nav_all_files) {
showFiles(false,false);
if (this instanceof FileDisplayActivity fda) {
fda.browseToRoot();
}
} else if (menuItemId == R.id.nav_favorites) {
handleSearchEvents(new SearchEvent("", SearchRemoteOperation.SearchType.FAVORITE_SEARCH), menuItemId);
} else if (menuItemId == R.id.nav_assistant) {
startComposeActivity(ComposeDestination.AssistantScreen, R.string.assistant_screen_top_bar_title);
} else if (menuItemId == R.id.nav_gallery) {
startPhotoSearch(menuItem.getItemId());
}
// Remove extra icon from the action bar
if (getSupportActionBar() != null) {
getSupportActionBar().setIcon(null);
}
setNavigationViewItemChecked();
return false;
});
} }
/** /**
@ -242,7 +296,7 @@ public abstract class DrawerActivity extends ToolbarActivity
if (menuItemIdFromTitle != null && menuItemIdFromTitle != menuItemId) { if (menuItemIdFromTitle != null && menuItemIdFromTitle != menuItemId) {
menuItemId = menuItemIdFromTitle; menuItemId = menuItemIdFromTitle;
} }
setDrawerMenuItemChecked(); setNavigationViewItemChecked();
isMenuItemChecked = true; isMenuItemChecked = true;
} }
} }
@ -500,7 +554,7 @@ public abstract class DrawerActivity extends ToolbarActivity
private void onNavigationItemClicked(final MenuItem menuItem) { private void onNavigationItemClicked(final MenuItem menuItem) {
int itemId = menuItem.getItemId(); int itemId = menuItem.getItemId();
menuItemId = itemId; menuItemId = itemId;
setDrawerMenuItemChecked(); setNavigationViewItemChecked();
if (itemId == R.id.nav_all_files || itemId == R.id.nav_personal_files) { if (itemId == R.id.nav_all_files || itemId == R.id.nav_personal_files) {
if (this instanceof FileDisplayActivity && if (this instanceof FileDisplayActivity &&
@ -809,9 +863,9 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
private void unsetAllDrawerMenuItems() { private void unsetAllDrawerMenuItems() {
if (mNavigationView != null) { if (drawerNavigationView != null) {
mNavigationView.getMenu(); drawerNavigationView.getMenu();
Menu menu = mNavigationView.getMenu(); Menu menu = drawerNavigationView.getMenu();
for (int i = 0; i < menu.size(); i++) { for (int i = 0; i < menu.size(); i++) {
menu.getItem(i).setChecked(false); menu.getItem(i).setChecked(false);
} }
@ -879,29 +933,29 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
/** /**
* checks/highlights the provided menu item if the drawer has been initialized and the menu item exists. * Sets the menu item as checked in both the drawer and bottom navigation views, if applicable.
*
*/ */
public void setDrawerMenuItemChecked() { @SuppressFBWarnings("RV")
if (mNavigationView == null) { public void setNavigationViewItemChecked() {
return; if (drawerNavigationView != null) {
MenuItem menuItem = drawerNavigationView.getMenu().findItem(menuItemId);
if (menuItem != null && !menuItem.isChecked()) {
viewThemeUtils.platform.colorNavigationView(drawerNavigationView);
menuItem.setChecked(true);
}
} }
MenuItem menuItem = mNavigationView.getMenu().findItem(menuItemId); if (bottomNavigationView != null) {
MenuItem menuItem = bottomNavigationView.getMenu().findItem(menuItemId);
if (menuItem == null) { // Don't highlight assistant bottom navigation item because Assistant screen doesn't have bottom navigation bar
Log_OC.w(TAG, "setDrawerMenuItemChecked has been called with invalid menu-item-ID"); if (menuItem != null && !menuItem.isChecked() && menuItem.getItemId() != R.id.nav_assistant) {
return; menuItem.setChecked(true);
} }
if (menuItem.isChecked()) {
return;
} }
Log_OC.d(TAG, "New menu item is: " + menuItemId); Log_OC.d(TAG, "New menu item is: " + menuItemId);
viewThemeUtils.platform.colorNavigationView(mNavigationView);
menuItem.setChecked(true);
} }
/** /**
@ -971,14 +1025,14 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
private void updateExternalLinksInDrawer() { private void updateExternalLinksInDrawer() {
if (mNavigationView != null && MDMConfig.INSTANCE.externalSiteSupport(this)) { if (drawerNavigationView != null && MDMConfig.INSTANCE.externalSiteSupport(this)) {
mNavigationView.getMenu().removeGroup(R.id.drawer_menu_external_links); drawerNavigationView.getMenu().removeGroup(R.id.drawer_menu_external_links);
int greyColor = ContextCompat.getColor(this, R.color.drawer_menu_icon); int greyColor = ContextCompat.getColor(this, R.color.drawer_menu_icon);
for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) { for (final ExternalLink link : externalLinksProvider.getExternalLink(ExternalLinkType.LINK)) {
int id = mNavigationView.getMenu().add(R.id.drawer_menu_external_links, int id = drawerNavigationView.getMenu().add(R.id.drawer_menu_external_links,
MENU_ITEM_EXTERNAL_LINK + link.getId(), MENU_ORDER_EXTERNAL_LINKS, link.getName()) MENU_ITEM_EXTERNAL_LINK + link.getId(), MENU_ORDER_EXTERNAL_LINKS, link.getName())
.setCheckable(true).getItemId(); .setCheckable(true).getItemId();
MenuSimpleTarget target = new MenuSimpleTarget<Drawable>(id) { MenuSimpleTarget target = new MenuSimpleTarget<Drawable>(id) {
@ -1005,7 +1059,7 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
private void setExternalLinkIcon(int id, Drawable drawable, int greyColor) { private void setExternalLinkIcon(int id, Drawable drawable, int greyColor) {
MenuItem menuItem = mNavigationView.getMenu().findItem(id); MenuItem menuItem = drawerNavigationView.getMenu().findItem(id);
if (menuItem != null) { if (menuItem != null) {
if (drawable != null) { if (drawable != null) {
@ -1037,7 +1091,7 @@ public abstract class DrawerActivity extends ToolbarActivity
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) { public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState);
mIsAccountChooserActive = savedInstanceState.getBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, false); mIsAccountChooserActive = savedInstanceState.getBoolean(KEY_IS_ACCOUNT_CHOOSER_ACTIVE, false);
setDrawerMenuItemChecked(); setNavigationViewItemChecked();
} }
@Override @Override
@ -1312,10 +1366,10 @@ public abstract class DrawerActivity extends ToolbarActivity
} }
private void handleNavItemClickEvent(@IdRes int menuItemId) { private void handleNavItemClickEvent(@IdRes int menuItemId) {
if (mNavigationView == null) { if (drawerNavigationView == null) {
mNavigationView = findViewById(R.id.nav_view); drawerNavigationView = findViewById(R.id.nav_view);
} }
Menu navMenu = mNavigationView.getMenu(); Menu navMenu = drawerNavigationView.getMenu();
onNavigationItemClicked(navMenu.findItem(menuItemId)); onNavigationItemClicked(navMenu.findItem(menuItemId));
} }
} }

View file

@ -1249,7 +1249,7 @@ public class FileDisplayActivity extends FileActivity
menuItemId = R.id.nav_all_files; menuItemId = R.id.nav_all_files;
} }
setDrawerMenuItemChecked(); setNavigationViewItemChecked();
if (MainApp.isOnlyOnDevice()) { if (MainApp.isOnlyOnDevice()) {
setupToolbar(); setupToolbar();

View file

@ -1839,7 +1839,10 @@ public class OCFileListFragment extends ExtendedListFragment implements
// avoid calling api multiple times if async task is already executing // avoid calling api multiple times if async task is already executing
if (remoteOperationAsyncTask != null && remoteOperationAsyncTask.getStatus() != AsyncTask.Status.FINISHED) { if (remoteOperationAsyncTask != null && remoteOperationAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
Log_OC.d(TAG, "OCFileListSearchAsyncTask already running skipping new api call for search event: " + searchEvent.getSearchType()); if (searchEvent != null) {
Log_OC.d(TAG, "OCFileListSearchAsyncTask already running skipping new api call for search event: " + searchEvent.getSearchType());
}
return; return;
} }

View file

@ -387,6 +387,7 @@ class TrashbinActivity :
} }
} }
@Suppress("ReturnCount")
private fun onFileActionChosen(@IdRes itemId: Int, checkedFiles: Set<TrashbinFile>): Boolean { private fun onFileActionChosen(@IdRes itemId: Int, checkedFiles: Set<TrashbinFile>): Boolean {
if (checkedFiles.isEmpty()) { if (checkedFiles.isEmpty()) {
return false return false

View file

@ -11,7 +11,6 @@ import android.content.res.Resources;
import android.view.Menu; import android.view.Menu;
import com.nextcloud.client.account.User; import com.nextcloud.client.account.User;
import com.owncloud.android.MainApp;
import com.owncloud.android.R; import com.owncloud.android.R;
import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.lib.resources.status.OCCapability;

View file

@ -596,7 +596,7 @@ class EncryptionUtilsV2 {
} }
@Throws(IllegalStateException::class) @Throws(IllegalStateException::class)
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught", "LongMethod")
fun parseAnyMetadata( fun parseAnyMetadata(
metadataResponse: MetadataResponse, metadataResponse: MetadataResponse,
user: User, user: User,

View file

@ -45,18 +45,26 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation"
app:labelVisibilityMode="labeled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="@menu/bottom_navigation_menu"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_main" android:id="@+id/fab_main"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_gravity="end|bottom" android:layout_gravity="end|bottom"
android:layout_marginEnd="@dimen/standard_margin" android:layout_marginEnd="@dimen/standard_margin"
android:layout_marginBottom="@dimen/standard_margin" android:layout_marginBottom="@dimen/floating_action_button_bottom_margin"
android:contentDescription="@string/fab_label" android:contentDescription="@string/fab_label"
android:visibility="gone"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
app:srcCompat="@drawable/ic_plus" app:srcCompat="@drawable/ic_plus"
app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior"
android:visibility="gone"
tools:visibility="visible" /> tools:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Nextcloud - Android Client
~
~ SPDX-FileCopyrightText: 2024 Alper Ozturk <alper.ozturk@nextcloud.com>
~ SPDX-License-Identifier: AGPL-3.0-or-later
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/nav_all_files"
android:enabled="true"
android:icon="@drawable/folder"
android:title="@string/bottom_navigation_menu_files_label"/>
<item
android:id="@+id/nav_favorites"
android:enabled="true"
android:icon="@drawable/nav_favorites"
android:title="@string/bottom_navigation_menu_favorites_label"/>
<item
android:id="@+id/nav_assistant"
android:enabled="true"
android:icon="@drawable/ic_assistant"
android:title="@string/bottom_navigation_menu_assistant_label"/>
<item
android:id="@+id/nav_gallery"
android:enabled="true"
android:icon="@drawable/nav_photos"
android:title="@string/bottom_navigation_menu_media_label"/>
</menu>

View file

@ -34,6 +34,7 @@
<dimen name="standard_margin">16dp</dimen> <dimen name="standard_margin">16dp</dimen>
<dimen name="standard_double_margin">32dp</dimen> <dimen name="standard_double_margin">32dp</dimen>
<dimen name="standard_half_margin">8dp</dimen> <dimen name="standard_half_margin">8dp</dimen>
<dimen name="floating_action_button_bottom_margin">100dp</dimen>
<dimen name="standard_quarter_margin">4dp</dimen> <dimen name="standard_quarter_margin">4dp</dimen>
<dimen name="grid_layout_item_size">10dp</dimen> <dimen name="grid_layout_item_size">10dp</dimen>
<dimen name="grid_layout_file_features_margin_end">24dp</dimen> <dimen name="grid_layout_file_features_margin_end">24dp</dimen>

View file

@ -6,6 +6,12 @@
~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only ~ SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
--> -->
<resources> <resources>
<string name="bottom_navigation_menu_files_label">All files</string>
<string name="bottom_navigation_menu_favorites_label">Favorites</string>
<string name="bottom_navigation_menu_assistant_label">Assistant</string>
<string name="bottom_navigation_menu_media_label">Media</string>
<string name="about_android">%1$s Android app</string> <string name="about_android">%1$s Android app</string>
<string name="about_version">version %1$s</string> <string name="about_version">version %1$s</string>
<string name="about_version_with_build">version %1$s, build #%2$s</string> <string name="about_version_with_build">version %1$s, build #%2$s</string>