Improved SectionedCursorAdapter, added support for the dummy key item view.

This commit is contained in:
Tobias Erthal
2016-09-06 01:40:16 +02:00
parent d5321a6fb2
commit 930ea073b3
11 changed files with 845 additions and 426 deletions

View File

@@ -21,12 +21,10 @@ package org.sufficientlysecure.keychain.ui;
import android.animation.ObjectAnimator; import android.animation.ObjectAnimator;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.database.MatrixCursor; import android.database.MatrixCursor;
import android.database.MergeCursor; import android.database.MergeCursor;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
@@ -35,7 +33,6 @@ import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.view.MenuItemCompat; import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.ActionMode; import android.view.ActionMode;
@@ -46,11 +43,8 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Button; import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.ViewAnimator; import android.widget.ViewAnimator;
import com.getbase.floatingactionbutton.FloatingActionButton; import com.getbase.floatingactionbutton.FloatingActionButton;
@@ -73,26 +67,22 @@ import org.sufficientlysecure.keychain.service.ConsolidateInputParcel;
import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper; import org.sufficientlysecure.keychain.ui.base.CryptoOperationHelper;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.ContentDescriptionHint;
import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.adapter.KeySectionedListAdapter; import org.sufficientlysecure.keychain.ui.adapter.KeySectionedListAdapter;
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment;
import org.sufficientlysecure.keychain.util.FabContainer; import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.util.Preferences;
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
/** /**
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses * Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
* StickyListHeaders library which does not extend upon ListView. * StickyListHeaders library which does not extend upon ListView.
*/ */
public class KeyListFragment extends LoaderFragment public class KeyListFragment extends RecyclerFragment<KeySectionedListAdapter>
implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener, implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor>, FabContainer, LoaderManager.LoaderCallbacks<Cursor>, FabContainer,
CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> { CryptoOperationHelper.Callback<ImportKeyringParcel, ImportKeyResult> {
@@ -101,10 +91,6 @@ public class KeyListFragment extends LoaderFragment
private static final int REQUEST_DELETE = 2; private static final int REQUEST_DELETE = 2;
private static final int REQUEST_VIEW_KEY = 3; private static final int REQUEST_VIEW_KEY = 3;
//private KeyListAdapter mAdapter;
private KeySectionedListAdapter mAdapter;
private RecyclerView mStickyList;
// saves the mode object for multiselect, needed for reset at some point // saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null; private ActionMode mActionMode = null;
@@ -126,12 +112,8 @@ public class KeyListFragment extends LoaderFragment
* Load custom layout with StickyListView from library * Load custom layout with StickyListView from library
*/ */
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.key_list_fragment, container, false);
View view = inflater.inflate(R.layout.key_list_fragment, getContainer());
mStickyList = (RecyclerView) view.findViewById(R.id.key_list_list);
//mStickyList.setOnItemClickListener(this);
mFab = (FloatingActionsMenu) view.findViewById(R.id.fab_main); mFab = (FloatingActionsMenu) view.findViewById(R.id.fab_main);
@@ -162,7 +144,7 @@ public class KeyListFragment extends LoaderFragment
}); });
return root; return view;
} }
/** /**
@@ -182,7 +164,7 @@ public class KeyListFragment extends LoaderFragment
//mStickyList.setDrawingListUnderStickyHeader(false); //mStickyList.setDrawingListUnderStickyHeader(false);
//mStickyList.setFastScrollEnabled(true); //mStickyList.setFastScrollEnabled(true);
// Adds an empty footer view so that the Floating Action Button won't block content /* Adds an empty footer view so that the Floating Action Button won't block content
// in last few rows. // in last few rows.
View footer = new View(activity); View footer = new View(activity);
@@ -197,6 +179,7 @@ public class KeyListFragment extends LoaderFragment
footer.setLayoutParams(params); footer.setLayoutParams(params);
//mStickyList.addFooterView(footer, null, false); //mStickyList.addFooterView(footer, null, false);
*/
/* /*
* Multi-selection * Multi-selection
@@ -269,7 +252,7 @@ public class KeyListFragment extends LoaderFragment
setHasOptionsMenu(true); setHasOptionsMenu(true);
// Start out with a progress indicator. // Start out with a progress indicator.
setContentShown(false); hideList(true);
// this view is made visible if no data is available // this view is made visible if no data is available
// mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty)); // mStickyList.setEmptyView(activity.findViewById(R.id.key_list_empty));
@@ -286,10 +269,9 @@ public class KeyListFragment extends LoaderFragment
// Create an empty adapter we will use to display the loaded data. // Create an empty adapter we will use to display the loaded data.
//mAdapter = new KeyListAdapter(activity, null, 0); //mAdapter = new KeyListAdapter(activity, null, 0);
mAdapter = new KeySectionedListAdapter(getContext(), null);
mStickyList.setAdapter(mAdapter); setAdapter(new KeySectionedListAdapter(getContext(), null));
mStickyList.setLayoutManager(new LayoutManager(getActivity())); setLayoutManager(new LayoutManager(getActivity()));
// Prepare the loader. Either re-connect with an existing one, // Prepare the loader. Either re-connect with an existing one,
// or start a new one. // or start a new one.
@@ -324,22 +306,22 @@ public class KeyListFragment extends LoaderFragment
// Now create and return a CursorLoader that will take care of // Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed. // creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), uri, KeyListAdapter.PROJECTION, null, null, ORDER); return new CursorLoader(getActivity(), uri, KeyAdapter.PROJECTION, null, null, ORDER);
} }
@Override @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) { public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the // Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.) // old cursor once we return.)
mAdapter.setSearchQuery(mQuery); getAdapter().setSearchQuery(mQuery);
if (data != null && (mQuery == null || TextUtils.isEmpty(mQuery))) { if (data != null && (mQuery == null || TextUtils.isEmpty(mQuery))) {
boolean isSecret = data.moveToFirst() && data.getInt(KeyListAdapter.INDEX_HAS_ANY_SECRET) != 0; boolean isSecret = data.moveToFirst() && data.getInt(KeyAdapter.INDEX_HAS_ANY_SECRET) != 0;
if (!isSecret) { if (!isSecret) {
MatrixCursor headerCursor = new MatrixCursor(KeyListAdapter.PROJECTION); MatrixCursor headerCursor = new MatrixCursor(KeyAdapter.PROJECTION);
Long[] row = new Long[KeyListAdapter.PROJECTION.length]; Long[] row = new Long[KeyAdapter.PROJECTION.length];
row[KeyListAdapter.INDEX_HAS_ANY_SECRET] = 1L; row[KeyAdapter.INDEX_HAS_ANY_SECRET] = 1L;
row[KeyListAdapter.INDEX_MASTER_KEY_ID] = 0L; row[KeyAdapter.INDEX_MASTER_KEY_ID] = 0L;
headerCursor.addRow(row); headerCursor.addRow(row);
Cursor dataCursor = data; Cursor dataCursor = data;
@@ -348,7 +330,8 @@ public class KeyListFragment extends LoaderFragment
}); });
} }
} }
mAdapter.swapCursor(data);
getAdapter().swapCursor(data);
// end action mode, if any // end action mode, if any
if (mActionMode != null) { if (mActionMode != null) {
@@ -357,9 +340,9 @@ public class KeyListFragment extends LoaderFragment
// The list should now be shown. // The list should now be shown.
if (isResumed()) { if (isResumed()) {
setContentShown(true); showList(true);
} else { } else {
setContentShownNoAnimation(true); showList(false);
} }
} }
@@ -368,7 +351,7 @@ public class KeyListFragment extends LoaderFragment
// This is called when the last Cursor provided to onLoadFinished() // This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no // above is about to be closed. We need to make sure we are no
// longer using it. // longer using it.
mAdapter.swapCursor(null); getAdapter().swapCursor(null);
} }
/** /**
@@ -378,7 +361,7 @@ public class KeyListFragment extends LoaderFragment
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) { public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class); Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
viewIntent.setData( viewIntent.setData(
KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position))); KeyRings.buildGenericKeyRingUri(getAdapter().getMasterKeyId(position)));
startActivityForResult(viewIntent, REQUEST_VIEW_KEY); startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
} }
@@ -762,6 +745,7 @@ public class KeyListFragment extends LoaderFragment
return false; return false;
} }
/*
public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter { public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter {
private HashMap<Integer, Boolean> mSelection = new HashMap<>(); private HashMap<Integer, Boolean> mSelection = new HashMap<>();
@@ -842,19 +826,13 @@ public class KeyListFragment extends LoaderFragment
TextView mCount; TextView mCount;
} }
/**
* Creates a new header view and binds the section headers to it. It uses the ViewHolder
* pattern. Most functionality is similar to getView() from Android's CursorAdapter.
* <p/>
* NOTE: The variables mDataValid and mCursor are available due to the super class
* CursorAdapter.
*/
@Override @Override
public View getHeaderView(int position, View convertView, ViewGroup parent) { public View getHeaderView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder; HeaderViewHolder holder;
if (convertView == null) { if (convertView == null) {
holder = new HeaderViewHolder(); holder = new HeaderViewHolder();
convertView = mInflater.inflate(R.layout.key_list_header, parent, false); convertView = mInflater.inflate(R.layout.key_list_header_public, parent, false);
holder.mText = (TextView) convertView.findViewById(R.id.stickylist_header_text); holder.mText = (TextView) convertView.findViewById(R.id.stickylist_header_text);
holder.mCount = (TextView) convertView.findViewById(R.id.contacts_num); holder.mCount = (TextView) convertView.findViewById(R.id.contacts_num);
convertView.setTag(holder); convertView.setTag(holder);
@@ -899,9 +877,6 @@ public class KeyListFragment extends LoaderFragment
return convertView; return convertView;
} }
/**
* Header IDs should be static, position=1 should always return the same Id that is.
*/
@Override @Override
public long getHeaderId(int position) { public long getHeaderId(int position) {
if (!mDataValid) { if (!mDataValid) {
@@ -927,9 +902,6 @@ public class KeyListFragment extends LoaderFragment
} }
} }
/**
* -------------------------- MULTI-SELECTION METHODS --------------
*/
public void setNewSelection(int position, boolean value) { public void setNewSelection(int position, boolean value) {
mSelection.put(position, value); mSelection.put(position, value);
notifyDataSetChanged(); notifyDataSetChanged();
@@ -965,5 +937,6 @@ public class KeyListFragment extends LoaderFragment
} }
} }
*/
} }

