Improve search

This commit is contained in:
Antonio Cappiello 2020-11-25 15:12:07 +01:00
parent 4848d4f3d0
commit 6c26c6d889
17 changed files with 243 additions and 69 deletions

View file

@ -63,6 +63,7 @@ dependencies {
// SearchBar
implementation "com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.3"
implementation "com.arthurivanets.adapster:adapster:1.0.13"
// Permission
implementation 'pub.devrel:easypermissions:3.0.0'

View file

@ -6,68 +6,69 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager.widget.PagerAdapter;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.util.Util;
import java.util.List;
public class DiscoverSongAdapter extends PagerAdapter {
public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapter.ViewHolder> {
private static final String TAG = "DiscoverSongAdapter";
private List<Song> songs;
private LayoutInflater layoutInflater;
private Context context;
private View view;
public DiscoverSongAdapter(Context context, List<Song> songs) {
this.context = context;
this.layoutInflater = LayoutInflater.from(context);
this.songs = songs;
}
@Override
public int getCount() {
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = layoutInflater.inflate(R.layout.item_home_discover_song, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Song song = songs.get(position);
holder.textTitle.setText(song.getTitle());
holder.textAlbum.setText(song.getAlbumName());
}
@Override
public int getItemCount() {
return songs.size();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object object) {
return view.equals(object);
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textTitle;
TextView textAlbum;
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, final int position) {
layoutInflater = LayoutInflater.from(context);
view = layoutInflater.inflate(R.layout.item_home_discover_song, container, false);
ViewHolder(View itemView) {
super(itemView);
TextView title = view.findViewById(R.id.title_discover_song_label);
TextView desc = view.findViewById(R.id.album_discover_song_label);
title.setText(songs.get(position).getTitle());
desc.setText(songs.get(position).getAlbumName());
textTitle = itemView.findViewById(R.id.title_discover_song_label);
textAlbum = itemView.findViewById(R.id.album_discover_song_label);
view.setOnClickListener(v -> {
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
SongRepository songRepository = new SongRepository(App.getInstance());
songRepository.update(songs.get(position));
});
container.addView(view, 0);
return view;
songRepository.update(songs.get(getAdapterPosition()));
}
}
public void setItems(List<Song> songs) {
this.songs = songs;
notifyDataSetChanged();
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
container.removeView((View) object);
}
}

View file

@ -10,10 +10,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.play.App;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.repository.SongRepository;
import com.cappielloantonio.play.util.Util;
import java.util.List;
@ -43,7 +41,7 @@ public class RecentMusicAdapter extends RecyclerView.Adapter<RecentMusicAdapter.
Song song = songs.get(position);
holder.textTitle.setText(song.getTitle());
holder.textArtist.setText(song.getAlbumName());
holder.textAlbum.setText(song.getAlbumName());
}
@Override
@ -53,13 +51,13 @@ public class RecentMusicAdapter extends RecyclerView.Adapter<RecentMusicAdapter.
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView textTitle;
TextView textArtist;
TextView textAlbum;
ViewHolder(View itemView) {
super(itemView);
textTitle = itemView.findViewById(R.id.title_track_label);
textArtist = itemView.findViewById(R.id.artist_track_label);
textAlbum = itemView.findViewById(R.id.album_track_label);
itemView.setOnClickListener(this);
}

View file

@ -36,4 +36,7 @@ public interface AlbumDao {
@Delete
void delete(Album album);
@Query("SELECT title FROM album WHERE title LIKE :query || '%' GROUP BY title LIMIT :number")
List<String> searchSuggestions(String query, int number);
}

View file

@ -33,4 +33,7 @@ public interface ArtistDao {
@Delete
void delete(Artist artist);
@Query("SELECT name FROM artist WHERE name LIKE :query || '%' GROUP BY name LIMIT :number")
List<String> searchSuggestions(String query, int number);
}

View file

@ -65,4 +65,7 @@ public interface SongDao {
@Query("SELECT * FROM song ORDER BY RANDOM() LIMIT :number")
List<Song> random(int number);
@Query("SELECT title FROM song WHERE title LIKE :query || '%' GROUP BY title LIMIT :number")
List<String> searchSuggestions(String query, int number);
}

View file

@ -1,19 +1,24 @@
package com.cappielloantonio.play.repository;
import android.app.Application;
import android.util.Log;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.AlbumDao;
import com.cappielloantonio.play.database.dao.SongDao;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Song;
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
import java.util.ArrayList;
import java.util.List;
public class AlbumRepository {
private static final String TAG = "AlbumRepository";
private AlbumDao albumDao;
private LiveData<List<Album>> listLiveAlbums;
private LiveData<List<Album>> artistListLiveAlbums;
@ -46,6 +51,23 @@ public class AlbumRepository {
return searchListLiveAlbum;
}
public List<String> getSearchSuggestion(String query) {
List<String> suggestions = new ArrayList<>();
SearchSuggestionsThreadSafe suggestionsThread = new SearchSuggestionsThreadSafe(albumDao, query, 5);
Thread thread = new Thread(suggestionsThread);
thread.start();
try {
thread.join();
suggestions = suggestionsThread.getSuggestions();
} catch (InterruptedException e) {
e.printStackTrace();
}
return suggestions;
}
public boolean exist(Album album) {
boolean exist = false;
@ -145,4 +167,26 @@ public class AlbumRepository {
albumDao.delete(album);
}
}
private static class SearchSuggestionsThreadSafe implements Runnable {
private AlbumDao albumDao;
private String query;
private int number;
private List<String> suggestions = new ArrayList<>();
public SearchSuggestionsThreadSafe(AlbumDao albumDao, String query, int number) {
this.albumDao = albumDao;
this.query = query;
this.number = number;
}
@Override
public void run() {
suggestions = albumDao.searchSuggestions(query, number);
}
public List<String> getSuggestions() {
return suggestions;
}
}
}

View file

@ -5,12 +5,14 @@ import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.AlbumDao;
import com.cappielloantonio.play.database.dao.ArtistDao;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.Song;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ArtistRepository {
@ -40,6 +42,23 @@ public class ArtistRepository {
return searchListLiveArtist;
}
public List<String> getSearchSuggestion(String query) {
List<String> suggestions = new ArrayList<>();
SearchSuggestionsThreadSafe suggestionsThread = new SearchSuggestionsThreadSafe(artistDao, query, 5);
Thread thread = new Thread(suggestionsThread);
thread.start();
try {
thread.join();
suggestions = suggestionsThread.getSuggestions();
} catch (InterruptedException e) {
e.printStackTrace();
}
return suggestions;
}
public boolean exist(Artist artist) {
boolean exist = false;
@ -140,4 +159,26 @@ public class ArtistRepository {
artistDao.delete(artist);
}
}
private static class SearchSuggestionsThreadSafe implements Runnable {
private ArtistDao artistDao;
private String query;
private int number;
private List<String> suggestions = new ArrayList<>();
public SearchSuggestionsThreadSafe(ArtistDao artistDao, String query, int number) {
this.artistDao = artistDao;
this.query = query;
this.number = number;
}
@Override
public void run() {
suggestions = artistDao.searchSuggestions(query, number);
}
public List<String> getSuggestions() {
return suggestions;
}
}
}

View file

@ -5,11 +5,13 @@ import android.app.Application;
import androidx.lifecycle.LiveData;
import com.cappielloantonio.play.database.AppDatabase;
import com.cappielloantonio.play.database.dao.AlbumDao;
import com.cappielloantonio.play.database.dao.SongDao;
import com.cappielloantonio.play.model.Album;
import com.cappielloantonio.play.model.Song;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class SongRepository {
@ -74,6 +76,23 @@ public class SongRepository {
return listLiveFilteredSongs;
}
public List<String> getSearchSuggestion(String query) {
List<String> suggestions = new ArrayList<>();
SearchSuggestionsThreadSafe suggestionsThread = new SearchSuggestionsThreadSafe(songDao, query, 5);
Thread thread = new Thread(suggestionsThread);
thread.start();
try {
thread.join();
suggestions = suggestionsThread.getSuggestions();
} catch (InterruptedException e) {
e.printStackTrace();
}
return suggestions;
}
public boolean exist(Song song) {
boolean exist = false;
@ -233,4 +252,26 @@ public class SongRepository {
return sample;
}
}
private static class SearchSuggestionsThreadSafe implements Runnable {
private SongDao songDao;
private String query;
private int number;
private List<String> suggestions = new ArrayList<>();
public SearchSuggestionsThreadSafe(SongDao songDao, String query, int number) {
this.songDao = songDao;
this.query = query;
this.number = number;
}
@Override
public void run() {
suggestions = songDao.searchSuggestions(query, number);
}
public List<String> getSuggestions() {
return suggestions;
}
}
}

View file

@ -7,9 +7,11 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.play.R;
import com.cappielloantonio.play.adapter.DiscoverSongAdapter;
@ -85,9 +87,12 @@ public class HomeFragment extends Fragment {
}
private void initDiscoverSongSlideView() {
bind.discoverSongViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
discoverSongAdapter = new DiscoverSongAdapter(requireContext(), homeViewModel.getDiscoverSongList());
bind.discoverSongViewPager.setAdapter(discoverSongAdapter);
bind.discoverSongViewPager.setPageMargin(20);
bind.discoverSongViewPager.setOffscreenPageLimit(3);
settDiscoverSongSlideViewOffset(20, 16);
}
private void initRecentAddedSongView() {
@ -116,4 +121,19 @@ public class HomeFragment extends Fragment {
bind.mostPlayedTracksRecyclerView.setAdapter(mostPlayedMusicAdapter);
homeViewModel.getMostPlayedSongList().observe(requireActivity(), songs -> mostPlayedMusicAdapter.setItems(songs));
}
private void settDiscoverSongSlideViewOffset(float pageOffset, float pageMargin) {
bind.discoverSongViewPager.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin);
if (bind.discoverSongViewPager.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
if (ViewCompat.getLayoutDirection(bind.discoverSongViewPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
page.setTranslationX(-myOffset);
} else {
page.setTranslationX(myOffset);
}
} else {
page.setTranslationY(myOffset);
}
});
}
}

