diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d983a6f71..a003040c8 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -13,6 +13,7 @@ Do not report crashes in the GitHub issue tracker. NewPipe has an automated cras * Check whether your issue/feature is already fixed/implemented * If you are an Android/Java developer, you are always welcome to fix/implement an issue/a feature yourself. PRs welcome! * We use English for development. Issues in other languages will be closed and ignored. +* Please only add *one* issue at a time. Do not put multiple issues into one thread. ## Bug Fixing * If you want to help NewPipe to become free of bugs (this is our utopic goal for NewPipe), you can send us an email to tnp@newpipe.schabi.org to let me know that you intend to help. We'll send you further instructions. You may, on request, register at our [Sentry](https://sentry.schabi.org) instance (see section "Crash reporting" for more information. diff --git a/.github/PULL_REQUEST_TEAMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 100% rename from .github/PULL_REQUEST_TEAMPLATE.md rename to .github/PULL_REQUEST_TEMPLATE.md diff --git a/.travis.yml b/.travis.yml index fad605996..e0fcfb82b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,6 @@ android: # The SDK version used to compile NewPipe - android-26 - # Additional components - - extra-android-m2repository - script: ./gradlew -Dorg.gradle.jvmargs=-Xmx1536m assembleDebug lintDebug testDebugUnitTest licenses: diff --git a/README.md b/README.md index ff53078c7..a127b10a5 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,32 @@ +

+

NewPipe

+

A free lightweight YouTube frontend for Android.

+

+ +

+ + + + + +

+
+

ScreenshotsDescriptionFeaturesContributionDonateLicense