View File

@@ -94,7 +94,6 @@ public class KeyAdapter extends CursorAdapter {
public static class KeyItemViewHolder { public static class KeyItemViewHolder {
public View mView; public View mView;
public View mLayoutDummy;
public View mLayoutData; public View mLayoutData;
public Long mMasterKeyId; public Long mMasterKeyId;
public TextView mMainUserId; public TextView mMainUserId;
@@ -109,7 +108,6 @@ public class KeyAdapter extends CursorAdapter {
public KeyItemViewHolder(View view) { public KeyItemViewHolder(View view) {
mView = view; mView = view;
mLayoutData = view.findViewById(R.id.key_list_item_data); mLayoutData = view.findViewById(R.id.key_list_item_data);
mLayoutDummy = view.findViewById(R.id.key_list_item_dummy);
mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name); mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name);
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email); mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon);
@@ -119,10 +117,6 @@ public class KeyAdapter extends CursorAdapter {
} }
public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) { public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) {
mLayoutData.setVisibility(View.VISIBLE);
mLayoutDummy.setVisibility(View.GONE);
mDisplayedItem = item; mDisplayedItem = item;
{ // set name and stuff, common to both key types { // set name and stuff, common to both key types
@@ -207,25 +201,8 @@ public class KeyAdapter extends CursorAdapter {
} else { } else {
mCreationDate.setVisibility(View.GONE); mCreationDate.setVisibility(View.GONE);
} }
} }
} }
/** Shows the "you have no keys yet" dummy view, and sets an OnClickListener. */
public void setDummy(OnClickListener listener) {
// just reset everything to display the dummy layout
mLayoutDummy.setVisibility(View.VISIBLE);
mLayoutData.setVisibility(View.GONE);
mSlinger.setVisibility(View.GONE);
mStatus.setVisibility(View.GONE);
mView.setClickable(false);
mLayoutDummy.setOnClickListener(listener);
}
} }
public boolean isEnabled(Cursor cursor) { public boolean isEnabled(Cursor cursor) {

View File

@@ -1,9 +1,10 @@
package org.sufficientlysecure.keychain.ui.util.adapter; package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.support.v7.widget.RecyclerView; import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.SparseBooleanArray; import android.util.SparseBooleanArray;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -14,15 +15,24 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.Highlighter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.adapter.*;
import org.sufficientlysecure.keychain.util.Log;
public class KeySectionedListAdapter extends SectionCursorAdapter<Character, SectionCursorAdapter.ViewHolder, KeySectionedListAdapter.KeyHeaderViewHolder> implements org.sufficientlysecure.keychain.ui.util.adapter.KeyAdapter {
private static final short VIEW_ITEM_TYPE_KEY = 0x0;
private static final short VIEW_ITEM_TYPE_DUMMY = 0x1;
private static final short VIEW_SECTION_TYPE_PRIVATE = 0x0;
private static final short VIEW_SECTION_TYPE_PUBLIC = 0x1;
public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySectionedListAdapter.KeyItemViewHolder, KeySectionedListAdapter.KeyHeaderViewHolder> implements KeyAdapter {
private String mQuery; private String mQuery;
private SparseBooleanArray mSelectionMap; private SparseBooleanArray mSelectionMap;
private boolean mHasDummy = false;
public KeySectionedListAdapter(Context context, Cursor cursor) { public KeySectionedListAdapter(Context context, Cursor cursor) {
super(context, cursor, 0); super(context, cursor, 0);
@@ -67,57 +77,148 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
} }
@Override @Override
protected String getSectionFromCursor(Cursor cursor) throws IllegalStateException { public void onContentChanged() {
mHasDummy = false;
super.onContentChanged();
}
/**
* Returns the number of database entries displayed.
* @return The item count
*/
public int getCount() {
if (getCursor() != null) {
return getCursor().getCount() - (mHasDummy ? 1 : 0);
} else {
return 0;
}
}
@Override
protected Character getSectionFromCursor(Cursor cursor) throws IllegalStateException {
if (cursor.getInt(INDEX_HAS_ANY_SECRET) != 0) { if (cursor.getInt(INDEX_HAS_ANY_SECRET) != 0) {
return getContext().getString(R.string.my_keys); if (cursor.getLong(INDEX_MASTER_KEY_ID) == 0L) {
} mHasDummy = true;
}
String userId = cursor.getString(INDEX_USER_ID); return '#';
String headerText = getContext().getString(R.string.user_id_no_name);
if (userId != null && userId.length() > 0) {
headerText = "" + userId.charAt(0);
}
return headerText;
}
@Override
protected KeyHeaderViewHolder onCreateSectionViewHolder(ViewGroup parent) {
return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.key_list_header, parent, false));
}
@Override
protected KeyItemViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
return new KeyItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.key_list_item, parent, false));
}
@Override
protected void onBindSectionViewHolder(KeyHeaderViewHolder holder, int sectionIndex, String section) {
System.out.println("SIX: " + sectionIndex);
if(sectionIndex == 0) {
holder.bind(section, getCursor().getCount());
} else { } else {
holder.bind(section); String userId = cursor.getString(INDEX_USER_ID);
if(TextUtils.isEmpty(userId)) {
return '?';
} else {
return Character.toUpperCase(userId.charAt(0));
}
} }
} }
@Override @Override
protected void onBindItemViewHolder(KeyItemViewHolder holder, Cursor cursor) { protected short getSectionHeaderViewType(int sectionIndex) {
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; return (sectionIndex < 1) ?
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); VIEW_SECTION_TYPE_PRIVATE :
if (isSecret && masterKeyId == 0L) { VIEW_SECTION_TYPE_PUBLIC;
holder.bindDummy(); }
@Override
protected short getSectionItemViewType(int position) {
if(moveCursor(position)) {
boolean hasMaster = getCursor().getLong(INDEX_MASTER_KEY_ID) != 0L;
boolean isSecret = getCursor().getInt(INDEX_HAS_ANY_SECRET) != 0;
if (isSecret && !hasMaster) {
return VIEW_ITEM_TYPE_DUMMY;
}
} else { } else {
Log.w(Constants.TAG, "Unable to determine key view type. "
+ "Reason: Could not move cursor over dataset.");
}
return VIEW_ITEM_TYPE_KEY;
}
@Override
protected KeyHeaderViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_SECTION_TYPE_PUBLIC:
return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.key_list_header_public, parent, false));
case VIEW_SECTION_TYPE_PRIVATE:
return new KeyHeaderViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.key_list_header_private, parent, false));
default:
return null;
}
}
@Override
protected ViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case VIEW_ITEM_TYPE_KEY:
return new KeyItemViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.key_list_item, parent, false));
case VIEW_ITEM_TYPE_DUMMY:
return new KeyDummyViewHolder(LayoutInflater.from(parent.getContext())
.inflate(R.layout.key_list_dummy, parent, false));
default:
return null;
}
}
@Override
protected void onBindSectionViewHolder(KeyHeaderViewHolder holder, Character section) {
switch (holder.getItemViewTypeWithoutSections()) {
case VIEW_SECTION_TYPE_PUBLIC: {
String title = section.equals('?') ?
getContext().getString(R.string.user_id_no_name) :
String.valueOf(section);
holder.bind(title);
break;
}
case VIEW_SECTION_TYPE_PRIVATE: {
int count = getCount();
String title = getContext().getResources()
.getQuantityString(R.plurals.n_keys, count, count);
holder.bind(title);
break;
}
}
}
@Override
protected void onBindItemViewHolder(ViewHolder holder, Cursor cursor) {
if (holder.getItemViewTypeWithoutSections() == VIEW_ITEM_TYPE_KEY) {
Highlighter highlighter = new Highlighter(getContext(), mQuery); Highlighter highlighter = new Highlighter(getContext(), mQuery);
holder.bindKey(new KeyItem(cursor), highlighter); ((KeyItemViewHolder) holder).bindKey(new KeyItem(cursor), highlighter);
} }
} }
static class KeyItemViewHolder extends RecyclerView.ViewHolder { private static class KeyDummyViewHolder extends SectionCursorAdapter.ViewHolder
private View mLayoutDummy; implements View.OnClickListener{
public KeyDummyViewHolder(View itemView) {
super(itemView);
itemView.setClickable(true);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
}
}
private static class KeyItemViewHolder extends SectionCursorAdapter.ViewHolder
implements View.OnClickListener, View.OnLongClickListener {
private View mLayoutData; private View mLayoutData;
private Long mMasterKeyId; private Long mMasterKeyId;
private TextView mMainUserId; private TextView mMainUserId;
@@ -130,22 +231,23 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
public KeyItemViewHolder(View itemView) { public KeyItemViewHolder(View itemView) {
super(itemView); super(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
mLayoutData = itemView.findViewById(R.id.key_list_item_data); mLayoutData = itemView.findViewById(R.id.key_list_item_data);
mLayoutDummy = itemView.findViewById(R.id.key_list_item_dummy);
mMainUserId = (TextView) itemView.findViewById(R.id.key_list_item_name); mMainUserId = (TextView) itemView.findViewById(R.id.key_list_item_name);
mMainUserIdRest = (TextView) itemView.findViewById(R.id.key_list_item_email); mMainUserIdRest = (TextView) itemView.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) itemView.findViewById(R.id.key_list_item_status_icon); mStatus = (ImageView) itemView.findViewById(R.id.key_list_item_status_icon);
mSlinger = itemView.findViewById(R.id.key_list_item_slinger_view); mSlinger = itemView.findViewById(R.id.key_list_item_slinger_view);
mSlingerButton = (ImageButton) itemView.findViewById(R.id.key_list_item_slinger_button); mSlingerButton = (ImageButton) itemView.findViewById(R.id.key_list_item_slinger_button);
mCreationDate = (TextView) itemView.findViewById(R.id.key_list_item_creation); mCreationDate = (TextView) itemView.findViewById(R.id.key_list_item_creation);
mSlingerButton.setOnClickListener(this);
} }
public void bindKey(KeyItem keyItem, Highlighter highlighter) { public void bindKey(KeyItem keyItem, Highlighter highlighter) {
Context ctx = itemView.getContext(); Context ctx = itemView.getContext();
mLayoutData.setVisibility(View.VISIBLE);
mLayoutDummy.setVisibility(View.GONE);
{ // set name and stuff, common to both key types { // set name and stuff, common to both key types
OpenPgpUtils.UserId userIdSplit = keyItem.mUserId; OpenPgpUtils.UserId userIdSplit = keyItem.mUserId;
if (userIdSplit.name != null) { if (userIdSplit.name != null) {
@@ -177,12 +279,12 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
.setStatusImage(ctx, mStatus, null, KeyFormattingUtils.State.REVOKED, R.color.key_flag_gray); .setStatusImage(ctx, mStatus, null, KeyFormattingUtils.State.REVOKED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE); mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE); mSlinger.setVisibility(View.GONE);
textColor = ctx.getResources().getColor(R.color.key_flag_gray); textColor = ContextCompat.getColor(ctx, R.color.key_flag_gray);
} else if (keyItem.mIsExpired) { } else if (keyItem.mIsExpired) {
KeyFormattingUtils.setStatusImage(ctx, mStatus, null, KeyFormattingUtils.State.EXPIRED, R.color.key_flag_gray); KeyFormattingUtils.setStatusImage(ctx, mStatus, null, KeyFormattingUtils.State.EXPIRED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE); mStatus.setVisibility(View.VISIBLE);
mSlinger.setVisibility(View.GONE); mSlinger.setVisibility(View.GONE);
textColor = ctx.getResources().getColor(R.color.key_flag_gray); textColor = ContextCompat.getColor(ctx, R.color.key_flag_gray);
} else if (keyItem.mIsSecret) { } else if (keyItem.mIsSecret) {
mStatus.setVisibility(View.GONE); mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) { if (mSlingerButton.hasOnClickListeners()) {
@@ -228,41 +330,27 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
} }
} }
public void bindDummy() { @Override
// just reset everything to display the dummy layout public void onClick(View v) {
mLayoutDummy.setVisibility(View.VISIBLE);
mLayoutData.setVisibility(View.GONE); }
mSlinger.setVisibility(View.GONE);
mStatus.setVisibility(View.GONE); @Override
itemView.setClickable(false); public boolean onLongClick(View v) {
return false;
} }
} }
static class KeyHeaderViewHolder extends RecyclerView.ViewHolder { static class KeyHeaderViewHolder extends SectionCursorAdapter.ViewHolder {
private TextView mHeaderText; private TextView mText1;
private TextView mHeaderCount;
public KeyHeaderViewHolder(View itemView) { public KeyHeaderViewHolder(View itemView) {
super(itemView); super(itemView);
mText1 = (TextView) itemView.findViewById(android.R.id.text1);
mHeaderText = (TextView) itemView.findViewById(R.id.stickylist_header_text);
mHeaderCount = (TextView) itemView.findViewById(R.id.contacts_num);
}
public void bind(String title, int count) {
mHeaderText.setText(title);
String contactsTotal = itemView.getResources()
.getQuantityString(R.plurals.n_keys, count, count);
mHeaderCount.setText(contactsTotal);
mHeaderCount.setVisibility(View.VISIBLE);
} }
public void bind(String title) { public void bind(String title) {
mHeaderText.setText(title); mText1.setText(title);
mHeaderCount.setVisibility(View.GONE);
} }
} }
} }

