simplify contact list (#2374)

* simplify contact list

this also disables "fast scrolling" for the contact list;
the "fast scrolling" code is too much bound to the bubbles and a-z
to be preserved without much effort.

however, "fast scrolling" seems to be much less important
with a list sorted by "last seen".
there is also the rough idea to raise minimal-origin of contact list,
this would make the list much smaller
(<https://github.com/deltachat/deltachat-core-rust/pull/2411>),
otoh, not sure if #2411 is a thing for list sorted by "last seen".

also the other lists (chatlist, archive, blocked) may become quite huge as well
and do never have "fast scrolling".

in case it is really needed again,
however, we should go for the implementation available in RecyclerView since some time:
<https://stackoverflow.com/questions/45370246/how-to-use-fastscrollenabled-in-recyclerview>

* less padding as the A-Z decorations do not longer need space

* remove unused contact_selection_recyclerview_header
This commit is contained in:
bjoern
2022-08-29 13:56:06 +02:00
committed by GitHub
parent ab554ac346
commit 3121cd9ade
11 changed files with 2 additions and 376 deletions
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topLeftRadius="44dp"
android:topRightRadius="44dp"
android:bottomLeftRadius="44dp"
android:bottomRightRadius="0dp"/>
<solid android:color="#FF0288D1" />
<size android:height="88dp" android:width="88dp" />
</shape>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:topLeftRadius="44dp"
android:topRightRadius="44dp"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="44dp"/>
<solid android:color="#FF0288D1" />
<size android:height="88dp" android:width="88dp" />
</shape>
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true">
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="#FF0288D1"/>
<size android:height="32dp" android:width="4dp"/>
</shape>
</item>
<item>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<corners android:radius="2dp"/>
<solid android:color="#FF737373"/>
<size android:height="32dp" android:width="4dp"/>
</shape>
</item>
</selector>
@@ -22,11 +22,4 @@
android:textSize="20sp"
tools:text="No contacts." />
<org.thoughtcrime.securesms.components.RecyclerViewFastScroller
android:id="@+id/fast_scroller"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_gravity="end"/>
</FrameLayout>
+2 -4
View File
@@ -8,10 +8,8 @@
android:gravity="center_vertical"
android:focusable="true"
android:background="?attr/conversation_list_item_background"
android:paddingLeft="48dp"
android:paddingStart="48dp"
android:paddingRight="24dp"
android:paddingEnd="24dp">
android:paddingLeft="24dp"
android:paddingRight="24dp">
<org.thoughtcrime.securesms.components.AvatarView
android:id="@+id/avatar"
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="48dp"
android:layout_height="60dp"
android:gravity="center"
android:textColor="#666"
android:textSize="22sp"
tools:text="H" />
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView android:id="@+id/fastscroller_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/recycler_view_fast_scroller_bubble"
android:gravity="center"
android:textColor="@color/white"
android:textSize="48sp"
android:visibility="gone"
tools:visibility="visible"
tools:text="A" />
<ImageView android:id="@+id/fastscroller_handle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:src="@drawable/recycler_view_fast_scroller_handle" />
</merge>
@@ -20,7 +20,6 @@ package org.thoughtcrime.securesms;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.os.AsyncTask;
@@ -44,14 +43,12 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEvent;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller;
import org.thoughtcrime.securesms.connect.DcContactsLoader;
import org.thoughtcrime.securesms.connect.DcEventCenter;
import org.thoughtcrime.securesms.connect.DcHelper;
@@ -60,7 +57,6 @@ import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter;
import org.thoughtcrime.securesms.contacts.ContactSelectionListItem;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -95,8 +91,6 @@ public class ContactSelectionListFragment extends Fragment
private OnContactSelectedListener onContactSelectedListener;
private String cursorFilter;
private RecyclerView recyclerView;
private StickyHeaderDecoration listDecoration;
private RecyclerViewFastScroller fastScroller;
private ActionMode actionMode;
private ActionMode.Callback actionModeCallback;
@@ -131,7 +125,6 @@ public class ContactSelectionListFragment extends Fragment
View view = inflater.inflate(R.layout.contact_selection_list_fragment, container, false);
recyclerView = ViewUtil.findById(view, R.id.recycler_view);
fastScroller = ViewUtil.findById(view, R.id.fast_scroller);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
actionModeCallback = new ActionMode.Callback() {
@Override
@@ -183,12 +176,6 @@ public class ContactSelectionListFragment extends Fragment
return view;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
listDecoration.onConfigurationChanged(newConfig);
}
private void handleSelectAll() {
getContactSelectionListAdapter().selectAll();
updateActionModeTitle();
@@ -286,8 +273,6 @@ public class ContactSelectionListFragment extends Fragment
selectedContacts.addAll(preselectedContacts);
}
recyclerView.setAdapter(adapter);
listDecoration = new StickyHeaderDecoration(adapter, true, true);
recyclerView.addItemDecoration(listDecoration);
}
public void setQueryFilter(String filter) {
@@ -295,18 +280,6 @@ public class ContactSelectionListFragment extends Fragment
this.getLoaderManager().restartLoader(0, null, this);
}
public void resetQueryFilter() {
setQueryFilter(null);
}
public void reset() {
selectedContacts.clear();
if (!isDetached() && !isRemoving() && getActivity() != null && !getActivity().isFinishing()) {
getLoaderManager().restartLoader(0, null, this);
}
}
@Override
public Loader<DcContactsLoader.Ret> onCreateLoader(int id, Bundle args) {
boolean addCreateGroupLinks = isFromShareActivity() || isRelayingMessageContent(getActivity()) ? false : !isMulti();
@@ -320,18 +293,11 @@ public class ContactSelectionListFragment extends Fragment
@Override
public void onLoadFinished(Loader<DcContactsLoader.Ret> loader, DcContactsLoader.Ret data) {
((ContactSelectionListAdapter) recyclerView.getAdapter()).changeData(data);
boolean useFastScroller = (recyclerView.getAdapter().getItemCount() > 20);
recyclerView.setVerticalScrollBarEnabled(!useFastScroller);
if (useFastScroller) {
fastScroller.setVisibility(View.VISIBLE);
fastScroller.setRecyclerView(recyclerView);
}
}
@Override
public void onLoaderReset(Loader<DcContactsLoader.Ret> loader) {
((ContactSelectionListAdapter) recyclerView.getAdapter()).changeData(null);
fastScroller.setVisibility(View.GONE);
}
@SuppressLint("StaticFieldLeak")
@@ -1,213 +0,0 @@
/**
* Modified version of
* https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller
*
* Their license:
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thoughtcrime.securesms.components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
public class RecyclerViewFastScroller extends LinearLayout {
private static final int BUBBLE_ANIMATION_DURATION = 100;
private static final int TRACK_SNAP_RANGE = 5;
private @NonNull TextView bubble;
private @NonNull View handle;
private @Nullable RecyclerView recyclerView;
private int height;
private ObjectAnimator currentAnimator;
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
if (handle.isSelected()) return;
final int offset = recyclerView.computeVerticalScrollOffset();
final int range = recyclerView.computeVerticalScrollRange();
final int extent = recyclerView.computeVerticalScrollExtent();
final int offsetRange = Math.max(range - extent, 1);
setBubbleAndHandlePosition((float) Util.clamp(offset, 0, offsetRange) / offsetRange);
}
};
public interface FastScrollAdapter {
CharSequence getBubbleText(int pos);
}
public RecyclerViewFastScroller(final Context context) {
this(context, null);
}
public RecyclerViewFastScroller(final Context context, final AttributeSet attrs) {
super(context, attrs);
setOrientation(HORIZONTAL);
setClipChildren(false);
setScrollContainer(true);
inflate(context, R.layout.recycler_view_fast_scroller, this);
bubble = ViewUtil.findById(this, R.id.fastscroller_bubble);
handle = ViewUtil.findById(this, R.id.fastscroller_handle);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
height = h;
}
@Override
@TargetApi(11)
public boolean onTouchEvent(@NonNull MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (event.getX() < ViewUtil.getX(handle) - handle.getPaddingLeft() ||
event.getY() < ViewUtil.getY(handle) - handle.getPaddingTop() ||
event.getY() > ViewUtil.getY(handle) + handle.getHeight() + handle.getPaddingBottom())
{
return false;
}
if (currentAnimator != null) {
currentAnimator.cancel();
}
if (bubble.getVisibility() != VISIBLE) {
showBubble();
}
handle.setSelected(true);
case MotionEvent.ACTION_MOVE:
final float y = event.getY();
setBubbleAndHandlePosition(y / height);
setRecyclerViewPosition(y);
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
handle.setSelected(false);
hideBubble();
return true;
}
return super.onTouchEvent(event);
}
public void setRecyclerView(final @NonNull RecyclerView recyclerView) {
if (this.recyclerView != null) {
this.recyclerView.removeOnScrollListener(onScrollListener);
}
this.recyclerView = recyclerView;
recyclerView.addOnScrollListener(onScrollListener);
recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
recyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
if (handle.isSelected()) return true;
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
float proportion = (float)verticalScrollOffset / ((float)verticalScrollRange - height);
setBubbleAndHandlePosition(height * proportion);
return true;
}
});
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (recyclerView != null)
recyclerView.removeOnScrollListener(onScrollListener);
}
private void setRecyclerViewPosition(float y) {
if (recyclerView != null) {
final int itemCount = recyclerView.getAdapter().getItemCount();
float proportion;
if (ViewUtil.getY(handle) == 0) {
proportion = 0f;
} else if (ViewUtil.getY(handle) + handle.getHeight() >= height - TRACK_SNAP_RANGE) {
proportion = 1f;
} else {
proportion = y / (float)height;
}
final int targetPos = Util.clamp((int)(proportion * (float)itemCount), 0, itemCount - 1);
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
final CharSequence bubbleText = ((FastScrollAdapter) recyclerView.getAdapter()).getBubbleText(targetPos);
bubble.setText(bubbleText);
}
}
private void setBubbleAndHandlePosition(float y) {
final int handleHeight = handle.getHeight();
final int bubbleHeight = bubble.getHeight();
final int handleY = Util.clamp((int)((height - handleHeight) * y), 0, height - handleHeight);
ViewUtil.setY(handle, handleY);
ViewUtil.setY(bubble, Util.clamp(handleY - bubbleHeight - bubble.getPaddingBottom() + handleHeight,
0,
height - bubbleHeight));
}
@TargetApi(11)
private void showBubble() {
bubble.setVisibility(VISIBLE);
if (VERSION.SDK_INT >= 11) {
if (currentAnimator != null) currentAnimator.cancel();
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.start();
}
}
@TargetApi(11)
private void hideBubble() {
if (VERSION.SDK_INT >= 11) {
if (currentAnimator != null) currentAnimator.cancel();
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
currentAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
super.onAnimationCancel(animation);
bubble.setVisibility(INVISIBLE);
currentAnimator = null;
}
});
currentAnimator.start();
} else {
bubble.setVisibility(INVISIBLE);
}
}
}
@@ -32,14 +32,10 @@ import com.b44t.messenger.DcContact;
import com.b44t.messenger.DcContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.RecyclerViewFastScroller.FastScrollAdapter;
import org.thoughtcrime.securesms.connect.DcContactsLoader;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.contacts.ContactSelectionListAdapter.HeaderViewHolder;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.LRUCache;
import org.thoughtcrime.securesms.util.StickyHeaderDecoration.StickyHeaderAdapter;
import org.thoughtcrime.securesms.util.Util;
import java.lang.ref.SoftReference;
import java.util.Collections;
@@ -53,8 +49,6 @@ import java.util.Set;
* @author Jake McGinty
*/
public class ContactSelectionListAdapter extends RecyclerView.Adapter
implements FastScrollAdapter,
StickyHeaderAdapter<HeaderViewHolder>
{
private final static String TAG = ContactSelectionListAdapter.class.getSimpleName();
@@ -243,12 +237,6 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
}
}
static class HeaderViewHolder extends RecyclerView.ViewHolder {
HeaderViewHolder(View itemView) {
super(itemView);
}
}
public ContactSelectionListAdapter(@NonNull Context context,
@NonNull GlideRequests glideRequests,
@Nullable ItemClickListener clickListener,
@@ -328,11 +316,6 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
return VIEW_TYPE_CONTACT;
}
@Override
public CharSequence getBubbleText(int position) {
return getHeaderString(position);
}
public Set<String> getSelectedContacts() {
return selectedContacts;
}
@@ -347,38 +330,6 @@ public class ContactSelectionListAdapter extends RecyclerView.Adapter
void onItemLongClick(ContactSelectionListItem view);
}
@Override
public long getHeaderId(int position) {
if (position < 0 || position >= getItemCount()) {
return -1;
}
return Util.hashCode(getHeaderString(position));
}
private @NonNull String getHeaderString(int position) {
DcContact dcContact = getContact(position);
String name = dcContact.getDisplayName();
if (!TextUtils.isEmpty(name)) {
String firstChar = name.trim().substring(0, 1).toUpperCase();
if (Character.isLetterOrDigit(firstChar.codePointAt(0))) {
return firstChar;
}
}
return "";
}
@Override
public HeaderViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
return new HeaderViewHolder(LayoutInflater.from(context).inflate(R.layout.contact_selection_recyclerview_header, parent, false));
}
@Override
public void onBindHeaderViewHolder(HeaderViewHolder viewHolder, int position) {
((TextView)viewHolder.itemView).setText(getHeaderString(position));
}
public void changeData(DcContactsLoader.Ret loaderRet) {
this.dcContactList = loaderRet==null? new int[0] : loaderRet.ids;
this.query = loaderRet==null? null : loaderRet.query;
@@ -12,7 +12,6 @@ import com.b44t.messenger.DcContact;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.AvatarView;
import org.thoughtcrime.securesms.connect.DcHelper;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;