From cd3d8dca1217727a24d239ddaf6da99abd095b81 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 11 Aug 2019 23:40:01 +0200 Subject: [PATCH 01/20] start to implement sticky header behavior Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ui/activities/ActivitiesActivity.java | 9 ++-- .../ui/activities/ActivityListElement.java | 4 ++ .../ui/activities/ActivityListHeader.java | 14 +++++++ .../ActivityListItemDecoration.java | 34 +++++++++++++++ .../ui/adapter/ActivityListAdapter.java | 42 ++++++++++--------- 5 files changed, 79 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java create mode 100644 src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java create mode 100644 src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 525e10773e70..152f24c7ba92 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -60,7 +60,7 @@ import butterknife.ButterKnife; import butterknife.Unbinder; -public class ActivitiesActivity extends FileActivity implements ActivityListInterface, ActivitiesContract.View { +public class ActivitiesActivity extends FileActivity implements ActivityListInterface, ActivityListElement, ActivitiesContract.View { private static final String TAG = ActivitiesActivity.class.getSimpleName(); @BindView(R.id.empty_list_view) @@ -104,9 +104,9 @@ public class ActivitiesActivity extends FileActivity implements ActivityListInte protected void onCreate(Bundle savedInstanceState) { Log_OC.v(TAG, "onCreate() start"); super.onCreate(savedInstanceState); - mActionListener = new ActivitiesPresenter(activitiesRepository, filesRepository, this); + setContentView(R.layout.activity_list_layout); setContentView(R.layout.activity_list_layout); unbinder = ButterKnife.bind(this); @@ -165,7 +165,7 @@ public void showFiles(boolean onDeviceOnly) { private void setupContent() { emptyContentIcon.setImageResource(R.drawable.ic_activity_light_grey); emptyContentProgressBar.getIndeterminateDrawable().setColorFilter(ThemeUtils.primaryAccentColor(this), - PorterDuff.Mode.SRC_IN); + PorterDuff.Mode.SRC_IN); FileDataStorageManager storageManager = new FileDataStorageManager(getAccount(), getContentResolver()); adapter = new ActivityListAdapter(this, getUserAccountManager(), this, storageManager, getCapabilities(), false); @@ -174,6 +174,7 @@ private void setupContent() { LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); + recyclerView.addItemDecoration(new ActivityListItemDecoration()); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -186,7 +187,7 @@ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { // synchronize loading state when item count changes if (!isLoadingActivities && (totalItemCount - visibleItemCount) <= (firstVisibleItemIndex + 5) - && nextPageUrl != null && !nextPageUrl.isEmpty()) { + && nextPageUrl != null && !nextPageUrl.isEmpty()) { // Almost reached the end, continue to load new activities mActionListener.loadActivities(nextPageUrl); } diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java new file mode 100644 index 000000000000..8f04141897f7 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java @@ -0,0 +1,4 @@ +package com.owncloud.android.ui.activities; + +public interface ActivityListElement { +} diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java new file mode 100644 index 000000000000..0ee30e93df57 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java @@ -0,0 +1,14 @@ +package com.owncloud.android.ui.activities; + +public class ActivityListHeader implements ActivityListElement { + final private String headline; + + public ActivityListHeader(String headline) { + this.headline = headline; + } + + public String getHeadline() { + return headline; + } + +} diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java new file mode 100644 index 000000000000..16085f1cb3a6 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -0,0 +1,34 @@ +package com.owncloud.android.ui.activities; + +import android.content.Context; +import android.graphics.Canvas; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +public class ActivityListItemDecoration extends RecyclerView.ItemDecoration { + private final String TAG = this.getClass().getSimpleName(); + @Override + public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + super.onDrawOver(c, parent, state); + + View topChild = parent.getChildAt(0); + View currentHeader = topChild; + + + + if (currentHeader != null) { + drawHeader(c, currentHeader); + Log.d(TAG,"Attach new Header" ); + } + } + + private void drawHeader(Canvas c, View header) { + c.save(); + c.translate(0, 0); + header.draw(c); + c.restore(); + } +} diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index 35cd3420a366..4afc55c454bd 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -65,6 +65,7 @@ import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.ui.activities.ActivityListHeader; import com.owncloud.android.ui.interfaces.ActivityListInterface; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -141,7 +142,7 @@ public void setActivityItems(List activityItems, OwnCloudClient client, values.add(activity); } else { sTime = time; - values.add(sTime); + values.add(new ActivityListHeader(sTime)); values.add(activity); } } @@ -173,11 +174,11 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } if (activity.getRichSubjectElement() != null && - !TextUtils.isEmpty(activity.getRichSubjectElement().getRichSubject())) { + !TextUtils.isEmpty(activity.getRichSubjectElement().getRichSubject())) { activityViewHolder.subject.setVisibility(View.VISIBLE); activityViewHolder.subject.setMovementMethod(LinkMovementMethod.getInstance()); activityViewHolder.subject.setText(addClickablePart(activity.getRichSubjectElement()), - TextView.BufferType.SPANNABLE); + TextView.BufferType.SPANNABLE); activityViewHolder.subject.setVisibility(View.VISIBLE); } else if (!TextUtils.isEmpty(activity.getSubject())) { activityViewHolder.subject.setVisibility(View.VISIBLE); @@ -198,7 +199,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } if (activity.getRichSubjectElement() != null && - activity.getRichSubjectElement().getRichObjectList().size() > 0) { + activity.getRichSubjectElement().getRichObjectList().size() > 0) { activityViewHolder.list.setVisibility(View.VISIBLE); activityViewHolder.list.removeAllViews(); @@ -237,7 +238,8 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } } else { ActivityViewHeaderHolder activityViewHeaderHolder = (ActivityViewHeaderHolder) holder; - activityViewHeaderHolder.title.setText((String) values.get(position)); + ActivityListHeader header = (ActivityListHeader) values.get(position); + activityViewHeaderHolder.title.setText((String) header.getHeadline()); } } @@ -325,23 +327,23 @@ private void setBitmap(OCFile file, ImageView fileIcon, boolean isDetailView) { private void downloadIcon(String icon, ImageView itemViewType) { GenericRequestBuilder requestBuilder = Glide.with(context) - .using(Glide.buildStreamModelLoader(Uri.class, context), InputStream.class) - .from(Uri.class) - .as(SVG.class) - .transcode(new SvgDrawableTranscoder(), PictureDrawable.class) - .sourceEncoder(new StreamEncoder()) - .cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder())) - .decoder(new SvgDecoder()) - .placeholder(R.drawable.ic_activity) - .error(R.drawable.ic_activity) - .animate(android.R.anim.fade_in) - .listener(new SvgSoftwareLayerSetter<>()); + .using(Glide.buildStreamModelLoader(Uri.class, context), InputStream.class) + .from(Uri.class) + .as(SVG.class) + .transcode(new SvgDrawableTranscoder(), PictureDrawable.class) + .sourceEncoder(new StreamEncoder()) + .cacheDecoder(new FileToStreamDecoder<>(new SvgDecoder())) + .decoder(new SvgDecoder()) + .placeholder(R.drawable.ic_activity) + .error(R.drawable.ic_activity) + .animate(android.R.anim.fade_in) + .listener(new SvgSoftwareLayerSetter<>()); Uri uri = Uri.parse(icon); requestBuilder - .diskCacheStrategy(DiskCacheStrategy.SOURCE) - .load(uri) - .into(itemViewType); + .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .load(uri) + .into(itemViewType); } private SpannableStringBuilder addClickablePart(RichElement richElement) { @@ -418,7 +420,7 @@ private int getThumbnailDimension() { CharSequence getHeaderDateString(Context context, long modificationTimestamp) { if ((System.currentTimeMillis() - modificationTimestamp) < DateUtils.WEEK_IN_MILLIS) { return DisplayUtils.getRelativeDateTimeString(context, modificationTimestamp, DateUtils.DAY_IN_MILLIS, - DateUtils.WEEK_IN_MILLIS, 0); + DateUtils.WEEK_IN_MILLIS, 0); } else { String pattern = "EEEE, MMMM d"; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) { From a184345c1a08dd8d8f2c04ceefa85ea6efa9b27b Mon Sep 17 00:00:00 2001 From: alexp Date: Wed, 14 Aug 2019 17:24:25 +0200 Subject: [PATCH 02/20] first implementation of sticky header logic. Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ActivityListItemDecoration.java | 20 ++++++++++++++----- .../ui/adapter/ActivityListAdapter.java | 5 +++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java index 16085f1cb3a6..f6d1fa93a2ed 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -1,28 +1,38 @@ package com.owncloud.android.ui.activities; +import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.util.Log; import android.view.View; +import com.owncloud.android.ui.adapter.ActivityListAdapter; + import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; public class ActivityListItemDecoration extends RecyclerView.ItemDecoration { private final String TAG = this.getClass().getSimpleName(); + private View currentHeader; + @Override public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { super.onDrawOver(c, parent, state); - View topChild = parent.getChildAt(0); - View currentHeader = topChild; + View topChild = parent.getChildAt(0); + int topChildPosition = parent.getChildAdapterPosition(topChild); + if (topChildPosition == -1) { + return; + } + ActivityListAdapter adapter = (ActivityListAdapter) parent.getAdapter(); + Object topElement = adapter.getActivityAtByPosition(topChildPosition); - if (currentHeader != null) { - drawHeader(c, currentHeader); - Log.d(TAG,"Attach new Header" ); + if (topElement instanceof ActivityListHeader) { + currentHeader = parent.getChildAt(0); } + drawHeader(c, currentHeader); } private void drawHeader(Canvas c, View header) { diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index 4afc55c454bd..702e3080f1ba 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -243,6 +243,11 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } } + public Object getActivityAtByPosition(int pos) { + return values.get(pos); + + } + private ImageView createThumbnailNew(PreviewObject previewObject) { LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(px, px); params.setMargins(10, 10, 10, 10); From 13e7aff0a5fa0014b24c6f820a48cd007359729a Mon Sep 17 00:00:00 2001 From: alexp Date: Wed, 14 Aug 2019 18:17:23 +0200 Subject: [PATCH 03/20] increase height of header element and set backgroundcolor to white Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../android/ui/activities/ActivityListItemDecoration.java | 5 ++--- src/main/res/layout/activity_list_item_header.xml | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java index f6d1fa93a2ed..ec7b406cd753 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -1,9 +1,7 @@ package com.owncloud.android.ui.activities; -import android.app.Activity; -import android.content.Context; import android.graphics.Canvas; -import android.util.Log; +import android.graphics.Color; import android.view.View; import com.owncloud.android.ui.adapter.ActivityListAdapter; @@ -31,6 +29,7 @@ public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull if (topElement instanceof ActivityListHeader) { currentHeader = parent.getChildAt(0); + currentHeader.setBackgroundColor(Color.WHITE); } drawHeader(c, currentHeader); } diff --git a/src/main/res/layout/activity_list_item_header.xml b/src/main/res/layout/activity_list_item_header.xml index 3eee1c3f7526..6c8e3de862f7 100644 --- a/src/main/res/layout/activity_list_item_header.xml +++ b/src/main/res/layout/activity_list_item_header.xml @@ -6,11 +6,11 @@ - \ No newline at end of file + From 792a6b95a656f3bbf411074a3ebf384969cca86d Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 17 Aug 2019 20:30:09 +0200 Subject: [PATCH 04/20] finish implementation of sticky header implementation. This feature was created according to the this implementation : https://stackoverflow.com/questions/32949971/how-can-i-make-sticky-headers-in-recyclerview-without-external-lib Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ui/activities/ActivitiesActivity.java | 3 +- .../ActivityListItemDecoration.java | 83 +++++++++++++++++-- .../ui/adapter/ActivityListAdapter.java | 47 ++++++++--- .../ui/adapter/StickyHeaderAdapter.java | 34 ++++++++ 4 files changed, 147 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 152f24c7ba92..98c5f1e17a97 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -41,6 +41,7 @@ import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.adapter.ActivityListAdapter; +import com.owncloud.android.ui.adapter.StickyHeaderAdapter; import com.owncloud.android.ui.interfaces.ActivityListInterface; import com.owncloud.android.ui.preview.PreviewImageActivity; import com.owncloud.android.ui.preview.PreviewImageFragment; @@ -174,7 +175,7 @@ private void setupContent() { LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); - recyclerView.addItemDecoration(new ActivityListItemDecoration()); + recyclerView.addItemDecoration(new ActivityListItemDecoration( adapter)); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java index ec7b406cd753..52339c7bc429 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -2,35 +2,54 @@ import android.graphics.Canvas; import android.graphics.Color; +import android.util.Log; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; -import com.owncloud.android.ui.adapter.ActivityListAdapter; +import com.owncloud.android.R; + +import com.owncloud.android.ui.adapter.StickyHeaderAdapter; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; public class ActivityListItemDecoration extends RecyclerView.ItemDecoration { private final String TAG = this.getClass().getSimpleName(); - private View currentHeader; + private final StickyHeaderAdapter adapter; + + + public ActivityListItemDecoration(StickyHeaderAdapter stickyHeaderAdapter) { + this.adapter = stickyHeaderAdapter; + } @Override public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { super.onDrawOver(c, parent, state); - View topChild = parent.getChildAt(0); + if (topChild == null) { + return; + } int topChildPosition = parent.getChildAdapterPosition(topChild); - if (topChildPosition == -1) { + + if (topChildPosition == RecyclerView.NO_POSITION) { return; } - ActivityListAdapter adapter = (ActivityListAdapter) parent.getAdapter(); + View currentHeader = getHeaderViewForItem(topChildPosition, parent); + fixLayoutSize(parent, currentHeader); + int contactPoint = currentHeader.getBottom(); + View childInContact = getChildInContact(parent, contactPoint); - Object topElement = adapter.getActivityAtByPosition(topChildPosition); + if (childInContact == null) { + return; + } - if (topElement instanceof ActivityListHeader) { - currentHeader = parent.getChildAt(0); - currentHeader.setBackgroundColor(Color.WHITE); + if (adapter.isHeader(parent.getChildAdapterPosition(childInContact))) { + moveHeader(c, currentHeader, childInContact); + return; } + drawHeader(c, currentHeader); } @@ -40,4 +59,50 @@ private void drawHeader(Canvas c, View header) { header.draw(c); c.restore(); } + + private void moveHeader(Canvas c, View currentHeader, View nextHeader) { + c.save(); + c.translate(0, nextHeader.getTop() - currentHeader.getHeight()); + currentHeader.draw(c); + c.restore(); + } + + private View getChildInContact(RecyclerView parent, int contactPoint) { + View childInContact = null; + for (int i = 0; i < parent.getChildCount(); i++) { + View currentChild = parent.getChildAt(i); + if (currentChild.getBottom() > contactPoint) { + if (currentChild.getTop() <= contactPoint) { + childInContact = currentChild; + break; + } + } + } + return childInContact; + } + + private View getHeaderViewForItem(int itemPosition, RecyclerView parent) { + int headerPosition = adapter.getHeaderPositionForItem(itemPosition); + int layoutId = adapter.getHeaderLayout(itemPosition); + View header = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false); + header.setBackgroundColor(Color.WHITE); + adapter.bindHeaderData(header, headerPosition); + return header; + } + + private void fixLayoutSize(ViewGroup parent, View view) { + + // Specs for parent (RecyclerView) + int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); + int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED); + + // Specs for children (headers) + int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width); + int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height); + + view.measure(childWidthSpec, childHeightSpec); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + } + + } diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index 702e3080f1ba..ef793421b880 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -80,12 +80,13 @@ import java.util.Locale; import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; /** * Adapter for the activity view */ -public class ActivityListAdapter extends RecyclerView.Adapter { +public class ActivityListAdapter extends RecyclerView.Adapter implements StickyHeaderAdapter { static final int HEADER_TYPE = 100; static final int ACTIVITY_TYPE = 101; @@ -142,7 +143,7 @@ public void setActivityItems(List activityItems, OwnCloudClient client, values.add(activity); } else { sTime = time; - values.add(new ActivityListHeader(sTime)); + values.add(sTime); values.add(activity); } } @@ -238,14 +239,9 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } } else { ActivityViewHeaderHolder activityViewHeaderHolder = (ActivityViewHeaderHolder) holder; - ActivityListHeader header = (ActivityListHeader) values.get(position); - activityViewHeaderHolder.title.setText((String) header.getHeadline()); - } - } - - public Object getActivityAtByPosition(int pos) { - return values.get(pos); + activityViewHeaderHolder.title.setText((String) values.get(position)); + } } private ImageView createThumbnailNew(PreviewObject previewObject) { @@ -435,6 +431,37 @@ CharSequence getHeaderDateString(Context context, long modificationTimestamp) { } } + + @Override + public int getHeaderPositionForItem(int itemPosition) { + int headerPosition = 0; + do { + if (this.isHeader(itemPosition)) { + headerPosition = itemPosition; + break; + } + itemPosition -= 1; + } while (itemPosition >= 0); + return headerPosition; + } + + @Override + public int getHeaderLayout(int headerPosition) { + return R.layout.activity_list_item_header; + } + + @Override + public void bindHeaderData(View header, int headerPosition) { + TextView textView = header.findViewById(R.id.title_header); + String headline = (String) values.get(headerPosition); + textView.setText(headline); + } + + @Override + public boolean isHeader(int itemPosition) { + return this.getItemViewType(itemPosition) == HEADER_TYPE; + } + protected class ActivityViewHolder extends RecyclerView.ViewHolder { private final ImageView activityIcon; @@ -453,7 +480,7 @@ protected class ActivityViewHolder extends RecyclerView.ViewHolder { } } - protected class ActivityViewHeaderHolder extends RecyclerView.ViewHolder { + public class ActivityViewHeaderHolder extends RecyclerView.ViewHolder { private final TextView title; diff --git a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java new file mode 100644 index 000000000000..6914a28cf7a5 --- /dev/null +++ b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java @@ -0,0 +1,34 @@ +package com.owncloud.android.ui.adapter; + +import android.view.View; + +public interface StickyHeaderAdapter { + /** + * This method gets called by {@link HeaderItemDecoration} to fetch the position of the header item in the adapter + * that is used for (represents) item at specified position. + * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item. + * @return int. Position of the header item in the adapter. + */ + int getHeaderPositionForItem(int itemPosition); + + /** + * This method gets called by {@link HeaderItemDecoration} to get layout resource id for the header item at specified adapter's position. + * @param headerPosition int. Position of the header item in the adapter. + * @return int. Layout resource id. + */ + int getHeaderLayout(int headerPosition); + + /** + * This method gets called by {@link HeaderItemDecoration} to setup the header View. + * @param header View. Header to set the data on. + * @param headerPosition int. Position of the header item in the adapter. + */ + void bindHeaderData(View header, int headerPosition); + + /** + * This method gets called by {@link HeaderItemDecoration} to verify whether the item represents a header. + * @param itemPosition int. + * @return true, if item at the specified adapter's position represents a header. + */ + boolean isHeader(int itemPosition); +} From b4764f7f642889fab79e945346b31d7671b560ed Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 17 Aug 2019 20:34:25 +0200 Subject: [PATCH 05/20] remove unnecessary files Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../android/ui/activities/ActivityListElement.java | 4 ---- .../android/ui/activities/ActivityListHeader.java | 14 -------------- 2 files changed, 18 deletions(-) delete mode 100644 src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java delete mode 100644 src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java deleted file mode 100644 index 8f04141897f7..000000000000 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListElement.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.owncloud.android.ui.activities; - -public interface ActivityListElement { -} diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java deleted file mode 100644 index 0ee30e93df57..000000000000 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListHeader.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.owncloud.android.ui.activities; - -public class ActivityListHeader implements ActivityListElement { - final private String headline; - - public ActivityListHeader(String headline) { - this.headline = headline; - } - - public String getHeadline() { - return headline; - } - -} From c41dd38ef220d0c6311ce85388965dd4c1321e30 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 17 Aug 2019 20:34:52 +0200 Subject: [PATCH 06/20] optimize imports Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../com/owncloud/android/ui/activities/ActivitiesActivity.java | 3 +-- .../android/ui/activities/ActivityListItemDecoration.java | 3 --- .../com/owncloud/android/ui/adapter/ActivityListAdapter.java | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 98c5f1e17a97..47165ea9e500 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -41,7 +41,6 @@ import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.adapter.ActivityListAdapter; -import com.owncloud.android.ui.adapter.StickyHeaderAdapter; import com.owncloud.android.ui.interfaces.ActivityListInterface; import com.owncloud.android.ui.preview.PreviewImageActivity; import com.owncloud.android.ui.preview.PreviewImageFragment; @@ -61,7 +60,7 @@ import butterknife.ButterKnife; import butterknife.Unbinder; -public class ActivitiesActivity extends FileActivity implements ActivityListInterface, ActivityListElement, ActivitiesContract.View { +public class ActivitiesActivity extends FileActivity implements ActivityListInterface, ActivitiesContract.View { private static final String TAG = ActivitiesActivity.class.getSimpleName(); @BindView(R.id.empty_list_view) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java index 52339c7bc429..441a1a1c0f3b 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -2,13 +2,10 @@ import android.graphics.Canvas; import android.graphics.Color; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import com.owncloud.android.R; - import com.owncloud.android.ui.adapter.StickyHeaderAdapter; import androidx.annotation.NonNull; diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index ef793421b880..22fe11ed53a4 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -65,7 +65,6 @@ import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.status.OCCapability; import com.owncloud.android.lib.resources.status.OwnCloudVersion; -import com.owncloud.android.ui.activities.ActivityListHeader; import com.owncloud.android.ui.interfaces.ActivityListInterface; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.MimeTypeUtil; @@ -80,7 +79,6 @@ import java.util.Locale; import androidx.annotation.NonNull; -import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; /** From a26a895047ebda060942f84ba12c089c6cea928d Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 17 Aug 2019 20:35:11 +0200 Subject: [PATCH 07/20] apply changes to java doc Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../android/ui/adapter/StickyHeaderAdapter.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java index 6914a28cf7a5..b99e9a9e50f5 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java @@ -2,9 +2,11 @@ import android.view.View; +import com.owncloud.android.ui.activities.ActivityListItemDecoration; + public interface StickyHeaderAdapter { /** - * This method gets called by {@link HeaderItemDecoration} to fetch the position of the header item in the adapter + * This method gets called by {@link ActivityListItemDecoration} to fetch the position of the header item in the adapter * that is used for (represents) item at specified position. * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item. * @return int. Position of the header item in the adapter. @@ -12,21 +14,21 @@ public interface StickyHeaderAdapter { int getHeaderPositionForItem(int itemPosition); /** - * This method gets called by {@link HeaderItemDecoration} to get layout resource id for the header item at specified adapter's position. + * This method gets called by {@link ActivityListItemDecoration} to get layout resource id for the header item at specified adapter's position. * @param headerPosition int. Position of the header item in the adapter. * @return int. Layout resource id. */ int getHeaderLayout(int headerPosition); /** - * This method gets called by {@link HeaderItemDecoration} to setup the header View. + * This method gets called by {@link ActivityListItemDecoration} to setup the header View. * @param header View. Header to set the data on. * @param headerPosition int. Position of the header item in the adapter. */ void bindHeaderData(View header, int headerPosition); /** - * This method gets called by {@link HeaderItemDecoration} to verify whether the item represents a header. + * This method gets called by {@link ActivityListItemDecoration} to verify whether the item represents a header. * @param itemPosition int. * @return true, if item at the specified adapter's position represents a header. */ From 7a30070be97d6df4483fcccd57f2983a5e759ca0 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 18 Aug 2019 13:01:08 +0200 Subject: [PATCH 08/20] remove unnecessary TAG field and replace nested if statement with && to fix codeacy-bot issues Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ui/activities/ActivityListItemDecoration.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java index 441a1a1c0f3b..d3cbf49fe963 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -12,7 +12,6 @@ import androidx.recyclerview.widget.RecyclerView; public class ActivityListItemDecoration extends RecyclerView.ItemDecoration { - private final String TAG = this.getClass().getSimpleName(); private final StickyHeaderAdapter adapter; @@ -68,11 +67,9 @@ private View getChildInContact(RecyclerView parent, int contactPoint) { View childInContact = null; for (int i = 0; i < parent.getChildCount(); i++) { View currentChild = parent.getChildAt(i); - if (currentChild.getBottom() > contactPoint) { - if (currentChild.getTop() <= contactPoint) { - childInContact = currentChild; - break; - } + if (currentChild.getBottom() > contactPoint && currentChild.getTop() <= contactPoint) { + childInContact = currentChild; + break; } } return childInContact; From 9012f43e8623f824d0e36d9cd91a94bedc5dcf53 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 18 Aug 2019 13:12:46 +0200 Subject: [PATCH 09/20] set visibility from ActivityViewHeaderHolder back to protected Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../com/owncloud/android/ui/activities/ActivitiesActivity.java | 2 +- .../com/owncloud/android/ui/adapter/ActivityListAdapter.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 47165ea9e500..eb2e68edd098 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -174,7 +174,7 @@ private void setupContent() { LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); - recyclerView.addItemDecoration(new ActivityListItemDecoration( adapter)); + recyclerView.addItemDecoration(new ActivityListItemDecoration(adapter)); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index 22fe11ed53a4..309085bfb830 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -478,7 +478,7 @@ protected class ActivityViewHolder extends RecyclerView.ViewHolder { } } - public class ActivityViewHeaderHolder extends RecyclerView.ViewHolder { + protected class ActivityViewHeaderHolder extends RecyclerView.ViewHolder { private final TextView title; From b61f9e02a2b34717fe7fc434b140802eac3e549a Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 18 Aug 2019 13:18:17 +0200 Subject: [PATCH 10/20] format ActivityListAdapter Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../com/owncloud/android/ui/adapter/ActivityListAdapter.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index 309085bfb830..d210bc98ac37 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -176,8 +176,7 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi !TextUtils.isEmpty(activity.getRichSubjectElement().getRichSubject())) { activityViewHolder.subject.setVisibility(View.VISIBLE); activityViewHolder.subject.setMovementMethod(LinkMovementMethod.getInstance()); - activityViewHolder.subject.setText(addClickablePart(activity.getRichSubjectElement()), - TextView.BufferType.SPANNABLE); + activityViewHolder.subject.setText(addClickablePart(activity.getRichSubjectElement()), TextView.BufferType.SPANNABLE); activityViewHolder.subject.setVisibility(View.VISIBLE); } else if (!TextUtils.isEmpty(activity.getSubject())) { activityViewHolder.subject.setVisibility(View.VISIBLE); @@ -199,7 +198,6 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi if (activity.getRichSubjectElement() != null && activity.getRichSubjectElement().getRichObjectList().size() > 0) { - activityViewHolder.list.setVisibility(View.VISIBLE); activityViewHolder.list.removeAllViews(); @@ -237,7 +235,6 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi } } else { ActivityViewHeaderHolder activityViewHeaderHolder = (ActivityViewHeaderHolder) holder; - activityViewHeaderHolder.title.setText((String) values.get(position)); } } From 4ea9667c06c7447964920d3959233ef5832417ac Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 18:43:12 +0200 Subject: [PATCH 11/20] unit test for isHeader(int pos) Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ui/adapter/ActivityListAdapterTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java diff --git a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java new file mode 100644 index 000000000000..95738d7351bd --- /dev/null +++ b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java @@ -0,0 +1,53 @@ +package com.owncloud.android.ui.adapter; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.internal.util.reflection.FieldSetter; + +import java.util.ArrayList; + +public final class ActivityListAdapterTest { + + + @Mock + private ActivityListAdapter activityListAdapter; + + @Before + public void setUp() throws NoSuchFieldException { + MockitoAnnotations.initMocks(this); + MockitoAnnotations.initMocks(activityListAdapter); + FieldSetter.setField(activityListAdapter, activityListAdapter.getClass().getDeclaredField("values"), new ArrayList<>()); + } + + @Test + public void isHeader__ObjectIsHeader_ReturnTrue() { + Object header = "Hello"; + Object activity = Mockito.mock(com.owncloud.android.lib.resources.activities.model.Activity.class); + + Mockito.when(activityListAdapter.isHeader(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(0)).thenCallRealMethod(); + + activityListAdapter.values.add(header); + activityListAdapter.values.add(activity); + + final boolean result = activityListAdapter.isHeader(0); + Assert.assertTrue(result); + } + + @Test + public void isHeader__ObjectIsActivity_ReturnFalse() { + Object header = "Hello"; + Object activity = Mockito.mock(com.owncloud.android.lib.resources.activities.model.Activity.class); + + Mockito.when(activityListAdapter.isHeader(1)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(1)).thenCallRealMethod(); + + activityListAdapter.values.add(header); + activityListAdapter.values.add(activity); + Assert.assertFalse(activityListAdapter.isHeader(1)); + } +} From 0ac5a8bcec1a6b13787fe637b2db713141cc284d Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 18:43:48 +0200 Subject: [PATCH 12/20] add static import for com.owncloud.android.lib.resources.activities.model.Activity; Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../android/ui/adapter/ActivityListAdapterTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java index 95738d7351bd..a1d5bbd9ca95 100644 --- a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java +++ b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java @@ -1,5 +1,7 @@ package com.owncloud.android.ui.adapter; +import com.owncloud.android.lib.resources.activities.model.Activity; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -26,7 +28,7 @@ public void setUp() throws NoSuchFieldException { @Test public void isHeader__ObjectIsHeader_ReturnTrue() { Object header = "Hello"; - Object activity = Mockito.mock(com.owncloud.android.lib.resources.activities.model.Activity.class); + Object activity = Mockito.mock(Activity.class); Mockito.when(activityListAdapter.isHeader(0)).thenCallRealMethod(); Mockito.when(activityListAdapter.getItemViewType(0)).thenCallRealMethod(); @@ -41,7 +43,7 @@ public void isHeader__ObjectIsHeader_ReturnTrue() { @Test public void isHeader__ObjectIsActivity_ReturnFalse() { Object header = "Hello"; - Object activity = Mockito.mock(com.owncloud.android.lib.resources.activities.model.Activity.class); + Object activity = Mockito.mock(Activity.class); Mockito.when(activityListAdapter.isHeader(1)).thenCallRealMethod(); Mockito.when(activityListAdapter.getItemViewType(1)).thenCallRealMethod(); From cdd5d38d3d2c3ef10d17603a582bf27e055e18d1 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 19:31:35 +0200 Subject: [PATCH 13/20] add getHeaderPositionForItem unit test Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ui/adapter/ActivityListAdapterTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java index a1d5bbd9ca95..92c3a07b64f9 100644 --- a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java +++ b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java @@ -52,4 +52,66 @@ public void isHeader__ObjectIsActivity_ReturnFalse() { activityListAdapter.values.add(activity); Assert.assertFalse(activityListAdapter.isHeader(1)); } + + @Test + public void getHeaderPositionForItem__AdapterIsEmpty_ReturnZero(){ + Mockito.when(activityListAdapter.isHeader(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(0)).thenCallRealMethod(); + + Assert.assertEquals(0,activityListAdapter.getHeaderPositionForItem(0)); + } + + @Test + public void getHeaderPositionForItem__ItemIsHeader_ReturnCurrentItem() { + Object header = "Hello"; + Object activity = Mockito.mock(Activity.class); + + Mockito.when(activityListAdapter.isHeader(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.isHeader(1)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(1)).thenCallRealMethod(); + Mockito.when(activityListAdapter.isHeader(2)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(2)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getHeaderPositionForItem(2)).thenCallRealMethod(); + Mockito.when(activityListAdapter.isHeader(3)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(3)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getHeaderPositionForItem(3)).thenCallRealMethod(); + + + activityListAdapter.values.add(header); + activityListAdapter.values.add(activity); + activityListAdapter.values.add(header); + activityListAdapter.values.add(activity); + + + Assert.assertEquals(2, activityListAdapter.getHeaderPositionForItem(2)); + + } + + @Test + public void getHeaderPositionForItem__ItemIsActivity_ReturnNextHeader() { + Object header = "Hello"; + Object activity = Mockito.mock(Activity.class); + + Mockito.when(activityListAdapter.isHeader(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getHeaderPositionForItem(0)).thenCallRealMethod(); + Mockito.when(activityListAdapter.isHeader(1)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(1)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getHeaderPositionForItem(1)).thenCallRealMethod(); + Mockito.when(activityListAdapter.isHeader(2)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(2)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getHeaderPositionForItem(2)).thenCallRealMethod(); + Mockito.when(activityListAdapter.isHeader(3)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getItemViewType(3)).thenCallRealMethod(); + Mockito.when(activityListAdapter.getHeaderPositionForItem(3)).thenCallRealMethod(); + + activityListAdapter.values.add(header); + activityListAdapter.values.add(activity); + activityListAdapter.values.add(header); + activityListAdapter.values.add(activity); + + Assert.assertEquals(2, activityListAdapter.getHeaderPositionForItem(2)); + } + } From 6ffa9d08fa63fe3dfc62ad599db189abb3b74aa0 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 19:46:06 +0200 Subject: [PATCH 14/20] remove duplicate entry of setContentView Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../com/owncloud/android/ui/activities/ActivitiesActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index eb2e68edd098..9ab2bad36f5b 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -104,9 +104,9 @@ public class ActivitiesActivity extends FileActivity implements ActivityListInte protected void onCreate(Bundle savedInstanceState) { Log_OC.v(TAG, "onCreate() start"); super.onCreate(savedInstanceState); + mActionListener = new ActivitiesPresenter(activitiesRepository, filesRepository, this); - setContentView(R.layout.activity_list_layout); setContentView(R.layout.activity_list_layout); unbinder = ButterKnife.bind(this); From 52a7ffe01aed4658a9b34b39a5561eeca16cedba Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 19:59:08 +0200 Subject: [PATCH 15/20] add license text Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../ActivityListItemDecoration.java | 20 +++++++++++++++++++ .../ui/adapter/StickyHeaderAdapter.java | 20 +++++++++++++++++++ .../ui/adapter/ActivityListAdapterTest.java | 20 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java index d3cbf49fe963..dfe1f64b7a06 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java @@ -1,3 +1,23 @@ +/* + * Nextcloud Android client application + * + + * Copyright (C) 2019 Sevastyan Savanyuk + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package com.owncloud.android.ui.activities; import android.graphics.Canvas; diff --git a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java index b99e9a9e50f5..c6dda7fa55a9 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java @@ -1,3 +1,23 @@ +/* + * Nextcloud Android client application + * + + * Copyright (C) 2019 Sevastyan Savanyuk + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package com.owncloud.android.ui.adapter; import android.view.View; diff --git a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java index 92c3a07b64f9..dd7c5859baa8 100644 --- a/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java +++ b/src/test/java/com/owncloud/android/ui/adapter/ActivityListAdapterTest.java @@ -1,3 +1,23 @@ +/* + * Nextcloud Android client application + * + * @author Alex Plutta + * Copyright (C) 2019 Alex Plutta + * Copyright (C) 2019 Nextcloud GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ package com.owncloud.android.ui.adapter; import com.owncloud.android.lib.resources.activities.model.Activity; From f4e964e46e6d4be5c713714ac9ffdf18b4a1580c Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 20:01:29 +0200 Subject: [PATCH 16/20] change class name of ActivityListItemDecoration to StickyHeaderItemDecoration, which is more generic Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../android/ui/activities/ActivitiesActivity.java | 2 +- ...Decoration.java => StickyHeaderItemDecoration.java} | 4 ++-- .../android/ui/adapter/StickyHeaderAdapter.java | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) rename src/main/java/com/owncloud/android/ui/activities/{ActivityListItemDecoration.java => StickyHeaderItemDecoration.java} (97%) diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java index 9ab2bad36f5b..b71ff27dbd6d 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java +++ b/src/main/java/com/owncloud/android/ui/activities/ActivitiesActivity.java @@ -174,7 +174,7 @@ private void setupContent() { LinearLayoutManager layoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(layoutManager); - recyclerView.addItemDecoration(new ActivityListItemDecoration(adapter)); + recyclerView.addItemDecoration(new StickyHeaderItemDecoration(adapter)); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override diff --git a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java similarity index 97% rename from src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java rename to src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java index dfe1f64b7a06..4698680ff05c 100644 --- a/src/main/java/com/owncloud/android/ui/activities/ActivityListItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java @@ -31,11 +31,11 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; -public class ActivityListItemDecoration extends RecyclerView.ItemDecoration { +public class StickyHeaderItemDecoration extends RecyclerView.ItemDecoration { private final StickyHeaderAdapter adapter; - public ActivityListItemDecoration(StickyHeaderAdapter stickyHeaderAdapter) { + public StickyHeaderItemDecoration(StickyHeaderAdapter stickyHeaderAdapter) { this.adapter = stickyHeaderAdapter; } diff --git a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java index c6dda7fa55a9..1ba23a9c27cf 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/StickyHeaderAdapter.java @@ -22,11 +22,11 @@ import android.view.View; -import com.owncloud.android.ui.activities.ActivityListItemDecoration; +import com.owncloud.android.ui.activities.StickyHeaderItemDecoration; public interface StickyHeaderAdapter { /** - * This method gets called by {@link ActivityListItemDecoration} to fetch the position of the header item in the adapter + * This method gets called by {@link StickyHeaderItemDecoration} to fetch the position of the header item in the adapter * that is used for (represents) item at specified position. * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item. * @return int. Position of the header item in the adapter. @@ -34,21 +34,21 @@ public interface StickyHeaderAdapter { int getHeaderPositionForItem(int itemPosition); /** - * This method gets called by {@link ActivityListItemDecoration} to get layout resource id for the header item at specified adapter's position. + * This method gets called by {@link StickyHeaderItemDecoration} to get layout resource id for the header item at specified adapter's position. * @param headerPosition int. Position of the header item in the adapter. * @return int. Layout resource id. */ int getHeaderLayout(int headerPosition); /** - * This method gets called by {@link ActivityListItemDecoration} to setup the header View. + * This method gets called by {@link StickyHeaderItemDecoration} to setup the header View. * @param header View. Header to set the data on. * @param headerPosition int. Position of the header item in the adapter. */ void bindHeaderData(View header, int headerPosition); /** - * This method gets called by {@link ActivityListItemDecoration} to verify whether the item represents a header. + * This method gets called by {@link StickyHeaderItemDecoration} to verify whether the item represents a header. * @param itemPosition int. * @return true, if item at the specified adapter's position represents a header. */ From 1dd2a5a71cd985e014d03d390b6b12f3fb553a32 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 20:03:09 +0200 Subject: [PATCH 17/20] change naming of Canvas c to Canvas canvas Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../StickyHeaderItemDecoration.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java b/src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java index 4698680ff05c..bdcf81795acd 100644 --- a/src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java +++ b/src/main/java/com/owncloud/android/ui/activities/StickyHeaderItemDecoration.java @@ -40,8 +40,8 @@ public StickyHeaderItemDecoration(StickyHeaderAdapter stickyHeaderAdapter) { } @Override - public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { - super.onDrawOver(c, parent, state); + public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { + super.onDrawOver(canvas, parent, state); View topChild = parent.getChildAt(0); if (topChild == null) { @@ -62,25 +62,25 @@ public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull } if (adapter.isHeader(parent.getChildAdapterPosition(childInContact))) { - moveHeader(c, currentHeader, childInContact); + moveHeader(canvas, currentHeader, childInContact); return; } - drawHeader(c, currentHeader); + drawHeader(canvas, currentHeader); } - private void drawHeader(Canvas c, View header) { - c.save(); - c.translate(0, 0); - header.draw(c); - c.restore(); + private void drawHeader(Canvas canvas, View header) { + canvas.save(); + canvas.translate(0, 0); + header.draw(canvas); + canvas.restore(); } - private void moveHeader(Canvas c, View currentHeader, View nextHeader) { - c.save(); - c.translate(0, nextHeader.getTop() - currentHeader.getHeight()); - currentHeader.draw(c); - c.restore(); + private void moveHeader(Canvas canvas, View currentHeader, View nextHeader) { + canvas.save(); + canvas.translate(0, nextHeader.getTop() - currentHeader.getHeight()); + currentHeader.draw(canvas); + canvas.restore(); } private View getChildInContact(RecyclerView parent, int contactPoint) { From 5a3db5ef2bd96a462fe7707a1b68dafc5efcc650 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 20:19:53 +0200 Subject: [PATCH 18/20] replace do while loop in getHeaderPositionForItem with while loop Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../owncloud/android/ui/adapter/ActivityListAdapter.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index d210bc98ac37..6710ac0ea1f7 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -429,17 +429,16 @@ CharSequence getHeaderDateString(Context context, long modificationTimestamp) { @Override public int getHeaderPositionForItem(int itemPosition) { - int headerPosition = 0; - do { + while (itemPosition >= 0) { if (this.isHeader(itemPosition)) { - headerPosition = itemPosition; break; } itemPosition -= 1; - } while (itemPosition >= 0); - return headerPosition; + } + return itemPosition; } + @Override public int getHeaderLayout(int headerPosition) { return R.layout.activity_list_item_header; From fa115658b31f748af05c41b755a1306d9eff2fd6 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 20:24:35 +0200 Subject: [PATCH 19/20] fixed issue Avoid reassigning parameters such as 'itemPosition' Signed-off-by: alex Signed-off-by: Andy Scherzinger --- .../owncloud/android/ui/adapter/ActivityListAdapter.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index 6710ac0ea1f7..a7f38cdae723 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -429,13 +429,14 @@ CharSequence getHeaderDateString(Context context, long modificationTimestamp) { @Override public int getHeaderPositionForItem(int itemPosition) { - while (itemPosition >= 0) { - if (this.isHeader(itemPosition)) { + int headerPosition = itemPosition; + while (headerPosition >= 0) { + if (this.isHeader(headerPosition)) { break; } - itemPosition -= 1; + headerPosition -= 1; } - return itemPosition; + return headerPosition; } From 96240194046915974ceff6ce4d1a46b6d9308c35 Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 19 Aug 2019 20:47:22 +0200 Subject: [PATCH 20/20] activity header has now the same font size as a activity element Signed-off-by: alex Signed-off-by: Andy Scherzinger --- src/main/res/values/dims.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values/dims.xml b/src/main/res/values/dims.xml index 0806007d9558..1db7bb7af6b7 100644 --- a/src/main/res/values/dims.xml +++ b/src/main/res/values/dims.xml @@ -95,7 +95,7 @@ 32dp 24dp -3dp - 20sp + 16sp -3dp 48dp 32dp