Commit 787d0275 authored by Geoffrey Métais's avatar Geoffrey Métais

New search activity, powered by medialibrary

parent 4cf3a67c
......@@ -3,6 +3,7 @@ package org.videolan.medialibrary;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.Nullable;
import android.text.TextUtils;
public class Tools {
......@@ -17,4 +18,8 @@ public class Tools {
String path = uri.toString();
return Uri.parse(path.replace("/sdcard", Environment.getExternalStorageDirectory().getPath()));
}
public static boolean isArrayEmpty(@Nullable Object[] array) {
return array == null || array.length == 0;
}
}
package org.videolan.medialibrary.media;
import org.videolan.medialibrary.Tools;
public class MediaSearchAggregate {
private final MediaWrapper[] episodes, movies, others, tracks;
......@@ -27,6 +29,10 @@ public class MediaSearchAggregate {
return tracks;
}
public boolean isEmpty() {
return Tools.isArrayEmpty(episodes) && Tools.isArrayEmpty(movies) && Tools.isArrayEmpty(others) && Tools.isArrayEmpty(tracks);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
......
package org.videolan.medialibrary.media;
import org.videolan.medialibrary.Tools;
public class SearchAggregate {
private final Album[] albums;
......@@ -43,6 +45,10 @@ public class SearchAggregate {
return playlists;
}
public boolean isEmpty() {
return Tools.isArrayEmpty(albums) && Tools.isArrayEmpty(artists) && Tools.isArrayEmpty(genres) && Tools.isArrayEmpty(playlists) && (mediaSearchAggregate == null || mediaSearchAggregate.isEmpty());
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
......
......@@ -437,6 +437,15 @@
android:name=".gui.SecondaryActivity"
android:windowSoftInputMode="adjustPan"
android:theme="@style/Theme.VLC"/>
<activity
android:name=".gui.SearchActivity"
android:theme="@style/Theme.VLC">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
<activity
android:name=".gui.browser.FilePickerActivity"
android:theme="@style/Theme.VLC.PickerDialog"/>
......
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" >
<data>
<import type="android.view.View" />
<variable
name="searchAggregate"
type="org.videolan.medialibrary.media.SearchAggregate" />
<variable
name="handler"
type="org.videolan.vlc.gui.SearchActivity.ClickHandler" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_arrow_back"
android:layout_gravity="center_vertical"
android:layout_marginEnd="10dp"
android:scaleType="center"
android:onClick="@{handler::onBack}" />
<android.support.design.widget.TextInputLayout
android:id="@+id/search_edit_layout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<EditText
android:id="@+id/search_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:hint="@string/search_hint"
android:imeOptions="actionSearch"
android:inputType="textFilter"
android:textColor="?attr/font_default"
android:background="@android:color/transparent" />
</android.support.design.widget.TextInputLayout>
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_clear_orange"
android:layout_gravity="center_vertical"
android:scaleType="center"
android:onClick="@{handler::onClean}" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default_darker"
android:visibility="@{searchAggregate.isEmpty ? View.GONE : View.VISIBLE}">
<LinearLayout
android:id="@+id/results_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@string/albums"
android:textAppearance="@style/Result.Title"
android:visibility="@{searchAggregate.albums.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/albums_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.albums.length == 0 ? View.GONE : View.VISIBLE}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/artists"
android:visibility="@{searchAggregate.artists.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/artists_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.artists.length == 0 ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/genres"
android:visibility="@{searchAggregate.genres.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/genres_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.genres.length == 0 ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/playlists"
android:visibility="@{searchAggregate.playlists.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/playlists_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.playlists.length == 0 ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/episodes"
android:visibility="@{searchAggregate.mediaSearchAggregate.episodes.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/episodes_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.mediaSearchAggregate.episodes.length == 0 ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/movies"
android:visibility="@{searchAggregate.mediaSearchAggregate.movies.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/movies_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.mediaSearchAggregate.movies.length == 0 ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/videos"
android:visibility="@{searchAggregate.mediaSearchAggregate.others.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/others_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.mediaSearchAggregate.others.length == 0 ? View.GONE : View.VISIBLE}"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textAppearance="@style/Result.Title"
android:text="@string/songs"
android:visibility="@{searchAggregate.mediaSearchAggregate.tracks.length == 0 ? View.GONE : View.VISIBLE}"/>
<org.videolan.vlc.gui.view.ContextMenuRecyclerView
android:id="@+id/songs_results"
style="@style/Result.List"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/background_default"
android:visibility="@{searchAggregate.mediaSearchAggregate.tracks.length == 0 ? View.GONE : View.VISIBLE}"/>
</LinearLayout>
</ScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textAppearance="@style/Result.Title"
android:visibility="@{searchAggregate.isEmpty ? View.VISIBLE : View.GONE}"
android:text="@string/search_no_result"/>
</LinearLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:vlc="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="cover"
type="android.graphics.drawable.BitmapDrawable"/>
<variable
name="item"
type="org.videolan.medialibrary.media.MediaLibraryItem"/>
<variable
name="holder"
type="org.videolan.vlc.gui.SearchResultAdapter.ViewHolder" />
<variable
name="handler"
type="org.videolan.vlc.gui.SearchActivity.ClickHandler" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:clickable="true"
android:background="?attr/background_default"
android:onClick="@{() -> handler.onItemClick(item)}">
<ImageView
android:id="@+id/item_image"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginRight="10dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
android:layout_alignParentLeft="true"
vlc:media="@{item}"
vlc:binding="@{holder.getDataBinding()}"
android:src="@{cover}"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/item_image"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/item_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.title}" />
<TextView
android:id="@+id/item_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{item.description}" />
</LinearLayout>
</RelativeLayout>
</layout>
\ No newline at end of file
......@@ -165,6 +165,7 @@
<string name="password">Password</string>
<string name="search_results">Search results</string>
<string name="search_no_result">No media found</string>
<string name="favorites_add">Add to favorites</string>
<string name="favorites_remove">Remove from favorites</string>
<string name="favorites_edit">Edit</string>
......@@ -660,6 +661,9 @@
<string name="subtitles_download_title">Subs download</string>
<string name="connecting">Connecting...</string>
<string name="save_bluetooth_delay">save delay for bluetooth device</string>
<string name="episodes">episodes</string>
<string name="movies">movies</string>
<string name="videos">videos</string>
<string-array name="chroma_formats" translatable="false">
<item>RGB 32-bit</item>
......
......@@ -233,6 +233,17 @@
<style name="Toolbar.VLC" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
<style name="Result.Title" parent="Base.TextAppearance.AppCompat.Subhead">
<item name="textAllCaps">true</item>
<item name="android:textColor">@color/orange800</item>
</style>
<style name="Result.List" parent="Base.Widget.AppCompat.ListView">
<item name="android:background">@color/white</item>
<item name="android:elevation">2sp</item>
<item name="android:layout_margin">10dp</item>
</style>
<!--Preferences-->
<style name="VLCPreferenceTheme.Light" parent="PreferenceThemeOverlay">
<item name="android:textColorPrimary">@color/grey900</item>
......
......@@ -21,7 +21,6 @@
package org.videolan.vlc.gui;
import android.annotation.TargetApi;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
......@@ -79,7 +78,6 @@ import org.videolan.vlc.gui.browser.ExtensionBrowser;
import org.videolan.vlc.gui.browser.FileBrowserFragment;
import org.videolan.vlc.gui.browser.MediaBrowserFragment;
import org.videolan.vlc.gui.browser.NetworkBrowserFragment;
import org.videolan.vlc.gui.helpers.SearchSuggestionsAdapter;
import org.videolan.vlc.gui.helpers.UiTools;
import org.videolan.vlc.gui.network.MRLPanelFragment;
import org.videolan.vlc.gui.preferences.PreferencesActivity;
......@@ -529,14 +527,14 @@ public class MainActivity extends AudioPlayerContainerActivity implements Device
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.media_library, menu);
SearchManager searchManager =
(SearchManager) VLCApplication.getAppContext().getSystemService(Context.SEARCH_SERVICE);
mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.ml_menu_search));
mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
mSearchView.setQueryHint(getString(R.string.search_hint));
SearchSuggestionsAdapter searchSuggestionsAdapter = new SearchSuggestionsAdapter(this, null);
searchSuggestionsAdapter.setFilterQueryProvider(this);
mSearchView.setSuggestionsAdapter(searchSuggestionsAdapter);
// SearchManager searchManager =
// (SearchManager) VLCApplication.getAppContext().getSystemService(Context.SEARCH_SERVICE);
// mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.ml_menu_search));
// mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
// mSearchView.setQueryHint(getString(R.string.search_hint));
// SearchSuggestionsAdapter searchSuggestionsAdapter = new SearchSuggestionsAdapter(this, null);
// searchSuggestionsAdapter.setFilterQueryProvider(this);
// mSearchView.setSuggestionsAdapter(searchSuggestionsAdapter);
return super.onCreateOptionsMenu(menu);
}
......@@ -632,6 +630,9 @@ public class MainActivity extends AudioPlayerContainerActivity implements Device
case R.id.ml_menu_refresh:
forceRefresh(current);
break;
case R.id.ml_menu_search:
startActivity(new Intent(Intent.ACTION_SEARCH, null, this, SearchActivity.class));
break;
// Restore last playlist
case R.id.ml_menu_last_playlist:
boolean audio = current instanceof AudioBrowserFragment;
......@@ -753,7 +754,7 @@ public class MainActivity extends AudioPlayerContainerActivity implements Device
}
@Override
public Cursor runQuery(CharSequence constraint) {
public Cursor runQuery(final CharSequence constraint) {
return MediaDatabase.getInstance().queryMedia(constraint.toString());
}
......
package org.videolan.vlc.gui;
import android.app.SearchManager;
import android.content.Intent;
import android.databinding.DataBindingUtil;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import org.videolan.medialibrary.Medialibrary;
import org.videolan.medialibrary.media.MediaLibraryItem;
import org.videolan.medialibrary.media.SearchAggregate;
import org.videolan.vlc.R;
import org.videolan.vlc.VLCApplication;
import org.videolan.vlc.databinding.SearchActivityBinding;
import org.videolan.vlc.gui.helpers.UiTools;
import org.videolan.vlc.gui.view.ContextMenuRecyclerView;
import org.videolan.vlc.media.MediaUtils;
public class SearchActivity extends AppCompatActivity implements TextWatcher, TextView.OnEditorActionListener {
public final static String TAG = "VLC/SearchActivity";
private Medialibrary mMedialibrary;
private SearchActivityBinding mBinding;
private ClickHandler mClickHandler = new ClickHandler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("enable_black_theme", false))
setTheme(R.style.Theme_VLC_Black);
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
mBinding = DataBindingUtil.setContentView(this, R.layout.search_activity);
mBinding.setHandler(mClickHandler);
mMedialibrary = VLCApplication.getMLInstance();
String query = intent.getStringExtra(SearchManager.QUERY);
initializeLists();
if (!TextUtils.isEmpty(query)) {
mBinding.searchEditText.setText(query);
mBinding.searchEditText.setSelection(query.length());
performSearh(query);
}
}
mBinding.searchEditText.addTextChangedListener(this);
mBinding.searchEditText.setOnEditorActionListener(this);
}
private void performSearh(final String query) {
VLCApplication.runBackground(new Runnable() {
@Override
public void run() {
final SearchAggregate searchAggregate = mMedialibrary.search(query);
mBinding.setSearchAggregate(searchAggregate);
SearchActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
((SearchResultAdapter)mBinding.albumsResults.getAdapter()).add(searchAggregate.getAlbums());
((SearchResultAdapter)mBinding.artistsResults.getAdapter()).add(searchAggregate.getArtists());
((SearchResultAdapter)mBinding.genresResults.getAdapter()).add(searchAggregate.getGenres());
((SearchResultAdapter)mBinding.playlistsResults.getAdapter()).add(searchAggregate.getPlaylists());
((SearchResultAdapter)mBinding.episodesResults.getAdapter()).add(searchAggregate.getMediaSearchAggregate().getEpisodes());
((SearchResultAdapter)mBinding.moviesResults.getAdapter()).add(searchAggregate.getMediaSearchAggregate().getMovies());
((SearchResultAdapter)mBinding.othersResults.getAdapter()).add(searchAggregate.getMediaSearchAggregate().getOthers());
((SearchResultAdapter)mBinding.songsResults.getAdapter()).add(searchAggregate.getMediaSearchAggregate().getTracks());
}
});
}
});
}
private void initializeLists() {
int count = mBinding.resultsContainer.getChildCount();
for (int i = 0; i < count; ++i) {
View v = mBinding.resultsContainer.getChildAt(i);
if (v instanceof ContextMenuRecyclerView) {
((RecyclerView)v).setAdapter(new SearchResultAdapter());
((RecyclerView)v).setLayoutManager(new LinearLayoutManager(this));
((SearchResultAdapter)((RecyclerView)v).getAdapter()).setClickHandler(mClickHandler);
}
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (s == null || s.length() < 3)
mBinding.setSearchAggregate(new SearchAggregate());
else
performSearh(s.toString());
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
UiTools.setKeyboardVisibility(mBinding.getRoot(), false);
return true;
}
return false;
}
private void clear() {
mBinding.searchEditText.removeTextChangedListener(this);
mBinding.searchEditText.setText("");
mBinding.searchEditText.addTextChangedListener(this);
mBinding.setSearchAggregate(new SearchAggregate());
}
public class ClickHandler {
public void onClean(View v) {
clear();
}
public void onBack(View v) {
finish();
}
public void onItemClick(MediaLibraryItem item) {
MediaUtils.openArray(SearchActivity.this, item.getTracks(mMedialibrary), 0);
finish();
}
}
}
package org.videolan.vlc.gui;
import android.content.Context;
import android.databinding.ViewDataBinding;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import org.videolan.medialibrary.media.MediaLibraryItem;
import org.videolan.vlc.databinding.SearchItemBinding;
public class SearchResultAdapter extends RecyclerView.Adapter<SearchResultAdapter.ViewHolder> {
MediaLibraryItem[] mDataList;
SearchActivity.ClickHandler mClickHandler;
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return new ViewHolder(SearchItemBinding.inflate(inflater, parent, false));
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.binding.setItem(mDataList[position]);
}
public void add(MediaLibraryItem[] newList) {
mDataList = newList;
notifyDataSetChanged();
}
public void setClickHandler(SearchActivity.ClickHandler clickHandler) {
mClickHandler = clickHandler;
}
@Override
public int getItemCount() {
return mDataList == null ? 0 : mDataList.length;
}
public class ViewHolder extends RecyclerView.ViewHolder {
public SearchItemBinding binding;
public ViewHolder(SearchItemBinding binding) {
super(binding.getRoot());
this.binding = binding;