blob: 083505c2b490580fc18f500bf2bfcf6dd62b03f2 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package examples.baku.io.permissions;
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.support.design.widget.TextInputLayout;
import android.text.Editable;
import android.text.InputType;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.method.KeyListener;
import android.text.style.BackgroundColorSpan;
import android.text.style.StrikethroughSpan;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.firebase.database.DatabaseError;
import com.joanzapata.iconify.IconDrawable;
import com.joanzapata.iconify.fonts.MaterialIcons;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import examples.baku.io.permissions.synchronization.SyncText;
import examples.baku.io.permissions.synchronization.SyncTextDiff;
/**
* Created by phamilton on 7/26/16.
*/
public class PermissionedTextLayout extends FrameLayout implements PermissionManager.OnPermissionChangeListener, PermissionManager.OnRequestListener {
private static final String ANDROID_NS = "http://schemas.android.com/apk/res/android";
private int permissions = -1;
final Set<PermissionRequest> requests = new HashSet<>();
private String label;
private SyncText syncText;
private TextInputLayout textInputLayout;
private PermissionedEditText editText;
private FrameLayout overlay;
private final SortedMap<Integer, ActionItem> mActions = new TreeMap<>();
private ActionItem mPrimaryAction;
private ImageView actionButton;
private PermissionedTextListener permissionedTextListener = null;
private int inputType = InputType.TYPE_CLASS_TEXT;
private boolean editable = true;
private int version = -1;
public void unlink() {
if (syncText != null) {
syncText.unlink();
}
}
public interface PermissionedTextListener {
void onSelected(SyncTextDiff diff, PermissionedTextLayout text);
void onAction(int action, PermissionedTextLayout text);
}
public PermissionedTextLayout(Context context) {
this(context, null, 0);
}
public PermissionedTextLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PermissionedTextLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
public void setAutoCompleteAdapter(ArrayAdapter<String> adapter) {
editText.setAdapter(adapter);
}
public void init(final Context context, AttributeSet attrs, int defStyleAttr) {
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
textInputLayout = new TextInputLayout(context);
textInputLayout.setLayoutParams(params);
String hint = attrs.getAttributeValue(ANDROID_NS, "hint");
if (hint != null) {
textInputLayout.setHint(hint);
}
editText = new PermissionedEditText(context);
editText.setSelectionListener(selectionListener);
editText.setId(View.generateViewId());
editText.setLayoutParams(params);
int inputType = attrs.getAttributeIntValue(ANDROID_NS, "inputType", EditorInfo.TYPE_NULL);
if (inputType != EditorInfo.TYPE_NULL) {
editText.setInputType(inputType);
}
textInputLayout.addView(editText, params);
addView(textInputLayout);
overlay = new FrameLayout(context);
overlay.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
// overlay.setBackgroundColor(Color.BLACK);
overlay.setBackgroundResource(R.drawable.permission_overlay_bg);
overlay.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
Toast.makeText(getContext(), "This device does not have permission to view this field", 0).show();
}
return true;
}
});
overlay.setVisibility(GONE);
TextView overlayLabel = new TextView(context);
overlayLabel.setTextSize(24);
overlayLabel.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
overlayLabel.setText(hint + " Unavailable");
overlay.addView(overlayLabel);
addView(overlay);
actionButton = new ImageView(context);
IconDrawable drawable = new IconDrawable(context, MaterialIcons.md_check);
drawable.color(Color.GREEN);
actionButton.setImageDrawable(drawable);
actionButton.setLayoutParams(new FrameLayout.LayoutParams(100, 100, Gravity.RIGHT));
actionButton.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
ImageView view = (ImageView) v;
view.getDrawable().setColorFilter(0x77000000, PorterDuff.Mode.SRC_ATOP);
view.invalidate();
break;
}
case MotionEvent.ACTION_UP:
if (permissionedTextListener != null) {
int aId = 0;
if (mPrimaryAction != null) {
aId = mPrimaryAction.id;
}
permissionedTextListener.onAction(aId, PermissionedTextLayout.this);
}
case MotionEvent.ACTION_CANCEL: {
ImageView view = (ImageView) v;
view.getDrawable().clearColorFilter();
view.invalidate();
break;
}
}
return true;
}
});
actionButton.setVisibility(GONE);
addView(actionButton);
}
public void setInputType(int inputType) {
this.inputType = inputType;
}
Spannable diffSpannable(LinkedList<SyncTextDiff> diffs) {
SpannableStringBuilder result = new SpannableStringBuilder();
int start;
String text;
int color = Color.YELLOW;
for (SyncTextDiff diff : diffs) {
start = result.length();
text = diff.text;
switch (diff.operation) {
case SyncTextDiff.DELETE:
result.append(text, new BackgroundColorSpan(color), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
result.setSpan(new StrikethroughSpan(), start, start + text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case SyncTextDiff.INSERT:
result.append(text, new BackgroundColorSpan(color), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
break;
case SyncTextDiff.EQUAL:
result.append(text);
break;
}
}
return result;
}
public void setPermissionedTextListener(PermissionedTextListener permissionedTextListener) {
this.permissionedTextListener = permissionedTextListener;
}
public void setSyncText(SyncText sync) {
this.syncText = sync;
syncText.setOnTextChangeListener(new SyncText.OnTextChangeListener() {
@Override
public void onTextChange(final String currentText, final LinkedList<SyncTextDiff> diffs, int ver) {
if (ver >= version) {
updateText(diffs);
}
}
});
editText.addTextChangedListener(watcher);
}
@Override
public void onPermissionChange(int current) {
if (current != permissions) {
this.permissions = current;
update();
}
}
private void update() {
if (syncText != null) {
this.syncText.setPermissions(permissions);
if ((permissions & PermissionManager.FLAG_WRITE) == PermissionManager.FLAG_WRITE) {
editText.setVisibility(VISIBLE);
overlay.setVisibility(GONE);
editText.setInputType(inputType);
editText.setEnabled(true);
syncText.acceptSuggestions();
} else if ((permissions & PermissionManager.FLAG_SUGGEST) == PermissionManager.FLAG_SUGGEST) {
editText.setVisibility(VISIBLE);
overlay.setVisibility(GONE);
editText.setInputType(inputType);
editText.setEnabled(true);
} else if ((permissions & PermissionManager.FLAG_READ) == PermissionManager.FLAG_READ) {
editText.setVisibility(VISIBLE);
overlay.setVisibility(GONE);
syncText.rejectSuggestions();
editText.setInputType(EditorInfo.TYPE_NULL);
editText.setEnabled(false);
} else {
editText.setVisibility(GONE);
overlay.setVisibility(VISIBLE);
syncText.rejectSuggestions();
editText.setInputType(EditorInfo.TYPE_NULL);
editText.setEnabled(false);
}
}
if (!editable) {
editText.disable();
} else {
editText.enable();
}
}
public void setEditable(boolean editable) {
// this.editable = editable;
update();
}
public String getText() {
return editText.getText().toString();
}
private synchronized void updateText(final LinkedList<SyncTextDiff> diffs) {
Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
editText.removeTextChangedListener(watcher);
int prevSel = editText.getSelectionStart();
editText.setText(diffSpannable(diffs));
int sel = Math.min(prevSel, editText.length());
if (sel > -1) {
editText.setSelection(sel);
}
editText.addTextChangedListener(watcher);
}
});
}
}
private TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
version = Math.max(version, syncText.update(s.toString()));
}
@Override
public void afterTextChanged(Editable s) {
}
};
public void clearActions() {
mActions.clear();
mPrimaryAction = null;
updateActions();
}
public void removeAction(int id) {
mActions.remove(id);
updateActions();
}
public void setAction(int id, Drawable icon, String label) {
mActions.put(id, new ActionItem(id, icon, label));
mPrimaryAction = mActions.values().iterator().next();
updateActions();
}
public void updateActions() {
if (mActions.size() == 1) {
ActionItem action = mPrimaryAction;
actionButton.setVisibility(VISIBLE);
actionButton.setImageDrawable(action.icon);
} else {
actionButton.setVisibility(GONE);
}
}
public void acceptSuggestions(String src) {
syncText.acceptSuggestions(src);
}
public void rejectSuggestions(String src) {
syncText.rejectSuggestions(src);
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
@Override
public boolean onRequest(PermissionRequest request, Blessing blessing) {
return false;
}
@Override
public void onRequestRemoved(PermissionRequest request, Blessing blessing) {
}
private Activity getActivity() {
Context context = getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
return (Activity) context;
}
context = ((ContextWrapper) context).getBaseContext();
}
return null;
}
private OnSelectionChangedListener selectionListener = new OnSelectionChangedListener() {
@Override
public void onSelectionChanged(int selStart, int selEnd, boolean focus) {
if (focus && selStart >= 0) {
SyncTextDiff diff = getDiffAt(selStart);
if (diff != null && diff.operation != SyncTextDiff.EQUAL) {
if (permissionedTextListener != null) {
permissionedTextListener.onSelected(diff, PermissionedTextLayout.this);
}
}
}
}
};
public List<SyncTextDiff> getSuggestions() {
List<SyncTextDiff> result = new LinkedList<>();
for (SyncTextDiff diff : syncText.getDiffs()) {
if (diff.operation != SyncTextDiff.EQUAL && diff.permission == PermissionManager.FLAG_SUGGEST) {
result.add(diff);
}
}
return result;
}
private SyncTextDiff getDiffAt(int index) {
int count = 0;
if (syncText != null) {
for (SyncTextDiff diff : syncText.getDiffs()) {
count += diff.length();
if (index < count) {
return diff;
}
}
}
return null;
}
class ActionItem {
int id;
Drawable icon;
String label;
public ActionItem(int id, Drawable icon, String label) {
this.id = id;
this.icon = icon;
this.label = label;
}
}
private interface OnSelectionChangedListener {
void onSelectionChanged(int selStart, int selEnd, boolean focus);
}
private class PermissionedEditText extends AutoCompleteTextView {
private OnSelectionChangedListener mSelectionListener;
private KeyListener keyListener;
public PermissionedEditText(Context context) {
super(context);
init();
}
public PermissionedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PermissionedEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
keyListener = getKeyListener();
}
public void setSelectionListener(OnSelectionChangedListener mSelectionListener) {
this.mSelectionListener = mSelectionListener;
}
public void enable() {
setKeyListener(keyListener);
}
public void disable() {
setKeyListener(null);
}
@Override
protected void onSelectionChanged(int selStart, int selEnd) {
super.onSelectionChanged(selStart, selEnd);
if (mSelectionListener != null) {
mSelectionListener.onSelectionChanged(selStart, selEnd, hasFocus());
}
}
}
}