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.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
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.Loader;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
import android.text.TextUtils;
import android.view.ActionMode;
@@ -46,11 +43,8 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.ViewAnimator;
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.ui.adapter.KeyAdapter;
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.ContentDescriptionHint;
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.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
* StickyListHeaders library which does not extend upon ListView.
*/
public class KeyListFragment extends LoaderFragment
public class KeyListFragment extends RecyclerFragment<KeySectionedListAdapter>
implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor>, FabContainer,
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_VIEW_KEY = 3;
//private KeyListAdapter mAdapter;
private KeySectionedListAdapter mAdapter;
private RecyclerView mStickyList;
// saves the mode object for multiselect, needed for reset at some point
private ActionMode mActionMode = null;
@@ -126,12 +112,8 @@ public class KeyListFragment extends LoaderFragment
* Load custom layout with StickyListView from library
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.key_list_fragment, getContainer());
mStickyList = (RecyclerView) view.findViewById(R.id.key_list_list);
//mStickyList.setOnItemClickListener(this);
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.key_list_fragment, container, false);
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.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.
View footer = new View(activity);
@@ -197,6 +179,7 @@ public class KeyListFragment extends LoaderFragment
footer.setLayoutParams(params);
//mStickyList.addFooterView(footer, null, false);
*/
/*
* Multi-selection
@@ -269,7 +252,7 @@ public class KeyListFragment extends LoaderFragment
setHasOptionsMenu(true);
// Start out with a progress indicator.
setContentShown(false);
hideList(true);
// this view is made visible if no data is available
// 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.
//mAdapter = new KeyListAdapter(activity, null, 0);
mAdapter = new KeySectionedListAdapter(getContext(), null);
mStickyList.setAdapter(mAdapter);
mStickyList.setLayoutManager(new LayoutManager(getActivity()));
setAdapter(new KeySectionedListAdapter(getContext(), null));
setLayoutManager(new LayoutManager(getActivity()));
// Prepare the loader. Either re-connect with an existing 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
// 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
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.setSearchQuery(mQuery);
getAdapter().setSearchQuery(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) {
MatrixCursor headerCursor = new MatrixCursor(KeyListAdapter.PROJECTION);
Long[] row = new Long[KeyListAdapter.PROJECTION.length];
row[KeyListAdapter.INDEX_HAS_ANY_SECRET] = 1L;
row[KeyListAdapter.INDEX_MASTER_KEY_ID] = 0L;
MatrixCursor headerCursor = new MatrixCursor(KeyAdapter.PROJECTION);
Long[] row = new Long[KeyAdapter.PROJECTION.length];
row[KeyAdapter.INDEX_HAS_ANY_SECRET] = 1L;
row[KeyAdapter.INDEX_MASTER_KEY_ID] = 0L;
headerCursor.addRow(row);
Cursor dataCursor = data;
@@ -348,7 +330,8 @@ public class KeyListFragment extends LoaderFragment
});
}
}
mAdapter.swapCursor(data);
getAdapter().swapCursor(data);
// end action mode, if any
if (mActionMode != null) {
@@ -357,9 +340,9 @@ public class KeyListFragment extends LoaderFragment
// The list should now be shown.
if (isResumed()) {
setContentShown(true);
showList(true);
} else {
setContentShownNoAnimation(true);
showList(false);
}
}
@@ -368,7 +351,7 @@ public class KeyListFragment extends LoaderFragment
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// 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) {
Intent viewIntent = new Intent(getActivity(), ViewKeyActivity.class);
viewIntent.setData(
KeyRings.buildGenericKeyRingUri(mAdapter.getMasterKeyId(position)));
KeyRings.buildGenericKeyRingUri(getAdapter().getMasterKeyId(position)));
startActivityForResult(viewIntent, REQUEST_VIEW_KEY);
}
@@ -762,6 +745,7 @@ public class KeyListFragment extends LoaderFragment
return false;
}
/*
public class KeyListAdapter extends KeyAdapter implements StickyListHeadersAdapter {
private HashMap<Integer, Boolean> mSelection = new HashMap<>();
@@ -842,19 +826,13 @@ public class KeyListFragment extends LoaderFragment
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
public View getHeaderView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
if (convertView == null) {
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.mCount = (TextView) convertView.findViewById(R.id.contacts_num);
convertView.setTag(holder);
@@ -899,9 +877,6 @@ public class KeyListFragment extends LoaderFragment
return convertView;
}
/**
* Header IDs should be static, position=1 should always return the same Id that is.
*/
@Override
public long getHeaderId(int position) {
if (!mDataValid) {
@@ -927,9 +902,6 @@ public class KeyListFragment extends LoaderFragment
}
}
/**
* -------------------------- MULTI-SELECTION METHODS --------------
*/
public void setNewSelection(int position, boolean value) {
mSelection.put(position, value);
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 View mView;
public View mLayoutDummy;
public View mLayoutData;
public Long mMasterKeyId;
public TextView mMainUserId;
@@ -109,7 +108,6 @@ public class KeyAdapter extends CursorAdapter {
public KeyItemViewHolder(View view) {
mView = view;
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);
mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email);
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) {
mLayoutData.setVisibility(View.VISIBLE);
mLayoutDummy.setVisibility(View.GONE);
mDisplayedItem = item;
{ // set name and stuff, common to both key types
@@ -207,25 +201,8 @@ public class KeyAdapter extends CursorAdapter {
} else {
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) {

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.database.Cursor;
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.util.SparseBooleanArray;
import android.view.LayoutInflater;
@@ -14,15 +15,24 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.openintents.openpgp.util.OpenPgpUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Highlighter;
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 SparseBooleanArray mSelectionMap;
private boolean mHasDummy = false;
public KeySectionedListAdapter(Context context, Cursor cursor) {
super(context, cursor, 0);
@@ -67,57 +77,148 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
}
@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) {
return getContext().getString(R.string.my_keys);
}
if (cursor.getLong(INDEX_MASTER_KEY_ID) == 0L) {
mHasDummy = true;
}
String userId = cursor.getString(INDEX_USER_ID);
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());
return '#';
} else {
holder.bind(section);
String userId = cursor.getString(INDEX_USER_ID);
if(TextUtils.isEmpty(userId)) {
return '?';
} else {
return Character.toUpperCase(userId.charAt(0));
}
}
}
@Override
protected void onBindItemViewHolder(KeyItemViewHolder holder, Cursor cursor) {
boolean isSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0;
long masterKeyId = cursor.getLong(INDEX_MASTER_KEY_ID);
if (isSecret && masterKeyId == 0L) {
holder.bindDummy();
protected short getSectionHeaderViewType(int sectionIndex) {
return (sectionIndex < 1) ?
VIEW_SECTION_TYPE_PRIVATE :
VIEW_SECTION_TYPE_PUBLIC;
}
@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 {
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);
holder.bindKey(new KeyItem(cursor), highlighter);
((KeyItemViewHolder) holder).bindKey(new KeyItem(cursor), highlighter);
}
}
static class KeyItemViewHolder extends RecyclerView.ViewHolder {
private View mLayoutDummy;
private static class KeyDummyViewHolder extends SectionCursorAdapter.ViewHolder
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 Long mMasterKeyId;
private TextView mMainUserId;
@@ -130,22 +231,23 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
public KeyItemViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
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);
mMainUserIdRest = (TextView) itemView.findViewById(R.id.key_list_item_email);
mStatus = (ImageView) itemView.findViewById(R.id.key_list_item_status_icon);
mSlinger = itemView.findViewById(R.id.key_list_item_slinger_view);
mSlingerButton = (ImageButton) itemView.findViewById(R.id.key_list_item_slinger_button);
mCreationDate = (TextView) itemView.findViewById(R.id.key_list_item_creation);
mSlingerButton.setOnClickListener(this);
}
public void bindKey(KeyItem keyItem, Highlighter highlighter) {
Context ctx = itemView.getContext();
mLayoutData.setVisibility(View.VISIBLE);
mLayoutDummy.setVisibility(View.GONE);
{ // set name and stuff, common to both key types
OpenPgpUtils.UserId userIdSplit = keyItem.mUserId;
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);
mStatus.setVisibility(View.VISIBLE);
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) {
KeyFormattingUtils.setStatusImage(ctx, mStatus, null, KeyFormattingUtils.State.EXPIRED, R.color.key_flag_gray);
mStatus.setVisibility(View.VISIBLE);
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) {
mStatus.setVisibility(View.GONE);
if (mSlingerButton.hasOnClickListeners()) {
@@ -228,41 +330,27 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<String, KeySec
}
}
public void bindDummy() {
// just reset everything to display the dummy layout
mLayoutDummy.setVisibility(View.VISIBLE);
mLayoutData.setVisibility(View.GONE);
mSlinger.setVisibility(View.GONE);
mStatus.setVisibility(View.GONE);
itemView.setClickable(false);
@Override
public void onClick(View v) {
}
@Override
public boolean onLongClick(View v) {
return false;
}
}
static class KeyHeaderViewHolder extends RecyclerView.ViewHolder {
private TextView mHeaderText;
private TextView mHeaderCount;
static class KeyHeaderViewHolder extends SectionCursorAdapter.ViewHolder {
private TextView mText1;
public KeyHeaderViewHolder(View itemView) {
super(itemView);
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);
mText1 = (TextView) itemView.findViewById(android.R.id.text1);
}
public void bind(String title) {
mHeaderText.setText(title);
mHeaderCount.setVisibility(View.GONE);
mText1.setText(title);
}
}
}

