reworked keyserver preferences with recyclerview

This commit is contained in:
Adithya Abraham Philip
2015-07-02 01:57:15 +05:30
parent 64a3cc5a9f
commit af3868d547
25 changed files with 924 additions and 314 deletions

View File

@@ -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',
]
}

View File

@@ -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;
@@ -123,27 +124,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);
@@ -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;
}

View File

@@ -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
}
SettingsKeyserverFragment fragment = SettingsKeyserverFragment.newInstance(keyservers);
@Override
public void onEdited() {
}
// 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();
}
}

View File

@@ -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();
}
}
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 B

View File

@@ -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">
<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"/>
<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">
<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"/>
<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">
<View
style="@style/Divider"/>
<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" />
<FrameLayout
android:id="@+id/keyserver_settings_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
<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>
</LinearLayout>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>