+
WARNING: PUTTING NEWPIPE OR ANY FORK OF IT INTO GOOGLE PLAYSTORE VIOLATES THEIR TERMS OF CONDITIONS. -# NewPipe -NewPipe: A free lightweight YouTube frontend for Android. - -[![NewPipe](app/src/main/res/mipmap-xhdpi/ic_launcher.png)](https://newpipe.schabi.org) -[![F-Droid](https://f-droid.org/wiki/images/0/06/F-Droid-button_get-it-on.png)](https://f-droid.org/packages/org.schabi.newpipe/) - - -Project status: -[![Translation Status](https://hosted.weblate.org/widgets/NewPipe/-/svg-badge.svg)](https://hosted.weblate.org/engage/NewPipe/) -[![Build Status](https://travis-ci.org/TeamNewPipe/NewPipe.svg)](https://travis-ci.org/TeamNewPipe/NewPipe) - -## Donate -![Bitcoin](https://bitcoin.org/img/icons/logotop.svg) -![BitcoinQR](assets/16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh.png) - -`16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh` - ## Screenshots -[](screenshots/screenshot_1.png) -[](screenshots/screenshot_2.png) -[](screenshots/screenshot_3.png) -[](screenshots/screenshot_4.png) -[](screenshots/screenshot_5.png) -[](screenshots/screenshot_6.png) -[](screenshots/screenshot_7.png) -[](screenshots/screenshot_8.png) -[](screenshots/screenshot_9.png) - +[](screenshots/shot_1.png) +[](screenshots/shot_2.png) +[](screenshots/shot_3.png) +[](screenshots/shot_4.png) +[](screenshots/shot_5.png) +[](screenshots/shot_6.png) +[](screenshots/shot_7.png) +[](screenshots/shot_8.png) +[](screenshots/shot_9.png) +[](screenshots/shot_10.png) ## Description @@ -39,7 +37,7 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Search videos * Display general information about a video * Watch YouTube videos -* Listen to YouTube videos (experimental) +* Listen to YouTube videos * Popup mode (floating player) * Select the streaming player to watch the video with * Download videos @@ -47,21 +45,23 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Open a video in Kodi * Show Next/Related videos * Search YouTube in a specific language -* Watch age restricted material +* Watch/Block age restricted material * Display general information about channels * Search channels * Watch videos from a channel * Orbot/Tor support (not yet directly) * 1080p/2k/4k support +* View history +* Subscribe to channels +* Search history +* Search/Watch Playlists ### Coming Features +* Multiservice support (eg. SoundCloud) * Bookmarks -* View history -* Search history -* Subscribe to channels -* Search/Watch Playlists -* Queeing videos +* Watch as queues Playlists +* Queuing videos * Subtitles support * livestream support * ... and many more @@ -75,6 +75,22 @@ The more is done the better it gets! If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md). +## Donate +If you like NewPipe we'd be happy about a donation. You can either donate via Bitcoin or BountySource. For further information about donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate/). + + + + + + + + + + + + +
BitcoinBitcoin QR Code16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
Visit NewPipe at bountysource.comCheck out how many bounties you can earn.
+ ## License [![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](http://www.gnu.org/licenses/gpl-3.0.en.html) diff --git a/app/build.gradle b/app/build.gradle index 941dc72d7..5d718ea0e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 26 - versionCode 38 - versionName "0.10.0" + versionCode 39 + versionName "0.10.1" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true @@ -26,6 +26,9 @@ android { debuggable true applicationIdSuffix ".debug" } + beta { + applicationIdSuffix ".beta" + } } lintOptions { @@ -45,7 +48,7 @@ dependencies { exclude module: 'support-annotations' } - compile 'com.github.TeamNewPipe:NewPipeExtractor:7ae274b' + compile 'com.github.TeamNewPipe:NewPipeExtractor:1df3f67' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.10.19' @@ -62,7 +65,7 @@ dependencies { compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' compile 'de.hdodenhof:circleimageview:2.1.0' compile 'com.github.nirhart:parallaxscroll:1.0' - compile 'com.nononsenseapps:filepicker:3.0.0' + compile 'com.nononsenseapps:filepicker:3.0.1' compile 'com.google.android.exoplayer:exoplayer:r2.5.1' debugCompile 'com.facebook.stetho:stetho:1.5.0' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index d38a631a2..1b2ac6835 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -24,4 +24,14 @@ -dontwarn org.mozilla.javascript.tools.** -dontwarn android.arch.util.paging.CountedDataSource --dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource \ No newline at end of file +-dontwarn android.arch.persistence.room.paging.LimitOffsetDataSource + + +# Rules for icepick. Copy paste from https://github.com/frankiesardo/icepick +-dontwarn icepick.** +-keep class icepick.** { *; } +-keep class **$$Icepick { *; } +-keepclasseswithmembernames class * { + @icepick.* ; +} +-keepnames class * { @icepick.State *;} diff --git a/app/src/beta/AndroidManifest.xml b/app/src/beta/AndroidManifest.xml new file mode 100644 index 000000000..dd390a318 --- /dev/null +++ b/app/src/beta/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/beta/res/mipmap-hdpi/ic_launcher.png b/app/src/beta/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..73c65771d Binary files /dev/null and b/app/src/beta/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/beta/res/mipmap-mdpi/ic_launcher.png b/app/src/beta/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..6f5eeeee1 Binary files /dev/null and b/app/src/beta/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/beta/res/mipmap-xhdpi/ic_launcher.png b/app/src/beta/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..e871a3ea9 Binary files /dev/null and b/app/src/beta/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/beta/res/mipmap-xxhdpi/ic_launcher.png b/app/src/beta/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..e38901a86 Binary files /dev/null and b/app/src/beta/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..1cf7e64be Binary files /dev/null and b/app/src/beta/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 826ae4f44..8ae994de7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -88,10 +88,14 @@ + android:theme="@style/FilePickerThemeDark"> + + + + + > entry : con.getHeaderFields().entrySet()) { - System.err.println(entry.getKey() + ": " + entry.getValue()); - } - String inputLine; + String inputLine; while ((inputLine = in.readLine()) != null) { response.append(inputLine); } diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index 434a34c7e..ce9c3802f 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -43,9 +43,8 @@ public class RouterActivity extends AppCompatActivity { } protected void handleUrl(String url) { - try { - NavigationHelper.openByLink(this, url); - } catch (Exception e) { + boolean success = NavigationHelper.openByLink(this, url); + if (!success) { Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show(); } diff --git a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java index 921ce63a1..70799d971 100644 --- a/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java +++ b/app/src/main/java/org/schabi/newpipe/database/history/dao/SearchHistoryDAO.java @@ -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.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.TABLE_NAME; @@ -27,11 +28,20 @@ public interface SearchHistoryDAO extends HistoryDAO { @Override int deleteAll(); + @Query("DELETE FROM " + TABLE_NAME + " WHERE " + SEARCH + " = :query") + int deleteAllWhereQuery(String query); + @Query("SELECT * FROM " + TABLE_NAME + ORDER_BY_CREATION_DATE) @Override Flowable> getAll(); + @Query("SELECT * FROM " + TABLE_NAME + " GROUP BY " + SEARCH + ORDER_BY_CREATION_DATE + " LIMIT :limit") + Flowable> getUniqueEntries(int limit); + @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SERVICE_ID + " = :serviceId" + ORDER_BY_CREATION_DATE) @Override Flowable> listByService(int serviceId); + + @Query("SELECT * FROM " + TABLE_NAME + " WHERE " + SEARCH + " LIKE :query || '%' GROUP BY " + SEARCH + " LIMIT :limit") + Flowable> getSimilarEntries(String query, int limit); } diff --git a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java index 567bec309..12d1764cc 100644 --- a/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/subscription/SubscriptionEntity.java @@ -7,6 +7,7 @@ import android.arch.persistence.room.Index; import android.arch.persistence.room.PrimaryKey; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.util.Constants; import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_SERVICE_ID; import static org.schabi.newpipe.database.subscription.SubscriptionEntity.SUBSCRIPTION_TABLE; @@ -28,7 +29,7 @@ public class SubscriptionEntity { private long uid = 0; @ColumnInfo(name = SUBSCRIPTION_SERVICE_ID) - private int serviceId = -1; + private int serviceId = Constants.NO_SERVICE_ID; @ColumnInfo(name = SUBSCRIPTION_URL) private String url; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 5f954cad2..4bb0c2cca 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -65,6 +65,7 @@ import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.player.old.PlayVideoActivity; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.InfoCache; import org.schabi.newpipe.util.ListHelper; @@ -110,7 +111,7 @@ public class VideoDetailFragment extends BaseStateFragment implement private boolean wasRelatedStreamsExpanded = false; @State - protected int serviceId = -1; + protected int serviceId = Constants.NO_SERVICE_ID; @State protected String name; @State diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 34fcaf873..4baf323ff 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -8,6 +8,7 @@ import android.view.View; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.ListInfo; +import org.schabi.newpipe.util.Constants; import java.util.Queue; @@ -21,7 +22,7 @@ import io.reactivex.schedulers.Schedulers; public abstract class BaseListInfoFragment extends BaseListFragment { @State - protected int serviceId = -1; + protected int serviceId = Constants.NO_SERVICE_ID; @State protected String name; @State diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 653c50109..90d4d9741 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -2,14 +2,16 @@ package org.schabi.newpipe.fragments.list.search; import android.app.Activity; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; -import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.Nullable; 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.text.Editable; import android.text.TextUtils; @@ -25,35 +27,50 @@ import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.AdapterView; -import android.widget.AutoCompleteTextView; +import android.widget.EditText; import android.widget.TextView; +import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; 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.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchResult; +import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.list.BaseListFragment; import org.schabi.newpipe.history.HistoryListener; import org.schabi.newpipe.report.UserAction; +import org.schabi.newpipe.util.Constants; +import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.LayoutManagerSmoothScroller; 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.Iterator; import java.util.List; import java.util.Queue; +import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import icepick.State; +import io.reactivex.Flowable; import io.reactivex.Notification; import io.reactivex.Observable; +import io.reactivex.ObservableSource; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.Disposable; +import io.reactivex.functions.BiFunction; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; import io.reactivex.functions.Predicate; @@ -62,69 +79,78 @@ import io.reactivex.subjects.PublishSubject; import static org.schabi.newpipe.util.AnimationUtils.animateView; -public class SearchFragment extends BaseListFragment { +public class SearchFragment extends BaseListFragment implements BackPressable { /*////////////////////////////////////////////////////////////////////////// // 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. */ - private static final int SUGGESTIONS_DEBOUNCE = 150; //ms + private static final int SUGGESTIONS_DEBOUNCE = 120; //ms @State protected int filterItemCheckedId = -1; private SearchEngine.Filter filter = SearchEngine.Filter.ANY; @State - protected int serviceId = -1; + protected int serviceId = Constants.NO_SERVICE_ID; @State - protected String searchQuery = ""; + protected String searchQuery; + @State + protected String lastSearchedQuery; @State protected boolean wasSearchFocused = false; private int currentPage = 0; private int currentNextPage = 0; private String searchLanguage; - private boolean showSuggestions = true; + private boolean isSuggestionsEnabled = true; private PublishSubject suggestionPublisher = PublishSubject.create(); private Disposable searchDisposable; - private Disposable suggestionWorkerDisposable; + private Disposable suggestionDisposable; + private CompositeDisposable disposables = new CompositeDisposable(); private SuggestionListAdapter suggestionListAdapter; + private SearchHistoryDAO searchHistoryDAO; /*////////////////////////////////////////////////////////////////////////// // Views //////////////////////////////////////////////////////////////////////////*/ private View searchToolbarContainer; - private AutoCompleteTextView searchEditText; + private EditText searchEditText; private View searchClear; + private View suggestionsPanel; + private RecyclerView suggestionsRecyclerView; + /*////////////////////////////////////////////////////////////////////////*/ public static SearchFragment getInstance(int serviceId, String query) { SearchFragment searchFragment = new SearchFragment(); searchFragment.setQuery(serviceId, query); - searchFragment.searchOnResume(); + + if (!TextUtils.isEmpty(query)) { + searchFragment.setSearchOnResume(); + } + return searchFragment; } /** * 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() { - if (!TextUtils.isEmpty(searchQuery)) { - wasLoading.set(true); - } + private void setSearchOnResume() { + wasLoading.set(true); } /*////////////////////////////////////////////////////////////////////////// @@ -135,6 +161,16 @@ public class SearchFragment extends BaseListFragment currentPage) loadMoreItems(); @@ -175,7 +216,16 @@ public class SearchFragment extends BaseListFragment= Build.VERSION_CODES.JELLY_BEAN_MR1) { - searchEditText.setText("", false); - } else searchEditText.setText(""); - suggestionListAdapter.updateAdapter(new ArrayList()); - showSoftKeyboard(searchEditText); + searchEditText.setText(""); + suggestionListAdapter.setItems(new ArrayList()); + showKeyboardSearch(); } }); @@ -366,7 +417,9 @@ public class SearchFragment extends BaseListFragment parent, View view, int position, long id) { - if (DEBUG) { - Log.d(TAG, "onItemClick() called with: parent = [" + parent + "], view = [" + view + "], position = [" + position + "], id = [" + id + "]"); - } - String s = suggestionListAdapter.getSuggestion(position); - if (DEBUG) Log.d(TAG, "onItemClick text = " + s); - submitQuery(s); + public void onSuggestionItemSelected(SuggestionItem item) { + search(item.query); + searchEditText.setText(item.query); + } + + @Override + public void onSuggestionItemLongClick(SuggestionItem item) { + if (item.fromHistory) showDeleteSuggestionDialog(item); } }); - searchEditText.setThreshold(THRESHOLD_SUGGESTION); if (textWatcher != null) searchEditText.removeTextChangedListener(textWatcher); textWatcher = new TextWatcher() { @@ -404,32 +459,32 @@ public class SearchFragment extends BaseListFragment() { + @Override + public Integer call() throws Exception { + return searchHistoryDAO.deleteAllWhereQuery(item.query); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Consumer() { + @Override + public void accept(Integer howManyDeleted) throws Exception { + suggestionPublisher.onNext(searchEditText.getText().toString()); + } + }, new Consumer() { + @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() { - showSoftKeyboard(searchEditText); + showKeyboardSearch(); } private void initSuggestionObserver() { - if (suggestionWorkerDisposable != null) suggestionWorkerDisposable.dispose(); - final Predicate checkEnabledAndLength = new Predicate() { - @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()); - } - // Only pass through if suggestions is enabled and the query length is equal or greater than THRESHOLD_SUGGESTION - return showSuggestions && lengthCheck; - } - }; + if (DEBUG) Log.d(TAG, "initSuggestionObserver() called"); + if (suggestionDisposable != null) suggestionDisposable.dispose(); - suggestionWorkerDisposable = suggestionPublisher + final Observable observable = suggestionPublisher .debounce(SUGGESTIONS_DEBOUNCE, TimeUnit.MILLISECONDS) - .startWith(!TextUtils.isEmpty(searchQuery) ? searchQuery : "") - .filter(checkEnabledAndLength) - .switchMap(new Function>>>() { + .startWith(searchQuery != null ? searchQuery : "") + .filter(new Predicate() { @Override - public Observable>> apply(@io.reactivex.annotations.NonNull String query) throws Exception { - return ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable().materialize(); + public boolean test(@io.reactivex.annotations.NonNull String query) throws Exception { + return isSuggestionsEnabled; + } + }); + + suggestionDisposable = observable + .switchMap(new Function>>>() { + @Override + public ObservableSource>> apply(@io.reactivex.annotations.NonNull final String query) throws Exception { + final Flowable> flowable = query.length() > 0 + ? searchHistoryDAO.getSimilarEntries(query, 3) + : searchHistoryDAO.getUniqueEntries(25); + final Observable> local = flowable.toObservable() + .map(new Function, List>() { + @Override + public List apply(@io.reactivex.annotations.NonNull List searchHistoryEntries) throws Exception { + List 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> network = ExtractorHelper.suggestionsFor(serviceId, query, searchLanguage).toObservable() + .map(new Function, List>() { + @Override + public List apply(@io.reactivex.annotations.NonNull List strings) throws Exception { + List result = new ArrayList<>(); + for (String entry : strings) result.add(new SuggestionItem(false, entry)); + return result; + } + }); + + return Observable.zip(local, network, new BiFunction, List, List>() { + @Override + public List apply(@io.reactivex.annotations.NonNull List localResult, @io.reactivex.annotations.NonNull List networkResult) throws Exception { + List result = new ArrayList<>(); + if (localResult.size() > 0) result.addAll(localResult); + + // Remove duplicates + final Iterator 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()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(new Consumer>>() { + .subscribe(new Consumer>>() { @Override - public void accept(@io.reactivex.annotations.NonNull Notification> listNotification) throws Exception { + public void accept(@io.reactivex.annotations.NonNull Notification> listNotification) throws Exception { if (listNotification.isOnNext()) { handleSuggestions(listNotification.getValue()); - if (errorPanelRoot.getVisibility() == View.VISIBLE) { - hideLoading(); - } } else if (listNotification.isOnError()) { Throwable error = listNotification.getError(); - if (!ExtractorHelper.isInterruptedCaused(error)) { + if (!ExtractorHelper.hasAssignableCauseThrowable(error, + IOException.class, SocketException.class, InterruptedException.class, InterruptedIOException.class)) { onSuggestionError(error); } } @@ -513,25 +666,58 @@ public class SearchFragment extends BaseListFragment() { + @Override + public Intent call() throws Exception { + return NavigationHelper.getIntentByLink(activity, service, query); + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Consumer() { + @Override + public void accept(Intent intent) throws Exception { + getFragmentManager().popBackStackImmediate(); + activity.startActivity(intent); + } + }, new Consumer() { + @Override + public void accept(Throwable throwable) throws Exception { + showError(getString(R.string.url_not_supported_toast), false); + } + })); + return; + } + } catch (Exception e) { + // Exception occurred, it's not a url + } + + lastSearchedQuery = query; + searchQuery = query; + currentPage = 0; infoListAdapter.clearStreamItemList(); + hideSuggestionsPanel(); + hideKeyboardSearch(); if (activity instanceof HistoryListener) { ((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); } @Override public void startLoading(boolean forceLoad) { super.startLoading(forceLoad); + if (disposables != null) disposables.clear(); if (searchDisposable != null) searchDisposable.dispose(); searchDisposable = ExtractorHelper.searchFor(serviceId, searchQuery, currentPage, searchLanguage, filter) .subscribeOn(Schedulers.io()) @@ -584,7 +770,7 @@ public class SearchFragment extends BaseListFragment suggestions) { + public void handleSuggestions(@NonNull final List 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) { @@ -642,6 +829,13 @@ public class SearchFragment extends BaseListFragment 0) { infoListAdapter.addInfoItemList(result.resultList); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java new file mode 100644 index 000000000..722638926 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionItem.java @@ -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 + "]"; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java index 0a7e3d613..71d9bf780 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SuggestionListAdapter.java @@ -1,89 +1,108 @@ package org.schabi.newpipe.fragments.list.search; import android.content.Context; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.support.v4.widget.ResourceCursorAdapter; +import android.content.res.TypedArray; +import android.support.annotation.AttrRes; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; +import org.schabi.newpipe.R; + +import java.util.ArrayList; import java.util.List; -/* - * Created by Christian Schabesberger on 02.08.16. - * - * Copyright (C) Christian Schabesberger 2016 - * 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 . - */ - -/** - * {@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 class SuggestionListAdapter extends RecyclerView.Adapter { + private final ArrayList items = new ArrayList<>(); + private final Context context; + private OnSuggestionItemSelected listener; + public interface OnSuggestionItemSelected { + void onSuggestionItemSelected(SuggestionItem item); + void onSuggestionItemLongClick(SuggestionItem item); + } public SuggestionListAdapter(Context context) { - super(context, android.R.layout.simple_list_item_1, null, 0); + this.context = context; + } + + public void setItems(List items) { + this.items.clear(); + this.items.addAll(items); + notifyDataSetChanged(); + } + + public void setListener(OnSuggestionItemSelected listener) { + this.listener = listener; } @Override - public void bindView(View view, Context context, Cursor cursor) { - ViewHolder viewHolder = new ViewHolder(view); - viewHolder.suggestionTitle.setText(cursor.getString(INDEX_TITLE)); - } - - /** - * Update the suggestion list - * @param suggestions the list of suggestions - */ - public void updateAdapter(List 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); + public SuggestionItemHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new SuggestionItemHolder(LayoutInflater.from(context).inflate(R.layout.item_search_suggestion, parent, false)); } @Override - public CharSequence convertToString(Cursor cursor) { - return cursor.getString(INDEX_TITLE); + public void onBindViewHolder(SuggestionItemHolder holder, int position) { + 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 final TextView suggestionTitle; - private ViewHolder(View view) { - this.suggestionTitle = view.findViewById(android.R.id.text1); + private SuggestionItem getItem(int position) { + return items.get(position); + } + + @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; } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 4b0604bb0..f90352fa1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -178,6 +178,10 @@ public abstract class BasePlayer implements Player.EventListener, AudioManager.O if (DEBUG) Log.d(TAG, "initPlayer() called with: context = [" + context + "]"); initExoPlayerCache(); + if (audioManager == null) { + this.audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); + } + AdaptiveTrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); DefaultTrackSelector defaultTrackSelector = new DefaultTrackSelector(trackSelectionFactory); DefaultLoadControl loadControl = new DefaultLoadControl(); diff --git a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java index f93134a5d..9a43374a5 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/DownloadSettingsFragment.java @@ -7,9 +7,8 @@ import android.support.annotation.Nullable; import android.support.v7.preference.Preference; import android.util.Log; -import com.nononsenseapps.filepicker.FilePickerActivity; - import org.schabi.newpipe.R; +import org.schabi.newpipe.util.FilePickerActivityHelper; public class DownloadSettingsFragment extends BasePreferenceFragment { private static final int REQUEST_DOWNLOAD_PATH = 0x1235; @@ -48,10 +47,10 @@ public class DownloadSettingsFragment extends BasePreferenceFragment { } if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE) || preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { - Intent i = new Intent(getActivity(), FilePickerActivity.class) - .putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false) - .putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true) - .putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR); + Intent i = new Intent(getActivity(), FilePickerActivityHelper.class) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_MULTIPLE, false) + .putExtra(FilePickerActivityHelper.EXTRA_ALLOW_CREATE_DIR, true) + .putExtra(FilePickerActivityHelper.EXTRA_MODE, FilePickerActivityHelper.MODE_DIR); if (preference.getKey().equals(DOWNLOAD_PATH_PREFERENCE)) { startActivityForResult(i, REQUEST_DOWNLOAD_PATH); } else if (preference.getKey().equals(DOWNLOAD_PATH_AUDIO_PREFERENCE)) { diff --git a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java index ac70bd05f..c954211fa 100644 --- a/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/AnimationUtils.java @@ -19,7 +19,7 @@ public class AnimationUtils { private static final boolean DEBUG = MainActivity.DEBUG; 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) { @@ -95,9 +95,16 @@ public class AnimationUtils { case LIGHT_SCALE_AND_ALPHA: animateLightScaleAndAlpha(view, enterOrExit, duration, delay, execOnEnd); 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 */ @@ -237,4 +244,50 @@ public class AnimationUtils { }).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(); + } + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/Constants.java b/app/src/main/java/org/schabi/newpipe/util/Constants.java index f9329b0be..b31a95cca 100644 --- a/app/src/main/java/org/schabi/newpipe/util/Constants.java +++ b/app/src/main/java/org/schabi/newpipe/util/Constants.java @@ -9,4 +9,6 @@ public class Constants { public static final String KEY_QUERY = "key_query"; public static final String KEY_THEME_CHANGE = "key_theme_change"; + + public static final int NO_SERVICE_ID = -1; } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 5cf9f057e..0dd2c00ab 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -50,7 +50,14 @@ public final class ExtractorHelper { //no instance } + private static void checkServiceId(int serviceId) { + if(serviceId == Constants.NO_SERVICE_ID) { + throw new IllegalArgumentException("serviceId is NO_SERVICE_ID"); + } + } + public static Single searchFor(final int serviceId, final String query, final int pageNumber, final String searchLanguage, final SearchEngine.Filter filter) { + checkServiceId(serviceId); return Single.fromCallable(new Callable() { @Override public SearchResult call() throws Exception { @@ -61,6 +68,7 @@ public final class ExtractorHelper { } public static Single getMoreSearchItems(final int serviceId, final String query, final int nextPageNumber, final String searchLanguage, final SearchEngine.Filter filter) { + checkServiceId(serviceId); return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter) .map(new Function() { @Override @@ -71,6 +79,7 @@ public final class ExtractorHelper { } public static Single> suggestionsFor(final int serviceId, final String query, final String searchLanguage) { + checkServiceId(serviceId); return Single.fromCallable(new Callable>() { @Override public List call() throws Exception { @@ -80,6 +89,7 @@ public final class ExtractorHelper { } public static Single getStreamInfo(final int serviceId, final String url, boolean forceLoad) { + checkServiceId(serviceId); return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { @Override public StreamInfo call() throws Exception { @@ -89,6 +99,7 @@ public final class ExtractorHelper { } public static Single getChannelInfo(final int serviceId, final String url, boolean forceLoad) { + checkServiceId(serviceId); return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { @Override public ChannelInfo call() throws Exception { @@ -98,6 +109,7 @@ public final class ExtractorHelper { } public static Single getMoreChannelItems(final int serviceId, final String url, final String nextStreamsUrl) { + checkServiceId(serviceId); return Single.fromCallable(new Callable() { @Override public NextItemsResult call() throws Exception { @@ -107,6 +119,7 @@ public final class ExtractorHelper { } public static Single getPlaylistInfo(final int serviceId, final String url, boolean forceLoad) { + checkServiceId(serviceId); return checkCache(forceLoad, serviceId, url, Single.fromCallable(new Callable() { @Override public PlaylistInfo call() throws Exception { @@ -116,6 +129,7 @@ public final class ExtractorHelper { } public static Single getMorePlaylistItems(final int serviceId, final String url, final String nextStreamsUrl) { + checkServiceId(serviceId); return Single.fromCallable(new Callable() { @Override public NextItemsResult call() throws Exception { @@ -133,6 +147,7 @@ public final class ExtractorHelper { * and put the results in the cache. */ private static Single checkCache(boolean forceLoad, int serviceId, String url, Single loadFromNetwork) { + checkServiceId(serviceId); loadFromNetwork = loadFromNetwork.doOnSuccess(new Consumer() { @Override public void accept(@NonNull I i) throws Exception { @@ -157,6 +172,7 @@ public final class ExtractorHelper { * Default implementation uses the {@link InfoCache} to get cached results */ public static Maybe loadFromCache(final int serviceId, final String url) { + checkServiceId(serviceId); return Maybe.defer(new Callable>() { @Override public MaybeSource call() throws Exception { diff --git a/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java new file mode 100644 index 000000000..5f588c5ca --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/FilePickerActivityHelper.java @@ -0,0 +1,17 @@ +package org.schabi.newpipe.util; + +import android.os.Bundle; +import org.schabi.newpipe.R; + +public class FilePickerActivityHelper extends com.nononsenseapps.filepicker.FilePickerActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + if(ThemeHelper.isLightThemeSelected(this)) { + this.setTheme(R.style.FilePickerThemeLight); + } else { + this.setTheme(R.style.FilePickerThemeDark); + } + super.onCreate(savedInstanceState); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java b/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java new file mode 100644 index 000000000..9eca2d610 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/LayoutManagerSmoothScroller.java @@ -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; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 0a529ab4e..538675685 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -14,7 +14,9 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.about.AboutActivity; import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.stream.AudioStream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.MainFragment; @@ -228,13 +230,17 @@ public class NavigationHelper { // Link handling //////////////////////////////////////////////////////////////////////////*/ - public static void openByLink(Context context, String url) throws Exception { - Intent intentByLink = getIntentByLink(context, url); - if (intentByLink == null) - throw new NullPointerException("getIntentByLink(context = [" + context + "], url = [" + url + "]) returned null"); + public static boolean openByLink(Context context, String url) { + Intent intentByLink; + try { + intentByLink = getIntentByLink(context, url); + } catch (ExtractionException e) { + return false; + } intentByLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intentByLink.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); context.startActivity(intentByLink); + return true; } private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) { @@ -245,14 +251,20 @@ public class NavigationHelper { return mIntent; } - private static Intent getIntentByLink(Context context, String url) throws Exception { - StreamingService service = NewPipe.getServiceByUrl(url); + public static Intent getIntentByLink(Context context, String url) throws ExtractionException { + return getIntentByLink(context, NewPipe.getServiceByUrl(url), url); + } + + public static Intent getIntentByLink(Context context, StreamingService service, String url) throws ExtractionException { + if (service != ServiceList.YouTube.getService()) { + throw new ExtractionException("Service not supported at the moment"); + } int serviceId = service.getServiceId(); StreamingService.LinkType linkType = service.getLinkTypeByUrl(url); if (linkType == StreamingService.LinkType.NONE) { - throw new Exception("Url not known to service. service=" + serviceId + " url=" + url); + throw new ExtractionException("Url not known to service. service=" + serviceId + " url=" + url); } url = getCleanUrl(service, url, linkType); @@ -268,7 +280,7 @@ public class NavigationHelper { return rIntent; } - private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws Exception { + private static String getCleanUrl(StreamingService service, String dirtyUrl, StreamingService.LinkType linkType) throws ExtractionException { switch (linkType) { case STREAM: return service.getStreamUrlIdHandler().cleanUrl(dirtyUrl); diff --git a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java index bd268abf7..51dceddf3 100644 --- a/app/src/main/java/org/schabi/newpipe/util/StateSaver.java +++ b/app/src/main/java/org/schabi/newpipe/util/StateSaver.java @@ -21,6 +21,7 @@ package org.schabi.newpipe.util; import android.content.Context; +import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -29,6 +30,7 @@ import android.support.annotation.Nullable; import android.text.TextUtils; import android.util.Log; +import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.MainActivity; import java.io.File; @@ -110,6 +112,7 @@ public class StateSaver { /** * Try to restore the state from memory and disk, using the {@link StateSaver.WriteRead#readFrom(Queue)} from the writeRead. */ + @Nullable private static SavedState tryToRestore(@NonNull SavedState savedState, @NonNull WriteRead writeRead) { if (MainActivity.DEBUG) { Log.d(TAG, "tryToRestore() called with: savedState = [" + savedState + "], writeRead = [" + writeRead + "]"); @@ -117,7 +120,7 @@ public class StateSaver { FileInputStream fileInputStream = null; try { - Queue savedObjects = stateObjectsHolder.remove(savedState.prefixFileSaved); + Queue savedObjects = stateObjectsHolder.remove(savedState.getPrefixFileSaved()); if (savedObjects != null) { writeRead.readFrom(savedObjects); if (MainActivity.DEBUG) { @@ -126,8 +129,13 @@ public class StateSaver { return savedState; } - File file = new File(savedState.pathFileSaved); - if (!file.exists()) return null; + File file = new File(savedState.getPathFileSaved()); + if (!file.exists()) { + if(MainActivity.DEBUG) { + Log.d(TAG, "Cache file doesn't exist: " + file.getAbsolutePath()); + } + return null; + } fileInputStream = new FileInputStream(file); ObjectInputStream inputStream = new ObjectInputStream(fileInputStream); @@ -139,7 +147,7 @@ public class StateSaver { return savedState; } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "Failed to restore state", e); } finally { if (fileInputStream != null) { try { @@ -154,10 +162,17 @@ public class StateSaver { /** * @see #tryToSave(boolean, String, String, WriteRead) */ + @Nullable public static SavedState tryToSave(boolean isChangingConfig, @Nullable SavedState savedState, Bundle outState, WriteRead writeRead) { - String currentSavedPrefix = savedState == null || TextUtils.isEmpty(savedState.prefixFileSaved) - ? System.nanoTime() - writeRead.hashCode() + "" - : savedState.prefixFileSaved; + @NonNull + String currentSavedPrefix; + if (savedState == null || TextUtils.isEmpty(savedState.getPrefixFileSaved())) { + // Generate unique prefix + currentSavedPrefix = System.nanoTime() - writeRead.hashCode() + ""; + } else { + // Reuse prefix + currentSavedPrefix = savedState.getPrefixFileSaved(); + } savedState = tryToSave(isChangingConfig, currentSavedPrefix, writeRead.generateSuffix(), writeRead); if (savedState != null) { @@ -173,22 +188,33 @@ public class StateSaver { * to the file with the name of prefixFileName + suffixFileName, in a cache folder got from the {@link #init(Context)}. *

