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

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