diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle index 496520b66..e15c5eeab 100644 --- a/OpenKeychain/build.gradle +++ b/OpenKeychain/build.gradle @@ -7,7 +7,7 @@ dependencies { // NOTE: Always use fixed version codes not dynamic ones, e.g. 0.7.3 instead of 0.7.+, see README for more information // NOTE: libraries are pinned to a specific build, see below - // from local Android SDK + // from local Android SDKre compile 'com.android.support:support-v4:25.0.0' compile 'com.android.support:appcompat-v7:25.0.0' compile 'com.android.support:design:25.0.0' @@ -18,22 +18,24 @@ dependencies { // JCenter etc. compile 'com.journeyapps:zxing-android-embedded:3.4.0@aar' compile 'com.google.zxing:core:3.3.0' - compile 'com.jpardogo.materialtabstrip:library:1.1.1' - compile 'com.getbase:floatingactionbutton:1.10.1' compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0' compile 'org.ocpsoft.prettytime:prettytime:4.0.1.Final' - compile 'com.splitwise:tokenautocomplete:2.0.8@aar' - compile 'org.sufficientlysecure:html-textview:2.0' compile 'org.sufficientlysecure:donations:2.4' - compile 'com.nispok:snackbar:2.11.0' compile 'com.squareup.okhttp3:okhttp:3.2.0' compile 'com.squareup.okhttp3:okhttp-urlconnection:3.2.0' compile 'org.apache.james:apache-mime4j-core:0.8.0' compile 'org.apache.james:apache-mime4j-dom:0.8.0' compile 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0' + + // UI + compile 'org.sufficientlysecure:html-textview:2.0' + compile 'com.splitwise:tokenautocomplete:2.0.8@aar' + compile 'com.jpardogo.materialtabstrip:library:1.1.1' + compile 'com.getbase:floatingactionbutton:1.10.1' + compile 'com.nispok:snackbar:2.11.0' compile 'com.cocosw:bottomsheet:1.3.0@aar' - compile 'se.emilsjolander:stickylistheaders:2.7.0' + // RecyclerView compile 'com.tonicartos:superslim:0.4.13' compile 'com.futuremind.recyclerfastscroll:fastscroll:0.2.4' @@ -113,7 +115,6 @@ dependencyVerification { 'org.apache.james:apache-mime4j-dom:e18717fe6d36f32e5c5f7cbeea1a9bf04645fdabc84e7e8374d9da10fd52e78d', 'org.thoughtcrime.ssl.pinning:AndroidPinning:afa1d74e699257fa75cb109ff29bac50726ef269c6e306bdeffe8223cee06ef4', 'com.cocosw:bottomsheet:4af6112a7f4cad4e2b70e5fdf1edc39f51275523a0f53011a012837dc103e597', - 'se.emilsjolander:stickylistheaders:a08ca948aa6b220f09d82f16bbbac395f6b78897e9eeac6a9f0b0ba755928eeb', 'com.tonicartos:superslim:ca89b5c674660cc6918a8f8fd385065bffeee27983e0d33c7c2f0ad7b34d2d49', 'com.futuremind.recyclerfastscroll:fastscroll:ae655201885a9dbb5fabecb4adfefbb23ffdbca26a2b4ea255ec1bf6f214c606', 'com.mikepenz:materialdrawer:8bba1428dcef5ad7c2decf49c612ad980b38e2f1031cbd66c152a8a104793929', diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java index f716103c8..ebf53cbaa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteSelectPubKeyActivity.java @@ -25,14 +25,12 @@ import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.BulletSpan; -import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.view.View; import android.widget.TextView; import org.openintents.openpgp.util.OpenPgpApi; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; import org.sufficientlysecure.keychain.ui.base.BaseActivity; import java.util.ArrayList; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java new file mode 100644 index 000000000..e9b93d1ac --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectPublicKeyFragment.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2012-2014 Dominik Schürmann + * Copyright (C) 2010-2014 Thialfihar + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Context; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; +import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.remote.ui.adapter.SelectEncryptKeyAdapter; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; + +public class SelectPublicKeyFragment extends RecyclerFragment + implements TextWatcher, LoaderManager.LoaderCallbacks { + public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; + + private EditText mSearchView; + private long mSelectedMasterKeyIds[]; + private String mQuery; + + /** + * Creates new instance of this fragment + */ + public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) { + SelectPublicKeyFragment frag = new SelectPublicKeyFragment(); + Bundle args = new Bundle(); + + args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds); + frag.setArguments(args); + + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final Context context = 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.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + LinearLayout innerListContainer = new LinearLayout(context); + innerListContainer.setOrientation(LinearLayout.VERTICAL); + + mSearchView = new EditText(context); + mSearchView.setId(android.R.id.input); + mSearchView.setHint(R.string.menu_search); + mSearchView.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable( + context, + R.drawable.ic_search_grey_24dp + ), null, null, null); + + innerListContainer.addView(mSearchView, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + + RecyclerView listView = new RecyclerView(context); + listView.setId(INTERNAL_LIST_VIEW_ID); + + int padding = FormattingUtils.dpToPx(context, 8); + listView.setPadding(padding, 0, padding, 0); + listView.setClipToPadding(false); + + innerListContainer.addView(listView, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + + listContainer.addView(innerListContainer, 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; + } + + /** + * Define Adapter and Loader on create of Activity + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText(getString(R.string.list_empty)); + mSearchView.addTextChangedListener(this); + + setAdapter(new SelectEncryptKeyAdapter(getContext(), null)); + setLayoutManager(new LinearLayoutManager(getContext())); + + // Start out with a progress indicator. + hideList(false); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + public long[] getSelectedMasterKeyIds() { + return getAdapter() != null ? + getAdapter().getMasterKeyIds() : new long[0]; + } + + public String[] getSelectedRawUserIds() { + return getAdapter() != null ? + getAdapter().getRawUserIds() : new String[0]; + } + + public OpenPgpUtils.UserId[] getSelectedUserIds() { + return getAdapter() != null ? + getAdapter().getUserIds() : new OpenPgpUtils.UserId[0]; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); + + // These are the rows that we will retrieve. + String[] projection = new String[]{ + KeyRings._ID, + KeyRings.MASTER_KEY_ID, + KeyRings.USER_ID, + KeyRings.IS_EXPIRED, + KeyRings.IS_REVOKED, + KeyRings.HAS_ENCRYPT, + KeyRings.VERIFIED, + KeyRings.HAS_DUPLICATE_USER_ID, + KeyRings.CREATION, + }; + + String inMasterKeyList = null; + if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { + inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; + for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { + if (i != 0) { + inMasterKeyList += ", "; + } + inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]); + } + inMasterKeyList += ")"; + } + + String orderBy = KeyRings.USER_ID + " ASC"; + if (inMasterKeyList != null) { + // sort by selected master keys + orderBy = inMasterKeyList + " DESC, " + orderBy; + } + String where = null; + String whereArgs[] = null; + if (mQuery != null) { + String[] words = mQuery.trim().split("\\s+"); + whereArgs = new String[words.length]; + for (int i = 0; i < words.length; ++i) { + if (where == null) { + where = ""; + } else { + where += " AND "; + } + where += KeyRings.USER_ID + " LIKE ?"; + whereArgs[i] = "%" + words[i] + "%"; + } + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), baseUri, projection, where, whereArgs, orderBy); + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + getAdapter().setQuery(mQuery); + getAdapter().swapCursor(SelectEncryptKeyAdapter.PublicKeyCursor.wrap(data)); + + // The list should now be shown. + if (isResumed()) { + showList(true); + } else { + showList(false); + } + + // preselect given master keys + getAdapter().preselectMasterKeyIds(mSelectedMasterKeyIds); + } + + @Override + public void onLoaderReset(Loader loader) { + // 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. + getAdapter().swapCursor(null); + } + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void afterTextChanged(Editable editable) { + mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; + getLoaderManager().restartLoader(0, null, this); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java index 946c77ce8..7c77f4599 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdActivity.java @@ -39,7 +39,7 @@ public class SelectSignKeyIdActivity extends BaseActivity { public static final String EXTRA_USER_ID = OpenPgpApi.EXTRA_USER_ID; public static final String EXTRA_DATA = "data"; - private static final int REQUEST_CODE_CREATE_KEY = 0x00008884; + protected static final int REQUEST_CODE_CREATE_KEY = 0x00008884; private String mPreferredUserId; private Intent mData; @@ -58,13 +58,6 @@ public class SelectSignKeyIdActivity extends BaseActivity { } }); - TextView createKeyButton = (TextView) findViewById(R.id.api_select_sign_key_create_key); - createKeyButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - createKey(mPreferredUserId); - } - }); TextView noneButton = (TextView) findViewById(R.id.api_select_sign_key_none); noneButton.setOnClickListener(new View.OnClickListener() { @Override @@ -86,20 +79,11 @@ public class SelectSignKeyIdActivity extends BaseActivity { return; } else { Log.d(Constants.TAG, "uri: " + appUri); - startListFragments(savedInstanceState, appUri, mData); + startListFragments(savedInstanceState, appUri, mData, mPreferredUserId); } } - private void createKey(String userId) { - OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(userId); - - Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NAME, userIdSplit.name); - intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userIdSplit.email); - startActivityForResult(intent, REQUEST_CODE_CREATE_KEY); - } - - private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data) { + private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data, String preferredUserId) { // However, if we're being restored from a previous state, // then we don't need to do anything and should return or else // we could end up with overlapping fragments. @@ -108,7 +92,8 @@ public class SelectSignKeyIdActivity extends BaseActivity { } // Create an instance of the fragments - SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment.newInstance(dataUri, data); + SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment + .newInstance(dataUri, data, preferredUserId); // Add the fragment to the 'fragment_container' FrameLayout // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! getSupportFragmentManager().beginTransaction() diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java index 73ce5fe47..5eaaac3c8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/SelectSignKeyIdListFragment.java @@ -19,7 +19,6 @@ package org.sufficientlysecure.keychain.remote.ui; import android.app.Activity; -import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -27,42 +26,44 @@ import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ListView; +import android.support.v7.widget.LinearLayoutManager; import org.openintents.openpgp.util.OpenPgpApi; +import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; +import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.ApiDataAccessObject; import org.sufficientlysecure.keychain.provider.KeychainContract; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; import org.sufficientlysecure.keychain.util.Log; -public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks { +public class SelectSignKeyIdListFragment extends RecyclerFragment + implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks { private static final String ARG_DATA_URI = "uri"; + private static final String ARG_PREF_UID = "pref_uid"; public static final String ARG_DATA = "data"; - private SelectKeyCursorAdapter mAdapter; - private ApiDataAccessObject mApiDao; - private Uri mDataUri; + private Intent mResult; + private String mPrefUid; + private ApiDataAccessObject mApiDao; /** * Creates new instance of this fragment */ - public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data) { + public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data, String preferredUserId) { SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment(); Bundle args = new Bundle(); args.putParcelable(ARG_DATA_URI, dataUri); args.putParcelable(ARG_DATA, data); + args.putString(ARG_PREF_UID, preferredUserId); frag.setArguments(args); @@ -72,33 +73,9 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mApiDao = new ApiDataAccessObject(getActivity()); } - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View layout = super.onCreateView(inflater, container, - savedInstanceState); - ListView lv = (ListView) layout.findViewById(android.R.id.list); - ViewGroup parent = (ViewGroup) lv.getParent(); - - /* - * http://stackoverflow.com/a/15880684 - * Remove ListView and add FixedListView in its place. - * This is done here programatically to be still able to use the progressBar of ListFragment. - * - * We want FixedListView to be able to put this ListFragment inside a ScrollView - */ - int lvIndex = parent.indexOfChild(lv); - parent.removeViewAt(lvIndex); - FixedListView newLv = new FixedListView(getActivity()); - newLv.setId(android.R.id.list); - parent.addView(newLv, lvIndex, lv.getLayoutParams()); - return layout; - } - /** * Define Adapter and Loader on create of Activity */ @@ -106,36 +83,19 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + mResult = getArguments().getParcelable(ARG_DATA); + mPrefUid = getArguments().getString(ARG_PREF_UID); mDataUri = getArguments().getParcelable(ARG_DATA_URI); - final Intent resultData = getArguments().getParcelable(ARG_DATA); - - getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); - getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - long masterKeyId = mAdapter.getMasterKeyId(position); - - Uri allowedKeysUri = mDataUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build(); - Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); - mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); - - resultData.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); - - getActivity().setResult(Activity.RESULT_OK, resultData); - getActivity().finish(); - } - }); // Give some text to display if there is no data. In a real // application this would come from a resource. setEmptyText(getString(R.string.list_empty)); - mAdapter = new SecretKeyCursorAdapter(getActivity(), null, 0, getListView()); - - setListAdapter(mAdapter); + setAdapter(new SelectSignKeyAdapter(getContext(), null)); + setLayoutManager(new LinearLayoutManager(getContext())); // Start out with a progress indicator. - setListShown(false); + hideList(false); // Prepare the loader. Either re-connect with an existing one, // or start a new one. @@ -172,13 +132,13 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen public void onLoadFinished(Loader loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mAdapter.swapCursor(data); + getAdapter().swapCursor(CursorAdapter.KeyCursor.wrap(data)); // The list should now be shown. if (isResumed()) { - setListShown(true); + showList(true); } else { - setListShownNoAnimation(true); + showList(false); } } @@ -188,35 +148,31 @@ public class SelectSignKeyIdListFragment extends ListFragmentWorkaround implemen // 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); } - private class SecretKeyCursorAdapter extends SelectKeyCursorAdapter { - - public SecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { - super(context, c, flags, listView); - } - - @Override - protected void initIndex(Cursor cursor) { - super.initIndex(cursor); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - super.bindView(view, context, cursor); - ViewHolderItem h = (ViewHolderItem) view.getTag(); - - h.selected.setVisibility(View.GONE); - - boolean enabled = false; - if ((Boolean) h.statusIcon.getTag()) { - h.statusIcon.setVisibility(View.GONE); - enabled = true; - } - h.setEnabled(enabled); - } + @Override + public void onCreateKeyDummyClicked() { + OpenPgpUtils.UserId userIdSplit = KeyRing.splitUserId(mPrefUid); + Intent intent = new Intent(getActivity(), CreateKeyActivity.class); + intent.putExtra(CreateKeyActivity.EXTRA_NAME, userIdSplit.name); + intent.putExtra(CreateKeyActivity.EXTRA_EMAIL, userIdSplit.email); + getActivity().startActivityForResult(intent, SelectSignKeyIdActivity.REQUEST_CODE_CREATE_KEY); } + @Override + public void onSelectKeyItemClicked(long masterKeyId) { + Uri allowedKeysUri = mDataUri.buildUpon() + .appendPath(KeychainContract.PATH_ALLOWED_KEYS) + .build(); + + mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId); + mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId); + + Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri); + + getActivity().setResult(Activity.RESULT_OK, mResult); + getActivity().finish(); + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java new file mode 100644 index 000000000..349d8d6f3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectEncryptKeyAdapter.java @@ -0,0 +1,310 @@ +package org.sufficientlysecure.keychain.remote.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.TextView; + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; +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.CursorAdapter; + +import java.util.ArrayList; +import java.util.Arrays; + +public class SelectEncryptKeyAdapter extends KeyCursorAdapter { + + private ArrayList mSelected; + + public SelectEncryptKeyAdapter(Context context, PublicKeyCursor cursor) { + super(context, cursor); + + mSelected = new ArrayList<>(); + } + + private boolean isSelected(int position) { + return mSelected.contains(position); + } + + private void select(int position) { + if(!isSelected(position)) { + mSelected.add(position); + notifyItemChanged(position); + } + } + + private void switchSelected(int position) { + int index = mSelected.indexOf(position); + if(index < 0) { + mSelected.add(position); + } else { + mSelected.remove(index); + } + + notifyItemChanged(position); + } + + public long[] getMasterKeyIds() { + long[] selected = new long[mSelected.size()]; + for(int i = 0; i < selected.length; i++) { + int position = mSelected.get(i); + if(!moveCursor(position)) { + return selected; + } + + selected[i] = getCursor().getKeyId(); + } + + return selected; + } + + public void preselectMasterKeyIds(long[] keyIds) { + if(keyIds != null) { + int count = 0; + for(int i = 0; i < getItemCount(); i++) { + if(!moveCursor(i)) { + continue; + } + + long id = getCursor().getKeyId(); + for(int l = 0; l < keyIds.length; l++) { + if(id == keyIds[l]) { + select(i); count ++; + break; + } + } + + if(count >= keyIds.length) { + return; + } + } + } + } + + public String[] getRawUserIds() { + String[] selected = new String[mSelected.size()]; + for(int i = 0; i < selected.length; i++) { + int position = mSelected.get(i); + if(!moveCursor(position)) { + return selected; + } + + selected[i] = getCursor().getRawUserId(); + } + + return selected; + } + + public OpenPgpUtils.UserId[] getUserIds() { + OpenPgpUtils.UserId[] selected = new OpenPgpUtils.UserId[mSelected.size()]; + for(int i = 0; i < selected.length; i++) { + int position = mSelected.get(i); + if(!moveCursor(position)) { + return selected; + } + + selected[i] = getCursor().getUserId(); + } + + return selected; + } + + @Override + public void onContentChanged() { + mSelected.clear(); + super.onContentChanged(); + } + + @Override + public void onBindViewHolder(EncryptKeyItemHolder holder, PublicKeyCursor cursor, String query) { + holder.bind(cursor, query, isSelected(holder.getAdapterPosition())); + } + + @Override + public EncryptKeyItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new EncryptKeyItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_encrypt_key_item, parent, false)); + } + + class EncryptKeyItemHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + private TextView mUserIdText; + private TextView mUserIdRestText; + private TextView mCreationText; + private ImageView mStatusIcon; + private CheckBox mChecked; + + public EncryptKeyItemHolder(View itemView) { + super(itemView); + itemView.setOnClickListener(this); + + mUserIdText = (TextView) itemView.findViewById(R.id.select_key_item_name); + mUserIdRestText = (TextView) itemView.findViewById(R.id.select_key_item_email); + mCreationText = (TextView) itemView.findViewById(R.id.select_key_item_creation); + mStatusIcon = (ImageView) itemView.findViewById(R.id.select_key_item_status_icon); + mChecked = (CheckBox) itemView.findViewById(R.id.selected); + } + + public void bind(PublicKeyCursor cursor, String query, boolean selected) { + Highlighter highlighter = new Highlighter(itemView.getContext(), query); + Context context = itemView.getContext(); + + { // set name and stuff, common to both key types + OpenPgpUtils.UserId userIdSplit = cursor.getUserId(); + if (userIdSplit.name != null) { + mUserIdText.setText(highlighter.highlight(userIdSplit.name)); + } else { + mUserIdText.setText(R.string.user_id_no_name); + } + if (userIdSplit.email != null) { + mUserIdRestText.setText(highlighter.highlight(userIdSplit.email)); + mUserIdRestText.setVisibility(View.VISIBLE); + } else { + mUserIdRestText.setVisibility(View.GONE); + } + } + + boolean enabled; + { // set edit button and status, specific by key type. Note: order is important! + int textColor; + if (cursor.isRevoked()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = false; + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (cursor.isExpired()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = false; + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (!cursor.hasEncrypt()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + KeyFormattingUtils.State.UNAVAILABLE + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = false; + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (cursor.isVerified()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + KeyFormattingUtils.State.VERIFIED + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = true; + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } else { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + KeyFormattingUtils.State.UNVERIFIED + ); + + mStatusIcon.setVisibility(View.VISIBLE); + + enabled = true; + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + mUserIdText.setTextColor(textColor); + mUserIdRestText.setTextColor(textColor); + + if (cursor.hasDuplicate()) { + String dateTime = DateUtils.formatDateTime(context, + cursor.getCreationTime(), + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + mCreationText.setText(context.getString(R.string.label_key_created, + dateTime)); + mCreationText.setTextColor(textColor); + mCreationText.setVisibility(View.VISIBLE); + } else { + mCreationText.setVisibility(View.GONE); + } + } + + itemView.setEnabled(enabled); + itemView.setClickable(enabled); + mChecked.setChecked(enabled && selected); + + } + + @Override + public void onClick(View v) { + switchSelected(getAdapterPosition()); + } + } + + public static class PublicKeyCursor extends CursorAdapter.KeyCursor { + public static final String[] PROJECTION; + + static { + ArrayList arr = new ArrayList<>(); + arr.addAll(Arrays.asList(KeyCursor.PROJECTION)); + arr.addAll(Arrays.asList( + KeychainContract.KeyRings.HAS_ENCRYPT, + KeychainContract.KeyRings.VERIFIED + )); + + PROJECTION = arr.toArray(new String[arr.size()]); + } + + public static PublicKeyCursor wrap(Cursor cursor) { + if (cursor != null) { + return new PublicKeyCursor(cursor); + } else { + return null; + } + } + + private PublicKeyCursor(Cursor cursor) { + super(cursor); + } + + public boolean hasEncrypt() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.HAS_ENCRYPT); + return getInt(index) != 0; + } + + public boolean isVerified() { + int index = getColumnIndexOrThrow(KeychainContract.KeyRings.VERIFIED); + return getInt(index) != 0; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java new file mode 100644 index 000000000..1e26d19d0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/adapter/SelectSignKeyAdapter.java @@ -0,0 +1,200 @@ +package org.sufficientlysecure.keychain.remote.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.adapter.KeyCursorAdapter; +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.CursorAdapter; + +public class SelectSignKeyAdapter extends KeyCursorAdapter { + private static final int VIEW_TYPE_KEY = 0; + private static final int VIEW_TYPE_DUMMY = 1; + + private SelectSignKeyListener mListener; + + public SelectSignKeyAdapter(Context context, Cursor cursor) { + super(context, KeyCursor.wrap(cursor)); + } + + public void setListener(SelectSignKeyListener listener) { + mListener = listener; + } + + @Override + public int getItemCount() { + return super.getItemCount() + 1; + } + + @Override + public long getItemId(int pos) { + if(pos < super.getItemCount()) { + return super.getItemId(pos); + } else { + return 0L; + } + } + + @Override + public int getItemViewType(int position) { + return position == getItemCount() -1 ? + VIEW_TYPE_DUMMY : VIEW_TYPE_KEY; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case VIEW_TYPE_KEY: + return new SignKeyItemHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_sign_key_item, parent, false)); + + case VIEW_TYPE_DUMMY: + return new DummyViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.select_dummy_key_item, parent, false)); + + default: + return null; + } + + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + if(holder.getItemViewType() == VIEW_TYPE_KEY) { + super.onBindViewHolder(holder, position - 1); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, KeyCursor cursor, String query) { + ((SignKeyItemHolder)holder).bind(cursor, query); + } + + private class DummyViewHolder extends RecyclerView.ViewHolder + implements View.OnClickListener{ + + public DummyViewHolder(View itemView) { + super(itemView); + itemView.setClickable(true); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onCreateKeyDummyClicked(); + } + } + } + + private class SignKeyItemHolder extends RecyclerView.ViewHolder + implements View.OnClickListener { + + private TextView mUserIdText; + private TextView mUserIdRestText; + private TextView mCreationText; + private ImageView mStatusIcon; + + public SignKeyItemHolder(View itemView) { + super(itemView); + + mUserIdText = (TextView) itemView.findViewById(R.id.select_key_item_name); + mUserIdRestText = (TextView) itemView.findViewById(R.id.select_key_item_email); + mCreationText = (TextView) itemView.findViewById(R.id.select_key_item_creation); + mStatusIcon = (ImageView) itemView.findViewById(R.id.select_key_item_status_icon); + } + + public void bind(KeyCursor cursor, String query) { + Highlighter highlighter = new Highlighter(itemView.getContext(), query); + Context context = itemView.getContext(); + + { // set name and stuff, common to both key types + OpenPgpUtils.UserId userIdSplit = cursor.getUserId(); + if (userIdSplit.name != null) { + mUserIdText.setText(highlighter.highlight(userIdSplit.name)); + } else { + mUserIdText.setText(R.string.user_id_no_name); + } + if (userIdSplit.email != null) { + mUserIdRestText.setText(highlighter.highlight(userIdSplit.email)); + mUserIdRestText.setVisibility(View.VISIBLE); + } else { + mUserIdRestText.setVisibility(View.GONE); + } + } + + { // set edit button and status, specific by key type. Note: order is important! + int textColor; + if (cursor.isRevoked()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.REVOKED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else if (cursor.isExpired()) { + KeyFormattingUtils.setStatusImage( + context, + mStatusIcon, + null, + KeyFormattingUtils.State.EXPIRED, + R.color.key_flag_gray + ); + + mStatusIcon.setVisibility(View.VISIBLE); + textColor = ContextCompat.getColor(context, R.color.key_flag_gray); + } else { + mStatusIcon.setVisibility(View.GONE); + textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); + } + + mUserIdText.setTextColor(textColor); + mUserIdRestText.setTextColor(textColor); + + if (cursor.hasDuplicate()) { + String dateTime = DateUtils.formatDateTime(context, + cursor.getCreationTime(), + DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR + | DateUtils.FORMAT_ABBREV_MONTH); + mCreationText.setText(context.getString(R.string.label_key_created, + dateTime)); + mCreationText.setTextColor(textColor); + mCreationText.setVisibility(View.VISIBLE); + } else { + mCreationText.setVisibility(View.GONE); + } + } + + } + + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onSelectKeyItemClicked(getItemId()); + } + } + } + + public interface SelectSignKeyListener { + void onCreateKeyDummyClicked(); + void onSelectKeyItemClicked(long masterKeyId); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java index 411dac3ef..b87075b91 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/LogDisplayFragment.java @@ -38,37 +38,35 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; +import com.tonicartos.superslim.LayoutManager; + import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogEntryParcel; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogLevel; import org.sufficientlysecure.keychain.operations.results.OperationResult.SubLogEntryParcel; import org.sufficientlysecure.keychain.provider.TemporaryFileProvider; +import org.sufficientlysecure.keychain.ui.adapter.NestedLogAdapter; import org.sufficientlysecure.keychain.ui.dialog.ShareLogDialogFragment; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; import java.io.IOException; import java.io.OutputStream; -public class LogDisplayFragment extends ListFragment implements OnItemClickListener { - - LogAdapter mAdapter; - - OperationResult mResult; +public class LogDisplayFragment extends RecyclerFragment + implements NestedLogAdapter.LogActionListener { + private OperationResult mResult; public static final String EXTRA_RESULT = "log"; - protected int mTextColor; - private Uri mLogTempFile; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mTextColor = FormattingUtils.getColorFromAttr(getActivity(), R.attr.colorText); - setHasOptionsMenu(true); } @@ -93,14 +91,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe return; } - mAdapter = new LogAdapter(getActivity(), mResult.getLog()); - setListAdapter(mAdapter); - - getListView().setOnItemClickListener(this); - - getListView().setFastScrollEnabled(true); - getListView().setDividerHeight(0); + NestedLogAdapter adapter = new NestedLogAdapter(getContext(), mResult.getLog()); + adapter.setListener(this); + setAdapter(adapter); + setLayoutManager(new LayoutManager(getContext())); } @Override @@ -130,7 +125,6 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe } private void shareLog() { - Activity activity = getActivity(); if (activity == null) { return; @@ -144,7 +138,7 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe try { OutputStream outputStream = activity.getContentResolver().openOutputStream(mLogTempFile); outputStream.write(log.getBytes()); - } catch (IOException e) { + } catch (IOException | NullPointerException e) { Notify.create(activity, R.string.error_log_share_internal, Style.ERROR).show(); return; } @@ -153,129 +147,12 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe ShareLogDialogFragment shareLogDialog = ShareLogDialogFragment.newInstance(mLogTempFile); shareLogDialog.show(getActivity().getSupportFragmentManager(), "shareLogDialog"); - } @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - LogEntryParcel parcel = mAdapter.getItem(position); - if ( ! (parcel instanceof SubLogEntryParcel)) { - return; - } - Intent intent = new Intent( - getActivity(), LogDisplayActivity.class); - intent.putExtra(LogDisplayFragment.EXTRA_RESULT, ((SubLogEntryParcel) parcel).getSubResult()); + public void onSubEntryClicked(SubLogEntryParcel subLogEntryParcel) { + Intent intent = new Intent(getActivity(), LogDisplayActivity.class); + intent.putExtra(LogDisplayFragment.EXTRA_RESULT, subLogEntryParcel.getSubResult()); startActivity(intent); } - - private class LogAdapter extends ArrayAdapter { - - private LayoutInflater mInflater; - private int dipFactor; - - public LogAdapter(Context context, OperationResult.OperationLog log) { - super(context, R.layout.log_display_item, log.toList()); - mInflater = LayoutInflater.from(getContext()); - dipFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - (float) 8, getResources().getDisplayMetrics()); - } - - private class ItemHolder { - final View mSecond; - final TextView mText, mSecondText; - final ImageView mImg, mSecondImg, mSub; - public ItemHolder(TextView text, ImageView image, ImageView sub, View second, TextView secondText, ImageView secondImg) { - mText = text; - mImg = image; - mSub = sub; - mSecond = second; - mSecondText = secondText; - mSecondImg = secondImg; - } - } - // Check if convertView.setPadding is redundant - @Override - public View getView(int position, View convertView, ViewGroup parent) { - LogEntryParcel entry = getItem(position); - ItemHolder ih; - if (convertView == null) { - convertView = mInflater.inflate(R.layout.log_display_item, parent, false); - ih = new ItemHolder( - (TextView) convertView.findViewById(R.id.log_text), - (ImageView) convertView.findViewById(R.id.log_img), - (ImageView) convertView.findViewById(R.id.log_sub), - convertView.findViewById(R.id.log_second), - (TextView) convertView.findViewById(R.id.log_second_text), - (ImageView) convertView.findViewById(R.id.log_second_img) - ); - convertView.setTag(ih); - } else { - ih = (ItemHolder) convertView.getTag(); - } - - if (entry instanceof SubLogEntryParcel) { - ih.mSub.setVisibility(View.VISIBLE); - convertView.setClickable(false); - convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); - - OperationResult result = ((SubLogEntryParcel) entry).getSubResult(); - LogEntryParcel subEntry = result.getLog().getLast(); - if (subEntry != null) { - ih.mSecond.setVisibility(View.VISIBLE); - // special case: first parameter may be a quantity - if (subEntry.mParameters != null && subEntry.mParameters.length > 0 - && subEntry.mParameters[0] instanceof Integer) { - ih.mSecondText.setText(getResources().getQuantityString(subEntry.mType.getMsgId(), - (Integer) subEntry.mParameters[0], - subEntry.mParameters)); - } else { - ih.mSecondText.setText(getResources().getString(subEntry.mType.getMsgId(), - subEntry.mParameters)); - } - ih.mSecondText.setTextColor(subEntry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); - switch (subEntry.mType.mLevel) { - case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break; - case INFO: ih.mSecondImg.setBackgroundColor(mTextColor); break; - case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; - case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - case START: ih.mSecondImg.setBackgroundColor(mTextColor); break; - case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; - case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - } - } else { - ih.mSecond.setVisibility(View.GONE); - } - - } else { - ih.mSub.setVisibility(View.GONE); - ih.mSecond.setVisibility(View.GONE); - convertView.setClickable(true); - } - - // special case: first parameter may be a quantity - if (entry.mParameters != null && entry.mParameters.length > 0 - && entry.mParameters[0] instanceof Integer) { - ih.mText.setText(getResources().getQuantityString(entry.mType.getMsgId(), - (Integer) entry.mParameters[0], - entry.mParameters)); - } else { - ih.mText.setText(getResources().getString(entry.mType.getMsgId(), - entry.mParameters)); - } - convertView.setPadding((entry.mIndent) * dipFactor, 0, 0, 0); - ih.mText.setTextColor(entry.mType.mLevel == LogLevel.DEBUG ? Color.GRAY : mTextColor); - switch (entry.mType.mLevel) { - case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break; - case INFO: ih.mImg.setBackgroundColor(mTextColor); break; - case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break; - case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - case START: ih.mImg.setBackgroundColor(mTextColor); break; - case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break; - case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break; - } - - return convertView; - } - - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java deleted file mode 100644 index 6f48b7455..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SelectPublicKeyFragment.java +++ /dev/null @@ -1,400 +0,0 @@ -/* - * Copyright (C) 2012-2014 Dominik Schürmann - * Copyright (C) 2010-2014 Thialfihar - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.ui; - -import android.content.Context; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; - -import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.compatibility.ListFragmentWorkaround; -import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; -import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; -import org.sufficientlysecure.keychain.ui.adapter.SelectKeyCursorAdapter; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; - -import java.util.Vector; - -public class SelectPublicKeyFragment extends ListFragmentWorkaround implements TextWatcher, - LoaderManager.LoaderCallbacks { - public static final String ARG_PRESELECTED_KEY_IDS = "preselected_key_ids"; - - private SelectKeyCursorAdapter mAdapter; - private EditText mSearchView; - private long mSelectedMasterKeyIds[]; - private String mQuery; - - // copied from ListFragment - static final int INTERNAL_EMPTY_ID = 0x00ff0001; - static final int INTERNAL_PROGRESS_CONTAINER_ID = 0x00ff0002; - static final int INTERNAL_LIST_CONTAINER_ID = 0x00ff0003; - // added for search view - static final int SEARCH_ID = 0x00ff0004; - - /** - * Creates new instance of this fragment - */ - public static SelectPublicKeyFragment newInstance(long[] preselectedKeyIds) { - SelectPublicKeyFragment frag = new SelectPublicKeyFragment(); - Bundle args = new Bundle(); - - args.putLongArray(ARG_PRESELECTED_KEY_IDS, preselectedKeyIds); - - frag.setArguments(args); - - return frag; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mSelectedMasterKeyIds = getArguments().getLongArray(ARG_PRESELECTED_KEY_IDS); - } - - /** - * Copied from ListFragment and added EditText for search on top of list. - * We do not use a custom layout here, because this breaks the progress bar functionality - * of ListFragment. - * - * @param inflater - * @param container - * @param savedInstanceState - * @return - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final Context context = getActivity(); - - FrameLayout root = new FrameLayout(context); - - // ------------------------------------------------------------------ - - LinearLayout pframe = new LinearLayout(context); - pframe.setId(INTERNAL_PROGRESS_CONTAINER_ID); - pframe.setOrientation(LinearLayout.VERTICAL); - pframe.setVisibility(View.GONE); - pframe.setGravity(Gravity.CENTER); - - ProgressBar progress = new ProgressBar(context, null, - android.R.attr.progressBarStyleLarge); - pframe.addView(progress, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - root.addView(pframe, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - // ------------------------------------------------------------------ - - FrameLayout lframe = new FrameLayout(context); - lframe.setId(INTERNAL_LIST_CONTAINER_ID); - - TextView tv = new TextView(getActivity()); - tv.setId(INTERNAL_EMPTY_ID); - tv.setGravity(Gravity.CENTER); - lframe.addView(tv, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - // Added for search view: linearLayout, mSearchView - LinearLayout linearLayout = new LinearLayout(context); - linearLayout.setOrientation(LinearLayout.VERTICAL); - - mSearchView = new EditText(context); - mSearchView.setId(SEARCH_ID); - mSearchView.setHint(R.string.menu_search); - mSearchView.setCompoundDrawablesWithIntrinsicBounds( - getResources().getDrawable(R.drawable.ic_search_grey_24dp), null, null, null); - - linearLayout.addView(mSearchView, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - - ListView lv = new ListView(getActivity()); - lv.setId(android.R.id.list); - lv.setDrawSelectorOnTop(false); - linearLayout.addView(lv, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - lframe.addView(linearLayout, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - root.addView(lframe, new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - // ------------------------------------------------------------------ - - root.setLayoutParams(new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); - - return root; - } - - /** - * Define Adapter and Loader on create of Activity - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - - // Give some text to display if there is no data. In a real - // application this would come from a resource. - setEmptyText(getString(R.string.list_empty)); - - mSearchView.addTextChangedListener(this); - - mAdapter = new SelectPublicKeyCursorAdapter(getActivity(), null, 0, getListView()); - - setListAdapter(mAdapter); - - // Start out with a progress indicator. - setListShown(false); - - // Prepare the loader. Either re-connect with an existing one, - // or start a new one. - getLoaderManager().initLoader(0, null, this); - } - - /** - * Selects items based on master key ids in list view - * - * @param masterKeyIds - */ - private void preselectMasterKeyIds(long[] masterKeyIds) { - if (masterKeyIds != null) { - for (int i = 0; i < getListView().getCount(); ++i) { - long keyId = mAdapter.getMasterKeyId(i); - for (long masterKeyId : masterKeyIds) { - if (keyId == masterKeyId) { - getListView().setItemChecked(i, true); - break; - } - } - } - } - } - - /** - * Returns all selected master key ids - * - * @return - */ - public long[] getSelectedMasterKeyIds() { - // mListView.getCheckedItemIds() would give the row ids of the KeyRings not the master key - // ids! - Vector vector = new Vector<>(); - for (int i = 0; i < getListView().getCount(); ++i) { - if (getListView().isItemChecked(i)) { - vector.add(mAdapter.getMasterKeyId(i)); - } - } - - // convert to long array - long[] selectedMasterKeyIds = new long[vector.size()]; - for (int i = 0; i < vector.size(); ++i) { - selectedMasterKeyIds[i] = vector.get(i); - } - - return selectedMasterKeyIds; - } - - /** - * Returns all selected user ids - * - * @return - */ - public String[] getSelectedUserIds() { - Vector userIds = new Vector<>(); - for (int i = 0; i < getListView().getCount(); ++i) { - if (getListView().isItemChecked(i)) { - userIds.add(mAdapter.getUserId(i)); - } - } - - // make empty array to not return null - String userIdArray[] = new String[0]; - return userIds.toArray(userIdArray); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - Uri baseUri = KeyRings.buildUnifiedKeyRingsUri(); - - // These are the rows that we will retrieve. - String[] projection = new String[]{ - KeyRings._ID, - KeyRings.MASTER_KEY_ID, - KeyRings.USER_ID, - KeyRings.IS_EXPIRED, - KeyRings.IS_REVOKED, - KeyRings.HAS_ENCRYPT, - KeyRings.VERIFIED, - KeyRings.HAS_DUPLICATE_USER_ID, - KeyRings.CREATION, - }; - - String inMasterKeyList = null; - if (mSelectedMasterKeyIds != null && mSelectedMasterKeyIds.length > 0) { - inMasterKeyList = Tables.KEYS + "." + KeyRings.MASTER_KEY_ID + " IN ("; - for (int i = 0; i < mSelectedMasterKeyIds.length; ++i) { - if (i != 0) { - inMasterKeyList += ", "; - } - inMasterKeyList += DatabaseUtils.sqlEscapeString("" + mSelectedMasterKeyIds[i]); - } - inMasterKeyList += ")"; - } - - String orderBy = KeyRings.USER_ID + " ASC"; - if (inMasterKeyList != null) { - // sort by selected master keys - orderBy = inMasterKeyList + " DESC, " + orderBy; - } - String where = null; - String whereArgs[] = null; - if (mQuery != null) { - String[] words = mQuery.trim().split("\\s+"); - whereArgs = new String[words.length]; - for (int i = 0; i < words.length; ++i) { - if (where == null) { - where = ""; - } else { - where += " AND "; - } - where += KeyRings.USER_ID + " LIKE ?"; - whereArgs[i] = "%" + words[i] + "%"; - } - } - - // Now create and return a CursorLoader that will take care of - // creating a Cursor for the data being displayed. - return new CursorLoader(getActivity(), baseUri, projection, where, whereArgs, orderBy); - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - mAdapter.setSearchQuery(mQuery); - mAdapter.swapCursor(data); - - // The list should now be shown. - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } - - // preselect given master keys - preselectMasterKeyIds(mSelectedMasterKeyIds); - } - - @Override - public void onLoaderReset(Loader loader) { - // 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); - } - - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { - - } - - @Override - public void afterTextChanged(Editable editable) { - mQuery = !TextUtils.isEmpty(editable.toString()) ? editable.toString() : null; - getLoaderManager().restartLoader(0, null, this); - } - - private class SelectPublicKeyCursorAdapter extends SelectKeyCursorAdapter { - - private int mIndexHasEncrypt, mIndexIsVerified; - - public SelectPublicKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) { - super(context, c, flags, listView); - } - - @Override - protected void initIndex(Cursor cursor) { - super.initIndex(cursor); - if (cursor != null) { - mIndexHasEncrypt = cursor.getColumnIndexOrThrow(KeyRings.HAS_ENCRYPT); - mIndexIsVerified = cursor.getColumnIndexOrThrow(KeyRings.VERIFIED); - } - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - super.bindView(view, context, cursor); - ViewHolderItem h = (SelectKeyCursorAdapter.ViewHolderItem) view.getTag(); - - // We care about the checkbox - h.selected.setVisibility(View.VISIBLE); - // the getListView works because this is not a static subclass! - h.selected.setChecked(getListView().isItemChecked(cursor.getPosition())); - - boolean enabled = false; - if((Boolean) h.statusIcon.getTag()) { - // Check if key is viable for our purposes - if (cursor.getInt(mIndexHasEncrypt) == 0) { - h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNAVAILABLE); - enabled = false; - } else if (cursor.getInt(mIndexIsVerified) != 0) { - h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.VERIFIED); - enabled = true; - } else { - h.statusIcon.setVisibility(View.VISIBLE); - KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, State.UNVERIFIED); - enabled = true; - } - } - - h.setEnabled(enabled); - } - - } - -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java index d540ba0cb..a2d31bb61 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvCertsFragment.java @@ -17,7 +17,6 @@ package org.sufficientlysecure.keychain.ui; -import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -25,56 +24,25 @@ import android.os.Bundle; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; -import android.support.v4.widget.CursorAdapter; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.TextView; +import android.view.LayoutInflater; + +import com.tonicartos.superslim.LayoutManager; -import org.openintents.openpgp.util.OpenPgpUtils; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.pgp.KeyRing; -import org.sufficientlysecure.keychain.pgp.WrappedSignature; import org.sufficientlysecure.keychain.provider.KeychainContract; -import org.sufficientlysecure.keychain.provider.KeychainDatabase; -import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.adapter.CertSectionedListAdapter; +import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerFragment; import org.sufficientlysecure.keychain.util.Log; -import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; -import se.emilsjolander.stickylistheaders.StickyListHeadersListView; - - -public class ViewKeyAdvCertsFragment extends LoaderFragment implements - LoaderManager.LoaderCallbacks, AdapterView.OnItemClickListener { +public class ViewKeyAdvCertsFragment extends RecyclerFragment + implements LoaderManager.LoaderCallbacks, CertSectionedListAdapter.CertListListener { public static final String ARG_DATA_URI = "data_uri"; - - private StickyListHeadersListView mStickyList; - private CertListAdapter mCertsAdapter; - private Uri mDataUriCerts; - // These are the rows that we will retrieve. - static final String[] CERTS_PROJECTION = new String[]{ - KeychainContract.Certs._ID, - KeychainContract.Certs.MASTER_KEY_ID, - KeychainContract.Certs.VERIFIED, - KeychainContract.Certs.TYPE, - KeychainContract.Certs.RANK, - KeychainContract.Certs.KEY_ID_CERTIFIER, - KeychainContract.Certs.USER_ID, - KeychainContract.Certs.SIGNER_UID - }; - - // sort by our user id, - static final String CERTS_SORT_ORDER = - KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.RANK + " ASC, " - + KeychainContract.Certs.VERIFIED + " DESC, " - + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.TYPE + " DESC, " - + KeychainContract.Certs.SIGNER_UID + " ASC"; - /** * Creates new instance of this fragment */ @@ -89,56 +57,39 @@ public class ViewKeyAdvCertsFragment extends LoaderFragment implements } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) { - View root = super.onCreateView(inflater, superContainer, savedInstanceState); - View view = inflater.inflate(R.layout.view_key_adv_certs_fragment, getContainer()); - - mStickyList = (StickyListHeadersListView) view.findViewById(R.id.certs_list); - - return root; + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + return inflater.inflate(R.layout.view_key_adv_certs_fragment, container, false); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + hideList(false); Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); if (dataUri == null) { Log.e(Constants.TAG, "Data missing. Should be Uri of key!"); getActivity().finish(); return; + } else { + mDataUriCerts = KeychainContract.Certs.buildCertsUri(dataUri); } - loadData(dataUri); - } + CertSectionedListAdapter adapter = new CertSectionedListAdapter(getActivity(), null); + adapter.setCertListListener(this); - private void loadData(Uri dataUri) { - mDataUriCerts = KeychainContract.Certs.buildCertsUri(dataUri); + setAdapter(adapter); + setLayoutManager(new LayoutManager(getActivity())); - mStickyList.setAreHeadersSticky(true); - mStickyList.setDrawingListUnderStickyHeader(false); - mStickyList.setOnItemClickListener(this); - - mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); - - // Create an empty adapter we will use to display the loaded data. - mCertsAdapter = new CertListAdapter(getActivity(), null); - mStickyList.setAdapter(mCertsAdapter); - - // Prepare the loaders. Either re-connect with an existing ones, - // or start new ones. getLoaderManager().initLoader(0, null, this); } public Loader onCreateLoader(int id, Bundle args) { - setContentShown(false); - - // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. return new CursorLoader(getActivity(), mDataUriCerts, - CERTS_PROJECTION, null, null, CERTS_SORT_ORDER); - + CertSectionedListAdapter.CertCursor.CERTS_PROJECTION, null, null, + CertSectionedListAdapter.CertCursor.CERTS_SORT_ORDER); } public void onLoadFinished(Loader loader, Cursor data) { @@ -149,11 +100,13 @@ public class ViewKeyAdvCertsFragment extends LoaderFragment implements // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) - mCertsAdapter.swapCursor(data); - mStickyList.setAdapter(mCertsAdapter); + getAdapter().swapCursor(CertSectionedListAdapter.CertCursor.wrap(data)); - // TODO: maybe show not before both are loaded! - setContentShown(true); + if (isResumed()) { + showList(true); + } else { + showList(false); + } } /** @@ -161,184 +114,180 @@ public class ViewKeyAdvCertsFragment extends LoaderFragment implements * We need to make sure we are no longer using it. */ public void onLoaderReset(Loader loader) { - mCertsAdapter.swapCursor(null); + getAdapter().swapCursor(null); } - /** - * On click on item, start key view activity - */ @Override - public void onItemClick(AdapterView adapterView, View view, int position, long id) { - if (view.getTag(R.id.tag_mki) != null) { - long masterKeyId = (Long) view.getTag(R.id.tag_mki); - long rank = (Long) view.getTag(R.id.tag_rank); - long certifierId = (Long) view.getTag(R.id.tag_certifierId); - + public void onClick(long masterKeyId, long signerKeyId, long rank) { + if(masterKeyId != 0L) { Intent viewIntent = new Intent(getActivity(), ViewCertActivity.class); viewIntent.setData(KeychainContract.Certs.buildCertsSpecificUri( - masterKeyId, rank, certifierId)); + masterKeyId, rank, signerKeyId)); startActivity(viewIntent); } } - - - /** - * Implements StickyListHeadersAdapter from library - */ - private class CertListAdapter extends CursorAdapter implements StickyListHeadersAdapter { - private LayoutInflater mInflater; - private int mIndexMasterKeyId, mIndexUserId, mIndexRank; - private int mIndexSignerKeyId, mIndexSignerUserId; - private int mIndexVerified, mIndexType; - - public CertListAdapter(Context context, Cursor c) { - super(context, c, 0); - - mInflater = LayoutInflater.from(context); - initIndex(c); - } - - @Override - public Cursor swapCursor(Cursor newCursor) { - initIndex(newCursor); - - return super.swapCursor(newCursor); - } - - /** - * Get column indexes for performance reasons just once in constructor and swapCursor. For a - * performance comparison see http://stackoverflow.com/a/17999582 - * - * @param cursor - */ - private void initIndex(Cursor cursor) { - if (cursor != null) { - mIndexMasterKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID); - mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.USER_ID); - mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.Certs.RANK); - mIndexType = cursor.getColumnIndexOrThrow(KeychainContract.Certs.TYPE); - mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); - mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); - mIndexSignerUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID); - } - } - - /** - * Bind cursor data to the item list view - *

- * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. - * Thus no ViewHolder is required here. - */ - @Override - public void bindView(View view, Context context, Cursor cursor) { - - // set name and stuff, common to both key types - TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId); - TextView wSignerName = (TextView) view.findViewById(R.id.signerName); - TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); - - String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix( - cursor.getLong(mIndexSignerKeyId)); - OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId)); - if (userId.name != null) { - wSignerName.setText(userId.name); - } else { - wSignerName.setText(R.string.user_id_no_name); - } - wSignerKeyId.setText(signerKeyId); - - switch (cursor.getInt(mIndexType)) { - case WrappedSignature.DEFAULT_CERTIFICATION: // 0x10 - wSignStatus.setText(R.string.cert_default); - break; - case WrappedSignature.NO_CERTIFICATION: // 0x11 - wSignStatus.setText(R.string.cert_none); - break; - case WrappedSignature.CASUAL_CERTIFICATION: // 0x12 - wSignStatus.setText(R.string.cert_casual); - break; - case WrappedSignature.POSITIVE_CERTIFICATION: // 0x13 - wSignStatus.setText(R.string.cert_positive); - break; - case WrappedSignature.CERTIFICATION_REVOCATION: // 0x30 - wSignStatus.setText(R.string.cert_revoke); - break; - } - - - view.setTag(R.id.tag_mki, cursor.getLong(mIndexMasterKeyId)); - view.setTag(R.id.tag_rank, cursor.getLong(mIndexRank)); - view.setTag(R.id.tag_certifierId, cursor.getLong(mIndexSignerKeyId)); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.view_key_adv_certs_item, parent, false); - } - - /** - * 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. - *

- * 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.view_key_adv_certs_header, parent, false); - holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); - holder.count = (TextView) convertView.findViewById(R.id.certs_num); - convertView.setTag(holder); - } else { - holder = (HeaderViewHolder) convertView.getTag(); - } - - if (!mDataValid) { - // no data available at this point - Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); - return convertView; - } - - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("couldn't move cursor to position " + position); - } - - // set header text as first char in user id - String userId = mCursor.getString(mIndexUserId); - holder.text.setText(userId); - holder.count.setVisibility(View.GONE); - 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) { - // no data available at this point - Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); - return -1; - } - - if (!mCursor.moveToPosition(position)) { - throw new IllegalStateException("couldn't move cursor to position " + position); - } - - // otherwise, return the first character of the name as ID - return mCursor.getInt(mIndexRank); - - // sort by the first four characters (should be enough I guess?) - // return ByteBuffer.wrap(userId.getBytes()).asLongBuffer().get(0); - } - - class HeaderViewHolder { - TextView text; - TextView count; - } - - } +//<<<<<<< HEAD +// +// +// /** +// * Implements StickyListHeadersAdapter from library +// */ +// private class CertListAdapter extends CursorAdapter implements StickyListHeadersAdapter { +// private LayoutInflater mInflater; +// private int mIndexMasterKeyId, mIndexUserId, mIndexRank; +// private int mIndexSignerKeyId, mIndexSignerUserId; +// private int mIndexVerified, mIndexType; +// +// public CertListAdapter(Context context, Cursor c) { +// super(context, c, 0); +// +// mInflater = LayoutInflater.from(context); +// initIndex(c); +// } +// +// @Override +// public Cursor swapCursor(Cursor newCursor) { +// initIndex(newCursor); +// +// return super.swapCursor(newCursor); +// } +// +// /** +// * Get column indexes for performance reasons just once in constructor and swapCursor. For a +// * performance comparison see http://stackoverflow.com/a/17999582 +// * +// * @param cursor +// */ +// private void initIndex(Cursor cursor) { +// if (cursor != null) { +// mIndexMasterKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID); +// mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.USER_ID); +// mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.Certs.RANK); +// mIndexType = cursor.getColumnIndexOrThrow(KeychainContract.Certs.TYPE); +// mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); +// mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); +// mIndexSignerUserId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID); +// } +// } +// +// /** +// * Bind cursor data to the item list view +// *

+// * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. +// * Thus no ViewHolder is required here. +// */ +// @Override +// public void bindView(View view, Context context, Cursor cursor) { +// +// // set name and stuff, common to both key types +// TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId); +// TextView wSignerName = (TextView) view.findViewById(R.id.signerName); +// TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); +// +// String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix( +// cursor.getLong(mIndexSignerKeyId)); +// OpenPgpUtils.UserId userId = KeyRing.splitUserId(cursor.getString(mIndexSignerUserId)); +// if (userId.name != null) { +// wSignerName.setText(userId.name); +// } else { +// wSignerName.setText(R.string.user_id_no_name); +// } +// wSignerKeyId.setText(signerKeyId); +// +// switch (cursor.getInt(mIndexType)) { +// case WrappedSignature.DEFAULT_CERTIFICATION: // 0x10 +// wSignStatus.setText(R.string.cert_default); +// break; +// case WrappedSignature.NO_CERTIFICATION: // 0x11 +// wSignStatus.setText(R.string.cert_none); +// break; +// case WrappedSignature.CASUAL_CERTIFICATION: // 0x12 +// wSignStatus.setText(R.string.cert_casual); +// break; +// case WrappedSignature.POSITIVE_CERTIFICATION: // 0x13 +// wSignStatus.setText(R.string.cert_positive); +// break; +// case WrappedSignature.CERTIFICATION_REVOCATION: // 0x30 +// wSignStatus.setText(R.string.cert_revoke); +// break; +// } +// +// +// view.setTag(R.id.tag_mki, cursor.getLong(mIndexMasterKeyId)); +// view.setTag(R.id.tag_rank, cursor.getLong(mIndexRank)); +// view.setTag(R.id.tag_certifierId, cursor.getLong(mIndexSignerKeyId)); +// } +// +// @Override +// public View newView(Context context, Cursor cursor, ViewGroup parent) { +// return mInflater.inflate(R.layout.view_key_adv_certs_item, parent, false); +// } +// +// /** +// * 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. +// *

+// * 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.view_key_adv_certs_header, parent, false); +// holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); +// holder.count = (TextView) convertView.findViewById(R.id.certs_num); +// convertView.setTag(holder); +// } else { +// holder = (HeaderViewHolder) convertView.getTag(); +// } +// +// if (!mDataValid) { +// // no data available at this point +// Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); +// return convertView; +// } +// +// if (!mCursor.moveToPosition(position)) { +// throw new IllegalStateException("couldn't move cursor to position " + position); +// } +// +// // set header text as first char in user id +// String userId = mCursor.getString(mIndexUserId); +// holder.text.setText(userId); +// holder.count.setVisibility(View.GONE); +// 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) { +// // no data available at this point +// Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); +// return -1; +// } +// +// if (!mCursor.moveToPosition(position)) { +// throw new IllegalStateException("couldn't move cursor to position " + position); +// } +// +// // otherwise, return the first character of the name as ID +// return mCursor.getInt(mIndexRank); +// +// // sort by the first four characters (should be enough I guess?) +// // return ByteBuffer.wrap(userId.getBytes()).asLongBuffer().get(0); +// } +// +// class HeaderViewHolder { +// TextView text; +// TextView count; +// } +// +// } +//======= +//>>>>>>> cc6f9037948f8d3e5481c7479b1ce5e607e9a01f } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java new file mode 100644 index 000000000..120e670b4 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/CertSectionedListAdapter.java @@ -0,0 +1,230 @@ +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + + +import org.openintents.openpgp.util.OpenPgpUtils; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.KeyRing; +import org.sufficientlysecure.keychain.pgp.WrappedSignature; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase; +import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; +import org.sufficientlysecure.keychain.ui.util.adapter.SectionCursorAdapter; + +import java.util.ArrayList; +import java.util.Arrays; + +public class CertSectionedListAdapter extends SectionCursorAdapter { + + private CertListListener mListener; + + public CertSectionedListAdapter(Context context, CertCursor cursor) { + super(context, CertCursor.wrap(cursor), 0); + } + + public void setCertListListener(CertListListener listener) { + mListener = listener; + } + + @Override + public long getIdFromCursor(CertCursor cursor) { + return cursor.getKeyId(); + } + + @Override + protected String getSectionFromCursor(CertCursor cursor) throws IllegalStateException { + return cursor.getRawSignerUserId(); + } + + @Override + protected CertSectionViewHolder onCreateSectionViewHolder(ViewGroup parent, int viewType) { + return new CertSectionViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.view_key_adv_certs_header, parent, false)); + } + + @Override + protected CertItemViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) { + return new CertItemViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.view_key_adv_certs_item, parent, false)); + } + + @Override + protected void onBindSectionViewHolder(CertSectionViewHolder holder, String section) { + holder.bind(section); + } + + @Override + protected void onBindItemViewHolder(CertItemViewHolder holder, CertCursor cursor) { + holder.bind(cursor); + } + + class CertItemViewHolder extends SectionCursorAdapter.ViewHolder + implements View.OnClickListener { + + private TextView mSignerKeyId; + private TextView mSignerName; + private TextView mSignStatus; + + public CertItemViewHolder(View itemView) { + super(itemView); + + itemView.setClickable(true); + itemView.setOnClickListener(this); + + mSignerName = (TextView) itemView.findViewById(R.id.signerName); + mSignStatus = (TextView) itemView.findViewById(R.id.signStatus); + mSignerKeyId = (TextView) itemView.findViewById(R.id.signerKeyId); + } + + public void bind(CertCursor cursor) { + String signerKeyId = KeyFormattingUtils.beautifyKeyIdWithPrefix( + cursor.getCertifierKeyId()); + + OpenPgpUtils.UserId userId = cursor.getSignerUserId(); + if (userId.name != null) { + mSignerName.setText(userId.name); + } else { + mSignerName.setText(R.string.user_id_no_name); + } + + mSignerKeyId.setText(signerKeyId); + switch (cursor.getType()) { + case WrappedSignature.DEFAULT_CERTIFICATION: // 0x10 + mSignStatus.setText(R.string.cert_default); + break; + case WrappedSignature.NO_CERTIFICATION: // 0x11 + mSignStatus.setText(R.string.cert_none); + break; + case WrappedSignature.CASUAL_CERTIFICATION: // 0x12 + mSignStatus.setText(R.string.cert_casual); + break; + case WrappedSignature.POSITIVE_CERTIFICATION: // 0x13 + mSignStatus.setText(R.string.cert_positive); + break; + case WrappedSignature.CERTIFICATION_REVOCATION: // 0x30 + mSignStatus.setText(R.string.cert_revoke); + break; + } + } + + @Override + public void onClick(View v) { + if(mListener != null) { + int index = getCursorPositionWithoutSections(getAdapterPosition()); + if (moveCursor(index)) { + CertCursor cursor = getCursor(); + mListener.onClick( + cursor.getKeyId(), + cursor.getCertifierKeyId(), + cursor.getRank() + ); + } + } + } + } + + static class CertSectionViewHolder extends SectionCursorAdapter.ViewHolder { + private TextView mHeaderText; + + public CertSectionViewHolder(View itemView) { + super(itemView); + mHeaderText = (TextView) itemView.findViewById(R.id.stickylist_header_text); + } + + public void bind(String text) { + mHeaderText.setText(text); + } + } + + public static class CertCursor extends CursorAdapter.AbstractCursor { + public static final String[] CERTS_PROJECTION; + static { + ArrayList projection = new ArrayList<>(); + projection.addAll(Arrays.asList(AbstractCursor.PROJECTION)); + projection.addAll(Arrays.asList( + KeychainContract.Certs.MASTER_KEY_ID, + KeychainContract.Certs.VERIFIED, + KeychainContract.Certs.TYPE, + KeychainContract.Certs.RANK, + KeychainContract.Certs.KEY_ID_CERTIFIER, + KeychainContract.Certs.USER_ID, + KeychainContract.Certs.SIGNER_UID + )); + + CERTS_PROJECTION = projection.toArray(new String[projection.size()]); + } + + public static final String CERTS_SORT_ORDER = + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.RANK + " ASC, " + + KeychainContract.Certs.VERIFIED + " DESC, " + + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.TYPE + " DESC, " + + KeychainContract.Certs.SIGNER_UID + " ASC"; + + public static CertCursor wrap(Cursor cursor) { + if(cursor != null) { + return new CertCursor(cursor); + } else { + return null; + } + } + + private CertCursor(Cursor cursor) { + super(cursor); + } + + public long getKeyId() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.MASTER_KEY_ID); + return getLong(index); + } + + public boolean isVerified() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); + return getInt(index) > 0; + } + + public int getType() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.TYPE); + return getInt(index); + } + + public long getRank() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.RANK); + return getLong(index); + } + + public long getCertifierKeyId() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); + return getLong(index); + } + + public String getRawUserId() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.USER_ID); + return getString(index); + } + + public String getRawSignerUserId() { + int index = getColumnIndexOrThrow(KeychainContract.Certs.SIGNER_UID); + return getString(index); + } + + public OpenPgpUtils.UserId getUserId() { + return KeyRing.splitUserId(getRawUserId()); + } + + public OpenPgpUtils.UserId getSignerUserId() { + return KeyRing.splitUserId(getRawSignerUserId()); + } + } + + public interface CertListListener { + void onClick(long masterKeyId, long signerKeyId, long rank); + } +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java new file mode 100644 index 000000000..2e5556f69 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/KeyCursorAdapter.java @@ -0,0 +1,42 @@ +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.database.Cursor; +import android.support.v7.widget.RecyclerView; + +import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter; + + +public abstract class KeyCursorAdapter + extends CursorAdapter { + + private String mQuery; + + public KeyCursorAdapter(Context context, C cursor){ + super(context, cursor, 0); + } + + public void setQuery(String query) { + mQuery = query; + } + + @Override + public int getItemViewType(int position) { + moveCursorOrThrow(position); + return getItemViewType(getCursor()); + } + + @Override + public long getIdFromCursor(C keyCursor) { + return keyCursor.getKeyId(); + } + + @Override + public void onBindViewHolder(VH holder, int position) { + moveCursorOrThrow(position); + onBindViewHolder(holder, getCursor(), mQuery); + } + + public int getItemViewType(C keyCursor) { return 0; } + public abstract void onBindViewHolder(VH holder, C cursor, String query); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java new file mode 100644 index 000000000..09e3ef42a --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/NestedLogAdapter.java @@ -0,0 +1,268 @@ +package org.sufficientlysecure.keychain.ui.adapter; + +import android.content.Context; +import android.graphics.Color; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.RecyclerView; +import android.util.Pair; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tonicartos.superslim.LayoutManager; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.operations.results.OperationResult; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; + +import java.util.ArrayList; +import java.util.List; + +public class NestedLogAdapter extends RecyclerView.Adapter { + private static final int ENTRY_TYPE_REGULAR = 0; + private static final int ENTRY_TYPE_SUBLOG = 1; + private static final int LOG_ENTRY_ITEM_INDENT = 2; + + + private final int mIndentFactor; + private LogActionListener mListener; + private List> mLogEntries; + + public NestedLogAdapter(Context context) { + super(); + + mIndentFactor = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + (float) 8, context.getResources().getDisplayMetrics()); + } + + public NestedLogAdapter(Context context, OperationResult.OperationLog log) { + this(context); + setLog(log); + } + + public void setListener(LogActionListener listener) { + mListener = listener; + } + + public void setLog(OperationResult.OperationLog log) { + List list = log.toList(); + + if (mLogEntries != null) { + mLogEntries.clear(); + } else { + mLogEntries = new ArrayList<>(list.size()); + } + + int lastSection = 0; + for (int i = 0; i < list.size(); i++) { + OperationResult.LogEntryParcel parcel = list.get(i); + if(parcel.mIndent < LOG_ENTRY_ITEM_INDENT) { + lastSection = i; + } + + mLogEntries.add(new Pair<>(parcel, lastSection)); + } + + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mLogEntries != null ? mLogEntries.size() : 0; + } + + @Override + public long getItemId(int position) { + OperationResult.LogEntryParcel parcel = getItem(position); + return parcel != null ? parcel.hashCode() : -1L; + } + + public OperationResult.LogEntryParcel getItem(int position) { + return mLogEntries != null ? + mLogEntries.get(position).first : null; + } + + public int getFirstSectionPosition(int position) { + return mLogEntries != null ? + mLogEntries.get(position).second : 0; + } + + @Override + public int getItemViewType(int position) { + return (getItem(position) instanceof OperationResult.SubLogEntryParcel) ? + ENTRY_TYPE_SUBLOG : ENTRY_TYPE_REGULAR; + } + + public boolean isSection(int position) { + return mLogEntries != null && mLogEntries.get(position).second == position; + } + + @Override + public LogEntryViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case ENTRY_TYPE_SUBLOG: + return new SublogEntryViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.log_display_sublog_item, parent, false)); + + case ENTRY_TYPE_REGULAR: + return new LogEntryViewHolder(LayoutInflater.from(parent.getContext()) + .inflate(R.layout.log_display_regular_item, parent, false)); + + default: + return null; + } + } + + @Override + public void onBindViewHolder(LogEntryViewHolder holder, int position) { + LayoutManager.LayoutParams layoutParams = LayoutManager.LayoutParams + .from(holder.itemView.getLayoutParams()); + + layoutParams.isHeader = isSection(position); + layoutParams.setFirstPosition(getFirstSectionPosition(position)); + + holder.bind(getItem(position), mIndentFactor); + holder.itemView.setLayoutParams(layoutParams); + } + + class LogEntryViewHolder extends RecyclerView.ViewHolder { + private TextView mLogText; + private ImageView mLogImg; + + public LogEntryViewHolder(View itemView) { + super(itemView); + + mLogText = (TextView) itemView.findViewById(R.id.log_text); + mLogImg = (ImageView) itemView.findViewById(R.id.log_img); + } + + public void bind(OperationResult.LogEntryParcel entry, int indentFactor) { + String logText; + if (entry.mParameters != null + && entry.mParameters.length > 0 + && entry.mParameters[0] instanceof Integer) { + + logText = itemView.getResources().getQuantityString(entry.mType.getMsgId(), + (int) entry.mParameters[0], entry.mParameters); + } else { + logText = itemView.getResources().getString(entry.mType.getMsgId(), + entry.mParameters); + } + + int textColor, indicatorColor; + textColor = indicatorColor = FormattingUtils.getColorFromAttr( + itemView.getContext(), R.attr.colorText); + + switch (entry.mType.mLevel) { + case DEBUG: + textColor = Color.GRAY; + indicatorColor = Color.GRAY; + break; + case WARN: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_orange_light); + break; + case ERROR: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_red_light); + break; + case OK: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_green_light); + break; + case CANCELLED: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_red_light); + break; + } + + mLogText.setText(logText); + mLogText.setTextColor(textColor); + mLogImg.setBackgroundColor(indicatorColor); + itemView.setPadding((entry.mIndent) * indentFactor, 0, 0, 0); + } + } + + class SublogEntryViewHolder extends LogEntryViewHolder implements View.OnClickListener { + private TextView mSublogText; + private ImageView mSublogImg; + + public SublogEntryViewHolder(View itemView) { + super(itemView); + + itemView.setClickable(true); + itemView.setOnClickListener(this); + + mSublogText = (TextView) itemView.findViewById(R.id.log_second_text); + mSublogImg = (ImageView) itemView.findViewById(R.id.log_second_img); + } + + @Override + public void bind(OperationResult.LogEntryParcel entry, int indentFactor) { + super.bind(entry, indentFactor); + + OperationResult.LogEntryParcel sublogEntry = ((OperationResult.SubLogEntryParcel) entry) + .getSubResult().getLog().getLast(); + + String logText; + if (sublogEntry.mParameters != null + && sublogEntry.mParameters.length > 0 + && sublogEntry.mParameters[0] instanceof Integer) { + + logText = itemView.getResources().getQuantityString(sublogEntry.mType.getMsgId(), + (int) sublogEntry.mParameters[0], sublogEntry.mParameters); + } else { + logText = itemView.getResources().getString(sublogEntry.mType.getMsgId(), + sublogEntry.mParameters); + } + + int textColor, indicatorColor; + textColor = indicatorColor = FormattingUtils.getColorFromAttr( + itemView.getContext(), R.attr.colorText); + + switch (sublogEntry.mType.mLevel) { + case DEBUG: + textColor = Color.GRAY; + indicatorColor = Color.GRAY; + break; + case WARN: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_orange_light); + break; + case ERROR: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_red_light); + break; + case OK: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_green_light); + break; + case CANCELLED: + indicatorColor = ContextCompat.getColor(itemView.getContext(), + R.color.android_red_light); + break; + } + + mSublogText.setText(logText); + mSublogText.setTextColor(textColor); + mSublogImg.setBackgroundColor(indicatorColor); + } + + @Override + public void onClick(View v) { + if (mListener != null) { + OperationResult.LogEntryParcel parcel = getItem(getAdapterPosition()); + if (parcel instanceof OperationResult.SubLogEntryParcel) { + mListener.onSubEntryClicked((OperationResult.SubLogEntryParcel) parcel); + } + } + } + } + + public interface LogActionListener { + void onSubEntryClicked(OperationResult.SubLogEntryParcel subLogEntryParcel); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java index 3e94ac728..1f0fddab6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/SelectKeyCursorAdapter.java @@ -166,7 +166,7 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = mInflater.inflate(R.layout.select_key_item, null); + View view = mInflater.inflate(R.layout.select_encrypt_key_item, null); ViewHolderItem holder = new ViewHolderItem(); holder.view = view; holder.mainUserId = (TextView) view.findViewById(R.id.select_key_item_name); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java index 5cf5f8d4b..8e8a68d23 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/adapter/SectionCursorAdapter.java @@ -1,6 +1,7 @@ package org.sufficientlysecure.keychain.ui.util.adapter; import android.content.Context; +import android.database.Cursor; import android.support.v4.util.SparseArrayCompat; import android.support.v7.widget.RecyclerView; import android.view.View; @@ -10,6 +11,10 @@ import com.tonicartos.superslim.LayoutManager; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.Objects; + /** * @param section type. * @param the view holder extending {@code BaseViewHolder} that is bound to the cursor data. diff --git a/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml b/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml index 981e01dd1..bc88baae4 100644 --- a/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml +++ b/OpenKeychain/src/main/res/layout/api_select_sign_key_activity.xml @@ -11,26 +11,16 @@ android:layout_below="@id/toolbar_include" android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent"> - - - - - + android:layout_height="match_parent" + android:padding="8dp"> @@ -60,33 +50,7 @@ + android:layout_height="match_parent" /> - - - - - - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_activity.xml b/OpenKeychain/src/main/res/layout/log_display_activity.xml index a243a9d2b..be08ef6fd 100644 --- a/OpenKeychain/src/main/res/layout/log_display_activity.xml +++ b/OpenKeychain/src/main/res/layout/log_display_activity.xml @@ -7,20 +7,11 @@ android:id="@+id/toolbar_include" layout="@layout/toolbar_standalone" /> - + android:layout_height="match_parent" /> - - - \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/log_display_regular_item.xml b/OpenKeychain/src/main/res/layout/log_display_regular_item.xml new file mode 100644 index 000000000..50505cb58 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/log_display_regular_item.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/OpenKeychain/src/main/res/layout/log_display_item.xml b/OpenKeychain/src/main/res/layout/log_display_sublog_item.xml similarity index 78% rename from OpenKeychain/src/main/res/layout/log_display_item.xml rename to OpenKeychain/src/main/res/layout/log_display_sublog_item.xml index 25249003f..0a75748ad 100644 --- a/OpenKeychain/src/main/res/layout/log_display_item.xml +++ b/OpenKeychain/src/main/res/layout/log_display_sublog_item.xml @@ -1,9 +1,18 @@ - + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + + super:slm_headerDisplay="sticky|inline" + super:slm_section_sectionManager="linear" + tools:ignore="ResAuto"> + android:layout_gravity="center_vertical" + tools:text="Log Entry Text" /> + android:layout_gravity="center_vertical" + tools:text="Log Entry Text" /> diff --git a/OpenKeychain/src/main/res/layout/select_dummy_key_item.xml b/OpenKeychain/src/main/res/layout/select_dummy_key_item.xml new file mode 100644 index 000000000..c217d018c --- /dev/null +++ b/OpenKeychain/src/main/res/layout/select_dummy_key_item.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/select_key_item.xml b/OpenKeychain/src/main/res/layout/select_encrypt_key_item.xml similarity index 87% rename from OpenKeychain/src/main/res/layout/select_key_item.xml rename to OpenKeychain/src/main/res/layout/select_encrypt_key_item.xml index aff0a8215..69e3f8cc0 100644 --- a/OpenKeychain/src/main/res/layout/select_key_item.xml +++ b/OpenKeychain/src/main/res/layout/select_encrypt_key_item.xml @@ -1,11 +1,14 @@ - + android:maxLines="1"> diff --git a/OpenKeychain/src/main/res/layout/select_sign_key_item.xml b/OpenKeychain/src/main/res/layout/select_sign_key_item.xml new file mode 100644 index 000000000..e27eb2518 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/select_sign_key_item.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml b/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml index 3b9215d50..80bb21e16 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_certs_fragment.xml @@ -26,22 +26,45 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:gravity="center"> - + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml b/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml index f99c012c9..ee8f0a196 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_certs_header.xml @@ -1,5 +1,8 @@ - @@ -9,8 +12,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="start|left" - android:text="header text" /> + tools:text="header text" /> + \ No newline at end of file diff --git a/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml b/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml index e84a98cdb..f1a59a4f9 100644 --- a/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml +++ b/OpenKeychain/src/main/res/layout/view_key_adv_certs_item.xml @@ -1,46 +1,52 @@ - + android:layout_alignParentStart="true" + tools:text="signer name" /> + android:layout_alignParentStart="true" + tools:text="<user@example.com>" /> + android:layout_marginRight="10dp" + android:layout_marginEnd="10dp" + tools:text="status" />