Adds collections to search and disambiguates results.
This commit is contained in:
@@ -8,6 +8,7 @@ import com.majinnaibu.monstercards.models.Collection;
|
|||||||
import com.majinnaibu.monstercards.models.CollectionMonster;
|
import com.majinnaibu.monstercards.models.CollectionMonster;
|
||||||
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
import com.majinnaibu.monstercards.models.CollectionWithCount;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.SearchResultItem;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -50,6 +51,43 @@ public class MonsterRepository {
|
|||||||
.observeOn(AndroidSchedulers.mainThread());
|
.observeOn(AndroidSchedulers.mainThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Flowable<List<SearchResultItem>> searchAll(String searchText) {
|
||||||
|
Flowable<List<Monster>> monstersFlowable = m_db.monsterDAO()
|
||||||
|
.getAll()
|
||||||
|
.map(monsters -> {
|
||||||
|
ArrayList<Monster> filteredMonsters = new ArrayList<>();
|
||||||
|
for (Monster monster : monsters) {
|
||||||
|
if (Helpers.monsterMatchesSearch(monster, searchText)) {
|
||||||
|
filteredMonsters.add(monster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (List<Monster>) filteredMonsters;
|
||||||
|
});
|
||||||
|
|
||||||
|
Flowable<List<Collection>> collectionsFlowable = m_db.collectionDAO()
|
||||||
|
.getAll()
|
||||||
|
.map(collections -> {
|
||||||
|
ArrayList<Collection> filteredCollections = new ArrayList<>();
|
||||||
|
for (Collection collection : collections) {
|
||||||
|
if (StringHelper.isNullOrEmpty(searchText) || StringHelper.containsCaseInsensitive(collection.name, searchText)) {
|
||||||
|
filteredCollections.add(collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (List<Collection>) filteredCollections;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Flowable.combineLatest(monstersFlowable, collectionsFlowable, (monsters, collections) -> {
|
||||||
|
ArrayList<SearchResultItem> results = new ArrayList<>();
|
||||||
|
for (Monster monster : monsters) {
|
||||||
|
results.add(new SearchResultItem(monster));
|
||||||
|
}
|
||||||
|
for (Collection collection : collections) {
|
||||||
|
results.add(new SearchResultItem(collection));
|
||||||
|
}
|
||||||
|
return (List<SearchResultItem>) results;
|
||||||
|
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
|
||||||
|
}
|
||||||
|
|
||||||
public Flowable<Monster> getMonster(@NonNull UUID monsterId) {
|
public Flowable<Monster> getMonster(@NonNull UUID monsterId) {
|
||||||
return m_db.monsterDAO()
|
return m_db.monsterDAO()
|
||||||
.loadAllByIds(new String[]{monsterId.toString()})
|
.loadAllByIds(new String[]{monsterId.toString()})
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.majinnaibu.monstercards.models;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class SearchResultItem {
|
||||||
|
public enum Type {
|
||||||
|
MONSTER,
|
||||||
|
COLLECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final Type type;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Monster monster;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public final Collection collection;
|
||||||
|
|
||||||
|
public SearchResultItem(@NonNull Monster monster) {
|
||||||
|
this.type = Type.MONSTER;
|
||||||
|
this.monster = monster;
|
||||||
|
this.collection = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchResultItem(@NonNull Collection collection) {
|
||||||
|
this.type = Type.COLLECTION;
|
||||||
|
this.monster = null;
|
||||||
|
this.collection = collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,11 +9,14 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.navigation.NavDirections;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.majinnaibu.monstercards.R;
|
import com.majinnaibu.monstercards.R;
|
||||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
|
import com.majinnaibu.monstercards.models.SearchResultItem;
|
||||||
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
import com.majinnaibu.monstercards.ui.shared.MCFragment;
|
||||||
|
|
||||||
public class SearchFragment extends MCFragment {
|
public class SearchFragment extends MCFragment {
|
||||||
@@ -22,7 +25,17 @@ public class SearchFragment extends MCFragment {
|
|||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
View root = inflater.inflate(R.layout.fragment_search, container, false);
|
View root = inflater.inflate(R.layout.fragment_search, container, false);
|
||||||
MonsterRepository repository = this.getMonsterRepository();
|
MonsterRepository repository = this.getMonsterRepository();
|
||||||
SearchResultsRecyclerViewAdapter adapter = new SearchResultsRecyclerViewAdapter(repository, null);
|
SearchResultsRecyclerViewAdapter adapter = new SearchResultsRecyclerViewAdapter(repository, item -> {
|
||||||
|
if (item != null) {
|
||||||
|
if (item.type == SearchResultItem.Type.MONSTER && item.monster != null) {
|
||||||
|
NavDirections action = SearchFragmentDirections.actionNavigationSearchToNavigationMonster(item.monster.id.toString());
|
||||||
|
Navigation.findNavController(requireView()).navigate(action);
|
||||||
|
} else if (item.type == SearchResultItem.Type.COLLECTION && item.collection != null) {
|
||||||
|
NavDirections action = SearchFragmentDirections.actionNavigationSearchToCollectionDetailFragment(item.collection.id.toString());
|
||||||
|
Navigation.findNavController(requireView()).navigate(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
final RecyclerView recyclerView = root.findViewById(R.id.monster_list);
|
final RecyclerView recyclerView = root.findViewById(R.id.monster_list);
|
||||||
assert recyclerView != null;
|
assert recyclerView != null;
|
||||||
setupRecyclerView(recyclerView, adapter);
|
setupRecyclerView(recyclerView, adapter);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.majinnaibu.monstercards.ui.search;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -10,7 +11,10 @@ import androidx.recyclerview.widget.RecyclerView;
|
|||||||
|
|
||||||
import com.majinnaibu.monstercards.R;
|
import com.majinnaibu.monstercards.R;
|
||||||
import com.majinnaibu.monstercards.data.MonsterRepository;
|
import com.majinnaibu.monstercards.data.MonsterRepository;
|
||||||
|
import com.majinnaibu.monstercards.helpers.StringHelper;
|
||||||
|
import com.majinnaibu.monstercards.models.Collection;
|
||||||
import com.majinnaibu.monstercards.models.Monster;
|
import com.majinnaibu.monstercards.models.Monster;
|
||||||
|
import com.majinnaibu.monstercards.models.SearchResultItem;
|
||||||
import com.majinnaibu.monstercards.utils.Logger;
|
import com.majinnaibu.monstercards.utils.Logger;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -23,7 +27,7 @@ public class SearchResultsRecyclerViewAdapter extends RecyclerView.Adapter<Searc
|
|||||||
private final MonsterRepository mRepository;
|
private final MonsterRepository mRepository;
|
||||||
private final ItemCallback mOnClickHandler;
|
private final ItemCallback mOnClickHandler;
|
||||||
private String mSearchText;
|
private String mSearchText;
|
||||||
private List<Monster> mValues;
|
private List<SearchResultItem> mValues;
|
||||||
private Disposable mSubscriptionHandler;
|
private Disposable mSubscriptionHandler;
|
||||||
|
|
||||||
public SearchResultsRecyclerViewAdapter(MonsterRepository repository,
|
public SearchResultsRecyclerViewAdapter(MonsterRepository repository,
|
||||||
@@ -42,9 +46,9 @@ public class SearchResultsRecyclerViewAdapter extends RecyclerView.Adapter<Searc
|
|||||||
mSubscriptionHandler.dispose();
|
mSubscriptionHandler.dispose();
|
||||||
}
|
}
|
||||||
mSearchText = searchText;
|
mSearchText = searchText;
|
||||||
Flowable<List<Monster>> foundMonsters = mRepository.searchMonsters(mSearchText);
|
Flowable<List<SearchResultItem>> resultsFlowable = mRepository.searchAll(mSearchText);
|
||||||
mSubscriptionHandler = foundMonsters.subscribe(monsters -> {
|
mSubscriptionHandler = resultsFlowable.subscribe(results -> {
|
||||||
mValues = monsters;
|
mValues = results;
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
},
|
},
|
||||||
throwable -> Logger.logError("Error performing search", throwable));
|
throwable -> Logger.logError("Error performing search", throwable));
|
||||||
@@ -54,18 +58,52 @@ public class SearchResultsRecyclerViewAdapter extends RecyclerView.Adapter<Searc
|
|||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
.inflate(R.layout.monster_list_content, parent, false);
|
.inflate(R.layout.search_result_list_item, parent, false);
|
||||||
return new ViewHolder(view);
|
return new ViewHolder(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||||
Monster monster = mValues.get(position);
|
SearchResultItem item = mValues.get(position);
|
||||||
holder.mContentView.setText(monster.name);
|
if (item.type == SearchResultItem.Type.MONSTER && item.monster != null) {
|
||||||
holder.itemView.setTag(monster);
|
Monster monster = item.monster;
|
||||||
|
holder.mTitleView.setText(monster.name);
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (!StringHelper.isNullOrEmpty(monster.alignment)) {
|
||||||
|
sb.append(monster.alignment);
|
||||||
|
}
|
||||||
|
String cr = monster.getChallengeRatingDescription();
|
||||||
|
if (!StringHelper.isNullOrEmpty(cr)) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append("CR ").append(cr);
|
||||||
|
}
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
holder.mSubtitleView.setText(sb.toString());
|
||||||
|
holder.mSubtitleView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.mSubtitleView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
holder.mIconView.setVisibility(View.GONE);
|
||||||
|
} else if (item.type == SearchResultItem.Type.COLLECTION && item.collection != null) {
|
||||||
|
Collection collection = item.collection;
|
||||||
|
holder.mTitleView.setText(collection.name);
|
||||||
|
|
||||||
|
if (!StringHelper.isNullOrEmpty(collection.description)) {
|
||||||
|
holder.mSubtitleView.setText(collection.description);
|
||||||
|
holder.mSubtitleView.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.mSubtitleView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
holder.mIconView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setTag(item);
|
||||||
holder.itemView.setOnClickListener(view -> {
|
holder.itemView.setOnClickListener(view -> {
|
||||||
if (mOnClickHandler != null) {
|
if (mOnClickHandler != null) {
|
||||||
mOnClickHandler.onItem(monster);
|
mOnClickHandler.onItem(item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -76,15 +114,19 @@ public class SearchResultsRecyclerViewAdapter extends RecyclerView.Adapter<Searc
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface ItemCallback {
|
public interface ItemCallback {
|
||||||
void onItem(Monster monster);
|
void onItem(SearchResultItem item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
final TextView mContentView;
|
final TextView mTitleView;
|
||||||
|
final TextView mSubtitleView;
|
||||||
|
final ImageView mIconView;
|
||||||
|
|
||||||
ViewHolder(View view) {
|
ViewHolder(View view) {
|
||||||
super(view);
|
super(view);
|
||||||
mContentView = view.findViewById(R.id.content);
|
mTitleView = view.findViewById(R.id.title);
|
||||||
|
mSubtitleView = view.findViewById(R.id.subtitle);
|
||||||
|
mIconView = view.findViewById(R.id.icon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
49
Android/app/src/main/res/layout/search_result_list_item.xml
Normal file
49
Android/app/src/main/res/layout/search_result_list_item.xml
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?attr/textAppearanceListItem"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/subtitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:textAppearance="?attr/textAppearanceListItemSecondary"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:contentDescription="@string/title_collections"
|
||||||
|
android:src="@drawable/ic_collections_black_24dp"
|
||||||
|
app:tint="?android:attr/textColorSecondary"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
<action
|
<action
|
||||||
android:id="@+id/action_navigation_search_to_navigation_monster"
|
android:id="@+id/action_navigation_search_to_navigation_monster"
|
||||||
app:destination="@id/navigation_monster" />
|
app:destination="@id/navigation_monster" />
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_navigation_search_to_collectionDetailFragment"
|
||||||
|
app:destination="@id/collectionDetailFragment" />
|
||||||
</fragment>
|
</fragment>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/navigation_dashboard"
|
android:id="@+id/navigation_dashboard"
|
||||||
|
|||||||
Reference in New Issue
Block a user