View File

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

View File

@@ -4,35 +4,26 @@ import android.content.Context;
import android.database.Cursor;
import android.support.v4.util.SparseArrayCompat;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SectionIndexer;
import com.tonicartos.superslim.GridSLM;
import com.tonicartos.superslim.LayoutManager;
import com.tonicartos.superslim.LinearSLM;
import org.sufficientlysecure.keychain.util.Log;
import java.lang.annotation.Inherited;
import java.util.ArrayList;
import java.util.List;
/**
* @param <T> section type.
* @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.
*/
public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder, SH extends RecyclerView.ViewHolder>
extends CursorAdapter implements SectionIndexer {
public abstract class SectionCursorAdapter<T, VH extends SectionCursorAdapter.ViewHolder,
SH extends SectionCursorAdapter.ViewHolder> extends CursorAdapter {
public static final String TAG = "SectionCursorAdapter";
private static final int VIEW_TYPE_ITEM = 0x0;
private static final int VIEW_TYPE_SECTION = 0x1;
private static final short VIEW_TYPE_ITEM = 0x1;
private static final short VIEW_TYPE_SECTION = 0x2;
private SparseArrayCompat<T> mSectionMap = new SparseArrayCompat<>();
private ArrayList<Integer> mSectionIndexList = new ArrayList<>();
private Comparator<T> mSectionComparator;
private Object[] mFastScrollItems;
public SectionCursorAdapter(Context context, Cursor cursor, int flags) {
this(context, cursor, flags, new Comparator<T>() {
@@ -55,8 +46,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
buildSections();
} else {
mSectionMap.clear();
mSectionIndexList.clear();
mFastScrollItems = null;
}
super.onContentChanged();
@@ -81,8 +70,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
moveCursor(-1);
try {
mSectionMap.clear();
mSectionIndexList.clear();
mFastScrollItems = null;
appendSections(getCursor());
} catch (IllegalStateException e) {
@@ -91,13 +78,11 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
swapCursor(null);
mSectionMap.clear();
mSectionIndexList.clear();
mFastScrollItems = null;
}
}
}
protected void appendSections(Cursor cursor) throws IllegalStateException {
private void appendSections(Cursor cursor) throws IllegalStateException {
int cursorPosition = 0;
while(hasValidData() && cursor.moveToNext()) {
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.
*/
protected abstract T getSectionFromCursor(Cursor cursor) throws IllegalStateException;
protected String getTitleFromSection(T section) {
return section != null ? section.toString() : "";
}
@Override
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
* 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;
}
/**
* {@inheritDoc}
*/
@Override
public int getSectionForPosition(int listPosition) {
boolean isSection = false;
int numPrecedingSections = 0;
@@ -264,27 +192,6 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
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) {
boolean hasSections = mSectionMap != null && mSectionMap.size() > 0;
return sectionIndex == 0 && hasSections && listPosition < mSectionMap.keyAt(0);
@@ -292,7 +199,21 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
@Override
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
@@ -304,7 +225,8 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
// assign first position of section to each item
layoutParams.setFirstPosition(getFirstSectionPosition(position));
switch (holder.getItemViewType()) {
int viewType = holder.getItemViewType() & 0xFF;
switch (viewType) {
case VIEW_TYPE_ITEM :
moveCursorOrThrow(getCursorPositionWithoutSections(position));
onBindItemViewHolder((VH) holder, getCursor());
@@ -314,8 +236,7 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
case VIEW_TYPE_SECTION:
T section = mSectionMap.get(position);
int sectionIndex = getSectionIndexForPosition(position);
onBindSectionViewHolder((SH) holder, sectionIndex, section);
onBindSectionViewHolder((SH) holder, section);
layoutParams.isHeader = true;
break;
@@ -326,26 +247,45 @@ public abstract class SectionCursorAdapter<T, VH extends RecyclerView.ViewHolder
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
switch (viewType & 0xFF) {
case VIEW_TYPE_SECTION:
return onCreateSectionViewHolder(parent);
return onCreateSectionViewHolder(parent, viewType >> 16);
case VIEW_TYPE_ITEM:
return onCreateItemViewHolder(parent, viewType);
return onCreateItemViewHolder(parent, viewType >> 16);
default:
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 void onBindSectionViewHolder(SH holder, int sectionIndex, T section);
protected abstract void onBindSectionViewHolder(SH holder, T section);
protected abstract void onBindItemViewHolder(VH holder, Cursor cursor);
public interface Comparator<T> {
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"?>
<RelativeLayout xmlns:fab="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
<FrameLayout
xmlns:fab="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
>
xmlns:android="http://schemas.android.com/apk/res/android"
<!--rebuild functionality of ListFragment -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@android:id/progress"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:gravity="center">
<android.support.v7.widget.RecyclerView
android:id="@+id/key_list_list"
<ProgressBar
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_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:scrollbarStyle="outsideOverlay" />
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/key_list_empty"
android:layout_width="match_parent"
android:layout_height="240dp"
android:gravity="center"
android:orientation="vertical"
android:animateLayoutChanges="true"
>
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:clipToPadding="false"
android:scrollbarStyle="outsideOverlay" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<LinearLayout
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="240dp"
android:gravity="center"
android:text="@string/key_list_empty_text1"
android:textAppearance="?android:attr/textAppearanceLarge" />
android:orientation="vertical"
android:animateLayoutChanges="true" >
<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">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:id="@+id/search_button"
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>
</FrameLayout>
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/fab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
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"
>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:id="@+id/search_button"
android:gravity="center"
tools:text="@string/btn_search_for_query"/>
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_qr_code"
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
</LinearLayout>
</FrameLayout>
<com.getbase.floatingactionbutton.FloatingActionsMenu
android:id="@+id/fab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_qrcode_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_qr_code"
fab:fab_size="mini" />
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
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
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_qr_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_qrcode_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_qr_code"
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"
tools:ignore="ResAuto">
<TextView
style="@style/SectionHeader"
android:id="@+id/stickylist_header_text"
<TextView style="@style/SectionHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start|left"
android:text="header text" />
android:text="@string/my_keys" />
<TextView
android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="key count"
android:id="@+id/contacts_num"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginRight="8dp"
android:visibility="gone"
android:textColor="@android:color/darker_gray" />
android:layout_marginLeft="8dp"
android:textColor="@android:color/darker_gray"
tools:text="11 Keys"/>
</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"?>
<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"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:singleLine="true"
android:maxLines="1"
android:orientation="horizontal"
android:descendantFocusability="blocksDescendants"
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
android:id="@+id/key_list_item_data"
android:layout_width="0dip"
@@ -83,7 +37,7 @@
android:id="@+id/key_list_item_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:maxLines="1"
android:ellipsize="end"
tools:text="user@example.com"
android:textAppearance="?android:attr/textAppearanceSmall" />
@@ -92,12 +46,11 @@
android:id="@+id/key_list_item_creation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:maxLines="1"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone"
tools:visibility="visible"
tools:text="Created on 10/10/2010 10:00"
/>
tools:text="Created on 10/10/2010 10:00" />
</LinearLayout>
@@ -134,7 +87,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
tools:src="@drawable/status_signature_revoked_cutout_24dp"
/>
tools:src="@drawable/status_signature_revoked_cutout_24dp" />
</LinearLayout>