mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 07:13:00 +00:00 
			
		
		
		
	Merge branch 'dev' into trending
This commit is contained in:
		| @@ -47,8 +47,8 @@ dependencies { | |||||||
|     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') { |     androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') { | ||||||
|         exclude module: 'support-annotations' |         exclude module: 'support-annotations' | ||||||
|     } |     } | ||||||
|      |  | ||||||
|     compile 'com.github.TeamNewPipe:NewPipeExtractor:466d87c' |     compile 'com.github.TeamNewPipe:NewPipeExtractor:1df3f67' | ||||||
|  |  | ||||||
|     testCompile 'junit:junit:4.12' |     testCompile 'junit:junit:4.12' | ||||||
|     testCompile 'org.mockito:mockito-core:1.10.19' |     testCompile 'org.mockito:mockito-core:1.10.19' | ||||||
|   | |||||||
| @@ -12,7 +12,6 @@ import java.io.InterruptedIOException; | |||||||
| import java.net.URL; | import java.net.URL; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.Iterator; | import java.util.Iterator; | ||||||
| import java.util.List; |  | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  |  | ||||||
| import javax.net.ssl.HttpsURLConnection; | import javax.net.ssl.HttpsURLConnection; | ||||||
| @@ -135,11 +134,8 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             in = new BufferedReader(new InputStreamReader(con.getInputStream())); |             in = new BufferedReader(new InputStreamReader(con.getInputStream())); | ||||||
|             for (Map.Entry<String, List<String>> entry : con.getHeaderFields().entrySet()) { |  | ||||||
|                 System.err.println(entry.getKey() + ": " + entry.getValue()); |  | ||||||
|             } |  | ||||||
|             String inputLine; |  | ||||||
|  |  | ||||||
|  |             String inputLine; | ||||||
|             while ((inputLine = in.readLine()) != null) { |             while ((inputLine = in.readLine()) != null) { | ||||||
|                 response.append(inputLine); |                 response.append(inputLine); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -11,6 +11,7 @@ import io.reactivex.Flowable; | |||||||
|  |  | ||||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE; | import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.CREATION_DATE; | ||||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID; | import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.ID; | ||||||
|  | import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SEARCH; | ||||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID; | import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.SERVICE_ID; | ||||||
| import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME; | import static org.schabi.newpipe.database.history.model.SearchHistoryEntry.TABLE_NAME; | ||||||
|  |  | ||||||
| @@ -27,11 +28,20 @@ public interface SearchHistoryDAO extends HistoryDAO<SearchHistoryEntry> { | |||||||
|     @Override |     @Override | ||||||
|     int deleteAll(); |     int deleteAll(); | ||||||
|  |  | ||||||
|  |     @Query("DELETE FROM " + TABLE_NAME + " WHERE " + SEARCH + " = :query") | ||||||
|  |     int deleteAllWhereQuery(String query); | ||||||
|  |  | ||||||
|     @Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE) |     @Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE) | ||||||
|     @Override |     @Override | ||||||
|     Flowable<List<SearchHistoryEntry>> getAll(); |     Flowable<List<SearchHistoryEntry>> getAll(); | ||||||
|  |  | ||||||
|  |     @Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit") | ||||||
|  |     Flowable<List<SearchHistoryEntry>> getUniqueEntries(int limit); | ||||||
|  |  | ||||||
|     @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) |     @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) | ||||||
|     @Override |     @Override | ||||||
|     Flowable<List<SearchHistoryEntry>> listByService(int serviceId); |     Flowable<List<SearchHistoryEntry>> listByService(int serviceId); | ||||||
|  |  | ||||||
|  |     @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit") | ||||||
|  |     Flowable<List<SearchHistoryEntry>> getSimilarEntries(String query, int limit); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,14 +2,16 @@ package org.schabi.newpipe.fragments.list.search; | |||||||
|  |  | ||||||
| import android.app.Activity; | import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  | import android.content.DialogInterface; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.SharedPreferences; | import android.content.SharedPreferences; | ||||||
| import android.os.Build; |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.preference.PreferenceManager; | import android.preference.PreferenceManager; | ||||||
| import android.support.annotation.NonNull; | import android.support.annotation.NonNull; | ||||||
| import android.support.annotation.Nullable; | import android.support.annotation.Nullable; | ||||||
| import android.support.v7.app.ActionBar; | import android.support.v7.app.ActionBar; | ||||||
|  | import android.support.v7.app.AlertDialog; | ||||||
|  | import android.support.v7.widget.RecyclerView; | ||||||
| import android.support.v7.widget.TooltipCompat; | import android.support.v7.widget.TooltipCompat; | ||||||
| import android.text.Editable; | import android.text.Editable; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
| @@ -25,12 +27,14 @@ import android.view.ViewGroup; | |||||||
| import android.view.animation.DecelerateInterpolator; | import android.view.animation.DecelerateInterpolator; | ||||||
| import android.view.inputmethod.EditorInfo; | import android.view.inputmethod.EditorInfo; | ||||||
| import android.view.inputmethod.InputMethodManager; | import android.view.inputmethod.InputMethodManager; | ||||||
| import android.widget.AdapterView; | import android.widget.EditText; | ||||||
| import android.widget.AutoCompleteTextView; |  | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.NewPipeDatabase; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.ReCaptchaActivity; | import org.schabi.newpipe.ReCaptchaActivity; | ||||||
|  | import org.schabi.newpipe.database.history.dao.SearchHistoryDAO; | ||||||
|  | import org.schabi.newpipe.database.history.model.SearchHistoryEntry; | ||||||
| import org.schabi.newpipe.extractor.InfoItem; | import org.schabi.newpipe.extractor.InfoItem; | ||||||
| import org.schabi.newpipe.extractor.ListExtractor; | import org.schabi.newpipe.extractor.ListExtractor; | ||||||
| import org.schabi.newpipe.extractor.NewPipe; | import org.schabi.newpipe.extractor.NewPipe; | ||||||
| @@ -38,25 +42,34 @@ import org.schabi.newpipe.extractor.StreamingService; | |||||||
| import org.schabi.newpipe.extractor.exceptions.ParsingException; | import org.schabi.newpipe.extractor.exceptions.ParsingException; | ||||||
| import org.schabi.newpipe.extractor.search.SearchEngine; | import org.schabi.newpipe.extractor.search.SearchEngine; | ||||||
| import org.schabi.newpipe.extractor.search.SearchResult; | import org.schabi.newpipe.extractor.search.SearchResult; | ||||||
|  | import org.schabi.newpipe.fragments.BackPressable; | ||||||
| import org.schabi.newpipe.fragments.list.BaseListFragment; | import org.schabi.newpipe.fragments.list.BaseListFragment; | ||||||
| import org.schabi.newpipe.history.HistoryListener; | import org.schabi.newpipe.history.HistoryListener; | ||||||
| import org.schabi.newpipe.report.UserAction; | import org.schabi.newpipe.report.UserAction; | ||||||
|  | import org.schabi.newpipe.util.AnimationUtils; | ||||||
| import org.schabi.newpipe.util.ExtractorHelper; | import org.schabi.newpipe.util.ExtractorHelper; | ||||||
|  | import org.schabi.newpipe.util.LayoutManagerSmoothScroller; | ||||||
| import org.schabi.newpipe.util.NavigationHelper; | import org.schabi.newpipe.util.NavigationHelper; | ||||||
| import org.schabi.newpipe.util.StateSaver; |  | ||||||
|  |  | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InterruptedIOException; | ||||||
|  | import java.net.SocketException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Iterator; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Queue; | import java.util.Queue; | ||||||
| import java.util.concurrent.Callable; | import java.util.concurrent.Callable; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
| import icepick.State; | import icepick.State; | ||||||
|  | import io.reactivex.Flowable; | ||||||
| import io.reactivex.Notification; | import io.reactivex.Notification; | ||||||
| import io.reactivex.Observable; | import io.reactivex.Observable; | ||||||
|  | import io.reactivex.ObservableSource; | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.disposables.CompositeDisposable; | import io.reactivex.disposables.CompositeDisposable; | ||||||
| import io.reactivex.disposables.Disposable; | import io.reactivex.disposables.Disposable; | ||||||
|  | import io.reactivex.functions.BiFunction; | ||||||
| import io.reactivex.functions.Consumer; | import io.reactivex.functions.Consumer; | ||||||
| import io.reactivex.functions.Function; | import io.reactivex.functions.Function; | ||||||
| import io.reactivex.functions.Predicate; | import io.reactivex.functions.Predicate; | ||||||
| @@ -65,21 +78,22 @@ import io.reactivex.subjects.PublishSubject; | |||||||
|  |  | ||||||
| import static org.schabi.newpipe.util.AnimationUtils.animateView; | import static org.schabi.newpipe.util.AnimationUtils.animateView; | ||||||
|  |  | ||||||
| public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor.NextItemsResult> { | public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor.NextItemsResult> implements BackPressable { | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Search |     // Search | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * The suggestions will appear only if the query meet this threshold (>=). |      * The suggestions will only be fetched from network if the query meet this threshold (>=). | ||||||
|  |      * (local ones will be fetched regardless of the length) | ||||||
|      */ |      */ | ||||||
|     private static final int THRESHOLD_SUGGESTION = 3; |     private static final int THRESHOLD_NETWORK_SUGGESTION = 1; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * How much time have to pass without emitting a item (i.e. the user stop typing) to fetch/show the suggestions, in milliseconds. |      * How much time have to pass without emitting a item (i.e. the user stop typing) to fetch/show the suggestions, in milliseconds. | ||||||
|      */ |      */ | ||||||
|     private static final int SUGGESTIONS_DEBOUNCE = 150; //ms |     private static final int SUGGESTIONS_DEBOUNCE = 120; //ms | ||||||
|  |  | ||||||
|     @State |     @State | ||||||
|     protected int filterItemCheckedId = -1; |     protected int filterItemCheckedId = -1; | ||||||
| @@ -88,47 +102,54 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|     @State |     @State | ||||||
|     protected int serviceId = -1; |     protected int serviceId = -1; | ||||||
|     @State |     @State | ||||||
|     protected String searchQuery = ""; |     protected String searchQuery; | ||||||
|  |     @State | ||||||
|  |     protected String lastSearchedQuery; | ||||||
|     @State |     @State | ||||||
|     protected boolean wasSearchFocused = false; |     protected boolean wasSearchFocused = false; | ||||||
|  |  | ||||||
|     private int currentPage = 0; |     private int currentPage = 0; | ||||||
|     private int currentNextPage = 0; |     private int currentNextPage = 0; | ||||||
|     private String searchLanguage; |     private String searchLanguage; | ||||||
|     private boolean showSuggestions = true; |     private boolean isSuggestionsEnabled = true; | ||||||
|  |  | ||||||
|     private PublishSubject<String> suggestionPublisher = PublishSubject.create(); |     private PublishSubject<String> suggestionPublisher = PublishSubject.create(); | ||||||
|     private Disposable searchDisposable; |     private Disposable searchDisposable; | ||||||
|     private Disposable suggestionWorkerDisposable; |     private Disposable suggestionDisposable; | ||||||
|     private CompositeDisposable disposables = new CompositeDisposable(); |     private CompositeDisposable disposables = new CompositeDisposable(); | ||||||
|  |  | ||||||
|     private SuggestionListAdapter suggestionListAdapter; |     private SuggestionListAdapter suggestionListAdapter; | ||||||
|  |     private SearchHistoryDAO searchHistoryDAO; | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Views |     // Views | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     private View searchToolbarContainer; |     private View searchToolbarContainer; | ||||||
|     private AutoCompleteTextView searchEditText; |     private EditText searchEditText; | ||||||
|     private View searchClear; |     private View searchClear; | ||||||
|  |  | ||||||
|  |     private View suggestionsPanel; | ||||||
|  |     private RecyclerView suggestionsRecyclerView; | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////*/ |     /*////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     public static SearchFragment getInstance(int serviceId, String query) { |     public static SearchFragment getInstance(int serviceId, String query) { | ||||||
|         SearchFragment searchFragment = new SearchFragment(); |         SearchFragment searchFragment = new SearchFragment(); | ||||||
|         searchFragment.setQuery(serviceId, query); |         searchFragment.setQuery(serviceId, query); | ||||||
|         searchFragment.searchOnResume(); |  | ||||||
|  |         if (!TextUtils.isEmpty(query)) { | ||||||
|  |             searchFragment.setSearchOnResume(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return searchFragment; |         return searchFragment; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Set wasLoading to true so when the fragment onResume is called, the initial search is done. |      * Set wasLoading to true so when the fragment onResume is called, the initial search is done. | ||||||
|      * (it will only start searching if the query is not null or empty) |  | ||||||
|      */ |      */ | ||||||
|     private void searchOnResume() { |     private void setSearchOnResume() { | ||||||
|         if (!TextUtils.isEmpty(searchQuery)) { |         wasLoading.set(true); | ||||||
|             wasLoading.set(true); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -139,6 +160,16 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|     public void onAttach(Context context) { |     public void onAttach(Context context) { | ||||||
|         super.onAttach(context); |         super.onAttach(context); | ||||||
|         suggestionListAdapter = new SuggestionListAdapter(activity); |         suggestionListAdapter = new SuggestionListAdapter(activity); | ||||||
|  |         searchHistoryDAO = NewPipeDatabase.getInstance().searchHistoryDAO(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onCreate(Bundle savedInstanceState) { | ||||||
|  |         super.onCreate(savedInstanceState); | ||||||
|  |  | ||||||
|  |         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); | ||||||
|  |         isSuggestionsEnabled = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true); | ||||||
|  |         searchLanguage = preferences.getString(getString(R.string.search_language_key), getString(R.string.default_language_value)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -146,15 +177,23 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         return inflater.inflate(R.layout.fragment_search, container, false); |         return inflater.inflate(R.layout.fragment_search, container, false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void onViewCreated(View rootView, Bundle savedInstanceState) { | ||||||
|  |         super.onViewCreated(rootView, savedInstanceState); | ||||||
|  |         showSearchOnStart(); | ||||||
|  |         initSearchListeners(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onPause() { |     public void onPause() { | ||||||
|         super.onPause(); |         super.onPause(); | ||||||
|  |  | ||||||
|         wasSearchFocused = searchEditText.hasFocus(); |         wasSearchFocused = searchEditText.hasFocus(); | ||||||
|  |  | ||||||
|         if (searchDisposable != null) searchDisposable.dispose(); |         if (searchDisposable != null) searchDisposable.dispose(); | ||||||
|         if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose(); |         if (suggestionDisposable != null) suggestionDisposable.dispose(); | ||||||
|         if (disposables != null) disposables.clear(); |         if (disposables != null) disposables.clear(); | ||||||
|         hideSoftKeyboard(searchEditText); |         hideKeyboardSearch(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -162,10 +201,6 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         if (DEBUG) Log.d(TAG, "onResume() called"); |         if (DEBUG) Log.d(TAG, "onResume() called"); | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
|  |  | ||||||
|         SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); |  | ||||||
|         showSuggestions = preferences.getBoolean(getString(R.string.show_search_suggestions_key), true); |  | ||||||
|         searchLanguage = preferences.getString(getString(R.string.search_language_key), getString(R.string.default_language_value)); |  | ||||||
|  |  | ||||||
|         if (!TextUtils.isEmpty(searchQuery)) { |         if (!TextUtils.isEmpty(searchQuery)) { | ||||||
|             if (wasLoading.getAndSet(false)) { |             if (wasLoading.getAndSet(false)) { | ||||||
|                 if (currentNextPage > currentPage) loadMoreItems(); |                 if (currentNextPage > currentPage) loadMoreItems(); | ||||||
| @@ -180,7 +215,16 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (suggestionWorkerDisposable == null || suggestionWorkerDisposable.isDisposed()) initSuggestionObserver(); |         if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver(); | ||||||
|  |  | ||||||
|  |         if (TextUtils.isEmpty(searchQuery) || wasSearchFocused) { | ||||||
|  |             showKeyboardSearch(); | ||||||
|  |             showSuggestionsPanel(); | ||||||
|  |         } else { | ||||||
|  |             hideKeyboardSearch(); | ||||||
|  |             hideSuggestionsPanel(); | ||||||
|  |         } | ||||||
|  |         wasSearchFocused = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
| @@ -193,10 +237,8 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|     @Override |     @Override | ||||||
|     public void onDestroy() { |     public void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|         if (!activity.isChangingConfigurations()) StateSaver.onDestroy(savedState); |  | ||||||
|  |  | ||||||
|         if (searchDisposable != null) searchDisposable.dispose(); |         if (searchDisposable != null) searchDisposable.dispose(); | ||||||
|         if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose(); |         if (suggestionDisposable != null) suggestionDisposable.dispose(); | ||||||
|         if (disposables != null) disposables.clear(); |         if (disposables != null) disposables.clear(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -204,7 +246,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|     public void onActivityResult(int requestCode, int resultCode, Intent data) { |     public void onActivityResult(int requestCode, int resultCode, Intent data) { | ||||||
|         switch (requestCode) { |         switch (requestCode) { | ||||||
|             case ReCaptchaActivity.RECAPTCHA_REQUEST: |             case ReCaptchaActivity.RECAPTCHA_REQUEST: | ||||||
|                 if (resultCode == Activity.RESULT_OK && searchQuery.length() != 0) { |                 if (resultCode == Activity.RESULT_OK && !TextUtils.isEmpty(searchQuery)) { | ||||||
|                     search(searchQuery); |                     search(searchQuery); | ||||||
|                 } else Log.e(TAG, "ReCaptcha failed"); |                 } else Log.e(TAG, "ReCaptcha failed"); | ||||||
|                 break; |                 break; | ||||||
| @@ -215,6 +257,23 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|  |     // Init | ||||||
|  |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     protected void initViews(View rootView, Bundle savedInstanceState) { | ||||||
|  |         super.initViews(rootView, savedInstanceState); | ||||||
|  |         suggestionsPanel = rootView.findViewById(R.id.suggestions_panel); | ||||||
|  |         suggestionsRecyclerView = rootView.findViewById(R.id.suggestions_list); | ||||||
|  |         suggestionsRecyclerView.setAdapter(suggestionListAdapter); | ||||||
|  |         suggestionsRecyclerView.setLayoutManager(new LayoutManagerSmoothScroller(activity)); | ||||||
|  |  | ||||||
|  |         searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container); | ||||||
|  |         searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text); | ||||||
|  |         searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // State Saving |     // State Saving | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -235,8 +294,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void onSaveInstanceState(Bundle bundle) { |     public void onSaveInstanceState(Bundle bundle) { | ||||||
|         searchQuery = searchEditText != null && !TextUtils.isEmpty(searchEditText.getText().toString()) |         searchQuery = searchEditText != null ? searchEditText.getText().toString() : searchQuery; | ||||||
|                 ? searchEditText.getText().toString() : searchQuery; |  | ||||||
|         super.onSaveInstanceState(bundle); |         super.onSaveInstanceState(bundle); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -251,7 +309,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         } else { |         } else { | ||||||
|             if (searchEditText != null) { |             if (searchEditText != null) { | ||||||
|                 searchEditText.setText(""); |                 searchEditText.setText(""); | ||||||
|                 showSoftKeyboard(searchEditText); |                 showKeyboardSearch(); | ||||||
|             } |             } | ||||||
|             animateView(errorPanelRoot, false, 200); |             animateView(errorPanelRoot, false, 200); | ||||||
|         } |         } | ||||||
| @@ -272,12 +330,6 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         inflater.inflate(R.menu.menu_search, menu); |         inflater.inflate(R.menu.menu_search, menu); | ||||||
|  |  | ||||||
|         searchToolbarContainer = activity.findViewById(R.id.toolbar_search_container); |  | ||||||
|         searchEditText = searchToolbarContainer.findViewById(R.id.toolbar_search_edit_text); |  | ||||||
|         searchClear = searchToolbarContainer.findViewById(R.id.toolbar_search_clear); |  | ||||||
|         setupSearchView(); |  | ||||||
|  |  | ||||||
|         restoreFilterChecked(menu, filterItemCheckedId); |         restoreFilterChecked(menu, filterItemCheckedId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -307,14 +359,13 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|  |  | ||||||
|     private SearchEngine.Filter getFilterFromMenuId(int itemId) { |     private SearchEngine.Filter getFilterFromMenuId(int itemId) { | ||||||
|         switch (itemId) { |         switch (itemId) { | ||||||
|             case R.id.menu_filter_all: |  | ||||||
|                 return SearchEngine.Filter.ANY; |  | ||||||
|             case R.id.menu_filter_video: |             case R.id.menu_filter_video: | ||||||
|                 return SearchEngine.Filter.STREAM; |                 return SearchEngine.Filter.STREAM; | ||||||
|             case R.id.menu_filter_channel: |             case R.id.menu_filter_channel: | ||||||
|                 return SearchEngine.Filter.CHANNEL; |                 return SearchEngine.Filter.CHANNEL; | ||||||
|             case R.id.menu_filter_playlist: |             case R.id.menu_filter_playlist: | ||||||
|                 return SearchEngine.Filter.PLAYLIST; |                 return SearchEngine.Filter.PLAYLIST; | ||||||
|  |             case R.id.menu_filter_all: | ||||||
|             default: |             default: | ||||||
|                 return SearchEngine.Filter.ANY; |                 return SearchEngine.Filter.ANY; | ||||||
|         } |         } | ||||||
| @@ -326,9 +377,9 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|  |  | ||||||
|     private TextWatcher textWatcher; |     private TextWatcher textWatcher; | ||||||
|  |  | ||||||
|     private void setupSearchView() { |     private void showSearchOnStart() { | ||||||
|         searchEditText.setText(searchQuery != null ? searchQuery : ""); |         if (DEBUG) Log.d(TAG, "showSearchOnStart() called, searchQuery → " + searchQuery+", lastSearchedQuery → " + lastSearchedQuery); | ||||||
|         searchEditText.setAdapter(suggestionListAdapter); |         searchEditText.setText(searchQuery); | ||||||
|  |  | ||||||
|         if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) { |         if (TextUtils.isEmpty(searchQuery) || TextUtils.isEmpty(searchEditText.getText())) { | ||||||
|             searchToolbarContainer.setTranslationX(100); |             searchToolbarContainer.setTranslationX(100); | ||||||
| @@ -340,15 +391,10 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             searchToolbarContainer.setAlpha(1f); |             searchToolbarContainer.setAlpha(1f); | ||||||
|             searchToolbarContainer.setVisibility(View.VISIBLE); |             searchToolbarContainer.setVisibility(View.VISIBLE); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         initSearchListeners(); |  | ||||||
|  |  | ||||||
|         if (TextUtils.isEmpty(searchQuery) || wasSearchFocused) showSoftKeyboard(searchEditText); |  | ||||||
|         else hideSoftKeyboard(searchEditText); |  | ||||||
|         wasSearchFocused = false; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void initSearchListeners() { |     private void initSearchListeners() { | ||||||
|  |         if (DEBUG) Log.d(TAG, "initSearchListeners() called"); | ||||||
|         searchClear.setOnClickListener(new View.OnClickListener() { |         searchClear.setOnClickListener(new View.OnClickListener() { | ||||||
|             @Override |             @Override | ||||||
|             public void onClick(View v) { |             public void onClick(View v) { | ||||||
| @@ -358,11 +404,9 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { |                 searchEditText.setText(""); | ||||||
|                     searchEditText.setText("", false); |                 suggestionListAdapter.setItems(new ArrayList<SuggestionItem>()); | ||||||
|                 } else searchEditText.setText(""); |                 showKeyboardSearch(); | ||||||
|                 suggestionListAdapter.updateAdapter(new ArrayList<String>()); |  | ||||||
|                 showSoftKeyboard(searchEditText); |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -372,7 +416,9 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             @Override |             @Override | ||||||
|             public void onClick(View v) { |             public void onClick(View v) { | ||||||
|                 if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); |                 if (DEBUG) Log.d(TAG, "onClick() called with: v = [" + v + "]"); | ||||||
|                 searchEditText.showDropDown(); |                 if (isSuggestionsEnabled && errorPanelRoot.getVisibility() != View.VISIBLE) { | ||||||
|  |                     showSuggestionsPanel(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
| @@ -380,22 +426,24 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             @Override |             @Override | ||||||
|             public void onFocusChange(View v, boolean hasFocus) { |             public void onFocusChange(View v, boolean hasFocus) { | ||||||
|                 if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]"); |                 if (DEBUG) Log.d(TAG, "onFocusChange() called with: v = [" + v + "], hasFocus = [" + hasFocus + "]"); | ||||||
|                 if (hasFocus) searchEditText.showDropDown(); |                 if (isSuggestionsEnabled && hasFocus && errorPanelRoot.getVisibility() != View.VISIBLE) { | ||||||
|  |                     showSuggestionsPanel(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         searchEditText.setOnItemClickListener(new AdapterView.OnItemClickListener() { |         suggestionListAdapter.setListener(new SuggestionListAdapter.OnSuggestionItemSelected() { | ||||||
|             @Override |             @Override | ||||||
|             public void onItemClick(AdapterView<?> parent, View view, int position, long id) { |             public void onSuggestionItemSelected(SuggestionItem item) { | ||||||
|                 if (DEBUG) { |                 search(item.query); | ||||||
|                     Log.d(TAG, "onItemClick() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); |                 searchEditText.setText(item.query); | ||||||
|                 } |             } | ||||||
|                 String s = suggestionListAdapter.getSuggestion(position); |  | ||||||
|                 if (DEBUG) Log.d(TAG, "onItemClick text = " + s); |             @Override | ||||||
|                 submitQuery(s); |             public void onSuggestionItemLongClick(SuggestionItem item) { | ||||||
|  |                 if (item.fromHistory) showDeleteSuggestionDialog(item); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         searchEditText.setThreshold(THRESHOLD_SUGGESTION); |  | ||||||
|  |  | ||||||
|         if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); |         if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); | ||||||
|         textWatcher = new TextWatcher() { |         textWatcher = new TextWatcher() { | ||||||
| @@ -410,32 +458,32 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             @Override |             @Override | ||||||
|             public void afterTextChanged(Editable s) { |             public void afterTextChanged(Editable s) { | ||||||
|                 String newText = searchEditText.getText().toString(); |                 String newText = searchEditText.getText().toString(); | ||||||
|                 if (!TextUtils.isEmpty(newText)) suggestionPublisher.onNext(newText); |                 suggestionPublisher.onNext(newText); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|         searchEditText.addTextChangedListener(textWatcher); |         searchEditText.addTextChangedListener(textWatcher); | ||||||
|  |  | ||||||
|         searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { |         searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { | ||||||
|             @Override |             @Override | ||||||
|             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { |             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { | ||||||
|                 if (DEBUG) |                 if (DEBUG) { | ||||||
|                     Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]"); |                     Log.d(TAG, "onEditorAction() called with: v = [" + v + "], actionId = [" + actionId + "], event = [" + event + "]"); | ||||||
|  |                 } | ||||||
|                 if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) { |                 if (event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getAction() == EditorInfo.IME_ACTION_SEARCH)) { | ||||||
|                     submitQuery(searchEditText.getText().toString()); |                     search(searchEditText.getText().toString()); | ||||||
|                     return true; |                     return true; | ||||||
|                 } |                 } | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         if (suggestionWorkerDisposable == null || suggestionWorkerDisposable.isDisposed()) initSuggestionObserver(); |         if (suggestionDisposable == null || suggestionDisposable.isDisposed()) initSuggestionObserver(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void unsetSearchListeners() { |     private void unsetSearchListeners() { | ||||||
|  |         if (DEBUG) Log.d(TAG, "unsetSearchListeners() called"); | ||||||
|         searchClear.setOnClickListener(null); |         searchClear.setOnClickListener(null); | ||||||
|         searchClear.setOnLongClickListener(null); |         searchClear.setOnLongClickListener(null); | ||||||
|         searchEditText.setOnClickListener(null); |         searchEditText.setOnClickListener(null); | ||||||
|         searchEditText.setOnItemClickListener(null); |  | ||||||
|         searchEditText.setOnFocusChangeListener(null); |         searchEditText.setOnFocusChangeListener(null); | ||||||
|         searchEditText.setOnEditorActionListener(null); |         searchEditText.setOnEditorActionListener(null); | ||||||
|  |  | ||||||
| @@ -443,68 +491,166 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         textWatcher = null; |         textWatcher = null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void showSoftKeyboard(View view) { |     private void showSuggestionsPanel() { | ||||||
|         if (DEBUG) Log.d(TAG, "showSoftKeyboard() called with: view = [" + view + "]"); |         if (DEBUG) Log.d(TAG, "showSuggestionsPanel() called"); | ||||||
|         if (view == null) return; |         animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, true, 200); | ||||||
|  |     } | ||||||
|  |  | ||||||
|         if (view.requestFocus()) { |     private void hideSuggestionsPanel() { | ||||||
|  |         if (DEBUG) Log.d(TAG, "hideSuggestionsPanel() called"); | ||||||
|  |         animateView(suggestionsPanel, AnimationUtils.Type.LIGHT_SLIDE_AND_ALPHA, false, 200); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void showKeyboardSearch() { | ||||||
|  |         if (DEBUG) Log.d(TAG, "showKeyboardSearch() called"); | ||||||
|  |         if (searchEditText == null) return; | ||||||
|  |  | ||||||
|  |         if (searchEditText.requestFocus()) { | ||||||
|             InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); |             InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); | ||||||
|             imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); |             imm.showSoftInput(searchEditText, InputMethodManager.SHOW_IMPLICIT); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void hideSoftKeyboard(View view) { |     private void hideKeyboardSearch() { | ||||||
|         if (DEBUG) Log.d(TAG, "hideSoftKeyboard() called with: view = [" + view + "]"); |         if (DEBUG) Log.d(TAG, "hideKeyboardSearch() called"); | ||||||
|         if (view == null) return; |         if (searchEditText == null) return; | ||||||
|  |  | ||||||
|         InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); |         InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); | ||||||
|         imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); |         imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS); | ||||||
|  |  | ||||||
|         view.clearFocus(); |         searchEditText.clearFocus(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private void showDeleteSuggestionDialog(final SuggestionItem item) { | ||||||
|  |         new AlertDialog.Builder(activity) | ||||||
|  |                 .setTitle(item.query) | ||||||
|  |                 .setMessage(R.string.delete_item_search_history) | ||||||
|  |                 .setCancelable(true) | ||||||
|  |                 .setNegativeButton(R.string.cancel, null) | ||||||
|  |                 .setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { | ||||||
|  |                     @Override | ||||||
|  |                     public void onClick(DialogInterface dialog, int which) { | ||||||
|  |                         disposables.add(Observable | ||||||
|  |                                 .fromCallable(new Callable<Integer>() { | ||||||
|  |                                     @Override | ||||||
|  |                                     public Integer call() throws Exception { | ||||||
|  |                                         return searchHistoryDAO.deleteAllWhereQuery(item.query); | ||||||
|  |                                     } | ||||||
|  |                                 }) | ||||||
|  |                                 .subscribeOn(Schedulers.io()) | ||||||
|  |                                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|  |                                 .subscribe(new Consumer<Integer>() { | ||||||
|  |                                     @Override | ||||||
|  |                                     public void accept(Integer howManyDeleted) throws Exception { | ||||||
|  |                                         suggestionPublisher.onNext(searchEditText.getText().toString()); | ||||||
|  |                                     } | ||||||
|  |                                 }, new Consumer<Throwable>() { | ||||||
|  |                                     @Override | ||||||
|  |                                     public void accept(Throwable throwable) throws Exception { | ||||||
|  |                                         showSnackBarError(throwable, UserAction.SOMETHING_ELSE, "none", "Deleting item failed", R.string.general_error); | ||||||
|  |                                     } | ||||||
|  |                                 })); | ||||||
|  |                     } | ||||||
|  |                 }).show(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean onBackPressed() { | ||||||
|  |         if (suggestionsPanel.getVisibility() == View.VISIBLE && infoListAdapter.getItemsList().size() > 0 && !isLoading.get()) { | ||||||
|  |             hideSuggestionsPanel(); | ||||||
|  |             hideKeyboardSearch(); | ||||||
|  |             searchEditText.setText(lastSearchedQuery); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void giveSearchEditTextFocus() { |     public void giveSearchEditTextFocus() { | ||||||
|         showSoftKeyboard(searchEditText); |         showKeyboardSearch(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void initSuggestionObserver() { |     private void initSuggestionObserver() { | ||||||
|         if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose(); |         if (DEBUG) Log.d(TAG, "initSuggestionObserver() called"); | ||||||
|         final Predicate<String> checkEnabledAndLength = new Predicate<String>() { |         if (suggestionDisposable != null) suggestionDisposable.dispose(); | ||||||
|             @Override |  | ||||||
|             public boolean test(@io.reactivex.annotations.NonNull String s) throws Exception { |  | ||||||
|                 boolean lengthCheck = s.length() >= THRESHOLD_SUGGESTION; |  | ||||||
|                 // Clear the suggestions adapter if the length check fails |  | ||||||
|                 if (!lengthCheck && !suggestionListAdapter.isEmpty()) { |  | ||||||
|                     suggestionListAdapter.updateAdapter(new ArrayList<String>()); |  | ||||||
|                 } |  | ||||||
|                 // Only pass through if suggestions is enabled and the query length is equal or greater than THRESHOLD_SUGGESTION |  | ||||||
|                 return showSuggestions && lengthCheck; |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         suggestionWorkerDisposable = suggestionPublisher |         final Observable<String> observable = suggestionPublisher | ||||||
|                 .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) |                 .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) | ||||||
|                 .startWith(!TextUtils.isEmpty(searchQuery) ? searchQuery : "") |                 .startWith(searchQuery != null ? searchQuery : "") | ||||||
|                 .filter(checkEnabledAndLength) |                 .filter(new Predicate<String>() { | ||||||
|                 .switchMap(new Function<String, Observable<Notification<List<String>>>>() { |  | ||||||
|                     @Override |                     @Override | ||||||
|                     public Observable<Notification<List<String>>> apply(@io.reactivex.annotations.NonNull String query) throws Exception { |                     public boolean test(@io.reactivex.annotations.NonNull String query) throws Exception { | ||||||
|                         return ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable().materialize(); |                         return isSuggestionsEnabled; | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|  |         suggestionDisposable = observable | ||||||
|  |                 .switchMap(new Function<String, ObservableSource<Notification<List<SuggestionItem>>>>() { | ||||||
|  |                     @Override | ||||||
|  |                     public ObservableSource<Notification<List<SuggestionItem>>> apply(@io.reactivex.annotations.NonNull final String query) throws Exception { | ||||||
|  |                         final Flowable<List<SearchHistoryEntry>> flowable = query.length() > 0 | ||||||
|  |                                 ? searchHistoryDAO.getSimilarEntries(query, 3) | ||||||
|  |                                 : searchHistoryDAO.getUniqueEntries(25); | ||||||
|  |                         final Observable<List<SuggestionItem>> local = flowable.toObservable() | ||||||
|  |                                 .map(new Function<List<SearchHistoryEntry>, List<SuggestionItem>>() { | ||||||
|  |                                     @Override | ||||||
|  |                                     public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<SearchHistoryEntry> searchHistoryEntries) throws Exception { | ||||||
|  |                                         List<SuggestionItem> result = new ArrayList<>(); | ||||||
|  |                                         for (SearchHistoryEntry entry : searchHistoryEntries) | ||||||
|  |                                             result.add(new SuggestionItem(true, entry.getSearch())); | ||||||
|  |                                         return result; | ||||||
|  |                                     } | ||||||
|  |                                 }); | ||||||
|  |  | ||||||
|  |                         if (query.length() < THRESHOLD_NETWORK_SUGGESTION) { | ||||||
|  |                             // Only pass through if the query length is equal or greater than THRESHOLD_NETWORK_SUGGESTION | ||||||
|  |                             return local.materialize(); | ||||||
|  |                         } | ||||||
|  |  | ||||||
|  |                         final Observable<List<SuggestionItem>> network = ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable() | ||||||
|  |                                 .map(new Function<List<String>, List<SuggestionItem>>() { | ||||||
|  |                                     @Override | ||||||
|  |                                     public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<String> strings) throws Exception { | ||||||
|  |                                         List<SuggestionItem> result = new ArrayList<>(); | ||||||
|  |                                         for (String entry : strings) result.add(new SuggestionItem(false, entry)); | ||||||
|  |                                         return result; | ||||||
|  |                                     } | ||||||
|  |                                 }); | ||||||
|  |  | ||||||
|  |                         return Observable.zip(local, network, new BiFunction<List<SuggestionItem>, List<SuggestionItem>, List<SuggestionItem>>() { | ||||||
|  |                             @Override | ||||||
|  |                             public List<SuggestionItem> apply(@io.reactivex.annotations.NonNull List<SuggestionItem> localResult, @io.reactivex.annotations.NonNull List<SuggestionItem> networkResult) throws Exception { | ||||||
|  |                                 List<SuggestionItem> result = new ArrayList<>(); | ||||||
|  |                                 if (localResult.size() > 0) result.addAll(localResult); | ||||||
|  |  | ||||||
|  |                                 // Remove duplicates | ||||||
|  |                                 final Iterator<SuggestionItem> iterator = networkResult.iterator(); | ||||||
|  |                                 while (iterator.hasNext() && localResult.size() > 0) { | ||||||
|  |                                     final SuggestionItem next = iterator.next(); | ||||||
|  |                                     for (SuggestionItem item : localResult) { | ||||||
|  |                                         if (item.query.equals(next.query)) { | ||||||
|  |                                             iterator.remove(); | ||||||
|  |                                             break; | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |  | ||||||
|  |                                 if (networkResult.size() > 0) result.addAll(networkResult); | ||||||
|  |                                 return result; | ||||||
|  |                             } | ||||||
|  |                         }).materialize(); | ||||||
|                     } |                     } | ||||||
|                 }) |                 }) | ||||||
|                 .subscribeOn(Schedulers.io()) |                 .subscribeOn(Schedulers.io()) | ||||||
|                 .observeOn(AndroidSchedulers.mainThread()) |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                 .subscribe(new Consumer<Notification<List<String>>>() { |                 .subscribe(new Consumer<Notification<List<SuggestionItem>>>() { | ||||||
|                     @Override |                     @Override | ||||||
|                     public void accept(@io.reactivex.annotations.NonNull Notification<List<String>> listNotification) throws Exception { |                     public void accept(@io.reactivex.annotations.NonNull Notification<List<SuggestionItem>> listNotification) throws Exception { | ||||||
|                         if (listNotification.isOnNext()) { |                         if (listNotification.isOnNext()) { | ||||||
|                             handleSuggestions(listNotification.getValue()); |                             handleSuggestions(listNotification.getValue()); | ||||||
|                             if (errorPanelRoot.getVisibility() == View.VISIBLE) { |  | ||||||
|                                 hideLoading(); |  | ||||||
|                             } |  | ||||||
|                         } else if (listNotification.isOnError()) { |                         } else if (listNotification.isOnError()) { | ||||||
|                             Throwable error = listNotification.getError(); |                             Throwable error = listNotification.getError(); | ||||||
|                             if (!ExtractorHelper.isInterruptedCaused(error)) { |                             if (!ExtractorHelper.hasAssignableCauseThrowable(error, | ||||||
|  |                                     IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class)) { | ||||||
|                                 onSuggestionError(error); |                                 onSuggestionError(error); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
| @@ -519,6 +665,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|  |  | ||||||
|     private void search(final String query) { |     private void search(final String query) { | ||||||
|         if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "]"); |         if (DEBUG) Log.d(TAG, "search() called with: query = [" + query + "]"); | ||||||
|  |         if (query.isEmpty()) return; | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             final StreamingService service = NewPipe.getServiceByUrl(query); |             final StreamingService service = NewPipe.getServiceByUrl(query); | ||||||
| @@ -543,7 +690,6 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|                             @Override |                             @Override | ||||||
|                             public void accept(Throwable throwable) throws Exception { |                             public void accept(Throwable throwable) throws Exception { | ||||||
|                                 showError(getString(R.string.url_not_supported_toast), false); |                                 showError(getString(R.string.url_not_supported_toast), false); | ||||||
|                                 hideLoading(); |  | ||||||
|                             } |                             } | ||||||
|                         })); |                         })); | ||||||
|                 return; |                 return; | ||||||
| @@ -552,18 +698,18 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             // Exception occurred, it's not a url |             // Exception occurred, it's not a url | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         hideSoftKeyboard(searchEditText); |         lastSearchedQuery = query; | ||||||
|         this.searchQuery = query; |         searchQuery = query; | ||||||
|         this.currentPage = 0; |         currentPage = 0; | ||||||
|         infoListAdapter.clearStreamItemList(); |         infoListAdapter.clearStreamItemList(); | ||||||
|  |         hideSuggestionsPanel(); | ||||||
|  |         hideKeyboardSearch(); | ||||||
|  |  | ||||||
|         if (activity instanceof HistoryListener) { |         if (activity instanceof HistoryListener) { | ||||||
|             ((HistoryListener) activity).onSearch(serviceId, query); |             ((HistoryListener) activity).onSearch(serviceId, query); | ||||||
|  |             suggestionPublisher.onNext(query); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext()); |  | ||||||
|         final String searchLanguageKey = getContext().getString(R.string.search_language_key); |  | ||||||
|         searchLanguage = sharedPreferences.getString(searchLanguageKey, getContext().getString(R.string.default_language_value)); |  | ||||||
|         startLoading(false); |         startLoading(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -623,7 +769,7 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|     @Override |     @Override | ||||||
|     protected void onItemSelected(InfoItem selectedItem) { |     protected void onItemSelected(InfoItem selectedItem) { | ||||||
|         super.onItemSelected(selectedItem); |         super.onItemSelected(selectedItem); | ||||||
|         hideSoftKeyboard(searchEditText); |         hideKeyboardSearch(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
| @@ -634,13 +780,10 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         this.filter = filter; |         this.filter = filter; | ||||||
|         this.filterItemCheckedId = item.getItemId(); |         this.filterItemCheckedId = item.getItemId(); | ||||||
|         item.setChecked(true); |         item.setChecked(true); | ||||||
|         if (searchQuery != null && !searchQuery.isEmpty()) search(searchQuery); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void submitQuery(String query) { |         if (!TextUtils.isEmpty(searchQuery)) { | ||||||
|         if (DEBUG) Log.d(TAG, "submitQuery() called with: query = [" + query + "]"); |             search(searchQuery); | ||||||
|         if (query.isEmpty()) return; |         } | ||||||
|         search(query); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void setQuery(int serviceId, String searchQuery) { |     private void setQuery(int serviceId, String searchQuery) { | ||||||
| @@ -648,19 +791,23 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         this.searchQuery = searchQuery; |         this.searchQuery = searchQuery; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public void showError(String message, boolean showRetryButton) { |  | ||||||
|         super.showError(message, showRetryButton); |  | ||||||
|         hideSoftKeyboard(searchEditText); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Suggestion Results |     // Suggestion Results | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
|  |  | ||||||
|     public void handleSuggestions(@NonNull List<String> suggestions) { |     public void handleSuggestions(@NonNull final List<SuggestionItem> suggestions) { | ||||||
|         if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); |         if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); | ||||||
|         suggestionListAdapter.updateAdapter(suggestions); |         suggestionsRecyclerView.smoothScrollToPosition(0); | ||||||
|  |         suggestionsRecyclerView.post(new Runnable() { | ||||||
|  |             @Override | ||||||
|  |             public void run() { | ||||||
|  |                 suggestionListAdapter.setItems(suggestions); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         if (errorPanelRoot.getVisibility() == View.VISIBLE) { | ||||||
|  |             hideLoading(); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void onSuggestionError(Throwable exception) { |     public void onSuggestionError(Throwable exception) { | ||||||
| @@ -681,6 +828,13 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|         showListFooter(false); |         showListFooter(false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void showError(String message, boolean showRetryButton) { | ||||||
|  |         super.showError(message, showRetryButton); | ||||||
|  |         hideSuggestionsPanel(); | ||||||
|  |         hideKeyboardSearch(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /*////////////////////////////////////////////////////////////////////////// |     /*////////////////////////////////////////////////////////////////////////// | ||||||
|     // Search Results |     // Search Results | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -691,6 +845,8 @@ public class SearchFragment extends BaseListFragment<SearchResult, ListExtractor | |||||||
|             showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, 0); |             showSnackBarError(result.errors, UserAction.SEARCHED, NewPipe.getNameOfService(serviceId), searchQuery, 0); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         lastSearchedQuery = searchQuery; | ||||||
|  |  | ||||||
|         if (infoListAdapter.getItemsList().size() == 0) { |         if (infoListAdapter.getItemsList().size() == 0) { | ||||||
|             if (result.resultList.size() > 0) { |             if (result.resultList.size() > 0) { | ||||||
|                 infoListAdapter.addInfoItemList(result.resultList); |                 infoListAdapter.addInfoItemList(result.resultList); | ||||||
|   | |||||||
| @@ -0,0 +1,16 @@ | |||||||
|  | package org.schabi.newpipe.fragments.list.search; | ||||||
|  |  | ||||||
|  | public class SuggestionItem { | ||||||
|  |     public final boolean fromHistory; | ||||||
|  |     public final String query; | ||||||
|  |  | ||||||
|  |     public SuggestionItem(boolean fromHistory, String query) { | ||||||
|  |         this.fromHistory = fromHistory; | ||||||
|  |         this.query = query; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public String toString() { | ||||||
|  |         return "[" + fromHistory + "→" + query + "]"; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,89 +1,108 @@ | |||||||
| package org.schabi.newpipe.fragments.list.search; | package org.schabi.newpipe.fragments.list.search; | ||||||
|  |  | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.database.Cursor; | import android.content.res.TypedArray; | ||||||
| import android.database.MatrixCursor; | import android.support.annotation.AttrRes; | ||||||
| import android.support.v4.widget.ResourceCursorAdapter; | import android.support.v7.widget.RecyclerView; | ||||||
|  | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
|  | import android.view.ViewGroup; | ||||||
|  | import android.widget.ImageView; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.R; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| /* | public class SuggestionListAdapter extends RecyclerView.Adapter<SuggestionListAdapter.SuggestionItemHolder> { | ||||||
|  * Created by Christian Schabesberger on 02.08.16. |     private final ArrayList<SuggestionItem> items = new ArrayList<>(); | ||||||
|  * |     private final Context context; | ||||||
|  * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> |     private OnSuggestionItemSelected listener; | ||||||
|  * SuggestionListAdapter.java is part of NewPipe. |  | ||||||
|  * |  | ||||||
|  * NewPipe is free software: you can redistribute it and/or modify |  | ||||||
|  * it under the terms of the GNU General Public License as published by |  | ||||||
|  * the Free Software Foundation, either version 3 of the License, or |  | ||||||
|  * (at your option) any later version. |  | ||||||
|  * |  | ||||||
|  * NewPipe is distributed in the hope that it will be useful, |  | ||||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|  * GNU General Public License for more details. |  | ||||||
|  * |  | ||||||
|  * You should have received a copy of the GNU General Public License |  | ||||||
|  * along with NewPipe.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * {@link ResourceCursorAdapter} to display suggestions. |  | ||||||
|  */ |  | ||||||
| public class SuggestionListAdapter extends ResourceCursorAdapter { |  | ||||||
|  |  | ||||||
|     private static final String[] columns = new String[]{"_id", "title"}; |  | ||||||
|     private static final int INDEX_ID = 0; |  | ||||||
|     private static final int INDEX_TITLE = 1; |  | ||||||
|  |  | ||||||
|  |     public interface OnSuggestionItemSelected { | ||||||
|  |         void onSuggestionItemSelected(SuggestionItem item); | ||||||
|  |         void onSuggestionItemLongClick(SuggestionItem item); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public SuggestionListAdapter(Context context) { |     public SuggestionListAdapter(Context context) { | ||||||
|         super(context, android.R.layout.simple_list_item_1, null, 0); |         this.context = context; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setItems(List<SuggestionItem> items) { | ||||||
|  |         this.items.clear(); | ||||||
|  |         this.items.addAll(items); | ||||||
|  |         notifyDataSetChanged(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public void setListener(OnSuggestionItemSelected listener) { | ||||||
|  |         this.listener = listener; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void bindView(View view, Context context, Cursor cursor) { |     public SuggestionItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { | ||||||
|         ViewHolder viewHolder = new ViewHolder(view); |         return new SuggestionItemHolder(LayoutInflater.from(context).inflate(R.layout.item_search_suggestion, parent, false)); | ||||||
|         viewHolder.suggestionTitle.setText(cursor.getString(INDEX_TITLE)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Update the suggestion list |  | ||||||
|      * @param suggestions the list of suggestions |  | ||||||
|      */ |  | ||||||
|     public void updateAdapter(List<String> suggestions) { |  | ||||||
|         MatrixCursor cursor = new MatrixCursor(columns, suggestions.size()); |  | ||||||
|         int i = 0; |  | ||||||
|         for (String suggestion : suggestions) { |  | ||||||
|             String[] columnValues = new String[columns.length]; |  | ||||||
|             columnValues[INDEX_TITLE] = suggestion; |  | ||||||
|             columnValues[INDEX_ID] = Integer.toString(i); |  | ||||||
|             cursor.addRow(columnValues); |  | ||||||
|             i++; |  | ||||||
|         } |  | ||||||
|         changeCursor(cursor); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * Get the suggestion for a position |  | ||||||
|      * @param position the position of the suggestion |  | ||||||
|      * @return the suggestion |  | ||||||
|      */ |  | ||||||
|     public String getSuggestion(int position) { |  | ||||||
|         return ((Cursor) getItem(position)).getString(INDEX_TITLE); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public CharSequence convertToString(Cursor cursor) { |     public void onBindViewHolder(SuggestionItemHolder holder, int position) { | ||||||
|         return cursor.getString(INDEX_TITLE); |         final SuggestionItem currentItem = getItem(position); | ||||||
|  |         holder.updateFrom(currentItem); | ||||||
|  |         holder.itemView.setOnClickListener(new View.OnClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public void onClick(View v) { | ||||||
|  |                 if (listener != null) listener.onSuggestionItemSelected(currentItem); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         holder.itemView.setOnLongClickListener(new View.OnLongClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public boolean onLongClick(View v) { | ||||||
|  |                 if (listener != null) listener.onSuggestionItemLongClick(currentItem); | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private class ViewHolder { |     private SuggestionItem getItem(int position) { | ||||||
|         private final TextView suggestionTitle; |         return items.get(position); | ||||||
|         private ViewHolder(View view) { |     } | ||||||
|             this.suggestionTitle = view.findViewById(android.R.id.text1); |  | ||||||
|  |     @Override | ||||||
|  |     public int getItemCount() { | ||||||
|  |         return items.size(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public boolean isEmpty() { | ||||||
|  |         return getItemCount() == 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public static class SuggestionItemHolder extends RecyclerView.ViewHolder { | ||||||
|  |         private final TextView itemSuggestionQuery; | ||||||
|  |         private final ImageView suggestionIcon; | ||||||
|  |  | ||||||
|  |         // Cache some ids, as they can potentially be constantly updated/recycled | ||||||
|  |         private final int historyResId; | ||||||
|  |         private final int searchResId; | ||||||
|  |  | ||||||
|  |         private SuggestionItemHolder(View rootView) { | ||||||
|  |             super(rootView); | ||||||
|  |             suggestionIcon = rootView.findViewById(R.id.item_suggestion_icon); | ||||||
|  |             itemSuggestionQuery = rootView.findViewById(R.id.item_suggestion_query); | ||||||
|  |  | ||||||
|  |             historyResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.history); | ||||||
|  |             searchResId = resolveResourceIdFromAttr(rootView.getContext(), R.attr.search); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private void updateFrom(SuggestionItem item) { | ||||||
|  |             suggestionIcon.setImageResource(item.fromHistory ? historyResId : searchResId); | ||||||
|  |             itemSuggestionQuery.setText(item.query); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         private static int resolveResourceIdFromAttr(Context context, @AttrRes int attr) { | ||||||
|  |             TypedArray a = context.getTheme().obtainStyledAttributes(new int[]{attr}); | ||||||
|  |             int attributeResourceId = a.getResourceId(0, 0); | ||||||
|  |             a.recycle(); | ||||||
|  |             return attributeResourceId; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -19,7 +19,7 @@ public class AnimationUtils { | |||||||
|     private static final boolean DEBUG = MainActivity.DEBUG; |     private static final boolean DEBUG = MainActivity.DEBUG; | ||||||
|  |  | ||||||
|     public enum Type { |     public enum Type { | ||||||
|         ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA |         ALPHA, SCALE_AND_ALPHA, LIGHT_SCALE_AND_ALPHA, SLIDE_AND_ALPHA, LIGHT_SLIDE_AND_ALPHA | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static void animateView(View view, boolean enterOrExit, long duration) { |     public static void animateView(View view, boolean enterOrExit, long duration) { | ||||||
| @@ -95,9 +95,16 @@ public class AnimationUtils { | |||||||
|             case LIGHT_SCALE_AND_ALPHA: |             case LIGHT_SCALE_AND_ALPHA: | ||||||
|                 animateLightScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd); |                 animateLightScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd); | ||||||
|                 break; |                 break; | ||||||
|  |             case SLIDE_AND_ALPHA: | ||||||
|  |                 animateSlideAndAlpha(view, enterOrExit, duration, delay, execOnEnd); | ||||||
|  |                 break; | ||||||
|  |             case LIGHT_SLIDE_AND_ALPHA: | ||||||
|  |                 animateLightSlideAndAlpha(view, enterOrExit, duration, delay, execOnEnd); | ||||||
|  |                 break; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Animate the background color of a view |      * Animate the background color of a view | ||||||
|      */ |      */ | ||||||
| @@ -237,4 +244,50 @@ public class AnimationUtils { | |||||||
|             }).start(); |             }).start(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private static void animateSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { | ||||||
|  |         if (enterOrExit) { | ||||||
|  |             view.setTranslationY(-view.getHeight()); | ||||||
|  |             view.setAlpha(0f); | ||||||
|  |             view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0) | ||||||
|  |                     .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { | ||||||
|  |                 @Override | ||||||
|  |                 public void onAnimationEnd(Animator animation) { | ||||||
|  |                     if (execOnEnd != null) execOnEnd.run(); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  |         } else { | ||||||
|  |             view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight()) | ||||||
|  |                     .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { | ||||||
|  |                 @Override | ||||||
|  |                 public void onAnimationEnd(Animator animation) { | ||||||
|  |                     view.setVisibility(View.GONE); | ||||||
|  |                     if (execOnEnd != null) execOnEnd.run(); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private static void animateLightSlideAndAlpha(final View view, boolean enterOrExit, long duration, long delay, final Runnable execOnEnd) { | ||||||
|  |         if (enterOrExit) { | ||||||
|  |             view.setTranslationY(-view.getHeight() / 2); | ||||||
|  |             view.setAlpha(0f); | ||||||
|  |             view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1f).translationY(0) | ||||||
|  |                     .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { | ||||||
|  |                 @Override | ||||||
|  |                 public void onAnimationEnd(Animator animation) { | ||||||
|  |                     if (execOnEnd != null) execOnEnd.run(); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  |         } else { | ||||||
|  |             view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(0f).translationY(-view.getHeight() / 2) | ||||||
|  |                     .setDuration(duration).setStartDelay(delay).setListener(new AnimatorListenerAdapter() { | ||||||
|  |                 @Override | ||||||
|  |                 public void onAnimationEnd(Animator animation) { | ||||||
|  |                     view.setVisibility(View.GONE); | ||||||
|  |                     if (execOnEnd != null) execOnEnd.run(); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | package org.schabi.newpipe.util; | ||||||
|  |  | ||||||
|  | import android.content.Context; | ||||||
|  | import android.graphics.PointF; | ||||||
|  | import android.support.v7.widget.LinearLayoutManager; | ||||||
|  | import android.support.v7.widget.LinearSmoothScroller; | ||||||
|  | import android.support.v7.widget.RecyclerView; | ||||||
|  |  | ||||||
|  | public class LayoutManagerSmoothScroller extends LinearLayoutManager { | ||||||
|  |  | ||||||
|  |     public LayoutManagerSmoothScroller(Context context) { | ||||||
|  |         super(context, VERTICAL, false); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public LayoutManagerSmoothScroller(Context context, int orientation, boolean reverseLayout) { | ||||||
|  |         super(context, orientation, reverseLayout); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { | ||||||
|  |         RecyclerView.SmoothScroller smoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()); | ||||||
|  |         smoothScroller.setTargetPosition(position); | ||||||
|  |         startSmoothScroll(smoothScroller); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private class TopSnappedSmoothScroller extends LinearSmoothScroller { | ||||||
|  |         public TopSnappedSmoothScroller(Context context) { | ||||||
|  |             super(context); | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         public PointF computeScrollVectorForPosition(int targetPosition) { | ||||||
|  |             return LayoutManagerSmoothScroller.this | ||||||
|  |                     .computeScrollVectorForPosition(targetPosition); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @Override | ||||||
|  |         protected int getVerticalSnapPreference() { | ||||||
|  |             return SNAP_TO_START; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -51,6 +51,25 @@ | |||||||
|  |  | ||||||
|     </LinearLayout> |     </LinearLayout> | ||||||
|  |  | ||||||
|  |     <LinearLayout | ||||||
|  |         android:id="@+id/suggestions_panel" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="match_parent" | ||||||
|  |         android:background="?android:attr/windowBackground" | ||||||
|  |         android:focusable="true" | ||||||
|  |         android:focusableInTouchMode="true" | ||||||
|  |         android:visibility="gone" | ||||||
|  |         tools:background="@android:color/transparent" | ||||||
|  |         tools:visibility="visible"> | ||||||
|  |  | ||||||
|  |         <android.support.v7.widget.RecyclerView | ||||||
|  |             android:id="@+id/suggestions_list" | ||||||
|  |             android:layout_width="match_parent" | ||||||
|  |             android:layout_height="match_parent" | ||||||
|  |             android:scrollbars="vertical" | ||||||
|  |             tools:listitem="@layout/item_search_suggestion"/> | ||||||
|  |     </LinearLayout> | ||||||
|  |  | ||||||
|     <!--ERROR PANEL--> |     <!--ERROR PANEL--> | ||||||
|     <include |     <include | ||||||
|         android:id="@+id/error_panel" |         android:id="@+id/error_panel" | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								app/src/main/res/layout/item_search_suggestion.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/src/main/res/layout/item_search_suggestion.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <LinearLayout | ||||||
|  |     xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |     android:layout_width="match_parent" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:background="?attr/selectableItemBackground" | ||||||
|  |     android:clickable="true" | ||||||
|  |     android:orientation="horizontal" | ||||||
|  |     android:paddingBottom="8dp" | ||||||
|  |     android:paddingTop="8dp"> | ||||||
|  |  | ||||||
|  |     <ImageView | ||||||
|  |         android:id="@+id/item_suggestion_icon" | ||||||
|  |         android:layout_width="24dp" | ||||||
|  |         android:layout_height="24dp" | ||||||
|  |         android:layout_gravity="center_vertical" | ||||||
|  |         android:layout_marginLeft="16dp" | ||||||
|  |         android:layout_marginRight="16dp" | ||||||
|  |         tools:ignore="ContentDescription,RtlHardcoded" | ||||||
|  |         tools:src="?attr/history"/> | ||||||
|  |  | ||||||
|  |     <TextView | ||||||
|  |         android:id="@+id/item_suggestion_query" | ||||||
|  |         android:layout_width="match_parent" | ||||||
|  |         android:layout_height="wrap_content" | ||||||
|  |         android:layout_gravity="center_vertical" | ||||||
|  |         android:layout_marginLeft="8dp" | ||||||
|  |         android:layout_marginRight="16dp" | ||||||
|  |         android:ellipsize="end" | ||||||
|  |         android:maxLines="2" | ||||||
|  |         android:textAppearance="@style/TextAppearance.AppCompat.Body1" | ||||||
|  |         android:textSize="14sp" | ||||||
|  |         tools:ignore="RtlHardcoded" | ||||||
|  |         tools:text="Search query"/> | ||||||
|  | </LinearLayout> | ||||||
| @@ -4,16 +4,9 @@ | |||||||
|     xmlns:tools="http://schemas.android.com/tools" |     xmlns:tools="http://schemas.android.com/tools" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="?attr/actionBarSize" |     android:layout_height="?attr/actionBarSize" | ||||||
|     android:background="?attr/colorPrimary" |     android:background="?attr/colorPrimary"> | ||||||
|     android:focusable="true" |  | ||||||
|     android:focusableInTouchMode="true"> |  | ||||||
|  |  | ||||||
|     <View |     <EditText | ||||||
|         android:id="@+id/dropdown_anchor" |  | ||||||
|         android:layout_width="match_parent" |  | ||||||
|         android:layout_height="match_parent"/> |  | ||||||
|  |  | ||||||
|     <AutoCompleteTextView |  | ||||||
|         android:id="@+id/toolbar_search_edit_text" |         android:id="@+id/toolbar_search_edit_text" | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="match_parent" |         android:layout_height="match_parent" | ||||||
| @@ -24,7 +17,6 @@ | |||||||
|         android:background="?attr/colorPrimary" |         android:background="?attr/colorPrimary" | ||||||
|         android:drawableLeft="?attr/search" |         android:drawableLeft="?attr/search" | ||||||
|         android:drawablePadding="8dp" |         android:drawablePadding="8dp" | ||||||
|         android:dropDownAnchor="@+id/dropdown_anchor" |  | ||||||
|         android:focusable="true" |         android:focusable="true" | ||||||
|         android:focusableInTouchMode="true" |         android:focusableInTouchMode="true" | ||||||
|         android:hint="@string/search" |         android:hint="@string/search" | ||||||
|   | |||||||
| @@ -119,7 +119,7 @@ | |||||||
|  |  | ||||||
|     <string name="start">Starten</string> |     <string name="start">Starten</string> | ||||||
|     <string name="pause">Pause</string> |     <string name="pause">Pause</string> | ||||||
|     <string name="view">Ansehen</string> |     <string name="view">Abspielen</string> | ||||||
|     <string name="add">Neue Mission</string> |     <string name="add">Neue Mission</string> | ||||||
|     <string name="finish">OK</string> |     <string name="finish">OK</string> | ||||||
|     <string name="msg_server_unsupported">Server nicht unterstützt</string> |     <string name="msg_server_unsupported">Server nicht unterstützt</string> | ||||||
| @@ -254,4 +254,9 @@ | |||||||
|     <string name="charset_most_special_characters">Die meisten Sonderzeichen</string> |     <string name="charset_most_special_characters">Die meisten Sonderzeichen</string> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Element gelöscht</string> |     <string name="item_deleted">Element gelöscht</string> | ||||||
|  | <string name="resume_on_audio_focus_gain_title">Fortsetzen bei erneutem Fokussieren</string> | ||||||
|  |     <string name="settings_category_player_title">Player</string> | ||||||
|  |     <string name="empty_subscription_feed_subtitle">Nichts hier außer Grillen</string> | ||||||
|  |  | ||||||
|  |     <string name="delete_item_search_history">Möchten Sie dieses Element aus dem Suchverlauf löschen?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ | |||||||
|     <string name="show_age_restricted_content_title">Mostrar contenido restringido por edad</string> |     <string name="show_age_restricted_content_title">Mostrar contenido restringido por edad</string> | ||||||
|     <string name="video_is_age_restricted">Vídeo restringido por edad. Permitir este tipo de material es posible desde Ajustes.</string> |     <string name="video_is_age_restricted">Vídeo restringido por edad. Permitir este tipo de material es posible desde Ajustes.</string> | ||||||
|  |  | ||||||
|     <string name="main_bg_subtitle">Toque buscar para empezar</string> |     <string name="main_bg_subtitle">Toque en buscar para empezar</string> | ||||||
|     <string name="autoplay_by_calling_app_title">Autoreproducir</string> |     <string name="autoplay_by_calling_app_title">Autoreproducir</string> | ||||||
|     <string name="autoplay_by_calling_app_summary">Reproducir automáticamente un vídeo cuando NewPipe es llamado desde otra aplicación</string> |     <string name="autoplay_by_calling_app_summary">Reproducir automáticamente un vídeo cuando NewPipe es llamado desde otra aplicación</string> | ||||||
|     <string name="duration_live">en vivo</string> |     <string name="duration_live">en vivo</string> | ||||||
| @@ -259,4 +259,5 @@ abrir en modo popup</string> | |||||||
| </plurals> | </plurals> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Elemento eliminado</string> |     <string name="item_deleted">Elemento eliminado</string> | ||||||
|  | <string name="delete_item_search_history">¿Desea eliminar este elemento del historial de búsqueda?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ | |||||||
|     <string name="settings">Paramètres</string> |     <string name="settings">Paramètres</string> | ||||||
|     <string name="share">Partager</string> |     <string name="share">Partager</string> | ||||||
|     <string name="share_dialog_title">Partager avec</string> |     <string name="share_dialog_title">Partager avec</string> | ||||||
|     <string name="show_play_with_kodi_summary">Afficher une option pour lire la vidéo via la médiathèque Kodi</string> |     <string name="show_play_with_kodi_summary">Afficher une option pour lire la vidéo via Kodi</string> | ||||||
|     <string name="show_play_with_kodi_title">Afficher l’option « Lire avec Kodi »</string> |     <string name="show_play_with_kodi_title">Afficher l’option « Lire avec Kodi »</string> | ||||||
|     <string name="upload_date_text">Ajoutée le %1$s</string> |     <string name="upload_date_text">Ajoutée le %1$s</string> | ||||||
|     <string name="view_count_text">%1$s vues</string> |     <string name="view_count_text">%1$s vues</string> | ||||||
| @@ -188,7 +188,7 @@ | |||||||
|     <string name="action_about">À propos</string> |     <string name="action_about">À propos</string> | ||||||
|     <string name="title_licenses">Licences tierces</string> |     <string name="title_licenses">Licences tierces</string> | ||||||
|     <string name="error_unable_to_load_license">Impossible de charger la licence</string> |     <string name="error_unable_to_load_license">Impossible de charger la licence</string> | ||||||
|     <string name="action_open_website">Ouvrir le site web</string> |     <string name="action_open_website">Ouvrir le site</string> | ||||||
|     <string name="tab_about">À propos</string> |     <string name="tab_about">À propos</string> | ||||||
|     <string name="tab_contributors">Contributeurs</string> |     <string name="tab_contributors">Contributeurs</string> | ||||||
|     <string name="tab_licenses">Licences</string> |     <string name="tab_licenses">Licences</string> | ||||||
| @@ -256,4 +256,5 @@ | |||||||
|     <string name="charset_most_special_characters">Caractères spéciaux</string> |     <string name="charset_most_special_characters">Caractères spéciaux</string> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Objet effacé</string> |     <string name="item_deleted">Objet effacé</string> | ||||||
|  | <string name="delete_item_search_history">Voulez-vous supprimer cet élément de l\'historique de recherche ?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -12,16 +12,16 @@ | |||||||
|     <string name="settings">Impostazioni</string> |     <string name="settings">Impostazioni</string> | ||||||
|     <string name="did_you_mean">Intendevi: %1$s ?</string> |     <string name="did_you_mean">Intendevi: %1$s ?</string> | ||||||
|     <string name="share_dialog_title">Condividi con</string> |     <string name="share_dialog_title">Condividi con</string> | ||||||
|     <string name="choose_browser">Scegli browser</string> |     <string name="choose_browser">Scegli il browser</string> | ||||||
|     <string name="screen_rotation">rotazione</string> |     <string name="screen_rotation">rotazione</string> | ||||||
|     <string name="download_path_title">Cartella dei video scaricati</string> |     <string name="download_path_title">Percorso dei video scaricati</string> | ||||||
|     <string name="download_path_summary">Cartella in cui memorizzare i video scaricati.</string> |     <string name="download_path_summary">Percorso in cui memorizzare i video scaricati</string> | ||||||
|     <string name="download_path_dialog_title">Inserisci il percorso per i download</string> |     <string name="download_path_dialog_title">Inserisci il percorso per i download</string> | ||||||
|     <string name="default_resolution_title">Risoluzione predefinita</string> |     <string name="default_resolution_title">Risoluzione predefinita</string> | ||||||
|     <string name="play_with_kodi_title">Riproduci con Kodi</string> |     <string name="play_with_kodi_title">Riproduci con Kodi</string> | ||||||
|     <string name="kore_not_found">L\'applicazione Kore non è stata trovata. Vorresti installarla?</string> |     <string name="kore_not_found">L\'applicazione Kore non è stata trovata. Vuoi installarla?</string> | ||||||
|     <string name="show_play_with_kodi_title">Mostra l\'opzione \"Riproduci con Kodi\"</string> |     <string name="show_play_with_kodi_title">Mostra l\'opzione \"Riproduci con Kodi\"</string> | ||||||
|     <string name="show_play_with_kodi_summary">Mostra l\'opzione per riprodurre i video tramite Kodi.</string> |     <string name="show_play_with_kodi_summary">Mostra l\'opzione per riprodurre i video tramite Kodi</string> | ||||||
|     <string name="play_audio">Audio</string> |     <string name="play_audio">Audio</string> | ||||||
|     <string name="default_audio_format_title">Formato audio predefinito</string> |     <string name="default_audio_format_title">Formato audio predefinito</string> | ||||||
|     <string name="webm_description">WebM — formato libero</string> |     <string name="webm_description">WebM — formato libero</string> | ||||||
| @@ -30,7 +30,7 @@ | |||||||
|     <string name="next_video_title">Prossimo video</string> |     <string name="next_video_title">Prossimo video</string> | ||||||
|     <string name="show_next_and_similar_title">Mostra video a seguire e video simili</string> |     <string name="show_next_and_similar_title">Mostra video a seguire e video simili</string> | ||||||
|     <string name="url_not_supported_toast">URL non supportato</string> |     <string name="url_not_supported_toast">URL non supportato</string> | ||||||
|     <string name="search_language_title">Lingua preferita per i contenuti</string> |     <string name="search_language_title">Lingua predefinita per i contenuti</string> | ||||||
|     <string name="settings_category_video_audio_title">Video e Audio</string> |     <string name="settings_category_video_audio_title">Video e Audio</string> | ||||||
|  |  | ||||||
|     <string name="list_thumbnail_view_description">Anteprima video</string> |     <string name="list_thumbnail_view_description">Anteprima video</string> | ||||||
| @@ -40,12 +40,12 @@ | |||||||
|     <string name="detail_likes_img_view_description">Mi piace</string> |     <string name="detail_likes_img_view_description">Mi piace</string> | ||||||
|     <string name="err_dir_create">Impossibile creare la cartella di download \'%1$s\'</string> |     <string name="err_dir_create">Impossibile creare la cartella di download \'%1$s\'</string> | ||||||
|     <string name="info_dir_created">Creata la cartella per i download \'%1$s\'</string> |     <string name="info_dir_created">Creata la cartella per i download \'%1$s\'</string> | ||||||
|     <string name="use_external_video_player_title">Usa un lettore video esterno</string> |     <string name="use_external_video_player_title">Usa un riproduttore video esterno</string> | ||||||
|     <string name="use_external_audio_player_title">Usa un lettore audio esterno</string> |     <string name="use_external_audio_player_title">Usa un riproduttore audio esterno</string> | ||||||
|  |  | ||||||
|     <string name="download_path_audio_title">Cartella degli audio scaricati</string> |     <string name="download_path_audio_title">Percorso degli audio scaricati</string> | ||||||
|     <string name="download_path_audio_summary">Cartella dove salvare gli audio scaricati.</string> |     <string name="download_path_audio_summary">Percorso dove salvare gli audio scaricati</string> | ||||||
|     <string name="download_path_audio_dialog_title">Inserisci la cartella per i file audio</string> |     <string name="download_path_audio_dialog_title">Inserisci il percorso per i file audio</string> | ||||||
|  |  | ||||||
|     <string name="theme_title">Tema</string> |     <string name="theme_title">Tema</string> | ||||||
|     <string name="dark_theme_title">Scuro</string> |     <string name="dark_theme_title">Scuro</string> | ||||||
| @@ -63,26 +63,26 @@ | |||||||
|     <string name="content_not_available">Contenuto non disponibile</string> |     <string name="content_not_available">Contenuto non disponibile</string> | ||||||
|     <string name="blocked_by_gema">Bloccato dalla GEMA</string> |     <string name="blocked_by_gema">Bloccato dalla GEMA</string> | ||||||
|     <string name="use_tor_title">Usa Tor</string> |     <string name="use_tor_title">Usa Tor</string> | ||||||
|     <string name="use_tor_summary">(Sperimentale) Forza il traffico in download tramite Tor per una maggiore privacy (lo streaming dei video non è ancora supportato).</string> |     <string name="use_tor_summary">(Sperimentale) Forza il traffico in download tramite Tor per una maggiore riservatezza (lo streaming dei video non è ancora supportato).</string> | ||||||
|  |  | ||||||
|     <string name="parsing_error">Impossibile analizzare il sito web</string> |     <string name="parsing_error">Impossibile analizzare il sito web</string> | ||||||
|     <string name="could_not_setup_download_menu">Impossibile impostare il menù di download</string> |     <string name="could_not_setup_download_menu">Impossibile impostare il menù di download</string> | ||||||
|  |  | ||||||
|  |  | ||||||
|     <string name="live_streams_not_supported">Questo è uno stream dal vivo. Gli stream dal vivo non sono ancora supportati.</string> |     <string name="live_streams_not_supported">Questo è uno stream in diretta, il quale non è ancora supportato.</string> | ||||||
|  |  | ||||||
|  |  | ||||||
|     <string name="content">Contenuti</string> |     <string name="content">Contenuti</string> | ||||||
|     <string name="show_age_restricted_content_title">Mostra contenuti vincolati all\'età</string> |     <string name="show_age_restricted_content_title">Mostra contenuti vincolati all\'età</string> | ||||||
|     <string name="video_is_age_restricted">Questo video è riservato ad un pubblico maturo. Per accedervi, abilita \"Mostra video vincolati all\'età\" nelle impostazioni.</string> |     <string name="video_is_age_restricted">Questo video è riservato ad un pubblico maggiorenne. Per accedervi, abilita \"Mostra video vincolati all\'età\" nelle impostazioni.</string> | ||||||
|  |  | ||||||
|     <string name="main_bg_subtitle">Tocca \"cerca\" per iniziare</string> |     <string name="main_bg_subtitle">Tocca \"cerca\" per iniziare</string> | ||||||
|     <string name="autoplay_by_calling_app_title">Inizia automaticamente la riproduzione se NewPipe viene aperto da un\'altra app</string> |     <string name="autoplay_by_calling_app_title">Riproduzione automatica</string> | ||||||
|     <string name="autoplay_by_calling_app_summary">Riproduci i video automaticamente quando NewPipe viene aperto da un\'altra app</string> |     <string name="autoplay_by_calling_app_summary">Riproduci i video automaticamente quando NewPipe viene aperto da un\'altra app</string> | ||||||
|     <string name="duration_live">in diretta</string> |     <string name="duration_live">in diretta</string> | ||||||
|  |  | ||||||
|     <string name="light_parsing_error">Impossibile eseguire il parsing completo del sito</string> |     <string name="light_parsing_error">Impossibile analizzare completamente il sito web</string> | ||||||
|     <string name="could_not_get_stream">Non è stato ottenuto alcuno stream</string> |     <string name="could_not_get_stream">Non è stato ottenuto alcun flusso</string> | ||||||
|     <string name="sorry_string">Ci dispiace, non sarebbe dovuto succedere.</string> |     <string name="sorry_string">Ci dispiace, non sarebbe dovuto succedere.</string> | ||||||
|     <string name="error_report_button_text">Segnala l\'errore via e-mail</string> |     <string name="error_report_button_text">Segnala l\'errore via e-mail</string> | ||||||
|     <string name="error_snackbar_message">Ci dispiace, c\'è stato qualche errore.</string> |     <string name="error_snackbar_message">Ci dispiace, c\'è stato qualche errore.</string> | ||||||
| @@ -99,18 +99,18 @@ | |||||||
|     <string name="video">Video</string> |     <string name="video">Video</string> | ||||||
|     <string name="audio">Audio</string> |     <string name="audio">Audio</string> | ||||||
|     <string name="retry">Riprova</string> |     <string name="retry">Riprova</string> | ||||||
|     <string name="storage_permission_denied">È stato negato il permesso di accedere all\'archiviazione di massa</string> |     <string name="storage_permission_denied">È stato negato il permesso di accesso all\'archiviazione di massa</string> | ||||||
|     <string name="downloads">Download</string> |     <string name="downloads">Download</string> | ||||||
|     <string name="downloads_title">Download</string> |     <string name="downloads_title">Download</string> | ||||||
|     <string name="error_report_title">Segnalazione errori</string> |     <string name="error_report_title">Segnalazione errori</string> | ||||||
|  |  | ||||||
|     <string name="start">Inizia</string> |     <string name="start">Inizia</string> | ||||||
|     <string name="pause">Pausa</string> |     <string name="pause">Pausa</string> | ||||||
|     <string name="view">Visualizza</string> |     <string name="view">Riproduci</string> | ||||||
|     <string name="delete">Elimina</string> |     <string name="delete">Elimina</string> | ||||||
|     <string name="checksum">Checksum</string> |     <string name="checksum">Codice di controllo</string> | ||||||
|  |  | ||||||
|     <string name="add">Nuova missione</string> |     <string name="add">Nuovo obiettivo</string> | ||||||
|     <string name="finish">OK</string> |     <string name="finish">OK</string> | ||||||
|  |  | ||||||
|     <string name="msg_name">Nome del file</string> |     <string name="msg_name">Nome del file</string> | ||||||
| @@ -126,7 +126,7 @@ | |||||||
|     <string name="no_available_dir">Seleziona una cartella disponibile in cui salvare i download</string> |     <string name="no_available_dir">Seleziona una cartella disponibile in cui salvare i download</string> | ||||||
|  |  | ||||||
|     <string name="could_not_load_image">Impossibile caricare l\'immagine</string> |     <string name="could_not_load_image">Impossibile caricare l\'immagine</string> | ||||||
|     <string name="app_ui_crash">L\'app/UI è andata in crash</string> |     <string name="app_ui_crash">L\'app/UI si è interrotta</string> | ||||||
|     <string name="info_labels">Cosa:\\nRichiesta:\\nLingua contenuto:\\nServizio:\\nOrario GMT:\\nPacchetto:\\nVersione:\\nVersione SO:\\nRange IP glob.:</string> |     <string name="info_labels">Cosa:\\nRichiesta:\\nLingua contenuto:\\nServizio:\\nOrario GMT:\\nPacchetto:\\nVersione:\\nVersione SO:\\nRange IP glob.:</string> | ||||||
|  |  | ||||||
|     <string name="reCaptchaActivity">reCAPTCHA</string> |     <string name="reCaptchaActivity">reCAPTCHA</string> | ||||||
| @@ -149,26 +149,26 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|     <string name="open_in_popup_mode">Apri in modalità popup</string> |     <string name="open_in_popup_mode">Apri in modalità popup</string> | ||||||
|     <string name="popup_mode_share_menu_title">NewPipe Modo popup</string> |     <string name="popup_mode_share_menu_title">NewPipe in modalità popup</string> | ||||||
|  |  | ||||||
|     <string name="popup_playing_toast">Riproduzione in modalità popup</string> |     <string name="popup_playing_toast">Riproduzione in modalità popup</string> | ||||||
|     <string name="disabled">Disattivato</string> |     <string name="disabled">Disattivato</string> | ||||||
|  |  | ||||||
|     <string name="use_old_player_title">Usa il vecchio riproduttore</string> |     <string name="use_old_player_title">Usa il vecchio riproduttore</string> | ||||||
| <string name="use_external_video_player_summary">Alcune risoluzioni non avranno audio se questa opzione viene abilitata.</string> | <string name="use_external_video_player_summary">Alcune risoluzioni NON avranno l\'audio se questa opzione viene abilitata</string> | ||||||
|     <string name="controls_background_title">In sottofondo</string> |     <string name="controls_background_title">In sottofondo</string> | ||||||
|     <string name="controls_popup_title">Popup</string> |     <string name="controls_popup_title">Popup</string> | ||||||
|  |  | ||||||
|     <string name="default_popup_resolution_title">Risoluzione predefinita per il popup</string> |     <string name="default_popup_resolution_title">Risoluzione predefinita per la modalità popup</string> | ||||||
|     <string name="show_higher_resolutions_title">Mostra risoluzioni più alte</string> |     <string name="show_higher_resolutions_title">Mostra risoluzioni più alte</string> | ||||||
|     <string name="show_higher_resolutions_summary">Solo alcuni dispositivi supportano la riproduzione di video in 2K e 4K.</string> |     <string name="show_higher_resolutions_summary">Solo alcuni dispositivi supportano la riproduzione di video in 2K e 4K</string> | ||||||
|     <string name="default_video_format_title">Formato video preferito</string> |     <string name="default_video_format_title">Formato video predefinito</string> | ||||||
|     <string name="popup_remember_size_pos_title">Ricorda grandezza e posizione del popup</string> |     <string name="popup_remember_size_pos_title">Ricorda grandezza e posizione del popup</string> | ||||||
|     <string name="popup_remember_size_pos_summary">Ricorda l\'ultima grandezza e posizione del popup</string> |     <string name="popup_remember_size_pos_summary">Ricorda l\'ultima grandezza e posizione del popup</string> | ||||||
|     <string name="player_gesture_controls_title">Controlli gestuali</string> |     <string name="player_gesture_controls_title">Controlli gestuali</string> | ||||||
|     <string name="player_gesture_controls_summary">Usa i gesti per controllare luminosità e volume.</string> |     <string name="player_gesture_controls_summary">Usa i gesti per controllare luminosità e volume</string> | ||||||
|     <string name="show_search_suggestions_title">Suggerimenti di ricerca</string> |     <string name="show_search_suggestions_title">Suggerimenti di ricerca</string> | ||||||
|     <string name="show_search_suggestions_summary">Mostra suggerimenti durante la ricerca.</string> |     <string name="show_search_suggestions_summary">Mostra i suggerimenti durante la ricerca</string> | ||||||
|  |  | ||||||
|     <string name="settings_category_popup_title">Popup</string> |     <string name="settings_category_popup_title">Popup</string> | ||||||
|     <string name="filter">Filtra i risultati</string> |     <string name="filter">Filtra i risultati</string> | ||||||
| @@ -177,10 +177,10 @@ | |||||||
|     <string name="popup_resizing_indicator_title">Ridimensionamento</string> |     <string name="popup_resizing_indicator_title">Ridimensionamento</string> | ||||||
|     <string name="best_resolution">Risoluzione migliore</string> |     <string name="best_resolution">Risoluzione migliore</string> | ||||||
|  |  | ||||||
|     <string name="use_old_player_summary">Precedente riproduttore integrato facente uso di Mediaframework</string> |     <string name="use_old_player_summary">Precedente riproduttore integrato Mediaframework</string> | ||||||
|  |  | ||||||
|     <string name="msg_popup_permission">Questo permesso è necessario |     <string name="msg_popup_permission">Questo permesso è necessario  | ||||||
| \nper la modalità popup</string> | \nper aprire la modalità popup</string> | ||||||
|  |  | ||||||
|     <string name="action_settings">Impostazioni</string> |     <string name="action_settings">Impostazioni</string> | ||||||
|     <string name="action_about">Informazioni</string> |     <string name="action_about">Informazioni</string> | ||||||
| @@ -206,7 +206,7 @@ | |||||||
|  |  | ||||||
|     <string name="tab_subscriptions">Iscrizioni</string> |     <string name="tab_subscriptions">Iscrizioni</string> | ||||||
|  |  | ||||||
|     <string name="fragment_whats_new">Nuovo</string> |     <string name="fragment_whats_new">Novità</string> | ||||||
|  |  | ||||||
|     <string name="enable_search_history_title">Cronologia ricerche</string> |     <string name="enable_search_history_title">Cronologia ricerche</string> | ||||||
|     <string name="enable_search_history_summary">Salva le ricerche localmente</string> |     <string name="enable_search_history_summary">Salva le ricerche localmente</string> | ||||||
| @@ -233,10 +233,10 @@ | |||||||
|     <string name="history_cleared">Cronologia cancellata</string> |     <string name="history_cleared">Cronologia cancellata</string> | ||||||
|  |  | ||||||
| <string name="tab_main">Principale</string> | <string name="tab_main">Principale</string> | ||||||
|     <string name="settings_category_player_title">Player</string> |     <string name="settings_category_player_title">Riproduttore</string> | ||||||
|     <string name="settings_category_player_behavior_title">Comportamento</string> |     <string name="settings_category_player_behavior_title">Comportamento</string> | ||||||
|     <string name="settings_category_history_title">Cronologia</string> |     <string name="settings_category_history_title">Cronologia</string> | ||||||
|     <string name="playlist">Playlist</string> |     <string name="playlist">Scaletta</string> | ||||||
|     <string name="undo">Annulla</string> |     <string name="undo">Annulla</string> | ||||||
|  |  | ||||||
|     <string name="notification_channel_name">Notifiche NewPipe</string> |     <string name="notification_channel_name">Notifiche NewPipe</string> | ||||||
| @@ -262,4 +262,7 @@ | |||||||
| </plurals> | </plurals> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Elemento eliminato</string> |     <string name="item_deleted">Elemento eliminato</string> | ||||||
|  | <string name="empty_subscription_feed_subtitle">Nulla da mostrare</string> | ||||||
|  |  | ||||||
|  |     <string name="delete_item_search_history">Vuoi eliminare questo elemento dalla cronologia?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -104,7 +104,7 @@ | |||||||
|     <string name="storage_permission_denied">Toegang tot opslag geweigerd</string> |     <string name="storage_permission_denied">Toegang tot opslag geweigerd</string> | ||||||
|     <string name="start">Begin</string> |     <string name="start">Begin</string> | ||||||
|     <string name="pause">Pauzeren</string> |     <string name="pause">Pauzeren</string> | ||||||
|     <string name="view">Bekijken</string> |     <string name="view">Afspelen</string> | ||||||
|     <string name="delete">Verwijderen</string> |     <string name="delete">Verwijderen</string> | ||||||
|     <string name="checksum">Controlesom</string> |     <string name="checksum">Controlesom</string> | ||||||
|  |  | ||||||
| @@ -260,4 +260,5 @@ te openen in pop-upmodus</string> | |||||||
| </plurals> | </plurals> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Item verwijderd</string> |     <string name="item_deleted">Item verwijderd</string> | ||||||
|  | <string name="delete_item_search_history">Wil je dit item uit je geschiedenis verwijderen?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ | |||||||
|     <string name="what_device_headline">Informações:</string> |     <string name="what_device_headline">Informações:</string> | ||||||
|     <string name="webm_description">WebM — formato aberto</string> |     <string name="webm_description">WebM — formato aberto</string> | ||||||
|     <string name="view_count_text">%1$s visualizações</string> |     <string name="view_count_text">%1$s visualizações</string> | ||||||
|     <string name="view">Ver</string> |     <string name="view">Reproduzir</string> | ||||||
|     <string name="video_is_age_restricted">Vídeo com restrição de idade. Permissão para vídeos com essa restrição podem ser feitas no menu configurações.</string> |     <string name="video_is_age_restricted">Vídeo com restrição de idade. Permissão para vídeos com essa restrição podem ser feitas no menu configurações.</string> | ||||||
|     <string name="video">Vídeo</string> |     <string name="video">Vídeo</string> | ||||||
|     <string name="autoplay_by_calling_app_summary">Reproduzir o vídeo automaticamente quando o NewPipe for aberto a partir de outro app</string> |     <string name="autoplay_by_calling_app_summary">Reproduzir o vídeo automaticamente quando o NewPipe for aberto a partir de outro app</string> | ||||||
| @@ -95,7 +95,7 @@ | |||||||
|     <string name="light_parsing_error">Não foi possível interpretar completamente o site</string> |     <string name="light_parsing_error">Não foi possível interpretar completamente o site</string> | ||||||
|     <string name="list_thumbnail_view_description">Miniatura do vídeo</string> |     <string name="list_thumbnail_view_description">Miniatura do vídeo</string> | ||||||
|     <string name="live_streams_not_supported">Isto é uma transmissão ao vivo, a qual ainda não é suportada.</string> |     <string name="live_streams_not_supported">Isto é uma transmissão ao vivo, a qual ainda não é suportada.</string> | ||||||
|     <string name="main_bg_subtitle">Toque em busca para começar</string> |     <string name="main_bg_subtitle">Toque em pesquisar para começar</string> | ||||||
|     <string name="msg_exists">Arquivo já existe</string> |     <string name="msg_exists">Arquivo já existe</string> | ||||||
|     <string name="msg_threads">Threads</string> |     <string name="msg_threads">Threads</string> | ||||||
|     <string name="msg_url_malform">URL inválida ou internet indisponível</string> |     <string name="msg_url_malform">URL inválida ou internet indisponível</string> | ||||||
| @@ -233,12 +233,13 @@ abrir em modo popup</string> | |||||||
|  |  | ||||||
|     <string name="no_videos">Nenhum video</string> |     <string name="no_videos">Nenhum video</string> | ||||||
|     <plurals name="videos"> |     <plurals name="videos"> | ||||||
| 	<item quantity="one">%s video</item> | 	<item quantity="one">%s vídeo</item> | ||||||
| 	<item quantity="other">%s videos</item> | 	<item quantity="other">%s vídeos</item> | ||||||
| </plurals> | </plurals> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Item excluído</string> |     <string name="item_deleted">Item excluído</string> | ||||||
| <string name="settings_category_player_title">Player</string> | <string name="settings_category_player_title">Reprodutor</string> | ||||||
|     <string name="empty_subscription_feed_subtitle">Não há nada aqui</string> |     <string name="empty_subscription_feed_subtitle">Não há nada aqui</string> | ||||||
|  |  | ||||||
|     </resources> |     <string name="delete_item_search_history">Deseja apagar este item do seu histórico de busca?</string> | ||||||
|  | </resources> | ||||||
|   | |||||||
| @@ -21,18 +21,18 @@ | |||||||
|     <string name="download_path_dialog_title">Introduza o caminho para os vídeos</string> |     <string name="download_path_dialog_title">Introduza o caminho para os vídeos</string> | ||||||
|     <string name="default_resolution_title">Resolução padrão</string> |     <string name="default_resolution_title">Resolução padrão</string> | ||||||
|     <string name="play_with_kodi_title">Reproduzir no Kodi</string> |     <string name="play_with_kodi_title">Reproduzir no Kodi</string> | ||||||
|     <string name="kore_not_found">Aplicação não encontrada. Instalar o Kore?</string> |     <string name="kore_not_found">Aplicação Kore não encontrada. Quer instalá-la?</string> | ||||||
|     <string name="show_play_with_kodi_title">Mostrar opção \"Reproduzir no Kodi\"</string> |     <string name="show_play_with_kodi_title">Mostrar opção \"Reproduzir no Kodi\"</string> | ||||||
|     <string name="show_play_with_kodi_summary">Mostra uma opção para reproduzir o vídeo no Kodi</string> |     <string name="show_play_with_kodi_summary">Mostra uma opção para reproduzir o vídeo no Kodi</string> | ||||||
|     <string name="play_audio">Áudio</string> |     <string name="play_audio">Áudio</string> | ||||||
|     <string name="default_audio_format_title">Formato áudio padrão</string> |     <string name="default_audio_format_title">Formato áudio padrão</string> | ||||||
|     <string name="webm_description">WebM — formato livre</string> |     <string name="webm_description">WebM — formato livre</string> | ||||||
|     <string name="m4a_description">m4a — melhor qualidade</string> |     <string name="m4a_description">M4A — melhor qualidade</string> | ||||||
|     <string name="download_dialog_title">Transferir</string> |     <string name="download_dialog_title">Transferir</string> | ||||||
|     <string name="next_video_title">Vídeo seguinte</string> |     <string name="next_video_title">Vídeo seguinte</string> | ||||||
|     <string name="show_next_and_similar_title">Mostrar vídeos seguintes e semelhantes</string> |     <string name="show_next_and_similar_title">Mostrar vídeos seguintes e semelhantes</string> | ||||||
|     <string name="url_not_supported_toast">URL não suportado</string> |     <string name="url_not_supported_toast">URL não suportado</string> | ||||||
|     <string name="search_language_title">Idioma preferido do conteúdo</string> |     <string name="search_language_title">Idioma predefinido do conteúdo</string> | ||||||
|     <string name="settings_category_video_audio_title">Vídeo e áudio</string> |     <string name="settings_category_video_audio_title">Vídeo e áudio</string> | ||||||
|  |  | ||||||
|     <string name="list_thumbnail_view_description">Miniatura de vídeos</string> |     <string name="list_thumbnail_view_description">Miniatura de vídeos</string> | ||||||
| @@ -60,19 +60,19 @@ | |||||||
|     <string name="info_dir_created">Diretório \'%1$s\' criado com sucesso</string> |     <string name="info_dir_created">Diretório \'%1$s\' criado com sucesso</string> | ||||||
|     <string name="general_error">Erro</string> |     <string name="general_error">Erro</string> | ||||||
|     <string name="could_not_load_thumbnails">Incapaz de carregar todas as miniaturas</string> |     <string name="could_not_load_thumbnails">Incapaz de carregar todas as miniaturas</string> | ||||||
|     <string name="youtube_signature_decryption_error">Incapaz de decodificar a assinatura do vídeo.</string> |     <string name="youtube_signature_decryption_error">Incapaz de decodificar a assinatura do vídeo</string> | ||||||
|     <string name="parsing_error">Incapaz de processar o sítio web.</string> |     <string name="parsing_error">Incapaz de processar o sítio da web</string> | ||||||
|     <string name="content_not_available">Conteúdo não disponível.</string> |     <string name="content_not_available">Conteúdo não disponível</string> | ||||||
|     <string name="blocked_by_gema">Bloqueado pela GEMA.</string> |     <string name="blocked_by_gema">Bloqueado pela GEMA</string> | ||||||
|  |  | ||||||
|     <string name="content">Conteúdo</string> |     <string name="content">Conteúdo</string> | ||||||
|     <string name="show_age_restricted_content_title">Restringir conteúdo por idade</string> |     <string name="show_age_restricted_content_title">Restringir conteúdo por idade</string> | ||||||
|     <string name="video_is_age_restricted">O vídeo está restrito por idade. Ative a restrição de vídeos por idade nas definições.</string> |     <string name="video_is_age_restricted">Vídeo com restrição de idade. É possível permitir este material através das Definições.</string> | ||||||
|  |  | ||||||
|     <string name="light_parsing_error">Não foi possível processar o sítio web.</string> |     <string name="light_parsing_error">Não foi possível processar o sítio da web</string> | ||||||
|     <string name="could_not_setup_download_menu">Não foi possível configurar o menu de transferências.</string> |     <string name="could_not_setup_download_menu">Não foi possível configurar o menu de transferências</string> | ||||||
|     <string name="live_streams_not_supported">Esta é uma EMISSÃO EM DIRETO. Estas emissões ainda não são suportadas.</string> |     <string name="live_streams_not_supported">Esta é uma EMISSÃO EM DIRETO, as quais ainda não são suportadas.</string> | ||||||
|     <string name="could_not_get_stream">Não foi possível obter a emissão.</string> |     <string name="could_not_get_stream">Não foi possível obter a emissão</string> | ||||||
|     <string name="sorry_string">Desculpe, isto não deveria ter acontecido.</string> |     <string name="sorry_string">Desculpe, isto não deveria ter acontecido.</string> | ||||||
|     <string name="error_report_button_text">Reportar erro por e-mail</string> |     <string name="error_report_button_text">Reportar erro por e-mail</string> | ||||||
|     <string name="error_snackbar_message">Ocorreram alguns erros.</string> |     <string name="error_snackbar_message">Ocorreram alguns erros.</string> | ||||||
| @@ -86,9 +86,9 @@ | |||||||
|     <string name="video">Vídeo</string> |     <string name="video">Vídeo</string> | ||||||
|     <string name="audio">Áudio</string> |     <string name="audio">Áudio</string> | ||||||
|     <string name="retry">Tentar novamente</string> |     <string name="retry">Tentar novamente</string> | ||||||
|     <string name="storage_permission_denied">Não foi concedida permissão para aceder ao armazenamento</string> |     <string name="storage_permission_denied">Permissão para aceder ao armazenamento foi negada</string> | ||||||
|     <string name="main_bg_subtitle">Toque para iniciar a pesquisa</string> |     <string name="main_bg_subtitle">Toque para iniciar a pesquisa</string> | ||||||
|     <string name="autoplay_by_calling_app_title">Reproduzir se invocado por outra aplicação</string> |     <string name="autoplay_by_calling_app_title">Reprodução automática</string> | ||||||
|     <string name="autoplay_by_calling_app_summary">Reproduzir vídeo automaticamente se o NewPipe for invocado por outra aplicação</string> |     <string name="autoplay_by_calling_app_summary">Reproduzir vídeo automaticamente se o NewPipe for invocado por outra aplicação</string> | ||||||
|     <string name="duration_live">direto</string> |     <string name="duration_live">direto</string> | ||||||
|  |  | ||||||
| @@ -101,7 +101,7 @@ | |||||||
|  |  | ||||||
|     <string name="start">Iniciar</string> |     <string name="start">Iniciar</string> | ||||||
|     <string name="pause">Pausa</string> |     <string name="pause">Pausa</string> | ||||||
|     <string name="view">Ver</string> |     <string name="view">Reproduzir</string> | ||||||
|     <string name="delete">Apagar</string> |     <string name="delete">Apagar</string> | ||||||
|     <string name="checksum">Checksum</string> |     <string name="checksum">Checksum</string> | ||||||
|  |  | ||||||
| @@ -115,8 +115,8 @@ | |||||||
|     <string name="msg_url_malform">URL inválido ou Internet não disponível</string> |     <string name="msg_url_malform">URL inválido ou Internet não disponível</string> | ||||||
|     <string name="msg_running_detail">Toque para detalhes</string> |     <string name="msg_running_detail">Toque para detalhes</string> | ||||||
|     <string name="msg_wait">Por favor aguarde…</string> |     <string name="msg_wait">Por favor aguarde…</string> | ||||||
|     <string name="msg_copied">Copiado para a área de transferência.</string> |     <string name="msg_copied">Copiado para a área de transferência</string> | ||||||
|     <string name="no_available_dir">Por favor selecione um diretório disponível.</string> |     <string name="no_available_dir">Por favor selecione um diretório disponível para download</string> | ||||||
|  |  | ||||||
|     <string name="finish">OK</string> |     <string name="finish">OK</string> | ||||||
|     <string name="msg_threads">Processos</string> |     <string name="msg_threads">Processos</string> | ||||||
| @@ -145,11 +145,11 @@ o modo “popup“</string> | |||||||
|     <string name="reCaptcha_title">Desafio reCAPTCHA</string> |     <string name="reCaptcha_title">Desafio reCAPTCHA</string> | ||||||
|     <string name="recaptcha_request_toast">Desafio reCAPTCHA solicitado</string> |     <string name="recaptcha_request_toast">Desafio reCAPTCHA solicitado</string> | ||||||
|  |  | ||||||
|     <string name="popup_mode_share_menu_title">Modo popup de NewPipe</string> |     <string name="popup_mode_share_menu_title">Modo popup do NewPipe</string> | ||||||
|  |  | ||||||
|     <string name="popup_playing_toast">Reproduzir em modo de popup</string> |     <string name="popup_playing_toast">Reproduzir em modo de popup</string> | ||||||
|     <string name="use_old_player_title">Usar reprodutor antigo</string> |     <string name="use_old_player_title">Usar reprodutor antigo</string> | ||||||
|     <string name="use_old_player_summary">Versão antiga no reprodutor Mediaframework.</string> |     <string name="use_old_player_summary">Versão antiga do reprodutor Mediaframework</string> | ||||||
|     <string name="default_video_format_title">Formato de vídeo preferido</string> |     <string name="default_video_format_title">Formato de vídeo preferido</string> | ||||||
|     <string name="disabled">Desativado</string> |     <string name="disabled">Desativado</string> | ||||||
|  |  | ||||||
| @@ -186,10 +186,73 @@ o modo “popup“</string> | |||||||
|     <string name="tab_about">Sobre</string> |     <string name="tab_about">Sobre</string> | ||||||
|     <string name="tab_contributors">Colaboradores</string> |     <string name="tab_contributors">Colaboradores</string> | ||||||
|     <string name="tab_licenses">Licenças</string> |     <string name="tab_licenses">Licenças</string> | ||||||
|     <string name="app_description">Aplicação leve, simples e grátis de Youtube para Android.</string> |     <string name="app_description">Aplicação leve, simples e grátis de YouTube para Android.</string> | ||||||
|     <string name="view_on_github">Ver no Github</string> |     <string name="view_on_github">Ver no GitHub</string> | ||||||
|     <string name="app_license_title">Licença do NewPipe</string> |     <string name="app_license_title">Licença do NewPipe</string> | ||||||
|     <string name="contribution_encouragement">Se tem ideias, tradução, alterações de design, limpeza de código ou alterações de código pesado, ajuda é sempre bem-vinda. Quanto mais se faz melhor fica!</string> |     <string name="contribution_encouragement">Se tem ideias de tradução, alterações de design, limpeza de código ou alterações de código pesado—ajuda é sempre bem-vinda. Quanto mais se faz melhor fica!</string> | ||||||
|     <string name="read_full_license">Ler licença</string> |     <string name="read_full_license">Ler licença</string> | ||||||
|     <string name="contribution_title">Contribuição</string> |     <string name="contribution_title">Contribuição</string> | ||||||
|  | <string name="subscribe_button_title">Subscrever</string> | ||||||
|  |     <string name="subscribed_button_title">Subscrito</string> | ||||||
|  |     <string name="channel_unsubscribed">Canal não subscrito</string> | ||||||
|  |     <string name="subscription_change_failed">Incapaz de alterar a subscrição</string> | ||||||
|  |     <string name="subscription_update_failed">Incapaz de atualizar a subscrição</string> | ||||||
|  |  | ||||||
|  |     <string name="tab_main">Principal</string> | ||||||
|  |     <string name="tab_subscriptions">Subscrições</string> | ||||||
|  |  | ||||||
|  |     <string name="fragment_whats_new">O que há de novo</string> | ||||||
|  |  | ||||||
|  |     <string name="enable_search_history_title">Histórico de Pesquisa</string> | ||||||
|  |     <string name="enable_search_history_summary">Armazenar termos de pesquisa localmente</string> | ||||||
|  |     <string name="enable_watch_history_title">Histórico</string> | ||||||
|  |     <string name="enable_watch_history_summary">Armazenar histórico de vídeos assistidos</string> | ||||||
|  |     <string name="resume_on_audio_focus_gain_title">Retomar reprodução ao ganhar foco</string> | ||||||
|  |     <string name="resume_on_audio_focus_gain_summary">Continuar reprodução após interrupções (ex. chamadas)</string> | ||||||
|  |     <string name="settings_category_player_title">Reprodutor de vídeo</string> | ||||||
|  |     <string name="settings_category_player_behavior_title">Comportamento</string> | ||||||
|  |     <string name="settings_category_history_title">Histórico</string> | ||||||
|  |     <string name="playlist">Lista de Reprodução</string> | ||||||
|  |     <string name="undo">Desfazer</string> | ||||||
|  |  | ||||||
|  |     <string name="notification_channel_name">Notificação do NewPipe</string> | ||||||
|  |     <string name="notification_channel_description">Notificações do NewPipe em Segundo Plano e Reprodutores de Vídeo em Popup</string> | ||||||
|  |  | ||||||
|  |     <string name="search_no_results">Sem resultados</string> | ||||||
|  |     <string name="empty_subscription_feed_subtitle">Aqui não há nada para ver</string> | ||||||
|  |  | ||||||
|  |     <string name="no_subscribers">Sem subscritores</string> | ||||||
|  |     <plurals name="subscribers"> | ||||||
|  | 	<item quantity="one">%s subscrito</item> | ||||||
|  | 	<item quantity="other">%s subscritos</item> | ||||||
|  | </plurals> | ||||||
|  |  | ||||||
|  |     <string name="no_views">Sem visualizações</string> | ||||||
|  |     <plurals name="views"> | ||||||
|  | 	<item quantity="one">%s visualização</item> | ||||||
|  | 	<item quantity="other">%s visualizações</item> | ||||||
|  | </plurals> | ||||||
|  |  | ||||||
|  |     <string name="no_videos">Sem vídeos</string> | ||||||
|  |     <plurals name="videos"> | ||||||
|  | 	<item quantity="one">%s vídeo</item> | ||||||
|  | 	<item quantity="other">%s vídeos</item> | ||||||
|  | </plurals> | ||||||
|  |  | ||||||
|  |     <string name="settings_category_downloads_title">Download</string> | ||||||
|  |     <string name="settings_file_charset_title">Caracteres permitidos em nomes de ficheiros</string> | ||||||
|  |     <string name="settings_file_replacement_character_summary">Caracteres inválidos são substituídos por este valor</string> | ||||||
|  |     <string name="settings_file_replacement_character_title">Caracter de substituição</string> | ||||||
|  |  | ||||||
|  |     <string name="charset_letters_and_digits">Letras e dígitos</string> | ||||||
|  |     <string name="charset_most_special_characters">Caracteres mais especiais</string> | ||||||
|  |  | ||||||
|  |     <string name="title_activity_history">Histórico</string> | ||||||
|  |     <string name="title_history_search">Procurados</string> | ||||||
|  |     <string name="title_history_view">Visualizado</string> | ||||||
|  |     <string name="history_disabled">Histórico está desativado</string> | ||||||
|  |     <string name="action_history">Histórico</string> | ||||||
|  |     <string name="history_empty">O histórico está vazio</string> | ||||||
|  |     <string name="history_cleared">Histórico eliminado</string> | ||||||
|  |     <string name="item_deleted">Objeto eliminado</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -79,7 +79,7 @@ | |||||||
|     <string name="general_error">Ошибка</string> |     <string name="general_error">Ошибка</string> | ||||||
|     <string name="your_comment">Ваш комментарий (на английском):</string> |     <string name="your_comment">Ваш комментарий (на английском):</string> | ||||||
|     <string name="err_dir_create">Невозможно создать папку для загрузки \'%1$s\'</string> |     <string name="err_dir_create">Невозможно создать папку для загрузки \'%1$s\'</string> | ||||||
|     <string name="autoplay_by_calling_app_title">Автоматически воспроизводить при открытии из другого приложения</string> |     <string name="autoplay_by_calling_app_title">Воспроизводить автоматически</string> | ||||||
|     <string name="autoplay_by_calling_app_summary">Автоматически воспроизводить видео при вызове NewPipe из другого приложения</string> |     <string name="autoplay_by_calling_app_summary">Автоматически воспроизводить видео при вызове NewPipe из другого приложения</string> | ||||||
|     <string name="content">Контент</string> |     <string name="content">Контент</string> | ||||||
|     <string name="video_is_age_restricted">Видео с возрастными ограничениями. Разрешить подобный контент можно в настройках.</string> |     <string name="video_is_age_restricted">Видео с возрастными ограничениями. Разрешить подобный контент можно в настройках.</string> | ||||||
| @@ -96,10 +96,10 @@ | |||||||
|     <string name="live_streams_not_supported">Это прямая трансляция, они пока не поддерживаются.</string> |     <string name="live_streams_not_supported">Это прямая трансляция, они пока не поддерживаются.</string> | ||||||
|     <string name="could_not_load_image">Не удалось загрузить изображение</string> |     <string name="could_not_load_image">Не удалось загрузить изображение</string> | ||||||
|     <string name="app_ui_crash">"Падение приложения/пользовательского интерфейса "</string> |     <string name="app_ui_crash">"Падение приложения/пользовательского интерфейса "</string> | ||||||
|     <string name="sorry_string">Простите, такое не должно было произойти.</string> |     <string name="sorry_string">Простите, это не должно было произойти.</string> | ||||||
|     <string name="error_report_button_text">Отправить отчёт об ошибке по электронной почте</string> |     <string name="error_report_button_text">Отправить отчёт об ошибке по электронной почте</string> | ||||||
|     <string name="error_snackbar_message">Простите, произошли ошибки.</string> |     <string name="error_snackbar_message">Простите, произошли ошибки.</string> | ||||||
|     <string name="error_snackbar_action">ОТЧЕТ</string> |     <string name="error_snackbar_action">ОТЧЁТ</string> | ||||||
|     <string name="what_device_headline">Информация:</string> |     <string name="what_device_headline">Информация:</string> | ||||||
|     <string name="what_happened_headline">Что произошло:</string> |     <string name="what_happened_headline">Что произошло:</string> | ||||||
|     <string name="error_details_headline">Детали:</string> |     <string name="error_details_headline">Детали:</string> | ||||||
| @@ -223,4 +223,38 @@ | |||||||
|     <string name="history_empty">История пуста</string> |     <string name="history_empty">История пуста</string> | ||||||
|     <string name="history_cleared">История очищена</string> |     <string name="history_cleared">История очищена</string> | ||||||
|  |  | ||||||
|  | <string name="settings_category_player_title">Плеер</string> | ||||||
|  |     <string name="settings_category_player_behavior_title">Поведение</string> | ||||||
|  |     <string name="settings_category_history_title">История</string> | ||||||
|  |     <string name="playlist">Плейлист</string> | ||||||
|  |     <string name="undo">Отменить</string> | ||||||
|  |  | ||||||
|  |     <string name="search_no_results">Нет результатов</string> | ||||||
|  |     <string name="empty_subscription_feed_subtitle">Тут только сверчки</string> | ||||||
|  |  | ||||||
|  |     <string name="no_subscribers">Нет подписчиков</string> | ||||||
|  |     <plurals name="subscribers"> | ||||||
|  | 	<item quantity="one">%s подписчик</item> | ||||||
|  | 	<item quantity="few">%s подписчика</item> | ||||||
|  | 	<item quantity="many">%s подписчиков</item> | ||||||
|  | 	<item quantity="other"/> | ||||||
|  | </plurals> | ||||||
|  |  | ||||||
|  |     <string name="no_views">Нет просмотров</string> | ||||||
|  |     <plurals name="views"> | ||||||
|  | 	<item quantity="one">%s просмотр</item> | ||||||
|  | 	<item quantity="few">%s просмотра</item> | ||||||
|  | 	<item quantity="many">%s просмотров</item> | ||||||
|  | 	<item quantity="other"/> | ||||||
|  | </plurals> | ||||||
|  |  | ||||||
|  |     <string name="no_videos">Нет видео</string> | ||||||
|  |     <plurals name="videos"> | ||||||
|  | 	<item quantity="one">%s видео</item> | ||||||
|  | 	<item quantity="few">%s видео</item> | ||||||
|  | 	<item quantity="many">%s видео</item> | ||||||
|  | 	<item quantity="other"/> | ||||||
|  | </plurals> | ||||||
|  |  | ||||||
|  |     <string name="item_deleted">Элемент удалён</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ | |||||||
|     <string name="main_bg_subtitle">Začnite z iskanjem</string> |     <string name="main_bg_subtitle">Začnite z iskanjem</string> | ||||||
|     <string name="start">Začni</string> |     <string name="start">Začni</string> | ||||||
|     <string name="pause">Premor</string> |     <string name="pause">Premor</string> | ||||||
|     <string name="view">Poglej</string> |     <string name="view">Predvajaj</string> | ||||||
|     <string name="delete">Izbriši</string> |     <string name="delete">Izbriši</string> | ||||||
|     <string name="checksum">Nadzorna vsota</string> |     <string name="checksum">Nadzorna vsota</string> | ||||||
|  |  | ||||||
| @@ -272,4 +272,5 @@ odpiranje v pojavnem načinu</string> | |||||||
| </plurals> | </plurals> | ||||||
|  |  | ||||||
|     <string name="item_deleted">Predmet je izbrisan</string> |     <string name="item_deleted">Predmet je izbrisan</string> | ||||||
|  | <string name="delete_item_search_history">Ali želite izbrisati predmet iz zgodovine iskanja?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -100,7 +100,7 @@ | |||||||
|     <string name="autoplay_by_calling_app_summary">Аутоматско пуштање видеа по позиву друге апликације</string> |     <string name="autoplay_by_calling_app_summary">Аутоматско пуштање видеа по позиву друге апликације</string> | ||||||
|     <string name="start">Почни</string> |     <string name="start">Почни</string> | ||||||
|     <string name="pause">Паузирај</string> |     <string name="pause">Паузирај</string> | ||||||
|     <string name="view">Приказ</string> |     <string name="view">Пусти</string> | ||||||
|     <string name="delete">Обриши</string> |     <string name="delete">Обриши</string> | ||||||
|     <string name="checksum">Хеш</string> |     <string name="checksum">Хеш</string> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -97,7 +97,7 @@ | |||||||
|  |  | ||||||
|     <string name="start">Başlat</string> |     <string name="start">Başlat</string> | ||||||
|     <string name="pause">Duraklat</string> |     <string name="pause">Duraklat</string> | ||||||
|     <string name="view">Görünüm</string> |     <string name="view">Oynat</string> | ||||||
|     <string name="delete">Sil</string> |     <string name="delete">Sil</string> | ||||||
|     <string name="checksum">Sağlama</string> |     <string name="checksum">Sağlama</string> | ||||||
|  |  | ||||||
| @@ -255,4 +255,5 @@ | |||||||
|     <string name="history_empty">Geçmiş boş</string> |     <string name="history_empty">Geçmiş boş</string> | ||||||
|     <string name="history_cleared">Geçmiş temizlendi</string> |     <string name="history_cleared">Geçmiş temizlendi</string> | ||||||
|     <string name="item_deleted">Öge silindi</string> |     <string name="item_deleted">Öge silindi</string> | ||||||
|  | <string name="delete_item_search_history">Bu içeriği arama geçmişinden silmek istiyor musunuz?</string> | ||||||
| </resources> | </resources> | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|     <string name="no_player_found">Không tìm thấy trình phát. Bạn có muốn cài đặt VLC?</string> |     <string name="no_player_found">Không tìm thấy trình phát. Bạn có muốn cài đặt VLC?</string> | ||||||
|     <string name="install">Cài đặt</string> |     <string name="install">Cài đặt</string> | ||||||
|     <string name="cancel">Hủy</string> |     <string name="cancel">Hủy</string> | ||||||
|     <string name="open_in_browser">Mở trong teinhf duyệt</string> |     <string name="open_in_browser">Mở trong trình duyệt</string> | ||||||
|     <string name="open_in_popup_mode">Mở trong chế độ popup</string> |     <string name="open_in_popup_mode">Mở trong chế độ popup</string> | ||||||
|     <string name="share">Chia sẻ</string> |     <string name="share">Chia sẻ</string> | ||||||
|     <string name="download">Tải về</string> |     <string name="download">Tải về</string> | ||||||
| @@ -179,4 +179,8 @@ | |||||||
|     <string name="search_language_title">Ngôn ngữ nội dung ưu tiên</string> |     <string name="search_language_title">Ngôn ngữ nội dung ưu tiên</string> | ||||||
|     <string name="settings_category_video_audio_title">Video & Âm thanh</string> |     <string name="settings_category_video_audio_title">Video & Âm thanh</string> | ||||||
|     <string name="settings_category_popup_title">Bật lên</string> |     <string name="settings_category_popup_title">Bật lên</string> | ||||||
|  |     <string name="enable_watch_history_title">Lịch sử</string> | ||||||
|  |     <string name="settings_category_history_title">Lịch sử</string> | ||||||
|  |     <string name="playlist">Danh sách</string> | ||||||
|  |     <string name="search_no_results">Không tìm thấy</string> | ||||||
|     </resources> |     </resources> | ||||||
|   | |||||||
| @@ -194,7 +194,7 @@ | |||||||
|     <!-- Missions --> |     <!-- Missions --> | ||||||
|     <string name="start">Start</string> |     <string name="start">Start</string> | ||||||
|     <string name="pause">Pause</string> |     <string name="pause">Pause</string> | ||||||
|     <string name="view">View</string> |     <string name="view">Play</string> | ||||||
|     <string name="delete">Delete</string> |     <string name="delete">Delete</string> | ||||||
|     <string name="checksum">Checksum</string> |     <string name="checksum">Checksum</string> | ||||||
|  |  | ||||||
| @@ -265,6 +265,7 @@ | |||||||
|     <string name="history_empty">The history is empty</string> |     <string name="history_empty">The history is empty</string> | ||||||
|     <string name="history_cleared">History cleared</string> |     <string name="history_cleared">History cleared</string> | ||||||
|     <string name="item_deleted">Item deleted</string> |     <string name="item_deleted">Item deleted</string> | ||||||
|  |     <string name="delete_item_search_history">Do you want to delete this item from search history?</string> | ||||||
|  |  | ||||||
|     <!-- Content --> |     <!-- Content --> | ||||||
|     <string name="main_page_content">Content of main page</string> |     <string name="main_page_content">Content of main page</string> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Christian Schabesberger
					Christian Schabesberger