package io.github.droidkaigi.confsched.activity;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.LinearLayoutManager;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.style.TextAppearanceSpan;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import org.parceler.Parcels;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import io.github.droidkaigi.confsched.R;
import io.github.droidkaigi.confsched.dao.CategoryDao;
import io.github.droidkaigi.confsched.dao.PlaceDao;
import io.github.droidkaigi.confsched.dao.SessionDao;
import io.github.droidkaigi.confsched.databinding.ActivitySearchBinding;
import io.github.droidkaigi.confsched.databinding.ItemSearchResultBinding;
import io.github.droidkaigi.confsched.model.SearchResult;
import io.github.droidkaigi.confsched.model.Session;
import io.github.droidkaigi.confsched.util.AnalyticsTracker;
import io.github.droidkaigi.confsched.util.LocaleUtil;
import io.github.droidkaigi.confsched.widget.ArrayRecyclerAdapter;
import io.github.droidkaigi.confsched.widget.BindingHolder;
import io.github.droidkaigi.confsched.widget.itemdecoration.DividerItemDecoration;
import rx.Observable;
public class SearchActivity extends BaseActivity implements TextWatcher,
TextView.OnEditorActionListener {
public static final String RESULT_STATUS_CHANGED_SESSIONS = "statusChangedSessions";
private static final int REQ_DETAIL = 1;
private static final int REQ_SEARCH_PLACES_AND_CATEGORIES_VIEW = 2;
@Inject
AnalyticsTracker analyticsTracker;
@Inject
ActivityNavigator activityNavigator;
@Inject
SessionDao sessionDao;
@Inject
PlaceDao placeDao;
@Inject
CategoryDao categoryDao;
List<Session> statusChangedSessions = new ArrayList<>();
private SearchResultsAdapter adapter;
private ActivitySearchBinding binding;
static void start(@NonNull Fragment fragment, int requestCode) {
Intent intent = new Intent(fragment.getContext(), SearchActivity.class);
fragment.startActivityForResult(intent, requestCode);
fragment.getActivity().overridePendingTransition(0, R.anim.activity_fade_exit);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_search);
getComponent().inject(this);
initToolbar();
initRecyclerView();
initPlacesAndCategoriesView();
loadData();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(RESULT_STATUS_CHANGED_SESSIONS, Parcels.wrap(statusChangedSessions));
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
statusChangedSessions = Parcels.unwrap(savedInstanceState.getParcelable(RESULT_STATUS_CHANGED_SESSIONS));
}
private void initPlacesAndCategoriesView() {
binding.searchPlacesAndCategoriesView.addPlaces(placeDao.findAll());
binding.searchPlacesAndCategoriesView.addCategories(categoryDao.findAll());
binding.searchPlacesAndCategoriesView.setOnClickSearchGroup(searchGroup ->
startActivityForResult(SearchedSessionsActivity.createIntent(SearchActivity.this, searchGroup),
REQ_SEARCH_PLACES_AND_CATEGORIES_VIEW));
}
@Override
protected void onStart() {
super.onStart();
analyticsTracker.sendScreenView("search");
}
private void initToolbar() {
setSupportActionBar(binding.searchToolbar.getToolbar());
ActionBar bar = getSupportActionBar();
if (bar != null) {
bar.setDisplayHomeAsUpEnabled(true);
bar.setDisplayShowHomeEnabled(true);
bar.setDisplayShowTitleEnabled(false);
bar.setHomeButtonEnabled(true);
}
binding.searchToolbar.addTextChangedListener(this);
binding.searchToolbar.setOnEditorActionListener(this);
}
private void initRecyclerView() {
adapter = new SearchResultsAdapter(this);
binding.recyclerView.setAdapter(adapter);
binding.recyclerView.addItemDecoration(new DividerItemDecoration(this));
binding.recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
private void loadData() {
// TODO It's waste logic...
List<Session> sessions = sessionDao.findAll().toBlocking().single();
List<SearchResult> titleResults = Observable.from(sessions)
.map(SearchResult::createTitleType)
.toList().toBlocking().single();
List<SearchResult> descriptionResults = Observable.from(sessions)
.map(SearchResult::createDescriptionType)
.toList().toBlocking().single();
List<SearchResult> speakerResults = Observable.from(sessions)
.map(SearchResult::createSpeakerType)
.toList().toBlocking().single();
titleResults.addAll(descriptionResults);
titleResults.addAll(speakerResults);
adapter.setAllList(titleResults);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
}
return super.onOptionsItemSelected(item);
}
@Override
public void finish() {
Intent intent = new Intent();
intent.putExtra(RESULT_STATUS_CHANGED_SESSIONS, Parcels.wrap(statusChangedSessions));
setResult(Activity.RESULT_OK, intent);
overridePendingTransition(0, R.anim.activity_fade_exit);
super.finish();
}
@Override
public void onBackPressed() {
finish();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.getFilter().filter(s);
}
@Override
public void afterTextChanged(Editable s) {
if (TextUtils.isEmpty(s)) {
binding.searchPlacesAndCategoriesView.setVisibility(View.VISIBLE);
binding.recyclerView.setVisibility(View.GONE);
} else {
binding.searchPlacesAndCategoriesView.setVisibility(View.GONE);
binding.recyclerView.setVisibility(View.VISIBLE);
}
}
@Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQ_DETAIL: {
if (resultCode != Activity.RESULT_OK) {
return;
}
Session session = Parcels.unwrap(data.getParcelableExtra(Session.class.getSimpleName()));
statusChangedSessions.add(session);
break;
}
case REQ_SEARCH_PLACES_AND_CATEGORIES_VIEW: {
if (resultCode != Activity.RESULT_OK) {
return;
}
List<Session> sessions = Parcels.unwrap(data.getParcelableExtra(RESULT_STATUS_CHANGED_SESSIONS));
statusChangedSessions.addAll(sessions);
break;
}
}
}
private class SearchResultsAdapter
extends ArrayRecyclerAdapter<SearchResult, BindingHolder<ItemSearchResultBinding>>
implements Filterable {
private static final String ELLIPSIZE_TEXT = "...";
private static final int ELLIPSIZE_LIMIT_COUNT = 30;
private TextAppearanceSpan textAppearanceSpan;
private List<SearchResult> filteredList;
private List<SearchResult> allList;
public SearchResultsAdapter(@NonNull Context context) {
super(context);
this.filteredList = new ArrayList<>();
this.textAppearanceSpan = new TextAppearanceSpan(context, R.style.SearchResultAppearance);
}
public void setAllList(List<SearchResult> searchResults) {
this.allList = searchResults;
}
@Override
public BindingHolder<ItemSearchResultBinding> onCreateViewHolder(ViewGroup parent, int viewType) {
return new BindingHolder<>(getContext(), parent, R.layout.item_search_result);
}
@Override
public void onBindViewHolder(BindingHolder<ItemSearchResultBinding> holder, int position) {
SearchResult searchResult = getItem(position);
ItemSearchResultBinding itemBinding = holder.binding;
itemBinding.setSearchResult(searchResult);
Drawable icon = ContextCompat.getDrawable(getContext(), searchResult.iconRes);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
itemBinding.txtType.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null);
} else if (LocaleUtil.shouldRtl()) {
itemBinding.txtType.setCompoundDrawablesWithIntrinsicBounds(null, null, icon, null);
} else {
itemBinding.txtType.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
}
bindText(itemBinding.txtSearchResult, searchResult, binding.searchToolbar.getText());
itemBinding.getRoot().setOnClickListener(v ->
activityNavigator.showSessionDetail(SearchActivity.this, searchResult.session, REQ_DETAIL));
}
private void bindText(TextView textView, SearchResult searchResult, String searchText) {
String text = searchResult.text;
if (TextUtils.isEmpty(text)) return;
text = text.replace("\n", " ");
if (TextUtils.isEmpty(searchText)) {
textView.setText(text);
} else {
int idx = text.toLowerCase().indexOf(searchText.toLowerCase());
if (idx >= 0) {
SpannableStringBuilder builder = new SpannableStringBuilder();
builder.append(text);
builder.setSpan(
textAppearanceSpan,
idx,
idx + searchText.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
if (idx > ELLIPSIZE_LIMIT_COUNT && searchResult.isDescriptionType()) {
builder.delete(0, idx - ELLIPSIZE_LIMIT_COUNT);
builder.insert(0, ELLIPSIZE_TEXT);
}
textView.setText(builder);
} else {
textView.setText(text);
}
}
}
@Override
public Filter getFilter() {
return new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
filteredList.clear();
FilterResults results = new FilterResults();
if (constraint.length() > 0) {
final String filterPattern = constraint.toString().toLowerCase().trim();
Observable.from(allList)
.filter(searchResult -> searchResult.text.toLowerCase().contains(filterPattern))
.forEach(filteredList::add);
}
results.values = filteredList;
results.count = filteredList.size();
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
clear();
addAll((List<SearchResult>) results.values);
notifyDataSetChanged();
}
};
}
}
}