View File

@@ -49,14 +49,14 @@ public interface KeyAdapter {
boolean isSecretAvailable(int position); boolean isSecretAvailable(int position);
class KeyItem implements Serializable { class KeyItem implements Serializable {
final String mUserIdFull; public final String mUserIdFull;
final OpenPgpUtils.UserId mUserId; public final OpenPgpUtils.UserId mUserId;
final long mKeyId; public final long mKeyId;
final boolean mHasDuplicate; public final boolean mHasDuplicate;
final boolean mHasEncrypt; public final boolean mHasEncrypt;
final Date mCreation; public final Date mCreation;
final String mFingerprint; public final String mFingerprint;
final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified; public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified;
public KeyItem(Cursor cursor) { public KeyItem(Cursor cursor) {
String userId = cursor.getString(INDEX_USER_ID); String userId = cursor.getString(INDEX_USER_ID);

View File

@@ -4,35 +4,26 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.support.v4.util.SparseArrayCompat; import android.support.v4.util.SparseArrayCompat;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.SectionIndexer;
import com.tonicartos.superslim.GridSLM;
import com.tonicartos.superslim.LayoutManager; import com.tonicartos.superslim.LayoutManager;
import com.tonicartos.superslim.LinearSLM;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.lang.annotation.Inherited;
import java.util.ArrayList;
import java.util.List;
/** /**
* @param <T> section type. * @param <T> section type.
* @param <VH> the view holder extending {@code BaseViewHolder<Cursor>} that is bound to the cursor data. * @param <VH> the view holder extending {@code BaseViewHolder<Cursor>} that is bound to the cursor data.
* @param <SH> the view holder extending {@code BaseViewHolder<<T>>} that is bound to the section data. * @param <SH> the view holder extending {@code BaseViewHolder<<T>>} that is bound to the section data.
*/ */
public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder, SH extends RecyclerView.ViewHolder> public abstract class SectionCursorAdapter<T, VH extends SectionCursorAdapter.ViewHolder,
extends CursorAdapter implements SectionIndexer { SH extends SectionCursorAdapter.ViewHolder> extends CursorAdapter {
public static final String TAG = "SectionCursorAdapter"; public static final String TAG = "SectionCursorAdapter";
private static final int VIEW_TYPE_ITEM = 0x0; private static final short VIEW_TYPE_ITEM = 0x1;
private static final int VIEW_TYPE_SECTION = 0x1; private static final short VIEW_TYPE_SECTION = 0x2;
private SparseArrayCompat<T> mSectionMap = new SparseArrayCompat<>(); private SparseArrayCompat<T> mSectionMap = new SparseArrayCompat<>();
private ArrayList<Integer> mSectionIndexList = new ArrayList<>();
private Comparator<T> mSectionComparator; private Comparator<T> mSectionComparator;
private Object[] mFastScrollItems;
public SectionCursorAdapter(Context context, Cursor cursor, int flags) { public SectionCursorAdapter(Context context, Cursor cursor, int flags) {
this(context, cursor, flags, new Comparator<T>() { this(context, cursor, flags, new Comparator<T>() {
@@ -55,8 +46,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
buildSections(); buildSections();
} else { } else {
mSectionMap.clear(); mSectionMap.clear();
mSectionIndexList.clear();
mFastScrollItems = null;
} }
super.onContentChanged(); super.onContentChanged();
@@ -81,8 +70,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
moveCursor(-1); moveCursor(-1);
try { try {
mSectionMap.clear(); mSectionMap.clear();
mSectionIndexList.clear();
mFastScrollItems = null;
appendSections(getCursor()); appendSections(getCursor());
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
@@ -91,13 +78,11 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
swapCursor(null); swapCursor(null);
mSectionMap.clear(); mSectionMap.clear();
mSectionIndexList.clear();
mFastScrollItems = null;
} }
} }
} }
protected void appendSections(Cursor cursor) throws IllegalStateException { private void appendSections(Cursor cursor) throws IllegalStateException {
int cursorPosition = 0; int cursorPosition = 0;
while(hasValidData() && cursor.moveToNext()) { while(hasValidData() && cursor.moveToNext()) {
T section = getSectionFromCursor(cursor); T section = getSectionFromCursor(cursor);
@@ -125,9 +110,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
* This object will be passed to newSectionView and bindSectionView. * This object will be passed to newSectionView and bindSectionView.
*/ */
protected abstract T getSectionFromCursor(Cursor cursor) throws IllegalStateException; protected abstract T getSectionFromCursor(Cursor cursor) throws IllegalStateException;
protected String getTitleFromSection(T section) {
return section != null ? section.toString() : "";
}
@Override @Override
public int getItemCount() { public int getItemCount() {
@@ -174,57 +156,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
} }
} }
/**
* Get the section object for the index within the array of sections.
* @param sectionPosition The section index.
* @return The specified section object for this position.
*/
public T getSection(int sectionPosition) {
if (mSectionIndexList.contains(sectionPosition)) {
return mSectionMap.get(mSectionIndexList.get(sectionPosition));
}
return null;
}
/**
* Returns all indices at which the first item of a section is placed.
* @return The first index of each section.
*/
public List<Integer> getSectionListPositions() {
return mSectionIndexList;
}
/**
* {@inheritDoc}
*/
@Override
public int getPositionForSection(int sectionIndex) {
if (mSectionIndexList.isEmpty()) {
for (int i = 0; i < mSectionMap.size(); i++) {
mSectionIndexList.add(mSectionMap.keyAt(i));
}
}
return sectionIndex < mSectionIndexList.size() ?
mSectionIndexList.get(sectionIndex) : getItemCount();
}
/**
* Given the position of a section, returns its index in the array of sections.
* @param sectionPosition The first position of the corresponding section in the array of items.
* @return The section index in the array of sections.
*/
public int getSectionIndexForPosition(int sectionPosition) {
if (mSectionIndexList.isEmpty()) {
for (int i = 0; i < mSectionMap.size(); i++) {
mSectionIndexList.add(mSectionMap.keyAt(i));
}
}
return mSectionIndexList.indexOf(sectionPosition);
}
/** /**
* Given the list position of an item in the adapter, returns the * Given the list position of an item in the adapter, returns the
* adapter position of the first item of the section the given item belongs to. * adapter position of the first item of the section the given item belongs to.
@@ -242,10 +173,7 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
return start; return start;
} }
/**
* {@inheritDoc}
*/
@Override
public int getSectionForPosition(int listPosition) { public int getSectionForPosition(int listPosition) {
boolean isSection = false; boolean isSection = false;
int numPrecedingSections = 0; int numPrecedingSections = 0;
@@ -264,27 +192,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
return isSection ? numPrecedingSections : Math.max(numPrecedingSections - 1, 0); return isSection ? numPrecedingSections : Math.max(numPrecedingSections - 1, 0);
} }
@Override
public Object[] getSections() {
if(mFastScrollItems == null) {
mFastScrollItems = getSectionLabels();
}
return mFastScrollItems;
}
private Object[] getSectionLabels() {
if(mSectionMap == null)
return new Object[0];
String[] ret = new String[mSectionMap.size()];
for(int i = 0; i < ret.length; i++) {
ret[i] = getTitleFromSection(mSectionMap.valueAt(i));
}
return ret;
}
private boolean isListPositionBeforeFirstSection(int listPosition, int sectionIndex) { private boolean isListPositionBeforeFirstSection(int listPosition, int sectionIndex) {
boolean hasSections = mSectionMap != null && mSectionMap.size() > 0; boolean hasSections = mSectionMap != null && mSectionMap.size() > 0;
return sectionIndex == 0 && hasSections && listPosition < mSectionMap.keyAt(0); return sectionIndex == 0 && hasSections && listPosition < mSectionMap.keyAt(0);
@@ -292,7 +199,21 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
@Override @Override
public final int getItemViewType(int listPosition) { public final int getItemViewType(int listPosition) {
return isSection(listPosition) ? VIEW_TYPE_SECTION : VIEW_TYPE_ITEM; int sectionIndex = mSectionMap.indexOfKey(listPosition);
if(sectionIndex < 0) {
int cursorPosition = getCursorPositionWithoutSections(listPosition);
return (getSectionItemViewType(cursorPosition) << 16) | VIEW_TYPE_ITEM;
} else {
return (getSectionHeaderViewType(sectionIndex) << 16) | VIEW_TYPE_SECTION;
}
}
protected short getSectionHeaderViewType(int sectionIndex) {
return 0;
}
protected short getSectionItemViewType(int position) {
return 0;
} }
@Override @Override
@@ -304,7 +225,8 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
// assign first position of section to each item // assign first position of section to each item
layoutParams.setFirstPosition(getFirstSectionPosition(position)); layoutParams.setFirstPosition(getFirstSectionPosition(position));
switch (holder.getItemViewType()) { int viewType = holder.getItemViewType() & 0xFF;
switch (viewType) {
case VIEW_TYPE_ITEM : case VIEW_TYPE_ITEM :
moveCursorOrThrow(getCursorPositionWithoutSections(position)); moveCursorOrThrow(getCursorPositionWithoutSections(position));
onBindItemViewHolder((VH) holder, getCursor()); onBindItemViewHolder((VH) holder, getCursor());
@@ -314,8 +236,7 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
case VIEW_TYPE_SECTION: case VIEW_TYPE_SECTION:
T section = mSectionMap.get(position); T section = mSectionMap.get(position);
int sectionIndex = getSectionIndexForPosition(position); onBindSectionViewHolder((SH) holder, section);
onBindSectionViewHolder((SH) holder, sectionIndex, section);
layoutParams.isHeader = true; layoutParams.isHeader = true;
break; break;
@@ -326,26 +247,45 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
@Override @Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) { switch (viewType & 0xFF) {
case VIEW_TYPE_SECTION: case VIEW_TYPE_SECTION:
return onCreateSectionViewHolder(parent); return onCreateSectionViewHolder(parent, viewType >> 16);
case VIEW_TYPE_ITEM: case VIEW_TYPE_ITEM:
return onCreateItemViewHolder(parent, viewType); return onCreateItemViewHolder(parent, viewType >> 16);
default: default:
return null; return null;
} }
} }
protected abstract SH onCreateSectionViewHolder(ViewGroup parent); protected abstract SH onCreateSectionViewHolder(ViewGroup parent, int viewType);
protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType); protected abstract VH onCreateItemViewHolder(ViewGroup parent, int viewType);
protected abstract void onBindSectionViewHolder(SH holder, int sectionIndex, T section); protected abstract void onBindSectionViewHolder(SH holder, T section);
protected abstract void onBindItemViewHolder(VH holder, Cursor cursor); protected abstract void onBindItemViewHolder(VH holder, Cursor cursor);
public interface Comparator<T> { public interface Comparator<T> {
boolean equal(T obj1, T obj2); boolean equal(T obj1, T obj2);
} }
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
super(itemView);
}
/**
* Returns the view type assigned in
* {@link SectionCursorAdapter#getSectionHeaderViewType(int)} or
* {@link SectionCursorAdapter#getSectionItemViewType(int)}
*
* Note that a call to {@link #getItemViewType()} will return a value that contains
* internal stuff necessary to distinguish sections from items.
* @return The view type you set.
*/
public short getItemViewTypeWithoutSections(){
return (short) (getItemViewType() >> 16);
}
}
} }

View File

@@ -0,0 +1,397 @@
/*
* Implementation of taken from the sourcecode of
* android.support.v4.app.ListFragment from the
* Android Open Source Project and changed to use
* RecyclerView instead of ListView.
*/
/*
* Copyright (C) 2013 The Android Open Source Project
*
* 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.sufficientlysecure.keychain.ui.util.recyclerview;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.sufficientlysecure.keychain.util.Log;
public class RecyclerFragment<A extends RecyclerView.Adapter> extends Fragment {
private static final int INTERNAL_LIST_VIEW_ID = android.R.id.list;
private static final int INTERNAL_EMPTY_VIEW_ID = android.R.id.empty;
private static final int INTERNAL_LIST_CONTAINER_ID = android.R.id.widget_frame;
private static final int INTERNAL_PROGRESS_CONTAINER_ID = android.R.id.progress;
private final Handler handler = new Handler();
private final Runnable requestFocus = new Runnable() {
@Override
public void run() {
listView.focusableViewAvailable(listView);
}
};
private boolean observerRegistered = false;
private final RecyclerView.AdapterDataObserver dataObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
checkDataSet();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkDataSet();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkDataSet();
}
};
private final RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
RecyclerFragment.this.onScrolled(dx, dy);
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
RecyclerFragment.this.onScrollStateChanged(newState);
}
};
private A adapter;
private RecyclerView.LayoutManager layoutManager;
private RecyclerView listView;
private View emptyView;
private View progressContainer;
private View listContainer;
private boolean listShown;
public RecyclerFragment() {
super();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
final Context context = parent.getContext();
FrameLayout root = new FrameLayout(context);
LinearLayout progressContainer = new LinearLayout(context);
progressContainer.setId(INTERNAL_PROGRESS_CONTAINER_ID);
progressContainer.setOrientation(LinearLayout.VERTICAL);
progressContainer.setGravity(Gravity.CENTER);
progressContainer.setVisibility(View.GONE);
ProgressBar progressBar = new ProgressBar(context, null,
android.R.attr.progressBarStyleLarge);
progressContainer.addView(progressBar, new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
root.addView(progressContainer, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
FrameLayout listContainer = new FrameLayout(context);
listContainer.setId(INTERNAL_LIST_CONTAINER_ID);
TextView textView = new TextView(context);
textView.setId(INTERNAL_EMPTY_VIEW_ID);
textView.setGravity(Gravity.CENTER);
listContainer.addView(textView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
RecyclerView listView = new RecyclerView(context);
listView.setId(INTERNAL_LIST_VIEW_ID);
listContainer.addView(listView, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
root.addView(listContainer, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
root.setLayoutParams(new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return root;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ensureList();
}
@Override
public void onDestroyView() {
handler.removeCallbacks(requestFocus);
listView.setLayoutManager(null);
listView.removeOnScrollListener(scrollListener);
listView = null;
listShown = false;
listContainer = null;
layoutManager = null;
emptyView = null;
progressContainer = null;
super.onDestroyView();
}
@Override
public void onDestroy() {
setAdapter(null);
super.onDestroy();
}
public Handler getHandler() {
return handler;
}
public void onScrollStateChanged(int state) {
// empty body
}
public void onScrolled(int dx, int dy) {
// empty body
}
public void setAdapter(A adapter) {
unregisterObserver();
boolean hadAdapter = this.adapter != null;
this.adapter = adapter;
registerObserver();
if(listView != null) {
listView.setAdapter(adapter);
if(!listShown && !hadAdapter) {
if(getView() != null)
setListShown(true, getView().getWindowToken() != null);
}
}
}
public void setLayoutManager(RecyclerView.LayoutManager manager) {
if(!manager.isAttachedToWindow()) {
layoutManager = manager;
if (listView != null) {
listView.setLayoutManager(manager);
}
}
}
public int getItemCount() {
if(adapter != null)
return adapter.getItemCount();
else
return 0;
}
public long getItemId(int position) {
if(adapter != null)
return adapter.getItemId(position);
else
return View.NO_ID;
}
public RecyclerView getRecyclerView() {
ensureList();
return listView;
}
public RecyclerView.LayoutManager getLayoutManager() {
ensureList();
return layoutManager;
}
private void registerObserver() {
if(!observerRegistered && adapter != null) {
adapter.registerAdapterDataObserver(dataObserver);
observerRegistered = true;
}
}
private void unregisterObserver() {
if(observerRegistered && adapter != null) {
adapter.unregisterAdapterDataObserver(dataObserver);
observerRegistered = false;
}
}
private void checkDataSet() {
boolean empty = treatAsEmpty(getItemCount());
Log.d("RecyclerFragment", "Dataset change detected! Count: "
+ getItemCount() + ", Empty: " + empty);
if(emptyView != null) {
emptyView.setVisibility(empty ? View.VISIBLE : View.GONE);
}
}
/**
* Set whether the data set of the recycler view should be treated as empty.
* This is useful e.g. if you have an empty padding row and therefore the item
* count is always greater than 0.
*
* @param itemCount the number of items in the data set.
* @return Whether to treat this as an empty set of data
*/
protected boolean treatAsEmpty(int itemCount) {
return itemCount < 1;
}
/**
* Set whether the recycler view should have a fixed size or not
*/
protected boolean isFixedSize() {
return true;
}
public void hideList(boolean animated) {
setListShown(false, animated);
}
public void showList(boolean animated) {
setListShown(true, animated);
}
private void setListShown(boolean shown, boolean animated) {
ensureList();
if(progressContainer == null)
throw new IllegalStateException("Can't be used with a custom content view");
if(listShown == shown)
return;
listShown = shown;
if(shown) {
if (animated) {
progressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
listContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
} else {
progressContainer.clearAnimation();
listContainer.clearAnimation();
}
progressContainer.setVisibility(View.GONE);
listContainer.setVisibility(View.VISIBLE);
} else {
if (animated) {
progressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
listContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
} else {
progressContainer.clearAnimation();
listContainer.clearAnimation();
}
progressContainer.setVisibility(View.VISIBLE);
listContainer.setVisibility(View.GONE);
}
}
public A getAdapter() {
return adapter;
}
@SuppressWarnings("unchecked")
private void ensureList() {
if (listView != null)
return;
View root = getView();
if (root == null)
throw new IllegalStateException("Content view not yet created");
if (root instanceof RecyclerView) {
listView = (RecyclerView) root;
} else {
emptyView = root.findViewById(INTERNAL_EMPTY_VIEW_ID);
if(emptyView != null) {
emptyView.setVisibility(View.GONE);
}
progressContainer = root.findViewById(INTERNAL_PROGRESS_CONTAINER_ID);
listContainer = root.findViewById(INTERNAL_LIST_CONTAINER_ID);
View rawListView = root.findViewById(INTERNAL_LIST_VIEW_ID);
if (!(rawListView instanceof RecyclerView)) {
if (rawListView == null) {
throw new RuntimeException(
"Your content must have a RecyclerView whose id attribute is " +
"'android.R.id.list'");
}
throw new RuntimeException(
"Content has view with id attribute 'android.R.id.list' "
+ "that is not a ListView class");
}
listView = (RecyclerView) rawListView;
}
if(layoutManager != null) {
RecyclerView.LayoutManager manager = layoutManager;
this.layoutManager = null;
setLayoutManager(manager);
}
listShown = true;
listView.setHasFixedSize(isFixedSize());
listView.addOnScrollListener(scrollListener);
if (this.adapter != null) {
A adapter = this.adapter;
this.adapter = null;
setAdapter(adapter);
} else {
// We are starting without an adapter, so assume we won't
// have our data right away and start with the progress indicator.
if (progressContainer != null) {
setListShown(false, false);
}
}
handler.post(requestFocus);
}
}

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:orientation="horizontal"
android:focusable="true"
android:background="?android:selectableItemBackground">
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingStart="8dp"
android:paddingRight="4dp"
android:paddingEnd="4dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="You don't have any keys yet!"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="1"
android:text="Click here to create or import one."
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:src="@drawable/ic_key_plus_grey600_24dp" />
</LinearLayout>

View File

@@ -1,115 +1,140 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:fab="http://schemas.android.com/apk/res-auto" <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:fab="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto" xmlns:custom="http://schemas.android.com/apk/res-auto"
> xmlns:android="http://schemas.android.com/apk/res/android"
<!--rebuild functionality of ListFragment --> android:layout_width="match_parent"
<FrameLayout android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@android:id/progress"
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:gravity="center">
<android.support.v7.widget.RecyclerView <ProgressBar
android:id="@+id/key_list_list" style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center" />
</LinearLayout>
<RelativeLayout
android:id="@android:id/widget_frame"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:visibility="gone">
<!--rebuild functionality of ListFragment -->
<FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:scrollbarStyle="outsideOverlay" />
<LinearLayout <android.support.v7.widget.RecyclerView
android:id="@+id/key_list_empty" android:id="@android:id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="240dp" android:layout_height="match_parent"
android:gravity="center" android:paddingLeft="16dp"
android:orientation="vertical" android:paddingRight="32dp"
android:animateLayoutChanges="true" android:clipToPadding="false"
> android:scrollbarStyle="outsideOverlay" />
<TextView <LinearLayout
android:layout_width="wrap_content" android:id="@android:id/empty"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_height="240dp"
android:gravity="center" android:gravity="center"
android:text="@string/key_list_empty_text1" android:orientation="vertical"
android:textAppearance="?android:attr/textAppearanceLarge" /> android:animateLayoutChanges="true" >
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_container"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
android:measureAllChildren="true"
custom:initialView="1">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:id="@+id/search_button"
android:gravity="center" android:gravity="center"
tools:text="@string/btn_search_for_query" android:text="@string/key_list_empty_text1"
/> android:textAppearance="?android:attr/textAppearanceLarge" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> <org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_container"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
android:measureAllChildren="true"
custom:initialView="1">
</LinearLayout> <Space
</FrameLayout> android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.getbase.floatingactionbutton.FloatingActionsMenu <Button
android:id="@+id/fab_main" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="24dp"
android:layout_alignParentRight="true" android:id="@+id/search_button"
android:layout_alignParentEnd="true" android:gravity="center"
android:layout_alignParentBottom="true" tools:text="@string/btn_search_for_query"/>
fab:fab_addButtonColorNormal="?attr/colorPrimary"
fab:fab_addButtonColorPressed="?attr/colorPrimaryDark"
fab:fab_addButtonSize="normal"
fab:fab_addButtonPlusIconColor="@color/icons"
fab:fab_expandDirection="up"
fab:fab_labelStyle="@style/FabMenuStyle"
android:layout_marginBottom="8dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
>
<com.getbase.floatingactionbutton.FloatingActionButton </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
android:id="@+id/fab_add_qr_code"
</LinearLayout>
</FrameLayout>
<com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/fab_main"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_qrcode_white_24dp" android:layout_alignParentRight="true"
fab:fab_colorNormal="?attr/colorPrimary" android:layout_alignParentEnd="true"
fab:fab_colorPressed="?attr/colorPrimaryDark" android:layout_alignParentBottom="true"
fab:fab_title="@string/key_list_fab_qr_code" fab:fab_addButtonColorNormal="?attr/colorPrimary"
fab:fab_size="mini" /> fab:fab_addButtonColorPressed="?attr/colorPrimaryDark"
fab:fab_addButtonSize="normal"
fab:fab_addButtonPlusIconColor="@color/icons"
fab:fab_expandDirection="up"
fab:fab_labelStyle="@style/FabMenuStyle"
android:layout_marginBottom="8dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp">
<com.getbase.floatingactionbutton.FloatingActionButton <com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_cloud" android:id="@+id/fab_add_qr_code"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_cloud_search_24dp" fab:fab_icon="@drawable/ic_qrcode_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary" fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark" fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_search" fab:fab_title="@string/key_list_fab_qr_code"
fab:fab_size="mini" /> fab:fab_size="mini" />
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_cloud"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_cloud_search_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_search"
fab:fab_size="mini" />
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_folder_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_import"
fab:fab_size="mini" />
</com.getbase.floatingactionbutton.FloatingActionsMenu>
</RelativeLayout>
</FrameLayout>
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_folder_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_import"
fab:fab_size="mini" />
</com.getbase.floatingactionbutton.FloatingActionsMenu>
</RelativeLayout>

View File

@@ -12,25 +12,23 @@
super:slm_section_sectionManager="linear" super:slm_section_sectionManager="linear"
tools:ignore="ResAuto"> tools:ignore="ResAuto">
<TextView <TextView style="@style/SectionHeader"
style="@style/SectionHeader"
android:id="@+id/stickylist_header_text"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start|left" android:layout_gravity="start|left"
android:text="header text" /> android:text="@string/my_keys" />
<TextView <TextView
android:id="@android:id/text1"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:text="key count"
android:id="@+id/contacts_num"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:visibility="gone" android:layout_marginLeft="8dp"
android:textColor="@android:color/darker_gray" /> android:textColor="@android:color/darker_gray"
tools:text="11 Keys"/>
</RelativeLayout> </RelativeLayout>

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:super="http://schemas.android.com/apk/lib-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:colorBackground"
super:slm_headerDisplay="sticky|inline"
super:slm_section_sectionManager="linear"
tools:ignore="ResAuto">
<TextView style="@style/SectionHeader"
android:id="@android:id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|left"
tools:text="A" />
</FrameLayout>

View File

@@ -1,63 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight" android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical" android:gravity="center_vertical"
android:singleLine="true" android:maxLines="1"
android:orientation="horizontal" android:orientation="horizontal"
android:descendantFocusability="blocksDescendants" android:descendantFocusability="blocksDescendants"
android:focusable="false"> android:focusable="false">
<LinearLayout
android:id="@+id/key_list_item_dummy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:focusable="true"
android:visibility="gone"
android:background="?android:selectableItemBackground"
tools:visibility="visible">
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:orientation="vertical"
android:paddingLeft="8dp"
android:paddingRight="4dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="You don't have any keys yet!"
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="Click here to create or import one."
android:textAppearance="?android:attr/textAppearanceSmall" />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:src="@drawable/ic_key_plus_grey600_24dp"
/>
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/key_list_item_data" android:id="@+id/key_list_item_data"
android:layout_width="0dip" android:layout_width="0dip"
@@ -83,7 +37,7 @@
android:id="@+id/key_list_item_email" android:id="@+id/key_list_item_email"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:maxLines="1"
android:ellipsize="end" android:ellipsize="end"
tools:text="user@example.com" tools:text="user@example.com"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall" />
@@ -92,12 +46,11 @@
android:id="@+id/key_list_item_creation" android:id="@+id/key_list_item_creation"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="visible"
tools:text="Created on 10/10/2010 10:00" tools:text="Created on 10/10/2010 10:00" />
/>
</LinearLayout> </LinearLayout>
@@ -134,7 +87,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:padding="16dp" android:padding="16dp"
tools:src="@drawable/status_signature_revoked_cutout_24dp" tools:src="@drawable/status_signature_revoked_cutout_24dp" />
/>
</LinearLayout> </LinearLayout>