View file

@ -4,13 +4,11 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -22,14 +20,14 @@ import com.cappielloantonio.play.adapter.RecentSearchAdapter;
import com.cappielloantonio.play.adapter.SongResultSearchAdapter;
import com.cappielloantonio.play.databinding.FragmentSearchBinding;
import com.cappielloantonio.play.helper.recyclerview.ItemlDecoration;
import com.cappielloantonio.play.model.Artist;
import com.cappielloantonio.play.model.RecentSearch;
import com.cappielloantonio.play.model.Song;
import com.cappielloantonio.play.ui.activities.MainActivity;
import com.cappielloantonio.play.viewmodel.SearchViewModel;
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
import java.util.ArrayList;
import java.util.List;
public class SearchFragment extends Fragment {
private static final String TAG = "SearchFragment";
@ -120,12 +118,23 @@ public class SearchFragment extends Fragment {
}
private void initSearchView() {
bind.persistentSearchView.showRightButton();
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(searchViewModel.getSearchSuggestion(newQuery)), false);
} else {
searchView.setSuggestions(new ArrayList<>());
}
});
bind.persistentSearchView.setOnLeftBtnClickListener(view -> {
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
@Override
public void onSuggestionPicked(SuggestionItem suggestion) {
search(suggestion.getItemModel().getText());
}
@Override
public void onSuggestionRemoved(SuggestionItem suggestion) {
}
});
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
@ -134,12 +143,14 @@ public class SearchFragment extends Fragment {
}
public void search(String query) {
if (!query.equals("")) {
if (!query.trim().equals("") && query.trim().length() > 1) {
searchViewModel.insertNewSearch(query);
bind.persistentSearchView.collapse();
bind.persistentSearchView.setInputQuery(query);
performSearch(query.trim());
} else {
Toast.makeText(requireContext(), "Enter at least two characters", Toast.LENGTH_SHORT).show();
}
}

View file

@ -1,6 +1,7 @@
package com.cappielloantonio.play.viewmodel;
import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@ -16,10 +17,14 @@ import com.cappielloantonio.play.repository.ArtistRepository;
import com.cappielloantonio.play.repository.GenreRepository;
import com.cappielloantonio.play.repository.RecentSearchRepository;
import com.cappielloantonio.play.repository.SongRepository;
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
import java.util.ArrayList;
import java.util.List;
public class SearchViewModel extends AndroidViewModel {
private static final String TAG = "SearchViewModel";
private SongRepository songRepository;
private AlbumRepository albumRepository;
private ArtistRepository artistRepository;
@ -66,4 +71,13 @@ public class SearchViewModel extends AndroidViewModel {
public void deleteAllRecentSearch() {
recentSearchRepository.deleteAll();
}
public List<String> getSearchSuggestion(String query) {
ArrayList<String> suggestions = new ArrayList<>();
suggestions.addAll(songRepository.getSearchSuggestion(query));
suggestions.addAll(albumRepository.getSearchSuggestion(query));
suggestions.addAll(artistRepository.getSearchSuggestion(query));
return suggestions;
}
}

View file

@ -30,29 +30,13 @@
android:textStyle="bold" />
<!-- slideview -->
<androidx.viewpager.widget.ViewPager
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/discover_song_view_pager"
android:layout_width="match_parent"
android:layout_height="172dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:layout_height="188dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp" />
<!-- <androidx.recyclerview.widget.RecyclerView-->
<!-- android:id="@+id/discover_song_recycler_view"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="172dp"-->
<!-- android:layout_marginTop="8dp"-->
<!-- android:layout_marginBottom="8dp"-->
<!-- android:clipToPadding="false"-->
<!-- android:paddingStart="16dp"-->
<!-- android:paddingTop="8dp"-->
<!-- android:paddingEnd="16dp"-->
<!-- android:paddingBottom="8dp" />-->
</LinearLayout>
<!-- Recently added tracks -->

View file

@ -11,7 +11,7 @@
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
app:areSuggestionsDisabled="true"
app:areSuggestionsDisabled="false"
app:cardBackgroundColor="@color/cardColor"
app:cardCornerRadius="4dp"
app:cardElevation="2dp"
@ -28,7 +28,13 @@
app:queryInputHint="@string/search_hint"
app:queryInputHintColor="@color/hintTextColor"
app:queryInputTextColor="@color/hintTextColor"
app:shouldDimBehind="false" />
app:shouldDimBehind="true"
app:suggestionIconColor="@color/suggestionIconColor"
app:suggestionRecentSearchIconColor="@color/suggestionIconColor"
app:suggestionSearchSuggestionIconColor="@color/suggestionIconColor"
app:suggestionTextColor="@color/suggestionTextColor"
app:suggestionSelectedTextColor="@color/suggestionSelectedTextColor" />
<LinearLayout
android:layout_width="match_parent"

View file

@ -2,9 +2,13 @@
<androidx.cardview.widget.CardView 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:layout_height="match_parent"
android:backgroundTint="@color/cardColor"
app:cardCornerRadius="4dp">
app:cardCornerRadius="4dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp">
<RelativeLayout
android:layout_width="match_parent"

View file

@ -30,7 +30,7 @@
android:textStyle="bold" />
<TextView
android:id="@+id/artist_track_label"
android:id="@+id/album_track_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/open_sans_font_family"

View file

@ -7,7 +7,7 @@
<string name="action_settings">Settings</string>
<string name="general_header">General</string>
<string name="search_hint">Search</string>
<string name="search_hint">Search title, artists or albums</string>
<string name="menu_home">Home</string>
<string name="settings_menu_label">Settings</string>
<string name="download_label">Download</string>