aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdithya Abraham Philip <adithyaphilip@gmail.com>2015-07-02 01:57:15 +0530
committerAdithya Abraham Philip <adithyaphilip@gmail.com>2015-07-02 02:30:49 +0530
commitaf3868d54715cdb09932caedd565a9e2778faf83 (patch)
treec5b57f7df0478ae5018c969cc432c2d6c28a139e
parent64a3cc5a9f232b8e3831bc1081982625e0e313ee (diff)
downloadopen-keychain-af3868d54715cdb09932caedd565a9e2778faf83.tar.gz
open-keychain-af3868d54715cdb09932caedd565a9e2778faf83.tar.bz2
open-keychain-af3868d54715cdb09932caedd565a9e2778faf83.zip
reworked keyserver preferences with recyclerview
-rw-r--r--OpenKeychain/build.gradle23
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java29
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java194
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java363
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java)89
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java106
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java92
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java39
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java70
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_add_white_24dp.pngbin0 -> 127 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-hdpi/ic_reorder_grey_500_24dp.pngbin0 -> 134 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_add_white_24dp.pngbin0 -> 88 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-mdpi/ic_reorder_grey_500_24dp.pngbin0 -> 101 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_add_white_24dp.pngbin0 -> 97 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xhdpi/ic_reorder_grey_500_24dp.pngbin0 -> 138 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_add_white_24dp.pngbin0 -> 97 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxhdpi/ic_reorder_grey_500_24dp.pngbin0 -> 186 bytes
-rw-r--r--OpenKeychain/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.pngbin0 -> 102 bytes
-rw-r--r--OpenKeychain/src/main/res/layout/key_server_preference.xml117
-rw-r--r--OpenKeychain/src/main/res/layout/settings_keyserver_fragment.xml7
-rw-r--r--OpenKeychain/src/main/res/layout/settings_keyserver_item.xml46
-rw-r--r--OpenKeychain/src/main/res/menu/keyserver_pref_menu.xml10
-rw-r--r--OpenKeychain/src/main/res/values/strings.xml12
-rw-r--r--OpenKeychain/src/main/res/values/styles.xml8
25 files changed, 928 insertions, 318 deletions
diff --git a/OpenKeychain/build.gradle b/OpenKeychain/build.gradle
index 17d0821a1..b7137e566 100644
--- a/OpenKeychain/build.gradle
+++ b/OpenKeychain/build.gradle
@@ -11,7 +11,7 @@ dependencies {
compile 'com.android.support:support-v4:22.2.0'
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'
- compile 'com.android.support:recyclerview-v7:22.1.0'
+ compile 'com.android.support:recyclerview-v7:22.2.0'
compile 'com.android.support:cardview-v7:22.1.0'
// Unit tests in the local JVM with Robolectric
@@ -75,7 +75,7 @@ dependencyVerification {
'com.android.support:support-v4:7bb6e40a18774aa2595e4d8f9fe0ae14e61670f71a1279272fb0b79b8be71180',
'com.android.support:appcompat-v7:2d5867698410b41f75140c91d6c1e58da74ae0f97baf6e0bdd1f7cc1017ceb2c',
'com.android.support:design:41ff5f700d1bf8c190e1d3ac33582f92ad6e1f3b1080c8b8e2ac4f7b703be76a',
- 'com.android.support:recyclerview-v7:522d323079a29bcd76173bd9bc7535223b4af3e5eefef9d9287df1f9e54d0c10',
+ 'com.android.support:recyclerview-v7:3a8da14585fa1c81f06e7cef4d93a7641f0323d8f984ff9a7bd7a6e416b46888',
'com.android.support:cardview-v7:8dc99af71fec000baa4470c3907755264f15f816920861bc015b2babdbb49807',
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718',
@@ -93,16 +93,15 @@ dependencyVerification {
'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b',
'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f',
'com.nispok:snackbar:80bebc8e5d8b3d728cd5f2336e2d0c1cc2a6b7dc4b55d36acd6b75a78265590a',
-// 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580',
-// 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429',
-// 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b',
-// 'com.madgag.spongycastle:pg:160b345b10a2c92dc731453eec87037377f66a8e14a0648d404d7b193c4e380d',
-// 'com.madgag.spongycastle:pkix:0b4f3301ea12dd9f25d71770e6ea9f75e0611bf53062543e47be5bc15340a7e4',
-// 'com.madgag.spongycastle:prov:7325942e0b39f5fb35d6380818eed4b826e7dfc7570ad35b696d778049d8c36a',
-// 'OpenKeychain.extern:minidns:77b1786d29469e3b21f9404827cab811edc857cd68bc732cd57f11307c332eae',
-// 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf',
-// 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f',
-// 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23',
+ //'open-keychain.extern.openpgp-api-lib:openpgp-api:cbac23f4dcfdd8cf580e6dcafaaeb8958894be9765b16058b30df173af714ce0',
+ //'open-keychain.extern.openkeychain-api-lib:openkeychain-intents:24c5403f1225597489924d31a2d3638f5800f5c06ac0adbef3b90a43c5e41b92',
+ //'open-keychain.extern.spongycastle:core:2cccd7471684eab9c3bc2d74e117d27dd2b10b9edbbf85d2718fc2dfb4c3fbe6',
+ //'open-keychain.extern.spongycastle:pg:9b333ccd06f8ec5d3857a8c516f83b0d1bff4d9b0208809c1468300ebbe2cd01',
+ //'open-keychain.extern.spongycastle:pkix:534001eca2a44ca4277a93c4293040bf4137c81f2c321f0cd92411acd7762bc4',
+ //'open-keychain.extern.spongycastle:prov:86c39b96a9604a6633ba4c47fac6f46b829d2a580dfac33514d5c2eaceda46d0',
+ //'open-keychain.extern:minidns:cad64ffcda40e2351b6a7429fbfb60e32ff167e852503fff4fa097cde134957a',
+ //'open-keychain.extern.KeybaseLib:Lib:a81380c89bb5f080d4f13cb24172a19d8f0031c32fa2ea0e346c54d215849900',
+ //'open-keychain.extern:safeslinger-exchange:5701b33044f5914ec58046183e900576c1285ad191a8a04d59c7600d31f36361',
'com.android.support:support-annotations:beac5cae60bdb597df9af9c916f785c2f71f8c8ae4be9a32d4298dea85496a42',
]
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
index a552e1c55..6fc0aaac6 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsActivity.java
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.AppCompatPreferenceActivity;
import org.sufficientlysecure.keychain.ui.widget.IntegerListPreference;
+import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Preferences;
import java.util.List;
@@ -124,27 +125,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case REQUEST_CODE_KEYSERVER_PREF: {
- if (resultCode == RESULT_CANCELED || data == null) {
- return;
- }
- String servers[] = data
- .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS);
- sPreferences.setKeyServers(servers);
- mKeyServerPreference.setSummary(keyserverSummary(this));
- break;
- }
-
- default: {
- super.onActivityResult(requestCode, resultCode, data);
- break;
- }
- }
- }
-
- @Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);
loadHeadersFromResource(R.xml.preference_headers, target);
@@ -190,12 +170,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_KEYSERVER_PREF: {
- if (resultCode == RESULT_CANCELED || data == null) {
- return;
- }
- String servers[] = data
- .getStringArrayExtra(SettingsKeyServerActivity.EXTRA_KEY_SERVERS);
- sPreferences.setKeyServers(servers);
+ // update preference, in case it changed
mKeyServerPreference.setSummary(keyserverSummary(getActivity()));
break;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
index dc203756f..f61ada84f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyServerActivity.java
@@ -17,89 +17,23 @@
package org.sufficientlysecure.keychain.ui;
-import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Messenger;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
-import org.sufficientlysecure.keychain.ui.dialog.AddKeyserverDialogFragment;
-import org.sufficientlysecure.keychain.ui.util.Notify;
-import org.sufficientlysecure.keychain.ui.widget.Editor;
-import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
-import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
-import java.util.Vector;
-
-public class SettingsKeyServerActivity extends BaseActivity implements OnClickListener,
- EditorListener {
+public class SettingsKeyServerActivity extends BaseActivity {
public static final String EXTRA_KEY_SERVERS = "key_servers";
- private LayoutInflater mInflater;
- private ViewGroup mEditors;
- private View mAdd;
- private View mRotate;
- private TextView mTitle;
- private TextView mSummary;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Inflate a "Done"/"Cancel" custom action bar view
- setFullScreenDialogDoneClose(R.string.btn_save,
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- okClicked();
- }
- },
- new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- cancelClicked();
- }
- });
-
- mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- mTitle = (TextView) findViewById(R.id.title);
- mSummary = (TextView) findViewById(R.id.summary);
- mSummary.setText(getText(R.string.label_first_keyserver_is_used));
-
- mTitle.setText(R.string.label_keyservers);
-
- mEditors = (ViewGroup) findViewById(R.id.editors);
- mAdd = findViewById(R.id.add);
- mAdd.setOnClickListener(this);
-
- mRotate = findViewById(R.id.rotate);
- mRotate.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- Vector<String> servers = serverList();
- String first = servers.get(0);
- if (first != null) {
- servers.remove(0);
- servers.add(first);
- String[] dummy = {};
- makeServerList(servers.toArray(dummy));
- }
- }
- });
-
Intent intent = getIntent();
String servers[] = intent.getStringArrayExtra(EXTRA_KEY_SERVERS);
- makeServerList(servers);
+ loadFragment(savedInstanceState, servers);
}
@Override
@@ -107,118 +41,22 @@ public class SettingsKeyServerActivity extends BaseActivity implements OnClickLi
setContentView(R.layout.key_server_preference);
}
- private void makeServerList(String[] servers) {
- if (servers != null) {
- mEditors.removeAllViews();
- for (String serv : servers) {
- KeyServerEditor view = (KeyServerEditor) mInflater.inflate(
- R.layout.key_server_editor, mEditors, false);
- view.setEditorListener(this);
- view.setValue(serv);
- mEditors.addView(view);
- }
+ private void loadFragment(Bundle savedInstanceState, String[] keyservers) {
+ // 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.
+ if (savedInstanceState != null) {
+ return;
}
- }
-
- public void onDeleted(Editor editor, boolean wasNewItem) {
- // nothing to do
- }
- @Override
- public void onEdited() {
+ SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers);
- }
-
- // button to add keyserver clicked
- public void onClick(View v) {
- Handler returnHandler = new Handler() {
- @Override
- public void handleMessage(Message message) {
- Bundle data = message.getData();
- switch (message.what) {
- case AddKeyserverDialogFragment.MESSAGE_OKAY: {
- boolean verified = data.getBoolean(AddKeyserverDialogFragment.MESSAGE_VERIFIED);
- if (verified) {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_verified, Notify.Style.OK).show();
- } else {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_without_verification,
- Notify.Style.WARN).show();
- }
- String keyserver = data.getString(AddKeyserverDialogFragment.MESSAGE_KEYSERVER);
- addKeyserver(keyserver);
- break;
- }
- case AddKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: {
- AddKeyserverDialogFragment.FailureReason failureReason =
- (AddKeyserverDialogFragment.FailureReason) data.getSerializable(
- AddKeyserverDialogFragment.MESSAGE_FAILURE_REASON);
- switch (failureReason) {
- case CONNECTION_FAILED: {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_connection_failed,
- Notify.Style.ERROR).show();
- break;
- }
- case INVALID_URL: {
- Notify.create(SettingsKeyServerActivity.this,
- R.string.add_keyserver_invalid_url,
- Notify.Style.ERROR).show();
- break;
- }
- }
- break;
- }
- }
- }
- };
-
- // Create a new Messenger for the communication back
- Messenger messenger = new Messenger(returnHandler);
- AddKeyserverDialogFragment dialogFragment = AddKeyserverDialogFragment
- .newInstance(messenger);
- dialogFragment.show(getSupportFragmentManager(), "addKeyserverDialog");
- }
-
- public void addKeyserver(String keyserverUrl) {
- KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
- mEditors, false);
- view.setEditorListener(this);
- view.setValue(keyserverUrl);
- mEditors.addView(view);
- }
-
- private void cancelClicked() {
- setResult(RESULT_CANCELED, null);
- finish();
- }
-
- private Vector<String> serverList() {
- Vector<String> servers = new Vector<>();
- for (int i = 0; i < mEditors.getChildCount(); ++i) {
- KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
- String tmp = editor.getValue();
- if (tmp.length() > 0) {
- servers.add(tmp);
- }
- }
- return servers;
- }
-
- private void okClicked() {
- Intent data = new Intent();
- Vector<String> servers = new Vector<>();
- for (int i = 0; i < mEditors.getChildCount(); ++i) {
- KeyServerEditor editor = (KeyServerEditor) mEditors.getChildAt(i);
- String tmp = editor.getValue();
- if (tmp.length() > 0) {
- servers.add(tmp);
- }
- }
- String[] dummy = new String[0];
- data.putExtra(EXTRA_KEY_SERVERS, servers.toArray(dummy));
- setResult(RESULT_OK, data);
- finish();
+ // Add the fragment to the 'fragment_container' FrameLayout
+ // NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.keyserver_settings_fragment_container, fragment)
+ .commitAllowingStateLoss();
+ // do it immediately!
+ getSupportFragmentManager().executePendingTransactions();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
new file mode 100644
index 000000000..20565aa7d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2012-2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2015 Adithya Abraham Philip <adithyaphilip@gmail.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.ui;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback;
+import org.sufficientlysecure.keychain.ui.util.Notify;
+import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
+import org.sufficientlysecure.keychain.util.Preferences;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class SettingsKeyserverFragment extends Fragment implements RecyclerItemClickListener.OnItemClickListener {
+
+ private static final String ARG_KEYSERVER_ARRAY = "arg_keyserver_array";
+ private ItemTouchHelper mItemTouchHelper;
+
+ private ArrayList<String> mKeyservers;
+ private KeyserverListAdapter mAdapter;
+
+ public static SettingsKeyserverFragment newInstance(String[] keyservers) {
+ Bundle args = new Bundle();
+ args.putStringArray(ARG_KEYSERVER_ARRAY, keyservers);
+
+ SettingsKeyserverFragment fragment = new SettingsKeyserverFragment();
+ fragment.setArguments(args);
+
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
+ savedInstanceState) {
+
+ return inflater.inflate(R.layout.settings_keyserver_fragment, null);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ String keyservers[] = getArguments().getStringArray(ARG_KEYSERVER_ARRAY);
+ mKeyservers = new ArrayList<>(Arrays.asList(keyservers));
+ saveKeyserverList(); // in case user does not make any changes
+
+ mAdapter = new KeyserverListAdapter(mKeyservers);
+
+ RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.keyserver_recycler_view);
+ // recyclerView.setHasFixedSize(true); // the size of the first item changes
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+
+
+ ItemTouchHelper.Callback callback = new ItemTouchHelperDragCallback(mAdapter);
+ mItemTouchHelper = new ItemTouchHelper(callback);
+ mItemTouchHelper.attachToRecyclerView(recyclerView);
+
+ // for clicks
+ recyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), this));
+
+ // can't use item decoration because it doesn't move with drag and drop
+ // recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), null));
+
+ // We have a menu item to show in action bar.
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.keyserver_pref_menu, menu);
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+
+ case R.id.menu_add_keyserver:
+ startAddKeyserverDialog();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void startAddKeyserverDialog() {
+ // keyserver and position have no meaning
+ startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.ADD, null, -1);
+ }
+
+ private void startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction action,
+ String keyserver, final int position) {
+ Handler returnHandler = new Handler() {
+ @Override
+ public void handleMessage(Message message) {
+ Bundle data = message.getData();
+ switch (message.what) {
+ case AddEditKeyserverDialogFragment.MESSAGE_OKAY: {
+ boolean deleted =
+ data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER_DELETED
+ , false);
+ if (deleted) {
+ Notify.create(getActivity(),
+ getActivity().getString(
+ R.string.keyserver_deleted, mKeyservers.get(position)),
+ Notify.Style.OK)
+ .show();
+ deleteKeyserver(position);
+ return;
+ }
+ boolean verified =
+ data.getBoolean(AddEditKeyserverDialogFragment.MESSAGE_VERIFIED);
+ if (verified) {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_verified, Notify.Style.OK).show();
+ } else {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_without_verification,
+ Notify.Style.WARN).show();
+ }
+ String keyserver = data.getString(
+ AddEditKeyserverDialogFragment.MESSAGE_KEYSERVER);
+
+ AddEditKeyserverDialogFragment.DialogAction dialogAction
+ = (AddEditKeyserverDialogFragment.DialogAction) data.getSerializable(
+ AddEditKeyserverDialogFragment.MESSAGE_DIALOG_ACTION);
+ switch (dialogAction) {
+ case ADD:
+ addKeyserver(keyserver);
+ break;
+ case EDIT:
+ editKeyserver(keyserver, position);
+ break;
+ }
+ break;
+ }
+ case AddEditKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: {
+ AddEditKeyserverDialogFragment.FailureReason failureReason =
+ (AddEditKeyserverDialogFragment.FailureReason) data.getSerializable(
+ AddEditKeyserverDialogFragment.MESSAGE_FAILURE_REASON);
+ switch (failureReason) {
+ case CONNECTION_FAILED: {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_connection_failed,
+ Notify.Style.ERROR).show();
+ break;
+ }
+ case INVALID_URL: {
+ Notify.create(getActivity(),
+ R.string.add_keyserver_invalid_url,
+ Notify.Style.ERROR).show();
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ };
+
+ // Create a new Messenger for the communication back
+ Messenger messenger = new Messenger(returnHandler);
+ AddEditKeyserverDialogFragment dialogFragment = AddEditKeyserverDialogFragment
+ .newInstance(messenger, action, keyserver, position);
+ dialogFragment.show(getFragmentManager(), "addKeyserverDialog");
+ }
+
+ private void addKeyserver(String keyserver) {
+ mKeyservers.add(keyserver);
+ mAdapter.notifyItemInserted(mKeyservers.size() - 1);
+ saveKeyserverList();
+ }
+
+ private void editKeyserver(String newKeyserver, int position) {
+ mKeyservers.set(position, newKeyserver);
+ mAdapter.notifyItemChanged(position);
+ saveKeyserverList();
+ }
+
+ private void deleteKeyserver(int position) {
+ mKeyservers.remove(position);
+ // we use this
+ mAdapter.notifyItemRemoved(position);
+ if (position == 0 && mKeyservers.size() > 0) {
+ // if we deleted the first item, we need the adapter to redraw the new first item
+ mAdapter.notifyItemChanged(0);
+ }
+ saveKeyserverList();
+ }
+
+ private void saveKeyserverList() {
+ String servers[] = mKeyservers.toArray(new String[mKeyservers.size()]);
+ Preferences.getPreferences(getActivity()).setKeyServers(servers);
+ }
+
+ @Override
+ public void onItemClick(View view, int position) {
+ startEditKeyserverDialog(AddEditKeyserverDialogFragment.DialogAction.EDIT,
+ mKeyservers.get(position), position);
+ }
+
+ public class KeyserverListAdapter extends RecyclerView.Adapter<KeyserverListAdapter.ViewHolder>
+ implements ItemTouchHelperAdapter {
+
+ // to update the ViewHolder associated with first item, for when an item is deleted
+ private ViewHolder mFirstItem;
+
+ private final List<String> mKeyservers;
+
+ public KeyserverListAdapter(List<String> keyservers) {
+ mKeyservers = keyservers;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.settings_keyserver_item, parent, false);
+ ViewHolder viewHolder = new ViewHolder(view);
+ return viewHolder;
+ }
+
+ @Override
+ public void onBindViewHolder(final ViewHolder holder, int position) {
+ if (position == 0) {
+ mFirstItem = holder;
+ }
+ holder.keyserverUrl.setText(mKeyservers.get(position));
+
+ // Start a drag whenever the handle view it touched
+ holder.dragHandleView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_DOWN) {
+ mItemTouchHelper.startDrag(holder);
+ }
+ return false;
+ }
+ });
+
+ selectUnselectKeyserver(holder, position);
+ }
+
+ private void selectUnselectKeyserver(ViewHolder holder, int position) {
+
+ if (position == 0) {
+ holder.showAsSelectedKeyserver();
+ } else {
+ holder.showAsUnselectedKeyserver();
+ }
+ }
+
+ @Override
+ public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
+ int fromPosition, int toPosition) {
+ Collections.swap(mKeyservers, fromPosition, toPosition);
+ saveKeyserverList();
+ selectUnselectKeyserver((ViewHolder) target, fromPosition);
+ // we don't want source to change color while dragging, therefore we just set
+ // isSelectedKeyserver instead of selectUnselectKeyserver
+ ((ViewHolder) source).isSelectedKeyserver = toPosition == 0;
+
+ notifyItemMoved(fromPosition, toPosition);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mKeyservers.size();
+ }
+
+ public class ViewHolder extends RecyclerView.ViewHolder implements
+ ItemTouchHelperViewHolder {
+
+ public final ViewGroup outerLayout;
+ public final TextView selectedServerLabel;
+ public final TextView keyserverUrl;
+ public final ImageView dragHandleView;
+
+ private boolean isSelectedKeyserver = false;
+
+ public ViewHolder(View itemView) {
+ super(itemView);
+ outerLayout = (ViewGroup) itemView.findViewById(R.id.outer_layout);
+ selectedServerLabel = (TextView) itemView.findViewById(
+ R.id.selected_keyserver_title);
+ keyserverUrl = (TextView) itemView.findViewById(R.id.keyserver_tv);
+ dragHandleView = (ImageView) itemView.findViewById(R.id.drag_handle);
+
+ itemView.setClickable(true);
+ }
+
+ public void showAsSelectedKeyserver() {
+ isSelectedKeyserver = true;
+ selectedServerLabel.setVisibility(View.VISIBLE);
+ outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark));
+ }
+
+ public void showAsUnselectedKeyserver() {
+ isSelectedKeyserver = false;
+ selectedServerLabel.setVisibility(View.GONE);
+ outerLayout.setBackgroundColor(Color.WHITE);
+ }
+
+ @Override
+ public void onItemSelected() {
+ selectedServerLabel.setVisibility(View.GONE);
+ itemView.setBackgroundColor(Color.LTGRAY);
+ }
+
+ @Override
+ public void onItemClear() {
+ if (isSelectedKeyserver) {
+ showAsSelectedKeyserver();
+ } else {
+ showAsUnselectedKeyserver();
+ }
+ }
+ }
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
index 2c1714b67..321242b2f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddKeyserverDialogFragment.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddEditKeyserverDialogFragment.java
@@ -53,8 +53,11 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.TlsHelper;
-public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
- private static final String ARG_MESSENGER = "messenger";
+public class AddEditKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
+ private static final String ARG_MESSENGER = "arg_messenger";
+ private static final String ARG_ACTION = "arg_dialog_action";
+ private static final String ARG_POSITION = "arg_position";
+ private static final String ARG_KEYSERVER = "arg_keyserver";
public static final int MESSAGE_OKAY = 1;
public static final int MESSAGE_VERIFICATION_FAILED = 2;
@@ -62,20 +65,37 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
public static final String MESSAGE_KEYSERVER = "new_keyserver";
public static final String MESSAGE_VERIFIED = "verified";
public static final String MESSAGE_FAILURE_REASON = "failure_reason";
+ public static final String MESSAGE_KEYSERVER_DELETED = "keyserver_deleted";
+ public static final String MESSAGE_DIALOG_ACTION = "message_dialog_action";
+ public static final String MESSAGE_EDIT_POSITION = "keyserver_edited_position";
private Messenger mMessenger;
+ private DialogAction mDialogAction;
+ private int mPosition;
+
private EditText mKeyserverEditText;
private CheckBox mVerifyKeyserverCheckBox;
+ public enum DialogAction {
+ ADD,
+ EDIT
+ }
+
public enum FailureReason {
INVALID_URL,
CONNECTION_FAILED
}
- public static AddKeyserverDialogFragment newInstance(Messenger messenger) {
- AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment();
+ public static AddEditKeyserverDialogFragment newInstance(Messenger messenger,
+ DialogAction action,
+ String keyserver,
+ int position) {
+ AddEditKeyserverDialogFragment frag = new AddEditKeyserverDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_MESSENGER, messenger);
+ args.putSerializable(ARG_ACTION, action);
+ args.putString(ARG_KEYSERVER, keyserver);
+ args.putInt(ARG_POSITION, position);
frag.setArguments(args);
@@ -88,11 +108,11 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
final Activity activity = getActivity();
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
+ mDialogAction = (DialogAction) getArguments().getSerializable(ARG_ACTION);
+ mPosition = getArguments().getInt(ARG_POSITION);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
- alert.setTitle(R.string.add_keyserver_dialog_title);
-
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.add_keyserver_dialog, null);
alert.setView(view);
@@ -100,14 +120,26 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text);
mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox);
- // we don't want dialog to be dismissed on click, thereby requiring the hack seen below
- // and in onStart
+ switch (mDialogAction) {
+ case ADD: {
+ alert.setTitle(R.string.add_keyserver_dialog_title);
+ break;
+ }
+ case EDIT: {
+ alert.setTitle(R.string.edit_keyserver_dialog_title);
+ mKeyserverEditText.setText(getArguments().getString(ARG_KEYSERVER));
+ break;
+ }
+ }
+
+ // we don't want dialog to be dismissed on click for keyserver addition or edit,
+ // thereby requiring the hack seen below and in onStart
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// we need to have an empty listener to prevent errors on some devices as mentioned
// at http://stackoverflow.com/q/13746412/3000919
- // actual listener set in onStart
+ // actual listener set in onStart for adding keyservers or editing them
}
});
@@ -119,6 +151,23 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
}
});
+ switch (mDialogAction) {
+ case EDIT: {
+ alert.setNeutralButton(R.string.label_keyserver_dialog_delete,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ deleteKeyserver(mPosition);
+ }
+ });
+ break;
+ }
+ case ADD: {
+ // do nothing
+ break;
+ }
+ }
+
// Hack to open keyboard.
// This is the only method that I found to work across all Android versions
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
@@ -155,25 +204,41 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
+ // behaviour same for edit and add
String keyserverUrl = mKeyserverEditText.getText().toString();
if (mVerifyKeyserverCheckBox.isChecked()) {
verifyConnection(keyserverUrl);
} else {
dismiss();
// return unverified keyserver back to activity
- addKeyserver(keyserverUrl, false);
+ keyserverEdited(keyserverUrl, false);
}
}
});
}
}
- public void addKeyserver(String keyserver, boolean verified) {
+ public void keyserverEdited(String keyserver, boolean verified) {
dismiss();
Bundle data = new Bundle();
+ data.putSerializable(MESSAGE_DIALOG_ACTION, mDialogAction);
data.putString(MESSAGE_KEYSERVER, keyserver);
data.putBoolean(MESSAGE_VERIFIED, verified);
+ if (mDialogAction == DialogAction.EDIT) {
+ data.putInt(MESSAGE_EDIT_POSITION, mPosition);
+ }
+
+ sendMessageToHandler(MESSAGE_OKAY, data);
+ }
+
+ public void deleteKeyserver(int position) {
+ dismiss();
+ Bundle data = new Bundle();
+ data.putSerializable(MESSAGE_DIALOG_ACTION, DialogAction.EDIT);
+ data.putInt(MESSAGE_EDIT_POSITION, position);
+ data.putBoolean(MESSAGE_KEYSERVER_DELETED, true);
+
sendMessageToHandler(MESSAGE_OKAY, data);
}
@@ -238,7 +303,7 @@ public class AddKeyserverDialogFragment extends DialogFragment implements OnEdit
protected void onPostExecute(FailureReason failureReason) {
mProgressDialog.dismiss();
if (failureReason == null) {
- addKeyserver(mKeyserver, true);
+ keyserverEdited(mKeyserver, true);
} else {
verificationFailed(failureReason);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java
new file mode 100644
index 000000000..95199bcd5
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/DividerItemDecoration.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+public class DividerItemDecoration extends RecyclerView.ItemDecoration {
+
+ private static final int[] ATTRS = new int[]{
+ android.R.attr.listDivider
+ };
+
+ public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
+
+ public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
+
+ private Drawable mDivider;
+
+ private int mOrientation;
+
+ public DividerItemDecoration(Context context, int orientation) {
+ final TypedArray a = context.obtainStyledAttributes(ATTRS);
+ mDivider = a.getDrawable(0);
+ a.recycle();
+ setOrientation(orientation);
+ }
+
+ public void setOrientation(int orientation) {
+ if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
+ throw new IllegalArgumentException("invalid orientation");
+ }
+ mOrientation = orientation;
+ }
+
+ @Override
+ public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ if (mOrientation == VERTICAL_LIST) {
+ drawVertical(c, parent);
+ } else {
+ drawHorizontal(c, parent);
+ }
+ }
+
+ public void drawVertical(Canvas c, RecyclerView parent) {
+ final int left = parent.getPaddingLeft();
+ final int right = parent.getWidth() - parent.getPaddingRight();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int top = child.getBottom() + params.bottomMargin;
+ final int bottom = top + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ public void drawHorizontal(Canvas c, RecyclerView parent) {
+ final int top = parent.getPaddingTop();
+ final int bottom = parent.getHeight() - parent.getPaddingBottom();
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ final int left = child.getRight() + params.rightMargin;
+ final int right = left + mDivider.getIntrinsicHeight();
+ mDivider.setBounds(left, top, right, bottom);
+ mDivider.draw(c);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ if (mOrientation == VERTICAL_LIST) {
+ outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
+ } else {
+ outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
+ }
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java
new file mode 100644
index 000000000..c691182bf
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * Interface to listen for a move or dismissal event from a {@link ItemTouchHelper.Callback}.
+ */
+public interface ItemTouchHelperAdapter {
+
+ /**
+ * Called when an item has been dragged far enough to trigger a move. This is called every time
+ * an item is shifted, and <strong>not</strong> at the end of a "drop" event.<br/>
+ * <br/>
+ * Implementations should call {@link RecyclerView.Adapter#notifyItemMoved(int, int)} after
+ * adjusting the underlying data to reflect this move.
+ *
+ * @param fromPosition The start position of the moved item.
+ * @param toPosition Then resolved position of the moved item.
+ * @see RecyclerView#getAdapterPositionFor(RecyclerView.ViewHolder)
+ * @see RecyclerView.ViewHolder#getAdapterPosition()
+ */
+ void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target,
+ int fromPosition, int toPosition);
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java
new file mode 100644
index 000000000..0fd24581d
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperDragCallback.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * An implementation of {@link ItemTouchHelper.Callback} that enables basic drag & drop and
+ * swipe-to-dismiss. Drag events are automatically started by an item long-press.<br/>
+ * </br/>
+ * Expects the <code>RecyclerView.Adapter</code> to listen for {@link
+ * ItemTouchHelperAdapter} callbacks and the <code>RecyclerView.ViewHolder</code> to implement
+ * {@link ItemTouchHelperViewHolder}.
+ */
+public class ItemTouchHelperDragCallback extends ItemTouchHelper.Callback {
+
+ private final ItemTouchHelperAdapter mAdapter;
+
+ public ItemTouchHelperDragCallback(ItemTouchHelperAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ @Override
+ public boolean isLongPressDragEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isItemViewSwipeEnabled() {
+ return false;
+ }
+
+ @Override
+ public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ // Enable drag and swipe in both directions
+ final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
+ final int swipeFlags = 0;
+ return makeMovementFlags(dragFlags, swipeFlags);
+ }
+
+ @Override
+ public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source,
+ RecyclerView.ViewHolder target) {
+ if (source.getItemViewType() != target.getItemViewType()) {
+ return false;
+ }
+
+ // Notify the adapter of the move
+ mAdapter.onItemMove(source, target, source.getAdapterPosition(), target.getAdapterPosition());
+ return true;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
+ // we don't support swipe
+ }
+
+ @Override
+ public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
+ if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
+ // Let the view holder know that this item is being moved or dragged
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemSelected();
+ }
+
+ super.onSelectedChanged(viewHolder, actionState);
+ }
+
+ @Override
+ public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
+ super.clearView(recyclerView, viewHolder);
+
+ // Tell the view holder it's time to restore the idle state
+ ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
+ itemViewHolder.onItemClear();
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java
new file mode 100644
index 000000000..97e70d71e
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/ItemTouchHelperViewHolder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 Paul Burke
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.support.v7.widget.helper.ItemTouchHelper;
+
+/**
+ * Interface to notify an item ViewHolder of relevant callbacks from {@link
+ * android.support.v7.widget.helper.ItemTouchHelper.Callback}.
+ */
+public interface ItemTouchHelperViewHolder {
+
+ /**
+ * Called when the {@link ItemTouchHelper} first registers an item as being moved or swiped.
+ * Implementations should update the item view to indicate it's active state.
+ */
+ void onItemSelected();
+
+
+ /**
+ * Called when the {@link ItemTouchHelper} has completed the move or swipe, and the active item
+ * state should be cleared.
+ */
+ void onItemClear();
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java
new file mode 100644
index 000000000..7efcbb30c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/recyclerview/RecyclerItemClickListener.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 Jacob Tabak
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package org.sufficientlysecure.keychain.ui.util.recyclerview;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * based on http://stackoverflow.com/a/26196831/3000919
+ */
+public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
+ private OnItemClickListener mListener;
+ private boolean mIgnoreTouch = false;
+
+ public interface OnItemClickListener {
+ void onItemClick(View view, int position);
+ }
+
+ GestureDetector mGestureDetector;
+
+ public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
+ if (mIgnoreTouch) {
+ return false;
+ }
+ View childView = view.findChildViewUnder(e.getX(), e.getY());
+ if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
+ mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
+ // TODO: should we move mListener.onItemClick here
+ }
+
+ @Override
+ public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+ mIgnoreTouch = disallowIntercept;
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_add_white_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..694179bd4
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_reorder_grey_500_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_reorder_grey_500_24dp.png
new file mode 100644
index 000000000..58fe2c52e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-hdpi/ic_reorder_grey_500_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_add_white_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..3856041d7
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_reorder_grey_500_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_reorder_grey_500_24dp.png
new file mode 100644
index 000000000..d6abf0936
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-mdpi/ic_reorder_grey_500_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_add_white_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..67bb598e5
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_reorder_grey_500_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_reorder_grey_500_24dp.png
new file mode 100644
index 000000000..0af0cb76d
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xhdpi/ic_reorder_grey_500_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..0fdced8fc
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_reorder_grey_500_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_reorder_grey_500_24dp.png
new file mode 100644
index 000000000..1d9c88d10
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_reorder_grey_500_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png
new file mode 100644
index 000000000..d64c22e9e
--- /dev/null
+++ b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_add_white_24dp.png
Binary files differ
diff --git a/OpenKeychain/src/main/res/layout/key_server_preference.xml b/OpenKeychain/src/main/res/layout/key_server_preference.xml
index 5319a3ec0..570d57d9b 100644
--- a/OpenKeychain/src/main/res/layout/key_server_preference.xml
+++ b/OpenKeychain/src/main/res/layout/key_server_preference.xml
@@ -1,94 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:orientation="vertical">
<include
android:id="@+id/toolbar_include"
layout="@layout/toolbar_standalone" />
- <LinearLayout
- android:layout_below="@id/toolbar_include"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/text_layout"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- android:minHeight="?android:attr/listPreferredItemHeight"
- android:orientation="horizontal">
-
- <LinearLayout
- android:layout_width="0dip"
- android:layout_height="wrap_content"
- android:layout_marginBottom="6sp"
- android:layout_marginLeft="16sp"
- android:layout_marginRight="6sp"
- android:layout_marginTop="6sp"
- android:layout_weight="1"
- android:background="@android:drawable/menuitem_background"
- android:orientation="vertical"
- android:focusable="true">
-
- <TextView
- android:id="@+id/title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:focusable="true"
- android:singleLine="true"
- android:textAppearance="?android:attr/textAppearanceLarge" />
-
- <TextView
- android:id="@+id/summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceSmall" />
- </LinearLayout>
-
- <Button
- android:id="@+id/rotate"
- android:layout_width="wrap_content"
- android:layout_height="31dp"
- android:layout_gravity="center_vertical"
- android:layout_marginLeft="4dip"
- android:layout_marginRight="6dip"
- android:text="rotate"
- android:textColor="#ffffffff"
- android:textStyle="bold"
- android:background="@drawable/button_rounded_blue" />
-
- <ImageButton
- android:id="@+id/add"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:layout_margin="10dp"
- android:src="@drawable/plus"
- android:background="@drawable/button_rounded_green" />
- </LinearLayout>
-
- <View
- android:id="@+id/separator"
- android:layout_width="fill_parent"
- android:layout_height="1dip"
- android:background="?android:attr/listDivider" />
-
- <ScrollView
- android:layout_width="fill_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/editors"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" />
- </ScrollView>
-
- </LinearLayout>
-
-</RelativeLayout> \ No newline at end of file
+ <TextView
+ android:layout_marginLeft="6sp"
+ android:layout_marginRight="6sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/label_keyservers_title"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="6sp"
+ android:layout_marginRight="6sp"
+ android:layout_marginBottom="6sp"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/label_keyserver_settings_hint"/>
+
+ <View
+ style="@style/Divider"/>
+
+ <FrameLayout
+ android:id="@+id/keyserver_settings_fragment_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/settings_keyserver_fragment.xml b/OpenKeychain/src/main/res/layout/settings_keyserver_fragment.xml
new file mode 100644
index 000000000..57c81a433
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/settings_keyserver_fragment.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<android.support.v7.widget.RecyclerView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/keyserver_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" /> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/layout/settings_keyserver_item.xml b/OpenKeychain/src/main/res/layout/settings_keyserver_item.xml
new file mode 100644
index 000000000..338104bda
--- /dev/null
+++ b/OpenKeychain/src/main/res/layout/settings_keyserver_item.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/outer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?listPreferredItemHeight">
+
+ <LinearLayout
+ android:id="@+id/keyserver_layout"
+ android:padding="6sp"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_toLeftOf="@+id/drag_handle"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/selected_keyserver_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/label_selected_keyserver_title"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textStyle="bold"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/keyserver_tv"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium" />
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/drag_handle"
+ android:layout_width="?listPreferredItemHeight"
+ android:layout_height="?listPreferredItemHeight"
+ android:scaleType="center"
+ android:layout_centerVertical="true"
+ android:layout_alignParentRight="true"
+ android:src="@drawable/ic_reorder_grey_500_24dp" />
+
+ <View
+ android:layout_alignParentBottom="true"
+ style="@style/Divider"/>
+
+</RelativeLayout> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/menu/keyserver_pref_menu.xml b/OpenKeychain/src/main/res/menu/keyserver_pref_menu.xml
new file mode 100644
index 000000000..c4002a682
--- /dev/null
+++ b/OpenKeychain/src/main/res/menu/keyserver_pref_menu.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item
+ android:id="@+id/menu_add_keyserver"
+ android:title="@string/menu_search"
+ android:icon="@drawable/ic_add_white_24dp"
+ app:showAsAction="always" />
+</menu> \ No newline at end of file
diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml
index a59b63800..619c67ee3 100644
--- a/OpenKeychain/src/main/res/values/strings.xml
+++ b/OpenKeychain/src/main/res/values/strings.xml
@@ -109,6 +109,8 @@
<string name="menu_certify_fingerprint">"Confirm via fingerprint comparison"</string>
<string name="menu_export_log">"Export Log"</string>
+ <string name="menu_keyserver_add">"Add"</string>
+
<!-- label -->
<string name="label_message">"Text"</string>
<string name="label_file">"File"</string>
@@ -153,13 +155,17 @@
<string name="label_send_key">"Synchronize with the cloud"</string>
<string name="label_fingerprint">"Fingerprint"</string>
<string name="expiry_date_dialog_title">"Set expiry date"</string>
- <string name="label_first_keyserver_is_used">"(First keyserver listed is preferred)"</string>
+ <string name="label_keyservers_title">"Keyservers"</string>
+ <string name="label_keyserver_settings_hint">"Drag to change order, tap to edit/delete"</string>
+ <string name="label_selected_keyserver_title">"Selected keyserver"</string>
<string name="label_preferred">"preferred"</string>
<string name="label_enable_compression">"Enable compression"</string>
<string name="label_encrypt_filenames">"Encrypt filenames"</string>
<string name="label_hidden_recipients">"Hide recipients"</string>
+
<string name="label_verify_keyserver">"Verify keyserver"</string>
<string name="label_enter_keyserver_url">"Enter keyserver URL"</string>
+ <string name="label_keyserver_dialog_delete">"Delete keyserver"</string>
<string name="pref_keyserver">"OpenPGP keyservers"</string>
<string name="pref_keyserver_summary">"Search keys on selected OpenPGP keyservers (HKP protocol)"</string>
@@ -672,12 +678,14 @@
<string name="view_key_fragment_no_system_contact">"&lt;none&gt;"</string>
- <!-- Add keyserver -->
+ <!-- Add/Edit keyserver -->
<string name="add_keyserver_dialog_title">"Add keyserver"</string>
+ <string name="edit_keyserver_dialog_title">"Edit keyserver"</string>
<string name="add_keyserver_verified">"Keyserver verified!"</string>
<string name="add_keyserver_without_verification">"Keyserver added without verification."</string>
<string name="add_keyserver_invalid_url">"Invalid URL!"</string>
<string name="add_keyserver_connection_failed">"Failed to connect to keyserver. Please check the URL and your internet connection."</string>
+ <string name="keyserver_deleted">"%s deleted"</string>
<!-- Navigation Drawer -->
<string name="nav_keys">"Keys"</string>
diff --git a/OpenKeychain/src/main/res/values/styles.xml b/OpenKeychain/src/main/res/values/styles.xml
index 9ac60c397..c7b326b7a 100644
--- a/OpenKeychain/src/main/res/values/styles.xml
+++ b/OpenKeychain/src/main/res/values/styles.xml
@@ -26,4 +26,12 @@
<item name="android:textColor">@color/white</item>
</style>
+ <!-- This style is for use with our drag and drop RecyclerView since ItemDecoration did not
+ move with the drag -->
+ <style name="Divider">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">2dp</item>
+ <item name="android:background">?android:attr/listDivider</item>
+ </style>
+
</resources> \ No newline at end of file