* It checks if the file already exists and if it does, just return the path, so a good way to save is: - *

  • A fixed prefix for the file - *
  • A changing suffix + *
      + *
    • A fixed prefix for the file
    • + *
    • A changing suffix
    • + *
    + * + * @param isChangingConfig + * @param prefixFileName + * @param suffixFileName + * @param writeRead */ + @Nullable private static SavedState tryToSave(boolean isChangingConfig, final String prefixFileName, String suffixFileName, WriteRead writeRead) { if (MainActivity.DEBUG) { Log.d(TAG, "tryToSave() called with: isChangingConfig = [" + isChangingConfig + "], prefixFileName = [" + prefixFileName + "], suffixFileName = [" + suffixFileName + "], writeRead = [" + writeRead + "]"); } - Queue savedObjects = new LinkedList<>(); + LinkedList savedObjects = new LinkedList<>(); writeRead.writeTo(savedObjects); if (isChangingConfig) { if (savedObjects.size() > 0) { stateObjectsHolder.put(prefixFileName, savedObjects); return new SavedState(prefixFileName, ""); - } else return null; + } else { + if(MainActivity.DEBUG) Log.d(TAG, "Nothing to save"); + return null; + } } FileOutputStream fileOutputStream = null; @@ -197,8 +223,12 @@ public class StateSaver { if (!cacheDir.exists()) throw new RuntimeException("Cache dir does not exist > " + cacheDirPath); cacheDir = new File(cacheDir, CACHE_DIR_NAME); if (!cacheDir.exists()) { - boolean mkdirResult = cacheDir.mkdir(); - if (!mkdirResult) return null; + if(!cacheDir.mkdir()) { + if(BuildConfig.DEBUG) { + Log.e(TAG, "Failed to create cache directory " + cacheDir.getAbsolutePath()); + } + return null; + } } if (TextUtils.isEmpty(suffixFileName)) suffixFileName = ".cache"; @@ -214,7 +244,9 @@ public class StateSaver { return name.contains(prefixFileName); } }); - for (File file1 : files) file1.delete(); + for (File fileToDelete : files) { + fileToDelete.delete(); + } } fileOutputStream = new FileOutputStream(file); @@ -223,7 +255,7 @@ public class StateSaver { return new SavedState(prefixFileName, file.getAbsolutePath()); } catch (Exception e) { - e.printStackTrace(); + Log.e(TAG, "Failed to save state", e); } finally { if (fileOutputStream != null) { try { @@ -241,11 +273,11 @@ public class StateSaver { public static void onDestroy(SavedState savedState) { if (MainActivity.DEBUG) Log.d(TAG, "onDestroy() called with: savedState = [" + savedState + "]"); - if (savedState != null && !TextUtils.isEmpty(savedState.pathFileSaved)) { - stateObjectsHolder.remove(savedState.prefixFileSaved); + if (savedState != null && !TextUtils.isEmpty(savedState.getPathFileSaved())) { + stateObjectsHolder.remove(savedState.getPrefixFileSaved()); try { //noinspection ResultOfMethodCallIgnored - new File(savedState.pathFileSaved).delete(); + new File(savedState.getPathFileSaved()).delete(); } catch (Exception ignored) { } } @@ -271,9 +303,12 @@ public class StateSaver { // Inner //////////////////////////////////////////////////////////////////////////*/ + /** + * Information about the saved state on the disk + */ public static class SavedState implements Parcelable { - public String prefixFileSaved; - public String pathFileSaved; + private final String prefixFileSaved; + private final String pathFileSaved; public SavedState(String prefixFileSaved, String pathFileSaved) { this.prefixFileSaved = prefixFileSaved; @@ -287,7 +322,7 @@ public class StateSaver { @Override public String toString() { - return prefixFileSaved + " > " + pathFileSaved; + return getPrefixFileSaved() + " > " + getPathFileSaved(); } @Override @@ -313,6 +348,22 @@ public class StateSaver { return new SavedState[size]; } }; + + /** + * Get the prefix of the saved file + * @return the file prefix + */ + public String getPrefixFileSaved() { + return prefixFileSaved; + } + + /** + * Get the path to the saved file + * @return the path to the saved file + */ + public String getPathFileSaved() { + return pathFileSaved; + } } diff --git a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java index 479a5cee3..acbd41680 100755 --- a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java +++ b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java @@ -101,6 +101,18 @@ public class DownloadManagerImpl implements DownloadManager { } + /** + * Sort a list of mission by its timestamp. Oldest first + * @param missions the missions to sort + */ + static void sortByTimestamp(List missions) { + Collections.sort(missions, new Comparator() { + @Override + public int compare(DownloadMission o1, DownloadMission o2) { + return Long.valueOf(o1.timestamp).compareTo(o2.timestamp); + } + }); + } /** * Loads finished missions from the data source @@ -111,12 +123,8 @@ public class DownloadManagerImpl implements DownloadManager { finishedMissions = new ArrayList<>(); } // Ensure its sorted - Collections.sort(finishedMissions, new Comparator() { - @Override - public int compare(DownloadMission o1, DownloadMission o2) { - return (int) (o1.timestamp - o2.timestamp); - } - }); + sortByTimestamp(finishedMissions); + mMissions.ensureCapacity(mMissions.size() + finishedMissions.size()); for (DownloadMission mission : finishedMissions) { File downloadedFile = mission.getDownloadedFile(); diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 9ba44e76f..04b10347c 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -51,6 +51,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toolbar_search_layout.xml b/app/src/main/res/layout/toolbar_search_layout.xml index 7780a0226..797eea48e 100644 --- a/app/src/main/res/layout/toolbar_search_layout.xml +++ b/app/src/main/res/layout/toolbar_search_layout.xml @@ -4,16 +4,9 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" - android:background="?attr/colorPrimary" - android:focusable="true" - android:focusableInTouchMode="true"> + android:background="?attr/colorPrimary"> - - - + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..a95153a1f Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..bccfaff0e Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..65ced7a8b Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..b7b42cbff Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 000000000..4ce448946 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/values-b+ast/strings.xml b/app/src/main/res/values-b+ast/strings.xml index 58a13dd09..c3bd768df 100644 --- a/app/src/main/res/values-b+ast/strings.xml +++ b/app/src/main/res/values-b+ast/strings.xml @@ -2,7 +2,7 @@ Calca na gueta pa entamar %1$s visiones - Espublizáu\'l %1$s + Espublizóse\'l %1$s Nun s\'alcontró un reproductor de fluxos. ¿Quies instalar VLC? Instalar Encaboxar @@ -14,15 +14,15 @@ ¿Quixesti dicir %1$s? Compartir con Escoyer restolador - rotación - Usar reproductor de videu esternu - Usar reproductor d\'audiu esternu + voltéu + Usar reproductor esternu de videu + Usar reproductor esternu d\'audiu Camín de descarga de vídeos - Camín nel qu\'atroxar los vídeos baxaos. + Camín nel qu\'atroxar los vídeos baxaos Introducir camín de descarga pa vídeos - Camín de descarga d\'audiu + Camín de descarga p\'audios Camín nel qu\'atroxar los audios baxaos. Introducir camín de descarga pa ficheros d\'audiu @@ -30,11 +30,11 @@ Reproducir con Kodi Nun s\'alcontró Kore. ¿Instalalu? Amosar opción «Reproducir con Kodi» - Amuesa una opción pa reproducir un videu per Kodi. + Amuesa una opción pa reproducir un videu per Kodi Audiu - Formatu d\'audiu por defeutu + Formatu por defeutu d\'audiu WebM — formatu llibre - m4a — calidá meyor + M4A — calidá meyor Tema Escuru Claru @@ -43,15 +43,15 @@ Videu siguiente Amosar vídeos siguientes y asemeyaos URL non sofitada - Llingua de conteníu preferíu + Llingua por defeutu del conteníu Videu y audiu Aspeutu Otru Reproduciendo de fondu Reproducir Conteníu - Amosar conteníu restrinxíu pola edá - El videu ta restrinxíu pola edá. Deshabilita esto diendo primero a axustes. + Amosar conteníu torgáu pola edá + El videu ta torgáu pola edá. Desactiva esto diendo primero a axustes. en direuto Descargues Descargues @@ -60,16 +60,16 @@ Fallu Fallu de rede Nun pudieron cargase toles miniatures - Nun pudo descargase la robla de la url del videu. - Nun pudo analizase\'l sitiu web. - Nun pudo analizase dafechu\'l sitiu web. - Conteníu non disponible. - Bloquiáu por GEMA. - Nun pudo configurase\'l menú de descarga. - Esto ye una tresmisión de direuto pero entá nun ta sofitao. - Nun pudo consiguise tresmisión dala. + Nun pudo descifrase la robla de la URL + Nun pudo analizase\'l sitiu web + Nun pudo analizase dafechu\'l sitiu web + Conteníu non disponible + Bloquiáu por GEMA + Nun pudo configurase\'l menú de descarga + Esto ye una tresmisión de direuto qu\'entá nun se sofita. + Nun pudo consiguise tresmisión dala Perdón, eso nun debió asoceder. - Fallu d\'informe per corréu + Informar per corréu del fallu Perdón, asocedieron dellos fallos. INFORMAR Información: @@ -78,11 +78,11 @@ Detalles: - Miniatura de previsualización de videu - Miniatura de previsualización de videu + Miniatura de previsualización del videu + Miniatura de previsualización del videu Préstames Usar Tor - (Esperimental) Forcia\'l tráficu de descargues pente Tor pa más privacidá (la tresmisión de vídeos entá nun ta sofitao). + (Esperimental) Forcia\'l tráficu de descargues pente Tor pa más privacidá (la tresmisión de vídeos entá nun se sofita). Informa d\'un fallu Informe d\'usuariu @@ -92,11 +92,11 @@ Videu Audiu Retentar - Ñegóse l\'accesu al almacenamientu + Ñegóse\'l permisu d\'accesu al almacenamientu Aniciar Posar - Ver + Reproducir Desaniciar Suma de comprobación @@ -110,23 +110,23 @@ URL malformada o internet non disponible Calca pa detallles Espera, por favor… - Copióse al cartafueyu. - Esbilla un direutoriu de descarga disponible, por favor. + Copióse al cartafueyu + Esbilla una carpeta disponible de descarga, por favor - Auto-reproduz un videu al llamar a NewPipe dende otra aplicación. - Auto-reproducir al llamar dende otra aplicación - Miniatura del xubidor + Auto-reproduz un videu al llamar a NewPipe dende otra aplicación + Auto-reproducción + Miniatura del avatar del xubidor Despréstames NewPipe baxando Nun pudo cargase la imaxe Cascó l\'aplicación/IU - + Lo qu\'asocedió:\\nSolicitú:\\nLlingua del conteníu:\\nServiciu:\\nHora GMT:\\nPaquete:\\nVersión:\\nVersión del SO:\\nRangu global d\'IP: Abrir en ventanu emerxente Mou de ventanu emerxente de NewPipe - Formatu preferíu de videu + Formatu por defeutu de videu Prietu Reproduciendo en ventanu emerxente @@ -134,7 +134,7 @@ Canal Más sero - Deshabilitóse + Desactivóse Usar reproductor vieyu @@ -142,26 +142,26 @@ M Mill MMill - Precísase esti permisu pa -abrir en ventanu emerxente + Precísase esti permisu p\'abrir +\nen ventanu emerxente reCAPTCHA Prueba reCAPTCHA - Prueba reCAPTCHA solicitada + Solicitóse la prueba reCAPTCHA Fondu Ventanu emerxente - Resolución por defeutu de ventanu emerxente + Resolución por defeutu del ventanu emerxente Amosar resoluciones más altes - Namái dellos preseos sofiten vídeos en 2k/4k + Namái dellos preseos sofiten vídeos en 2K/4K Peñera Refrescar Llimpiar - Delles resoluciones NUN tendrán audiu al habilitar esta opción - Tamañu y posición del ventanu emerxente - Recuerda la cabera posición y resolución afitada nel ventanu emerxente + Delles resoluciones NUN tendrán audiu al activar esta opción + Recuerdar tamañu y posición del ventanu emerxente + Recuerda la cabera posición y resolución afitaes nel ventanu emerxente Controles per xestos del reproductor Usa xestos pa controlar el brilléu y volume del reproductor Suxerencies de gueta @@ -170,5 +170,87 @@ abrir en ventanu emerxente Ventanu emerxente Redimensionáu - Compilación vieya del reproductor Mediaframework. + Reproductor vieyu integráu de Mediaframework +Soscribise + Soscribiéstite + Desoscribiéstite de la canal + Nun pue camudase la resolución + Nun pue anovase la soscripción + + Principal + Soscripciones + + Qué hai nuevo + + Historial de gueta + Atroxa de mou llocal les solicitúes de gueta + Historial + Fai un siguimientu de los vídeos vistos + Siguir al ganar el focu + Sigue reproduciendo tres les interrupciones (por exemplu, llamaes de teléfonu) + Reproductor + Comportamientu + Historial + Llistáu de reproducción + La meyor resolución + Desfacer + + Avisu de NewPipe + Avisos pa los reproductores de fondu y en ventanu emerxente de NewPipe + + Ensin resultaos + Equí nun hai más que grillos + + Ensin soscriptores + + %s soscriptor + %s soscriptores + + + Ensin visionaos + + %s visionáu + %s visionaos + + + Nun hai vídeos + + %s videu + %s vídeos + + + Descargues + Caráuteres permitíos nos nomes de ficheros + Los caráuteres non válidos tróquense por esti valor + Troquéu de caráuteres + + Lletres y díxitos + La mayoría de caráuteres especiales + + Tocante a NewPipe + Axustes + Tocante a + Llicencies de terceros + © %1$s por %2$s so la %3$s + Nun pudo cargase la llicencia + Abrir sitiu web + Tocante a + Collaboradores + Llicencies + Un frontal llixeru de YouTube p\'Android. + Ver en GitHub + Llicencia de NewPipe + Si tienes idees, quies traducir, facer cambeos, llimpiar el códigu u otres coses, l\'ayuda siempres s\'agradez. ¡Cuánto más se faiga, más s\'ameyora! + Lleer llicencia + Collaboración + + Historial + Guetao + Visto + L\'historial ta desactiváu + Historial + L\'historial ta baleru + Llimpióse l\'historial + Desanicióse l\'elementu + ¿Quies desaniciar esti elementu del historial de gueta? diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d863d8114..0e183f2f8 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -119,7 +119,7 @@ Starten Pause - Ansehen + Abspielen Neue Mission OK Server nicht unterstützt @@ -254,8 +254,9 @@ Die meisten Sonderzeichen Element gelöscht -Fortsetzen beim erneuten fokussieren + Fortsetzen bei erneutem Fokussieren Player Nichts hier außer Grillen - + Möchten Sie dieses Element aus dem Suchverlauf löschen? + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b05b30a75..7d09d85b9 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -72,7 +72,7 @@ Mostrar contenido restringido por edad Vídeo restringido por edad. Permitir este tipo de material es posible desde Ajustes. - Toque buscar para empezar + Toque en buscar para empezar Autoreproducir Reproducir automáticamente un vídeo cuando NewPipe es llamado desde otra aplicación en vivo @@ -259,4 +259,5 @@ abrir en modo popup Elemento eliminado +¿Desea eliminar este elemento del historial de búsqueda? diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 063afe594..f8de02050 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -18,7 +18,7 @@ Paramètres Partager Partager avec - Afficher une option pour lire la vidéo via la médiathèque Kodi + Afficher une option pour lire la vidéo via Kodi Afficher l’option « Lire avec Kodi » Ajoutée le %1$s %1$s vues @@ -256,4 +256,5 @@ Caractères spéciaux Objet effacé +Voulez-vous supprimer cet élément de l\'historique de recherche ? diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 39084cecb..bac01e17f 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -23,7 +23,7 @@ Lokasi untuk menyimpan audio yang diunduh Masukkan lokasi unduhan berkas audio - Putar otomatis ketika dipanggil dari aplikasi lain + Putar otomatis Otomatis memutar video ketika NewPipe dijalankan dari aplikasi lain Resolusi baku Putar dengan Kodi @@ -33,7 +33,7 @@ Audio Format audio baku WebM — format bebas - m4a — kualitas lebih baik + M4A — kualitas lebih baik Tema Gelap Terang @@ -61,15 +61,15 @@ Laporan galat Galat - Tidak bisa mengurai situs web. - Sama sekali tidak bisa mengurai situs web. - Konten tidak tersedia. - Diblokir oleh GEMA. - Tidak bisa menyiapkan menu unduhan. - Ini adalah SIARAN LANGSUNG. Fitur ini belum didukung. + Tidak dapat mengurai situs web + Sama sekali tidak dapat mengurai situs web + Konten tidak tersedia + Diblokir oleh GEMA + Tidak bisa menyiapkan menu unduhan + Ini adalah SIARAN LANGSUNG, yang mana ini belum didukung. Tidak bisa memuat gambar Maaf, hal tersebut seharusnya tidak terjadi. - Lapor galat via surel + Lapor galat via surat elektronik Maaf, telah terjadi galat. LAPOR Info: @@ -92,7 +92,7 @@ Video Audio Ulangi - Izin untuk mengakses penyimpanan ditolak + Izin akses penyimpanan ditolak Hapus Tonton @@ -117,7 +117,7 @@ Tidak ditemukan pemutar stream. Apakah anda ingin memasang VLC? Tidak bisa mendekrip tanda tangan URL video. App/UI rusak - Tidak bisa mendapatkan stream apapun. + Tidak bisa mendapatkan stream apapun Apa:\\nPermintaan:\\nBahasa Konten:\\nLayanan:\\nWaktu GMT:\\nPaket:\\nVersi:\\nVersi OS:\\nIP: Laporan pengguna @@ -145,11 +145,11 @@ Izin ini dibutuhkan untuk membuka di mode popup - Mode Popup NewPipe + Mode popup NewPipe Memutar dalam mode popup Gunakan pemutar lama - Versi lama pemutar Mediaframework. + Versi lama pemutar Mediaframework Dinonaktifkan Pilihan format video @@ -166,7 +166,7 @@ membuka di mode popup Filter Beberapa resolusi TIDAK akan memiliki suara ketika opsi ini diaktifkan Ingat ukuran dan posisi sembulan - Ingat ukuran terakhir dan pengaturan posisi sembulan + Ingat ukuran terakhir dan pengaturan posisi popup Sembulan Ubah ukuran @@ -212,7 +212,7 @@ membuka di mode popup Utama Cari riwayat Simpan pencarian secara lokal - Riwayat tontonan + Riwayat Notifikasi NewPipe Riwayat Riwayat dinonaktifkan diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 30530c73d..0081c9844 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -12,16 +12,16 @@ Impostazioni Intendevi: %1$s ? Condividi con - Scegli browser + Scegli il browser rotazione - Cartella dei video scaricati - Cartella in cui memorizzare i video scaricati. + Percorso dei video scaricati + Percorso in cui memorizzare i video scaricati Inserisci il percorso per i download Risoluzione predefinita Riproduci con Kodi - L\'applicazione Kore non è stata trovata. Vorresti installarla? + L\'applicazione Kore non è stata trovata. Vuoi installarla? Mostra l\'opzione \"Riproduci con Kodi\" - Mostra l\'opzione per riprodurre i video tramite Kodi. + Mostra l\'opzione per riprodurre i video tramite Kodi Audio Formato audio predefinito WebM — formato libero @@ -30,7 +30,7 @@ Prossimo video Mostra video a seguire e video simili URL non supportato - Lingua preferita per i contenuti + Lingua predefinita per i contenuti Video e Audio Anteprima video @@ -40,12 +40,12 @@ Mi piace Impossibile creare la cartella di download \'%1$s\' Creata la cartella per i download \'%1$s\' - Usa un lettore video esterno - Usa un lettore audio esterno + Usa un riproduttore video esterno + Usa un riproduttore audio esterno - Cartella degli audio scaricati - Cartella dove salvare gli audio scaricati. - Inserisci la cartella per i file audio + Percorso degli audio scaricati + Percorso dove salvare gli audio scaricati + Inserisci il percorso per i file audio Tema Scuro @@ -63,26 +63,26 @@ Contenuto non disponibile Bloccato dalla GEMA Usa Tor - (Sperimentale) Forza il traffico in download tramite Tor per una maggiore privacy (lo streaming dei video non è ancora supportato). + (Sperimentale) Forza il traffico in download tramite Tor per una maggiore riservatezza (lo streaming dei video non è ancora supportato). Impossibile analizzare il sito web Impossibile impostare il menù di download - Questo è uno stream dal vivo. Gli stream dal vivo non sono ancora supportati. + Questo è uno stream in diretta, il quale non è ancora supportato. Contenuti Mostra contenuti vincolati all\'età - Questo video è riservato ad un pubblico maturo. Per accedervi, abilita \"Mostra video vincolati all\'età\" nelle impostazioni. + Questo video è riservato ad un pubblico maggiorenne. Per accedervi, abilita \"Mostra video vincolati all\'età\" nelle impostazioni. Tocca \"cerca\" per iniziare - Inizia automaticamente la riproduzione se NewPipe viene aperto da un\'altra app + Riproduzione automatica Riproduci i video automaticamente quando NewPipe viene aperto da un\'altra app in diretta - Impossibile eseguire il parsing completo del sito - Non è stato ottenuto alcuno stream + Impossibile analizzare completamente il sito web + Non è stato ottenuto alcun flusso Ci dispiace, non sarebbe dovuto succedere. Segnala l\'errore via e-mail Ci dispiace, c\'è stato qualche errore. @@ -99,18 +99,18 @@ Video Audio Riprova - È stato negato il permesso di accedere all\'archiviazione di massa + È stato negato il permesso di accesso all\'archiviazione di massa Download Download Segnalazione errori Inizia Pausa - Visualizza + Riproduci Elimina - Checksum + Codice di controllo - Nuova missione + Nuovo obiettivo OK Nome del file @@ -126,7 +126,7 @@ Seleziona una cartella disponibile in cui salvare i download Impossibile caricare l\'immagine - L\'app/UI è andata in crash + L\'app/UI si è interrotta Cosa:\\nRichiesta:\\nLingua contenuto:\\nServizio:\\nOrario GMT:\\nPacchetto:\\nVersione:\\nVersione SO:\\nRange IP glob.: reCAPTCHA @@ -149,26 +149,26 @@ Apri in modalità popup - NewPipe Modo popup + NewPipe in modalità popup Riproduzione in modalità popup Disattivato Usa il vecchio riproduttore -Alcune risoluzioni non avranno audio se questa opzione viene abilitata. +Alcune risoluzioni NON avranno l\'audio se questa opzione viene abilitata In sottofondo Popup - Risoluzione predefinita per il popup + Risoluzione predefinita per la modalità popup Mostra risoluzioni più alte - Solo alcuni dispositivi supportano la riproduzione di video in 2K e 4K. - Formato video preferito + Solo alcuni dispositivi supportano la riproduzione di video in 2K e 4K + Formato video predefinito Ricorda grandezza e posizione del popup Ricorda l\'ultima grandezza e posizione del popup Controlli gestuali - Usa i gesti per controllare luminosità e volume. + Usa i gesti per controllare luminosità e volume Suggerimenti di ricerca - Mostra suggerimenti durante la ricerca. + Mostra i suggerimenti durante la ricerca Popup Filtra i risultati @@ -177,10 +177,10 @@ Ridimensionamento Risoluzione migliore - Precedente riproduttore integrato facente uso di Mediaframework + Precedente riproduttore integrato Mediaframework - Questo permesso è necessario -\nper la modalità popup + Questo permesso è necessario +\nper aprire la modalità popup Impostazioni Informazioni @@ -206,7 +206,7 @@ Iscrizioni - Nuovo + Novità Cronologia ricerche Salva le ricerche localmente @@ -233,10 +233,10 @@ Cronologia cancellata Principale - Player + Riproduttore Comportamento Cronologia - Playlist + Scaletta Annulla Notifiche NewPipe @@ -262,4 +262,7 @@ Elemento eliminato +Nulla da mostrare + + Vuoi eliminare questo elemento dalla cronologia? diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 02fa7cacf..ae4d6e10e 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -253,4 +253,5 @@ Historikken er tom Historikk tømt Element slettet +Ønsker du å slette dette elementet fra søkehistorikken? diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 200c02810..75cebf954 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -104,7 +104,7 @@ Toegang tot opslag geweigerd Begin Pauzeren - Bekijken + Afspelen Verwijderen Controlesom @@ -260,4 +260,5 @@ te openen in pop-upmodus Item verwijderd +Wil je dit item uit je geschiedenis verwijderen? diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 3340d24a6..9123f868c 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -7,7 +7,7 @@ Informações: WebM — formato aberto %1$s visualizações - Ver + Reproduzir Vídeo com restrição de idade. Permissão para vídeos com essa restrição podem ser feitas no menu configurações. Vídeo Reproduzir o vídeo automaticamente quando o NewPipe for aberto a partir de outro app @@ -238,7 +238,8 @@ abrir em modo popup Item excluído -Player +Reprodutor Não há nada aqui - + Deseja apagar este item do seu histórico de busca? + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index c9e138d1e..bb040c28f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -21,18 +21,18 @@ Introduza o caminho para os vídeos Resolução padrão Reproduzir no Kodi - Aplicação não encontrada. Instalar o Kore? + Aplicação Kore não encontrada. Quer instalá-la? Mostrar opção \"Reproduzir no Kodi\" Mostra uma opção para reproduzir o vídeo no Kodi Áudio Formato áudio padrão WebM — formato livre - m4a — melhor qualidade + M4A — melhor qualidade Transferir Vídeo seguinte Mostrar vídeos seguintes e semelhantes URL não suportado - Idioma preferido do conteúdo + Idioma predefinido do conteúdo Vídeo e áudio Miniatura de vídeos @@ -60,19 +60,19 @@ Diretório \'%1$s\' criado com sucesso Erro Incapaz de carregar todas as miniaturas - Incapaz de decodificar a assinatura do vídeo. - Incapaz de processar o sítio web. - Conteúdo não disponível. - Bloqueado pela GEMA. + Incapaz de decodificar a assinatura do vídeo + Incapaz de processar o site + Conteúdo não disponível + Bloqueado pela GEMA Conteúdo Restringir conteúdo por idade - O vídeo está restrito por idade. Ative a restrição de vídeos por idade nas definições. + Vídeo com restrição de idade. É possível permitir este material através das Definições. - Não foi possível processar o sítio web. - Não foi possível configurar o menu de transferências. - Esta é uma EMISSÃO EM DIRETO. Estas emissões ainda não são suportadas. - Não foi possível obter a emissão. + Não foi possível processar totalmente o site + Não foi possível configurar o menu de transferências + Esta é uma EMISSÃO EM DIRETO, as quais ainda não são suportadas. + Não foi possível obter a emissão Desculpe, isto não deveria ter acontecido. Reportar erro por e-mail Ocorreram alguns erros. @@ -86,9 +86,9 @@ Vídeo Áudio Tentar novamente - Não foi concedida permissão para aceder ao armazenamento + Permissão para aceder ao armazenamento foi negada Toque para iniciar a pesquisa - Reproduzir se invocado por outra aplicação + Reprodução automática Reproduzir vídeo automaticamente se o NewPipe for invocado por outra aplicação direto @@ -101,7 +101,7 @@ Iniciar Pausa - Ver + Reproduzir Apagar Checksum @@ -115,8 +115,8 @@ URL inválido ou Internet não disponível Toque para detalhes Por favor aguarde… - Copiado para a área de transferência. - Por favor selecione um diretório disponível. + Copiado para a área de transferência + Por favor selecione a pasta para as descargas OK Processos @@ -145,11 +145,11 @@ o modo “popup“ Desafio reCAPTCHA Desafio reCAPTCHA solicitado - Modo popup de NewPipe + Modo popup do NewPipe Reproduzir em modo de popup Usar reprodutor antigo - Versão antiga no reprodutor Mediaframework. + Versão antiga do reprodutor Mediaframework Formato de vídeo preferido Desativado @@ -186,10 +186,74 @@ o modo “popup“ Sobre Colaboradores Licenças - Aplicação leve, simples e grátis de Youtube para Android. - Ver no Github + Aplicação leve, simples e grátis de YouTube para Android. + Ver no GitHub Licença do NewPipe - 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! + 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! Ler licença Contribuição +Subscrever + Subscrito + Canal não subscrito + Incapaz de alterar a subscrição + Incapaz de atualizar a subscrição + + Principal + Subscrições + + O que há de novo + + Histórico de Pesquisa + Armazenar termos de pesquisa localmente + Histórico + Armazenar histórico de vídeos assistidos + Retomar ao ganhar foco + Continuar reprodução após interrupções (ex. chamadas) + Reprodutor + Comportamento + Histórico + Lista de Reprodução + Desfazer + + Notificação do NewPipe + Notificações do NewPipe e para reprodutores pop-up + + Sem resultados + Aqui não há nada para ver + + Sem subscritores + + %s subscrito + %s subscritos + + + Sem visualizações + + %s visualização + %s visualizações + + + Sem vídeos + + %s vídeo + %s vídeos + + + Download + Caracteres permitidos em nomes de ficheiros + Caracteres inválidos são substituídos por este valor + Carácter de substituição + + Letras e dígitos + Caracteres especiais + + Histórico + Pesquisado + Visto + Histórico está desativado + Histórico + O histórico está vazio + Histórico eliminado + Item apagado +Deseja apagar este item do histórico de pesquisa? diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 115f7eaf7..e60ec9917 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -96,10 +96,10 @@ Это прямая трансляция, они пока не поддерживаются. Не удалось загрузить изображение "Падение приложения/пользовательского интерфейса " - Простите, такое не должно было произойти. + Простите, это не должно было произойти. Отправить отчёт об ошибке по электронной почте Простите, произошли ошибки. - ОТЧЕТ + ОТЧЁТ Информация: Что произошло: Детали: @@ -237,7 +237,7 @@ %s подписчик %s подписчика %s подписчиков - + Нет просмотров @@ -245,7 +245,7 @@ %s просмотр %s просмотра %s просмотров - + Нет видео @@ -253,7 +253,9 @@ %s видео %s видео %s видео - + - + Элемент удалён +Удалить этот элемент из истории поиска? + diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 3bd31c4c5..798063738 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -102,7 +102,7 @@ Začnite z iskanjem Začni Premor - Poglej + Predvajaj Izbriši Nadzorna vsota @@ -272,4 +272,5 @@ odpiranje v pojavnem načinu Predmet je izbrisan +Ali želite izbrisati predmet iz zgodovine iskanja? diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 26fc272ff..9bb47a7c7 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -100,7 +100,7 @@ Аутоматско пуштање видеа по позиву друге апликације Почни Паузирај - Приказ + Пусти Обриши Хеш diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fbedb9cc6..d56ff5e11 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -97,7 +97,7 @@ Başlat Duraklat - Görünüm + Oynat Sil Sağlama @@ -255,4 +255,5 @@ Geçmiş boş Geçmiş temizlendi Öge silindi +Bu içeriği arama geçmişinden silmek istiyor musunuz? diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 429cbda0d..3f9865c47 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -27,7 +27,7 @@ 顯示「用 Kodi 播放」的選項 預設音訊格式 WebM — 開放格式 - m4a — 更佳畫質 + M4A — 更佳畫質 主題 暗色系 明亮色系 @@ -36,7 +36,7 @@ 下一部影片 顯示下一部和相關的影片 不支援此網址 - 內容語言 + 預設內容語言 影片和音訊 外觀 其他 @@ -62,7 +62,7 @@ 勾選後,部分解析度可能不會有音訊 NewPipe 懸浮視窗模式 背景 - 從其他應用程式開啟時自動播放 + 自動播放 當 NewPipe 被其他應用程式呼叫時自動播放影片 懸浮視窗預設解析度 顯示更高的解析度 @@ -97,14 +97,14 @@ 重新設定大小 錯誤 無法載入所有縮圖 - 無法解析影片 URL 簽名。 - 無法解析網站。 - 無法完全解析網站。 - 內容無法使用。 - 已被 GEMA 阻擋。 - 無法設定下載選單。 + 無法解析影片 URL 簽章 + 無法解析網站 + 無法完全解析網站 + 內容無法使用 + 已被 GEMA 阻擋 + 無法設定下載選單 尚未支援現場直播。 - 無法取得串流。 + 無法取得串流 無法載入圖片 應用程式或 UI 已停止運作 抱歉,這不應該發生的。 @@ -126,7 +126,7 @@ 重試 無法存取儲存空間 使用舊的播放器 - 舊型內建媒體播放器。 + 舊型內建媒體播放器 @@ -135,7 +135,7 @@ 開始 暫停 - 檢視 + 播放 刪除 檢查碼 @@ -151,8 +151,8 @@ NewPipe 下載中 輕觸顯示詳細資訊 請稍候… - 已複製至剪貼簿。 - 請選擇下載資料夾。 + 已複製至剪貼簿 + 請選擇下載資料夾 使用懸浮視窗模式需要此權限 reCAPTCHA 驗證 @@ -162,4 +162,82 @@ 懸浮視窗 現場直播 - + 訂閱 + 已訂閱 + 已取消訂閱頻道 + 無法切換訂閱 + 無法更新訂閱 + + 主選單 + 訂閱項目 + + 有什麼新鮮事 + + 搜尋紀錄 + 在本機儲存搜尋紀錄 + 歷史紀錄 + 記錄觀看過的影片 + 在取得視窗焦點時繼續播放 + 在干擾結束後繼續播放(例如有來電) + 播放器 + 行為 + 歷史紀錄 + 播放清單 + 復原 + + NewPipe 通知 + NewPipe 背景播放與懸浮模式播放器的通知 + + 沒有結果 + 空空如也 + + 無訂閱者 + + %s 位訂閱者 + + + 無觀看次數 + + %s 次觀看 + + + 沒有影片 + + %s 部影片 + + + 下載 + 檔案名稱中允許的字元 + 不符合設定的字元將會被替換為此字串 + 替換為 + + 字母與數字 + 大部分的特殊字元 + + 關於 NewPipe + 設定 + 關於 + 第三方授權 + © %1$s 由 %2$s 使用 %3$s 授權條款發佈 + 無法載入授權條款 + 開啟網站 + 關於 + 貢獻者 + 授權條款 + 一款在 Android 上免費輕巧的 YouTube 前端。 + 在 GitHub 上檢視 + NewPipe 使用的授權條款 + 不管你有什麼點子,翻譯、設計、程式碼整理,或者程式碼撰寫,我們永遠歡迎你來幫忙。完成的越多,NewPipe 也會更好! + 閱讀授權條款 + 貢獻 + + 歷史紀錄 + 已搜尋 + 已觀看 + 歷史紀錄已被停用 + 歷史紀錄 + 沒有歷史紀錄 + 已清除歷史紀錄 + 項目已刪除 + 確定要刪除此項搜尋紀錄嗎? + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..1633e193a --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #CD201F + diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index c779e8216..76cd10681 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -51,7 +51,7 @@ MPEG-4 WebM - 3GPP + 3GP @string/video_mp4_key diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5b3993d8..e798e62e9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -194,7 +194,7 @@ Start Pause - View + Play Delete Checksum @@ -265,4 +265,5 @@ The history is empty History cleared Item deleted + Do you want to delete this item from search history? diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 6663177e6..fa37f0e5d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -109,23 +109,47 @@ @color/video_overlay_color - - - + + + + + + + + + diff --git a/app/src/test/java/us/shandian/giga/get/get/DownloadManagerImplTest.java b/app/src/test/java/us/shandian/giga/get/DownloadManagerImplTest.java similarity index 84% rename from app/src/test/java/us/shandian/giga/get/get/DownloadManagerImplTest.java rename to app/src/test/java/us/shandian/giga/get/DownloadManagerImplTest.java index a7242ba10..6ff702273 100644 --- a/app/src/test/java/us/shandian/giga/get/get/DownloadManagerImplTest.java +++ b/app/src/test/java/us/shandian/giga/get/DownloadManagerImplTest.java @@ -1,4 +1,4 @@ -package us.shandian.giga.get.get; +package us.shandian.giga.get; import org.junit.Ignore; import org.junit.Test; @@ -153,4 +153,34 @@ public class DownloadManagerImplTest { assertSame(missions.get(1), downloadManager.getMission(1)); } + @Test + public void sortByTimestamp() throws Exception { + ArrayList downloadMissions = new ArrayList<>(); + DownloadMission mission = new DownloadMission(); + mission.timestamp = 0; + + DownloadMission mission1 = new DownloadMission(); + mission1.timestamp = Integer.MAX_VALUE + 1L; + + DownloadMission mission2 = new DownloadMission(); + mission2.timestamp = 2L * Integer.MAX_VALUE ; + + DownloadMission mission3 = new DownloadMission(); + mission3.timestamp = 2L * Integer.MAX_VALUE + 5L; + + + downloadMissions.add(mission3); + downloadMissions.add(mission1); + downloadMissions.add(mission2); + downloadMissions.add(mission); + + + DownloadManagerImpl.sortByTimestamp(downloadMissions); + + assertEquals(mission, downloadMissions.get(0)); + assertEquals(mission1, downloadMissions.get(1)); + assertEquals(mission2, downloadMissions.get(2)); + assertEquals(mission3, downloadMissions.get(3)); + } + } \ No newline at end of file diff --git a/assets/BETA_new_pipe_icon_5.svg b/assets/BETA_new_pipe_icon_5.svg new file mode 100644 index 000000000..9406f2469 --- /dev/null +++ b/assets/BETA_new_pipe_icon_5.svg @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + BETA + + diff --git a/assets/16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh.png b/assets/bitcoin_qr_code.png similarity index 100% rename from assets/16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh.png rename to assets/bitcoin_qr_code.png diff --git a/assets/bountysource_qr_code.png b/assets/bountysource_qr_code.png new file mode 100644 index 000000000..4fe03236a Binary files /dev/null and b/assets/bountysource_qr_code.png differ diff --git a/build.gradle b/build.gradle index 52a96908f..5c494fe59 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ buildscript { repositories { jcenter() + google() } dependencies { classpath 'com.android.tools.build:gradle:2.3.3' @@ -16,7 +17,7 @@ allprojects { repositories { jcenter() maven { url 'https://jitpack.io' } - maven { url 'https://maven.google.com' } + google() maven { url 'https://clojars.org/repo' } } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8..ed88a042a 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f3ed0a0af..74bb77845 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Mar 07 14:05:42 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip diff --git a/gradlew b/gradlew index 91a7e269e..cccdd3d51 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -6,47 +6,6 @@ ## ############################################################################## -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn ( ) { - echo "$*" -} - -die ( ) { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; -esac - -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" @@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do fi done SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- +cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" -cd "$SAVED" >&- +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730b..e95643d6a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/screenshots/screenshot_1.png b/screenshots/screenshot_1.png deleted file mode 100644 index f2fd046bc..000000000 Binary files a/screenshots/screenshot_1.png and /dev/null differ diff --git a/screenshots/screenshot_2.png b/screenshots/screenshot_2.png deleted file mode 100644 index 6330e420d..000000000 Binary files a/screenshots/screenshot_2.png and /dev/null differ diff --git a/screenshots/screenshot_3.png b/screenshots/screenshot_3.png deleted file mode 100644 index e6477b2cc..000000000 Binary files a/screenshots/screenshot_3.png and /dev/null differ diff --git a/screenshots/screenshot_4.png b/screenshots/screenshot_4.png deleted file mode 100644 index ea9d8b1c7..000000000 Binary files a/screenshots/screenshot_4.png and /dev/null differ diff --git a/screenshots/screenshot_5.png b/screenshots/screenshot_5.png deleted file mode 100644 index c8ddb9fa7..000000000 Binary files a/screenshots/screenshot_5.png and /dev/null differ diff --git a/screenshots/screenshot_6.png b/screenshots/screenshot_6.png deleted file mode 100644 index d4d5122eb..000000000 Binary files a/screenshots/screenshot_6.png and /dev/null differ diff --git a/screenshots/screenshot_7.png b/screenshots/screenshot_7.png deleted file mode 100644 index 9a1e71385..000000000 Binary files a/screenshots/screenshot_7.png and /dev/null differ diff --git a/screenshots/screenshot_8.png b/screenshots/screenshot_8.png deleted file mode 100644 index 3e73bb358..000000000 Binary files a/screenshots/screenshot_8.png and /dev/null differ diff --git a/screenshots/screenshot_9.png b/screenshots/screenshot_9.png deleted file mode 100644 index d76c53f73..000000000 Binary files a/screenshots/screenshot_9.png and /dev/null differ diff --git a/screenshots/shot_1.png b/screenshots/shot_1.png new file mode 100644 index 000000000..986f201a1 Binary files /dev/null and b/screenshots/shot_1.png differ diff --git a/screenshots/shot_10.png b/screenshots/shot_10.png new file mode 100644 index 000000000..7e840d6dd Binary files /dev/null and b/screenshots/shot_10.png differ diff --git a/screenshots/shot_2.png b/screenshots/shot_2.png new file mode 100644 index 000000000..10c70b54d Binary files /dev/null and b/screenshots/shot_2.png differ diff --git a/screenshots/shot_3.png b/screenshots/shot_3.png new file mode 100644 index 000000000..7e3af77c1 Binary files /dev/null and b/screenshots/shot_3.png differ diff --git a/screenshots/shot_4.png b/screenshots/shot_4.png new file mode 100644 index 000000000..cdaf96153 Binary files /dev/null and b/screenshots/shot_4.png differ diff --git a/screenshots/shot_5.png b/screenshots/shot_5.png new file mode 100644 index 000000000..64c0a164d Binary files /dev/null and b/screenshots/shot_5.png differ diff --git a/screenshots/shot_6.png b/screenshots/shot_6.png new file mode 100644 index 000000000..8a7af3c16 Binary files /dev/null and b/screenshots/shot_6.png differ diff --git a/screenshots/shot_7.png b/screenshots/shot_7.png new file mode 100644 index 000000000..d6a9b1ce2 Binary files /dev/null and b/screenshots/shot_7.png differ diff --git a/screenshots/shot_8.png b/screenshots/shot_8.png new file mode 100644 index 000000000..5fe620d00 Binary files /dev/null and b/screenshots/shot_8.png differ diff --git a/screenshots/shot_9.png b/screenshots/shot_9.png new file mode 100644 index 000000000..b4d51b7e0 Binary files /dev/null and b/screenshots/shot_9.png differ