package com.photo.photogallery.other;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.constraint.ConstraintLayout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
import com.jakewharton.rxbinding.widget.RxTextView;
import com.jakewharton.rxbinding.widget.TextViewTextChangeEvent;
import com.photo.photogallery.R;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import rx.Observer;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Func1;
import timber.log.Timber;
public class CustomSearchView extends FrameLayout {
private boolean mIsSearchOpen = false;
private int mAnimationDuration;
private boolean mClearingFocus;
private long searchDebounce;
//Views
private View mSearchLayout;
private View mTintView;
private EditText mSearchSrcTextView;
private ImageButton mBackBtn;
private ImageButton mEmptyBtn;
private ConstraintLayout mSearchTopBar;
private CharSequence mOldQueryText;
private CharSequence mUserQuery;
private OnQueryTextListener mOnQueryChangeListener;
private SearchViewListener mSearchViewListener;
private SavedState mSavedState;
private boolean submit = false;
private boolean ellipsize = false;
private Context mContext;
private Subscription subscription;
public CustomSearchView(Context context) {
this(context, null);
}
public CustomSearchView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomSearchView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs);
mContext = context;
initiateView();
initStyle(attrs, defStyleAttr);
}
private void initStyle(AttributeSet attrs, int defStyleAttr) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.CustomSearchView, defStyleAttr, 0);
if (a != null) {
if (a.hasValue(R.styleable.CustomSearchView_searchBackground)) {
setBackground(a.getDrawable(R.styleable.CustomSearchView_searchBackground));
}
if (a.hasValue(R.styleable.CustomSearchView_android_textColor)) {
setTextColor(a.getColor(R.styleable.CustomSearchView_android_textColor, 0));
}
if (a.hasValue(R.styleable.CustomSearchView_android_textColorHint)) {
setHintTextColor(a.getColor(R.styleable.CustomSearchView_android_textColorHint, 0));
}
if (a.hasValue(R.styleable.CustomSearchView_android_hint)) {
setHint(a.getString(R.styleable.CustomSearchView_android_hint));
}
if (a.hasValue(R.styleable.CustomSearchView_searchCloseIcon)) {
setCloseIcon(a.getDrawable(R.styleable.CustomSearchView_searchCloseIcon));
}
if (a.hasValue(R.styleable.CustomSearchView_searchBackIcon)) {
setBackIcon(a.getDrawable(R.styleable.CustomSearchView_searchBackIcon));
}
a.recycle();
}
}
private void initiateView() {
LayoutInflater.from(mContext).inflate(R.layout.search_view, this, true);
mSearchLayout = findViewById(R.id.search_layout);
mSearchTopBar = (ConstraintLayout) mSearchLayout.findViewById(R.id.search_top_bar);
mSearchSrcTextView = (EditText) mSearchLayout.findViewById(R.id.searchTextView);
mBackBtn = (ImageButton) mSearchLayout.findViewById(R.id.action_up_btn);
mEmptyBtn = (ImageButton) mSearchLayout.findViewById(R.id.action_empty_btn);
mTintView = mSearchLayout.findViewById(R.id.transparent_view);
mBackBtn.setOnClickListener(mOnClickListener);
mEmptyBtn.setOnClickListener(mOnClickListener);
mTintView.setOnClickListener(mOnClickListener);
initSearchView();
setAnimationDuration(AnimationUtil.ANIMATION_DURATION_MEDIUM);
}
private void initSearchView() {
mSearchSrcTextView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
onSubmitQuery();
return true;
}
});
subscription = RxTextView.textChangeEvents(mSearchSrcTextView)//
.debounce(750, TimeUnit.MILLISECONDS)// default Scheduler is Computation
.filter(new Func1<TextViewTextChangeEvent, Boolean>() {
@Override
public Boolean call(TextViewTextChangeEvent changes) {
String str = mSearchSrcTextView.getText().toString();
return !(str == null || str.length() == 0);
}
})
.observeOn(AndroidSchedulers.mainThread())//
.subscribe(getSearchObserver());
mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
showKeyboard(mSearchSrcTextView);
}
}
});
}
private Observer<TextViewTextChangeEvent> getSearchObserver() {
return new Observer<TextViewTextChangeEvent>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(TextViewTextChangeEvent onTextChangeEvent) {
String query = onTextChangeEvent.text().toString();
mUserQuery = query;
CustomSearchView.this.onTextChanged(query);
}
};
}
private final OnClickListener mOnClickListener = new OnClickListener() {
public void onClick(View v) {
if (v == mBackBtn) {
//closeSearch();
if (mSearchViewListener != null) {
mSearchViewListener.onBackButtonPressed();
}
} else if (v == mEmptyBtn) {
mSearchSrcTextView.setText(null);
} else if (v == mTintView) {
closeSearch();
}
}
};
private void onTextChanged(CharSequence newText) {
CharSequence text = mSearchSrcTextView.getText();
mUserQuery = text;
boolean hasText = !TextUtils.isEmpty(text);
if (hasText) {
mEmptyBtn.setVisibility(VISIBLE);
} else {
mEmptyBtn.setVisibility(GONE);
}
if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
mOnQueryChangeListener.onQueryTextChange(newText.toString());
}
mOldQueryText = newText.toString();
}
private void onSubmitQuery() {
CharSequence query = mSearchSrcTextView.getText();
if (query != null && TextUtils.getTrimmedLength(query) > 0) {
if (mOnQueryChangeListener == null || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
closeSearch();
mSearchSrcTextView.setText(null);
}
}
}
public void hideKeyboard(Activity activity) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}
public void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
public void showKeyboard(Activity activity) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
public void showKeyboard(View view) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1 && view.hasFocus()) {
view.clearFocus();
}
view.requestFocus();
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(view, 0);
}
//Public Attributes
public void setSearchDebounce(long timeout) {
searchDebounce = timeout;
}
public Subscription getSubscription() {
return subscription;
}
@Override
public void setBackground(Drawable background) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mSearchTopBar.setBackground(background);
} else {
mSearchTopBar.setBackgroundDrawable(background);
}
}
@Override
public void setBackgroundColor(int color) {
mSearchTopBar.setBackgroundColor(color);
}
public void setTextColor(int color) {
mSearchSrcTextView.setTextColor(color);
}
public void setHintTextColor(int color) {
mSearchSrcTextView.setHintTextColor(color);
}
public void setHint(CharSequence hint) {
mSearchSrcTextView.setHint(hint);
}
public void setCloseIcon(Drawable drawable) {
mEmptyBtn.setImageDrawable(drawable);
}
public void setBackIcon(Drawable drawable) {
mBackBtn.setImageDrawable(drawable);
}
public void setCursorDrawable(int drawable) {
try {
// https://github.com/android/platform_frameworks_base/blob/kitkat-release/core/java/android/widget/TextView.java#L562-564
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
f.set(mSearchSrcTextView, drawable);
} catch (Exception ignored) {
Timber.e("set cursor drawable error: %s", ignored.toString());
}
}
//Public Methods
/**
* Submit the query as soon as the user clicks the item.
*
* @param submit submit state
*/
public void setSubmitOnClick(boolean submit) {
this.submit = submit;
}
/**
* Calling this will set the query to search text box. if submit is true, it'll submit the query.
*
* @param query
* @param submit
*/
public void setQuery(CharSequence query, boolean submit) {
mSearchSrcTextView.setText(query);
if (query != null) {
mSearchSrcTextView.setSelection(mSearchSrcTextView.length());
mUserQuery = query;
}
if (submit && !TextUtils.isEmpty(query)) {
onSubmitQuery();
}
}
/**
* Return true if search is open
*
* @return
*/
public boolean isSearchOpen() {
return mIsSearchOpen;
}
/**
* Sets animation duration. ONLY FOR PRE-LOLLIPOP!!
*
* @param duration duration of the animation
*/
public void setAnimationDuration(int duration) {
mAnimationDuration = duration;
}
/**
* Open Search View. This will animate the showing of the view.
*/
public void showSearch() {
showSearch(true);
}
/**
* Open Search View. If animate is true, Animate the showing of the view.
*
* @param animate true for animate
*/
public void showSearch(boolean animate) {
if (isSearchOpen()) {
return;
}
//Request Focus
mSearchSrcTextView.setText(null);
mSearchSrcTextView.requestFocus();
if (animate) {
setVisibleWithAnimation();
} else {
mSearchLayout.setVisibility(VISIBLE);
if (mSearchViewListener != null) {
mSearchViewListener.onSearchViewShown();
}
}
mIsSearchOpen = true;
}
private void setVisibleWithAnimation() {
AnimationUtil.AnimationListener animationListener = new AnimationUtil.AnimationListener() {
@Override
public boolean onAnimationStart(View view) {
return false;
}
@Override
public boolean onAnimationEnd(View view) {
if (mSearchViewListener != null) {
mSearchViewListener.onSearchViewShown();
}
return false;
}
@Override
public boolean onAnimationCancel(View view) {
return false;
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mSearchLayout.setVisibility(View.VISIBLE);
AnimationUtil.reveal(mSearchTopBar, animationListener);
} else {
AnimationUtil.fadeInView(mSearchLayout, mAnimationDuration, animationListener);
}
}
/**
* Close search view.
*/
public void closeSearch() {
if (!isSearchOpen()) {
return;
}
mSearchSrcTextView.setText(null);
clearFocus();
//mSearchLayout.setVisibility(GONE);
if (mSearchViewListener != null) {
mSearchViewListener.onSearchViewClosed();
}
mIsSearchOpen = false;
}
/**
* Set this listener to listen to Query Change events.
*
* @param listener
*/
public void setOnQueryTextListener(OnQueryTextListener listener) {
mOnQueryChangeListener = listener;
}
/**
* Set this listener to listen to Search View open and close events
*
* @param listener
*/
public void setOnSearchViewListener(SearchViewListener listener) {
mSearchViewListener = listener;
}
/**
* Ellipsize suggestions longer than one line.
*
* @param ellipsize
*/
public void setEllipsize(boolean ellipsize) {
this.ellipsize = ellipsize;
}
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
// Don't accept focus if in the middle of clearing focus
if (mClearingFocus) return false;
// Check if SearchView is focusable.
if (!isFocusable()) return false;
return mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect);
}
@Override
public void clearFocus() {
mClearingFocus = true;
hideKeyboard(this);
super.clearFocus();
mSearchSrcTextView.clearFocus();
mClearingFocus = false;
}
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
mSavedState = new SavedState(superState);
mSavedState.query = mUserQuery != null ? mUserQuery.toString() : null;
mSavedState.isSearchOpen = this.mIsSearchOpen;
return mSavedState;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
super.onRestoreInstanceState(state);
return;
}
mSavedState = (SavedState) state;
if (mSavedState.isSearchOpen) {
showSearch(false);
setQuery(mSavedState.query, false);
}
super.onRestoreInstanceState(mSavedState.getSuperState());
}
static class SavedState extends BaseSavedState {
String query;
boolean isSearchOpen;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
this.query = in.readString();
this.isSearchOpen = in.readInt() == 1;
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(query);
out.writeInt(isSearchOpen ? 1 : 0);
}
//required field that makes Parcelables from a Parcel
public static final Creator<SavedState> CREATOR =
new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
public interface OnQueryTextListener {
/**
* Called when the user submits the query. This could be due to a key press on the
* keyboard or due to pressing a submit button.
* The listener can override the standard behavior by returning true
* to indicate that it has handled the submit request. Otherwise return false to
* let the SearchView handle the submission by launching any associated intent.
*
* @param query the query text that is to be submitted
* @return true if the query has been handled by the listener, false to let the
* SearchView perform the default action.
*/
boolean onQueryTextSubmit(String query);
/**
* Called when the query text is changed by the user.
*
* @param newText the new content of the query text field.
* @return false if the SearchView should perform the default action of showing any
* suggestions if available, true if the action was handled by the listener.
*/
boolean onQueryTextChange(String newText);
}
public interface SearchViewListener {
void onSearchViewShown();
void onSearchViewClosed();
void onBackButtonPressed();
}
}