diff --git a/app/src/main/java/com/nmc/android/utils/DisplayUtils.kt b/app/src/main/java/com/nmc/android/utils/DisplayUtils.kt new file mode 100644 index 000000000000..f58e93c4252a --- /dev/null +++ b/app/src/main/java/com/nmc/android/utils/DisplayUtils.kt @@ -0,0 +1,18 @@ +package com.nmc.android.utils + +import android.content.res.Configuration +import com.owncloud.android.MainApp +import com.owncloud.android.R + +object DisplayUtils { + + @JvmStatic + fun isShowDividerForList(): Boolean = isTablet() || isLandscapeOrientation() + + @JvmStatic + fun isTablet(): Boolean = MainApp.getAppContext().resources.getBoolean(R.bool.isTablet) + + @JvmStatic + fun isLandscapeOrientation(): Boolean = + MainApp.getAppContext().resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE +} \ No newline at end of file diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java index 7ebc62326ae5..8c247e9fb0d4 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.view.Menu; @@ -56,11 +57,13 @@ import com.owncloud.android.operations.CheckCurrentCredentialsOperation; import com.owncloud.android.ui.adapter.UploadListAdapter; import com.owncloud.android.ui.decoration.MediaGridItemDecoration; +import com.owncloud.android.ui.decoration.SimpleListItemDividerDecoration; import com.owncloud.android.utils.FilesSyncHelper; import com.owncloud.android.utils.theme.ViewThemeUtils; import javax.inject.Inject; +import androidx.annotation.NonNull; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.recyclerview.widget.GridLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -108,6 +111,8 @@ public class UploadListActivity extends FileActivity { private UploadListLayoutBinding binding; + private SimpleListItemDividerDecoration simpleListItemDividerDecoration; + public static Intent createIntent(OCFile file, User user, Integer flag, Context context) { Intent intent = new Intent(context, UploadListActivity.class); if (flag != null) { @@ -172,6 +177,8 @@ private void setupContent() { int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing); binding.list.addItemDecoration(new MediaGridItemDecoration(spacing)); binding.list.setLayoutManager(lm); + simpleListItemDividerDecoration = new SimpleListItemDividerDecoration(this, R.drawable.item_divider, true); + addListItemDecorator(); binding.list.setAdapter(uploadListAdapter); viewThemeUtils.androidx.themeSwipeRefreshLayout(swipeListRefreshLayout); @@ -180,6 +187,23 @@ private void setupContent() { loadItems(); } + private void addListItemDecorator() { + if (com.nmc.android.utils.DisplayUtils.isShowDividerForList()) { + //check and remove divider item decorator if exist then add item decorator + removeListDividerDecorator(); + binding.list.addItemDecoration(simpleListItemDividerDecoration); + } + } + + /** + * method to remove the divider item decorator + */ + private void removeListDividerDecorator() { + if (binding.list.getItemDecorationCount() > 0) { + binding.list.removeItemDecoration(simpleListItemDividerDecoration); + } + } + private void loadItems() { uploadListAdapter.loadUploadItemsFromDb(); @@ -362,4 +386,20 @@ public void onReceive(Context context, Intent intent) { }); } } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + //this should only run when device is not tablet because we are adding dividers in tablet for both the + // orientations + if (!com.nmc.android.utils.DisplayUtils.isTablet()) { + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + //add the divider item decorator when orientation is landscape + addListItemDecorator(); + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + //remove the divider item decorator when orientation is portrait + removeListDividerDecorator(); + } + } + } } diff --git a/app/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java b/app/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java index f4f705ed095b..6dc9de8a8bff 100644 --- a/app/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java +++ b/app/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java @@ -28,6 +28,7 @@ import android.util.DisplayMetrics; import android.view.View; +import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.RecyclerView; @@ -39,7 +40,8 @@ public class SimpleListItemDividerDecoration extends DividerItemDecoration { private final Rect bounds = new Rect(); private Drawable divider; - private int leftPadding; + private int leftPadding = 0; + private boolean hasFooter; /** * Default divider will be used @@ -52,6 +54,17 @@ public SimpleListItemDividerDecoration(Context context) { styledAttributes.recycle(); } + /** + * Custom divider will be used + * + * @param hasFooter if recyclerview has footer and no divider should be shown for footer then pass true else false + */ + public SimpleListItemDividerDecoration(Context context, int resId, boolean hasFooter) { + super(context, DividerItemDecoration.VERTICAL); + this.hasFooter = hasFooter; + divider = ContextCompat.getDrawable(context, resId); + } + @Override public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) { canvas.save(); @@ -65,7 +78,12 @@ public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) right = parent.getWidth(); } - final int childCount = parent.getChildCount(); + int childCount = parent.getChildCount(); + + if (hasFooter) { + childCount = childCount - 1; + } + for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, bounds); diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 01a6070baea8..71c1b38d87e1 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -27,6 +27,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -41,6 +42,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver; import android.widget.AbsListView; import android.widget.Toast; @@ -59,6 +61,7 @@ import com.nextcloud.client.jobs.BackgroundJobManager; import com.nextcloud.client.network.ClientFactory; import com.nextcloud.client.preferences.AppPreferences; +import com.nextcloud.client.preferences.AppPreferencesImpl; import com.nextcloud.client.utils.Throttler; import com.nextcloud.common.NextcloudClient; import com.nextcloud.ui.fileactions.FileActionsBottomSheet; @@ -90,6 +93,8 @@ import com.owncloud.android.ui.activity.UploadFilesActivity; import com.owncloud.android.ui.adapter.CommonOCFileListAdapterInterface; import com.owncloud.android.ui.adapter.OCFileListAdapter; +import com.owncloud.android.ui.decoration.MediaGridItemDecoration; +import com.owncloud.android.ui.decoration.SimpleListItemDividerDecoration; import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment; import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; @@ -230,6 +235,9 @@ public class OCFileListFragment extends ExtendedListFragment implements protected String mLimitToMimeType; private FloatingActionButton mFabMain; + private SimpleListItemDividerDecoration simpleListItemDividerDecoration; + private MediaGridItemDecoration mediaGridItemDecoration; + @Inject DeviceInfo deviceInfo; protected enum MenuItemAddRemove { @@ -243,6 +251,13 @@ protected enum MenuItemAddRemove { private List mOriginalMenuItems = new ArrayList<>(); + private int maxColumnSizeLandscape = 5; + + //this variable will help us to provide number of span count for grid view + //the width for single item is approx to 360 + private static final int GRID_ITEM_DEFAULT_WIDTH = 360; + private static final int DEFAULT_FALLBACK_SPAN_COUNT = 1; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -438,6 +453,10 @@ protected void setAdapter(Bundle args) { viewThemeUtils ); + simpleListItemDividerDecoration = new SimpleListItemDividerDecoration(getContext(), R.drawable.item_divider, true); + int spacing = getResources().getDimensionPixelSize(R.dimen.media_grid_spacing); + mediaGridItemDecoration = new MediaGridItemDecoration(spacing); + setRecyclerViewAdapter(mAdapter); fastScrollUtils.applyFastScroll(getRecyclerView()); @@ -554,7 +573,7 @@ public void uploadFiles() { getActivity(), ((FileActivity) getActivity()).getUser().orElseThrow(RuntimeException::new), FileDisplayActivity.REQUEST_CODE__SELECT_FILES_FROM_FILE_SYSTEM, - getCurrentFile().isEncrypted() + getCurrentFile().isEncrypted() ); } @@ -1440,6 +1459,7 @@ public void switchToListView() { if (isGridEnabled()) { switchLayoutManager(false); } + addRemoveRecyclerViewItemDecorator(); } public void setGridAsPreferred() { @@ -1451,6 +1471,33 @@ public void switchToGridView() { if (!isGridEnabled()) { switchLayoutManager(true); } + addRemoveRecyclerViewItemDecorator(); + } + + private void addRemoveRecyclerViewItemDecorator() { + if (getRecyclerView().getLayoutManager() instanceof GridLayoutManager) { + removeItemDecorator(); + if (getRecyclerView().getItemDecorationCount() == 0) { + getRecyclerView().addItemDecoration(mediaGridItemDecoration); + int padding = getResources().getDimensionPixelSize(R.dimen.grid_recyclerview_padding); + getRecyclerView().setPadding(padding, padding, padding, padding); + } + } else { + removeItemDecorator(); + if (getRecyclerView().getItemDecorationCount() == 0 && com.nmc.android.utils.DisplayUtils.isShowDividerForList()) { + getRecyclerView().addItemDecoration(simpleListItemDividerDecoration); + getRecyclerView().setPadding(0, 0, 0, 0); + } + } + } + + /** + * method to remove the item decorator + */ + private void removeItemDecorator() { + while (getRecyclerView().getItemDecorationCount() > 0) { + getRecyclerView().removeItemDecorationAt(0); + } } public void switchLayoutManager(boolean grid) { @@ -1481,12 +1528,40 @@ public int getSpanSize(int position) { } getRecyclerView().setLayoutManager(layoutManager); + updateSpanCount(getResources().getConfiguration()); getRecyclerView().scrollToPosition(position); getAdapter().setGridView(grid); getRecyclerView().setAdapter(getAdapter()); getAdapter().notifyDataSetChanged(); } + /** + * method will calculate the number of spans required for grid item and will update the span accordingly + * + * @param isGrid + */ + private void calculateAndUpdateSpanCount(boolean isGrid) { + getRecyclerView().getViewTreeObserver().addOnGlobalLayoutListener( + new ViewTreeObserver.OnGlobalLayoutListener() { + @Override + public void onGlobalLayout() { + getRecyclerView().getViewTreeObserver().removeOnGlobalLayoutListener(this); + if (isGrid) { + int viewWidth = getRecyclerView().getMeasuredWidth(); + int newSpanCount = viewWidth / GRID_ITEM_DEFAULT_WIDTH; + RecyclerView.LayoutManager layoutManager = getRecyclerView().getLayoutManager(); + if (layoutManager instanceof GridLayoutManager) { + if (newSpanCount < 1) { + newSpanCount = DEFAULT_FALLBACK_SPAN_COUNT; + } + ((GridLayoutManager) layoutManager).setSpanCount(newSpanCount); + layoutManager.requestLayout(); + } + } + } + }); + } + public CommonOCFileListAdapterInterface getCommonAdapter() { return mAdapter; } @@ -2024,4 +2099,52 @@ public void setFabEnabled(final boolean enabled) { public boolean isEmpty() { return mAdapter == null || mAdapter.isEmpty(); } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (getAdapter() != null) { + getAdapter().notifyDataSetChanged(); + } + updateSpanCount(newConfig); + } + + /** + * method will update the span count on basis of device orientation for the file listing + * + * @param newConfig current configuration + */ + private void updateSpanCount(Configuration newConfig) { + //this should only run when current view is not media gallery + if (getAdapter() != null) { + int maxColumnSize = (int) AppPreferencesImpl.DEFAULT_GRID_COLUMN; + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + //add the divider item decorator when orientation is landscape and device is not tablet + //because we don't have to add divider again as it is already added + if (!com.nmc.android.utils.DisplayUtils.isTablet()) { + addRemoveRecyclerViewItemDecorator(); + } + maxColumnSize = maxColumnSizeLandscape; + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + //remove the divider item decorator when orientation is portrait and when device is not tablet + //because we have to show divider in both landscape and portrait mode + if (!com.nmc.android.utils.DisplayUtils.isTablet()) { + removeItemDecorator(); + } + maxColumnSize = (int) AppPreferencesImpl.DEFAULT_GRID_COLUMN; + } + + if (isGridEnabled()) { + //for tablet calculate size on the basis of screen width + if (com.nmc.android.utils.DisplayUtils.isTablet()) { + calculateAndUpdateSpanCount(true); + } else { + //and for phones directly show the hardcoded column size + if (getRecyclerView().getLayoutManager() instanceof GridLayoutManager) { + ((GridLayoutManager) getRecyclerView().getLayoutManager()).setSpanCount(maxColumnSize); + } + } + } + } + } } diff --git a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java index 7a823e21fb7b..6b16b28e4ad5 100644 --- a/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/trashbin/TrashbinActivity.java @@ -24,6 +24,7 @@ package com.owncloud.android.ui.trashbin; import android.content.Intent; +import android.content.res.Configuration; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; @@ -45,6 +46,7 @@ import com.owncloud.android.ui.EmptyRecyclerView; import com.owncloud.android.ui.activity.DrawerActivity; import com.owncloud.android.ui.adapter.TrashbinListAdapter; +import com.owncloud.android.ui.decoration.SimpleListItemDividerDecoration; import com.owncloud.android.ui.dialog.SortingOrderDialogFragment; import com.owncloud.android.ui.interfaces.TrashbinActivityInterface; import com.owncloud.android.utils.DisplayUtils; @@ -55,6 +57,7 @@ import javax.inject.Inject; +import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.core.content.res.ResourcesCompat; import androidx.recyclerview.widget.LinearLayoutManager; @@ -84,6 +87,8 @@ public class TrashbinActivity extends DrawerActivity implements private boolean active; private TrashbinActivityBinding binding; + private SimpleListItemDividerDecoration simpleListItemDividerDecoration; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -150,6 +155,8 @@ private void setupContent() { recyclerView.setHasFixedSize(true); recyclerView.setHasFooter(true); recyclerView.setLayoutManager(new LinearLayoutManager(this)); + simpleListItemDividerDecoration = new SimpleListItemDividerDecoration(this, R.drawable.item_divider, true); + addListItemDecorator(); viewThemeUtils.androidx.themeSwipeRefreshLayout(binding.swipeContainingList); binding.swipeContainingList.setOnRefreshListener(this::loadFolder); @@ -166,6 +173,23 @@ private void setupContent() { loadFolder(); } + private void addListItemDecorator() { + if (com.nmc.android.utils.DisplayUtils.isShowDividerForList()) { + //check and remove divider item decorator if exist then add item decorator + removeListDividerDecorator(); + binding.list.addItemDecoration(simpleListItemDividerDecoration); + } + } + + /** + * method to remove the divider item decorator + */ + private void removeListDividerDecorator() { + if (binding.list.getItemDecorationCount() > 0) { + binding.list.removeItemDecoration(simpleListItemDividerDecoration); + } + } + protected void loadFolder() { if (trashbinListAdapter.getItemCount() > EMPTY_LIST_COUNT) { binding.swipeContainingList.setRefreshing(true); @@ -331,4 +355,20 @@ public void showError(int message) { binding.emptyList.emptyListView.setVisibility(View.VISIBLE); } } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + //this should only run when device is not tablet because we are adding dividers in tablet for both the + // orientations + if (!com.nmc.android.utils.DisplayUtils.isTablet()) { + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + //add the divider item decorator when orientation is landscape + addListItemDecorator(); + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + //remove the divider item decorator when orientation is portrait + removeListDividerDecorator(); + } + } + } } diff --git a/app/src/main/res/drawable/item_divider.xml b/app/src/main/res/drawable/item_divider.xml new file mode 100644 index 000000000000..9f742e91d67c --- /dev/null +++ b/app/src/main/res/drawable/item_divider.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 08bf64d552ad..88a29668c4cd 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -48,4 +48,70 @@ #1E1E1E @android:color/white + + + #FFFFFF + @color/grey_30 + @color/grey_30 + #CCCCCC + @color/grey_70 + @color/grey_80 + #2D2D2D + @color/grey_70 + @color/grey_70 + + + @color/grey_80 + @color/grey_0 + + + @color/grey_80 + @color/grey_0 + + + @color/grey_60 + @color/grey_0 + @color/grey_0 + @color/grey_30 + #FFFFFF + @color/grey_30 + @color/grey_80 + #FFFFFF + + + @color/grey_80 + @color/grey_30 + @color/grey_0 + + + @color/grey_80 + @color/grey_0 + @color/grey_80 + + + @color/grey_70 + @color/grey_60 + @color/grey_70 + @color/grey_60 + + + @color/grey_70 + @color/grey_70 + + + #FFFFFF + @color/grey_30 + @color/grey_0 + @color/grey_0 + @color/grey_0 + @color/grey_0 + @color/grey_60 + @color/grey_0 + #FFFFFF + + + #121212 + @color/grey_0 + @color/grey_80 + @color/grey_80 diff --git a/app/src/main/res/values-sw480dp/bool.xml b/app/src/main/res/values-sw480dp/bool.xml new file mode 100644 index 000000000000..8e66f10e898c --- /dev/null +++ b/app/src/main/res/values-sw480dp/bool.xml @@ -0,0 +1,4 @@ + + + true + diff --git a/app/src/main/res/values/bool.xml b/app/src/main/res/values/bool.xml new file mode 100644 index 000000000000..c2dcd8baf0ea --- /dev/null +++ b/app/src/main/res/values/bool.xml @@ -0,0 +1,4 @@ + + + false + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f844b3156f33..1ddf1a93ca08 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -81,4 +81,95 @@ @android:color/white #666666 #A5A5A5 + + + #191919 + @color/primary + #191919 + #191919 + @color/grey_30 + @android:color/white + #FFFFFF + @color/grey_0 + #CCCCCC + #77c4ff + #B3FFFFFF + @color/grey_10 + + + #101010 + #F2F2F2 + #E5E5E5 + #B2B2B2 + #666666 + #4C4C4C + #333333 + + + @color/design_snackbar_background_color + @color/white + + + #FFFFFF + #191919 + + + @color/grey_0 + #191919 + @color/primary + #191919 + @color/primary + @color/grey_30 + @color/white + #191919 + + + #FFFFFF + #191919 + #191919 + + + #FFFFFF + #191919 + #FFFFFF + + + @color/primary + #F399C7 + @color/grey_0 + @color/grey_0 + #FFFFFF + @color/grey_30 + @color/grey_0 + @color/grey_0 + + + @color/primary + @color/grey_30 + @color/grey_30 + #CCCCCC + + + #191919 + @color/grey_30 + #191919 + #191919 + #191919 + #191919 + @color/grey_30 + #191919 + #000000 + #191919 + #F6E5EB + #C16F81 + #0D39DF + #0099ff + + + @color/grey_0 + #191919 + @color/grey_0 + @color/grey_30 + #77b6bb + #5077b6bb diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000000..cc9e25255a10 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,31 @@ + + + 4dp + 16dp + 24dp + 6dp + 18sp + 15sp + 15dp + 56dp + 86dp + 80dp + 11sp + 30dp + 55dp + 258dp + 17sp + 20dp + 160dp + 50dp + 150dp + 55dp + 48dp + 48dp + 24dp + 26dp + 20sp + 145dp + 1dp + 13sp + \ No newline at end of file